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