UNPKG

7.92 kBTypeScriptView Raw
1/**
2 * @license
3 * Copyright 2017 Google LLC
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6/**
7 * Overview:
8 *
9 * This module is designed to add support for an async `setValue` API and
10 * `disconnected` callback to directives with the least impact on the core
11 * runtime or payload when that feature is not used.
12 *
13 * The strategy is to introduce a `AsyncDirective` subclass of
14 * `Directive` that climbs the "parent" tree in its constructor to note which
15 * branches of lit-html's "logical tree" of data structures contain such
16 * directives and thus need to be crawled when a subtree is being cleared (or
17 * manually disconnected) in order to run the `disconnected` callback.
18 *
19 * The "nodes" of the logical tree include Parts, TemplateInstances (for when a
20 * TemplateResult is committed to a value of a ChildPart), and Directives; these
21 * all implement a common interface called `DisconnectableChild`. Each has a
22 * `_$parent` reference which is set during construction in the core code, and a
23 * `_$disconnectableChildren` field which is initially undefined.
24 *
25 * The sparse tree created by means of the `AsyncDirective` constructor
26 * crawling up the `_$parent` tree and placing a `_$disconnectableChildren` Set
27 * on each parent that includes each child that contains a
28 * `AsyncDirective` directly or transitively via its children. In order to
29 * notify connection state changes and disconnect (or reconnect) a tree, the
30 * `_$notifyConnectionChanged` API is patched onto ChildParts as a directive
31 * climbs the parent tree, which is called by the core when clearing a part if
32 * it exists. When called, that method iterates over the sparse tree of
33 * Set<DisconnectableChildren> built up by AsyncDirectives, and calls
34 * `_$notifyDirectiveConnectionChanged` on any directives that are encountered
35 * in that tree, running the required callbacks.
36 *
37 * A given "logical tree" of lit-html data-structures might look like this:
38 *
39 * ChildPart(N1) _$dC=[D2,T3]
40 * ._directive
41 * AsyncDirective(D2)
42 * ._value // user value was TemplateResult
43 * TemplateInstance(T3) _$dC=[A4,A6,N10,N12]
44 * ._parts[]
45 * AttributePart(A4) _$dC=[D5]
46 * ._directives[]
47 * AsyncDirective(D5)
48 * AttributePart(A6) _$dC=[D7,D8]
49 * ._directives[]
50 * AsyncDirective(D7)
51 * Directive(D8) _$dC=[D9]
52 * ._directive
53 * AsyncDirective(D9)
54 * ChildPart(N10) _$dC=[D11]
55 * ._directive
56 * AsyncDirective(D11)
57 * ._value
58 * string
59 * ChildPart(N12) _$dC=[D13,N14,N16]
60 * ._directive
61 * AsyncDirective(D13)
62 * ._value // user value was iterable
63 * Array<ChildPart>
64 * ChildPart(N14) _$dC=[D15]
65 * ._value
66 * string
67 * ChildPart(N16) _$dC=[D17,T18]
68 * ._directive
69 * AsyncDirective(D17)
70 * ._value // user value was TemplateResult
71 * TemplateInstance(T18) _$dC=[A19,A21,N25]
72 * ._parts[]
73 * AttributePart(A19) _$dC=[D20]
74 * ._directives[]
75 * AsyncDirective(D20)
76 * AttributePart(A21) _$dC=[22,23]
77 * ._directives[]
78 * AsyncDirective(D22)
79 * Directive(D23) _$dC=[D24]
80 * ._directive
81 * AsyncDirective(D24)
82 * ChildPart(N25) _$dC=[D26]
83 * ._directive
84 * AsyncDirective(D26)
85 * ._value
86 * string
87 *
88 * Example 1: The directive in ChildPart(N12) updates and returns `nothing`. The
89 * ChildPart will _clear() itself, and so we need to disconnect the "value" of
90 * the ChildPart (but not its directive). In this case, when `_clear()` calls
91 * `_$notifyConnectionChanged()`, we don't iterate all of the
92 * _$disconnectableChildren, rather we do a value-specific disconnection: i.e.
93 * since the _value was an Array<ChildPart> (because an iterable had been
94 * committed), we iterate the array of ChildParts (N14, N16) and run
95 * `setConnected` on them (which does recurse down the full tree of
96 * `_$disconnectableChildren` below it, and also removes N14 and N16 from N12's
97 * `_$disconnectableChildren`). Once the values have been disconnected, we then
98 * check whether the ChildPart(N12)'s list of `_$disconnectableChildren` is empty
99 * (and would remove it from its parent TemplateInstance(T3) if so), but since
100 * it would still contain its directive D13, it stays in the disconnectable
101 * tree.
102 *
103 * Example 2: In the course of Example 1, `setConnected` will reach
104 * ChildPart(N16); in this case the entire part is being disconnected, so we
105 * simply iterate all of N16's `_$disconnectableChildren` (D17,T18) and
106 * recursively run `setConnected` on them. Note that we only remove children
107 * from `_$disconnectableChildren` for the top-level values being disconnected
108 * on a clear; doing this bookkeeping lower in the tree is wasteful since it's
109 * all being thrown away.
110 *
111 * Example 3: If the LitElement containing the entire tree above becomes
112 * disconnected, it will run `childPart.setConnected()` (which calls
113 * `childPart._$notifyConnectionChanged()` if it exists); in this case, we
114 * recursively run `setConnected()` over the entire tree, without removing any
115 * children from `_$disconnectableChildren`, since this tree is required to
116 * re-connect the tree, which does the same operation, simply passing
117 * `isConnectd: true` down the tree, signaling which callback to run.
118 */
119import { Disconnectable, Part } from './lit-html.js';
120import { Directive } from './directive.js';
121export { directive } from './directive.js';
122/**
123 * An abstract `Directive` base class whose `disconnected` method will be
124 * called when the part containing the directive is cleared as a result of
125 * re-rendering, or when the user calls `part.setConnected(false)` on
126 * a part that was previously rendered containing the directive (as happens
127 * when e.g. a LitElement disconnects from the DOM).
128 *
129 * If `part.setConnected(true)` is subsequently called on a
130 * containing part, the directive's `reconnected` method will be called prior
131 * to its next `update`/`render` callbacks. When implementing `disconnected`,
132 * `reconnected` should also be implemented to be compatible with reconnection.
133 *
134 * Note that updates may occur while the directive is disconnected. As such,
135 * directives should generally check the `this.isConnected` flag during
136 * render/update to determine whether it is safe to subscribe to resources
137 * that may prevent garbage collection.
138 */
139export declare abstract class AsyncDirective extends Directive {
140 /**
141 * The connection state for this Directive.
142 */
143 isConnected: boolean;
144 /**
145 * Initialize the part with internal fields
146 * @param part
147 * @param parent
148 * @param attributeIndex
149 */
150 _$initialize(part: Part, parent: Disconnectable, attributeIndex: number | undefined): void;
151 /**
152 * Sets the value of the directive's Part outside the normal `update`/`render`
153 * lifecycle of a directive.
154 *
155 * This method should not be called synchronously from a directive's `update`
156 * or `render`.
157 *
158 * @param directive The directive to update
159 * @param value The value to set
160 */
161 setValue(value: unknown): void;
162 /**
163 * User callbacks for implementing logic to release any resources/subscriptions
164 * that may have been retained by this directive. Since directives may also be
165 * re-connected, `reconnected` should also be implemented to restore the
166 * working state of the directive prior to the next render.
167 */
168 protected disconnected(): void;
169 protected reconnected(): void;
170}
171//# sourceMappingURL=async-directive.d.ts.map
\No newline at end of file