@b9g/crank 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +203 -138
- package/_css.cjs +80 -0
- package/_css.cjs.map +1 -0
- package/_css.d.ts +21 -0
- package/_css.js +76 -0
- package/_css.js.map +1 -0
- package/_utils.cjs +106 -0
- package/_utils.cjs.map +1 -0
- package/_utils.d.ts +14 -0
- package/_utils.js +99 -0
- package/_utils.js.map +1 -0
- package/async.cjs +236 -0
- package/async.cjs.map +1 -0
- package/async.d.ts +104 -0
- package/async.js +233 -0
- package/async.js.map +1 -0
- package/crank.cjs +1609 -1345
- package/crank.cjs.map +1 -1
- package/crank.d.ts +461 -228
- package/crank.js +1602 -1339
- package/crank.js.map +1 -1
- package/dom.cjs +365 -231
- package/dom.cjs.map +1 -1
- package/dom.d.ts +5 -5
- package/dom.js +365 -231
- package/dom.js.map +1 -1
- package/event-target.cjs +254 -0
- package/event-target.cjs.map +1 -0
- package/event-target.d.ts +26 -0
- package/event-target.js +249 -0
- package/event-target.js.map +1 -0
- package/html.cjs +11 -25
- package/html.cjs.map +1 -1
- package/html.d.ts +3 -3
- package/html.js +11 -25
- package/html.js.map +1 -1
- package/jsx-tag.cjs +2 -2
- package/jsx-tag.cjs.map +1 -1
- package/jsx-tag.js +2 -2
- package/jsx-tag.js.map +1 -1
- package/package.json +18 -2
- package/standalone.cjs +1 -0
- package/standalone.cjs.map +1 -1
- package/standalone.js +1 -1
- package/umd.js +2361 -1569
- package/umd.js.map +1 -1
package/crank.js
CHANGED
|
@@ -1,40 +1,17 @@
|
|
|
1
1
|
/// <reference types="crank.d.ts" />
|
|
2
|
+
import { removeEventTargetDelegates, addEventTargetDelegates, clearEventListeners, CustomEventTarget } from './event-target.js';
|
|
3
|
+
import { isPromiseLike, unwrap, wrap, arrayify, safeRace, isIteratorLike } from './_utils.js';
|
|
4
|
+
|
|
2
5
|
const NOOP = () => { };
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Ensures a value is an array.
|
|
12
|
-
*
|
|
13
|
-
* This function does the same thing as wrap() above except it handles nulls
|
|
14
|
-
* and iterables, so it is appropriate for wrapping user-provided element
|
|
15
|
-
* children.
|
|
16
|
-
*/
|
|
17
|
-
function arrayify(value) {
|
|
18
|
-
return value == null
|
|
19
|
-
? []
|
|
20
|
-
: Array.isArray(value)
|
|
21
|
-
? value
|
|
22
|
-
: typeof value === "string" ||
|
|
23
|
-
typeof value[Symbol.iterator] !== "function"
|
|
24
|
-
? [value]
|
|
25
|
-
: [...value];
|
|
26
|
-
}
|
|
27
|
-
function isIteratorLike(value) {
|
|
28
|
-
return value != null && typeof value.next === "function";
|
|
29
|
-
}
|
|
30
|
-
function isPromiseLike(value) {
|
|
31
|
-
return value != null && typeof value.then === "function";
|
|
6
|
+
function getTagName(tag) {
|
|
7
|
+
return typeof tag === "function"
|
|
8
|
+
? tag.name || "Anonymous"
|
|
9
|
+
: typeof tag === "string"
|
|
10
|
+
? tag
|
|
11
|
+
: // tag is symbol, using else branch to avoid typeof tag === "symbol"
|
|
12
|
+
tag.description || "Anonymous";
|
|
32
13
|
}
|
|
33
|
-
/***
|
|
34
|
-
* SPECIAL TAGS
|
|
35
|
-
*
|
|
36
|
-
* Crank provides a couple tags which have special meaning for the renderer.
|
|
37
|
-
***/
|
|
14
|
+
/*** SPECIAL TAGS ***/
|
|
38
15
|
/**
|
|
39
16
|
* A special tag for grouping multiple children within the same parent.
|
|
40
17
|
*
|
|
@@ -46,8 +23,8 @@ function isPromiseLike(value) {
|
|
|
46
23
|
* reference this export.
|
|
47
24
|
*/
|
|
48
25
|
const Fragment = "";
|
|
49
|
-
// TODO: We assert the following symbol tags as
|
|
50
|
-
// for symbol tags in JSX doesn
|
|
26
|
+
// TODO: We assert the following symbol tags as Components because TypeScript
|
|
27
|
+
// support for symbol tags in JSX doesn't exist yet.
|
|
51
28
|
// https://github.com/microsoft/TypeScript/issues/38367
|
|
52
29
|
/**
|
|
53
30
|
* A special tag for rendering into a new root node via a root prop.
|
|
@@ -55,13 +32,13 @@ const Fragment = "";
|
|
|
55
32
|
* This tag is useful for creating element trees with multiple roots, for
|
|
56
33
|
* things like modals or tooltips.
|
|
57
34
|
*
|
|
58
|
-
* Renderer.prototype.render()
|
|
59
|
-
*
|
|
35
|
+
* Renderer.prototype.render() implicitly wraps top-level in a Portal element
|
|
36
|
+
* with the root set to the second argument passed in.
|
|
60
37
|
*/
|
|
61
38
|
const Portal = Symbol.for("crank.Portal");
|
|
62
39
|
/**
|
|
63
40
|
* A special tag which preserves whatever was previously rendered in the
|
|
64
|
-
* element
|
|
41
|
+
* element's position.
|
|
65
42
|
*
|
|
66
43
|
* Copy elements are useful for when you want to prevent a subtree from
|
|
67
44
|
* rerendering as a performance optimization. Copy elements can also be keyed,
|
|
@@ -69,10 +46,13 @@ const Portal = Symbol.for("crank.Portal");
|
|
|
69
46
|
*/
|
|
70
47
|
const Copy = Symbol.for("crank.Copy");
|
|
71
48
|
/**
|
|
72
|
-
* A special tag for
|
|
49
|
+
* A special tag for rendering text nodes.
|
|
73
50
|
*
|
|
74
|
-
*
|
|
51
|
+
* Strings in the element tree are implicitly wrapped in a Text element with
|
|
52
|
+
* value set to the string.
|
|
75
53
|
*/
|
|
54
|
+
const Text = Symbol.for("crank.Text");
|
|
55
|
+
/** A special tag for injecting raw nodes or strings via a value prop. */
|
|
76
56
|
const Raw = Symbol.for("crank.Raw");
|
|
77
57
|
const ElementSymbol = Symbol.for("crank.Element");
|
|
78
58
|
/**
|
|
@@ -100,15 +80,6 @@ class Element {
|
|
|
100
80
|
this.tag = tag;
|
|
101
81
|
this.props = props;
|
|
102
82
|
}
|
|
103
|
-
get key() {
|
|
104
|
-
return this.props.key;
|
|
105
|
-
}
|
|
106
|
-
get ref() {
|
|
107
|
-
return this.props.ref;
|
|
108
|
-
}
|
|
109
|
-
get copy() {
|
|
110
|
-
return !!this.props.copy;
|
|
111
|
-
}
|
|
112
83
|
}
|
|
113
84
|
// See Element interface
|
|
114
85
|
Element.prototype.$$typeof = ElementSymbol;
|
|
@@ -116,34 +87,34 @@ function isElement(value) {
|
|
|
116
87
|
return value != null && value.$$typeof === ElementSymbol;
|
|
117
88
|
}
|
|
118
89
|
const DEPRECATED_PROP_PREFIXES = ["crank-", "c-", "$"];
|
|
119
|
-
const DEPRECATED_SPECIAL_PROP_BASES = ["key", "ref", "static"];
|
|
120
|
-
const SPECIAL_PROPS = new Set(["children", "key", "ref", "copy"]);
|
|
121
|
-
for (const propPrefix of DEPRECATED_PROP_PREFIXES) {
|
|
122
|
-
for (const propBase of DEPRECATED_SPECIAL_PROP_BASES) {
|
|
123
|
-
SPECIAL_PROPS.add(propPrefix + propBase);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
90
|
+
const DEPRECATED_SPECIAL_PROP_BASES = ["key", "ref", "static", "copy"];
|
|
126
91
|
/**
|
|
127
92
|
* Creates an element with the specified tag, props and children.
|
|
128
93
|
*
|
|
129
94
|
* This function is usually used as a transpilation target for JSX transpilers,
|
|
130
95
|
* but it can also be called directly. It additionally extracts special props so
|
|
131
|
-
* they aren
|
|
96
|
+
* they aren't accessible to renderer methods or components, and assigns the
|
|
132
97
|
* children prop according to any additional arguments passed to the function.
|
|
133
98
|
*/
|
|
134
99
|
function createElement(tag, props, ...children) {
|
|
135
100
|
if (props == null) {
|
|
136
101
|
props = {};
|
|
137
102
|
}
|
|
103
|
+
if ("static" in props) {
|
|
104
|
+
console.error(`The \`static\` prop is deprecated. Use \`copy\` instead.`);
|
|
105
|
+
props["copy"] = props["static"];
|
|
106
|
+
delete props["static"];
|
|
107
|
+
}
|
|
138
108
|
for (let i = 0; i < DEPRECATED_PROP_PREFIXES.length; i++) {
|
|
139
109
|
const propPrefix = DEPRECATED_PROP_PREFIXES[i];
|
|
140
110
|
for (let j = 0; j < DEPRECATED_SPECIAL_PROP_BASES.length; j++) {
|
|
141
111
|
const propBase = DEPRECATED_SPECIAL_PROP_BASES[j];
|
|
142
112
|
const deprecatedPropName = propPrefix + propBase;
|
|
143
|
-
const targetPropBase = propBase === "static" ? "copy" : propBase;
|
|
144
113
|
if (deprecatedPropName in props) {
|
|
145
|
-
|
|
114
|
+
const targetPropBase = propBase === "static" ? "copy" : propBase;
|
|
115
|
+
console.error(`The \`${deprecatedPropName}\` prop is deprecated. Use \`${targetPropBase}\` instead.`);
|
|
146
116
|
props[targetPropBase] = props[deprecatedPropName];
|
|
117
|
+
delete props[deprecatedPropName];
|
|
147
118
|
}
|
|
148
119
|
}
|
|
149
120
|
}
|
|
@@ -158,13 +129,13 @@ function createElement(tag, props, ...children) {
|
|
|
158
129
|
/** Clones a given element, shallowly copying the props object. */
|
|
159
130
|
function cloneElement(el) {
|
|
160
131
|
if (!isElement(el)) {
|
|
161
|
-
throw new TypeError(
|
|
132
|
+
throw new TypeError(`Cannot clone non-element: ${String(el)}`);
|
|
162
133
|
}
|
|
163
134
|
return new Element(el.tag, { ...el.props });
|
|
164
135
|
}
|
|
165
136
|
function narrow(value) {
|
|
166
137
|
if (typeof value === "boolean" || value == null) {
|
|
167
|
-
return
|
|
138
|
+
return;
|
|
168
139
|
}
|
|
169
140
|
else if (typeof value === "string" || isElement(value)) {
|
|
170
141
|
return value;
|
|
@@ -174,137 +145,203 @@ function narrow(value) {
|
|
|
174
145
|
}
|
|
175
146
|
return value.toString();
|
|
176
147
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
else {
|
|
204
|
-
// We could use recursion here but it’s just easier to do it inline.
|
|
205
|
-
for (let j = 0; j < value.length; j++) {
|
|
206
|
-
const value1 = value[j];
|
|
207
|
-
if (!value1) ;
|
|
208
|
-
else if (typeof value1 === "string") {
|
|
209
|
-
buffer = (buffer || "") + value1;
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
if (buffer) {
|
|
213
|
-
result.push(buffer);
|
|
214
|
-
buffer = undefined;
|
|
215
|
-
}
|
|
216
|
-
result.push(value1);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
148
|
+
/*** RETAINER FLAGS ***/
|
|
149
|
+
const DidDiff = 1 << 0;
|
|
150
|
+
const DidCommit = 1 << 1;
|
|
151
|
+
const IsCopied = 1 << 2;
|
|
152
|
+
const IsUpdating = 1 << 3;
|
|
153
|
+
const IsExecuting = 1 << 4;
|
|
154
|
+
const IsRefreshing = 1 << 5;
|
|
155
|
+
const IsScheduling = 1 << 6;
|
|
156
|
+
const IsSchedulingFallback = 1 << 7;
|
|
157
|
+
const IsUnmounted = 1 << 8;
|
|
158
|
+
// TODO: Is this flag still necessary or can we use IsUnmounted?
|
|
159
|
+
const IsErrored = 1 << 9;
|
|
160
|
+
const IsResurrecting = 1 << 10;
|
|
161
|
+
// TODO: Maybe we can get rid of IsSyncGen and IsAsyncGen
|
|
162
|
+
const IsSyncGen = 1 << 11;
|
|
163
|
+
const IsAsyncGen = 1 << 12;
|
|
164
|
+
const IsInForOfLoop = 1 << 13;
|
|
165
|
+
const IsInForAwaitOfLoop = 1 << 14;
|
|
166
|
+
const NeedsToYield = 1 << 15;
|
|
167
|
+
const PropsAvailable = 1 << 16;
|
|
168
|
+
function getFlag(ret, flag) {
|
|
169
|
+
return !!(ret.f & flag);
|
|
170
|
+
}
|
|
171
|
+
function setFlag(ret, flag, value = true) {
|
|
172
|
+
if (value) {
|
|
173
|
+
ret.f |= flag;
|
|
220
174
|
}
|
|
221
|
-
|
|
222
|
-
|
|
175
|
+
else {
|
|
176
|
+
ret.f &= ~flag;
|
|
223
177
|
}
|
|
224
|
-
return result;
|
|
225
178
|
}
|
|
226
179
|
/**
|
|
227
180
|
* @internal
|
|
228
|
-
*
|
|
229
|
-
*
|
|
181
|
+
* Retainers are objects which act as the internal representation of elements,
|
|
182
|
+
* mirroring the element tree.
|
|
230
183
|
*/
|
|
231
184
|
class Retainer {
|
|
232
185
|
constructor(el) {
|
|
186
|
+
this.f = 0;
|
|
233
187
|
this.el = el;
|
|
234
188
|
this.ctx = undefined;
|
|
235
189
|
this.children = undefined;
|
|
190
|
+
this.fallback = undefined;
|
|
236
191
|
this.value = undefined;
|
|
237
|
-
this.
|
|
238
|
-
this.
|
|
239
|
-
this.
|
|
240
|
-
this.
|
|
192
|
+
this.oldProps = undefined;
|
|
193
|
+
this.pendingDiff = undefined;
|
|
194
|
+
this.onNextDiff = undefined;
|
|
195
|
+
this.graveyard = undefined;
|
|
196
|
+
this.lingerers = undefined;
|
|
241
197
|
}
|
|
242
198
|
}
|
|
199
|
+
function cloneRetainer(ret) {
|
|
200
|
+
const clone = new Retainer(ret.el);
|
|
201
|
+
clone.f = ret.f;
|
|
202
|
+
clone.ctx = ret.ctx;
|
|
203
|
+
clone.children = ret.children;
|
|
204
|
+
clone.fallback = ret.fallback;
|
|
205
|
+
clone.value = ret.value;
|
|
206
|
+
clone.scope = ret.scope;
|
|
207
|
+
clone.oldProps = ret.oldProps;
|
|
208
|
+
clone.pendingDiff = ret.pendingDiff;
|
|
209
|
+
clone.onNextDiff = ret.onNextDiff;
|
|
210
|
+
clone.graveyard = ret.graveyard;
|
|
211
|
+
clone.lingerers = ret.lingerers;
|
|
212
|
+
return clone;
|
|
213
|
+
}
|
|
243
214
|
/**
|
|
244
215
|
* Finds the value of the element according to its type.
|
|
245
216
|
*
|
|
246
|
-
* @returns
|
|
217
|
+
* @returns A node, an array of nodes or undefined.
|
|
247
218
|
*/
|
|
248
|
-
function getValue(ret) {
|
|
249
|
-
if (
|
|
250
|
-
return
|
|
251
|
-
|
|
252
|
-
|
|
219
|
+
function getValue(ret, isNested = false, index) {
|
|
220
|
+
if (getFlag(ret, IsScheduling) && isNested) {
|
|
221
|
+
return ret.fallback ? getValue(ret.fallback, isNested, index) : undefined;
|
|
222
|
+
}
|
|
223
|
+
else if (ret.fallback && !getFlag(ret, DidDiff)) {
|
|
224
|
+
return ret.fallback
|
|
225
|
+
? getValue(ret.fallback, isNested, index)
|
|
226
|
+
: ret.fallback;
|
|
253
227
|
}
|
|
254
228
|
else if (ret.el.tag === Portal) {
|
|
255
229
|
return;
|
|
256
230
|
}
|
|
257
|
-
else if (
|
|
258
|
-
|
|
231
|
+
else if (ret.el.tag === Fragment || typeof ret.el.tag === "function") {
|
|
232
|
+
if (index != null && ret.ctx) {
|
|
233
|
+
ret.ctx.index = index;
|
|
234
|
+
}
|
|
235
|
+
return unwrap(getChildValues(ret, index));
|
|
259
236
|
}
|
|
260
|
-
return
|
|
237
|
+
return ret.value;
|
|
261
238
|
}
|
|
262
239
|
/**
|
|
263
|
-
* Walks an element
|
|
240
|
+
* Walks an element's children to find its child values.
|
|
264
241
|
*
|
|
265
|
-
* @
|
|
242
|
+
* @param ret - The retainer whose child values we are reading.
|
|
243
|
+
* @param startIndex - Starting index to thread through for context index updates.
|
|
244
|
+
*
|
|
245
|
+
* @returns An array of nodes.
|
|
266
246
|
*/
|
|
267
|
-
function getChildValues(ret) {
|
|
268
|
-
if (ret.cachedChildValues) {
|
|
269
|
-
return wrap(ret.cachedChildValues);
|
|
270
|
-
}
|
|
247
|
+
function getChildValues(ret, startIndex) {
|
|
271
248
|
const values = [];
|
|
249
|
+
const lingerers = ret.lingerers;
|
|
272
250
|
const children = wrap(ret.children);
|
|
251
|
+
let currentIndex = startIndex;
|
|
273
252
|
for (let i = 0; i < children.length; i++) {
|
|
253
|
+
if (lingerers != null && lingerers[i] != null) {
|
|
254
|
+
const rets = lingerers[i];
|
|
255
|
+
for (const ret of rets) {
|
|
256
|
+
const value = getValue(ret, true, currentIndex);
|
|
257
|
+
if (Array.isArray(value)) {
|
|
258
|
+
for (let j = 0; j < value.length; j++) {
|
|
259
|
+
values.push(value[j]);
|
|
260
|
+
}
|
|
261
|
+
if (currentIndex != null) {
|
|
262
|
+
currentIndex += value.length;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else if (value) {
|
|
266
|
+
values.push(value);
|
|
267
|
+
if (currentIndex != null) {
|
|
268
|
+
currentIndex++;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
274
273
|
const child = children[i];
|
|
275
274
|
if (child) {
|
|
276
|
-
|
|
275
|
+
const value = getValue(child, true, currentIndex);
|
|
276
|
+
if (Array.isArray(value)) {
|
|
277
|
+
for (let j = 0; j < value.length; j++) {
|
|
278
|
+
values.push(value[j]);
|
|
279
|
+
}
|
|
280
|
+
if (currentIndex != null) {
|
|
281
|
+
currentIndex += value.length;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else if (value) {
|
|
285
|
+
values.push(value);
|
|
286
|
+
if (currentIndex != null) {
|
|
287
|
+
currentIndex++;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
277
290
|
}
|
|
278
291
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
292
|
+
if (lingerers != null && lingerers.length > children.length) {
|
|
293
|
+
for (let i = children.length; i < lingerers.length; i++) {
|
|
294
|
+
const rets = lingerers[i];
|
|
295
|
+
if (rets != null) {
|
|
296
|
+
for (const ret of rets) {
|
|
297
|
+
const value = getValue(ret, true, currentIndex);
|
|
298
|
+
if (Array.isArray(value)) {
|
|
299
|
+
for (let j = 0; j < value.length; j++) {
|
|
300
|
+
values.push(value[j]);
|
|
301
|
+
}
|
|
302
|
+
if (currentIndex != null) {
|
|
303
|
+
currentIndex += value.length;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else if (value) {
|
|
307
|
+
values.push(value);
|
|
308
|
+
if (currentIndex != null) {
|
|
309
|
+
currentIndex++;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
283
315
|
}
|
|
284
|
-
return
|
|
316
|
+
return values;
|
|
317
|
+
}
|
|
318
|
+
function stripSpecialProps(props) {
|
|
319
|
+
let _;
|
|
320
|
+
let result;
|
|
321
|
+
({ key: _, ref: _, copy: _, hydrate: _, children: _, ...result } = props);
|
|
322
|
+
return result;
|
|
285
323
|
}
|
|
286
|
-
const
|
|
324
|
+
const defaultAdapter = {
|
|
287
325
|
create() {
|
|
288
|
-
throw new Error("
|
|
326
|
+
throw new Error("adapter must implement create");
|
|
289
327
|
},
|
|
290
|
-
|
|
291
|
-
throw new Error("
|
|
328
|
+
adopt() {
|
|
329
|
+
throw new Error("adapter must implement adopt() for hydration");
|
|
292
330
|
},
|
|
293
|
-
scope:
|
|
294
|
-
read:
|
|
295
|
-
text:
|
|
296
|
-
raw:
|
|
331
|
+
scope: ({ scope }) => scope,
|
|
332
|
+
read: (value) => value,
|
|
333
|
+
text: ({ value }) => value,
|
|
334
|
+
raw: ({ value }) => value,
|
|
297
335
|
patch: NOOP,
|
|
298
336
|
arrange: NOOP,
|
|
299
|
-
|
|
300
|
-
|
|
337
|
+
remove: NOOP,
|
|
338
|
+
finalize: NOOP,
|
|
301
339
|
};
|
|
302
|
-
const _RendererImpl = Symbol.for("crank.RendererImpl");
|
|
303
340
|
/**
|
|
304
341
|
* An abstract class which is subclassed to render to different target
|
|
305
|
-
* environments. Subclasses
|
|
306
|
-
*
|
|
307
|
-
*
|
|
342
|
+
* environments. Subclasses call super() with a custom RenderAdapter object.
|
|
343
|
+
* This class is responsible for kicking off the rendering process and caching
|
|
344
|
+
* previous trees by root.
|
|
308
345
|
*
|
|
309
346
|
* @template TNode - The type of the node for a rendering environment.
|
|
310
347
|
* @template TScope - Data which is passed down the tree.
|
|
@@ -312,125 +349,128 @@ const _RendererImpl = Symbol.for("crank.RendererImpl");
|
|
|
312
349
|
* @template TResult - The type of exposed values.
|
|
313
350
|
*/
|
|
314
351
|
class Renderer {
|
|
315
|
-
constructor(
|
|
352
|
+
constructor(adapter) {
|
|
316
353
|
this.cache = new WeakMap();
|
|
317
|
-
this
|
|
318
|
-
...defaultRendererImpl,
|
|
319
|
-
...impl,
|
|
320
|
-
};
|
|
354
|
+
this.adapter = { ...defaultAdapter, ...adapter };
|
|
321
355
|
}
|
|
322
356
|
/**
|
|
323
357
|
* Renders an element tree into a specific root.
|
|
324
358
|
*
|
|
325
|
-
* @param children - An element tree.
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
* the same or an error will be thrown.
|
|
359
|
+
* @param children - An element tree. Rendering null deletes cached renders.
|
|
360
|
+
* @param root - The root to be rendered into. The renderer caches renders
|
|
361
|
+
* per root.
|
|
362
|
+
* @param bridge - An optional context that will be the ancestor context of
|
|
363
|
+
* all elements in the tree. Useful for connecting different renderers so
|
|
364
|
+
* that events/provisions/errors properly propagate. The context for a given
|
|
365
|
+
* root must be the same between renders.
|
|
333
366
|
*
|
|
334
367
|
* @returns The result of rendering the children, or a possible promise of
|
|
335
368
|
* the result if the element tree renders asynchronously.
|
|
336
369
|
*/
|
|
337
370
|
render(children, root, bridge) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (typeof root === "object" && root !== null) {
|
|
341
|
-
ret = this.cache.get(root);
|
|
342
|
-
}
|
|
343
|
-
let oldProps;
|
|
344
|
-
if (ret === undefined) {
|
|
345
|
-
ret = new Retainer(createElement(Portal, { children, root }));
|
|
346
|
-
ret.value = root;
|
|
347
|
-
ret.ctx = ctx;
|
|
348
|
-
if (typeof root === "object" && root !== null && children != null) {
|
|
349
|
-
this.cache.set(root, ret);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
else if (ret.ctx !== ctx) {
|
|
353
|
-
throw new Error("Context mismatch");
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
oldProps = ret.el.props;
|
|
357
|
-
ret.el = createElement(Portal, { children, root });
|
|
358
|
-
if (typeof root === "object" && root !== null && children == null) {
|
|
359
|
-
this.cache.delete(root);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
const impl = this[_RendererImpl];
|
|
363
|
-
const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children, undefined);
|
|
364
|
-
// We return the child values of the portal because portal elements
|
|
365
|
-
// themselves have no readable value.
|
|
366
|
-
if (isPromiseLike(childValues)) {
|
|
367
|
-
return childValues.then((childValues) => commitRootRender(impl, root, ctx, ret, childValues, oldProps));
|
|
368
|
-
}
|
|
369
|
-
return commitRootRender(impl, root, ctx, ret, childValues, oldProps);
|
|
371
|
+
const ret = getRootRetainer(this, bridge, { children, root });
|
|
372
|
+
return renderRoot(this.adapter, root, ret, children);
|
|
370
373
|
}
|
|
371
374
|
hydrate(children, root, bridge) {
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
375
|
+
const ret = getRootRetainer(this, bridge, {
|
|
376
|
+
children,
|
|
377
|
+
root,
|
|
378
|
+
hydrate: true,
|
|
379
|
+
});
|
|
380
|
+
return renderRoot(this.adapter, root, ret, children);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/*** PRIVATE RENDERER FUNCTIONS ***/
|
|
384
|
+
function getRootRetainer(renderer, bridge, { children, root, hydrate, }) {
|
|
385
|
+
let ret;
|
|
386
|
+
const bridgeCtx = bridge && bridge[_ContextState];
|
|
387
|
+
if (typeof root === "object" && root !== null) {
|
|
388
|
+
ret = renderer.cache.get(root);
|
|
389
|
+
}
|
|
390
|
+
const adapter = renderer.adapter;
|
|
391
|
+
if (ret === undefined) {
|
|
392
|
+
ret = new Retainer(createElement(Portal, { children, root, hydrate }));
|
|
382
393
|
ret.value = root;
|
|
394
|
+
ret.ctx = bridgeCtx;
|
|
395
|
+
ret.scope = adapter.scope({
|
|
396
|
+
tag: Portal,
|
|
397
|
+
tagName: getTagName(Portal),
|
|
398
|
+
props: stripSpecialProps(ret.el.props),
|
|
399
|
+
scope: undefined,
|
|
400
|
+
});
|
|
401
|
+
// remember that typeof null === "object"
|
|
383
402
|
if (typeof root === "object" && root !== null && children != null) {
|
|
384
|
-
|
|
403
|
+
renderer.cache.set(root, ret);
|
|
385
404
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
405
|
+
}
|
|
406
|
+
else if (ret.ctx !== bridgeCtx) {
|
|
407
|
+
throw new Error("A previous call to render() was passed a different context");
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
ret.el = createElement(Portal, { children, root, hydrate });
|
|
411
|
+
if (typeof root === "object" && root !== null && children == null) {
|
|
412
|
+
renderer.cache.delete(root);
|
|
392
413
|
}
|
|
393
|
-
return commitRootRender(impl, root, ctx, ret, childValues, oldProps);
|
|
394
414
|
}
|
|
415
|
+
return ret;
|
|
395
416
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (
|
|
400
|
-
|
|
401
|
-
|
|
417
|
+
function renderRoot(adapter, root, ret, children) {
|
|
418
|
+
const diff = diffChildren(adapter, root, ret, ret.ctx, ret.scope, ret, children);
|
|
419
|
+
const schedulePromises = [];
|
|
420
|
+
if (isPromiseLike(diff)) {
|
|
421
|
+
return diff.then(() => {
|
|
422
|
+
commit(adapter, ret, ret, ret.ctx, ret.scope, 0, schedulePromises, undefined);
|
|
423
|
+
if (schedulePromises.length > 0) {
|
|
424
|
+
return Promise.all(schedulePromises).then(() => {
|
|
425
|
+
if (typeof root !== "object" || root === null) {
|
|
426
|
+
unmount(adapter, ret, ret.ctx, ret, false);
|
|
427
|
+
}
|
|
428
|
+
return adapter.read(unwrap(getChildValues(ret)));
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
if (typeof root !== "object" || root === null) {
|
|
432
|
+
unmount(adapter, ret, ret.ctx, ret, false);
|
|
433
|
+
}
|
|
434
|
+
return adapter.read(unwrap(getChildValues(ret)));
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
commit(adapter, ret, ret, ret.ctx, ret.scope, 0, schedulePromises, undefined);
|
|
438
|
+
if (schedulePromises.length > 0) {
|
|
439
|
+
return Promise.all(schedulePromises).then(() => {
|
|
440
|
+
if (typeof root !== "object" || root === null) {
|
|
441
|
+
unmount(adapter, ret, ret.ctx, ret, false);
|
|
442
|
+
}
|
|
443
|
+
return adapter.read(unwrap(getChildValues(ret)));
|
|
444
|
+
});
|
|
402
445
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
unmount(renderer, ret, ctx, ret);
|
|
446
|
+
if (typeof root !== "object" || root === null) {
|
|
447
|
+
unmount(adapter, ret, ret.ctx, ret, false);
|
|
406
448
|
}
|
|
407
|
-
return
|
|
449
|
+
return adapter.read(unwrap(getChildValues(ret)));
|
|
408
450
|
}
|
|
409
|
-
function diffChildren(
|
|
451
|
+
function diffChildren(adapter, root, host, ctx, scope, parent, newChildren) {
|
|
410
452
|
const oldRetained = wrap(parent.children);
|
|
411
453
|
const newRetained = [];
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
let graveyard;
|
|
454
|
+
const newChildren1 = arrayify(newChildren);
|
|
455
|
+
const diffs = [];
|
|
415
456
|
let childrenByKey;
|
|
416
457
|
let seenKeys;
|
|
417
458
|
let isAsync = false;
|
|
418
|
-
// When hydrating, sibling element trees must be rendered in order, because
|
|
419
|
-
// we do not know how many DOM nodes an element will render.
|
|
420
|
-
let hydrationBlock;
|
|
421
459
|
let oi = 0;
|
|
422
460
|
let oldLength = oldRetained.length;
|
|
423
|
-
|
|
461
|
+
let graveyard;
|
|
462
|
+
for (let ni = 0, newLength = newChildren1.length; ni < newLength; ni++) {
|
|
424
463
|
// length checks to prevent index out of bounds deoptimizations.
|
|
425
464
|
let ret = oi >= oldLength ? undefined : oldRetained[oi];
|
|
426
|
-
let child = narrow(
|
|
465
|
+
let child = narrow(newChildren1[ni]);
|
|
427
466
|
{
|
|
428
467
|
// aligning new children with old retainers
|
|
429
|
-
let oldKey = typeof ret === "object" ? ret.el.key : undefined;
|
|
430
|
-
let newKey = typeof child === "object" ? child.key : undefined;
|
|
468
|
+
let oldKey = typeof ret === "object" ? ret.el.props.key : undefined;
|
|
469
|
+
let newKey = typeof child === "object" ? child.props.key : undefined;
|
|
431
470
|
if (newKey !== undefined && seenKeys && seenKeys.has(newKey)) {
|
|
432
|
-
console.error(
|
|
433
|
-
|
|
471
|
+
console.error(`Duplicate key found in <${getTagName(parent.el.tag)}>`, newKey);
|
|
472
|
+
child = cloneElement(child);
|
|
473
|
+
newKey = child.props.key = undefined;
|
|
434
474
|
}
|
|
435
475
|
if (oldKey === newKey) {
|
|
436
476
|
if (childrenByKey !== undefined && newKey !== undefined) {
|
|
@@ -444,7 +484,7 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children, hydrat
|
|
|
444
484
|
while (ret !== undefined && oldKey !== undefined) {
|
|
445
485
|
oi++;
|
|
446
486
|
ret = oldRetained[oi];
|
|
447
|
-
oldKey = typeof ret === "object" ? ret.el.key : undefined;
|
|
487
|
+
oldKey = typeof ret === "object" ? ret.el.props.key : undefined;
|
|
448
488
|
}
|
|
449
489
|
oi++;
|
|
450
490
|
}
|
|
@@ -457,405 +497,748 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children, hydrat
|
|
|
457
497
|
}
|
|
458
498
|
}
|
|
459
499
|
}
|
|
460
|
-
|
|
461
|
-
let value;
|
|
500
|
+
let diff = undefined;
|
|
462
501
|
if (typeof child === "object") {
|
|
463
|
-
|
|
464
|
-
|
|
502
|
+
let childCopied = false;
|
|
503
|
+
if (child.tag === Copy) {
|
|
504
|
+
childCopied = true;
|
|
505
|
+
}
|
|
506
|
+
else if (typeof ret === "object" &&
|
|
507
|
+
ret.el === child &&
|
|
508
|
+
getFlag(ret, DidCommit)) {
|
|
509
|
+
// If the child is the same as the retained element, we skip
|
|
510
|
+
// re-rendering.
|
|
511
|
+
childCopied = true;
|
|
465
512
|
}
|
|
466
513
|
else {
|
|
467
|
-
|
|
468
|
-
let copy = false;
|
|
469
|
-
if (typeof ret === "object" && ret.el.tag === child.tag) {
|
|
470
|
-
oldProps = ret.el.props;
|
|
514
|
+
if (ret && ret.el.tag === child.tag) {
|
|
471
515
|
ret.el = child;
|
|
472
|
-
if (child.copy) {
|
|
473
|
-
|
|
474
|
-
copy = true;
|
|
516
|
+
if (child.props.copy && typeof child.props.copy !== "string") {
|
|
517
|
+
childCopied = true;
|
|
475
518
|
}
|
|
476
519
|
}
|
|
477
|
-
else {
|
|
478
|
-
|
|
479
|
-
|
|
520
|
+
else if (ret) {
|
|
521
|
+
// we do not need to add the retainer to the graveyard if it is the
|
|
522
|
+
// fallback of another retainer
|
|
523
|
+
// search for the tag in fallback chain
|
|
524
|
+
let candidateFound = false;
|
|
525
|
+
for (let predecessor = ret, candidate = ret.fallback; candidate; predecessor = candidate, candidate = candidate.fallback) {
|
|
526
|
+
if (candidate.el.tag === child.tag) {
|
|
527
|
+
// If we find a retainer in the fallback chain with the same tag,
|
|
528
|
+
// we reuse it rather than creating a new retainer to preserve
|
|
529
|
+
// state. This behavior is useful for when a Suspense component
|
|
530
|
+
// re-renders and the children are re-rendered quickly.
|
|
531
|
+
const clone = cloneRetainer(candidate);
|
|
532
|
+
setFlag(clone, IsResurrecting);
|
|
533
|
+
predecessor.fallback = clone;
|
|
534
|
+
const fallback = ret;
|
|
535
|
+
ret = candidate;
|
|
536
|
+
ret.el = child;
|
|
537
|
+
ret.fallback = fallback;
|
|
538
|
+
setFlag(ret, DidDiff, false);
|
|
539
|
+
candidateFound = true;
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (!candidateFound) {
|
|
544
|
+
const fallback = ret;
|
|
545
|
+
ret = new Retainer(child);
|
|
546
|
+
ret.fallback = fallback;
|
|
480
547
|
}
|
|
481
|
-
const fallback = ret;
|
|
482
|
-
ret = new Retainer(child);
|
|
483
|
-
ret.fallbackValue = fallback;
|
|
484
548
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
value = hydrationBlock
|
|
488
|
-
? hydrationBlock.then(() => updateRaw(renderer, ret, scope, oldProps, hydrationData))
|
|
489
|
-
: updateRaw(renderer, ret, scope, oldProps, hydrationData);
|
|
549
|
+
else {
|
|
550
|
+
ret = new Retainer(child);
|
|
490
551
|
}
|
|
552
|
+
if (childCopied && getFlag(ret, DidCommit)) ;
|
|
553
|
+
else if (child.tag === Raw || child.tag === Text) ;
|
|
491
554
|
else if (child.tag === Fragment) {
|
|
492
|
-
|
|
493
|
-
? hydrationBlock.then(() => updateFragment(renderer, root, host, ctx, scope, ret, hydrationData))
|
|
494
|
-
: updateFragment(renderer, root, host, ctx, scope, ret, hydrationData);
|
|
555
|
+
diff = diffChildren(adapter, root, host, ctx, scope, ret, ret.el.props.children);
|
|
495
556
|
}
|
|
496
557
|
else if (typeof child.tag === "function") {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
558
|
+
diff = diffComponent(adapter, root, host, ctx, scope, ret);
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
diff = diffHost(adapter, root, ctx, scope, ret);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (typeof ret === "object") {
|
|
565
|
+
if (childCopied) {
|
|
566
|
+
setFlag(ret, IsCopied);
|
|
567
|
+
diff = getInflightDiff(ret);
|
|
500
568
|
}
|
|
501
569
|
else {
|
|
502
|
-
|
|
503
|
-
? hydrationBlock.then(() => updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData))
|
|
504
|
-
: updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData);
|
|
570
|
+
setFlag(ret, IsCopied, false);
|
|
505
571
|
}
|
|
506
572
|
}
|
|
507
|
-
if (isPromiseLike(
|
|
573
|
+
if (isPromiseLike(diff)) {
|
|
508
574
|
isAsync = true;
|
|
509
|
-
|
|
510
|
-
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
else if (typeof child === "string") {
|
|
578
|
+
if (typeof ret === "object" && ret.el.tag === Text) {
|
|
579
|
+
ret.el.props.value = child;
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
if (typeof ret === "object") {
|
|
583
|
+
(graveyard = graveyard || []).push(ret);
|
|
511
584
|
}
|
|
585
|
+
ret = new Retainer(createElement(Text, { value: child }));
|
|
512
586
|
}
|
|
513
587
|
}
|
|
514
588
|
else {
|
|
515
|
-
// child is a string or undefined
|
|
516
589
|
if (typeof ret === "object") {
|
|
517
590
|
(graveyard = graveyard || []).push(ret);
|
|
518
591
|
}
|
|
519
|
-
|
|
520
|
-
value = ret = renderer.text(child, scope, hydrationData);
|
|
521
|
-
}
|
|
522
|
-
else {
|
|
523
|
-
ret = undefined;
|
|
524
|
-
}
|
|
592
|
+
ret = undefined;
|
|
525
593
|
}
|
|
526
|
-
|
|
594
|
+
diffs[ni] = diff;
|
|
527
595
|
newRetained[ni] = ret;
|
|
528
596
|
}
|
|
529
597
|
// cleanup remaining retainers
|
|
530
598
|
for (; oi < oldLength; oi++) {
|
|
531
599
|
const ret = oldRetained[oi];
|
|
532
600
|
if (typeof ret === "object" &&
|
|
533
|
-
(typeof ret.el.key === "undefined" ||
|
|
601
|
+
(typeof ret.el.props.key === "undefined" ||
|
|
534
602
|
!seenKeys ||
|
|
535
|
-
!seenKeys.has(ret.el.key))) {
|
|
603
|
+
!seenKeys.has(ret.el.props.key))) {
|
|
536
604
|
(graveyard = graveyard || []).push(ret);
|
|
537
605
|
}
|
|
538
606
|
}
|
|
539
607
|
if (childrenByKey !== undefined && childrenByKey.size > 0) {
|
|
540
|
-
|
|
608
|
+
graveyard = graveyard || [];
|
|
609
|
+
for (const ret of childrenByKey.values()) {
|
|
610
|
+
graveyard.push(ret);
|
|
611
|
+
}
|
|
541
612
|
}
|
|
542
613
|
parent.children = unwrap(newRetained);
|
|
543
614
|
if (isAsync) {
|
|
544
|
-
|
|
615
|
+
const diffs1 = Promise.all(diffs)
|
|
616
|
+
.then(() => undefined)
|
|
617
|
+
.finally(() => {
|
|
618
|
+
setFlag(parent, DidDiff);
|
|
545
619
|
if (graveyard) {
|
|
546
|
-
|
|
547
|
-
|
|
620
|
+
if (parent.graveyard) {
|
|
621
|
+
for (let i = 0; i < graveyard.length; i++) {
|
|
622
|
+
parent.graveyard.push(graveyard[i]);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
parent.graveyard = graveyard;
|
|
548
627
|
}
|
|
549
628
|
}
|
|
550
629
|
});
|
|
551
|
-
let
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
new Promise((resolve) => (
|
|
555
|
-
]);
|
|
556
|
-
if (parent.
|
|
557
|
-
parent.
|
|
558
|
-
}
|
|
559
|
-
parent.
|
|
560
|
-
return
|
|
561
|
-
parent.inflightValue = parent.fallbackValue = undefined;
|
|
562
|
-
return normalize(childValues);
|
|
563
|
-
});
|
|
630
|
+
let onNextDiffs;
|
|
631
|
+
const diffs2 = (parent.pendingDiff = safeRace([
|
|
632
|
+
diffs1,
|
|
633
|
+
new Promise((resolve) => (onNextDiffs = resolve)),
|
|
634
|
+
]));
|
|
635
|
+
if (parent.onNextDiff) {
|
|
636
|
+
parent.onNextDiff(diffs2);
|
|
637
|
+
}
|
|
638
|
+
parent.onNextDiff = onNextDiffs;
|
|
639
|
+
return diffs2;
|
|
564
640
|
}
|
|
565
641
|
else {
|
|
642
|
+
setFlag(parent, DidDiff);
|
|
566
643
|
if (graveyard) {
|
|
567
|
-
|
|
568
|
-
|
|
644
|
+
if (parent.graveyard) {
|
|
645
|
+
for (let i = 0; i < graveyard.length; i++) {
|
|
646
|
+
parent.graveyard.push(graveyard[i]);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
parent.graveyard = graveyard;
|
|
569
651
|
}
|
|
570
652
|
}
|
|
571
|
-
if (parent.
|
|
572
|
-
parent.
|
|
573
|
-
parent.
|
|
653
|
+
if (parent.onNextDiff) {
|
|
654
|
+
parent.onNextDiff(diffs);
|
|
655
|
+
parent.onNextDiff = undefined;
|
|
574
656
|
}
|
|
575
|
-
parent.
|
|
576
|
-
|
|
577
|
-
|
|
657
|
+
parent.pendingDiff = undefined;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function getInflightDiff(ret) {
|
|
661
|
+
// It is not enough to check pendingDiff because pendingDiff is the diff for
|
|
662
|
+
// children, but not the diff of an async component retainer's current run.
|
|
663
|
+
// For the latter we check ctx.inflight.
|
|
664
|
+
if (ret.ctx && ret.ctx.inflight) {
|
|
665
|
+
return ret.ctx.inflight[1];
|
|
666
|
+
}
|
|
667
|
+
else if (ret.pendingDiff) {
|
|
668
|
+
return ret.pendingDiff;
|
|
578
669
|
}
|
|
579
670
|
}
|
|
580
671
|
function createChildrenByKey(children, offset) {
|
|
581
672
|
const childrenByKey = new Map();
|
|
582
673
|
for (let i = offset; i < children.length; i++) {
|
|
583
674
|
const child = children[i];
|
|
584
|
-
if (typeof child === "object" &&
|
|
585
|
-
|
|
675
|
+
if (typeof child === "object" &&
|
|
676
|
+
typeof child.el.props.key !== "undefined") {
|
|
677
|
+
childrenByKey.set(child.el.props.key, child);
|
|
586
678
|
}
|
|
587
679
|
}
|
|
588
680
|
return childrenByKey;
|
|
589
681
|
}
|
|
590
|
-
function
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if (ctx && ctx.f & IsUpdating && ctx.inflightValue) {
|
|
596
|
-
return ctx.inflightValue;
|
|
682
|
+
function diffHost(adapter, root, ctx, scope, ret) {
|
|
683
|
+
const el = ret.el;
|
|
684
|
+
const tag = el.tag;
|
|
685
|
+
if (el.tag === Portal) {
|
|
686
|
+
root = ret.value = el.props.root;
|
|
597
687
|
}
|
|
598
|
-
|
|
599
|
-
|
|
688
|
+
if (getFlag(ret, DidCommit)) {
|
|
689
|
+
scope = ret.scope;
|
|
600
690
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
ret.el.ref(ret.value);
|
|
609
|
-
}
|
|
691
|
+
else {
|
|
692
|
+
scope = ret.scope = adapter.scope({
|
|
693
|
+
tag,
|
|
694
|
+
tagName: getTagName(tag),
|
|
695
|
+
props: el.props,
|
|
696
|
+
scope,
|
|
697
|
+
});
|
|
610
698
|
}
|
|
611
|
-
return ret.
|
|
699
|
+
return diffChildren(adapter, root, ret, ctx, scope, ret, ret.el.props.children);
|
|
612
700
|
}
|
|
613
|
-
function
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
ret.inflightValue = childValues.then((childValues) => unwrap(childValues));
|
|
617
|
-
return ret.inflightValue;
|
|
701
|
+
function commit(adapter, host, ret, ctx, scope, index, schedulePromises, hydrationNodes) {
|
|
702
|
+
if (getFlag(ret, IsCopied) && getFlag(ret, DidCommit)) {
|
|
703
|
+
return getValue(ret);
|
|
618
704
|
}
|
|
619
|
-
return unwrap(childValues);
|
|
620
|
-
}
|
|
621
|
-
function updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData) {
|
|
622
705
|
const el = ret.el;
|
|
623
706
|
const tag = el.tag;
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
707
|
+
if (typeof tag === "function" ||
|
|
708
|
+
tag === Fragment ||
|
|
709
|
+
tag === Portal ||
|
|
710
|
+
tag === Raw ||
|
|
711
|
+
tag === Text) {
|
|
712
|
+
if (typeof el.props.copy === "string") {
|
|
713
|
+
console.error(`String copy prop ignored for <${getTagName(tag)}>. Use booleans instead.`);
|
|
714
|
+
}
|
|
715
|
+
if (typeof el.props.hydrate === "string") {
|
|
716
|
+
console.error(`String hydrate prop ignored for <${getTagName(tag)}>. Use booleans instead.`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
let value;
|
|
720
|
+
let skippedHydrationNodes;
|
|
721
|
+
if (hydrationNodes &&
|
|
722
|
+
el.props.hydrate != null &&
|
|
723
|
+
!el.props.hydrate &&
|
|
724
|
+
typeof el.props.hydrate !== "string") {
|
|
725
|
+
skippedHydrationNodes = hydrationNodes;
|
|
726
|
+
hydrationNodes = undefined;
|
|
727
|
+
}
|
|
728
|
+
if (typeof tag === "function") {
|
|
729
|
+
ret.ctx.index = index;
|
|
730
|
+
value = commitComponent(ret.ctx, schedulePromises, hydrationNodes);
|
|
627
731
|
}
|
|
628
732
|
else {
|
|
629
|
-
if (
|
|
630
|
-
|
|
631
|
-
hydrationValue = value;
|
|
733
|
+
if (tag === Fragment) {
|
|
734
|
+
value = commitChildren(adapter, host, ctx, scope, ret, index, schedulePromises, hydrationNodes);
|
|
632
735
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
736
|
+
else if (tag === Text) {
|
|
737
|
+
value = commitText(adapter, ret, el, scope, hydrationNodes);
|
|
738
|
+
}
|
|
739
|
+
else if (tag === Raw) {
|
|
740
|
+
value = commitRaw(adapter, host, ret, scope, hydrationNodes);
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
value = commitHost(adapter, ret, ctx, schedulePromises, hydrationNodes);
|
|
744
|
+
}
|
|
745
|
+
if (ret.fallback) {
|
|
746
|
+
unmount(adapter, host, ctx, ret.fallback, false);
|
|
747
|
+
ret.fallback = undefined;
|
|
640
748
|
}
|
|
641
749
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
ret.inflightValue = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue));
|
|
645
|
-
return ret.inflightValue;
|
|
750
|
+
if (skippedHydrationNodes) {
|
|
751
|
+
skippedHydrationNodes.splice(0, wrap(value).length);
|
|
646
752
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if (typeof ret.el.ref === "function") {
|
|
655
|
-
ret.el.ref(value);
|
|
753
|
+
if (!getFlag(ret, DidCommit)) {
|
|
754
|
+
setFlag(ret, DidCommit);
|
|
755
|
+
if (typeof tag !== "function" &&
|
|
756
|
+
tag !== Fragment &&
|
|
757
|
+
tag !== Portal &&
|
|
758
|
+
typeof el.props.ref === "function") {
|
|
759
|
+
el.props.ref(adapter.read(value));
|
|
656
760
|
}
|
|
657
761
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
762
|
+
return value;
|
|
763
|
+
}
|
|
764
|
+
function commitChildren(adapter, host, ctx, scope, parent, index, schedulePromises, hydrationNodes) {
|
|
765
|
+
let values = [];
|
|
766
|
+
for (let i = 0, children = wrap(parent.children); i < children.length; i++) {
|
|
767
|
+
let child = children[i];
|
|
768
|
+
let schedulePromises1;
|
|
769
|
+
let isSchedulingFallback = false;
|
|
770
|
+
while (child &&
|
|
771
|
+
((!getFlag(child, DidDiff) && child.fallback) ||
|
|
772
|
+
getFlag(child, IsScheduling))) {
|
|
773
|
+
// If the child is scheduling, it is a component retainer so ctx will be
|
|
774
|
+
// defined.
|
|
775
|
+
if (getFlag(child, IsScheduling) && child.ctx.schedule) {
|
|
776
|
+
(schedulePromises1 = schedulePromises1 || []).push(child.ctx.schedule.promise);
|
|
777
|
+
isSchedulingFallback = true;
|
|
778
|
+
}
|
|
779
|
+
if (!getFlag(child, DidDiff) && getFlag(child, DidCommit)) {
|
|
780
|
+
// If this child has not diffed but has committed, it means it is a
|
|
781
|
+
// fallback that is being resurrected.
|
|
782
|
+
for (const node of getChildValues(child)) {
|
|
783
|
+
adapter.remove({
|
|
784
|
+
node,
|
|
785
|
+
parentNode: host.value,
|
|
786
|
+
isNested: false,
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
child = child.fallback;
|
|
791
|
+
// When a scheduling component is mounting asynchronously but diffs
|
|
792
|
+
// immediately, it will cause previous async diffs to settle due to the
|
|
793
|
+
// chasing mechanism. This would cause earlier renders to resolve sooner
|
|
794
|
+
// than expected, because the render would be missing both its usual
|
|
795
|
+
// children and the children of the scheduling render. Therefore, we need
|
|
796
|
+
// to defer the settling of previous renders until either that render
|
|
797
|
+
// settles, or the scheduling component finally finishes scheduling.
|
|
798
|
+
//
|
|
799
|
+
// To do this, we take advantage of the fact that commits for aborted
|
|
800
|
+
// renders will still fire and walk the tree. During that commit walk,
|
|
801
|
+
// when we encounter a scheduling element, we push a race of the
|
|
802
|
+
// scheduling promise with the inflight diff of the async fallback
|
|
803
|
+
// fallback to schedulePromises to delay the initiator.
|
|
804
|
+
//
|
|
805
|
+
// However, we need to make sure we only use the inflight diffs for the
|
|
806
|
+
// fallback which we are trying to delay, in the case of multiple renders
|
|
807
|
+
// and fallbacks. To do this, we take advantage of the fact that when
|
|
808
|
+
// multiple renders race (e.g., render1->render2->render3->scheduling
|
|
809
|
+
// component), the chasing mechanism will call stale commits in reverse
|
|
810
|
+
// order.
|
|
811
|
+
//
|
|
812
|
+
// We can use this ordering to delay to find which fallbacks we need to
|
|
813
|
+
// add to the race. Each commit call progressively marks an additional
|
|
814
|
+
// fallback as a scheduling fallback, and does not contribute to the
|
|
815
|
+
// scheduling promises if it is further than the last seen level.
|
|
816
|
+
//
|
|
817
|
+
// This prevents promise contamination where newer renders settle early
|
|
818
|
+
// due to diffs from older renders.
|
|
819
|
+
if (schedulePromises1 && isSchedulingFallback && child) {
|
|
820
|
+
if (!getFlag(child, DidDiff)) {
|
|
821
|
+
const inflightDiff = getInflightDiff(child);
|
|
822
|
+
schedulePromises1.push(inflightDiff);
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
// If a scheduling component's fallback has already diffed, we do not
|
|
826
|
+
// need delay the render.
|
|
827
|
+
schedulePromises1 = undefined;
|
|
828
|
+
}
|
|
829
|
+
if (getFlag(child, IsSchedulingFallback)) {
|
|
830
|
+
// This fallback was marked by a more recent commit - keep processing
|
|
831
|
+
// deeper levels
|
|
832
|
+
isSchedulingFallback = true;
|
|
833
|
+
}
|
|
834
|
+
else {
|
|
835
|
+
// First unmarked fallback we've encountered - mark it and stop
|
|
836
|
+
// contributing to schedulePromises1 for deeper levels.
|
|
837
|
+
setFlag(child, IsSchedulingFallback, true);
|
|
838
|
+
isSchedulingFallback = false;
|
|
839
|
+
}
|
|
666
840
|
}
|
|
667
841
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
842
|
+
if (schedulePromises1 && schedulePromises1.length > 1) {
|
|
843
|
+
schedulePromises.push(safeRace(schedulePromises1));
|
|
844
|
+
}
|
|
845
|
+
if (child) {
|
|
846
|
+
const value = commit(adapter, host, child, ctx, scope, index, schedulePromises, hydrationNodes);
|
|
847
|
+
if (Array.isArray(value)) {
|
|
848
|
+
for (let j = 0; j < value.length; j++) {
|
|
849
|
+
values.push(value[j]);
|
|
850
|
+
}
|
|
851
|
+
index += value.length;
|
|
674
852
|
}
|
|
675
|
-
else if (
|
|
676
|
-
|
|
853
|
+
else if (value) {
|
|
854
|
+
values.push(value);
|
|
855
|
+
index++;
|
|
677
856
|
}
|
|
678
857
|
}
|
|
679
858
|
}
|
|
680
|
-
if (
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
859
|
+
if (parent.graveyard) {
|
|
860
|
+
for (let i = 0; i < parent.graveyard.length; i++) {
|
|
861
|
+
const child = parent.graveyard[i];
|
|
862
|
+
unmount(adapter, host, ctx, child, false);
|
|
684
863
|
}
|
|
685
|
-
|
|
864
|
+
parent.graveyard = undefined;
|
|
686
865
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
return;
|
|
866
|
+
if (parent.lingerers) {
|
|
867
|
+
// if parent.lingerers is set, a descendant component is unmounting
|
|
868
|
+
// asynchronously, so we overwrite values to include lingerering DOM nodes.
|
|
869
|
+
values = getChildValues(parent);
|
|
692
870
|
}
|
|
871
|
+
return values;
|
|
872
|
+
}
|
|
873
|
+
function commitText(adapter, ret, el, scope, hydrationNodes) {
|
|
874
|
+
const value = adapter.text({
|
|
875
|
+
value: el.props.value,
|
|
876
|
+
scope,
|
|
877
|
+
oldNode: ret.value,
|
|
878
|
+
hydrationNodes,
|
|
879
|
+
});
|
|
880
|
+
ret.value = value;
|
|
693
881
|
return value;
|
|
694
882
|
}
|
|
695
|
-
function
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
883
|
+
function commitRaw(adapter, host, ret, scope, hydrationNodes) {
|
|
884
|
+
if (!ret.oldProps || ret.oldProps.value !== ret.el.props.value) {
|
|
885
|
+
const oldNodes = wrap(ret.value);
|
|
886
|
+
for (let i = 0; i < oldNodes.length; i++) {
|
|
887
|
+
const oldNode = oldNodes[i];
|
|
888
|
+
adapter.remove({
|
|
889
|
+
node: oldNode,
|
|
890
|
+
parentNode: host.value,
|
|
891
|
+
isNested: false,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
ret.value = adapter.raw({
|
|
895
|
+
value: ret.el.props.value,
|
|
896
|
+
scope,
|
|
897
|
+
hydrationNodes,
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
ret.oldProps = stripSpecialProps(ret.el.props);
|
|
901
|
+
return ret.value;
|
|
902
|
+
}
|
|
903
|
+
function commitHost(adapter, ret, ctx, schedulePromises, hydrationNodes) {
|
|
904
|
+
if (getFlag(ret, IsCopied) && getFlag(ret, DidCommit)) {
|
|
905
|
+
return getValue(ret);
|
|
699
906
|
}
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
907
|
+
const tag = ret.el.tag;
|
|
908
|
+
const props = stripSpecialProps(ret.el.props);
|
|
909
|
+
const oldProps = ret.oldProps;
|
|
910
|
+
let node = ret.value;
|
|
911
|
+
let copyProps;
|
|
912
|
+
let copyChildren = false;
|
|
913
|
+
if (oldProps) {
|
|
914
|
+
for (const propName in props) {
|
|
915
|
+
if (props[propName] === Copy) {
|
|
916
|
+
// The Copy tag can be used to skip the patching of a prop.
|
|
917
|
+
// <div class={shouldPatchClass ? "class-name" : Copy} />
|
|
918
|
+
props[propName] = oldProps[propName];
|
|
919
|
+
(copyProps = copyProps || new Set()).add(propName);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
if (typeof ret.el.props.copy === "string") {
|
|
923
|
+
const copyMetaProp = new MetaProp("copy", ret.el.props.copy);
|
|
924
|
+
if (copyMetaProp.include) {
|
|
925
|
+
for (const propName of copyMetaProp.props) {
|
|
926
|
+
if (propName in oldProps) {
|
|
927
|
+
props[propName] = oldProps[propName];
|
|
928
|
+
(copyProps = copyProps || new Set()).add(propName);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
for (const propName in oldProps) {
|
|
934
|
+
if (!copyMetaProp.props.has(propName)) {
|
|
935
|
+
props[propName] = oldProps[propName];
|
|
936
|
+
(copyProps = copyProps || new Set()).add(propName);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
copyChildren = copyMetaProp.includes("children");
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
const scope = ret.scope;
|
|
944
|
+
let childHydrationNodes;
|
|
945
|
+
let quietProps;
|
|
946
|
+
let hydrationMetaProp;
|
|
947
|
+
if (!getFlag(ret, DidCommit)) {
|
|
948
|
+
if (tag === Portal) {
|
|
949
|
+
if (ret.el.props.hydrate && typeof ret.el.props.hydrate !== "string") {
|
|
950
|
+
childHydrationNodes = adapter.adopt({
|
|
951
|
+
tag,
|
|
952
|
+
tagName: getTagName(tag),
|
|
953
|
+
node,
|
|
954
|
+
props,
|
|
955
|
+
scope,
|
|
956
|
+
});
|
|
957
|
+
if (childHydrationNodes) {
|
|
958
|
+
for (let i = 0; i < childHydrationNodes.length; i++) {
|
|
959
|
+
adapter.remove({
|
|
960
|
+
node: childHydrationNodes[i],
|
|
961
|
+
parentNode: node,
|
|
962
|
+
isNested: false,
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
else {
|
|
969
|
+
if (!node && hydrationNodes) {
|
|
970
|
+
const nextChild = hydrationNodes.shift();
|
|
971
|
+
if (typeof ret.el.props.hydrate === "string") {
|
|
972
|
+
hydrationMetaProp = new MetaProp("hydration", ret.el.props.hydrate);
|
|
973
|
+
if (hydrationMetaProp.include) {
|
|
974
|
+
// if we're in inclusive mode, we add all props to quietProps and
|
|
975
|
+
// remove props specified in the metaprop
|
|
976
|
+
quietProps = new Set(Object.keys(props));
|
|
977
|
+
for (const propName of hydrationMetaProp.props) {
|
|
978
|
+
quietProps.delete(propName);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
else {
|
|
982
|
+
quietProps = hydrationMetaProp.props;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
childHydrationNodes = adapter.adopt({
|
|
986
|
+
tag,
|
|
987
|
+
tagName: getTagName(tag),
|
|
988
|
+
node: nextChild,
|
|
989
|
+
props,
|
|
990
|
+
scope,
|
|
991
|
+
});
|
|
992
|
+
if (childHydrationNodes) {
|
|
993
|
+
node = nextChild;
|
|
994
|
+
for (let i = 0; i < childHydrationNodes.length; i++) {
|
|
995
|
+
adapter.remove({
|
|
996
|
+
node: childHydrationNodes[i],
|
|
997
|
+
parentNode: node,
|
|
998
|
+
isNested: false,
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
708
1001
|
}
|
|
709
1002
|
}
|
|
710
|
-
|
|
711
|
-
|
|
1003
|
+
// TODO: For some reason, there are cases where the node is already set
|
|
1004
|
+
// and the DidCommit flag is false. Not checking for node fails a test
|
|
1005
|
+
// where a child dispatches an event in a schedule callback, the parent
|
|
1006
|
+
// listens for this event and refreshes.
|
|
1007
|
+
if (!node) {
|
|
1008
|
+
node = adapter.create({
|
|
1009
|
+
tag,
|
|
1010
|
+
tagName: getTagName(tag),
|
|
1011
|
+
props,
|
|
1012
|
+
scope,
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
ret.value = node;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (tag !== Portal) {
|
|
1019
|
+
adapter.patch({
|
|
1020
|
+
tag,
|
|
1021
|
+
tagName: getTagName(tag),
|
|
1022
|
+
node,
|
|
1023
|
+
props,
|
|
1024
|
+
oldProps,
|
|
1025
|
+
scope,
|
|
1026
|
+
copyProps,
|
|
1027
|
+
isHydrating: !!childHydrationNodes,
|
|
1028
|
+
quietProps,
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
if (!copyChildren) {
|
|
1032
|
+
const children = commitChildren(adapter, ret, ctx, scope, ret, 0, schedulePromises, hydrationMetaProp && !hydrationMetaProp.includes("children")
|
|
1033
|
+
? undefined
|
|
1034
|
+
: childHydrationNodes);
|
|
1035
|
+
adapter.arrange({
|
|
1036
|
+
tag,
|
|
1037
|
+
tagName: getTagName(tag),
|
|
1038
|
+
node: node,
|
|
1039
|
+
props,
|
|
1040
|
+
children,
|
|
1041
|
+
oldProps,
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
ret.oldProps = props;
|
|
1045
|
+
if (tag === Portal) {
|
|
1046
|
+
flush(adapter, ret.value);
|
|
1047
|
+
// The root passed to Portal elements are opaque to parents so we return
|
|
1048
|
+
// undefined here.
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
return node;
|
|
1052
|
+
}
|
|
1053
|
+
class MetaProp {
|
|
1054
|
+
constructor(propName, propValue) {
|
|
1055
|
+
this.include = true;
|
|
1056
|
+
this.props = new Set();
|
|
1057
|
+
let noBangs = true;
|
|
1058
|
+
let allBangs = true;
|
|
1059
|
+
const tokens = propValue.split(/[,\s]+/);
|
|
1060
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1061
|
+
const token = tokens[i].trim();
|
|
1062
|
+
if (!token) {
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
else if (token.startsWith("!")) {
|
|
1066
|
+
noBangs = false;
|
|
1067
|
+
this.props.add(token.slice(1));
|
|
712
1068
|
}
|
|
713
1069
|
else {
|
|
714
|
-
|
|
1070
|
+
allBangs = false;
|
|
1071
|
+
this.props.add(token);
|
|
715
1072
|
}
|
|
716
1073
|
}
|
|
1074
|
+
if (!allBangs && !noBangs) {
|
|
1075
|
+
console.error(`Invalid ${propName} prop "${propValue}".\nUse prop or !prop but not both.`);
|
|
1076
|
+
this.include = true;
|
|
1077
|
+
this.props.clear();
|
|
1078
|
+
}
|
|
1079
|
+
else {
|
|
1080
|
+
this.include = noBangs;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
includes(propName) {
|
|
1084
|
+
if (this.include) {
|
|
1085
|
+
return this.props.has(propName);
|
|
1086
|
+
}
|
|
1087
|
+
else {
|
|
1088
|
+
return !this.props.has(propName);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
function contextContains(parent, child) {
|
|
1093
|
+
for (let current = child; current !== undefined; current = current.parent) {
|
|
1094
|
+
if (current === parent) {
|
|
1095
|
+
return true;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
return false;
|
|
1099
|
+
}
|
|
1100
|
+
// When rendering is done without a root, we use this special anonymous root to
|
|
1101
|
+
// make sure after callbacks are still called.
|
|
1102
|
+
const ANONYMOUS_ROOT = {};
|
|
1103
|
+
function flush(adapter, root, initiator) {
|
|
1104
|
+
if (root != null) {
|
|
1105
|
+
adapter.finalize(root);
|
|
1106
|
+
}
|
|
1107
|
+
if (typeof root !== "object" || root === null) {
|
|
1108
|
+
root = ANONYMOUS_ROOT;
|
|
1109
|
+
}
|
|
1110
|
+
// The initiator is the context which initiated the rendering process. If
|
|
1111
|
+
// initiator is defined we call and clear all flush callbacks which are
|
|
1112
|
+
// registered with the initiator or with a child context of the initiator,
|
|
1113
|
+
// because they are fully rendered.
|
|
1114
|
+
//
|
|
1115
|
+
// If no initiator is provided, we can call and clear all flush callbacks
|
|
1116
|
+
// which are not scheduling.
|
|
1117
|
+
const afterMap = afterMapByRoot.get(root);
|
|
1118
|
+
if (afterMap) {
|
|
1119
|
+
const afterMap1 = new Map();
|
|
1120
|
+
for (const [ctx, callbacks] of afterMap) {
|
|
1121
|
+
if (getFlag(ctx.ret, IsScheduling) ||
|
|
1122
|
+
(initiator && !contextContains(initiator, ctx))) {
|
|
1123
|
+
// copy over callbacks to the new map (defer them)
|
|
1124
|
+
afterMap.delete(ctx);
|
|
1125
|
+
afterMap1.set(ctx, callbacks);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (afterMap1.size) {
|
|
1129
|
+
afterMapByRoot.set(root, afterMap1);
|
|
1130
|
+
}
|
|
717
1131
|
else {
|
|
718
|
-
|
|
1132
|
+
afterMapByRoot.delete(root);
|
|
719
1133
|
}
|
|
720
|
-
for (const [ctx, callbacks] of
|
|
721
|
-
const value =
|
|
1134
|
+
for (const [ctx, callbacks] of afterMap) {
|
|
1135
|
+
const value = adapter.read(getValue(ctx.ret));
|
|
722
1136
|
for (const callback of callbacks) {
|
|
723
1137
|
callback(value);
|
|
724
1138
|
}
|
|
725
1139
|
}
|
|
726
1140
|
}
|
|
727
1141
|
}
|
|
728
|
-
function unmount(
|
|
1142
|
+
function unmount(adapter, host, ctx, ret, isNested) {
|
|
1143
|
+
// TODO: set the IsUnmounted flag consistently for all retainers
|
|
1144
|
+
if (ret.fallback) {
|
|
1145
|
+
unmount(adapter, host, ctx, ret.fallback, isNested);
|
|
1146
|
+
ret.fallback = undefined;
|
|
1147
|
+
}
|
|
1148
|
+
if (getFlag(ret, IsResurrecting)) {
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
if (ret.lingerers) {
|
|
1152
|
+
for (let i = 0; i < ret.lingerers.length; i++) {
|
|
1153
|
+
const lingerers = ret.lingerers[i];
|
|
1154
|
+
if (lingerers) {
|
|
1155
|
+
for (const lingerer of lingerers) {
|
|
1156
|
+
unmount(adapter, host, ctx, lingerer, isNested);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
ret.lingerers = undefined;
|
|
1161
|
+
}
|
|
729
1162
|
if (typeof ret.el.tag === "function") {
|
|
730
|
-
|
|
731
|
-
|
|
1163
|
+
unmountComponent(ret.ctx, isNested);
|
|
1164
|
+
}
|
|
1165
|
+
else if (ret.el.tag === Fragment) {
|
|
1166
|
+
unmountChildren(adapter, host, ctx, ret, isNested);
|
|
732
1167
|
}
|
|
733
1168
|
else if (ret.el.tag === Portal) {
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
1169
|
+
unmountChildren(adapter, ret, ctx, ret, false);
|
|
1170
|
+
if (ret.value != null) {
|
|
1171
|
+
adapter.finalize(ret.value);
|
|
1172
|
+
}
|
|
737
1173
|
}
|
|
738
|
-
else
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
}
|
|
1174
|
+
else {
|
|
1175
|
+
unmountChildren(adapter, ret, ctx, ret, true);
|
|
1176
|
+
if (getFlag(ret, DidCommit)) {
|
|
1177
|
+
if (ctx) {
|
|
1178
|
+
// Remove the value from every context which shares the same host.
|
|
1179
|
+
removeEventTargetDelegates(ctx.ctx, [ret.value], (ctx1) => ctx1[_ContextState].host === host);
|
|
1180
|
+
}
|
|
1181
|
+
adapter.remove({
|
|
1182
|
+
node: ret.value,
|
|
1183
|
+
parentNode: host.value,
|
|
1184
|
+
isNested,
|
|
1185
|
+
});
|
|
745
1186
|
}
|
|
746
|
-
renderer.dispose(ret.el.tag, ret.value, ret.el.props);
|
|
747
|
-
host = ret;
|
|
748
1187
|
}
|
|
749
|
-
|
|
750
|
-
|
|
1188
|
+
}
|
|
1189
|
+
function unmountChildren(adapter, host, ctx, ret, isNested) {
|
|
1190
|
+
if (ret.graveyard) {
|
|
1191
|
+
for (let i = 0; i < ret.graveyard.length; i++) {
|
|
1192
|
+
const child = ret.graveyard[i];
|
|
1193
|
+
unmount(adapter, host, ctx, child, isNested);
|
|
1194
|
+
}
|
|
1195
|
+
ret.graveyard = undefined;
|
|
1196
|
+
}
|
|
1197
|
+
for (let i = 0, children = wrap(ret.children); i < children.length; i++) {
|
|
751
1198
|
const child = children[i];
|
|
752
1199
|
if (typeof child === "object") {
|
|
753
|
-
unmount(
|
|
1200
|
+
unmount(adapter, host, ctx, child, isNested);
|
|
754
1201
|
}
|
|
755
1202
|
}
|
|
756
1203
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
* rearranged.
|
|
764
|
-
*/
|
|
765
|
-
const IsUpdating = 1 << 0;
|
|
766
|
-
/**
|
|
767
|
-
* A flag which is true when the component is synchronously executing.
|
|
768
|
-
*
|
|
769
|
-
* Used to guard against components triggering stack overflow or generator error.
|
|
770
|
-
*/
|
|
771
|
-
const IsSyncExecuting = 1 << 1;
|
|
1204
|
+
const provisionMaps = new WeakMap();
|
|
1205
|
+
const scheduleMap = new WeakMap();
|
|
1206
|
+
const cleanupMap = new WeakMap();
|
|
1207
|
+
// keys are roots
|
|
1208
|
+
const afterMapByRoot = new WeakMap();
|
|
1209
|
+
// TODO: allow ContextState to be initialized for testing purposes
|
|
772
1210
|
/**
|
|
773
|
-
*
|
|
1211
|
+
* @internal
|
|
1212
|
+
* The internal class which holds context data.
|
|
774
1213
|
*/
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
*/
|
|
779
|
-
const IsInForAwaitOfLoop = 1 << 3;
|
|
780
|
-
/**
|
|
781
|
-
* A flag which is true when the component starts the render loop but has not
|
|
782
|
-
* yielded yet.
|
|
783
|
-
*
|
|
784
|
-
* Used to make sure that components yield at least once per loop.
|
|
785
|
-
*/
|
|
786
|
-
const NeedsToYield = 1 << 4;
|
|
787
|
-
/**
|
|
788
|
-
* A flag used by async generator components in conjunction with the
|
|
789
|
-
* onAvailable callback to mark whether new props can be pulled via the context
|
|
790
|
-
* async iterator. See the Symbol.asyncIterator method and the
|
|
791
|
-
* resumeCtxIterator function.
|
|
792
|
-
*/
|
|
793
|
-
const PropsAvailable = 1 << 5;
|
|
794
|
-
/**
|
|
795
|
-
* A flag which is set when a component errors.
|
|
796
|
-
*
|
|
797
|
-
* This is mainly used to prevent some false positives in "component yields or
|
|
798
|
-
* returns undefined" warnings. The reason we’re using this versus IsUnmounted
|
|
799
|
-
* is a very troubling test (cascades sync generator parent and sync generator
|
|
800
|
-
* child) where synchronous code causes a stack overflow error in a
|
|
801
|
-
* non-deterministic way. Deeply disturbing stuff.
|
|
802
|
-
*/
|
|
803
|
-
const IsErrored = 1 << 6;
|
|
804
|
-
/**
|
|
805
|
-
* A flag which is set when the component is unmounted. Unmounted components
|
|
806
|
-
* are no longer in the element tree and cannot refresh or rerender.
|
|
807
|
-
*/
|
|
808
|
-
const IsUnmounted = 1 << 7;
|
|
809
|
-
/**
|
|
810
|
-
* A flag which indicates that the component is a sync generator component.
|
|
811
|
-
*/
|
|
812
|
-
const IsSyncGen = 1 << 8;
|
|
813
|
-
/**
|
|
814
|
-
* A flag which indicates that the component is an async generator component.
|
|
815
|
-
*/
|
|
816
|
-
const IsAsyncGen = 1 << 9;
|
|
817
|
-
/**
|
|
818
|
-
* A flag which is set while schedule callbacks are called.
|
|
819
|
-
*/
|
|
820
|
-
const IsScheduling = 1 << 10;
|
|
821
|
-
/**
|
|
822
|
-
* A flag which is set when a schedule callback calls refresh.
|
|
823
|
-
*/
|
|
824
|
-
const IsSchedulingRefresh = 1 << 11;
|
|
825
|
-
const provisionMaps = new WeakMap();
|
|
826
|
-
const scheduleMap = new WeakMap();
|
|
827
|
-
const cleanupMap = new WeakMap();
|
|
828
|
-
// keys are roots
|
|
829
|
-
const flushMaps = new WeakMap();
|
|
830
|
-
/**
|
|
831
|
-
* @internal
|
|
832
|
-
* The internal class which holds context data.
|
|
833
|
-
*/
|
|
834
|
-
class ContextImpl {
|
|
835
|
-
constructor(renderer, root, host, parent, scope, ret) {
|
|
836
|
-
this.f = 0;
|
|
837
|
-
this.owner = new Context(this);
|
|
838
|
-
this.renderer = renderer;
|
|
1214
|
+
class ContextState {
|
|
1215
|
+
constructor(adapter, root, host, parent, scope, ret) {
|
|
1216
|
+
this.adapter = adapter;
|
|
839
1217
|
this.root = root;
|
|
840
1218
|
this.host = host;
|
|
841
1219
|
this.parent = parent;
|
|
1220
|
+
// This property must be set after this.parent is set because the Context
|
|
1221
|
+
// constructor reads this.parent.
|
|
1222
|
+
this.ctx = new Context(this);
|
|
842
1223
|
this.scope = scope;
|
|
843
1224
|
this.ret = ret;
|
|
844
1225
|
this.iterator = undefined;
|
|
845
|
-
this.
|
|
846
|
-
this.
|
|
847
|
-
this.
|
|
848
|
-
this.enqueuedValue = undefined;
|
|
849
|
-
this.onProps = undefined;
|
|
1226
|
+
this.inflight = undefined;
|
|
1227
|
+
this.enqueued = undefined;
|
|
1228
|
+
this.onPropsProvided = undefined;
|
|
850
1229
|
this.onPropsRequested = undefined;
|
|
1230
|
+
this.pull = undefined;
|
|
1231
|
+
this.index = 0;
|
|
1232
|
+
this.schedule = undefined;
|
|
851
1233
|
}
|
|
852
1234
|
}
|
|
853
|
-
const
|
|
1235
|
+
const _ContextState = Symbol.for("crank.ContextState");
|
|
854
1236
|
/**
|
|
855
1237
|
* A class which is instantiated and passed to every component as its this
|
|
856
|
-
* value. Contexts form a tree just like elements and all
|
|
857
|
-
* element tree are connected via contexts. Components can
|
|
858
|
-
* communicate data upwards via events and downwards via
|
|
1238
|
+
* value/second parameter. Contexts form a tree just like elements and all
|
|
1239
|
+
* components in the element tree are connected via contexts. Components can
|
|
1240
|
+
* use this tree to communicate data upwards via events and downwards via
|
|
1241
|
+
* provisions.
|
|
859
1242
|
*
|
|
860
1243
|
* @template [T=*] - The expected shape of the props passed to the component,
|
|
861
1244
|
* or a component function. Used to strongly type the Context iterator methods.
|
|
@@ -863,17 +1246,18 @@ const _ContextImpl = Symbol.for("crank.ContextImpl");
|
|
|
863
1246
|
* places such as the return value of refresh and the argument passed to
|
|
864
1247
|
* schedule and cleanup callbacks.
|
|
865
1248
|
*/
|
|
866
|
-
class Context {
|
|
1249
|
+
class Context extends CustomEventTarget {
|
|
867
1250
|
// TODO: If we could make the constructor function take a nicer value, it
|
|
868
1251
|
// would be useful for testing purposes.
|
|
869
|
-
constructor(
|
|
870
|
-
|
|
1252
|
+
constructor(state) {
|
|
1253
|
+
super(state.parent ? state.parent.ctx : null);
|
|
1254
|
+
this[_ContextState] = state;
|
|
871
1255
|
}
|
|
872
1256
|
/**
|
|
873
1257
|
* The current props of the associated element.
|
|
874
1258
|
*/
|
|
875
1259
|
get props() {
|
|
876
|
-
return this[
|
|
1260
|
+
return this[_ContextState].ret.el.props;
|
|
877
1261
|
}
|
|
878
1262
|
/**
|
|
879
1263
|
* The current value of the associated element.
|
|
@@ -881,47 +1265,51 @@ class Context {
|
|
|
881
1265
|
* @deprecated
|
|
882
1266
|
*/
|
|
883
1267
|
get value() {
|
|
884
|
-
|
|
1268
|
+
console.warn("Context.value is deprecated.");
|
|
1269
|
+
return this[_ContextState].adapter.read(getValue(this[_ContextState].ret));
|
|
1270
|
+
}
|
|
1271
|
+
get isExecuting() {
|
|
1272
|
+
return getFlag(this[_ContextState].ret, IsExecuting);
|
|
1273
|
+
}
|
|
1274
|
+
get isUnmounted() {
|
|
1275
|
+
return getFlag(this[_ContextState].ret, IsUnmounted);
|
|
885
1276
|
}
|
|
886
1277
|
*[Symbol.iterator]() {
|
|
887
|
-
const ctx = this[
|
|
1278
|
+
const ctx = this[_ContextState];
|
|
1279
|
+
setFlag(ctx.ret, IsInForOfLoop);
|
|
888
1280
|
try {
|
|
889
|
-
ctx.
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
throw new Error("Context iterated twice without a yield");
|
|
1281
|
+
while (!getFlag(ctx.ret, IsUnmounted) && !getFlag(ctx.ret, IsErrored)) {
|
|
1282
|
+
if (getFlag(ctx.ret, NeedsToYield)) {
|
|
1283
|
+
throw new Error(`<${getTagName(ctx.ret.el.tag)}> context iterated twice without a yield`);
|
|
893
1284
|
}
|
|
894
1285
|
else {
|
|
895
|
-
ctx.
|
|
1286
|
+
setFlag(ctx.ret, NeedsToYield);
|
|
896
1287
|
}
|
|
897
1288
|
yield ctx.ret.el.props;
|
|
898
1289
|
}
|
|
899
1290
|
}
|
|
900
1291
|
finally {
|
|
901
|
-
ctx.
|
|
1292
|
+
setFlag(ctx.ret, IsInForOfLoop, false);
|
|
902
1293
|
}
|
|
903
1294
|
}
|
|
904
1295
|
async *[Symbol.asyncIterator]() {
|
|
905
|
-
const ctx = this[
|
|
906
|
-
|
|
907
|
-
throw new Error("Use for...of in sync generator components");
|
|
908
|
-
}
|
|
1296
|
+
const ctx = this[_ContextState];
|
|
1297
|
+
setFlag(ctx.ret, IsInForAwaitOfLoop);
|
|
909
1298
|
try {
|
|
910
|
-
ctx.
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
throw new Error("Context iterated twice without a yield");
|
|
1299
|
+
while (!getFlag(ctx.ret, IsUnmounted) && !getFlag(ctx.ret, IsErrored)) {
|
|
1300
|
+
if (getFlag(ctx.ret, NeedsToYield)) {
|
|
1301
|
+
throw new Error(`<${getTagName(ctx.ret.el.tag)}> context iterated twice without a yield`);
|
|
914
1302
|
}
|
|
915
1303
|
else {
|
|
916
|
-
ctx.
|
|
1304
|
+
setFlag(ctx.ret, NeedsToYield);
|
|
917
1305
|
}
|
|
918
|
-
if (ctx.
|
|
919
|
-
ctx.
|
|
1306
|
+
if (getFlag(ctx.ret, PropsAvailable)) {
|
|
1307
|
+
setFlag(ctx.ret, PropsAvailable, false);
|
|
920
1308
|
yield ctx.ret.el.props;
|
|
921
1309
|
}
|
|
922
1310
|
else {
|
|
923
|
-
const props = await new Promise((resolve) => (ctx.
|
|
924
|
-
if (ctx.
|
|
1311
|
+
const props = await new Promise((resolve) => (ctx.onPropsProvided = resolve));
|
|
1312
|
+
if (getFlag(ctx.ret, IsUnmounted) || getFlag(ctx.ret, IsErrored)) {
|
|
925
1313
|
break;
|
|
926
1314
|
}
|
|
927
1315
|
yield props;
|
|
@@ -933,7 +1321,7 @@ class Context {
|
|
|
933
1321
|
}
|
|
934
1322
|
}
|
|
935
1323
|
finally {
|
|
936
|
-
ctx.
|
|
1324
|
+
setFlag(ctx.ret, IsInForAwaitOfLoop, false);
|
|
937
1325
|
if (ctx.onPropsRequested) {
|
|
938
1326
|
ctx.onPropsRequested();
|
|
939
1327
|
ctx.onPropsRequested = undefined;
|
|
@@ -943,37 +1331,108 @@ class Context {
|
|
|
943
1331
|
/**
|
|
944
1332
|
* Re-executes a component.
|
|
945
1333
|
*
|
|
946
|
-
* @
|
|
1334
|
+
* @param callback - Optional callback to execute before refresh
|
|
1335
|
+
* @returns The rendered result of the component or a promise thereof if the
|
|
947
1336
|
* component or its children execute asynchronously.
|
|
948
|
-
*
|
|
949
|
-
* The refresh method works a little differently for async generator
|
|
950
|
-
* components, in that it will resume the Context’s props async iterator
|
|
951
|
-
* rather than resuming execution. This is because async generator components
|
|
952
|
-
* are perpetually resumed independent of updates, and rely on the props
|
|
953
|
-
* async iterator to suspend.
|
|
954
1337
|
*/
|
|
955
|
-
refresh() {
|
|
956
|
-
const ctx = this[
|
|
957
|
-
if (ctx.
|
|
958
|
-
console.error(
|
|
959
|
-
return ctx.
|
|
1338
|
+
refresh(callback) {
|
|
1339
|
+
const ctx = this[_ContextState];
|
|
1340
|
+
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
1341
|
+
console.error(`Component <${getTagName(ctx.ret.el.tag)}> is unmounted. Check the isUnmounted property if necessary.`);
|
|
1342
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1343
|
+
}
|
|
1344
|
+
else if (getFlag(ctx.ret, IsExecuting)) {
|
|
1345
|
+
console.error(`Component <${getTagName(ctx.ret.el.tag)}> is already executing Check the isExecuting property if necessary.`);
|
|
1346
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1347
|
+
}
|
|
1348
|
+
if (callback) {
|
|
1349
|
+
const result = callback();
|
|
1350
|
+
if (isPromiseLike(result)) {
|
|
1351
|
+
return Promise.resolve(result).then(() => {
|
|
1352
|
+
if (!getFlag(ctx.ret, IsUnmounted)) {
|
|
1353
|
+
return this.refresh();
|
|
1354
|
+
}
|
|
1355
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
960
1358
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1359
|
+
let diff;
|
|
1360
|
+
const schedulePromises = [];
|
|
1361
|
+
try {
|
|
1362
|
+
setFlag(ctx.ret, IsRefreshing);
|
|
1363
|
+
diff = enqueueComponent(ctx);
|
|
1364
|
+
if (isPromiseLike(diff)) {
|
|
1365
|
+
return diff
|
|
1366
|
+
.then(() => ctx.adapter.read(commitComponent(ctx, schedulePromises)))
|
|
1367
|
+
.then((result) => {
|
|
1368
|
+
if (schedulePromises.length) {
|
|
1369
|
+
return Promise.all(schedulePromises).then(() => {
|
|
1370
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
return result;
|
|
1374
|
+
})
|
|
1375
|
+
.catch((err) => {
|
|
1376
|
+
const diff = propagateError(ctx, err, schedulePromises);
|
|
1377
|
+
if (diff) {
|
|
1378
|
+
return diff.then(() => {
|
|
1379
|
+
if (schedulePromises.length) {
|
|
1380
|
+
return Promise.all(schedulePromises).then(() => {
|
|
1381
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
if (schedulePromises.length) {
|
|
1388
|
+
return Promise.all(schedulePromises).then(() => {
|
|
1389
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1393
|
+
})
|
|
1394
|
+
.finally(() => setFlag(ctx.ret, IsRefreshing, false));
|
|
1395
|
+
}
|
|
1396
|
+
const result = ctx.adapter.read(commitComponent(ctx, schedulePromises));
|
|
1397
|
+
if (schedulePromises.length) {
|
|
1398
|
+
return Promise.all(schedulePromises).then(() => {
|
|
1399
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
return result;
|
|
1403
|
+
}
|
|
1404
|
+
catch (err) {
|
|
1405
|
+
// TODO: await schedulePromises
|
|
1406
|
+
const diff = propagateError(ctx, err, schedulePromises);
|
|
1407
|
+
if (diff) {
|
|
1408
|
+
return diff
|
|
1409
|
+
.then(() => {
|
|
1410
|
+
if (schedulePromises.length) {
|
|
1411
|
+
return Promise.all(schedulePromises).then(() => {
|
|
1412
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
})
|
|
1416
|
+
.then(() => ctx.adapter.read(getValue(ctx.ret)));
|
|
1417
|
+
}
|
|
1418
|
+
if (schedulePromises.length) {
|
|
1419
|
+
return Promise.all(schedulePromises).then(() => {
|
|
1420
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
964
1424
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1425
|
+
finally {
|
|
1426
|
+
if (!isPromiseLike(diff)) {
|
|
1427
|
+
setFlag(ctx.ret, IsRefreshing, false);
|
|
1428
|
+
}
|
|
968
1429
|
}
|
|
969
|
-
return ctx.renderer.read(value);
|
|
970
1430
|
}
|
|
971
|
-
/**
|
|
972
|
-
* Registers a callback which fires when the component commits. Will only
|
|
973
|
-
* fire once per callback and update.
|
|
974
|
-
*/
|
|
975
1431
|
schedule(callback) {
|
|
976
|
-
|
|
1432
|
+
if (!callback) {
|
|
1433
|
+
return new Promise((resolve) => this.schedule(resolve));
|
|
1434
|
+
}
|
|
1435
|
+
const ctx = this[_ContextState];
|
|
977
1436
|
let callbacks = scheduleMap.get(ctx);
|
|
978
1437
|
if (!callbacks) {
|
|
979
1438
|
callbacks = new Set();
|
|
@@ -981,35 +1440,35 @@ class Context {
|
|
|
981
1440
|
}
|
|
982
1441
|
callbacks.add(callback);
|
|
983
1442
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
*/
|
|
988
|
-
flush(callback) {
|
|
989
|
-
const ctx = this[_ContextImpl];
|
|
990
|
-
if (typeof ctx.root !== "object" || ctx.root === null) {
|
|
991
|
-
return;
|
|
1443
|
+
after(callback) {
|
|
1444
|
+
if (!callback) {
|
|
1445
|
+
return new Promise((resolve) => this.after(resolve));
|
|
992
1446
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1447
|
+
const ctx = this[_ContextState];
|
|
1448
|
+
const root = ctx.root || ANONYMOUS_ROOT;
|
|
1449
|
+
let afterMap = afterMapByRoot.get(root);
|
|
1450
|
+
if (!afterMap) {
|
|
1451
|
+
afterMap = new Map();
|
|
1452
|
+
afterMapByRoot.set(root, afterMap);
|
|
997
1453
|
}
|
|
998
|
-
let callbacks =
|
|
1454
|
+
let callbacks = afterMap.get(ctx);
|
|
999
1455
|
if (!callbacks) {
|
|
1000
1456
|
callbacks = new Set();
|
|
1001
|
-
|
|
1457
|
+
afterMap.set(ctx, callbacks);
|
|
1002
1458
|
}
|
|
1003
1459
|
callbacks.add(callback);
|
|
1004
1460
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1461
|
+
flush(callback) {
|
|
1462
|
+
console.error("Context.flush() method has been renamed to after()");
|
|
1463
|
+
this.after(callback);
|
|
1464
|
+
}
|
|
1009
1465
|
cleanup(callback) {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1466
|
+
if (!callback) {
|
|
1467
|
+
return new Promise((resolve) => this.cleanup(resolve));
|
|
1468
|
+
}
|
|
1469
|
+
const ctx = this[_ContextState];
|
|
1470
|
+
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
1471
|
+
const value = ctx.adapter.read(getValue(ctx.ret));
|
|
1013
1472
|
callback(value);
|
|
1014
1473
|
return;
|
|
1015
1474
|
}
|
|
@@ -1021,7 +1480,7 @@ class Context {
|
|
|
1021
1480
|
callbacks.add(callback);
|
|
1022
1481
|
}
|
|
1023
1482
|
consume(key) {
|
|
1024
|
-
for (let ctx = this[
|
|
1483
|
+
for (let ctx = this[_ContextState].parent; ctx !== undefined; ctx = ctx.parent) {
|
|
1025
1484
|
const provisions = provisionMaps.get(ctx);
|
|
1026
1485
|
if (provisions && provisions.has(key)) {
|
|
1027
1486
|
return provisions.get(key);
|
|
@@ -1029,7 +1488,7 @@ class Context {
|
|
|
1029
1488
|
}
|
|
1030
1489
|
}
|
|
1031
1490
|
provide(key, value) {
|
|
1032
|
-
const ctx = this[
|
|
1491
|
+
const ctx = this[_ContextState];
|
|
1033
1492
|
let provisions = provisionMaps.get(ctx);
|
|
1034
1493
|
if (!provisions) {
|
|
1035
1494
|
provisions = new Map();
|
|
@@ -1037,977 +1496,781 @@ class Context {
|
|
|
1037
1496
|
}
|
|
1038
1497
|
provisions.set(key, value);
|
|
1039
1498
|
}
|
|
1040
|
-
|
|
1041
|
-
const ctx = this[
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
else {
|
|
1047
|
-
const listeners1 = listenersMap.get(ctx);
|
|
1048
|
-
if (listeners1) {
|
|
1049
|
-
listeners = listeners1;
|
|
1050
|
-
}
|
|
1051
|
-
else {
|
|
1052
|
-
listeners = [];
|
|
1053
|
-
listenersMap.set(ctx, listeners);
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
options = normalizeListenerOptions(options);
|
|
1057
|
-
let callback;
|
|
1058
|
-
if (typeof listener === "object") {
|
|
1059
|
-
callback = () => listener.handleEvent.apply(listener, arguments);
|
|
1499
|
+
[CustomEventTarget.dispatchEventOnSelf](ev) {
|
|
1500
|
+
const ctx = this[_ContextState];
|
|
1501
|
+
// dispatchEvent calls the prop callback if it exists
|
|
1502
|
+
let propCallback = ctx.ret.el.props["on" + ev.type];
|
|
1503
|
+
if (typeof propCallback === "function") {
|
|
1504
|
+
propCallback(ev);
|
|
1060
1505
|
}
|
|
1061
1506
|
else {
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
const i = listeners.indexOf(record);
|
|
1068
|
-
if (i !== -1) {
|
|
1069
|
-
listeners.splice(i, 1);
|
|
1070
|
-
}
|
|
1071
|
-
return callback.apply(this, arguments);
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
|
-
if (listeners.some((record1) => record.type === record1.type &&
|
|
1075
|
-
record.listener === record1.listener &&
|
|
1076
|
-
!record.options.capture === !record1.options.capture)) {
|
|
1077
|
-
return;
|
|
1078
|
-
}
|
|
1079
|
-
listeners.push(record);
|
|
1080
|
-
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1081
|
-
for (const value of getChildValues(ctx.ret)) {
|
|
1082
|
-
if (isEventTarget(value)) {
|
|
1083
|
-
value.addEventListener(record.type, record.callback, record.options);
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
removeEventListener(type, listener, options) {
|
|
1088
|
-
const ctx = this[_ContextImpl];
|
|
1089
|
-
const listeners = listenersMap.get(ctx);
|
|
1090
|
-
if (listeners == null || !isListenerOrListenerObject(listener)) {
|
|
1091
|
-
return;
|
|
1092
|
-
}
|
|
1093
|
-
const options1 = normalizeListenerOptions(options);
|
|
1094
|
-
const i = listeners.findIndex((record) => record.type === type &&
|
|
1095
|
-
record.listener === listener &&
|
|
1096
|
-
!record.options.capture === !options1.capture);
|
|
1097
|
-
if (i === -1) {
|
|
1098
|
-
return;
|
|
1099
|
-
}
|
|
1100
|
-
const record = listeners[i];
|
|
1101
|
-
listeners.splice(i, 1);
|
|
1102
|
-
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1103
|
-
for (const value of getChildValues(ctx.ret)) {
|
|
1104
|
-
if (isEventTarget(value)) {
|
|
1105
|
-
value.removeEventListener(record.type, record.callback, record.options);
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
dispatchEvent(ev) {
|
|
1110
|
-
const ctx = this[_ContextImpl];
|
|
1111
|
-
const path = [];
|
|
1112
|
-
for (let parent = ctx.parent; parent !== undefined; parent = parent.parent) {
|
|
1113
|
-
path.push(parent);
|
|
1114
|
-
}
|
|
1115
|
-
// We patch the stopImmediatePropagation method because ev.cancelBubble
|
|
1116
|
-
// only informs us if stopPropagation was called and there are no
|
|
1117
|
-
// properties which inform us if stopImmediatePropagation was called.
|
|
1118
|
-
let immediateCancelBubble = false;
|
|
1119
|
-
const stopImmediatePropagation = ev.stopImmediatePropagation;
|
|
1120
|
-
setEventProperty(ev, "stopImmediatePropagation", () => {
|
|
1121
|
-
immediateCancelBubble = true;
|
|
1122
|
-
return stopImmediatePropagation.call(ev);
|
|
1123
|
-
});
|
|
1124
|
-
setEventProperty(ev, "target", ctx.owner);
|
|
1125
|
-
// The only possible errors in this block are errors thrown by callbacks,
|
|
1126
|
-
// and dispatchEvent will only log these errors rather than throwing
|
|
1127
|
-
// them. Therefore, we place all code in a try block, log errors in the
|
|
1128
|
-
// catch block, and use an unsafe return statement in the finally block.
|
|
1129
|
-
//
|
|
1130
|
-
// Each early return within the try block returns true because while the
|
|
1131
|
-
// return value is overridden in the finally block, TypeScript
|
|
1132
|
-
// (justifiably) does not recognize the unsafe return statement.
|
|
1133
|
-
try {
|
|
1134
|
-
setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
|
|
1135
|
-
for (let i = path.length - 1; i >= 0; i--) {
|
|
1136
|
-
const target = path[i];
|
|
1137
|
-
const listeners = listenersMap.get(target);
|
|
1138
|
-
if (listeners) {
|
|
1139
|
-
setEventProperty(ev, "currentTarget", target.owner);
|
|
1140
|
-
for (const record of listeners) {
|
|
1141
|
-
if (record.type === ev.type && record.options.capture) {
|
|
1142
|
-
try {
|
|
1143
|
-
record.callback.call(target.owner, ev);
|
|
1144
|
-
}
|
|
1145
|
-
catch (err) {
|
|
1146
|
-
console.error(err);
|
|
1147
|
-
}
|
|
1148
|
-
if (immediateCancelBubble) {
|
|
1149
|
-
return true;
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
if (ev.cancelBubble) {
|
|
1155
|
-
return true;
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
{
|
|
1159
|
-
setEventProperty(ev, "eventPhase", AT_TARGET);
|
|
1160
|
-
setEventProperty(ev, "currentTarget", ctx.owner);
|
|
1161
|
-
// dispatchEvent calls the prop callback if it exists
|
|
1162
|
-
let propCallback = ctx.ret.el.props["on" + ev.type];
|
|
1163
|
-
if (typeof propCallback === "function") {
|
|
1164
|
-
propCallback(ev);
|
|
1165
|
-
if (immediateCancelBubble || ev.cancelBubble) {
|
|
1166
|
-
return true;
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
else {
|
|
1170
|
-
// Checks for camel-cased event props
|
|
1171
|
-
for (const propName in ctx.ret.el.props) {
|
|
1172
|
-
if (propName.toLowerCase() === "on" + ev.type.toLowerCase()) {
|
|
1173
|
-
propCallback = ctx.ret.el.props[propName];
|
|
1174
|
-
if (typeof propCallback === "function") {
|
|
1175
|
-
propCallback(ev);
|
|
1176
|
-
if (immediateCancelBubble || ev.cancelBubble) {
|
|
1177
|
-
return true;
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
const listeners = listenersMap.get(ctx);
|
|
1184
|
-
if (listeners) {
|
|
1185
|
-
for (const record of listeners) {
|
|
1186
|
-
if (record.type === ev.type) {
|
|
1187
|
-
try {
|
|
1188
|
-
record.callback.call(ctx.owner, ev);
|
|
1189
|
-
}
|
|
1190
|
-
catch (err) {
|
|
1191
|
-
console.error(err);
|
|
1192
|
-
}
|
|
1193
|
-
if (immediateCancelBubble) {
|
|
1194
|
-
return true;
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
if (ev.cancelBubble) {
|
|
1199
|
-
return true;
|
|
1507
|
+
for (const propName in ctx.ret.el.props) {
|
|
1508
|
+
if (propName.toLowerCase() === "on" + ev.type.toLowerCase()) {
|
|
1509
|
+
propCallback = ctx.ret.el.props[propName];
|
|
1510
|
+
if (typeof propCallback === "function") {
|
|
1511
|
+
propCallback(ev);
|
|
1200
1512
|
}
|
|
1201
1513
|
}
|
|
1202
1514
|
}
|
|
1203
|
-
if (ev.bubbles) {
|
|
1204
|
-
setEventProperty(ev, "eventPhase", BUBBLING_PHASE);
|
|
1205
|
-
for (let i = 0; i < path.length; i++) {
|
|
1206
|
-
const target = path[i];
|
|
1207
|
-
const listeners = listenersMap.get(target);
|
|
1208
|
-
if (listeners) {
|
|
1209
|
-
setEventProperty(ev, "currentTarget", target.owner);
|
|
1210
|
-
for (const record of listeners) {
|
|
1211
|
-
if (record.type === ev.type && !record.options.capture) {
|
|
1212
|
-
try {
|
|
1213
|
-
record.callback.call(target.owner, ev);
|
|
1214
|
-
}
|
|
1215
|
-
catch (err) {
|
|
1216
|
-
console.error(err);
|
|
1217
|
-
}
|
|
1218
|
-
if (immediateCancelBubble) {
|
|
1219
|
-
return true;
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
if (ev.cancelBubble) {
|
|
1225
|
-
return true;
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
finally {
|
|
1231
|
-
setEventProperty(ev, "eventPhase", NONE);
|
|
1232
|
-
setEventProperty(ev, "currentTarget", null);
|
|
1233
|
-
// eslint-disable-next-line no-unsafe-finally
|
|
1234
|
-
return !ev.defaultPrevented;
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
/*** PRIVATE CONTEXT FUNCTIONS ***/
|
|
1239
|
-
function ctxContains(parent, child) {
|
|
1240
|
-
for (let current = child; current !== undefined; current = current.parent) {
|
|
1241
|
-
if (current === parent) {
|
|
1242
|
-
return true;
|
|
1243
1515
|
}
|
|
1244
1516
|
}
|
|
1245
|
-
return false;
|
|
1246
1517
|
}
|
|
1247
|
-
function
|
|
1518
|
+
function diffComponent(adapter, root, host, parent, scope, ret) {
|
|
1248
1519
|
let ctx;
|
|
1249
|
-
if (
|
|
1520
|
+
if (ret.ctx) {
|
|
1250
1521
|
ctx = ret.ctx;
|
|
1251
|
-
if (ctx.
|
|
1252
|
-
console.error(
|
|
1253
|
-
return
|
|
1522
|
+
if (getFlag(ctx.ret, IsExecuting)) {
|
|
1523
|
+
console.error(`Component <${getTagName(ctx.ret.el.tag)}> is already executing`);
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
else if (ctx.schedule) {
|
|
1527
|
+
return ctx.schedule.promise.then(() => {
|
|
1528
|
+
return diffComponent(adapter, root, host, parent, scope, ret);
|
|
1529
|
+
});
|
|
1254
1530
|
}
|
|
1255
1531
|
}
|
|
1256
1532
|
else {
|
|
1257
|
-
ctx = ret.ctx = new
|
|
1533
|
+
ctx = ret.ctx = new ContextState(adapter, root, host, parent, scope, ret);
|
|
1258
1534
|
}
|
|
1259
|
-
ctx.
|
|
1260
|
-
return
|
|
1535
|
+
setFlag(ctx.ret, IsUpdating);
|
|
1536
|
+
return enqueueComponent(ctx);
|
|
1261
1537
|
}
|
|
1262
|
-
function
|
|
1263
|
-
if (ctx.
|
|
1264
|
-
return;
|
|
1265
|
-
}
|
|
1266
|
-
else if (ctx.f & IsErrored) {
|
|
1267
|
-
// This branch is necessary for some race conditions where this function is
|
|
1268
|
-
// called after iterator.throw() in async generator components.
|
|
1538
|
+
function diffComponentChildren(ctx, children, isYield) {
|
|
1539
|
+
if (getFlag(ctx.ret, IsUnmounted) || getFlag(ctx.ret, IsErrored)) {
|
|
1269
1540
|
return;
|
|
1270
1541
|
}
|
|
1271
1542
|
else if (children === undefined) {
|
|
1272
|
-
console.error(
|
|
1543
|
+
console.error(`Component <${getTagName(ctx.ret.el.tag)}> has ${isYield ? "yielded" : "returned"} undefined. If this was intentional, ${isYield ? "yield" : "return"} null instead.`);
|
|
1273
1544
|
}
|
|
1274
|
-
let
|
|
1545
|
+
let diff;
|
|
1275
1546
|
try {
|
|
1276
|
-
// TODO:
|
|
1547
|
+
// TODO: Use a different flag here to indicate the component is
|
|
1548
|
+
// synchronously rendering children
|
|
1277
1549
|
// We set the isExecuting flag in case a child component dispatches an event
|
|
1278
1550
|
// which bubbles to this component and causes a synchronous refresh().
|
|
1279
|
-
ctx.
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
ctx.f &= -3;
|
|
1284
|
-
}
|
|
1285
|
-
if (isPromiseLike(childValues)) {
|
|
1286
|
-
ctx.ret.inflightValue = childValues.then((childValues) => commitComponent(ctx, childValues));
|
|
1287
|
-
return ctx.ret.inflightValue;
|
|
1288
|
-
}
|
|
1289
|
-
return commitComponent(ctx, childValues);
|
|
1290
|
-
}
|
|
1291
|
-
function commitComponent(ctx, values) {
|
|
1292
|
-
if (ctx.f & IsUnmounted) {
|
|
1293
|
-
return;
|
|
1294
|
-
}
|
|
1295
|
-
const listeners = listenersMap.get(ctx);
|
|
1296
|
-
if (listeners && listeners.length) {
|
|
1297
|
-
for (let i = 0; i < values.length; i++) {
|
|
1298
|
-
const value = values[i];
|
|
1299
|
-
if (isEventTarget(value)) {
|
|
1300
|
-
for (let j = 0; j < listeners.length; j++) {
|
|
1301
|
-
const record = listeners[j];
|
|
1302
|
-
value.addEventListener(record.type, record.callback, record.options);
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
const oldValues = wrap(ctx.ret.cachedChildValues);
|
|
1308
|
-
let value = (ctx.ret.cachedChildValues = unwrap(values));
|
|
1309
|
-
if (ctx.f & IsScheduling) {
|
|
1310
|
-
ctx.f |= IsSchedulingRefresh;
|
|
1311
|
-
}
|
|
1312
|
-
else if (!(ctx.f & IsUpdating)) {
|
|
1313
|
-
// If we’re not updating the component, which happens when components are
|
|
1314
|
-
// refreshed, or when async generator components iterate, we have to do a
|
|
1315
|
-
// little bit housekeeping when a component’s child values have changed.
|
|
1316
|
-
if (!arrayEqual(oldValues, values)) {
|
|
1317
|
-
const records = getListenerRecords(ctx.parent, ctx.host);
|
|
1318
|
-
if (records.length) {
|
|
1319
|
-
for (let i = 0; i < values.length; i++) {
|
|
1320
|
-
const value = values[i];
|
|
1321
|
-
if (isEventTarget(value)) {
|
|
1322
|
-
for (let j = 0; j < records.length; j++) {
|
|
1323
|
-
const record = records[j];
|
|
1324
|
-
value.addEventListener(record.type, record.callback, record.options);
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
// rearranging the nearest ancestor host element
|
|
1330
|
-
const host = ctx.host;
|
|
1331
|
-
const oldHostValues = wrap(host.cachedChildValues);
|
|
1332
|
-
invalidate(ctx, host);
|
|
1333
|
-
const hostValues = getChildValues(host);
|
|
1334
|
-
ctx.renderer.arrange(host.el.tag, host.value, host.el.props, hostValues,
|
|
1335
|
-
// props and oldProps are the same because the host isn’t updated.
|
|
1336
|
-
host.el.props, oldHostValues);
|
|
1337
|
-
}
|
|
1338
|
-
flush(ctx.renderer, ctx.root, ctx);
|
|
1339
|
-
}
|
|
1340
|
-
const callbacks = scheduleMap.get(ctx);
|
|
1341
|
-
if (callbacks) {
|
|
1342
|
-
scheduleMap.delete(ctx);
|
|
1343
|
-
ctx.f |= IsScheduling;
|
|
1344
|
-
const value1 = ctx.renderer.read(value);
|
|
1345
|
-
for (const callback of callbacks) {
|
|
1346
|
-
callback(value1);
|
|
1347
|
-
}
|
|
1348
|
-
ctx.f &= -1025;
|
|
1349
|
-
// Handles an edge case where refresh() is called during a schedule().
|
|
1350
|
-
if (ctx.f & IsSchedulingRefresh) {
|
|
1351
|
-
ctx.f &= -2049;
|
|
1352
|
-
value = getValue(ctx.ret);
|
|
1551
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1552
|
+
diff = diffChildren(ctx.adapter, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children));
|
|
1553
|
+
if (diff) {
|
|
1554
|
+
diff = diff.catch((err) => handleChildError(ctx, err));
|
|
1353
1555
|
}
|
|
1354
1556
|
}
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
}
|
|
1358
|
-
function invalidate(ctx, host) {
|
|
1359
|
-
for (let parent = ctx.parent; parent !== undefined && parent.host === host; parent = parent.parent) {
|
|
1360
|
-
parent.ret.cachedChildValues = undefined;
|
|
1361
|
-
}
|
|
1362
|
-
host.cachedChildValues = undefined;
|
|
1363
|
-
}
|
|
1364
|
-
function arrayEqual(arr1, arr2) {
|
|
1365
|
-
if (arr1.length !== arr2.length) {
|
|
1366
|
-
return false;
|
|
1557
|
+
catch (err) {
|
|
1558
|
+
diff = handleChildError(ctx, err);
|
|
1367
1559
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
const value2 = arr2[i];
|
|
1371
|
-
if (value1 !== value2) {
|
|
1372
|
-
return false;
|
|
1373
|
-
}
|
|
1560
|
+
finally {
|
|
1561
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1374
1562
|
}
|
|
1375
|
-
return
|
|
1563
|
+
return diff;
|
|
1376
1564
|
}
|
|
1377
1565
|
/** Enqueues and executes the component associated with the context. */
|
|
1378
|
-
function
|
|
1379
|
-
if (
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
//
|
|
1389
|
-
//
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
// The component is suspended somewhere in the loop. When the component
|
|
1402
|
-
// reaches the bottom of the loop, it will suspend.
|
|
1403
|
-
//
|
|
1404
|
-
// Components will never be both available and suspended at
|
|
1405
|
-
// the same time.
|
|
1406
|
-
//
|
|
1407
|
-
// If the component is at the loop bottom, this means that the next value
|
|
1408
|
-
// produced by the component will have the most up to date props, so we can
|
|
1409
|
-
// simply return the current inflight value. Otherwise, we have to wait for
|
|
1410
|
-
// the bottom of the loop to be reached before returning the inflight
|
|
1411
|
-
// value.
|
|
1412
|
-
const isAtLoopbottom = ctx.f & IsInForAwaitOfLoop && !ctx.onProps;
|
|
1413
|
-
resumePropsAsyncIterator(ctx);
|
|
1414
|
-
if (isAtLoopbottom) {
|
|
1415
|
-
if (ctx.inflightBlock == null) {
|
|
1416
|
-
ctx.inflightBlock = new Promise((resolve) => (ctx.onPropsRequested = resolve));
|
|
1417
|
-
}
|
|
1418
|
-
return ctx.inflightBlock.then(() => {
|
|
1419
|
-
ctx.inflightBlock = undefined;
|
|
1420
|
-
return ctx.inflightValue;
|
|
1421
|
-
});
|
|
1422
|
-
}
|
|
1423
|
-
return ctx.inflightValue;
|
|
1424
|
-
}
|
|
1425
|
-
else if (!ctx.inflightBlock) {
|
|
1426
|
-
try {
|
|
1427
|
-
const [block, value] = runComponent(ctx, hydrationData);
|
|
1428
|
-
if (block) {
|
|
1429
|
-
ctx.inflightBlock = block
|
|
1430
|
-
// TODO: there is some fuckery going on here related to async
|
|
1431
|
-
// generator components resuming when they’re meant to be returned.
|
|
1432
|
-
.then((v) => v)
|
|
1433
|
-
.finally(() => advanceComponent(ctx));
|
|
1434
|
-
// stepComponent will only return a block if the value is asynchronous
|
|
1435
|
-
ctx.inflightValue = value;
|
|
1436
|
-
}
|
|
1437
|
-
return value;
|
|
1438
|
-
}
|
|
1439
|
-
catch (err) {
|
|
1440
|
-
if (!(ctx.f & IsUpdating)) {
|
|
1441
|
-
if (!ctx.parent) {
|
|
1442
|
-
throw err;
|
|
1443
|
-
}
|
|
1444
|
-
return propagateError(ctx.parent, err);
|
|
1445
|
-
}
|
|
1446
|
-
throw err;
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
else if (!ctx.enqueuedBlock) {
|
|
1450
|
-
if (hydrationData !== undefined) {
|
|
1451
|
-
throw new Error("Hydration error");
|
|
1452
|
-
}
|
|
1453
|
-
// We need to assign enqueuedBlock and enqueuedValue synchronously, hence
|
|
1454
|
-
// the Promise constructor call here.
|
|
1455
|
-
let resolveEnqueuedBlock;
|
|
1456
|
-
ctx.enqueuedBlock = new Promise((resolve) => (resolveEnqueuedBlock = resolve));
|
|
1457
|
-
ctx.enqueuedValue = ctx.inflightBlock.then(() => {
|
|
1458
|
-
try {
|
|
1459
|
-
const [block, value] = runComponent(ctx);
|
|
1460
|
-
if (block) {
|
|
1461
|
-
resolveEnqueuedBlock(block.finally(() => advanceComponent(ctx)));
|
|
1462
|
-
}
|
|
1463
|
-
return value;
|
|
1464
|
-
}
|
|
1465
|
-
catch (err) {
|
|
1466
|
-
if (!(ctx.f & IsUpdating)) {
|
|
1467
|
-
if (!ctx.parent) {
|
|
1468
|
-
throw err;
|
|
1469
|
-
}
|
|
1470
|
-
return propagateError(ctx.parent, err);
|
|
1471
|
-
}
|
|
1472
|
-
throw err;
|
|
1473
|
-
}
|
|
1474
|
-
});
|
|
1475
|
-
}
|
|
1476
|
-
return ctx.enqueuedValue;
|
|
1566
|
+
function enqueueComponent(ctx) {
|
|
1567
|
+
if (!ctx.inflight) {
|
|
1568
|
+
const [block, diff] = runComponent(ctx);
|
|
1569
|
+
if (block) {
|
|
1570
|
+
// if block is a promise, diff is a promise
|
|
1571
|
+
ctx.inflight = [block.finally(() => advanceComponent(ctx)), diff];
|
|
1572
|
+
}
|
|
1573
|
+
return diff;
|
|
1574
|
+
}
|
|
1575
|
+
else if (!ctx.enqueued) {
|
|
1576
|
+
// The enqueuedBlock and enqueuedDiff properties must be set
|
|
1577
|
+
// simultaneously, hence the usage of the Promise constructor.
|
|
1578
|
+
let resolve;
|
|
1579
|
+
ctx.enqueued = [
|
|
1580
|
+
new Promise((resolve1) => (resolve = resolve1)).finally(() => advanceComponent(ctx)),
|
|
1581
|
+
ctx.inflight[0].finally(() => {
|
|
1582
|
+
const [block, diff] = runComponent(ctx);
|
|
1583
|
+
resolve(block);
|
|
1584
|
+
return diff;
|
|
1585
|
+
}),
|
|
1586
|
+
];
|
|
1587
|
+
}
|
|
1588
|
+
return ctx.enqueued[1];
|
|
1477
1589
|
}
|
|
1478
1590
|
/** Called when the inflight block promise settles. */
|
|
1479
1591
|
function advanceComponent(ctx) {
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
}
|
|
1483
|
-
ctx.inflightBlock = ctx.enqueuedBlock;
|
|
1484
|
-
ctx.inflightValue = ctx.enqueuedValue;
|
|
1485
|
-
ctx.enqueuedBlock = undefined;
|
|
1486
|
-
ctx.enqueuedValue = undefined;
|
|
1592
|
+
ctx.inflight = ctx.enqueued;
|
|
1593
|
+
ctx.enqueued = undefined;
|
|
1487
1594
|
}
|
|
1488
1595
|
/**
|
|
1489
|
-
* This function is responsible for executing
|
|
1490
|
-
*
|
|
1491
|
-
* generator or async without calling it and inspecting the return value.
|
|
1596
|
+
* This function is responsible for executing components, and handling the
|
|
1597
|
+
* different component types.
|
|
1492
1598
|
*
|
|
1493
|
-
* @returns {[block,
|
|
1494
|
-
* block
|
|
1495
|
-
* component is blocked
|
|
1496
|
-
*
|
|
1599
|
+
* @returns {[block, diff]} A tuple where:
|
|
1600
|
+
* - block is a promise or undefined which represents the duration during which
|
|
1601
|
+
* the component is blocked.
|
|
1602
|
+
* - diff is a promise or undefined which represents the duration for diffing
|
|
1603
|
+
* of children.
|
|
1497
1604
|
*
|
|
1498
|
-
*
|
|
1499
|
-
*
|
|
1500
|
-
* to
|
|
1501
|
-
* -
|
|
1502
|
-
*
|
|
1503
|
-
* -
|
|
1504
|
-
*
|
|
1605
|
+
* While a component is blocked, further updates to the component are enqueued.
|
|
1606
|
+
*
|
|
1607
|
+
* Each component type blocks according to its implementation:
|
|
1608
|
+
* - Sync function components never block; when props or state change,
|
|
1609
|
+
* updates are immediately passed to children.
|
|
1610
|
+
* - Async function components block only while awaiting their own async work
|
|
1611
|
+
* (e.g., during an await), but do not block while their async children are rendering.
|
|
1612
|
+
* - Sync generator components block while their children are rendering;
|
|
1613
|
+
* they only resume once their children have finished.
|
|
1614
|
+
* - Async generator components can block in two different ways:
|
|
1615
|
+
* - By default, they behave like sync generator components, blocking while
|
|
1616
|
+
* the component or its children are rendering.
|
|
1617
|
+
* - Within a for await...of loop, they block only while waiting for new
|
|
1618
|
+
* props to be requested, and not while children are rendering.
|
|
1505
1619
|
*/
|
|
1506
|
-
function runComponent(ctx
|
|
1620
|
+
function runComponent(ctx) {
|
|
1621
|
+
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
1622
|
+
return [undefined, undefined];
|
|
1623
|
+
}
|
|
1507
1624
|
const ret = ctx.ret;
|
|
1508
1625
|
const initial = !ctx.iterator;
|
|
1509
1626
|
if (initial) {
|
|
1510
|
-
|
|
1511
|
-
ctx.
|
|
1512
|
-
|
|
1513
|
-
let result;
|
|
1627
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1628
|
+
clearEventListeners(ctx.ctx);
|
|
1629
|
+
let returned;
|
|
1514
1630
|
try {
|
|
1515
|
-
|
|
1631
|
+
returned = ret.el.tag.call(ctx.ctx, ret.el.props, ctx.ctx);
|
|
1516
1632
|
}
|
|
1517
1633
|
catch (err) {
|
|
1518
|
-
ctx.
|
|
1634
|
+
setFlag(ctx.ret, IsErrored);
|
|
1519
1635
|
throw err;
|
|
1520
1636
|
}
|
|
1521
1637
|
finally {
|
|
1522
|
-
ctx.
|
|
1638
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1523
1639
|
}
|
|
1524
|
-
if (isIteratorLike(
|
|
1525
|
-
ctx.iterator =
|
|
1640
|
+
if (isIteratorLike(returned)) {
|
|
1641
|
+
ctx.iterator = returned;
|
|
1526
1642
|
}
|
|
1527
|
-
else if (isPromiseLike(
|
|
1528
|
-
// async function component
|
|
1529
|
-
const result1 = result instanceof Promise ? result : Promise.resolve(result);
|
|
1530
|
-
const value = result1.then((result) => updateComponentChildren(ctx, result, hydrationData), (err) => {
|
|
1531
|
-
ctx.f |= IsErrored;
|
|
1532
|
-
throw err;
|
|
1533
|
-
});
|
|
1534
|
-
return [result1.catch(NOOP), value];
|
|
1535
|
-
}
|
|
1536
|
-
else {
|
|
1643
|
+
else if (!isPromiseLike(returned)) {
|
|
1537
1644
|
// sync function component
|
|
1538
1645
|
return [
|
|
1539
1646
|
undefined,
|
|
1540
|
-
|
|
1647
|
+
diffComponentChildren(ctx, returned, false),
|
|
1648
|
+
];
|
|
1649
|
+
}
|
|
1650
|
+
else {
|
|
1651
|
+
// async function component
|
|
1652
|
+
const returned1 = returned instanceof Promise ? returned : Promise.resolve(returned);
|
|
1653
|
+
return [
|
|
1654
|
+
returned1.catch(NOOP),
|
|
1655
|
+
returned1.then((returned) => diffComponentChildren(ctx, returned, false), (err) => {
|
|
1656
|
+
setFlag(ctx.ret, IsErrored);
|
|
1657
|
+
throw err;
|
|
1658
|
+
}),
|
|
1541
1659
|
];
|
|
1542
1660
|
}
|
|
1543
|
-
}
|
|
1544
|
-
else if (hydrationData !== undefined) {
|
|
1545
|
-
// hydration data should only be passed on the initial render
|
|
1546
|
-
throw new Error("Hydration error");
|
|
1547
1661
|
}
|
|
1548
1662
|
let iteration;
|
|
1549
1663
|
if (initial) {
|
|
1550
1664
|
try {
|
|
1551
|
-
ctx.
|
|
1665
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1552
1666
|
iteration = ctx.iterator.next();
|
|
1553
1667
|
}
|
|
1554
1668
|
catch (err) {
|
|
1555
|
-
ctx.
|
|
1669
|
+
setFlag(ctx.ret, IsErrored);
|
|
1556
1670
|
throw err;
|
|
1557
1671
|
}
|
|
1558
1672
|
finally {
|
|
1559
|
-
ctx.
|
|
1673
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1560
1674
|
}
|
|
1561
1675
|
if (isPromiseLike(iteration)) {
|
|
1562
|
-
ctx.
|
|
1676
|
+
setFlag(ctx.ret, IsAsyncGen);
|
|
1563
1677
|
}
|
|
1564
1678
|
else {
|
|
1565
|
-
ctx.
|
|
1679
|
+
setFlag(ctx.ret, IsSyncGen);
|
|
1566
1680
|
}
|
|
1567
1681
|
}
|
|
1568
|
-
if (ctx.
|
|
1682
|
+
if (getFlag(ctx.ret, IsSyncGen)) {
|
|
1569
1683
|
// sync generator component
|
|
1570
1684
|
if (!initial) {
|
|
1571
1685
|
try {
|
|
1572
|
-
ctx.
|
|
1573
|
-
|
|
1686
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1687
|
+
const oldResult = ctx.adapter.read(getValue(ctx.ret));
|
|
1688
|
+
iteration = ctx.iterator.next(oldResult);
|
|
1574
1689
|
}
|
|
1575
1690
|
catch (err) {
|
|
1576
|
-
ctx.
|
|
1691
|
+
setFlag(ctx.ret, IsErrored);
|
|
1577
1692
|
throw err;
|
|
1578
1693
|
}
|
|
1579
1694
|
finally {
|
|
1580
|
-
ctx.
|
|
1695
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1581
1696
|
}
|
|
1582
1697
|
}
|
|
1583
1698
|
if (isPromiseLike(iteration)) {
|
|
1584
1699
|
throw new Error("Mixed generator component");
|
|
1585
1700
|
}
|
|
1586
|
-
if (ctx.
|
|
1587
|
-
!(ctx.
|
|
1588
|
-
!(ctx.
|
|
1589
|
-
|
|
1701
|
+
if (getFlag(ctx.ret, IsInForOfLoop) &&
|
|
1702
|
+
!getFlag(ctx.ret, NeedsToYield) &&
|
|
1703
|
+
!getFlag(ctx.ret, IsUnmounted) &&
|
|
1704
|
+
!getFlag(ctx.ret, IsScheduling)) {
|
|
1705
|
+
console.error(`Component <${getTagName(ctx.ret.el.tag)}> yielded/returned more than once in for...of loop`);
|
|
1590
1706
|
}
|
|
1591
|
-
ctx.
|
|
1707
|
+
setFlag(ctx.ret, NeedsToYield, false);
|
|
1592
1708
|
if (iteration.done) {
|
|
1593
|
-
ctx.
|
|
1709
|
+
setFlag(ctx.ret, IsSyncGen, false);
|
|
1594
1710
|
ctx.iterator = undefined;
|
|
1595
1711
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
// Children can be void so we eliminate that here
|
|
1600
|
-
iteration.value, hydrationData);
|
|
1601
|
-
if (isPromiseLike(value)) {
|
|
1602
|
-
value = value.catch((err) => handleChildError(ctx, err));
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
catch (err) {
|
|
1606
|
-
value = handleChildError(ctx, err);
|
|
1607
|
-
}
|
|
1608
|
-
const block = isPromiseLike(value) ? value.catch(NOOP) : undefined;
|
|
1609
|
-
return [block, value];
|
|
1712
|
+
const diff = diffComponentChildren(ctx, iteration.value, !iteration.done);
|
|
1713
|
+
const block = isPromiseLike(diff) ? diff.catch(NOOP) : undefined;
|
|
1714
|
+
return [block, diff];
|
|
1610
1715
|
}
|
|
1611
1716
|
else {
|
|
1612
|
-
if (ctx.
|
|
1613
|
-
//
|
|
1614
|
-
|
|
1615
|
-
|
|
1717
|
+
if (getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
1718
|
+
// initializes the async generator loop
|
|
1719
|
+
pullComponent(ctx, iteration);
|
|
1720
|
+
const block = resumePropsAsyncIterator(ctx);
|
|
1721
|
+
return [block, ctx.pull && ctx.pull.diff];
|
|
1722
|
+
}
|
|
1723
|
+
else {
|
|
1724
|
+
// We call resumePropsAsyncIterator in case the component exits the
|
|
1725
|
+
// for...of loop
|
|
1726
|
+
resumePropsAsyncIterator(ctx);
|
|
1616
1727
|
if (!initial) {
|
|
1617
1728
|
try {
|
|
1618
|
-
ctx.
|
|
1619
|
-
|
|
1729
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1730
|
+
const oldResult = ctx.adapter.read(getValue(ctx.ret));
|
|
1731
|
+
iteration = ctx.iterator.next(oldResult);
|
|
1620
1732
|
}
|
|
1621
1733
|
catch (err) {
|
|
1622
|
-
ctx.
|
|
1734
|
+
setFlag(ctx.ret, IsErrored);
|
|
1623
1735
|
throw err;
|
|
1624
1736
|
}
|
|
1625
1737
|
finally {
|
|
1626
|
-
ctx.
|
|
1738
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1627
1739
|
}
|
|
1628
1740
|
}
|
|
1629
1741
|
if (!isPromiseLike(iteration)) {
|
|
1630
1742
|
throw new Error("Mixed generator component");
|
|
1631
1743
|
}
|
|
1632
|
-
const
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
runAsyncGenComponent(ctx, Promise.resolve(iteration), hydrationData);
|
|
1744
|
+
const diff = iteration.then((iteration) => {
|
|
1745
|
+
if (getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
1746
|
+
// We have entered a for await...of loop, so we start pulling
|
|
1747
|
+
pullComponent(ctx, iteration);
|
|
1637
1748
|
}
|
|
1638
1749
|
else {
|
|
1639
|
-
if (
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
try {
|
|
1645
|
-
value = updateComponentChildren(ctx,
|
|
1646
|
-
// Children can be void so we eliminate that here
|
|
1647
|
-
iteration.value, hydrationData);
|
|
1648
|
-
if (isPromiseLike(value)) {
|
|
1649
|
-
value = value.catch((err) => handleChildError(ctx, err));
|
|
1750
|
+
if (getFlag(ctx.ret, IsInForOfLoop) &&
|
|
1751
|
+
!getFlag(ctx.ret, NeedsToYield) &&
|
|
1752
|
+
!getFlag(ctx.ret, IsUnmounted) &&
|
|
1753
|
+
!getFlag(ctx.ret, IsScheduling)) {
|
|
1754
|
+
console.error(`Component <${getTagName(ctx.ret.el.tag)}> yielded/returned more than once in for...of loop`);
|
|
1650
1755
|
}
|
|
1651
1756
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1757
|
+
setFlag(ctx.ret, NeedsToYield, false);
|
|
1758
|
+
if (iteration.done) {
|
|
1759
|
+
setFlag(ctx.ret, IsAsyncGen, false);
|
|
1760
|
+
ctx.iterator = undefined;
|
|
1654
1761
|
}
|
|
1655
|
-
return
|
|
1762
|
+
return diffComponentChildren(ctx,
|
|
1763
|
+
// Children can be void so we eliminate that here
|
|
1764
|
+
iteration.value, !iteration.done);
|
|
1656
1765
|
}, (err) => {
|
|
1657
|
-
ctx.
|
|
1766
|
+
setFlag(ctx.ret, IsErrored);
|
|
1658
1767
|
throw err;
|
|
1659
1768
|
});
|
|
1660
|
-
return [
|
|
1769
|
+
return [diff.catch(NOOP), diff];
|
|
1661
1770
|
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Called to resume the props async iterator for async generator components.
|
|
1775
|
+
*
|
|
1776
|
+
* @returns {Promise<undefined> | undefined} A possible promise which
|
|
1777
|
+
* represents the duration during which the component is blocked.
|
|
1778
|
+
*/
|
|
1779
|
+
function resumePropsAsyncIterator(ctx) {
|
|
1780
|
+
if (ctx.onPropsProvided) {
|
|
1781
|
+
ctx.onPropsProvided(ctx.ret.el.props);
|
|
1782
|
+
ctx.onPropsProvided = undefined;
|
|
1783
|
+
setFlag(ctx.ret, PropsAvailable, false);
|
|
1784
|
+
}
|
|
1785
|
+
else {
|
|
1786
|
+
setFlag(ctx.ret, PropsAvailable);
|
|
1787
|
+
if (getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
1788
|
+
return new Promise((resolve) => (ctx.onPropsRequested = resolve));
|
|
1665
1789
|
}
|
|
1666
1790
|
}
|
|
1791
|
+
return (ctx.pull && ctx.pull.iterationP && ctx.pull.iterationP.then(NOOP, NOOP));
|
|
1667
1792
|
}
|
|
1668
|
-
|
|
1793
|
+
/**
|
|
1794
|
+
* The logic for pulling from async generator components when they are in a for
|
|
1795
|
+
* await...of loop is implemented here.
|
|
1796
|
+
*
|
|
1797
|
+
* It makes sense to group this logic in a single async loop to prevent race
|
|
1798
|
+
* conditions caused by calling next(), throw() and return() concurrently.
|
|
1799
|
+
*/
|
|
1800
|
+
async function pullComponent(ctx, iterationP) {
|
|
1801
|
+
if (!iterationP || ctx.pull) {
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
ctx.pull = { iterationP: undefined, diff: undefined, onChildError: undefined };
|
|
1805
|
+
// TODO: replace done with iteration
|
|
1806
|
+
//let iteration: ChildrenIteratorResult | undefined;
|
|
1669
1807
|
let done = false;
|
|
1670
1808
|
try {
|
|
1809
|
+
let childError;
|
|
1671
1810
|
while (!done) {
|
|
1672
|
-
if (
|
|
1673
|
-
|
|
1674
|
-
}
|
|
1675
|
-
// inflightValue must be set synchronously.
|
|
1676
|
-
let onValue;
|
|
1677
|
-
ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
|
|
1678
|
-
if (ctx.f & IsUpdating) {
|
|
1679
|
-
// We should not swallow unhandled promise rejections if the component is
|
|
1680
|
-
// updating independently.
|
|
1681
|
-
// TODO: Does this handle this.refresh() calls?
|
|
1682
|
-
ctx.inflightValue.catch(NOOP);
|
|
1811
|
+
if (isPromiseLike(iterationP)) {
|
|
1812
|
+
ctx.pull.iterationP = iterationP;
|
|
1683
1813
|
}
|
|
1814
|
+
let onDiff;
|
|
1815
|
+
ctx.pull.diff = new Promise((resolve) => (onDiff = resolve)).then(() => {
|
|
1816
|
+
if (!(getFlag(ctx.ret, IsUpdating) || getFlag(ctx.ret, IsRefreshing))) {
|
|
1817
|
+
commitComponent(ctx, []);
|
|
1818
|
+
}
|
|
1819
|
+
}, (err) => {
|
|
1820
|
+
if (!(getFlag(ctx.ret, IsUpdating) || getFlag(ctx.ret, IsRefreshing)) ||
|
|
1821
|
+
// TODO: is this flag necessary?
|
|
1822
|
+
!getFlag(ctx.ret, NeedsToYield)) {
|
|
1823
|
+
return propagateError(ctx, err, []);
|
|
1824
|
+
}
|
|
1825
|
+
throw err;
|
|
1826
|
+
});
|
|
1684
1827
|
let iteration;
|
|
1685
1828
|
try {
|
|
1686
1829
|
iteration = await iterationP;
|
|
1687
1830
|
}
|
|
1688
1831
|
catch (err) {
|
|
1689
1832
|
done = true;
|
|
1690
|
-
ctx.
|
|
1691
|
-
|
|
1833
|
+
setFlag(ctx.ret, IsErrored);
|
|
1834
|
+
setFlag(ctx.ret, NeedsToYield, false);
|
|
1835
|
+
onDiff(Promise.reject(err));
|
|
1692
1836
|
break;
|
|
1693
1837
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1838
|
+
// this must be set after iterationP is awaited
|
|
1839
|
+
let oldResult;
|
|
1840
|
+
{
|
|
1841
|
+
// The 'floating' flag tracks whether the promise passed to the generator
|
|
1842
|
+
// is handled (via await, then, or catch). If handled, we reject the
|
|
1843
|
+
// promise so the user can catch errors. If not, we inject the error back
|
|
1844
|
+
// into the generator using throw, like for sync generator components.
|
|
1845
|
+
let floating = true;
|
|
1846
|
+
const oldResult1 = new Promise((resolve, reject) => {
|
|
1847
|
+
ctx.ctx.schedule(resolve);
|
|
1848
|
+
ctx.pull.onChildError = (err) => {
|
|
1849
|
+
reject(err);
|
|
1850
|
+
if (floating) {
|
|
1851
|
+
childError = err;
|
|
1852
|
+
resumePropsAsyncIterator(ctx);
|
|
1853
|
+
return ctx.pull.diff;
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
});
|
|
1857
|
+
oldResult1.catch(NOOP);
|
|
1858
|
+
// We use Object.create() to clone the promise for float detection
|
|
1859
|
+
// because modern JS engines skip calling .then() on promises awaited
|
|
1860
|
+
// with await.
|
|
1861
|
+
oldResult = Object.create(oldResult1);
|
|
1862
|
+
oldResult.then = function (onfulfilled, onrejected) {
|
|
1863
|
+
floating = false;
|
|
1864
|
+
return oldResult1.then(onfulfilled, onrejected);
|
|
1865
|
+
};
|
|
1866
|
+
oldResult.catch = function (onrejected) {
|
|
1867
|
+
floating = false;
|
|
1868
|
+
return oldResult1.catch(onrejected);
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
if (childError != null) {
|
|
1872
|
+
try {
|
|
1873
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1874
|
+
if (typeof ctx.iterator.throw !== "function") {
|
|
1875
|
+
throw childError;
|
|
1876
|
+
}
|
|
1877
|
+
iteration = await ctx.iterator.throw(childError);
|
|
1878
|
+
}
|
|
1879
|
+
catch (err) {
|
|
1880
|
+
done = true;
|
|
1881
|
+
setFlag(ctx.ret, IsErrored);
|
|
1882
|
+
setFlag(ctx.ret, NeedsToYield, false);
|
|
1883
|
+
onDiff(Promise.reject(err));
|
|
1884
|
+
break;
|
|
1885
|
+
}
|
|
1886
|
+
finally {
|
|
1887
|
+
childError = undefined;
|
|
1888
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
// this makes sure we pause before entering a loop if we yield before it
|
|
1892
|
+
if (!getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
1893
|
+
setFlag(ctx.ret, PropsAvailable, false);
|
|
1696
1894
|
}
|
|
1697
1895
|
done = !!iteration.done;
|
|
1698
|
-
let
|
|
1896
|
+
let diff;
|
|
1699
1897
|
try {
|
|
1700
|
-
if (!(
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1898
|
+
if (!isPromiseLike(iterationP)) {
|
|
1899
|
+
// if iterationP is an iteration and not a promise, the component was
|
|
1900
|
+
// not in a for await...of loop when the iteration started, so we can
|
|
1901
|
+
// skip the diffing of children as it is handled elsewhere.
|
|
1902
|
+
diff = undefined;
|
|
1903
|
+
}
|
|
1904
|
+
else if (!getFlag(ctx.ret, NeedsToYield) &&
|
|
1905
|
+
getFlag(ctx.ret, PropsAvailable) &&
|
|
1906
|
+
getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
1907
|
+
// logic to skip yielded children in a stale for await of iteration.
|
|
1908
|
+
diff = undefined;
|
|
1707
1909
|
}
|
|
1708
1910
|
else {
|
|
1709
|
-
|
|
1710
|
-
hydrationData = undefined;
|
|
1711
|
-
if (isPromiseLike(value)) {
|
|
1712
|
-
value = value.catch((err) => handleChildError(ctx, err));
|
|
1713
|
-
}
|
|
1911
|
+
diff = diffComponentChildren(ctx, iteration.value, !iteration.done);
|
|
1714
1912
|
}
|
|
1715
|
-
ctx.f &= ~NeedsToYield;
|
|
1716
1913
|
}
|
|
1717
1914
|
catch (err) {
|
|
1718
|
-
|
|
1719
|
-
// promise rejections?
|
|
1720
|
-
value = handleChildError(ctx, err);
|
|
1915
|
+
onDiff(Promise.reject(err));
|
|
1721
1916
|
}
|
|
1722
1917
|
finally {
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
if (ctx.ret
|
|
1727
|
-
//
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
if (ctx.f & IsUpdating) {
|
|
1735
|
-
return;
|
|
1918
|
+
onDiff(diff);
|
|
1919
|
+
setFlag(ctx.ret, NeedsToYield, false);
|
|
1920
|
+
}
|
|
1921
|
+
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
1922
|
+
// TODO: move this unmounted branch outside the loop
|
|
1923
|
+
while ((!iteration || !iteration.done) &&
|
|
1924
|
+
ctx.iterator &&
|
|
1925
|
+
getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
1926
|
+
try {
|
|
1927
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1928
|
+
iteration = await ctx.iterator.next(oldResult);
|
|
1736
1929
|
}
|
|
1737
|
-
|
|
1930
|
+
catch (err) {
|
|
1931
|
+
setFlag(ctx.ret, IsErrored);
|
|
1932
|
+
// we throw the error here to cause an unhandled rejection because
|
|
1933
|
+
// the promise returned from pullComponent is never awaited
|
|
1738
1934
|
throw err;
|
|
1739
1935
|
}
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
if (ctx.f & IsInForAwaitOfLoop) {
|
|
1936
|
+
finally {
|
|
1937
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
if ((!iteration || !iteration.done) &&
|
|
1941
|
+
ctx.iterator &&
|
|
1942
|
+
typeof ctx.iterator.return === "function") {
|
|
1748
1943
|
try {
|
|
1749
|
-
ctx.
|
|
1750
|
-
|
|
1944
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1945
|
+
await ctx.iterator.return();
|
|
1946
|
+
}
|
|
1947
|
+
catch (err) {
|
|
1948
|
+
setFlag(ctx.ret, IsErrored);
|
|
1949
|
+
throw err;
|
|
1751
1950
|
}
|
|
1752
1951
|
finally {
|
|
1753
|
-
ctx.
|
|
1952
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1754
1953
|
}
|
|
1755
1954
|
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1955
|
+
break;
|
|
1956
|
+
}
|
|
1957
|
+
else if (!getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
1958
|
+
// we have exited the for...await of, so updates will be handled by the
|
|
1959
|
+
// regular runComponent/enqueueComponent logic.
|
|
1960
|
+
break;
|
|
1760
1961
|
}
|
|
1761
|
-
else if (!done
|
|
1962
|
+
else if (!iteration.done) {
|
|
1762
1963
|
try {
|
|
1763
|
-
ctx.
|
|
1964
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1764
1965
|
iterationP = ctx.iterator.next(oldResult);
|
|
1765
1966
|
}
|
|
1766
1967
|
finally {
|
|
1767
|
-
ctx.
|
|
1968
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1768
1969
|
}
|
|
1769
1970
|
}
|
|
1770
|
-
initial = false;
|
|
1771
1971
|
}
|
|
1772
1972
|
}
|
|
1773
1973
|
finally {
|
|
1774
1974
|
if (done) {
|
|
1775
|
-
ctx.
|
|
1975
|
+
setFlag(ctx.ret, IsAsyncGen, false);
|
|
1776
1976
|
ctx.iterator = undefined;
|
|
1777
1977
|
}
|
|
1978
|
+
ctx.pull = undefined;
|
|
1778
1979
|
}
|
|
1779
1980
|
}
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
ctx.
|
|
1787
|
-
|
|
1981
|
+
function commitComponent(ctx, schedulePromises, hydrationNodes) {
|
|
1982
|
+
if (ctx.schedule) {
|
|
1983
|
+
ctx.schedule.promise.then(() => {
|
|
1984
|
+
commitComponent(ctx, []);
|
|
1985
|
+
propagateComponent(ctx);
|
|
1986
|
+
});
|
|
1987
|
+
return getValue(ctx.ret);
|
|
1988
|
+
}
|
|
1989
|
+
const wasScheduling = getFlag(ctx.ret, IsScheduling);
|
|
1990
|
+
const values = commitChildren(ctx.adapter, ctx.host, ctx, ctx.scope, ctx.ret, ctx.index, schedulePromises, hydrationNodes);
|
|
1991
|
+
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
addEventTargetDelegates(ctx.ctx, values);
|
|
1995
|
+
// Execute schedule callbacks early to check for async deferral
|
|
1996
|
+
const callbacks = scheduleMap.get(ctx);
|
|
1997
|
+
let schedulePromises1;
|
|
1998
|
+
if (callbacks) {
|
|
1999
|
+
scheduleMap.delete(ctx);
|
|
2000
|
+
// TODO: think about error handling for schedule callbacks
|
|
2001
|
+
setFlag(ctx.ret, IsScheduling);
|
|
2002
|
+
const result = ctx.adapter.read(unwrap(values));
|
|
2003
|
+
for (const callback of callbacks) {
|
|
2004
|
+
const scheduleResult = callback(result);
|
|
2005
|
+
if (isPromiseLike(scheduleResult)) {
|
|
2006
|
+
(schedulePromises1 = schedulePromises1 || []).push(scheduleResult);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
if (schedulePromises1 && !getFlag(ctx.ret, DidCommit)) {
|
|
2010
|
+
const scheduleCallbacksP = Promise.all(schedulePromises1).then(() => {
|
|
2011
|
+
setFlag(ctx.ret, IsScheduling, false);
|
|
2012
|
+
propagateComponent(ctx);
|
|
2013
|
+
if (ctx.ret.fallback) {
|
|
2014
|
+
unmount(ctx.adapter, ctx.host, ctx.parent, ctx.ret.fallback, false);
|
|
2015
|
+
}
|
|
2016
|
+
ctx.ret.fallback = undefined;
|
|
2017
|
+
});
|
|
2018
|
+
let onAbort;
|
|
2019
|
+
const scheduleP = safeRace([
|
|
2020
|
+
scheduleCallbacksP,
|
|
2021
|
+
new Promise((resolve) => (onAbort = resolve)),
|
|
2022
|
+
]).finally(() => {
|
|
2023
|
+
ctx.schedule = undefined;
|
|
2024
|
+
});
|
|
2025
|
+
ctx.schedule = { promise: scheduleP, onAbort };
|
|
2026
|
+
schedulePromises.push(scheduleP);
|
|
2027
|
+
}
|
|
2028
|
+
else {
|
|
2029
|
+
setFlag(ctx.ret, IsScheduling, wasScheduling);
|
|
2030
|
+
}
|
|
1788
2031
|
}
|
|
1789
2032
|
else {
|
|
1790
|
-
ctx.
|
|
2033
|
+
setFlag(ctx.ret, IsScheduling, wasScheduling);
|
|
1791
2034
|
}
|
|
2035
|
+
if (!getFlag(ctx.ret, IsScheduling)) {
|
|
2036
|
+
if (!getFlag(ctx.ret, IsUpdating)) {
|
|
2037
|
+
propagateComponent(ctx);
|
|
2038
|
+
}
|
|
2039
|
+
if (ctx.ret.fallback) {
|
|
2040
|
+
unmount(ctx.adapter, ctx.host, ctx.parent, ctx.ret.fallback, false);
|
|
2041
|
+
}
|
|
2042
|
+
ctx.ret.fallback = undefined;
|
|
2043
|
+
setFlag(ctx.ret, IsUpdating, false);
|
|
2044
|
+
}
|
|
2045
|
+
setFlag(ctx.ret, DidCommit);
|
|
2046
|
+
// We always use getValue() instead of the unwrapping values because there
|
|
2047
|
+
// are various ways in which the values could have been updated, especially
|
|
2048
|
+
// if schedule callbacks call refresh() or async mounting is happening.
|
|
2049
|
+
return getValue(ctx.ret, true);
|
|
2050
|
+
}
|
|
2051
|
+
/**
|
|
2052
|
+
* Propagates component changes up to ancestors when rendering starts from a
|
|
2053
|
+
* component via refresh() or multiple for await...of renders. This handles
|
|
2054
|
+
* event listeners and DOM arrangement that would normally happen during
|
|
2055
|
+
* top-down rendering.
|
|
2056
|
+
*/
|
|
2057
|
+
function propagateComponent(ctx) {
|
|
2058
|
+
const values = getChildValues(ctx.ret, ctx.index);
|
|
2059
|
+
addEventTargetDelegates(ctx.ctx, values, (ctx1) => ctx1[_ContextState].host === ctx.host);
|
|
2060
|
+
const host = ctx.host;
|
|
2061
|
+
const props = stripSpecialProps(host.el.props);
|
|
2062
|
+
ctx.adapter.arrange({
|
|
2063
|
+
tag: host.el.tag,
|
|
2064
|
+
tagName: getTagName(host.el.tag),
|
|
2065
|
+
node: host.value,
|
|
2066
|
+
props,
|
|
2067
|
+
oldProps: props,
|
|
2068
|
+
children: getChildValues(host, 0),
|
|
2069
|
+
});
|
|
2070
|
+
flush(ctx.adapter, ctx.root, ctx);
|
|
1792
2071
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
if (ctx.f & IsUnmounted) {
|
|
2072
|
+
async function unmountComponent(ctx, isNested) {
|
|
2073
|
+
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
1796
2074
|
return;
|
|
1797
2075
|
}
|
|
1798
|
-
|
|
2076
|
+
let cleanupPromises;
|
|
2077
|
+
// TODO: think about errror handling for callbacks
|
|
1799
2078
|
const callbacks = cleanupMap.get(ctx);
|
|
1800
2079
|
if (callbacks) {
|
|
2080
|
+
const oldResult = ctx.adapter.read(getValue(ctx.ret));
|
|
1801
2081
|
cleanupMap.delete(ctx);
|
|
1802
|
-
const value = ctx.renderer.read(getValue(ctx.ret));
|
|
1803
2082
|
for (const callback of callbacks) {
|
|
1804
|
-
callback(
|
|
2083
|
+
const cleanup = callback(oldResult);
|
|
2084
|
+
if (isPromiseLike(cleanup)) {
|
|
2085
|
+
(cleanupPromises = cleanupPromises || []).push(cleanup);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
let didLinger = false;
|
|
2090
|
+
if (!isNested && cleanupPromises && getChildValues(ctx.ret).length > 0) {
|
|
2091
|
+
didLinger = true;
|
|
2092
|
+
const index = ctx.index;
|
|
2093
|
+
const lingerers = ctx.host.lingerers || (ctx.host.lingerers = []);
|
|
2094
|
+
let set = lingerers[index];
|
|
2095
|
+
if (set == null) {
|
|
2096
|
+
set = new Set();
|
|
2097
|
+
lingerers[index] = set;
|
|
2098
|
+
}
|
|
2099
|
+
set.add(ctx.ret);
|
|
2100
|
+
await Promise.all(cleanupPromises);
|
|
2101
|
+
set.delete(ctx.ret);
|
|
2102
|
+
if (set.size === 0) {
|
|
2103
|
+
lingerers[index] = undefined;
|
|
2104
|
+
}
|
|
2105
|
+
if (!lingerers.some(Boolean)) {
|
|
2106
|
+
// If there are no lingerers remaining, we can remove the lingerers array
|
|
2107
|
+
ctx.host.lingerers = undefined;
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
2111
|
+
// If the component was unmounted while awaiting the cleanup callbacks,
|
|
2112
|
+
// we do not need to continue unmounting.
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
setFlag(ctx.ret, IsUnmounted);
|
|
2116
|
+
// If component has pending schedule promises, resolve them since component
|
|
2117
|
+
// is unmounting
|
|
2118
|
+
if (ctx.schedule) {
|
|
2119
|
+
ctx.schedule.onAbort();
|
|
2120
|
+
ctx.schedule = undefined;
|
|
2121
|
+
}
|
|
2122
|
+
clearEventListeners(ctx.ctx);
|
|
2123
|
+
unmountChildren(ctx.adapter, ctx.host, ctx, ctx.ret, isNested);
|
|
2124
|
+
if (didLinger) {
|
|
2125
|
+
// If we lingered, we call finalize to ensure rendering is finalized
|
|
2126
|
+
if (ctx.root != null) {
|
|
2127
|
+
ctx.adapter.finalize(ctx.root);
|
|
1805
2128
|
}
|
|
1806
2129
|
}
|
|
1807
|
-
ctx.f |= IsUnmounted;
|
|
1808
2130
|
if (ctx.iterator) {
|
|
1809
|
-
if (ctx.
|
|
1810
|
-
let
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
2131
|
+
if (ctx.pull) {
|
|
2132
|
+
// we let pullComponent handle unmounting
|
|
2133
|
+
resumePropsAsyncIterator(ctx);
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
// we wait for inflight value so yields resume with the most up to date
|
|
2137
|
+
// props
|
|
2138
|
+
if (ctx.inflight) {
|
|
2139
|
+
await ctx.inflight[1];
|
|
2140
|
+
}
|
|
2141
|
+
let iteration;
|
|
2142
|
+
if (getFlag(ctx.ret, IsInForOfLoop)) {
|
|
2143
|
+
try {
|
|
2144
|
+
setFlag(ctx.ret, IsExecuting);
|
|
2145
|
+
const oldResult = ctx.adapter.read(getValue(ctx.ret));
|
|
2146
|
+
const iterationP = ctx.iterator.next(oldResult);
|
|
2147
|
+
if (isPromiseLike(iterationP)) {
|
|
2148
|
+
if (!getFlag(ctx.ret, IsAsyncGen)) {
|
|
2149
|
+
throw new Error("Mixed generator component");
|
|
1825
2150
|
}
|
|
1826
|
-
|
|
1827
|
-
});
|
|
1828
|
-
}
|
|
1829
|
-
else {
|
|
1830
|
-
if (ctx.f & IsInForOfLoop) {
|
|
1831
|
-
unmountComponent(ctx);
|
|
2151
|
+
iteration = await iterationP;
|
|
1832
2152
|
}
|
|
1833
2153
|
else {
|
|
1834
|
-
|
|
2154
|
+
if (!getFlag(ctx.ret, IsSyncGen)) {
|
|
2155
|
+
throw new Error("Mixed generator component");
|
|
2156
|
+
}
|
|
2157
|
+
iteration = iterationP;
|
|
1835
2158
|
}
|
|
1836
2159
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
const value = enqueueComponentRun(ctx);
|
|
1841
|
-
value.then(() => {
|
|
1842
|
-
if (ctx.f & IsInForOfLoop) {
|
|
1843
|
-
unmountComponent(ctx);
|
|
1844
|
-
}
|
|
1845
|
-
else {
|
|
1846
|
-
returnComponent(ctx);
|
|
1847
|
-
}
|
|
1848
|
-
}, (err) => {
|
|
1849
|
-
if (!ctx.parent) {
|
|
1850
|
-
throw err;
|
|
1851
|
-
}
|
|
1852
|
-
return propagateError(ctx.parent, err);
|
|
1853
|
-
});
|
|
2160
|
+
catch (err) {
|
|
2161
|
+
setFlag(ctx.ret, IsErrored);
|
|
2162
|
+
throw err;
|
|
1854
2163
|
}
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
// runAsyncGenComponent function.
|
|
1858
|
-
resumePropsAsyncIterator(ctx);
|
|
2164
|
+
finally {
|
|
2165
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1859
2166
|
}
|
|
1860
2167
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
iteration.catch((err) => {
|
|
1871
|
-
if (!ctx.parent) {
|
|
1872
|
-
throw err;
|
|
2168
|
+
if ((!iteration || !iteration.done) &&
|
|
2169
|
+
ctx.iterator &&
|
|
2170
|
+
typeof ctx.iterator.return === "function") {
|
|
2171
|
+
try {
|
|
2172
|
+
setFlag(ctx.ret, IsExecuting);
|
|
2173
|
+
const iterationP = ctx.iterator.return();
|
|
2174
|
+
if (isPromiseLike(iterationP)) {
|
|
2175
|
+
if (!getFlag(ctx.ret, IsAsyncGen)) {
|
|
2176
|
+
throw new Error("Mixed generator component");
|
|
1873
2177
|
}
|
|
1874
|
-
|
|
1875
|
-
}
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
/*** EVENT TARGET UTILITIES ***/
|
|
1884
|
-
// EVENT PHASE CONSTANTS
|
|
1885
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
|
|
1886
|
-
const NONE = 0;
|
|
1887
|
-
const CAPTURING_PHASE = 1;
|
|
1888
|
-
const AT_TARGET = 2;
|
|
1889
|
-
const BUBBLING_PHASE = 3;
|
|
1890
|
-
const listenersMap = new WeakMap();
|
|
1891
|
-
function isListenerOrListenerObject(value) {
|
|
1892
|
-
return (typeof value === "function" ||
|
|
1893
|
-
(value !== null &&
|
|
1894
|
-
typeof value === "object" &&
|
|
1895
|
-
typeof value.handleEvent === "function"));
|
|
1896
|
-
}
|
|
1897
|
-
function normalizeListenerOptions(options) {
|
|
1898
|
-
if (typeof options === "boolean") {
|
|
1899
|
-
return { capture: options };
|
|
1900
|
-
}
|
|
1901
|
-
else if (options == null) {
|
|
1902
|
-
return {};
|
|
1903
|
-
}
|
|
1904
|
-
return options;
|
|
1905
|
-
}
|
|
1906
|
-
function isEventTarget(value) {
|
|
1907
|
-
return (value != null &&
|
|
1908
|
-
typeof value.addEventListener === "function" &&
|
|
1909
|
-
typeof value.removeEventListener === "function" &&
|
|
1910
|
-
typeof value.dispatchEvent === "function");
|
|
1911
|
-
}
|
|
1912
|
-
function setEventProperty(ev, key, value) {
|
|
1913
|
-
Object.defineProperty(ev, key, { value, writable: false, configurable: true });
|
|
1914
|
-
}
|
|
1915
|
-
// TODO: Maybe we can pass in the current context directly, rather than
|
|
1916
|
-
// starting from the parent?
|
|
1917
|
-
/**
|
|
1918
|
-
* A function to reconstruct an array of every listener given a context and a
|
|
1919
|
-
* host element.
|
|
1920
|
-
*
|
|
1921
|
-
* This function exploits the fact that contexts retain their nearest ancestor
|
|
1922
|
-
* host element. We can determine all the contexts which are directly listening
|
|
1923
|
-
* to an element by traversing up the context tree and checking that the host
|
|
1924
|
-
* element passed in matches the parent context’s host element.
|
|
1925
|
-
*/
|
|
1926
|
-
function getListenerRecords(ctx, ret) {
|
|
1927
|
-
let listeners = [];
|
|
1928
|
-
while (ctx !== undefined && ctx.host === ret) {
|
|
1929
|
-
const listeners1 = listenersMap.get(ctx);
|
|
1930
|
-
if (listeners1) {
|
|
1931
|
-
listeners = listeners.concat(listeners1);
|
|
1932
|
-
}
|
|
1933
|
-
ctx = ctx.parent;
|
|
1934
|
-
}
|
|
1935
|
-
return listeners;
|
|
1936
|
-
}
|
|
1937
|
-
function clearEventListeners(ctx) {
|
|
1938
|
-
const listeners = listenersMap.get(ctx);
|
|
1939
|
-
if (listeners && listeners.length) {
|
|
1940
|
-
for (const value of getChildValues(ctx.ret)) {
|
|
1941
|
-
if (isEventTarget(value)) {
|
|
1942
|
-
for (const record of listeners) {
|
|
1943
|
-
value.removeEventListener(record.type, record.callback, record.options);
|
|
2178
|
+
iteration = await iterationP;
|
|
2179
|
+
}
|
|
2180
|
+
else {
|
|
2181
|
+
if (!getFlag(ctx.ret, IsSyncGen)) {
|
|
2182
|
+
throw new Error("Mixed generator component");
|
|
2183
|
+
}
|
|
2184
|
+
iteration = iterationP;
|
|
1944
2185
|
}
|
|
1945
2186
|
}
|
|
2187
|
+
catch (err) {
|
|
2188
|
+
setFlag(ctx.ret, IsErrored);
|
|
2189
|
+
throw err;
|
|
2190
|
+
}
|
|
2191
|
+
finally {
|
|
2192
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
2193
|
+
}
|
|
1946
2194
|
}
|
|
1947
|
-
listeners.length = 0;
|
|
1948
2195
|
}
|
|
1949
2196
|
}
|
|
1950
2197
|
/*** ERROR HANDLING UTILITIES ***/
|
|
1951
2198
|
function handleChildError(ctx, err) {
|
|
1952
|
-
if (!ctx.iterator
|
|
2199
|
+
if (!ctx.iterator) {
|
|
2200
|
+
throw err;
|
|
2201
|
+
}
|
|
2202
|
+
if (ctx.pull) {
|
|
2203
|
+
// we let pullComponent handle child errors
|
|
2204
|
+
ctx.pull.onChildError(err);
|
|
2205
|
+
return ctx.pull.diff;
|
|
2206
|
+
}
|
|
2207
|
+
if (!ctx.iterator.throw) {
|
|
1953
2208
|
throw err;
|
|
1954
2209
|
}
|
|
1955
2210
|
resumePropsAsyncIterator(ctx);
|
|
1956
2211
|
let iteration;
|
|
1957
2212
|
try {
|
|
1958
|
-
ctx.
|
|
2213
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1959
2214
|
iteration = ctx.iterator.throw(err);
|
|
1960
2215
|
}
|
|
1961
2216
|
catch (err) {
|
|
1962
|
-
ctx.
|
|
2217
|
+
setFlag(ctx.ret, IsErrored);
|
|
1963
2218
|
throw err;
|
|
1964
2219
|
}
|
|
1965
2220
|
finally {
|
|
1966
|
-
ctx.
|
|
2221
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1967
2222
|
}
|
|
1968
2223
|
if (isPromiseLike(iteration)) {
|
|
1969
2224
|
return iteration.then((iteration) => {
|
|
1970
2225
|
if (iteration.done) {
|
|
1971
|
-
ctx.
|
|
2226
|
+
setFlag(ctx.ret, IsSyncGen, false);
|
|
2227
|
+
setFlag(ctx.ret, IsAsyncGen, false);
|
|
1972
2228
|
ctx.iterator = undefined;
|
|
1973
2229
|
}
|
|
1974
|
-
return
|
|
2230
|
+
return diffComponentChildren(ctx, iteration.value, !iteration.done);
|
|
1975
2231
|
}, (err) => {
|
|
1976
|
-
ctx.
|
|
2232
|
+
setFlag(ctx.ret, IsErrored);
|
|
1977
2233
|
throw err;
|
|
1978
2234
|
});
|
|
1979
2235
|
}
|
|
1980
2236
|
if (iteration.done) {
|
|
1981
|
-
ctx.
|
|
1982
|
-
ctx.
|
|
2237
|
+
setFlag(ctx.ret, IsSyncGen, false);
|
|
2238
|
+
setFlag(ctx.ret, IsAsyncGen, false);
|
|
1983
2239
|
ctx.iterator = undefined;
|
|
1984
2240
|
}
|
|
1985
|
-
return
|
|
2241
|
+
return diffComponentChildren(ctx, iteration.value, !iteration.done);
|
|
1986
2242
|
}
|
|
1987
|
-
|
|
1988
|
-
|
|
2243
|
+
/**
|
|
2244
|
+
* Propagates an error up the context tree by calling handleChildError with
|
|
2245
|
+
* each parent.
|
|
2246
|
+
*
|
|
2247
|
+
* @returns A promise which resolves to undefined when the error has been
|
|
2248
|
+
* handled, or undefined if the error was handled synchronously.
|
|
2249
|
+
*/
|
|
2250
|
+
function propagateError(ctx, err, schedulePromises) {
|
|
2251
|
+
const parent = ctx.parent;
|
|
2252
|
+
if (!parent) {
|
|
2253
|
+
throw err;
|
|
2254
|
+
}
|
|
2255
|
+
let diff;
|
|
1989
2256
|
try {
|
|
1990
|
-
|
|
2257
|
+
diff = handleChildError(parent, err);
|
|
1991
2258
|
}
|
|
1992
2259
|
catch (err) {
|
|
1993
|
-
|
|
1994
|
-
throw err;
|
|
1995
|
-
}
|
|
1996
|
-
return propagateError(ctx.parent, err);
|
|
2260
|
+
return propagateError(parent, err, schedulePromises);
|
|
1997
2261
|
}
|
|
1998
|
-
if (isPromiseLike(
|
|
1999
|
-
return
|
|
2000
|
-
if (!ctx.parent) {
|
|
2001
|
-
throw err;
|
|
2002
|
-
}
|
|
2003
|
-
return propagateError(ctx.parent, err);
|
|
2004
|
-
});
|
|
2262
|
+
if (isPromiseLike(diff)) {
|
|
2263
|
+
return diff.then(() => void commitComponent(parent, schedulePromises), (err) => propagateError(parent, err, schedulePromises));
|
|
2005
2264
|
}
|
|
2006
|
-
|
|
2265
|
+
commitComponent(parent, schedulePromises);
|
|
2007
2266
|
}
|
|
2008
|
-
|
|
2009
|
-
|
|
2267
|
+
/**
|
|
2268
|
+
* A re-export of some Crank exports as the default export.
|
|
2269
|
+
*
|
|
2270
|
+
* Some JSX tools expect things like createElement/Fragment to be defined on
|
|
2271
|
+
* the default export. Prefer using the named exports directly.
|
|
2272
|
+
*/
|
|
2010
2273
|
var crank = { createElement, Fragment };
|
|
2011
2274
|
|
|
2012
|
-
export { Context, Copy, Element, Fragment, Portal, Raw, Renderer, cloneElement, createElement, crank as default, isElement };
|
|
2275
|
+
export { Context, Copy, Element, Fragment, Portal, Raw, Renderer, Text, cloneElement, createElement, crank as default, isElement };
|
|
2013
2276
|
//# sourceMappingURL=crank.js.map
|