timepiece/node_modules/lit-html/node/development/async-directive.js

249 lines
10 KiB
JavaScript

import { isSingleExpression } from './directive-helpers.js';
import { Directive, PartType } from './directive.js';
export { Directive, PartType, directive } from './directive.js';
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* Recursively walks down the tree of Parts/TemplateInstances/Directives to set
* the connected state of directives and run `disconnected`/ `reconnected`
* callbacks.
*
* @return True if there were children to disconnect; false otherwise
*/
const notifyChildrenConnectedChanged = (parent, isConnected) => {
var _a, _b;
const children = parent._$disconnectableChildren;
if (children === undefined) {
return false;
}
for (const obj of children) {
// The existence of `_$notifyDirectiveConnectionChanged` is used as a "brand" to
// disambiguate AsyncDirectives from other DisconnectableChildren
// (as opposed to using an instanceof check to know when to call it); the
// redundancy of "Directive" in the API name is to avoid conflicting with
// `_$notifyConnectionChanged`, which exists `ChildParts` which are also in
// this list
// Disconnect Directive (and any nested directives contained within)
// This property needs to remain unminified.
(_b = (_a = obj)['_$notifyDirectiveConnectionChanged']) === null || _b === void 0 ? void 0 : _b.call(_a, isConnected, false);
// Disconnect Part/TemplateInstance
notifyChildrenConnectedChanged(obj, isConnected);
}
return true;
};
/**
* Removes the given child from its parent list of disconnectable children, and
* if the parent list becomes empty as a result, removes the parent from its
* parent, and so forth up the tree when that causes subsequent parent lists to
* become empty.
*/
const removeDisconnectableFromParent = (obj) => {
let parent, children;
do {
if ((parent = obj._$parent) === undefined) {
break;
}
children = parent._$disconnectableChildren;
children.delete(obj);
obj = parent;
} while ((children === null || children === void 0 ? void 0 : children.size) === 0);
};
const addDisconnectableToParent = (obj) => {
// Climb the parent tree, creating a sparse tree of children needing
// disconnection
for (let parent; (parent = obj._$parent); obj = parent) {
let children = parent._$disconnectableChildren;
if (children === undefined) {
parent._$disconnectableChildren = children = new Set();
}
else if (children.has(obj)) {
// Once we've reached a parent that already contains this child, we
// can short-circuit
break;
}
children.add(obj);
installDisconnectAPI(parent);
}
};
/**
* Changes the parent reference of the ChildPart, and updates the sparse tree of
* Disconnectable children accordingly.
*
* Note, this method will be patched onto ChildPart instances and called from
* the core code when parts are moved between different parents.
*/
function reparentDisconnectables(newParent) {
if (this._$disconnectableChildren !== undefined) {
removeDisconnectableFromParent(this);
this._$parent = newParent;
addDisconnectableToParent(this);
}
else {
this._$parent = newParent;
}
}
/**
* Sets the connected state on any directives contained within the committed
* value of this part (i.e. within a TemplateInstance or iterable of
* ChildParts) and runs their `disconnected`/`reconnected`s, as well as within
* any directives stored on the ChildPart (when `valueOnly` is false).
*
* `isClearingValue` should be passed as `true` on a top-level part that is
* clearing itself, and not as a result of recursively disconnecting directives
* as part of a `clear` operation higher up the tree. This both ensures that any
* directive on this ChildPart that produced a value that caused the clear
* operation is not disconnected, and also serves as a performance optimization
* to avoid needless bookkeeping when a subtree is going away; when clearing a
* subtree, only the top-most part need to remove itself from the parent.
*
* `fromPartIndex` is passed only in the case of a partial `_clear` running as a
* result of truncating an iterable.
*
* Note, this method will be patched onto ChildPart instances and called from the
* core code when parts are cleared or the connection state is changed by the
* user.
*/
function notifyChildPartConnectedChanged(isConnected, isClearingValue = false, fromPartIndex = 0) {
const value = this._$committedValue;
const children = this._$disconnectableChildren;
if (children === undefined || children.size === 0) {
return;
}
if (isClearingValue) {
if (Array.isArray(value)) {
// Iterable case: Any ChildParts created by the iterable should be
// disconnected and removed from this ChildPart's disconnectable
// children (starting at `fromPartIndex` in the case of truncation)
for (let i = fromPartIndex; i < value.length; i++) {
notifyChildrenConnectedChanged(value[i], false);
removeDisconnectableFromParent(value[i]);
}
}
else if (value != null) {
// TemplateInstance case: If the value has disconnectable children (will
// only be in the case that it is a TemplateInstance), we disconnect it
// and remove it from this ChildPart's disconnectable children
notifyChildrenConnectedChanged(value, false);
removeDisconnectableFromParent(value);
}
}
else {
notifyChildrenConnectedChanged(this, isConnected);
}
}
/**
* Patches disconnection API onto ChildParts.
*/
const installDisconnectAPI = (obj) => {
var _a, _b;
var _c, _d;
if (obj.type == PartType.CHILD) {
(_a = (_c = obj)._$notifyConnectionChanged) !== null && _a !== void 0 ? _a : (_c._$notifyConnectionChanged = notifyChildPartConnectedChanged);
(_b = (_d = obj)._$reparentDisconnectables) !== null && _b !== void 0 ? _b : (_d._$reparentDisconnectables = reparentDisconnectables);
}
};
/**
* An abstract `Directive` base class whose `disconnected` method will be
* called when the part containing the directive is cleared as a result of
* re-rendering, or when the user calls `part.setConnected(false)` on
* a part that was previously rendered containing the directive (as happens
* when e.g. a LitElement disconnects from the DOM).
*
* If `part.setConnected(true)` is subsequently called on a
* containing part, the directive's `reconnected` method will be called prior
* to its next `update`/`render` callbacks. When implementing `disconnected`,
* `reconnected` should also be implemented to be compatible with reconnection.
*
* Note that updates may occur while the directive is disconnected. As such,
* directives should generally check the `this.isConnected` flag during
* render/update to determine whether it is safe to subscribe to resources
* that may prevent garbage collection.
*/
class AsyncDirective extends Directive {
constructor() {
super(...arguments);
// @internal
this._$disconnectableChildren = undefined;
}
/**
* Initialize the part with internal fields
* @param part
* @param parent
* @param attributeIndex
*/
_$initialize(part, parent, attributeIndex) {
super._$initialize(part, parent, attributeIndex);
addDisconnectableToParent(this);
this.isConnected = part._$isConnected;
}
// This property needs to remain unminified.
/**
* Called from the core code when a directive is going away from a part (in
* which case `shouldRemoveFromParent` should be true), and from the
* `setChildrenConnected` helper function when recursively changing the
* connection state of a tree (in which case `shouldRemoveFromParent` should
* be false).
*
* @param isConnected
* @param isClearingDirective - True when the directive itself is being
* removed; false when the tree is being disconnected
* @internal
*/
['_$notifyDirectiveConnectionChanged'](isConnected, isClearingDirective = true) {
var _a, _b;
if (isConnected !== this.isConnected) {
this.isConnected = isConnected;
if (isConnected) {
(_a = this.reconnected) === null || _a === void 0 ? void 0 : _a.call(this);
}
else {
(_b = this.disconnected) === null || _b === void 0 ? void 0 : _b.call(this);
}
}
if (isClearingDirective) {
notifyChildrenConnectedChanged(this, isConnected);
removeDisconnectableFromParent(this);
}
}
/**
* Sets the value of the directive's Part outside the normal `update`/`render`
* lifecycle of a directive.
*
* This method should not be called synchronously from a directive's `update`
* or `render`.
*
* @param directive The directive to update
* @param value The value to set
*/
setValue(value) {
if (isSingleExpression(this.__part)) {
this.__part._$setValue(value, this);
}
else {
// this.__attributeIndex will be defined in this case, but
// assert it in dev mode
if (this.__attributeIndex === undefined) {
throw new Error(`Expected this.__attributeIndex to be a number`);
}
const newValues = [...this.__part._$committedValue];
newValues[this.__attributeIndex] = value;
this.__part._$setValue(newValues, this, 0);
}
}
/**
* User callbacks for implementing logic to release any resources/subscriptions
* that may have been retained by this directive. Since directives may also be
* re-connected, `reconnected` should also be implemented to restore the
* working state of the directive prior to the next render.
*/
disconnected() { }
reconnected() { }
}
export { AsyncDirective };
//# sourceMappingURL=async-directive.js.map