1 line
20 KiB
Plaintext
1 line
20 KiB
Plaintext
{"version":3,"file":"async-directive.js","sources":["../../src/async-directive.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * Overview:\n *\n * This module is designed to add support for an async `setValue` API and\n * `disconnected` callback to directives with the least impact on the core\n * runtime or payload when that feature is not used.\n *\n * The strategy is to introduce a `AsyncDirective` subclass of\n * `Directive` that climbs the \"parent\" tree in its constructor to note which\n * branches of lit-html's \"logical tree\" of data structures contain such\n * directives and thus need to be crawled when a subtree is being cleared (or\n * manually disconnected) in order to run the `disconnected` callback.\n *\n * The \"nodes\" of the logical tree include Parts, TemplateInstances (for when a\n * TemplateResult is committed to a value of a ChildPart), and Directives; these\n * all implement a common interface called `DisconnectableChild`. Each has a\n * `_$parent` reference which is set during construction in the core code, and a\n * `_$disconnectableChildren` field which is initially undefined.\n *\n * The sparse tree created by means of the `AsyncDirective` constructor\n * crawling up the `_$parent` tree and placing a `_$disconnectableChildren` Set\n * on each parent that includes each child that contains a\n * `AsyncDirective` directly or transitively via its children. In order to\n * notify connection state changes and disconnect (or reconnect) a tree, the\n * `_$notifyConnectionChanged` API is patched onto ChildParts as a directive\n * climbs the parent tree, which is called by the core when clearing a part if\n * it exists. When called, that method iterates over the sparse tree of\n * Set<DisconnectableChildren> built up by AsyncDirectives, and calls\n * `_$notifyDirectiveConnectionChanged` on any directives that are encountered\n * in that tree, running the required callbacks.\n *\n * A given \"logical tree\" of lit-html data-structures might look like this:\n *\n * ChildPart(N1) _$dC=[D2,T3]\n * ._directive\n * AsyncDirective(D2)\n * ._value // user value was TemplateResult\n * TemplateInstance(T3) _$dC=[A4,A6,N10,N12]\n * ._$parts[]\n * AttributePart(A4) _$dC=[D5]\n * ._directives[]\n * AsyncDirective(D5)\n * AttributePart(A6) _$dC=[D7,D8]\n * ._directives[]\n * AsyncDirective(D7)\n * Directive(D8) _$dC=[D9]\n * ._directive\n * AsyncDirective(D9)\n * ChildPart(N10) _$dC=[D11]\n * ._directive\n * AsyncDirective(D11)\n * ._value\n * string\n * ChildPart(N12) _$dC=[D13,N14,N16]\n * ._directive\n * AsyncDirective(D13)\n * ._value // user value was iterable\n * Array<ChildPart>\n * ChildPart(N14) _$dC=[D15]\n * ._value\n * string\n * ChildPart(N16) _$dC=[D17,T18]\n * ._directive\n * AsyncDirective(D17)\n * ._value // user value was TemplateResult\n * TemplateInstance(T18) _$dC=[A19,A21,N25]\n * ._$parts[]\n * AttributePart(A19) _$dC=[D20]\n * ._directives[]\n * AsyncDirective(D20)\n * AttributePart(A21) _$dC=[22,23]\n * ._directives[]\n * AsyncDirective(D22)\n * Directive(D23) _$dC=[D24]\n * ._directive\n * AsyncDirective(D24)\n * ChildPart(N25) _$dC=[D26]\n * ._directive\n * AsyncDirective(D26)\n * ._value\n * string\n *\n * Example 1: The directive in ChildPart(N12) updates and returns `nothing`. The\n * ChildPart will _clear() itself, and so we need to disconnect the \"value\" of\n * the ChildPart (but not its directive). In this case, when `_clear()` calls\n * `_$notifyConnectionChanged()`, we don't iterate all of the\n * _$disconnectableChildren, rather we do a value-specific disconnection: i.e.\n * since the _value was an Array<ChildPart> (because an iterable had been\n * committed), we iterate the array of ChildParts (N14, N16) and run\n * `setConnected` on them (which does recurse down the full tree of\n * `_$disconnectableChildren` below it, and also removes N14 and N16 from N12's\n * `_$disconnectableChildren`). Once the values have been disconnected, we then\n * check whether the ChildPart(N12)'s list of `_$disconnectableChildren` is empty\n * (and would remove it from its parent TemplateInstance(T3) if so), but since\n * it would still contain its directive D13, it stays in the disconnectable\n * tree.\n *\n * Example 2: In the course of Example 1, `setConnected` will reach\n * ChildPart(N16); in this case the entire part is being disconnected, so we\n * simply iterate all of N16's `_$disconnectableChildren` (D17,T18) and\n * recursively run `setConnected` on them. Note that we only remove children\n * from `_$disconnectableChildren` for the top-level values being disconnected\n * on a clear; doing this bookkeeping lower in the tree is wasteful since it's\n * all being thrown away.\n *\n * Example 3: If the LitElement containing the entire tree above becomes\n * disconnected, it will run `childPart.setConnected()` (which calls\n * `childPart._$notifyConnectionChanged()` if it exists); in this case, we\n * recursively run `setConnected()` over the entire tree, without removing any\n * children from `_$disconnectableChildren`, since this tree is required to\n * re-connect the tree, which does the same operation, simply passing\n * `isConnected: true` down the tree, signaling which callback to run.\n */\n\nimport {AttributePart, ChildPart, Disconnectable, Part} from './lit-html.js';\nimport {isSingleExpression} from './directive-helpers.js';\nimport {Directive, PartInfo, PartType} from './directive.js';\nexport * from './directive.js';\n\nconst DEV_MODE = true;\n\n/**\n * Recursively walks down the tree of Parts/TemplateInstances/Directives to set\n * the connected state of directives and run `disconnected`/ `reconnected`\n * callbacks.\n *\n * @return True if there were children to disconnect; false otherwise\n */\nconst notifyChildrenConnectedChanged = (\n parent: Disconnectable,\n isConnected: boolean\n): boolean => {\n const children = parent._$disconnectableChildren;\n if (children === undefined) {\n return false;\n }\n for (const obj of children) {\n // The existence of `_$notifyDirectiveConnectionChanged` is used as a \"brand\" to\n // disambiguate AsyncDirectives from other DisconnectableChildren\n // (as opposed to using an instanceof check to know when to call it); the\n // redundancy of \"Directive\" in the API name is to avoid conflicting with\n // `_$notifyConnectionChanged`, which exists `ChildParts` which are also in\n // this list\n // Disconnect Directive (and any nested directives contained within)\n // This property needs to remain unminified.\n (obj as AsyncDirective)['_$notifyDirectiveConnectionChanged']?.(\n isConnected,\n false\n );\n // Disconnect Part/TemplateInstance\n notifyChildrenConnectedChanged(obj, isConnected);\n }\n return true;\n};\n\n/**\n * Removes the given child from its parent list of disconnectable children, and\n * if the parent list becomes empty as a result, removes the parent from its\n * parent, and so forth up the tree when that causes subsequent parent lists to\n * become empty.\n */\nconst removeDisconnectableFromParent = (obj: Disconnectable) => {\n let parent, children;\n do {\n if ((parent = obj._$parent) === undefined) {\n break;\n }\n children = parent._$disconnectableChildren!;\n children.delete(obj);\n obj = parent;\n } while (children?.size === 0);\n};\n\nconst addDisconnectableToParent = (obj: Disconnectable) => {\n // Climb the parent tree, creating a sparse tree of children needing\n // disconnection\n for (let parent; (parent = obj._$parent); obj = parent) {\n let children = parent._$disconnectableChildren;\n if (children === undefined) {\n parent._$disconnectableChildren = children = new Set();\n } else if (children.has(obj)) {\n // Once we've reached a parent that already contains this child, we\n // can short-circuit\n break;\n }\n children.add(obj);\n installDisconnectAPI(parent);\n }\n};\n\n/**\n * Changes the parent reference of the ChildPart, and updates the sparse tree of\n * Disconnectable children accordingly.\n *\n * Note, this method will be patched onto ChildPart instances and called from\n * the core code when parts are moved between different parents.\n */\nfunction reparentDisconnectables(this: ChildPart, newParent: Disconnectable) {\n if (this._$disconnectableChildren !== undefined) {\n removeDisconnectableFromParent(this);\n this._$parent = newParent;\n addDisconnectableToParent(this);\n } else {\n this._$parent = newParent;\n }\n}\n\n/**\n * Sets the connected state on any directives contained within the committed\n * value of this part (i.e. within a TemplateInstance or iterable of\n * ChildParts) and runs their `disconnected`/`reconnected`s, as well as within\n * any directives stored on the ChildPart (when `valueOnly` is false).\n *\n * `isClearingValue` should be passed as `true` on a top-level part that is\n * clearing itself, and not as a result of recursively disconnecting directives\n * as part of a `clear` operation higher up the tree. This both ensures that any\n * directive on this ChildPart that produced a value that caused the clear\n * operation is not disconnected, and also serves as a performance optimization\n * to avoid needless bookkeeping when a subtree is going away; when clearing a\n * subtree, only the top-most part need to remove itself from the parent.\n *\n * `fromPartIndex` is passed only in the case of a partial `_clear` running as a\n * result of truncating an iterable.\n *\n * Note, this method will be patched onto ChildPart instances and called from the\n * core code when parts are cleared or the connection state is changed by the\n * user.\n */\nfunction notifyChildPartConnectedChanged(\n this: ChildPart,\n isConnected: boolean,\n isClearingValue = false,\n fromPartIndex = 0\n) {\n const value = this._$committedValue;\n const children = this._$disconnectableChildren;\n if (children === undefined || children.size === 0) {\n return;\n }\n if (isClearingValue) {\n if (Array.isArray(value)) {\n // Iterable case: Any ChildParts created by the iterable should be\n // disconnected and removed from this ChildPart's disconnectable\n // children (starting at `fromPartIndex` in the case of truncation)\n for (let i = fromPartIndex; i < value.length; i++) {\n notifyChildrenConnectedChanged(value[i], false);\n removeDisconnectableFromParent(value[i]);\n }\n } else if (value != null) {\n // TemplateInstance case: If the value has disconnectable children (will\n // only be in the case that it is a TemplateInstance), we disconnect it\n // and remove it from this ChildPart's disconnectable children\n notifyChildrenConnectedChanged(value as Disconnectable, false);\n removeDisconnectableFromParent(value as Disconnectable);\n }\n } else {\n notifyChildrenConnectedChanged(this, isConnected);\n }\n}\n\n/**\n * Patches disconnection API onto ChildParts.\n */\nconst installDisconnectAPI = (obj: Disconnectable) => {\n if ((obj as ChildPart).type == PartType.CHILD) {\n (obj as ChildPart)._$notifyConnectionChanged ??=\n notifyChildPartConnectedChanged;\n (obj as ChildPart)._$reparentDisconnectables ??= reparentDisconnectables;\n }\n};\n\n/**\n * An abstract `Directive` base class whose `disconnected` method will be\n * called when the part containing the directive is cleared as a result of\n * re-rendering, or when the user calls `part.setConnected(false)` on\n * a part that was previously rendered containing the directive (as happens\n * when e.g. a LitElement disconnects from the DOM).\n *\n * If `part.setConnected(true)` is subsequently called on a\n * containing part, the directive's `reconnected` method will be called prior\n * to its next `update`/`render` callbacks. When implementing `disconnected`,\n * `reconnected` should also be implemented to be compatible with reconnection.\n *\n * Note that updates may occur while the directive is disconnected. As such,\n * directives should generally check the `this.isConnected` flag during\n * render/update to determine whether it is safe to subscribe to resources\n * that may prevent garbage collection.\n */\nexport abstract class AsyncDirective extends Directive {\n // As opposed to other Disconnectables, AsyncDirectives always get notified\n // when the RootPart connection changes, so the public `isConnected`\n // is a locally stored variable initialized via its part's getter and synced\n // via `_$notifyDirectiveConnectionChanged`. This is cheaper than using\n // the _$isConnected getter, which has to look back up the tree each time.\n /**\n * The connection state for this Directive.\n */\n isConnected!: boolean;\n\n // @internal\n override _$disconnectableChildren?: Set<Disconnectable> = undefined;\n /**\n * Initialize the part with internal fields\n * @param part\n * @param parent\n * @param attributeIndex\n */\n override _$initialize(\n part: Part,\n parent: Disconnectable,\n attributeIndex: number | undefined\n ) {\n super._$initialize(part, parent, attributeIndex);\n addDisconnectableToParent(this);\n this.isConnected = part._$isConnected;\n }\n // This property needs to remain unminified.\n /**\n * Called from the core code when a directive is going away from a part (in\n * which case `shouldRemoveFromParent` should be true), and from the\n * `setChildrenConnected` helper function when recursively changing the\n * connection state of a tree (in which case `shouldRemoveFromParent` should\n * be false).\n *\n * @param isConnected\n * @param isClearingDirective - True when the directive itself is being\n * removed; false when the tree is being disconnected\n * @internal\n */\n override ['_$notifyDirectiveConnectionChanged'](\n isConnected: boolean,\n isClearingDirective = true\n ) {\n if (isConnected !== this.isConnected) {\n this.isConnected = isConnected;\n if (isConnected) {\n this.reconnected?.();\n } else {\n this.disconnected?.();\n }\n }\n if (isClearingDirective) {\n notifyChildrenConnectedChanged(this, isConnected);\n removeDisconnectableFromParent(this);\n }\n }\n\n /**\n * Sets the value of the directive's Part outside the normal `update`/`render`\n * lifecycle of a directive.\n *\n * This method should not be called synchronously from a directive's `update`\n * or `render`.\n *\n * @param directive The directive to update\n * @param value The value to set\n */\n setValue(value: unknown) {\n if (isSingleExpression(this.__part as unknown as PartInfo)) {\n this.__part._$setValue(value, this);\n } else {\n // this.__attributeIndex will be defined in this case, but\n // assert it in dev mode\n if (DEV_MODE && this.__attributeIndex === undefined) {\n throw new Error(`Expected this.__attributeIndex to be a number`);\n }\n const newValues = [...(this.__part._$committedValue as Array<unknown>)];\n newValues[this.__attributeIndex!] = value;\n (this.__part as AttributePart)._$setValue(newValues, this, 0);\n }\n }\n\n /**\n * User callbacks for implementing logic to release any resources/subscriptions\n * that may have been retained by this directive. Since directives may also be\n * re-connected, `reconnected` should also be implemented to restore the\n * working state of the directive prior to the next render.\n */\n protected disconnected() {}\n protected reconnected() {}\n}\n"],"names":[],"mappings":"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"} |