{"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":["notifyChildrenConnectedChanged","parent","isConnected","children","_$disconnectableChildren","undefined","obj","_b","_a","removeDisconnectableFromParent","_$parent","delete","size","addDisconnectableToParent","Set","has","add","installDisconnectAPI","reparentDisconnectables","newParent","this","notifyChildPartConnectedChanged","isClearingValue","fromPartIndex","value","_$committedValue","Array","isArray","i","length","type","PartType","CHILD","_c","_$notifyConnectionChanged","_d","_$reparentDisconnectables","AsyncDirective","Directive","constructor","_$initialize","part","attributeIndex","super","_$isConnected","_$AO","isClearingDirective","reconnected","call","disconnected","setValue","isSingleExpression","__part","_$setValue","newValues","__attributeIndex"],"mappings":";;;;;GAsIA,MAAMA,EAAiC,CACrCC,EACAC,aAEA,MAAMC,EAAWF,EAAOG,KACxB,QAAiBC,IAAbF,EACF,OAAO,EAET,IAAK,MAAMG,KAAOH,UAShBI,GAAAC,EAACF,GAA2D,4BAC1DJ,GACA,GAGFF,EAA+BM,EAAKJ,GAEtC,OAAO,CAAI,EASPO,EAAkCH,IACtC,IAAIL,EAAQE,EACZ,EAAG,CACD,QAAgCE,KAA3BJ,EAASK,EAAII,MAChB,MAEFP,EAAWF,EAAOG,KAClBD,EAASQ,OAAOL,GAChBA,EAAML,QACoB,KAAnBE,aAAQ,EAARA,EAAUS,MAAY,EAG3BC,EAA6BP,IAGjC,IAAK,IAAIL,EAASA,EAASK,EAAII,KAAWJ,EAAML,EAAQ,CACtD,IAAIE,EAAWF,EAAOG,KACtB,QAAiBC,IAAbF,EACFF,EAAOG,KAA2BD,EAAW,IAAIW,SAC5C,GAAIX,EAASY,IAAIT,GAGtB,MAEFH,EAASa,IAAIV,GACbW,EAAqBhB,EACtB,GAUH,SAASiB,EAAyCC,QACVd,IAAlCe,KAAKhB,MACPK,EAA+BW,MAC/BA,KAAKV,KAAWS,EAChBN,EAA0BO,OAE1BA,KAAKV,KAAWS,CAEpB,CAuBA,SAASE,EAEPnB,EACAoB,GAAkB,EAClBC,EAAgB,GAEhB,MAAMC,EAAQJ,KAAKK,KACbtB,EAAWiB,KAAKhB,KACtB,QAAiBC,IAAbF,GAA4C,IAAlBA,EAASS,KAGvC,GAAIU,EACF,GAAII,MAAMC,QAAQH,GAIhB,IAAK,IAAII,EAAIL,EAAeK,EAAIJ,EAAMK,OAAQD,IAC5C5B,EAA+BwB,EAAMI,IAAI,GACzCnB,EAA+Be,EAAMI,SAErB,MAATJ,IAITxB,EAA+BwB,GAAyB,GACxDf,EAA+Be,SAGjCxB,EAA+BoB,KAAMlB,EAEzC,CAKA,MAAMe,EAAwBX,gBACvBA,EAAkBwB,MAAQC,EAASC,gBACtCxB,GAAAyB,EAAC3B,GAAkB4B,sBAAAA,KACjBb,WACFd,GAAA4B,EAAC7B,GAAkB8B,sBAAAA,KAA8BlB,GAClD,EAoBG,MAAgBmB,UAAuBC,EAA7CC,kCAYWnB,KAAwBhB,UAAyBC,CAgF3D,CAzEUmC,KACPC,EACAxC,EACAyC,GAEAC,MAAMH,KAAaC,EAAMxC,EAAQyC,GACjC7B,EAA0BO,MAC1BA,KAAKlB,YAAcuC,EAAKG,IACzB,CAcQC,KACP3C,EACA4C,GAAsB,WAElB5C,IAAgBkB,KAAKlB,cACvBkB,KAAKlB,YAAcA,EACfA,EACc,QAAhBM,EAAAY,KAAK2B,mBAAW,IAAAvC,GAAAA,EAAAwC,KAAA5B,MAEC,QAAjBb,EAAAa,KAAK6B,oBAAY,IAAA1C,GAAAA,EAAAyC,KAAA5B,OAGjB0B,IACF9C,EAA+BoB,KAAMlB,GACrCO,EAA+BW,MAElC,CAYD8B,SAAS1B,GACP,GAAI2B,EAAmB/B,KAAKgC,MAC1BhC,KAAKgC,KAAOC,KAAW7B,EAAOJ,UACzB,CAML,MAAMkC,EAAY,IAAKlC,KAAKgC,KAAO3B,MACnC6B,EAAUlC,KAAKmC,MAAqB/B,EACnCJ,KAAKgC,KAAyBC,KAAWC,EAAWlC,KAAM,EAC5D,CACF,CAQS6B,eAAiB,CACjBF,cAAgB"}