@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/umd.js
CHANGED
|
@@ -4,8 +4,252 @@
|
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Crank = {}));
|
|
5
5
|
})(this, (function (exports) { 'use strict';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// EVENT PHASE CONSTANTS
|
|
8
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
|
|
9
|
+
const NONE = 0;
|
|
10
|
+
const CAPTURING_PHASE = 1;
|
|
11
|
+
const AT_TARGET = 2;
|
|
12
|
+
const BUBBLING_PHASE = 3;
|
|
13
|
+
function isEventTarget(value) {
|
|
14
|
+
return (value != null &&
|
|
15
|
+
typeof value.addEventListener === "function" &&
|
|
16
|
+
typeof value.removeEventListener === "function" &&
|
|
17
|
+
typeof value.dispatchEvent === "function");
|
|
18
|
+
}
|
|
19
|
+
function setEventProperty(ev, key, value) {
|
|
20
|
+
Object.defineProperty(ev, key, { value, writable: false, configurable: true });
|
|
21
|
+
}
|
|
22
|
+
function isListenerOrListenerObject(value) {
|
|
23
|
+
return (typeof value === "function" ||
|
|
24
|
+
(value !== null &&
|
|
25
|
+
typeof value === "object" &&
|
|
26
|
+
typeof value.handleEvent === "function"));
|
|
27
|
+
}
|
|
28
|
+
function normalizeListenerOptions(options) {
|
|
29
|
+
if (typeof options === "boolean") {
|
|
30
|
+
return { capture: options };
|
|
31
|
+
}
|
|
32
|
+
else if (options == null) {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
return options;
|
|
36
|
+
}
|
|
37
|
+
const _parent = Symbol.for("CustomEventTarget.parent");
|
|
38
|
+
const _listeners = Symbol.for("CustomEventTarget.listeners");
|
|
39
|
+
const _delegates = Symbol.for("CustomEventTarget.delegates");
|
|
40
|
+
const _dispatchEventOnSelf = Symbol.for("CustomEventTarget.dispatchSelf");
|
|
41
|
+
class CustomEventTarget {
|
|
42
|
+
constructor(parent = null) {
|
|
43
|
+
this[_parent] = parent;
|
|
44
|
+
this[_listeners] = [];
|
|
45
|
+
this[_delegates] = new Set();
|
|
46
|
+
}
|
|
47
|
+
addEventListener(type, listener, options) {
|
|
48
|
+
if (!isListenerOrListenerObject(listener)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const listeners = this[_listeners];
|
|
52
|
+
options = normalizeListenerOptions(options);
|
|
53
|
+
let callback;
|
|
54
|
+
if (typeof listener === "function") {
|
|
55
|
+
callback = listener;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
callback = (ev) => listener.handleEvent(ev);
|
|
59
|
+
}
|
|
60
|
+
const record = { type, listener, callback, options };
|
|
61
|
+
if (options.once) {
|
|
62
|
+
record.callback = function () {
|
|
63
|
+
const i = listeners.indexOf(record);
|
|
64
|
+
if (i !== -1) {
|
|
65
|
+
listeners.splice(i, 1);
|
|
66
|
+
}
|
|
67
|
+
return callback.apply(this, arguments);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (listeners.some((record1) => record.type === record1.type &&
|
|
71
|
+
record.listener === record1.listener &&
|
|
72
|
+
!record.options.capture === !record1.options.capture)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
listeners.push(record);
|
|
76
|
+
for (const delegate of this[_delegates]) {
|
|
77
|
+
delegate.addEventListener(type, record.callback, record.options);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
removeEventListener(type, listener, options) {
|
|
81
|
+
const listeners = this[_listeners];
|
|
82
|
+
if (listeners == null || !isListenerOrListenerObject(listener)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const options1 = normalizeListenerOptions(options);
|
|
86
|
+
const i = listeners.findIndex((record) => record.type === type &&
|
|
87
|
+
record.listener === listener &&
|
|
88
|
+
!record.options.capture === !options1.capture);
|
|
89
|
+
if (i === -1) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const record = listeners[i];
|
|
93
|
+
listeners.splice(i, 1);
|
|
94
|
+
for (const delegate of this[_delegates]) {
|
|
95
|
+
delegate.removeEventListener(record.type, record.callback, record.options);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
dispatchEvent(ev) {
|
|
99
|
+
const path = [];
|
|
100
|
+
for (let parent = this[_parent]; parent; parent = parent[_parent]) {
|
|
101
|
+
path.push(parent);
|
|
102
|
+
}
|
|
103
|
+
let cancelBubble = false;
|
|
104
|
+
let immediateCancelBubble = false;
|
|
105
|
+
const stopPropagation = ev.stopPropagation;
|
|
106
|
+
setEventProperty(ev, "stopPropagation", () => {
|
|
107
|
+
cancelBubble = true;
|
|
108
|
+
return stopPropagation.call(ev);
|
|
109
|
+
});
|
|
110
|
+
const stopImmediatePropagation = ev.stopImmediatePropagation;
|
|
111
|
+
setEventProperty(ev, "stopImmediatePropagation", () => {
|
|
112
|
+
immediateCancelBubble = true;
|
|
113
|
+
return stopImmediatePropagation.call(ev);
|
|
114
|
+
});
|
|
115
|
+
setEventProperty(ev, "target", this);
|
|
116
|
+
// The only possible errors in this block are errors thrown by callbacks,
|
|
117
|
+
// and dispatchEvent will only log these errors rather than throwing them.
|
|
118
|
+
// Therefore, we place all code in a try block, log errors in the catch
|
|
119
|
+
// block, and use an unsafe return statement in the finally block.
|
|
120
|
+
//
|
|
121
|
+
// Each early return within the try block returns true because while the
|
|
122
|
+
// return value is overridden in the finally block, TypeScript
|
|
123
|
+
// (justifiably) does not recognize the unsafe return statement.
|
|
124
|
+
try {
|
|
125
|
+
setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
|
|
126
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
127
|
+
const target = path[i];
|
|
128
|
+
const listeners = target[_listeners];
|
|
129
|
+
setEventProperty(ev, "currentTarget", target);
|
|
130
|
+
for (let i = 0; i < listeners.length; i++) {
|
|
131
|
+
const record = listeners[i];
|
|
132
|
+
if (record.type === ev.type && record.options.capture) {
|
|
133
|
+
try {
|
|
134
|
+
record.callback.call(target, ev);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
console.error(err);
|
|
138
|
+
}
|
|
139
|
+
if (immediateCancelBubble) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (cancelBubble) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
{
|
|
149
|
+
setEventProperty(ev, "eventPhase", AT_TARGET);
|
|
150
|
+
setEventProperty(ev, "currentTarget", this);
|
|
151
|
+
this[_dispatchEventOnSelf](ev);
|
|
152
|
+
if (immediateCancelBubble) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
const listeners = this[_listeners];
|
|
156
|
+
for (let i = 0; i < listeners.length; i++) {
|
|
157
|
+
const record = listeners[i];
|
|
158
|
+
if (record.type === ev.type) {
|
|
159
|
+
try {
|
|
160
|
+
record.callback.call(this, ev);
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
console.error(err);
|
|
164
|
+
}
|
|
165
|
+
if (immediateCancelBubble) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (cancelBubble) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (ev.bubbles) {
|
|
175
|
+
setEventProperty(ev, "eventPhase", BUBBLING_PHASE);
|
|
176
|
+
for (let i = 0; i < path.length; i++) {
|
|
177
|
+
const target = path[i];
|
|
178
|
+
setEventProperty(ev, "currentTarget", target);
|
|
179
|
+
const listeners = target[_listeners];
|
|
180
|
+
for (let i = 0; i < listeners.length; i++) {
|
|
181
|
+
const record = listeners[i];
|
|
182
|
+
if (record.type === ev.type && !record.options.capture) {
|
|
183
|
+
try {
|
|
184
|
+
record.callback.call(target, ev);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
console.error(err);
|
|
188
|
+
}
|
|
189
|
+
if (immediateCancelBubble) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (cancelBubble) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
setEventProperty(ev, "eventPhase", NONE);
|
|
202
|
+
setEventProperty(ev, "currentTarget", null);
|
|
203
|
+
// eslint-disable-next-line no-unsafe-finally
|
|
204
|
+
return !ev.defaultPrevented;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
[_dispatchEventOnSelf](_ev) { }
|
|
208
|
+
}
|
|
209
|
+
CustomEventTarget.dispatchEventOnSelf = _dispatchEventOnSelf;
|
|
210
|
+
function addEventTargetDelegates(target, delegates, include = (target1) => target === target1) {
|
|
211
|
+
const delegates1 = delegates.filter(isEventTarget);
|
|
212
|
+
for (let target1 = target; target1 && include(target1); target1 = target1[_parent]) {
|
|
213
|
+
for (let i = 0; i < delegates1.length; i++) {
|
|
214
|
+
const delegate = delegates1[i];
|
|
215
|
+
if (target1[_delegates].has(delegate)) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
target1[_delegates].add(delegate);
|
|
219
|
+
for (const record of target1[_listeners]) {
|
|
220
|
+
delegate.addEventListener(record.type, record.callback, record.options);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function removeEventTargetDelegates(target, delegates, include = (target1) => target === target1) {
|
|
226
|
+
const delegates1 = delegates.filter(isEventTarget);
|
|
227
|
+
for (let target1 = target; target1 && include(target1); target1 = target1[_parent]) {
|
|
228
|
+
for (let i = 0; i < delegates1.length; i++) {
|
|
229
|
+
const delegate = delegates1[i];
|
|
230
|
+
if (!target1[_delegates].has(delegate)) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
target1[_delegates].delete(delegate);
|
|
234
|
+
for (const record of target1[_listeners]) {
|
|
235
|
+
delegate.removeEventListener(record.type, record.callback, record.options);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function clearEventListeners(target) {
|
|
241
|
+
const listeners = target[_listeners];
|
|
242
|
+
const delegates = target[_delegates];
|
|
243
|
+
for (let i = 0; i < listeners.length; i++) {
|
|
244
|
+
const record = listeners[i];
|
|
245
|
+
for (const delegate of delegates) {
|
|
246
|
+
delegate.removeEventListener(record.type, record.callback, record.options);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
listeners.length = 0;
|
|
250
|
+
delegates.clear();
|
|
251
|
+
}
|
|
252
|
+
|
|
9
253
|
function wrap(value) {
|
|
10
254
|
return value === undefined ? [] : Array.isArray(value) ? value : [value];
|
|
11
255
|
}
|
|
@@ -27,8 +271,7 @@
|
|
|
27
271
|
: typeof value === "string" ||
|
|
28
272
|
typeof value[Symbol.iterator] !== "function"
|
|
29
273
|
? [value]
|
|
30
|
-
:
|
|
31
|
-
[...value];
|
|
274
|
+
: [...value];
|
|
32
275
|
}
|
|
33
276
|
function isIteratorLike(value) {
|
|
34
277
|
return value != null && typeof value.next === "function";
|
|
@@ -36,11 +279,84 @@
|
|
|
36
279
|
function isPromiseLike(value) {
|
|
37
280
|
return value != null && typeof value.then === "function";
|
|
38
281
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
282
|
+
function createRaceRecord(contender) {
|
|
283
|
+
const deferreds = new Set();
|
|
284
|
+
const record = { deferreds, settled: false };
|
|
285
|
+
// This call to `then` happens once for the lifetime of the value.
|
|
286
|
+
Promise.resolve(contender).then((value) => {
|
|
287
|
+
for (const { resolve } of deferreds) {
|
|
288
|
+
resolve(value);
|
|
289
|
+
}
|
|
290
|
+
deferreds.clear();
|
|
291
|
+
record.settled = true;
|
|
292
|
+
}, (err) => {
|
|
293
|
+
for (const { reject } of deferreds) {
|
|
294
|
+
reject(err);
|
|
295
|
+
}
|
|
296
|
+
deferreds.clear();
|
|
297
|
+
record.settled = true;
|
|
298
|
+
});
|
|
299
|
+
return record;
|
|
300
|
+
}
|
|
301
|
+
// Promise.race is memory unsafe. This is alternative which is. See:
|
|
302
|
+
// https://github.com/nodejs/node/issues/17469#issuecomment-685235106
|
|
303
|
+
// Keys are the values passed to race.
|
|
304
|
+
// Values are a record of data containing a set of deferreds and whether the
|
|
305
|
+
// value has settled.
|
|
306
|
+
const wm = new WeakMap();
|
|
307
|
+
function safeRace(contenders) {
|
|
308
|
+
let deferred;
|
|
309
|
+
const result = new Promise((resolve, reject) => {
|
|
310
|
+
deferred = { resolve, reject };
|
|
311
|
+
for (const contender of contenders) {
|
|
312
|
+
if (!isPromiseLike(contender)) {
|
|
313
|
+
// If the contender is a not a then-able, attempting to use it as a key
|
|
314
|
+
// in the weakmap would throw an error. Luckily, it is safe to call
|
|
315
|
+
// `Promise.resolve(contender).then` on regular values multiple
|
|
316
|
+
// times because the promise fulfills immediately.
|
|
317
|
+
Promise.resolve(contender).then(resolve, reject);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
let record = wm.get(contender);
|
|
321
|
+
if (record === undefined) {
|
|
322
|
+
record = createRaceRecord(contender);
|
|
323
|
+
record.deferreds.add(deferred);
|
|
324
|
+
wm.set(contender, record);
|
|
325
|
+
}
|
|
326
|
+
else if (record.settled) {
|
|
327
|
+
// If the value has settled, it is safe to call
|
|
328
|
+
// `Promise.resolve(contender).then` on it.
|
|
329
|
+
Promise.resolve(contender).then(resolve, reject);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
record.deferreds.add(deferred);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
// The finally callback executes when any value settles, preventing any of
|
|
337
|
+
// the unresolved values from retaining a reference to the resolved value.
|
|
338
|
+
return result.finally(() => {
|
|
339
|
+
for (const contender of contenders) {
|
|
340
|
+
if (isPromiseLike(contender)) {
|
|
341
|
+
const record = wm.get(contender);
|
|
342
|
+
if (record) {
|
|
343
|
+
record.deferreds.delete(deferred);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const NOOP = () => { };
|
|
351
|
+
function getTagName(tag) {
|
|
352
|
+
return typeof tag === "function"
|
|
353
|
+
? tag.name || "Anonymous"
|
|
354
|
+
: typeof tag === "string"
|
|
355
|
+
? tag
|
|
356
|
+
: // tag is symbol, using else branch to avoid typeof tag === "symbol"
|
|
357
|
+
tag.description || "Anonymous";
|
|
358
|
+
}
|
|
359
|
+
/*** SPECIAL TAGS ***/
|
|
44
360
|
/**
|
|
45
361
|
* A special tag for grouping multiple children within the same parent.
|
|
46
362
|
*
|
|
@@ -52,8 +368,8 @@
|
|
|
52
368
|
* reference this export.
|
|
53
369
|
*/
|
|
54
370
|
const Fragment = "";
|
|
55
|
-
// TODO: We assert the following symbol tags as
|
|
56
|
-
// for symbol tags in JSX doesn
|
|
371
|
+
// TODO: We assert the following symbol tags as Components because TypeScript
|
|
372
|
+
// support for symbol tags in JSX doesn't exist yet.
|
|
57
373
|
// https://github.com/microsoft/TypeScript/issues/38367
|
|
58
374
|
/**
|
|
59
375
|
* A special tag for rendering into a new root node via a root prop.
|
|
@@ -61,13 +377,13 @@
|
|
|
61
377
|
* This tag is useful for creating element trees with multiple roots, for
|
|
62
378
|
* things like modals or tooltips.
|
|
63
379
|
*
|
|
64
|
-
* Renderer.prototype.render()
|
|
65
|
-
*
|
|
380
|
+
* Renderer.prototype.render() implicitly wraps top-level in a Portal element
|
|
381
|
+
* with the root set to the second argument passed in.
|
|
66
382
|
*/
|
|
67
383
|
const Portal = Symbol.for("crank.Portal");
|
|
68
384
|
/**
|
|
69
385
|
* A special tag which preserves whatever was previously rendered in the
|
|
70
|
-
* element
|
|
386
|
+
* element's position.
|
|
71
387
|
*
|
|
72
388
|
* Copy elements are useful for when you want to prevent a subtree from
|
|
73
389
|
* rerendering as a performance optimization. Copy elements can also be keyed,
|
|
@@ -75,10 +391,13 @@
|
|
|
75
391
|
*/
|
|
76
392
|
const Copy = Symbol.for("crank.Copy");
|
|
77
393
|
/**
|
|
78
|
-
* A special tag for
|
|
394
|
+
* A special tag for rendering text nodes.
|
|
79
395
|
*
|
|
80
|
-
*
|
|
396
|
+
* Strings in the element tree are implicitly wrapped in a Text element with
|
|
397
|
+
* value set to the string.
|
|
81
398
|
*/
|
|
399
|
+
const Text = Symbol.for("crank.Text");
|
|
400
|
+
/** A special tag for injecting raw nodes or strings via a value prop. */
|
|
82
401
|
const Raw = Symbol.for("crank.Raw");
|
|
83
402
|
const ElementSymbol = Symbol.for("crank.Element");
|
|
84
403
|
/**
|
|
@@ -106,15 +425,6 @@
|
|
|
106
425
|
this.tag = tag;
|
|
107
426
|
this.props = props;
|
|
108
427
|
}
|
|
109
|
-
get key() {
|
|
110
|
-
return this.props.key;
|
|
111
|
-
}
|
|
112
|
-
get ref() {
|
|
113
|
-
return this.props.ref;
|
|
114
|
-
}
|
|
115
|
-
get copy() {
|
|
116
|
-
return !!this.props.copy;
|
|
117
|
-
}
|
|
118
428
|
}
|
|
119
429
|
// See Element interface
|
|
120
430
|
Element.prototype.$$typeof = ElementSymbol;
|
|
@@ -122,34 +432,34 @@
|
|
|
122
432
|
return value != null && value.$$typeof === ElementSymbol;
|
|
123
433
|
}
|
|
124
434
|
const DEPRECATED_PROP_PREFIXES = ["crank-", "c-", "$"];
|
|
125
|
-
const DEPRECATED_SPECIAL_PROP_BASES = ["key", "ref", "static"];
|
|
126
|
-
const SPECIAL_PROPS = new Set(["children", "key", "ref", "copy"]);
|
|
127
|
-
for (const propPrefix of DEPRECATED_PROP_PREFIXES) {
|
|
128
|
-
for (const propBase of DEPRECATED_SPECIAL_PROP_BASES) {
|
|
129
|
-
SPECIAL_PROPS.add(propPrefix + propBase);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
435
|
+
const DEPRECATED_SPECIAL_PROP_BASES = ["key", "ref", "static", "copy"];
|
|
132
436
|
/**
|
|
133
437
|
* Creates an element with the specified tag, props and children.
|
|
134
438
|
*
|
|
135
439
|
* This function is usually used as a transpilation target for JSX transpilers,
|
|
136
440
|
* but it can also be called directly. It additionally extracts special props so
|
|
137
|
-
* they aren
|
|
441
|
+
* they aren't accessible to renderer methods or components, and assigns the
|
|
138
442
|
* children prop according to any additional arguments passed to the function.
|
|
139
443
|
*/
|
|
140
444
|
function createElement(tag, props, ...children) {
|
|
141
445
|
if (props == null) {
|
|
142
446
|
props = {};
|
|
143
447
|
}
|
|
448
|
+
if ("static" in props) {
|
|
449
|
+
console.error(`The \`static\` prop is deprecated. Use \`copy\` instead.`);
|
|
450
|
+
props["copy"] = props["static"];
|
|
451
|
+
delete props["static"];
|
|
452
|
+
}
|
|
144
453
|
for (let i = 0; i < DEPRECATED_PROP_PREFIXES.length; i++) {
|
|
145
454
|
const propPrefix = DEPRECATED_PROP_PREFIXES[i];
|
|
146
455
|
for (let j = 0; j < DEPRECATED_SPECIAL_PROP_BASES.length; j++) {
|
|
147
456
|
const propBase = DEPRECATED_SPECIAL_PROP_BASES[j];
|
|
148
457
|
const deprecatedPropName = propPrefix + propBase;
|
|
149
|
-
const targetPropBase = propBase === "static" ? "copy" : propBase;
|
|
150
458
|
if (deprecatedPropName in props) {
|
|
151
|
-
|
|
459
|
+
const targetPropBase = propBase === "static" ? "copy" : propBase;
|
|
460
|
+
console.error(`The \`${deprecatedPropName}\` prop is deprecated. Use \`${targetPropBase}\` instead.`);
|
|
152
461
|
props[targetPropBase] = props[deprecatedPropName];
|
|
462
|
+
delete props[deprecatedPropName];
|
|
153
463
|
}
|
|
154
464
|
}
|
|
155
465
|
}
|
|
@@ -164,13 +474,13 @@
|
|
|
164
474
|
/** Clones a given element, shallowly copying the props object. */
|
|
165
475
|
function cloneElement(el) {
|
|
166
476
|
if (!isElement(el)) {
|
|
167
|
-
throw new TypeError(
|
|
477
|
+
throw new TypeError(`Cannot clone non-element: ${String(el)}`);
|
|
168
478
|
}
|
|
169
479
|
return new Element(el.tag, { ...el.props });
|
|
170
480
|
}
|
|
171
481
|
function narrow(value) {
|
|
172
482
|
if (typeof value === "boolean" || value == null) {
|
|
173
|
-
return
|
|
483
|
+
return;
|
|
174
484
|
}
|
|
175
485
|
else if (typeof value === "string" || isElement(value)) {
|
|
176
486
|
return value;
|
|
@@ -180,137 +490,203 @@
|
|
|
180
490
|
}
|
|
181
491
|
return value.toString();
|
|
182
492
|
}
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
else {
|
|
210
|
-
// We could use recursion here but it’s just easier to do it inline.
|
|
211
|
-
for (let j = 0; j < value.length; j++) {
|
|
212
|
-
const value1 = value[j];
|
|
213
|
-
if (!value1) ;
|
|
214
|
-
else if (typeof value1 === "string") {
|
|
215
|
-
buffer = (buffer || "") + value1;
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
if (buffer) {
|
|
219
|
-
result.push(buffer);
|
|
220
|
-
buffer = undefined;
|
|
221
|
-
}
|
|
222
|
-
result.push(value1);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
493
|
+
/*** RETAINER FLAGS ***/
|
|
494
|
+
const DidDiff = 1 << 0;
|
|
495
|
+
const DidCommit = 1 << 1;
|
|
496
|
+
const IsCopied = 1 << 2;
|
|
497
|
+
const IsUpdating = 1 << 3;
|
|
498
|
+
const IsExecuting = 1 << 4;
|
|
499
|
+
const IsRefreshing = 1 << 5;
|
|
500
|
+
const IsScheduling = 1 << 6;
|
|
501
|
+
const IsSchedulingFallback = 1 << 7;
|
|
502
|
+
const IsUnmounted = 1 << 8;
|
|
503
|
+
// TODO: Is this flag still necessary or can we use IsUnmounted?
|
|
504
|
+
const IsErrored = 1 << 9;
|
|
505
|
+
const IsResurrecting = 1 << 10;
|
|
506
|
+
// TODO: Maybe we can get rid of IsSyncGen and IsAsyncGen
|
|
507
|
+
const IsSyncGen = 1 << 11;
|
|
508
|
+
const IsAsyncGen = 1 << 12;
|
|
509
|
+
const IsInForOfLoop = 1 << 13;
|
|
510
|
+
const IsInForAwaitOfLoop = 1 << 14;
|
|
511
|
+
const NeedsToYield = 1 << 15;
|
|
512
|
+
const PropsAvailable = 1 << 16;
|
|
513
|
+
function getFlag(ret, flag) {
|
|
514
|
+
return !!(ret.f & flag);
|
|
515
|
+
}
|
|
516
|
+
function setFlag(ret, flag, value = true) {
|
|
517
|
+
if (value) {
|
|
518
|
+
ret.f |= flag;
|
|
226
519
|
}
|
|
227
|
-
|
|
228
|
-
|
|
520
|
+
else {
|
|
521
|
+
ret.f &= ~flag;
|
|
229
522
|
}
|
|
230
|
-
return result;
|
|
231
523
|
}
|
|
232
524
|
/**
|
|
233
525
|
* @internal
|
|
234
|
-
*
|
|
235
|
-
*
|
|
526
|
+
* Retainers are objects which act as the internal representation of elements,
|
|
527
|
+
* mirroring the element tree.
|
|
236
528
|
*/
|
|
237
529
|
class Retainer {
|
|
238
530
|
constructor(el) {
|
|
531
|
+
this.f = 0;
|
|
239
532
|
this.el = el;
|
|
240
533
|
this.ctx = undefined;
|
|
241
534
|
this.children = undefined;
|
|
535
|
+
this.fallback = undefined;
|
|
242
536
|
this.value = undefined;
|
|
243
|
-
this.
|
|
244
|
-
this.
|
|
245
|
-
this.
|
|
246
|
-
this.
|
|
537
|
+
this.oldProps = undefined;
|
|
538
|
+
this.pendingDiff = undefined;
|
|
539
|
+
this.onNextDiff = undefined;
|
|
540
|
+
this.graveyard = undefined;
|
|
541
|
+
this.lingerers = undefined;
|
|
247
542
|
}
|
|
248
543
|
}
|
|
544
|
+
function cloneRetainer(ret) {
|
|
545
|
+
const clone = new Retainer(ret.el);
|
|
546
|
+
clone.f = ret.f;
|
|
547
|
+
clone.ctx = ret.ctx;
|
|
548
|
+
clone.children = ret.children;
|
|
549
|
+
clone.fallback = ret.fallback;
|
|
550
|
+
clone.value = ret.value;
|
|
551
|
+
clone.scope = ret.scope;
|
|
552
|
+
clone.oldProps = ret.oldProps;
|
|
553
|
+
clone.pendingDiff = ret.pendingDiff;
|
|
554
|
+
clone.onNextDiff = ret.onNextDiff;
|
|
555
|
+
clone.graveyard = ret.graveyard;
|
|
556
|
+
clone.lingerers = ret.lingerers;
|
|
557
|
+
return clone;
|
|
558
|
+
}
|
|
249
559
|
/**
|
|
250
560
|
* Finds the value of the element according to its type.
|
|
251
561
|
*
|
|
252
|
-
* @returns
|
|
562
|
+
* @returns A node, an array of nodes or undefined.
|
|
253
563
|
*/
|
|
254
|
-
function getValue(ret) {
|
|
255
|
-
if (
|
|
256
|
-
return
|
|
257
|
-
|
|
258
|
-
|
|
564
|
+
function getValue(ret, isNested = false, index) {
|
|
565
|
+
if (getFlag(ret, IsScheduling) && isNested) {
|
|
566
|
+
return ret.fallback ? getValue(ret.fallback, isNested, index) : undefined;
|
|
567
|
+
}
|
|
568
|
+
else if (ret.fallback && !getFlag(ret, DidDiff)) {
|
|
569
|
+
return ret.fallback
|
|
570
|
+
? getValue(ret.fallback, isNested, index)
|
|
571
|
+
: ret.fallback;
|
|
259
572
|
}
|
|
260
573
|
else if (ret.el.tag === Portal) {
|
|
261
574
|
return;
|
|
262
575
|
}
|
|
263
|
-
else if (
|
|
264
|
-
|
|
576
|
+
else if (ret.el.tag === Fragment || typeof ret.el.tag === "function") {
|
|
577
|
+
if (index != null && ret.ctx) {
|
|
578
|
+
ret.ctx.index = index;
|
|
579
|
+
}
|
|
580
|
+
return unwrap(getChildValues(ret, index));
|
|
265
581
|
}
|
|
266
|
-
return
|
|
582
|
+
return ret.value;
|
|
267
583
|
}
|
|
268
584
|
/**
|
|
269
|
-
* Walks an element
|
|
585
|
+
* Walks an element's children to find its child values.
|
|
586
|
+
*
|
|
587
|
+
* @param ret - The retainer whose child values we are reading.
|
|
588
|
+
* @param startIndex - Starting index to thread through for context index updates.
|
|
270
589
|
*
|
|
271
|
-
* @returns
|
|
590
|
+
* @returns An array of nodes.
|
|
272
591
|
*/
|
|
273
|
-
function getChildValues(ret) {
|
|
274
|
-
if (ret.cachedChildValues) {
|
|
275
|
-
return wrap(ret.cachedChildValues);
|
|
276
|
-
}
|
|
592
|
+
function getChildValues(ret, startIndex) {
|
|
277
593
|
const values = [];
|
|
594
|
+
const lingerers = ret.lingerers;
|
|
278
595
|
const children = wrap(ret.children);
|
|
596
|
+
let currentIndex = startIndex;
|
|
279
597
|
for (let i = 0; i < children.length; i++) {
|
|
598
|
+
if (lingerers != null && lingerers[i] != null) {
|
|
599
|
+
const rets = lingerers[i];
|
|
600
|
+
for (const ret of rets) {
|
|
601
|
+
const value = getValue(ret, true, currentIndex);
|
|
602
|
+
if (Array.isArray(value)) {
|
|
603
|
+
for (let j = 0; j < value.length; j++) {
|
|
604
|
+
values.push(value[j]);
|
|
605
|
+
}
|
|
606
|
+
if (currentIndex != null) {
|
|
607
|
+
currentIndex += value.length;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
else if (value) {
|
|
611
|
+
values.push(value);
|
|
612
|
+
if (currentIndex != null) {
|
|
613
|
+
currentIndex++;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
280
618
|
const child = children[i];
|
|
281
619
|
if (child) {
|
|
282
|
-
|
|
620
|
+
const value = getValue(child, true, currentIndex);
|
|
621
|
+
if (Array.isArray(value)) {
|
|
622
|
+
for (let j = 0; j < value.length; j++) {
|
|
623
|
+
values.push(value[j]);
|
|
624
|
+
}
|
|
625
|
+
if (currentIndex != null) {
|
|
626
|
+
currentIndex += value.length;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
else if (value) {
|
|
630
|
+
values.push(value);
|
|
631
|
+
if (currentIndex != null) {
|
|
632
|
+
currentIndex++;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
283
635
|
}
|
|
284
636
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
637
|
+
if (lingerers != null && lingerers.length > children.length) {
|
|
638
|
+
for (let i = children.length; i < lingerers.length; i++) {
|
|
639
|
+
const rets = lingerers[i];
|
|
640
|
+
if (rets != null) {
|
|
641
|
+
for (const ret of rets) {
|
|
642
|
+
const value = getValue(ret, true, currentIndex);
|
|
643
|
+
if (Array.isArray(value)) {
|
|
644
|
+
for (let j = 0; j < value.length; j++) {
|
|
645
|
+
values.push(value[j]);
|
|
646
|
+
}
|
|
647
|
+
if (currentIndex != null) {
|
|
648
|
+
currentIndex += value.length;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
else if (value) {
|
|
652
|
+
values.push(value);
|
|
653
|
+
if (currentIndex != null) {
|
|
654
|
+
currentIndex++;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
289
660
|
}
|
|
290
|
-
return
|
|
661
|
+
return values;
|
|
662
|
+
}
|
|
663
|
+
function stripSpecialProps(props) {
|
|
664
|
+
let _;
|
|
665
|
+
let result;
|
|
666
|
+
({ key: _, ref: _, copy: _, hydrate: _, children: _, ...result } = props);
|
|
667
|
+
return result;
|
|
291
668
|
}
|
|
292
|
-
const
|
|
669
|
+
const defaultAdapter = {
|
|
293
670
|
create() {
|
|
294
|
-
throw new Error("
|
|
671
|
+
throw new Error("adapter must implement create");
|
|
295
672
|
},
|
|
296
|
-
|
|
297
|
-
throw new Error("
|
|
673
|
+
adopt() {
|
|
674
|
+
throw new Error("adapter must implement adopt() for hydration");
|
|
298
675
|
},
|
|
299
|
-
scope:
|
|
300
|
-
read:
|
|
301
|
-
text:
|
|
302
|
-
raw:
|
|
676
|
+
scope: ({ scope }) => scope,
|
|
677
|
+
read: (value) => value,
|
|
678
|
+
text: ({ value }) => value,
|
|
679
|
+
raw: ({ value }) => value,
|
|
303
680
|
patch: NOOP,
|
|
304
681
|
arrange: NOOP,
|
|
305
|
-
|
|
306
|
-
|
|
682
|
+
remove: NOOP,
|
|
683
|
+
finalize: NOOP,
|
|
307
684
|
};
|
|
308
|
-
const _RendererImpl = Symbol.for("crank.RendererImpl");
|
|
309
685
|
/**
|
|
310
686
|
* An abstract class which is subclassed to render to different target
|
|
311
|
-
* environments. Subclasses
|
|
312
|
-
*
|
|
313
|
-
*
|
|
687
|
+
* environments. Subclasses call super() with a custom RenderAdapter object.
|
|
688
|
+
* This class is responsible for kicking off the rendering process and caching
|
|
689
|
+
* previous trees by root.
|
|
314
690
|
*
|
|
315
691
|
* @template TNode - The type of the node for a rendering environment.
|
|
316
692
|
* @template TScope - Data which is passed down the tree.
|
|
@@ -318,125 +694,128 @@
|
|
|
318
694
|
* @template TResult - The type of exposed values.
|
|
319
695
|
*/
|
|
320
696
|
class Renderer {
|
|
321
|
-
constructor(
|
|
697
|
+
constructor(adapter) {
|
|
322
698
|
this.cache = new WeakMap();
|
|
323
|
-
this
|
|
324
|
-
...defaultRendererImpl,
|
|
325
|
-
...impl,
|
|
326
|
-
};
|
|
699
|
+
this.adapter = { ...defaultAdapter, ...adapter };
|
|
327
700
|
}
|
|
328
701
|
/**
|
|
329
702
|
* Renders an element tree into a specific root.
|
|
330
703
|
*
|
|
331
|
-
* @param children - An element tree.
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
* the same or an error will be thrown.
|
|
704
|
+
* @param children - An element tree. Rendering null deletes cached renders.
|
|
705
|
+
* @param root - The root to be rendered into. The renderer caches renders
|
|
706
|
+
* per root.
|
|
707
|
+
* @param bridge - An optional context that will be the ancestor context of
|
|
708
|
+
* all elements in the tree. Useful for connecting different renderers so
|
|
709
|
+
* that events/provisions/errors properly propagate. The context for a given
|
|
710
|
+
* root must be the same between renders.
|
|
339
711
|
*
|
|
340
712
|
* @returns The result of rendering the children, or a possible promise of
|
|
341
713
|
* the result if the element tree renders asynchronously.
|
|
342
714
|
*/
|
|
343
715
|
render(children, root, bridge) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (typeof root === "object" && root !== null) {
|
|
347
|
-
ret = this.cache.get(root);
|
|
348
|
-
}
|
|
349
|
-
let oldProps;
|
|
350
|
-
if (ret === undefined) {
|
|
351
|
-
ret = new Retainer(createElement(Portal, { children, root }));
|
|
352
|
-
ret.value = root;
|
|
353
|
-
ret.ctx = ctx;
|
|
354
|
-
if (typeof root === "object" && root !== null && children != null) {
|
|
355
|
-
this.cache.set(root, ret);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
else if (ret.ctx !== ctx) {
|
|
359
|
-
throw new Error("Context mismatch");
|
|
360
|
-
}
|
|
361
|
-
else {
|
|
362
|
-
oldProps = ret.el.props;
|
|
363
|
-
ret.el = createElement(Portal, { children, root });
|
|
364
|
-
if (typeof root === "object" && root !== null && children == null) {
|
|
365
|
-
this.cache.delete(root);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
const impl = this[_RendererImpl];
|
|
369
|
-
const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children, undefined);
|
|
370
|
-
// We return the child values of the portal because portal elements
|
|
371
|
-
// themselves have no readable value.
|
|
372
|
-
if (isPromiseLike(childValues)) {
|
|
373
|
-
return childValues.then((childValues) => commitRootRender(impl, root, ctx, ret, childValues, oldProps));
|
|
374
|
-
}
|
|
375
|
-
return commitRootRender(impl, root, ctx, ret, childValues, oldProps);
|
|
716
|
+
const ret = getRootRetainer(this, bridge, { children, root });
|
|
717
|
+
return renderRoot(this.adapter, root, ret, children);
|
|
376
718
|
}
|
|
377
719
|
hydrate(children, root, bridge) {
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
720
|
+
const ret = getRootRetainer(this, bridge, {
|
|
721
|
+
children,
|
|
722
|
+
root,
|
|
723
|
+
hydrate: true,
|
|
724
|
+
});
|
|
725
|
+
return renderRoot(this.adapter, root, ret, children);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
/*** PRIVATE RENDERER FUNCTIONS ***/
|
|
729
|
+
function getRootRetainer(renderer, bridge, { children, root, hydrate, }) {
|
|
730
|
+
let ret;
|
|
731
|
+
const bridgeCtx = bridge && bridge[_ContextState];
|
|
732
|
+
if (typeof root === "object" && root !== null) {
|
|
733
|
+
ret = renderer.cache.get(root);
|
|
734
|
+
}
|
|
735
|
+
const adapter = renderer.adapter;
|
|
736
|
+
if (ret === undefined) {
|
|
737
|
+
ret = new Retainer(createElement(Portal, { children, root, hydrate }));
|
|
388
738
|
ret.value = root;
|
|
739
|
+
ret.ctx = bridgeCtx;
|
|
740
|
+
ret.scope = adapter.scope({
|
|
741
|
+
tag: Portal,
|
|
742
|
+
tagName: getTagName(Portal),
|
|
743
|
+
props: stripSpecialProps(ret.el.props),
|
|
744
|
+
scope: undefined,
|
|
745
|
+
});
|
|
746
|
+
// remember that typeof null === "object"
|
|
389
747
|
if (typeof root === "object" && root !== null && children != null) {
|
|
390
|
-
|
|
748
|
+
renderer.cache.set(root, ret);
|
|
391
749
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
750
|
+
}
|
|
751
|
+
else if (ret.ctx !== bridgeCtx) {
|
|
752
|
+
throw new Error("A previous call to render() was passed a different context");
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
ret.el = createElement(Portal, { children, root, hydrate });
|
|
756
|
+
if (typeof root === "object" && root !== null && children == null) {
|
|
757
|
+
renderer.cache.delete(root);
|
|
398
758
|
}
|
|
399
|
-
return commitRootRender(impl, root, ctx, ret, childValues, oldProps);
|
|
400
759
|
}
|
|
760
|
+
return ret;
|
|
401
761
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (
|
|
406
|
-
|
|
407
|
-
|
|
762
|
+
function renderRoot(adapter, root, ret, children) {
|
|
763
|
+
const diff = diffChildren(adapter, root, ret, ret.ctx, ret.scope, ret, children);
|
|
764
|
+
const schedulePromises = [];
|
|
765
|
+
if (isPromiseLike(diff)) {
|
|
766
|
+
return diff.then(() => {
|
|
767
|
+
commit(adapter, ret, ret, ret.ctx, ret.scope, 0, schedulePromises, undefined);
|
|
768
|
+
if (schedulePromises.length > 0) {
|
|
769
|
+
return Promise.all(schedulePromises).then(() => {
|
|
770
|
+
if (typeof root !== "object" || root === null) {
|
|
771
|
+
unmount(adapter, ret, ret.ctx, ret, false);
|
|
772
|
+
}
|
|
773
|
+
return adapter.read(unwrap(getChildValues(ret)));
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
if (typeof root !== "object" || root === null) {
|
|
777
|
+
unmount(adapter, ret, ret.ctx, ret, false);
|
|
778
|
+
}
|
|
779
|
+
return adapter.read(unwrap(getChildValues(ret)));
|
|
780
|
+
});
|
|
408
781
|
}
|
|
409
|
-
ret.
|
|
410
|
-
if (
|
|
411
|
-
|
|
782
|
+
commit(adapter, ret, ret, ret.ctx, ret.scope, 0, schedulePromises, undefined);
|
|
783
|
+
if (schedulePromises.length > 0) {
|
|
784
|
+
return Promise.all(schedulePromises).then(() => {
|
|
785
|
+
if (typeof root !== "object" || root === null) {
|
|
786
|
+
unmount(adapter, ret, ret.ctx, ret, false);
|
|
787
|
+
}
|
|
788
|
+
return adapter.read(unwrap(getChildValues(ret)));
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
if (typeof root !== "object" || root === null) {
|
|
792
|
+
unmount(adapter, ret, ret.ctx, ret, false);
|
|
412
793
|
}
|
|
413
|
-
return
|
|
794
|
+
return adapter.read(unwrap(getChildValues(ret)));
|
|
414
795
|
}
|
|
415
|
-
function diffChildren(
|
|
796
|
+
function diffChildren(adapter, root, host, ctx, scope, parent, newChildren) {
|
|
416
797
|
const oldRetained = wrap(parent.children);
|
|
417
798
|
const newRetained = [];
|
|
418
|
-
const
|
|
419
|
-
const
|
|
420
|
-
let graveyard;
|
|
799
|
+
const newChildren1 = arrayify(newChildren);
|
|
800
|
+
const diffs = [];
|
|
421
801
|
let childrenByKey;
|
|
422
802
|
let seenKeys;
|
|
423
803
|
let isAsync = false;
|
|
424
|
-
// When hydrating, sibling element trees must be rendered in order, because
|
|
425
|
-
// we do not know how many DOM nodes an element will render.
|
|
426
|
-
let hydrationBlock;
|
|
427
804
|
let oi = 0;
|
|
428
805
|
let oldLength = oldRetained.length;
|
|
429
|
-
|
|
806
|
+
let graveyard;
|
|
807
|
+
for (let ni = 0, newLength = newChildren1.length; ni < newLength; ni++) {
|
|
430
808
|
// length checks to prevent index out of bounds deoptimizations.
|
|
431
809
|
let ret = oi >= oldLength ? undefined : oldRetained[oi];
|
|
432
|
-
let child = narrow(
|
|
810
|
+
let child = narrow(newChildren1[ni]);
|
|
433
811
|
{
|
|
434
812
|
// aligning new children with old retainers
|
|
435
|
-
let oldKey = typeof ret === "object" ? ret.el.key : undefined;
|
|
436
|
-
let newKey = typeof child === "object" ? child.key : undefined;
|
|
813
|
+
let oldKey = typeof ret === "object" ? ret.el.props.key : undefined;
|
|
814
|
+
let newKey = typeof child === "object" ? child.props.key : undefined;
|
|
437
815
|
if (newKey !== undefined && seenKeys && seenKeys.has(newKey)) {
|
|
438
|
-
console.error(
|
|
439
|
-
|
|
816
|
+
console.error(`Duplicate key found in <${getTagName(parent.el.tag)}>`, newKey);
|
|
817
|
+
child = cloneElement(child);
|
|
818
|
+
newKey = child.props.key = undefined;
|
|
440
819
|
}
|
|
441
820
|
if (oldKey === newKey) {
|
|
442
821
|
if (childrenByKey !== undefined && newKey !== undefined) {
|
|
@@ -450,7 +829,7 @@
|
|
|
450
829
|
while (ret !== undefined && oldKey !== undefined) {
|
|
451
830
|
oi++;
|
|
452
831
|
ret = oldRetained[oi];
|
|
453
|
-
oldKey = typeof ret === "object" ? ret.el.key : undefined;
|
|
832
|
+
oldKey = typeof ret === "object" ? ret.el.props.key : undefined;
|
|
454
833
|
}
|
|
455
834
|
oi++;
|
|
456
835
|
}
|
|
@@ -463,405 +842,748 @@
|
|
|
463
842
|
}
|
|
464
843
|
}
|
|
465
844
|
}
|
|
466
|
-
|
|
467
|
-
let value;
|
|
845
|
+
let diff = undefined;
|
|
468
846
|
if (typeof child === "object") {
|
|
469
|
-
|
|
470
|
-
|
|
847
|
+
let childCopied = false;
|
|
848
|
+
if (child.tag === Copy) {
|
|
849
|
+
childCopied = true;
|
|
850
|
+
}
|
|
851
|
+
else if (typeof ret === "object" &&
|
|
852
|
+
ret.el === child &&
|
|
853
|
+
getFlag(ret, DidCommit)) {
|
|
854
|
+
// If the child is the same as the retained element, we skip
|
|
855
|
+
// re-rendering.
|
|
856
|
+
childCopied = true;
|
|
471
857
|
}
|
|
472
858
|
else {
|
|
473
|
-
|
|
474
|
-
let copy = false;
|
|
475
|
-
if (typeof ret === "object" && ret.el.tag === child.tag) {
|
|
476
|
-
oldProps = ret.el.props;
|
|
859
|
+
if (ret && ret.el.tag === child.tag) {
|
|
477
860
|
ret.el = child;
|
|
478
|
-
if (child.copy) {
|
|
479
|
-
|
|
480
|
-
copy = true;
|
|
861
|
+
if (child.props.copy && typeof child.props.copy !== "string") {
|
|
862
|
+
childCopied = true;
|
|
481
863
|
}
|
|
482
864
|
}
|
|
483
|
-
else {
|
|
484
|
-
|
|
485
|
-
|
|
865
|
+
else if (ret) {
|
|
866
|
+
// we do not need to add the retainer to the graveyard if it is the
|
|
867
|
+
// fallback of another retainer
|
|
868
|
+
// search for the tag in fallback chain
|
|
869
|
+
let candidateFound = false;
|
|
870
|
+
for (let predecessor = ret, candidate = ret.fallback; candidate; predecessor = candidate, candidate = candidate.fallback) {
|
|
871
|
+
if (candidate.el.tag === child.tag) {
|
|
872
|
+
// If we find a retainer in the fallback chain with the same tag,
|
|
873
|
+
// we reuse it rather than creating a new retainer to preserve
|
|
874
|
+
// state. This behavior is useful for when a Suspense component
|
|
875
|
+
// re-renders and the children are re-rendered quickly.
|
|
876
|
+
const clone = cloneRetainer(candidate);
|
|
877
|
+
setFlag(clone, IsResurrecting);
|
|
878
|
+
predecessor.fallback = clone;
|
|
879
|
+
const fallback = ret;
|
|
880
|
+
ret = candidate;
|
|
881
|
+
ret.el = child;
|
|
882
|
+
ret.fallback = fallback;
|
|
883
|
+
setFlag(ret, DidDiff, false);
|
|
884
|
+
candidateFound = true;
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
if (!candidateFound) {
|
|
889
|
+
const fallback = ret;
|
|
890
|
+
ret = new Retainer(child);
|
|
891
|
+
ret.fallback = fallback;
|
|
486
892
|
}
|
|
487
|
-
const fallback = ret;
|
|
488
|
-
ret = new Retainer(child);
|
|
489
|
-
ret.fallbackValue = fallback;
|
|
490
893
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
value = hydrationBlock
|
|
494
|
-
? hydrationBlock.then(() => updateRaw(renderer, ret, scope, oldProps, hydrationData))
|
|
495
|
-
: updateRaw(renderer, ret, scope, oldProps, hydrationData);
|
|
894
|
+
else {
|
|
895
|
+
ret = new Retainer(child);
|
|
496
896
|
}
|
|
897
|
+
if (childCopied && getFlag(ret, DidCommit)) ;
|
|
898
|
+
else if (child.tag === Raw || child.tag === Text) ;
|
|
497
899
|
else if (child.tag === Fragment) {
|
|
498
|
-
|
|
499
|
-
? hydrationBlock.then(() => updateFragment(renderer, root, host, ctx, scope, ret, hydrationData))
|
|
500
|
-
: updateFragment(renderer, root, host, ctx, scope, ret, hydrationData);
|
|
900
|
+
diff = diffChildren(adapter, root, host, ctx, scope, ret, ret.el.props.children);
|
|
501
901
|
}
|
|
502
902
|
else if (typeof child.tag === "function") {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
903
|
+
diff = diffComponent(adapter, root, host, ctx, scope, ret);
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
diff = diffHost(adapter, root, ctx, scope, ret);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (typeof ret === "object") {
|
|
910
|
+
if (childCopied) {
|
|
911
|
+
setFlag(ret, IsCopied);
|
|
912
|
+
diff = getInflightDiff(ret);
|
|
506
913
|
}
|
|
507
914
|
else {
|
|
508
|
-
|
|
509
|
-
? hydrationBlock.then(() => updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData))
|
|
510
|
-
: updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData);
|
|
915
|
+
setFlag(ret, IsCopied, false);
|
|
511
916
|
}
|
|
512
917
|
}
|
|
513
|
-
if (isPromiseLike(
|
|
918
|
+
if (isPromiseLike(diff)) {
|
|
514
919
|
isAsync = true;
|
|
515
|
-
|
|
516
|
-
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
else if (typeof child === "string") {
|
|
923
|
+
if (typeof ret === "object" && ret.el.tag === Text) {
|
|
924
|
+
ret.el.props.value = child;
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
if (typeof ret === "object") {
|
|
928
|
+
(graveyard = graveyard || []).push(ret);
|
|
517
929
|
}
|
|
930
|
+
ret = new Retainer(createElement(Text, { value: child }));
|
|
518
931
|
}
|
|
519
932
|
}
|
|
520
933
|
else {
|
|
521
|
-
// child is a string or undefined
|
|
522
934
|
if (typeof ret === "object") {
|
|
523
935
|
(graveyard = graveyard || []).push(ret);
|
|
524
936
|
}
|
|
525
|
-
|
|
526
|
-
value = ret = renderer.text(child, scope, hydrationData);
|
|
527
|
-
}
|
|
528
|
-
else {
|
|
529
|
-
ret = undefined;
|
|
530
|
-
}
|
|
937
|
+
ret = undefined;
|
|
531
938
|
}
|
|
532
|
-
|
|
939
|
+
diffs[ni] = diff;
|
|
533
940
|
newRetained[ni] = ret;
|
|
534
941
|
}
|
|
535
942
|
// cleanup remaining retainers
|
|
536
943
|
for (; oi < oldLength; oi++) {
|
|
537
944
|
const ret = oldRetained[oi];
|
|
538
945
|
if (typeof ret === "object" &&
|
|
539
|
-
(typeof ret.el.key === "undefined" ||
|
|
946
|
+
(typeof ret.el.props.key === "undefined" ||
|
|
540
947
|
!seenKeys ||
|
|
541
|
-
!seenKeys.has(ret.el.key))) {
|
|
948
|
+
!seenKeys.has(ret.el.props.key))) {
|
|
542
949
|
(graveyard = graveyard || []).push(ret);
|
|
543
950
|
}
|
|
544
951
|
}
|
|
545
952
|
if (childrenByKey !== undefined && childrenByKey.size > 0) {
|
|
546
|
-
|
|
953
|
+
graveyard = graveyard || [];
|
|
954
|
+
for (const ret of childrenByKey.values()) {
|
|
955
|
+
graveyard.push(ret);
|
|
956
|
+
}
|
|
547
957
|
}
|
|
548
958
|
parent.children = unwrap(newRetained);
|
|
549
959
|
if (isAsync) {
|
|
550
|
-
|
|
960
|
+
const diffs1 = Promise.all(diffs)
|
|
961
|
+
.then(() => undefined)
|
|
962
|
+
.finally(() => {
|
|
963
|
+
setFlag(parent, DidDiff);
|
|
551
964
|
if (graveyard) {
|
|
552
|
-
|
|
553
|
-
|
|
965
|
+
if (parent.graveyard) {
|
|
966
|
+
for (let i = 0; i < graveyard.length; i++) {
|
|
967
|
+
parent.graveyard.push(graveyard[i]);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
parent.graveyard = graveyard;
|
|
554
972
|
}
|
|
555
973
|
}
|
|
556
974
|
});
|
|
557
|
-
let
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
new Promise((resolve) => (
|
|
561
|
-
]);
|
|
562
|
-
if (parent.
|
|
563
|
-
parent.
|
|
564
|
-
}
|
|
565
|
-
parent.
|
|
566
|
-
return
|
|
567
|
-
parent.inflightValue = parent.fallbackValue = undefined;
|
|
568
|
-
return normalize(childValues);
|
|
569
|
-
});
|
|
975
|
+
let onNextDiffs;
|
|
976
|
+
const diffs2 = (parent.pendingDiff = safeRace([
|
|
977
|
+
diffs1,
|
|
978
|
+
new Promise((resolve) => (onNextDiffs = resolve)),
|
|
979
|
+
]));
|
|
980
|
+
if (parent.onNextDiff) {
|
|
981
|
+
parent.onNextDiff(diffs2);
|
|
982
|
+
}
|
|
983
|
+
parent.onNextDiff = onNextDiffs;
|
|
984
|
+
return diffs2;
|
|
570
985
|
}
|
|
571
986
|
else {
|
|
987
|
+
setFlag(parent, DidDiff);
|
|
572
988
|
if (graveyard) {
|
|
573
|
-
|
|
574
|
-
|
|
989
|
+
if (parent.graveyard) {
|
|
990
|
+
for (let i = 0; i < graveyard.length; i++) {
|
|
991
|
+
parent.graveyard.push(graveyard[i]);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
else {
|
|
995
|
+
parent.graveyard = graveyard;
|
|
575
996
|
}
|
|
576
997
|
}
|
|
577
|
-
if (parent.
|
|
578
|
-
parent.
|
|
579
|
-
parent.
|
|
998
|
+
if (parent.onNextDiff) {
|
|
999
|
+
parent.onNextDiff(diffs);
|
|
1000
|
+
parent.onNextDiff = undefined;
|
|
580
1001
|
}
|
|
581
|
-
parent.
|
|
582
|
-
|
|
583
|
-
|
|
1002
|
+
parent.pendingDiff = undefined;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
function getInflightDiff(ret) {
|
|
1006
|
+
// It is not enough to check pendingDiff because pendingDiff is the diff for
|
|
1007
|
+
// children, but not the diff of an async component retainer's current run.
|
|
1008
|
+
// For the latter we check ctx.inflight.
|
|
1009
|
+
if (ret.ctx && ret.ctx.inflight) {
|
|
1010
|
+
return ret.ctx.inflight[1];
|
|
1011
|
+
}
|
|
1012
|
+
else if (ret.pendingDiff) {
|
|
1013
|
+
return ret.pendingDiff;
|
|
584
1014
|
}
|
|
585
1015
|
}
|
|
586
1016
|
function createChildrenByKey(children, offset) {
|
|
587
1017
|
const childrenByKey = new Map();
|
|
588
1018
|
for (let i = offset; i < children.length; i++) {
|
|
589
1019
|
const child = children[i];
|
|
590
|
-
if (typeof child === "object" &&
|
|
591
|
-
|
|
1020
|
+
if (typeof child === "object" &&
|
|
1021
|
+
typeof child.el.props.key !== "undefined") {
|
|
1022
|
+
childrenByKey.set(child.el.props.key, child);
|
|
592
1023
|
}
|
|
593
1024
|
}
|
|
594
1025
|
return childrenByKey;
|
|
595
1026
|
}
|
|
596
|
-
function
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
if (ctx && ctx.f & IsUpdating && ctx.inflightValue) {
|
|
602
|
-
return ctx.inflightValue;
|
|
1027
|
+
function diffHost(adapter, root, ctx, scope, ret) {
|
|
1028
|
+
const el = ret.el;
|
|
1029
|
+
const tag = el.tag;
|
|
1030
|
+
if (el.tag === Portal) {
|
|
1031
|
+
root = ret.value = el.props.root;
|
|
603
1032
|
}
|
|
604
|
-
|
|
605
|
-
|
|
1033
|
+
if (getFlag(ret, DidCommit)) {
|
|
1034
|
+
scope = ret.scope;
|
|
606
1035
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
ret.el.ref(ret.value);
|
|
615
|
-
}
|
|
1036
|
+
else {
|
|
1037
|
+
scope = ret.scope = adapter.scope({
|
|
1038
|
+
tag,
|
|
1039
|
+
tagName: getTagName(tag),
|
|
1040
|
+
props: el.props,
|
|
1041
|
+
scope,
|
|
1042
|
+
});
|
|
616
1043
|
}
|
|
617
|
-
return ret.
|
|
1044
|
+
return diffChildren(adapter, root, ret, ctx, scope, ret, ret.el.props.children);
|
|
618
1045
|
}
|
|
619
|
-
function
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
ret.inflightValue = childValues.then((childValues) => unwrap(childValues));
|
|
623
|
-
return ret.inflightValue;
|
|
1046
|
+
function commit(adapter, host, ret, ctx, scope, index, schedulePromises, hydrationNodes) {
|
|
1047
|
+
if (getFlag(ret, IsCopied) && getFlag(ret, DidCommit)) {
|
|
1048
|
+
return getValue(ret);
|
|
624
1049
|
}
|
|
625
|
-
return unwrap(childValues);
|
|
626
|
-
}
|
|
627
|
-
function updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData) {
|
|
628
1050
|
const el = ret.el;
|
|
629
1051
|
const tag = el.tag;
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1052
|
+
if (typeof tag === "function" ||
|
|
1053
|
+
tag === Fragment ||
|
|
1054
|
+
tag === Portal ||
|
|
1055
|
+
tag === Raw ||
|
|
1056
|
+
tag === Text) {
|
|
1057
|
+
if (typeof el.props.copy === "string") {
|
|
1058
|
+
console.error(`String copy prop ignored for <${getTagName(tag)}>. Use booleans instead.`);
|
|
1059
|
+
}
|
|
1060
|
+
if (typeof el.props.hydrate === "string") {
|
|
1061
|
+
console.error(`String hydrate prop ignored for <${getTagName(tag)}>. Use booleans instead.`);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
let value;
|
|
1065
|
+
let skippedHydrationNodes;
|
|
1066
|
+
if (hydrationNodes &&
|
|
1067
|
+
el.props.hydrate != null &&
|
|
1068
|
+
!el.props.hydrate &&
|
|
1069
|
+
typeof el.props.hydrate !== "string") {
|
|
1070
|
+
skippedHydrationNodes = hydrationNodes;
|
|
1071
|
+
hydrationNodes = undefined;
|
|
1072
|
+
}
|
|
1073
|
+
if (typeof tag === "function") {
|
|
1074
|
+
ret.ctx.index = index;
|
|
1075
|
+
value = commitComponent(ret.ctx, schedulePromises, hydrationNodes);
|
|
633
1076
|
}
|
|
634
1077
|
else {
|
|
635
|
-
if (
|
|
636
|
-
|
|
637
|
-
hydrationValue = value;
|
|
1078
|
+
if (tag === Fragment) {
|
|
1079
|
+
value = commitChildren(adapter, host, ctx, scope, ret, index, schedulePromises, hydrationNodes);
|
|
638
1080
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
1081
|
+
else if (tag === Text) {
|
|
1082
|
+
value = commitText(adapter, ret, el, scope, hydrationNodes);
|
|
1083
|
+
}
|
|
1084
|
+
else if (tag === Raw) {
|
|
1085
|
+
value = commitRaw(adapter, host, ret, scope, hydrationNodes);
|
|
1086
|
+
}
|
|
1087
|
+
else {
|
|
1088
|
+
value = commitHost(adapter, ret, ctx, schedulePromises, hydrationNodes);
|
|
1089
|
+
}
|
|
1090
|
+
if (ret.fallback) {
|
|
1091
|
+
unmount(adapter, host, ctx, ret.fallback, false);
|
|
1092
|
+
ret.fallback = undefined;
|
|
646
1093
|
}
|
|
647
1094
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
ret.inflightValue = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue));
|
|
651
|
-
return ret.inflightValue;
|
|
1095
|
+
if (skippedHydrationNodes) {
|
|
1096
|
+
skippedHydrationNodes.splice(0, wrap(value).length);
|
|
652
1097
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (typeof ret.el.ref === "function") {
|
|
661
|
-
ret.el.ref(value);
|
|
1098
|
+
if (!getFlag(ret, DidCommit)) {
|
|
1099
|
+
setFlag(ret, DidCommit);
|
|
1100
|
+
if (typeof tag !== "function" &&
|
|
1101
|
+
tag !== Fragment &&
|
|
1102
|
+
tag !== Portal &&
|
|
1103
|
+
typeof el.props.ref === "function") {
|
|
1104
|
+
el.props.ref(adapter.read(value));
|
|
662
1105
|
}
|
|
663
1106
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
1107
|
+
return value;
|
|
1108
|
+
}
|
|
1109
|
+
function commitChildren(adapter, host, ctx, scope, parent, index, schedulePromises, hydrationNodes) {
|
|
1110
|
+
let values = [];
|
|
1111
|
+
for (let i = 0, children = wrap(parent.children); i < children.length; i++) {
|
|
1112
|
+
let child = children[i];
|
|
1113
|
+
let schedulePromises1;
|
|
1114
|
+
let isSchedulingFallback = false;
|
|
1115
|
+
while (child &&
|
|
1116
|
+
((!getFlag(child, DidDiff) && child.fallback) ||
|
|
1117
|
+
getFlag(child, IsScheduling))) {
|
|
1118
|
+
// If the child is scheduling, it is a component retainer so ctx will be
|
|
1119
|
+
// defined.
|
|
1120
|
+
if (getFlag(child, IsScheduling) && child.ctx.schedule) {
|
|
1121
|
+
(schedulePromises1 = schedulePromises1 || []).push(child.ctx.schedule.promise);
|
|
1122
|
+
isSchedulingFallback = true;
|
|
1123
|
+
}
|
|
1124
|
+
if (!getFlag(child, DidDiff) && getFlag(child, DidCommit)) {
|
|
1125
|
+
// If this child has not diffed but has committed, it means it is a
|
|
1126
|
+
// fallback that is being resurrected.
|
|
1127
|
+
for (const node of getChildValues(child)) {
|
|
1128
|
+
adapter.remove({
|
|
1129
|
+
node,
|
|
1130
|
+
parentNode: host.value,
|
|
1131
|
+
isNested: false,
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
child = child.fallback;
|
|
1136
|
+
// When a scheduling component is mounting asynchronously but diffs
|
|
1137
|
+
// immediately, it will cause previous async diffs to settle due to the
|
|
1138
|
+
// chasing mechanism. This would cause earlier renders to resolve sooner
|
|
1139
|
+
// than expected, because the render would be missing both its usual
|
|
1140
|
+
// children and the children of the scheduling render. Therefore, we need
|
|
1141
|
+
// to defer the settling of previous renders until either that render
|
|
1142
|
+
// settles, or the scheduling component finally finishes scheduling.
|
|
1143
|
+
//
|
|
1144
|
+
// To do this, we take advantage of the fact that commits for aborted
|
|
1145
|
+
// renders will still fire and walk the tree. During that commit walk,
|
|
1146
|
+
// when we encounter a scheduling element, we push a race of the
|
|
1147
|
+
// scheduling promise with the inflight diff of the async fallback
|
|
1148
|
+
// fallback to schedulePromises to delay the initiator.
|
|
1149
|
+
//
|
|
1150
|
+
// However, we need to make sure we only use the inflight diffs for the
|
|
1151
|
+
// fallback which we are trying to delay, in the case of multiple renders
|
|
1152
|
+
// and fallbacks. To do this, we take advantage of the fact that when
|
|
1153
|
+
// multiple renders race (e.g., render1->render2->render3->scheduling
|
|
1154
|
+
// component), the chasing mechanism will call stale commits in reverse
|
|
1155
|
+
// order.
|
|
1156
|
+
//
|
|
1157
|
+
// We can use this ordering to delay to find which fallbacks we need to
|
|
1158
|
+
// add to the race. Each commit call progressively marks an additional
|
|
1159
|
+
// fallback as a scheduling fallback, and does not contribute to the
|
|
1160
|
+
// scheduling promises if it is further than the last seen level.
|
|
1161
|
+
//
|
|
1162
|
+
// This prevents promise contamination where newer renders settle early
|
|
1163
|
+
// due to diffs from older renders.
|
|
1164
|
+
if (schedulePromises1 && isSchedulingFallback && child) {
|
|
1165
|
+
if (!getFlag(child, DidDiff)) {
|
|
1166
|
+
const inflightDiff = getInflightDiff(child);
|
|
1167
|
+
schedulePromises1.push(inflightDiff);
|
|
1168
|
+
}
|
|
1169
|
+
else {
|
|
1170
|
+
// If a scheduling component's fallback has already diffed, we do not
|
|
1171
|
+
// need delay the render.
|
|
1172
|
+
schedulePromises1 = undefined;
|
|
1173
|
+
}
|
|
1174
|
+
if (getFlag(child, IsSchedulingFallback)) {
|
|
1175
|
+
// This fallback was marked by a more recent commit - keep processing
|
|
1176
|
+
// deeper levels
|
|
1177
|
+
isSchedulingFallback = true;
|
|
1178
|
+
}
|
|
1179
|
+
else {
|
|
1180
|
+
// First unmarked fallback we've encountered - mark it and stop
|
|
1181
|
+
// contributing to schedulePromises1 for deeper levels.
|
|
1182
|
+
setFlag(child, IsSchedulingFallback, true);
|
|
1183
|
+
isSchedulingFallback = false;
|
|
1184
|
+
}
|
|
672
1185
|
}
|
|
673
1186
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1187
|
+
if (schedulePromises1 && schedulePromises1.length > 1) {
|
|
1188
|
+
schedulePromises.push(safeRace(schedulePromises1));
|
|
1189
|
+
}
|
|
1190
|
+
if (child) {
|
|
1191
|
+
const value = commit(adapter, host, child, ctx, scope, index, schedulePromises, hydrationNodes);
|
|
1192
|
+
if (Array.isArray(value)) {
|
|
1193
|
+
for (let j = 0; j < value.length; j++) {
|
|
1194
|
+
values.push(value[j]);
|
|
1195
|
+
}
|
|
1196
|
+
index += value.length;
|
|
680
1197
|
}
|
|
681
|
-
else if (
|
|
682
|
-
|
|
1198
|
+
else if (value) {
|
|
1199
|
+
values.push(value);
|
|
1200
|
+
index++;
|
|
683
1201
|
}
|
|
684
1202
|
}
|
|
685
1203
|
}
|
|
686
|
-
if (
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
1204
|
+
if (parent.graveyard) {
|
|
1205
|
+
for (let i = 0; i < parent.graveyard.length; i++) {
|
|
1206
|
+
const child = parent.graveyard[i];
|
|
1207
|
+
unmount(adapter, host, ctx, child, false);
|
|
690
1208
|
}
|
|
691
|
-
|
|
1209
|
+
parent.graveyard = undefined;
|
|
692
1210
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
return;
|
|
1211
|
+
if (parent.lingerers) {
|
|
1212
|
+
// if parent.lingerers is set, a descendant component is unmounting
|
|
1213
|
+
// asynchronously, so we overwrite values to include lingerering DOM nodes.
|
|
1214
|
+
values = getChildValues(parent);
|
|
698
1215
|
}
|
|
1216
|
+
return values;
|
|
1217
|
+
}
|
|
1218
|
+
function commitText(adapter, ret, el, scope, hydrationNodes) {
|
|
1219
|
+
const value = adapter.text({
|
|
1220
|
+
value: el.props.value,
|
|
1221
|
+
scope,
|
|
1222
|
+
oldNode: ret.value,
|
|
1223
|
+
hydrationNodes,
|
|
1224
|
+
});
|
|
1225
|
+
ret.value = value;
|
|
699
1226
|
return value;
|
|
700
1227
|
}
|
|
701
|
-
function
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1228
|
+
function commitRaw(adapter, host, ret, scope, hydrationNodes) {
|
|
1229
|
+
if (!ret.oldProps || ret.oldProps.value !== ret.el.props.value) {
|
|
1230
|
+
const oldNodes = wrap(ret.value);
|
|
1231
|
+
for (let i = 0; i < oldNodes.length; i++) {
|
|
1232
|
+
const oldNode = oldNodes[i];
|
|
1233
|
+
adapter.remove({
|
|
1234
|
+
node: oldNode,
|
|
1235
|
+
parentNode: host.value,
|
|
1236
|
+
isNested: false,
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
ret.value = adapter.raw({
|
|
1240
|
+
value: ret.el.props.value,
|
|
1241
|
+
scope,
|
|
1242
|
+
hydrationNodes,
|
|
1243
|
+
});
|
|
705
1244
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
1245
|
+
ret.oldProps = stripSpecialProps(ret.el.props);
|
|
1246
|
+
return ret.value;
|
|
1247
|
+
}
|
|
1248
|
+
function commitHost(adapter, ret, ctx, schedulePromises, hydrationNodes) {
|
|
1249
|
+
if (getFlag(ret, IsCopied) && getFlag(ret, DidCommit)) {
|
|
1250
|
+
return getValue(ret);
|
|
1251
|
+
}
|
|
1252
|
+
const tag = ret.el.tag;
|
|
1253
|
+
const props = stripSpecialProps(ret.el.props);
|
|
1254
|
+
const oldProps = ret.oldProps;
|
|
1255
|
+
let node = ret.value;
|
|
1256
|
+
let copyProps;
|
|
1257
|
+
let copyChildren = false;
|
|
1258
|
+
if (oldProps) {
|
|
1259
|
+
for (const propName in props) {
|
|
1260
|
+
if (props[propName] === Copy) {
|
|
1261
|
+
// The Copy tag can be used to skip the patching of a prop.
|
|
1262
|
+
// <div class={shouldPatchClass ? "class-name" : Copy} />
|
|
1263
|
+
props[propName] = oldProps[propName];
|
|
1264
|
+
(copyProps = copyProps || new Set()).add(propName);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
if (typeof ret.el.props.copy === "string") {
|
|
1268
|
+
const copyMetaProp = new MetaProp("copy", ret.el.props.copy);
|
|
1269
|
+
if (copyMetaProp.include) {
|
|
1270
|
+
for (const propName of copyMetaProp.props) {
|
|
1271
|
+
if (propName in oldProps) {
|
|
1272
|
+
props[propName] = oldProps[propName];
|
|
1273
|
+
(copyProps = copyProps || new Set()).add(propName);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
else {
|
|
1278
|
+
for (const propName in oldProps) {
|
|
1279
|
+
if (!copyMetaProp.props.has(propName)) {
|
|
1280
|
+
props[propName] = oldProps[propName];
|
|
1281
|
+
(copyProps = copyProps || new Set()).add(propName);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
copyChildren = copyMetaProp.includes("children");
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
const scope = ret.scope;
|
|
1289
|
+
let childHydrationNodes;
|
|
1290
|
+
let quietProps;
|
|
1291
|
+
let hydrationMetaProp;
|
|
1292
|
+
if (!getFlag(ret, DidCommit)) {
|
|
1293
|
+
if (tag === Portal) {
|
|
1294
|
+
if (ret.el.props.hydrate && typeof ret.el.props.hydrate !== "string") {
|
|
1295
|
+
childHydrationNodes = adapter.adopt({
|
|
1296
|
+
tag,
|
|
1297
|
+
tagName: getTagName(tag),
|
|
1298
|
+
node,
|
|
1299
|
+
props,
|
|
1300
|
+
scope,
|
|
1301
|
+
});
|
|
1302
|
+
if (childHydrationNodes) {
|
|
1303
|
+
for (let i = 0; i < childHydrationNodes.length; i++) {
|
|
1304
|
+
adapter.remove({
|
|
1305
|
+
node: childHydrationNodes[i],
|
|
1306
|
+
parentNode: node,
|
|
1307
|
+
isNested: false,
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
else {
|
|
1314
|
+
if (!node && hydrationNodes) {
|
|
1315
|
+
const nextChild = hydrationNodes.shift();
|
|
1316
|
+
if (typeof ret.el.props.hydrate === "string") {
|
|
1317
|
+
hydrationMetaProp = new MetaProp("hydration", ret.el.props.hydrate);
|
|
1318
|
+
if (hydrationMetaProp.include) {
|
|
1319
|
+
// if we're in inclusive mode, we add all props to quietProps and
|
|
1320
|
+
// remove props specified in the metaprop
|
|
1321
|
+
quietProps = new Set(Object.keys(props));
|
|
1322
|
+
for (const propName of hydrationMetaProp.props) {
|
|
1323
|
+
quietProps.delete(propName);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
else {
|
|
1327
|
+
quietProps = hydrationMetaProp.props;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
childHydrationNodes = adapter.adopt({
|
|
1331
|
+
tag,
|
|
1332
|
+
tagName: getTagName(tag),
|
|
1333
|
+
node: nextChild,
|
|
1334
|
+
props,
|
|
1335
|
+
scope,
|
|
1336
|
+
});
|
|
1337
|
+
if (childHydrationNodes) {
|
|
1338
|
+
node = nextChild;
|
|
1339
|
+
for (let i = 0; i < childHydrationNodes.length; i++) {
|
|
1340
|
+
adapter.remove({
|
|
1341
|
+
node: childHydrationNodes[i],
|
|
1342
|
+
parentNode: node,
|
|
1343
|
+
isNested: false,
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
714
1346
|
}
|
|
715
1347
|
}
|
|
716
|
-
|
|
717
|
-
|
|
1348
|
+
// TODO: For some reason, there are cases where the node is already set
|
|
1349
|
+
// and the DidCommit flag is false. Not checking for node fails a test
|
|
1350
|
+
// where a child dispatches an event in a schedule callback, the parent
|
|
1351
|
+
// listens for this event and refreshes.
|
|
1352
|
+
if (!node) {
|
|
1353
|
+
node = adapter.create({
|
|
1354
|
+
tag,
|
|
1355
|
+
tagName: getTagName(tag),
|
|
1356
|
+
props,
|
|
1357
|
+
scope,
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
ret.value = node;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
if (tag !== Portal) {
|
|
1364
|
+
adapter.patch({
|
|
1365
|
+
tag,
|
|
1366
|
+
tagName: getTagName(tag),
|
|
1367
|
+
node,
|
|
1368
|
+
props,
|
|
1369
|
+
oldProps,
|
|
1370
|
+
scope,
|
|
1371
|
+
copyProps,
|
|
1372
|
+
isHydrating: !!childHydrationNodes,
|
|
1373
|
+
quietProps,
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
if (!copyChildren) {
|
|
1377
|
+
const children = commitChildren(adapter, ret, ctx, scope, ret, 0, schedulePromises, hydrationMetaProp && !hydrationMetaProp.includes("children")
|
|
1378
|
+
? undefined
|
|
1379
|
+
: childHydrationNodes);
|
|
1380
|
+
adapter.arrange({
|
|
1381
|
+
tag,
|
|
1382
|
+
tagName: getTagName(tag),
|
|
1383
|
+
node: node,
|
|
1384
|
+
props,
|
|
1385
|
+
children,
|
|
1386
|
+
oldProps,
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
ret.oldProps = props;
|
|
1390
|
+
if (tag === Portal) {
|
|
1391
|
+
flush(adapter, ret.value);
|
|
1392
|
+
// The root passed to Portal elements are opaque to parents so we return
|
|
1393
|
+
// undefined here.
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
return node;
|
|
1397
|
+
}
|
|
1398
|
+
class MetaProp {
|
|
1399
|
+
constructor(propName, propValue) {
|
|
1400
|
+
this.include = true;
|
|
1401
|
+
this.props = new Set();
|
|
1402
|
+
let noBangs = true;
|
|
1403
|
+
let allBangs = true;
|
|
1404
|
+
const tokens = propValue.split(/[,\s]+/);
|
|
1405
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1406
|
+
const token = tokens[i].trim();
|
|
1407
|
+
if (!token) {
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
else if (token.startsWith("!")) {
|
|
1411
|
+
noBangs = false;
|
|
1412
|
+
this.props.add(token.slice(1));
|
|
718
1413
|
}
|
|
719
1414
|
else {
|
|
720
|
-
|
|
1415
|
+
allBangs = false;
|
|
1416
|
+
this.props.add(token);
|
|
721
1417
|
}
|
|
722
1418
|
}
|
|
1419
|
+
if (!allBangs && !noBangs) {
|
|
1420
|
+
console.error(`Invalid ${propName} prop "${propValue}".\nUse prop or !prop but not both.`);
|
|
1421
|
+
this.include = true;
|
|
1422
|
+
this.props.clear();
|
|
1423
|
+
}
|
|
723
1424
|
else {
|
|
724
|
-
|
|
1425
|
+
this.include = noBangs;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
includes(propName) {
|
|
1429
|
+
if (this.include) {
|
|
1430
|
+
return this.props.has(propName);
|
|
725
1431
|
}
|
|
726
|
-
|
|
727
|
-
|
|
1432
|
+
else {
|
|
1433
|
+
return !this.props.has(propName);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
function contextContains(parent, child) {
|
|
1438
|
+
for (let current = child; current !== undefined; current = current.parent) {
|
|
1439
|
+
if (current === parent) {
|
|
1440
|
+
return true;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
return false;
|
|
1444
|
+
}
|
|
1445
|
+
// When rendering is done without a root, we use this special anonymous root to
|
|
1446
|
+
// make sure after callbacks are still called.
|
|
1447
|
+
const ANONYMOUS_ROOT = {};
|
|
1448
|
+
function flush(adapter, root, initiator) {
|
|
1449
|
+
if (root != null) {
|
|
1450
|
+
adapter.finalize(root);
|
|
1451
|
+
}
|
|
1452
|
+
if (typeof root !== "object" || root === null) {
|
|
1453
|
+
root = ANONYMOUS_ROOT;
|
|
1454
|
+
}
|
|
1455
|
+
// The initiator is the context which initiated the rendering process. If
|
|
1456
|
+
// initiator is defined we call and clear all flush callbacks which are
|
|
1457
|
+
// registered with the initiator or with a child context of the initiator,
|
|
1458
|
+
// because they are fully rendered.
|
|
1459
|
+
//
|
|
1460
|
+
// If no initiator is provided, we can call and clear all flush callbacks
|
|
1461
|
+
// which are not scheduling.
|
|
1462
|
+
const afterMap = afterMapByRoot.get(root);
|
|
1463
|
+
if (afterMap) {
|
|
1464
|
+
const afterMap1 = new Map();
|
|
1465
|
+
for (const [ctx, callbacks] of afterMap) {
|
|
1466
|
+
if (getFlag(ctx.ret, IsScheduling) ||
|
|
1467
|
+
(initiator && !contextContains(initiator, ctx))) {
|
|
1468
|
+
// copy over callbacks to the new map (defer them)
|
|
1469
|
+
afterMap.delete(ctx);
|
|
1470
|
+
afterMap1.set(ctx, callbacks);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
if (afterMap1.size) {
|
|
1474
|
+
afterMapByRoot.set(root, afterMap1);
|
|
1475
|
+
}
|
|
1476
|
+
else {
|
|
1477
|
+
afterMapByRoot.delete(root);
|
|
1478
|
+
}
|
|
1479
|
+
for (const [ctx, callbacks] of afterMap) {
|
|
1480
|
+
const value = adapter.read(getValue(ctx.ret));
|
|
728
1481
|
for (const callback of callbacks) {
|
|
729
1482
|
callback(value);
|
|
730
1483
|
}
|
|
731
1484
|
}
|
|
732
1485
|
}
|
|
733
1486
|
}
|
|
734
|
-
function unmount(
|
|
1487
|
+
function unmount(adapter, host, ctx, ret, isNested) {
|
|
1488
|
+
// TODO: set the IsUnmounted flag consistently for all retainers
|
|
1489
|
+
if (ret.fallback) {
|
|
1490
|
+
unmount(adapter, host, ctx, ret.fallback, isNested);
|
|
1491
|
+
ret.fallback = undefined;
|
|
1492
|
+
}
|
|
1493
|
+
if (getFlag(ret, IsResurrecting)) {
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
if (ret.lingerers) {
|
|
1497
|
+
for (let i = 0; i < ret.lingerers.length; i++) {
|
|
1498
|
+
const lingerers = ret.lingerers[i];
|
|
1499
|
+
if (lingerers) {
|
|
1500
|
+
for (const lingerer of lingerers) {
|
|
1501
|
+
unmount(adapter, host, ctx, lingerer, isNested);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
ret.lingerers = undefined;
|
|
1506
|
+
}
|
|
735
1507
|
if (typeof ret.el.tag === "function") {
|
|
736
|
-
|
|
737
|
-
|
|
1508
|
+
unmountComponent(ret.ctx, isNested);
|
|
1509
|
+
}
|
|
1510
|
+
else if (ret.el.tag === Fragment) {
|
|
1511
|
+
unmountChildren(adapter, host, ctx, ret, isNested);
|
|
738
1512
|
}
|
|
739
1513
|
else if (ret.el.tag === Portal) {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1514
|
+
unmountChildren(adapter, ret, ctx, ret, false);
|
|
1515
|
+
if (ret.value != null) {
|
|
1516
|
+
adapter.finalize(ret.value);
|
|
1517
|
+
}
|
|
743
1518
|
}
|
|
744
|
-
else
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
}
|
|
1519
|
+
else {
|
|
1520
|
+
unmountChildren(adapter, ret, ctx, ret, true);
|
|
1521
|
+
if (getFlag(ret, DidCommit)) {
|
|
1522
|
+
if (ctx) {
|
|
1523
|
+
// Remove the value from every context which shares the same host.
|
|
1524
|
+
removeEventTargetDelegates(ctx.ctx, [ret.value], (ctx1) => ctx1[_ContextState].host === host);
|
|
1525
|
+
}
|
|
1526
|
+
adapter.remove({
|
|
1527
|
+
node: ret.value,
|
|
1528
|
+
parentNode: host.value,
|
|
1529
|
+
isNested,
|
|
1530
|
+
});
|
|
751
1531
|
}
|
|
752
|
-
renderer.dispose(ret.el.tag, ret.value, ret.el.props);
|
|
753
|
-
host = ret;
|
|
754
1532
|
}
|
|
755
|
-
|
|
756
|
-
|
|
1533
|
+
}
|
|
1534
|
+
function unmountChildren(adapter, host, ctx, ret, isNested) {
|
|
1535
|
+
if (ret.graveyard) {
|
|
1536
|
+
for (let i = 0; i < ret.graveyard.length; i++) {
|
|
1537
|
+
const child = ret.graveyard[i];
|
|
1538
|
+
unmount(adapter, host, ctx, child, isNested);
|
|
1539
|
+
}
|
|
1540
|
+
ret.graveyard = undefined;
|
|
1541
|
+
}
|
|
1542
|
+
for (let i = 0, children = wrap(ret.children); i < children.length; i++) {
|
|
757
1543
|
const child = children[i];
|
|
758
1544
|
if (typeof child === "object") {
|
|
759
|
-
unmount(
|
|
1545
|
+
unmount(adapter, host, ctx, child, isNested);
|
|
760
1546
|
}
|
|
761
1547
|
}
|
|
762
1548
|
}
|
|
763
|
-
/*** CONTEXT FLAGS ***/
|
|
764
|
-
/**
|
|
765
|
-
* A flag which is true when the component is initialized or updated by an
|
|
766
|
-
* ancestor component or the root render call.
|
|
767
|
-
*
|
|
768
|
-
* Used to determine things like whether the nearest host ancestor needs to be
|
|
769
|
-
* rearranged.
|
|
770
|
-
*/
|
|
771
|
-
const IsUpdating = 1 << 0;
|
|
772
|
-
/**
|
|
773
|
-
* A flag which is true when the component is synchronously executing.
|
|
774
|
-
*
|
|
775
|
-
* Used to guard against components triggering stack overflow or generator error.
|
|
776
|
-
*/
|
|
777
|
-
const IsSyncExecuting = 1 << 1;
|
|
778
|
-
/**
|
|
779
|
-
* A flag which is true when the component is in a for...of loop.
|
|
780
|
-
*/
|
|
781
|
-
const IsInForOfLoop = 1 << 2;
|
|
782
|
-
/**
|
|
783
|
-
* A flag which is true when the component is in a for await...of loop.
|
|
784
|
-
*/
|
|
785
|
-
const IsInForAwaitOfLoop = 1 << 3;
|
|
786
|
-
/**
|
|
787
|
-
* A flag which is true when the component starts the render loop but has not
|
|
788
|
-
* yielded yet.
|
|
789
|
-
*
|
|
790
|
-
* Used to make sure that components yield at least once per loop.
|
|
791
|
-
*/
|
|
792
|
-
const NeedsToYield = 1 << 4;
|
|
793
|
-
/**
|
|
794
|
-
* A flag used by async generator components in conjunction with the
|
|
795
|
-
* onAvailable callback to mark whether new props can be pulled via the context
|
|
796
|
-
* async iterator. See the Symbol.asyncIterator method and the
|
|
797
|
-
* resumeCtxIterator function.
|
|
798
|
-
*/
|
|
799
|
-
const PropsAvailable = 1 << 5;
|
|
800
|
-
/**
|
|
801
|
-
* A flag which is set when a component errors.
|
|
802
|
-
*
|
|
803
|
-
* This is mainly used to prevent some false positives in "component yields or
|
|
804
|
-
* returns undefined" warnings. The reason we’re using this versus IsUnmounted
|
|
805
|
-
* is a very troubling test (cascades sync generator parent and sync generator
|
|
806
|
-
* child) where synchronous code causes a stack overflow error in a
|
|
807
|
-
* non-deterministic way. Deeply disturbing stuff.
|
|
808
|
-
*/
|
|
809
|
-
const IsErrored = 1 << 6;
|
|
810
|
-
/**
|
|
811
|
-
* A flag which is set when the component is unmounted. Unmounted components
|
|
812
|
-
* are no longer in the element tree and cannot refresh or rerender.
|
|
813
|
-
*/
|
|
814
|
-
const IsUnmounted = 1 << 7;
|
|
815
|
-
/**
|
|
816
|
-
* A flag which indicates that the component is a sync generator component.
|
|
817
|
-
*/
|
|
818
|
-
const IsSyncGen = 1 << 8;
|
|
819
|
-
/**
|
|
820
|
-
* A flag which indicates that the component is an async generator component.
|
|
821
|
-
*/
|
|
822
|
-
const IsAsyncGen = 1 << 9;
|
|
823
|
-
/**
|
|
824
|
-
* A flag which is set while schedule callbacks are called.
|
|
825
|
-
*/
|
|
826
|
-
const IsScheduling = 1 << 10;
|
|
827
|
-
/**
|
|
828
|
-
* A flag which is set when a schedule callback calls refresh.
|
|
829
|
-
*/
|
|
830
|
-
const IsSchedulingRefresh = 1 << 11;
|
|
831
1549
|
const provisionMaps = new WeakMap();
|
|
832
1550
|
const scheduleMap = new WeakMap();
|
|
833
1551
|
const cleanupMap = new WeakMap();
|
|
834
1552
|
// keys are roots
|
|
835
|
-
const
|
|
1553
|
+
const afterMapByRoot = new WeakMap();
|
|
1554
|
+
// TODO: allow ContextState to be initialized for testing purposes
|
|
836
1555
|
/**
|
|
837
1556
|
* @internal
|
|
838
1557
|
* The internal class which holds context data.
|
|
839
1558
|
*/
|
|
840
|
-
class
|
|
841
|
-
constructor(
|
|
842
|
-
this.
|
|
843
|
-
this.owner = new Context(this);
|
|
844
|
-
this.renderer = renderer;
|
|
1559
|
+
class ContextState {
|
|
1560
|
+
constructor(adapter, root, host, parent, scope, ret) {
|
|
1561
|
+
this.adapter = adapter;
|
|
845
1562
|
this.root = root;
|
|
846
1563
|
this.host = host;
|
|
847
1564
|
this.parent = parent;
|
|
1565
|
+
// This property must be set after this.parent is set because the Context
|
|
1566
|
+
// constructor reads this.parent.
|
|
1567
|
+
this.ctx = new Context(this);
|
|
848
1568
|
this.scope = scope;
|
|
849
1569
|
this.ret = ret;
|
|
850
1570
|
this.iterator = undefined;
|
|
851
|
-
this.
|
|
852
|
-
this.
|
|
853
|
-
this.
|
|
854
|
-
this.enqueuedValue = undefined;
|
|
855
|
-
this.onProps = undefined;
|
|
1571
|
+
this.inflight = undefined;
|
|
1572
|
+
this.enqueued = undefined;
|
|
1573
|
+
this.onPropsProvided = undefined;
|
|
856
1574
|
this.onPropsRequested = undefined;
|
|
1575
|
+
this.pull = undefined;
|
|
1576
|
+
this.index = 0;
|
|
1577
|
+
this.schedule = undefined;
|
|
857
1578
|
}
|
|
858
1579
|
}
|
|
859
|
-
const
|
|
1580
|
+
const _ContextState = Symbol.for("crank.ContextState");
|
|
860
1581
|
/**
|
|
861
1582
|
* A class which is instantiated and passed to every component as its this
|
|
862
|
-
* value. Contexts form a tree just like elements and all
|
|
863
|
-
* element tree are connected via contexts. Components can
|
|
864
|
-
* communicate data upwards via events and downwards via
|
|
1583
|
+
* value/second parameter. Contexts form a tree just like elements and all
|
|
1584
|
+
* components in the element tree are connected via contexts. Components can
|
|
1585
|
+
* use this tree to communicate data upwards via events and downwards via
|
|
1586
|
+
* provisions.
|
|
865
1587
|
*
|
|
866
1588
|
* @template [T=*] - The expected shape of the props passed to the component,
|
|
867
1589
|
* or a component function. Used to strongly type the Context iterator methods.
|
|
@@ -869,17 +1591,18 @@
|
|
|
869
1591
|
* places such as the return value of refresh and the argument passed to
|
|
870
1592
|
* schedule and cleanup callbacks.
|
|
871
1593
|
*/
|
|
872
|
-
class Context {
|
|
1594
|
+
class Context extends CustomEventTarget {
|
|
873
1595
|
// TODO: If we could make the constructor function take a nicer value, it
|
|
874
1596
|
// would be useful for testing purposes.
|
|
875
|
-
constructor(
|
|
876
|
-
|
|
1597
|
+
constructor(state) {
|
|
1598
|
+
super(state.parent ? state.parent.ctx : null);
|
|
1599
|
+
this[_ContextState] = state;
|
|
877
1600
|
}
|
|
878
1601
|
/**
|
|
879
1602
|
* The current props of the associated element.
|
|
880
1603
|
*/
|
|
881
1604
|
get props() {
|
|
882
|
-
return this[
|
|
1605
|
+
return this[_ContextState].ret.el.props;
|
|
883
1606
|
}
|
|
884
1607
|
/**
|
|
885
1608
|
* The current value of the associated element.
|
|
@@ -887,47 +1610,51 @@
|
|
|
887
1610
|
* @deprecated
|
|
888
1611
|
*/
|
|
889
1612
|
get value() {
|
|
890
|
-
|
|
1613
|
+
console.warn("Context.value is deprecated.");
|
|
1614
|
+
return this[_ContextState].adapter.read(getValue(this[_ContextState].ret));
|
|
1615
|
+
}
|
|
1616
|
+
get isExecuting() {
|
|
1617
|
+
return getFlag(this[_ContextState].ret, IsExecuting);
|
|
1618
|
+
}
|
|
1619
|
+
get isUnmounted() {
|
|
1620
|
+
return getFlag(this[_ContextState].ret, IsUnmounted);
|
|
891
1621
|
}
|
|
892
1622
|
*[Symbol.iterator]() {
|
|
893
|
-
const ctx = this[
|
|
1623
|
+
const ctx = this[_ContextState];
|
|
1624
|
+
setFlag(ctx.ret, IsInForOfLoop);
|
|
894
1625
|
try {
|
|
895
|
-
ctx.
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
throw new Error("Context iterated twice without a yield");
|
|
1626
|
+
while (!getFlag(ctx.ret, IsUnmounted) && !getFlag(ctx.ret, IsErrored)) {
|
|
1627
|
+
if (getFlag(ctx.ret, NeedsToYield)) {
|
|
1628
|
+
throw new Error(`<${getTagName(ctx.ret.el.tag)}> context iterated twice without a yield`);
|
|
899
1629
|
}
|
|
900
1630
|
else {
|
|
901
|
-
ctx.
|
|
1631
|
+
setFlag(ctx.ret, NeedsToYield);
|
|
902
1632
|
}
|
|
903
1633
|
yield ctx.ret.el.props;
|
|
904
1634
|
}
|
|
905
1635
|
}
|
|
906
1636
|
finally {
|
|
907
|
-
ctx.
|
|
1637
|
+
setFlag(ctx.ret, IsInForOfLoop, false);
|
|
908
1638
|
}
|
|
909
1639
|
}
|
|
910
1640
|
async *[Symbol.asyncIterator]() {
|
|
911
|
-
const ctx = this[
|
|
912
|
-
|
|
913
|
-
throw new Error("Use for...of in sync generator components");
|
|
914
|
-
}
|
|
1641
|
+
const ctx = this[_ContextState];
|
|
1642
|
+
setFlag(ctx.ret, IsInForAwaitOfLoop);
|
|
915
1643
|
try {
|
|
916
|
-
ctx.
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
throw new Error("Context iterated twice without a yield");
|
|
1644
|
+
while (!getFlag(ctx.ret, IsUnmounted) && !getFlag(ctx.ret, IsErrored)) {
|
|
1645
|
+
if (getFlag(ctx.ret, NeedsToYield)) {
|
|
1646
|
+
throw new Error(`<${getTagName(ctx.ret.el.tag)}> context iterated twice without a yield`);
|
|
920
1647
|
}
|
|
921
1648
|
else {
|
|
922
|
-
ctx.
|
|
1649
|
+
setFlag(ctx.ret, NeedsToYield);
|
|
923
1650
|
}
|
|
924
|
-
if (ctx.
|
|
925
|
-
ctx.
|
|
1651
|
+
if (getFlag(ctx.ret, PropsAvailable)) {
|
|
1652
|
+
setFlag(ctx.ret, PropsAvailable, false);
|
|
926
1653
|
yield ctx.ret.el.props;
|
|
927
1654
|
}
|
|
928
1655
|
else {
|
|
929
|
-
const props = await new Promise((resolve) => (ctx.
|
|
930
|
-
if (ctx.
|
|
1656
|
+
const props = await new Promise((resolve) => (ctx.onPropsProvided = resolve));
|
|
1657
|
+
if (getFlag(ctx.ret, IsUnmounted) || getFlag(ctx.ret, IsErrored)) {
|
|
931
1658
|
break;
|
|
932
1659
|
}
|
|
933
1660
|
yield props;
|
|
@@ -939,7 +1666,7 @@
|
|
|
939
1666
|
}
|
|
940
1667
|
}
|
|
941
1668
|
finally {
|
|
942
|
-
ctx.
|
|
1669
|
+
setFlag(ctx.ret, IsInForAwaitOfLoop, false);
|
|
943
1670
|
if (ctx.onPropsRequested) {
|
|
944
1671
|
ctx.onPropsRequested();
|
|
945
1672
|
ctx.onPropsRequested = undefined;
|
|
@@ -949,37 +1676,108 @@
|
|
|
949
1676
|
/**
|
|
950
1677
|
* Re-executes a component.
|
|
951
1678
|
*
|
|
952
|
-
* @
|
|
1679
|
+
* @param callback - Optional callback to execute before refresh
|
|
1680
|
+
* @returns The rendered result of the component or a promise thereof if the
|
|
953
1681
|
* component or its children execute asynchronously.
|
|
954
|
-
*
|
|
955
|
-
* The refresh method works a little differently for async generator
|
|
956
|
-
* components, in that it will resume the Context’s props async iterator
|
|
957
|
-
* rather than resuming execution. This is because async generator components
|
|
958
|
-
* are perpetually resumed independent of updates, and rely on the props
|
|
959
|
-
* async iterator to suspend.
|
|
960
1682
|
*/
|
|
961
|
-
refresh() {
|
|
962
|
-
const ctx = this[
|
|
963
|
-
if (ctx.
|
|
964
|
-
console.error(
|
|
965
|
-
return ctx.
|
|
1683
|
+
refresh(callback) {
|
|
1684
|
+
const ctx = this[_ContextState];
|
|
1685
|
+
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
1686
|
+
console.error(`Component <${getTagName(ctx.ret.el.tag)}> is unmounted. Check the isUnmounted property if necessary.`);
|
|
1687
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1688
|
+
}
|
|
1689
|
+
else if (getFlag(ctx.ret, IsExecuting)) {
|
|
1690
|
+
console.error(`Component <${getTagName(ctx.ret.el.tag)}> is already executing Check the isExecuting property if necessary.`);
|
|
1691
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1692
|
+
}
|
|
1693
|
+
if (callback) {
|
|
1694
|
+
const result = callback();
|
|
1695
|
+
if (isPromiseLike(result)) {
|
|
1696
|
+
return Promise.resolve(result).then(() => {
|
|
1697
|
+
if (!getFlag(ctx.ret, IsUnmounted)) {
|
|
1698
|
+
return this.refresh();
|
|
1699
|
+
}
|
|
1700
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
966
1703
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1704
|
+
let diff;
|
|
1705
|
+
const schedulePromises = [];
|
|
1706
|
+
try {
|
|
1707
|
+
setFlag(ctx.ret, IsRefreshing);
|
|
1708
|
+
diff = enqueueComponent(ctx);
|
|
1709
|
+
if (isPromiseLike(diff)) {
|
|
1710
|
+
return diff
|
|
1711
|
+
.then(() => ctx.adapter.read(commitComponent(ctx, schedulePromises)))
|
|
1712
|
+
.then((result) => {
|
|
1713
|
+
if (schedulePromises.length) {
|
|
1714
|
+
return Promise.all(schedulePromises).then(() => {
|
|
1715
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
return result;
|
|
1719
|
+
})
|
|
1720
|
+
.catch((err) => {
|
|
1721
|
+
const diff = propagateError(ctx, err, schedulePromises);
|
|
1722
|
+
if (diff) {
|
|
1723
|
+
return diff.then(() => {
|
|
1724
|
+
if (schedulePromises.length) {
|
|
1725
|
+
return Promise.all(schedulePromises).then(() => {
|
|
1726
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
if (schedulePromises.length) {
|
|
1733
|
+
return Promise.all(schedulePromises).then(() => {
|
|
1734
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1738
|
+
})
|
|
1739
|
+
.finally(() => setFlag(ctx.ret, IsRefreshing, false));
|
|
1740
|
+
}
|
|
1741
|
+
const result = ctx.adapter.read(commitComponent(ctx, schedulePromises));
|
|
1742
|
+
if (schedulePromises.length) {
|
|
1743
|
+
return Promise.all(schedulePromises).then(() => {
|
|
1744
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
return result;
|
|
970
1748
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1749
|
+
catch (err) {
|
|
1750
|
+
// TODO: await schedulePromises
|
|
1751
|
+
const diff = propagateError(ctx, err, schedulePromises);
|
|
1752
|
+
if (diff) {
|
|
1753
|
+
return diff
|
|
1754
|
+
.then(() => {
|
|
1755
|
+
if (schedulePromises.length) {
|
|
1756
|
+
return Promise.all(schedulePromises).then(() => {
|
|
1757
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
})
|
|
1761
|
+
.then(() => ctx.adapter.read(getValue(ctx.ret)));
|
|
1762
|
+
}
|
|
1763
|
+
if (schedulePromises.length) {
|
|
1764
|
+
return Promise.all(schedulePromises).then(() => {
|
|
1765
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
return ctx.adapter.read(getValue(ctx.ret));
|
|
1769
|
+
}
|
|
1770
|
+
finally {
|
|
1771
|
+
if (!isPromiseLike(diff)) {
|
|
1772
|
+
setFlag(ctx.ret, IsRefreshing, false);
|
|
1773
|
+
}
|
|
974
1774
|
}
|
|
975
|
-
return ctx.renderer.read(value);
|
|
976
1775
|
}
|
|
977
|
-
/**
|
|
978
|
-
* Registers a callback which fires when the component commits. Will only
|
|
979
|
-
* fire once per callback and update.
|
|
980
|
-
*/
|
|
981
1776
|
schedule(callback) {
|
|
982
|
-
|
|
1777
|
+
if (!callback) {
|
|
1778
|
+
return new Promise((resolve) => this.schedule(resolve));
|
|
1779
|
+
}
|
|
1780
|
+
const ctx = this[_ContextState];
|
|
983
1781
|
let callbacks = scheduleMap.get(ctx);
|
|
984
1782
|
if (!callbacks) {
|
|
985
1783
|
callbacks = new Set();
|
|
@@ -987,35 +1785,35 @@
|
|
|
987
1785
|
}
|
|
988
1786
|
callbacks.add(callback);
|
|
989
1787
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
*/
|
|
994
|
-
flush(callback) {
|
|
995
|
-
const ctx = this[_ContextImpl];
|
|
996
|
-
if (typeof ctx.root !== "object" || ctx.root === null) {
|
|
997
|
-
return;
|
|
1788
|
+
after(callback) {
|
|
1789
|
+
if (!callback) {
|
|
1790
|
+
return new Promise((resolve) => this.after(resolve));
|
|
998
1791
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1792
|
+
const ctx = this[_ContextState];
|
|
1793
|
+
const root = ctx.root || ANONYMOUS_ROOT;
|
|
1794
|
+
let afterMap = afterMapByRoot.get(root);
|
|
1795
|
+
if (!afterMap) {
|
|
1796
|
+
afterMap = new Map();
|
|
1797
|
+
afterMapByRoot.set(root, afterMap);
|
|
1003
1798
|
}
|
|
1004
|
-
let callbacks =
|
|
1799
|
+
let callbacks = afterMap.get(ctx);
|
|
1005
1800
|
if (!callbacks) {
|
|
1006
1801
|
callbacks = new Set();
|
|
1007
|
-
|
|
1802
|
+
afterMap.set(ctx, callbacks);
|
|
1008
1803
|
}
|
|
1009
1804
|
callbacks.add(callback);
|
|
1010
1805
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1806
|
+
flush(callback) {
|
|
1807
|
+
console.error("Context.flush() method has been renamed to after()");
|
|
1808
|
+
this.after(callback);
|
|
1809
|
+
}
|
|
1015
1810
|
cleanup(callback) {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1811
|
+
if (!callback) {
|
|
1812
|
+
return new Promise((resolve) => this.cleanup(resolve));
|
|
1813
|
+
}
|
|
1814
|
+
const ctx = this[_ContextState];
|
|
1815
|
+
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
1816
|
+
const value = ctx.adapter.read(getValue(ctx.ret));
|
|
1019
1817
|
callback(value);
|
|
1020
1818
|
return;
|
|
1021
1819
|
}
|
|
@@ -1027,7 +1825,7 @@
|
|
|
1027
1825
|
callbacks.add(callback);
|
|
1028
1826
|
}
|
|
1029
1827
|
consume(key) {
|
|
1030
|
-
for (let ctx = this[
|
|
1828
|
+
for (let ctx = this[_ContextState].parent; ctx !== undefined; ctx = ctx.parent) {
|
|
1031
1829
|
const provisions = provisionMaps.get(ctx);
|
|
1032
1830
|
if (provisions && provisions.has(key)) {
|
|
1033
1831
|
return provisions.get(key);
|
|
@@ -1035,7 +1833,7 @@
|
|
|
1035
1833
|
}
|
|
1036
1834
|
}
|
|
1037
1835
|
provide(key, value) {
|
|
1038
|
-
const ctx = this[
|
|
1836
|
+
const ctx = this[_ContextState];
|
|
1039
1837
|
let provisions = provisionMaps.get(ctx);
|
|
1040
1838
|
if (!provisions) {
|
|
1041
1839
|
provisions = new Map();
|
|
@@ -1043,980 +1841,820 @@
|
|
|
1043
1841
|
}
|
|
1044
1842
|
provisions.set(key, value);
|
|
1045
1843
|
}
|
|
1046
|
-
|
|
1047
|
-
const ctx = this[
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
else {
|
|
1053
|
-
const listeners1 = listenersMap.get(ctx);
|
|
1054
|
-
if (listeners1) {
|
|
1055
|
-
listeners = listeners1;
|
|
1056
|
-
}
|
|
1057
|
-
else {
|
|
1058
|
-
listeners = [];
|
|
1059
|
-
listenersMap.set(ctx, listeners);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
options = normalizeListenerOptions(options);
|
|
1063
|
-
let callback;
|
|
1064
|
-
if (typeof listener === "object") {
|
|
1065
|
-
callback = () => listener.handleEvent.apply(listener, arguments);
|
|
1844
|
+
[CustomEventTarget.dispatchEventOnSelf](ev) {
|
|
1845
|
+
const ctx = this[_ContextState];
|
|
1846
|
+
// dispatchEvent calls the prop callback if it exists
|
|
1847
|
+
let propCallback = ctx.ret.el.props["on" + ev.type];
|
|
1848
|
+
if (typeof propCallback === "function") {
|
|
1849
|
+
propCallback(ev);
|
|
1066
1850
|
}
|
|
1067
1851
|
else {
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
const i = listeners.indexOf(record);
|
|
1074
|
-
if (i !== -1) {
|
|
1075
|
-
listeners.splice(i, 1);
|
|
1076
|
-
}
|
|
1077
|
-
return callback.apply(this, arguments);
|
|
1078
|
-
};
|
|
1079
|
-
}
|
|
1080
|
-
if (listeners.some((record1) => record.type === record1.type &&
|
|
1081
|
-
record.listener === record1.listener &&
|
|
1082
|
-
!record.options.capture === !record1.options.capture)) {
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
listeners.push(record);
|
|
1086
|
-
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1087
|
-
for (const value of getChildValues(ctx.ret)) {
|
|
1088
|
-
if (isEventTarget(value)) {
|
|
1089
|
-
value.addEventListener(record.type, record.callback, record.options);
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
removeEventListener(type, listener, options) {
|
|
1094
|
-
const ctx = this[_ContextImpl];
|
|
1095
|
-
const listeners = listenersMap.get(ctx);
|
|
1096
|
-
if (listeners == null || !isListenerOrListenerObject(listener)) {
|
|
1097
|
-
return;
|
|
1098
|
-
}
|
|
1099
|
-
const options1 = normalizeListenerOptions(options);
|
|
1100
|
-
const i = listeners.findIndex((record) => record.type === type &&
|
|
1101
|
-
record.listener === listener &&
|
|
1102
|
-
!record.options.capture === !options1.capture);
|
|
1103
|
-
if (i === -1) {
|
|
1104
|
-
return;
|
|
1105
|
-
}
|
|
1106
|
-
const record = listeners[i];
|
|
1107
|
-
listeners.splice(i, 1);
|
|
1108
|
-
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1109
|
-
for (const value of getChildValues(ctx.ret)) {
|
|
1110
|
-
if (isEventTarget(value)) {
|
|
1111
|
-
value.removeEventListener(record.type, record.callback, record.options);
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
dispatchEvent(ev) {
|
|
1116
|
-
const ctx = this[_ContextImpl];
|
|
1117
|
-
const path = [];
|
|
1118
|
-
for (let parent = ctx.parent; parent !== undefined; parent = parent.parent) {
|
|
1119
|
-
path.push(parent);
|
|
1120
|
-
}
|
|
1121
|
-
// We patch the stopImmediatePropagation method because ev.cancelBubble
|
|
1122
|
-
// only informs us if stopPropagation was called and there are no
|
|
1123
|
-
// properties which inform us if stopImmediatePropagation was called.
|
|
1124
|
-
let immediateCancelBubble = false;
|
|
1125
|
-
const stopImmediatePropagation = ev.stopImmediatePropagation;
|
|
1126
|
-
setEventProperty(ev, "stopImmediatePropagation", () => {
|
|
1127
|
-
immediateCancelBubble = true;
|
|
1128
|
-
return stopImmediatePropagation.call(ev);
|
|
1129
|
-
});
|
|
1130
|
-
setEventProperty(ev, "target", ctx.owner);
|
|
1131
|
-
// The only possible errors in this block are errors thrown by callbacks,
|
|
1132
|
-
// and dispatchEvent will only log these errors rather than throwing
|
|
1133
|
-
// them. Therefore, we place all code in a try block, log errors in the
|
|
1134
|
-
// catch block, and use an unsafe return statement in the finally block.
|
|
1135
|
-
//
|
|
1136
|
-
// Each early return within the try block returns true because while the
|
|
1137
|
-
// return value is overridden in the finally block, TypeScript
|
|
1138
|
-
// (justifiably) does not recognize the unsafe return statement.
|
|
1139
|
-
try {
|
|
1140
|
-
setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
|
|
1141
|
-
for (let i = path.length - 1; i >= 0; i--) {
|
|
1142
|
-
const target = path[i];
|
|
1143
|
-
const listeners = listenersMap.get(target);
|
|
1144
|
-
if (listeners) {
|
|
1145
|
-
setEventProperty(ev, "currentTarget", target.owner);
|
|
1146
|
-
for (const record of listeners) {
|
|
1147
|
-
if (record.type === ev.type && record.options.capture) {
|
|
1148
|
-
try {
|
|
1149
|
-
record.callback.call(target.owner, ev);
|
|
1150
|
-
}
|
|
1151
|
-
catch (err) {
|
|
1152
|
-
console.error(err);
|
|
1153
|
-
}
|
|
1154
|
-
if (immediateCancelBubble) {
|
|
1155
|
-
return true;
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1852
|
+
for (const propName in ctx.ret.el.props) {
|
|
1853
|
+
if (propName.toLowerCase() === "on" + ev.type.toLowerCase()) {
|
|
1854
|
+
propCallback = ctx.ret.el.props[propName];
|
|
1855
|
+
if (typeof propCallback === "function") {
|
|
1856
|
+
propCallback(ev);
|
|
1158
1857
|
}
|
|
1159
1858
|
}
|
|
1160
|
-
if (ev.cancelBubble) {
|
|
1161
|
-
return true;
|
|
1162
|
-
}
|
|
1163
1859
|
}
|
|
1164
|
-
{
|
|
1165
|
-
setEventProperty(ev, "eventPhase", AT_TARGET);
|
|
1166
|
-
setEventProperty(ev, "currentTarget", ctx.owner);
|
|
1167
|
-
// dispatchEvent calls the prop callback if it exists
|
|
1168
|
-
let propCallback = ctx.ret.el.props["on" + ev.type];
|
|
1169
|
-
if (typeof propCallback === "function") {
|
|
1170
|
-
propCallback(ev);
|
|
1171
|
-
if (immediateCancelBubble || ev.cancelBubble) {
|
|
1172
|
-
return true;
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
else {
|
|
1176
|
-
// Checks for camel-cased event props
|
|
1177
|
-
for (const propName in ctx.ret.el.props) {
|
|
1178
|
-
if (propName.toLowerCase() === "on" + ev.type.toLowerCase()) {
|
|
1179
|
-
propCallback = ctx.ret.el.props[propName];
|
|
1180
|
-
if (typeof propCallback === "function") {
|
|
1181
|
-
propCallback(ev);
|
|
1182
|
-
if (immediateCancelBubble || ev.cancelBubble) {
|
|
1183
|
-
return true;
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
const listeners = listenersMap.get(ctx);
|
|
1190
|
-
if (listeners) {
|
|
1191
|
-
for (const record of listeners) {
|
|
1192
|
-
if (record.type === ev.type) {
|
|
1193
|
-
try {
|
|
1194
|
-
record.callback.call(ctx.owner, ev);
|
|
1195
|
-
}
|
|
1196
|
-
catch (err) {
|
|
1197
|
-
console.error(err);
|
|
1198
|
-
}
|
|
1199
|
-
if (immediateCancelBubble) {
|
|
1200
|
-
return true;
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
if (ev.cancelBubble) {
|
|
1205
|
-
return true;
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
if (ev.bubbles) {
|
|
1210
|
-
setEventProperty(ev, "eventPhase", BUBBLING_PHASE);
|
|
1211
|
-
for (let i = 0; i < path.length; i++) {
|
|
1212
|
-
const target = path[i];
|
|
1213
|
-
const listeners = listenersMap.get(target);
|
|
1214
|
-
if (listeners) {
|
|
1215
|
-
setEventProperty(ev, "currentTarget", target.owner);
|
|
1216
|
-
for (const record of listeners) {
|
|
1217
|
-
if (record.type === ev.type && !record.options.capture) {
|
|
1218
|
-
try {
|
|
1219
|
-
record.callback.call(target.owner, ev);
|
|
1220
|
-
}
|
|
1221
|
-
catch (err) {
|
|
1222
|
-
console.error(err);
|
|
1223
|
-
}
|
|
1224
|
-
if (immediateCancelBubble) {
|
|
1225
|
-
return true;
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
if (ev.cancelBubble) {
|
|
1231
|
-
return true;
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
finally {
|
|
1237
|
-
setEventProperty(ev, "eventPhase", NONE);
|
|
1238
|
-
setEventProperty(ev, "currentTarget", null);
|
|
1239
|
-
// eslint-disable-next-line no-unsafe-finally
|
|
1240
|
-
return !ev.defaultPrevented;
|
|
1241
1860
|
}
|
|
1242
1861
|
}
|
|
1243
1862
|
}
|
|
1244
|
-
|
|
1245
|
-
function ctxContains(parent, child) {
|
|
1246
|
-
for (let current = child; current !== undefined; current = current.parent) {
|
|
1247
|
-
if (current === parent) {
|
|
1248
|
-
return true;
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
return false;
|
|
1252
|
-
}
|
|
1253
|
-
function updateComponent(renderer, root, host, parent, scope, ret, oldProps, hydrationData) {
|
|
1863
|
+
function diffComponent(adapter, root, host, parent, scope, ret) {
|
|
1254
1864
|
let ctx;
|
|
1255
|
-
if (
|
|
1865
|
+
if (ret.ctx) {
|
|
1256
1866
|
ctx = ret.ctx;
|
|
1257
|
-
if (ctx.
|
|
1258
|
-
console.error(
|
|
1259
|
-
return
|
|
1867
|
+
if (getFlag(ctx.ret, IsExecuting)) {
|
|
1868
|
+
console.error(`Component <${getTagName(ctx.ret.el.tag)}> is already executing`);
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
else if (ctx.schedule) {
|
|
1872
|
+
return ctx.schedule.promise.then(() => {
|
|
1873
|
+
return diffComponent(adapter, root, host, parent, scope, ret);
|
|
1874
|
+
});
|
|
1260
1875
|
}
|
|
1261
1876
|
}
|
|
1262
1877
|
else {
|
|
1263
|
-
ctx = ret.ctx = new
|
|
1878
|
+
ctx = ret.ctx = new ContextState(adapter, root, host, parent, scope, ret);
|
|
1264
1879
|
}
|
|
1265
|
-
ctx.
|
|
1266
|
-
return
|
|
1880
|
+
setFlag(ctx.ret, IsUpdating);
|
|
1881
|
+
return enqueueComponent(ctx);
|
|
1267
1882
|
}
|
|
1268
|
-
function
|
|
1269
|
-
if (ctx.
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
|
-
else if (ctx.f & IsErrored) {
|
|
1273
|
-
// This branch is necessary for some race conditions where this function is
|
|
1274
|
-
// called after iterator.throw() in async generator components.
|
|
1883
|
+
function diffComponentChildren(ctx, children, isYield) {
|
|
1884
|
+
if (getFlag(ctx.ret, IsUnmounted) || getFlag(ctx.ret, IsErrored)) {
|
|
1275
1885
|
return;
|
|
1276
1886
|
}
|
|
1277
1887
|
else if (children === undefined) {
|
|
1278
|
-
console.error(
|
|
1888
|
+
console.error(`Component <${getTagName(ctx.ret.el.tag)}> has ${isYield ? "yielded" : "returned"} undefined. If this was intentional, ${isYield ? "yield" : "return"} null instead.`);
|
|
1279
1889
|
}
|
|
1280
|
-
let
|
|
1890
|
+
let diff;
|
|
1281
1891
|
try {
|
|
1282
|
-
// TODO:
|
|
1892
|
+
// TODO: Use a different flag here to indicate the component is
|
|
1893
|
+
// synchronously rendering children
|
|
1283
1894
|
// We set the isExecuting flag in case a child component dispatches an event
|
|
1284
1895
|
// which bubbles to this component and causes a synchronous refresh().
|
|
1285
|
-
ctx.
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
ctx.f &= ~IsSyncExecuting;
|
|
1290
|
-
}
|
|
1291
|
-
if (isPromiseLike(childValues)) {
|
|
1292
|
-
ctx.ret.inflightValue = childValues.then((childValues) => commitComponent(ctx, childValues));
|
|
1293
|
-
return ctx.ret.inflightValue;
|
|
1294
|
-
}
|
|
1295
|
-
return commitComponent(ctx, childValues);
|
|
1296
|
-
}
|
|
1297
|
-
function commitComponent(ctx, values) {
|
|
1298
|
-
if (ctx.f & IsUnmounted) {
|
|
1299
|
-
return;
|
|
1300
|
-
}
|
|
1301
|
-
const listeners = listenersMap.get(ctx);
|
|
1302
|
-
if (listeners && listeners.length) {
|
|
1303
|
-
for (let i = 0; i < values.length; i++) {
|
|
1304
|
-
const value = values[i];
|
|
1305
|
-
if (isEventTarget(value)) {
|
|
1306
|
-
for (let j = 0; j < listeners.length; j++) {
|
|
1307
|
-
const record = listeners[j];
|
|
1308
|
-
value.addEventListener(record.type, record.callback, record.options);
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
const oldValues = wrap(ctx.ret.cachedChildValues);
|
|
1314
|
-
let value = (ctx.ret.cachedChildValues = unwrap(values));
|
|
1315
|
-
if (ctx.f & IsScheduling) {
|
|
1316
|
-
ctx.f |= IsSchedulingRefresh;
|
|
1317
|
-
}
|
|
1318
|
-
else if (!(ctx.f & IsUpdating)) {
|
|
1319
|
-
// If we’re not updating the component, which happens when components are
|
|
1320
|
-
// refreshed, or when async generator components iterate, we have to do a
|
|
1321
|
-
// little bit housekeeping when a component’s child values have changed.
|
|
1322
|
-
if (!arrayEqual(oldValues, values)) {
|
|
1323
|
-
const records = getListenerRecords(ctx.parent, ctx.host);
|
|
1324
|
-
if (records.length) {
|
|
1325
|
-
for (let i = 0; i < values.length; i++) {
|
|
1326
|
-
const value = values[i];
|
|
1327
|
-
if (isEventTarget(value)) {
|
|
1328
|
-
for (let j = 0; j < records.length; j++) {
|
|
1329
|
-
const record = records[j];
|
|
1330
|
-
value.addEventListener(record.type, record.callback, record.options);
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
// rearranging the nearest ancestor host element
|
|
1336
|
-
const host = ctx.host;
|
|
1337
|
-
const oldHostValues = wrap(host.cachedChildValues);
|
|
1338
|
-
invalidate(ctx, host);
|
|
1339
|
-
const hostValues = getChildValues(host);
|
|
1340
|
-
ctx.renderer.arrange(host.el.tag, host.value, host.el.props, hostValues,
|
|
1341
|
-
// props and oldProps are the same because the host isn’t updated.
|
|
1342
|
-
host.el.props, oldHostValues);
|
|
1343
|
-
}
|
|
1344
|
-
flush(ctx.renderer, ctx.root, ctx);
|
|
1345
|
-
}
|
|
1346
|
-
const callbacks = scheduleMap.get(ctx);
|
|
1347
|
-
if (callbacks) {
|
|
1348
|
-
scheduleMap.delete(ctx);
|
|
1349
|
-
ctx.f |= IsScheduling;
|
|
1350
|
-
const value1 = ctx.renderer.read(value);
|
|
1351
|
-
for (const callback of callbacks) {
|
|
1352
|
-
callback(value1);
|
|
1353
|
-
}
|
|
1354
|
-
ctx.f &= ~IsScheduling;
|
|
1355
|
-
// Handles an edge case where refresh() is called during a schedule().
|
|
1356
|
-
if (ctx.f & IsSchedulingRefresh) {
|
|
1357
|
-
ctx.f &= ~IsSchedulingRefresh;
|
|
1358
|
-
value = getValue(ctx.ret);
|
|
1896
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1897
|
+
diff = diffChildren(ctx.adapter, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children));
|
|
1898
|
+
if (diff) {
|
|
1899
|
+
diff = diff.catch((err) => handleChildError(ctx, err));
|
|
1359
1900
|
}
|
|
1360
1901
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
}
|
|
1364
|
-
function invalidate(ctx, host) {
|
|
1365
|
-
for (let parent = ctx.parent; parent !== undefined && parent.host === host; parent = parent.parent) {
|
|
1366
|
-
parent.ret.cachedChildValues = undefined;
|
|
1367
|
-
}
|
|
1368
|
-
host.cachedChildValues = undefined;
|
|
1369
|
-
}
|
|
1370
|
-
function arrayEqual(arr1, arr2) {
|
|
1371
|
-
if (arr1.length !== arr2.length) {
|
|
1372
|
-
return false;
|
|
1902
|
+
catch (err) {
|
|
1903
|
+
diff = handleChildError(ctx, err);
|
|
1373
1904
|
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
const value2 = arr2[i];
|
|
1377
|
-
if (value1 !== value2) {
|
|
1378
|
-
return false;
|
|
1379
|
-
}
|
|
1905
|
+
finally {
|
|
1906
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1380
1907
|
}
|
|
1381
|
-
return
|
|
1908
|
+
return diff;
|
|
1382
1909
|
}
|
|
1383
1910
|
/** Enqueues and executes the component associated with the context. */
|
|
1384
|
-
function
|
|
1385
|
-
if (
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
//
|
|
1395
|
-
//
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
// The component is suspended somewhere in the loop. When the component
|
|
1408
|
-
// reaches the bottom of the loop, it will suspend.
|
|
1409
|
-
//
|
|
1410
|
-
// Components will never be both available and suspended at
|
|
1411
|
-
// the same time.
|
|
1412
|
-
//
|
|
1413
|
-
// If the component is at the loop bottom, this means that the next value
|
|
1414
|
-
// produced by the component will have the most up to date props, so we can
|
|
1415
|
-
// simply return the current inflight value. Otherwise, we have to wait for
|
|
1416
|
-
// the bottom of the loop to be reached before returning the inflight
|
|
1417
|
-
// value.
|
|
1418
|
-
const isAtLoopbottom = ctx.f & IsInForAwaitOfLoop && !ctx.onProps;
|
|
1419
|
-
resumePropsAsyncIterator(ctx);
|
|
1420
|
-
if (isAtLoopbottom) {
|
|
1421
|
-
if (ctx.inflightBlock == null) {
|
|
1422
|
-
ctx.inflightBlock = new Promise((resolve) => (ctx.onPropsRequested = resolve));
|
|
1423
|
-
}
|
|
1424
|
-
return ctx.inflightBlock.then(() => {
|
|
1425
|
-
ctx.inflightBlock = undefined;
|
|
1426
|
-
return ctx.inflightValue;
|
|
1427
|
-
});
|
|
1428
|
-
}
|
|
1429
|
-
return ctx.inflightValue;
|
|
1430
|
-
}
|
|
1431
|
-
else if (!ctx.inflightBlock) {
|
|
1432
|
-
try {
|
|
1433
|
-
const [block, value] = runComponent(ctx, hydrationData);
|
|
1434
|
-
if (block) {
|
|
1435
|
-
ctx.inflightBlock = block
|
|
1436
|
-
// TODO: there is some fuckery going on here related to async
|
|
1437
|
-
// generator components resuming when they’re meant to be returned.
|
|
1438
|
-
.then((v) => v)
|
|
1439
|
-
.finally(() => advanceComponent(ctx));
|
|
1440
|
-
// stepComponent will only return a block if the value is asynchronous
|
|
1441
|
-
ctx.inflightValue = value;
|
|
1442
|
-
}
|
|
1443
|
-
return value;
|
|
1444
|
-
}
|
|
1445
|
-
catch (err) {
|
|
1446
|
-
if (!(ctx.f & IsUpdating)) {
|
|
1447
|
-
if (!ctx.parent) {
|
|
1448
|
-
throw err;
|
|
1449
|
-
}
|
|
1450
|
-
return propagateError(ctx.parent, err);
|
|
1451
|
-
}
|
|
1452
|
-
throw err;
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
else if (!ctx.enqueuedBlock) {
|
|
1456
|
-
if (hydrationData !== undefined) {
|
|
1457
|
-
throw new Error("Hydration error");
|
|
1458
|
-
}
|
|
1459
|
-
// We need to assign enqueuedBlock and enqueuedValue synchronously, hence
|
|
1460
|
-
// the Promise constructor call here.
|
|
1461
|
-
let resolveEnqueuedBlock;
|
|
1462
|
-
ctx.enqueuedBlock = new Promise((resolve) => (resolveEnqueuedBlock = resolve));
|
|
1463
|
-
ctx.enqueuedValue = ctx.inflightBlock.then(() => {
|
|
1464
|
-
try {
|
|
1465
|
-
const [block, value] = runComponent(ctx);
|
|
1466
|
-
if (block) {
|
|
1467
|
-
resolveEnqueuedBlock(block.finally(() => advanceComponent(ctx)));
|
|
1468
|
-
}
|
|
1469
|
-
return value;
|
|
1470
|
-
}
|
|
1471
|
-
catch (err) {
|
|
1472
|
-
if (!(ctx.f & IsUpdating)) {
|
|
1473
|
-
if (!ctx.parent) {
|
|
1474
|
-
throw err;
|
|
1475
|
-
}
|
|
1476
|
-
return propagateError(ctx.parent, err);
|
|
1477
|
-
}
|
|
1478
|
-
throw err;
|
|
1479
|
-
}
|
|
1480
|
-
});
|
|
1481
|
-
}
|
|
1482
|
-
return ctx.enqueuedValue;
|
|
1911
|
+
function enqueueComponent(ctx) {
|
|
1912
|
+
if (!ctx.inflight) {
|
|
1913
|
+
const [block, diff] = runComponent(ctx);
|
|
1914
|
+
if (block) {
|
|
1915
|
+
// if block is a promise, diff is a promise
|
|
1916
|
+
ctx.inflight = [block.finally(() => advanceComponent(ctx)), diff];
|
|
1917
|
+
}
|
|
1918
|
+
return diff;
|
|
1919
|
+
}
|
|
1920
|
+
else if (!ctx.enqueued) {
|
|
1921
|
+
// The enqueuedBlock and enqueuedDiff properties must be set
|
|
1922
|
+
// simultaneously, hence the usage of the Promise constructor.
|
|
1923
|
+
let resolve;
|
|
1924
|
+
ctx.enqueued = [
|
|
1925
|
+
new Promise((resolve1) => (resolve = resolve1)).finally(() => advanceComponent(ctx)),
|
|
1926
|
+
ctx.inflight[0].finally(() => {
|
|
1927
|
+
const [block, diff] = runComponent(ctx);
|
|
1928
|
+
resolve(block);
|
|
1929
|
+
return diff;
|
|
1930
|
+
}),
|
|
1931
|
+
];
|
|
1932
|
+
}
|
|
1933
|
+
return ctx.enqueued[1];
|
|
1483
1934
|
}
|
|
1484
1935
|
/** Called when the inflight block promise settles. */
|
|
1485
1936
|
function advanceComponent(ctx) {
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
}
|
|
1489
|
-
ctx.inflightBlock = ctx.enqueuedBlock;
|
|
1490
|
-
ctx.inflightValue = ctx.enqueuedValue;
|
|
1491
|
-
ctx.enqueuedBlock = undefined;
|
|
1492
|
-
ctx.enqueuedValue = undefined;
|
|
1937
|
+
ctx.inflight = ctx.enqueued;
|
|
1938
|
+
ctx.enqueued = undefined;
|
|
1493
1939
|
}
|
|
1494
1940
|
/**
|
|
1495
|
-
* This function is responsible for executing
|
|
1496
|
-
*
|
|
1497
|
-
* generator or async without calling it and inspecting the return value.
|
|
1941
|
+
* This function is responsible for executing components, and handling the
|
|
1942
|
+
* different component types.
|
|
1498
1943
|
*
|
|
1499
|
-
* @returns {[block,
|
|
1500
|
-
* block
|
|
1501
|
-
* component is blocked
|
|
1502
|
-
*
|
|
1944
|
+
* @returns {[block, diff]} A tuple where:
|
|
1945
|
+
* - block is a promise or undefined which represents the duration during which
|
|
1946
|
+
* the component is blocked.
|
|
1947
|
+
* - diff is a promise or undefined which represents the duration for diffing
|
|
1948
|
+
* of children.
|
|
1503
1949
|
*
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
1506
|
-
* to
|
|
1507
|
-
* -
|
|
1508
|
-
*
|
|
1509
|
-
* -
|
|
1510
|
-
*
|
|
1950
|
+
* While a component is blocked, further updates to the component are enqueued.
|
|
1951
|
+
*
|
|
1952
|
+
* Each component type blocks according to its implementation:
|
|
1953
|
+
* - Sync function components never block; when props or state change,
|
|
1954
|
+
* updates are immediately passed to children.
|
|
1955
|
+
* - Async function components block only while awaiting their own async work
|
|
1956
|
+
* (e.g., during an await), but do not block while their async children are rendering.
|
|
1957
|
+
* - Sync generator components block while their children are rendering;
|
|
1958
|
+
* they only resume once their children have finished.
|
|
1959
|
+
* - Async generator components can block in two different ways:
|
|
1960
|
+
* - By default, they behave like sync generator components, blocking while
|
|
1961
|
+
* the component or its children are rendering.
|
|
1962
|
+
* - Within a for await...of loop, they block only while waiting for new
|
|
1963
|
+
* props to be requested, and not while children are rendering.
|
|
1511
1964
|
*/
|
|
1512
|
-
function runComponent(ctx
|
|
1965
|
+
function runComponent(ctx) {
|
|
1966
|
+
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
1967
|
+
return [undefined, undefined];
|
|
1968
|
+
}
|
|
1513
1969
|
const ret = ctx.ret;
|
|
1514
1970
|
const initial = !ctx.iterator;
|
|
1515
1971
|
if (initial) {
|
|
1516
|
-
|
|
1517
|
-
ctx.
|
|
1518
|
-
|
|
1519
|
-
let result;
|
|
1972
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1973
|
+
clearEventListeners(ctx.ctx);
|
|
1974
|
+
let returned;
|
|
1520
1975
|
try {
|
|
1521
|
-
|
|
1976
|
+
returned = ret.el.tag.call(ctx.ctx, ret.el.props, ctx.ctx);
|
|
1522
1977
|
}
|
|
1523
1978
|
catch (err) {
|
|
1524
|
-
ctx.
|
|
1979
|
+
setFlag(ctx.ret, IsErrored);
|
|
1525
1980
|
throw err;
|
|
1526
1981
|
}
|
|
1527
1982
|
finally {
|
|
1528
|
-
ctx.
|
|
1529
|
-
}
|
|
1530
|
-
if (isIteratorLike(result)) {
|
|
1531
|
-
ctx.iterator = result;
|
|
1983
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1532
1984
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
const result1 = result instanceof Promise ? result : Promise.resolve(result);
|
|
1536
|
-
const value = result1.then((result) => updateComponentChildren(ctx, result, hydrationData), (err) => {
|
|
1537
|
-
ctx.f |= IsErrored;
|
|
1538
|
-
throw err;
|
|
1539
|
-
});
|
|
1540
|
-
return [result1.catch(NOOP), value];
|
|
1985
|
+
if (isIteratorLike(returned)) {
|
|
1986
|
+
ctx.iterator = returned;
|
|
1541
1987
|
}
|
|
1542
|
-
else {
|
|
1988
|
+
else if (!isPromiseLike(returned)) {
|
|
1543
1989
|
// sync function component
|
|
1544
1990
|
return [
|
|
1545
1991
|
undefined,
|
|
1546
|
-
|
|
1992
|
+
diffComponentChildren(ctx, returned, false),
|
|
1993
|
+
];
|
|
1994
|
+
}
|
|
1995
|
+
else {
|
|
1996
|
+
// async function component
|
|
1997
|
+
const returned1 = returned instanceof Promise ? returned : Promise.resolve(returned);
|
|
1998
|
+
return [
|
|
1999
|
+
returned1.catch(NOOP),
|
|
2000
|
+
returned1.then((returned) => diffComponentChildren(ctx, returned, false), (err) => {
|
|
2001
|
+
setFlag(ctx.ret, IsErrored);
|
|
2002
|
+
throw err;
|
|
2003
|
+
}),
|
|
1547
2004
|
];
|
|
1548
2005
|
}
|
|
1549
|
-
}
|
|
1550
|
-
else if (hydrationData !== undefined) {
|
|
1551
|
-
// hydration data should only be passed on the initial render
|
|
1552
|
-
throw new Error("Hydration error");
|
|
1553
2006
|
}
|
|
1554
2007
|
let iteration;
|
|
1555
2008
|
if (initial) {
|
|
1556
2009
|
try {
|
|
1557
|
-
ctx.
|
|
2010
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1558
2011
|
iteration = ctx.iterator.next();
|
|
1559
2012
|
}
|
|
1560
2013
|
catch (err) {
|
|
1561
|
-
ctx.
|
|
2014
|
+
setFlag(ctx.ret, IsErrored);
|
|
1562
2015
|
throw err;
|
|
1563
2016
|
}
|
|
1564
2017
|
finally {
|
|
1565
|
-
ctx.
|
|
2018
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1566
2019
|
}
|
|
1567
2020
|
if (isPromiseLike(iteration)) {
|
|
1568
|
-
ctx.
|
|
2021
|
+
setFlag(ctx.ret, IsAsyncGen);
|
|
1569
2022
|
}
|
|
1570
2023
|
else {
|
|
1571
|
-
ctx.
|
|
2024
|
+
setFlag(ctx.ret, IsSyncGen);
|
|
1572
2025
|
}
|
|
1573
2026
|
}
|
|
1574
|
-
if (ctx.
|
|
2027
|
+
if (getFlag(ctx.ret, IsSyncGen)) {
|
|
1575
2028
|
// sync generator component
|
|
1576
2029
|
if (!initial) {
|
|
1577
2030
|
try {
|
|
1578
|
-
ctx.
|
|
1579
|
-
|
|
2031
|
+
setFlag(ctx.ret, IsExecuting);
|
|
2032
|
+
const oldResult = ctx.adapter.read(getValue(ctx.ret));
|
|
2033
|
+
iteration = ctx.iterator.next(oldResult);
|
|
1580
2034
|
}
|
|
1581
2035
|
catch (err) {
|
|
1582
|
-
ctx.
|
|
2036
|
+
setFlag(ctx.ret, IsErrored);
|
|
1583
2037
|
throw err;
|
|
1584
2038
|
}
|
|
1585
2039
|
finally {
|
|
1586
|
-
ctx.
|
|
2040
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1587
2041
|
}
|
|
1588
2042
|
}
|
|
1589
2043
|
if (isPromiseLike(iteration)) {
|
|
1590
2044
|
throw new Error("Mixed generator component");
|
|
1591
2045
|
}
|
|
1592
|
-
if (ctx.
|
|
1593
|
-
!(ctx.
|
|
1594
|
-
!(ctx.
|
|
1595
|
-
|
|
2046
|
+
if (getFlag(ctx.ret, IsInForOfLoop) &&
|
|
2047
|
+
!getFlag(ctx.ret, NeedsToYield) &&
|
|
2048
|
+
!getFlag(ctx.ret, IsUnmounted) &&
|
|
2049
|
+
!getFlag(ctx.ret, IsScheduling)) {
|
|
2050
|
+
console.error(`Component <${getTagName(ctx.ret.el.tag)}> yielded/returned more than once in for...of loop`);
|
|
1596
2051
|
}
|
|
1597
|
-
ctx.
|
|
2052
|
+
setFlag(ctx.ret, NeedsToYield, false);
|
|
1598
2053
|
if (iteration.done) {
|
|
1599
|
-
ctx.
|
|
2054
|
+
setFlag(ctx.ret, IsSyncGen, false);
|
|
1600
2055
|
ctx.iterator = undefined;
|
|
1601
2056
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
// Children can be void so we eliminate that here
|
|
1606
|
-
iteration.value, hydrationData);
|
|
1607
|
-
if (isPromiseLike(value)) {
|
|
1608
|
-
value = value.catch((err) => handleChildError(ctx, err));
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
catch (err) {
|
|
1612
|
-
value = handleChildError(ctx, err);
|
|
1613
|
-
}
|
|
1614
|
-
const block = isPromiseLike(value) ? value.catch(NOOP) : undefined;
|
|
1615
|
-
return [block, value];
|
|
2057
|
+
const diff = diffComponentChildren(ctx, iteration.value, !iteration.done);
|
|
2058
|
+
const block = isPromiseLike(diff) ? diff.catch(NOOP) : undefined;
|
|
2059
|
+
return [block, diff];
|
|
1616
2060
|
}
|
|
1617
2061
|
else {
|
|
1618
|
-
if (ctx.
|
|
1619
|
-
//
|
|
1620
|
-
|
|
1621
|
-
|
|
2062
|
+
if (getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
2063
|
+
// initializes the async generator loop
|
|
2064
|
+
pullComponent(ctx, iteration);
|
|
2065
|
+
const block = resumePropsAsyncIterator(ctx);
|
|
2066
|
+
return [block, ctx.pull && ctx.pull.diff];
|
|
2067
|
+
}
|
|
2068
|
+
else {
|
|
2069
|
+
// We call resumePropsAsyncIterator in case the component exits the
|
|
2070
|
+
// for...of loop
|
|
2071
|
+
resumePropsAsyncIterator(ctx);
|
|
1622
2072
|
if (!initial) {
|
|
1623
2073
|
try {
|
|
1624
|
-
ctx.
|
|
1625
|
-
|
|
2074
|
+
setFlag(ctx.ret, IsExecuting);
|
|
2075
|
+
const oldResult = ctx.adapter.read(getValue(ctx.ret));
|
|
2076
|
+
iteration = ctx.iterator.next(oldResult);
|
|
1626
2077
|
}
|
|
1627
2078
|
catch (err) {
|
|
1628
|
-
ctx.
|
|
2079
|
+
setFlag(ctx.ret, IsErrored);
|
|
1629
2080
|
throw err;
|
|
1630
2081
|
}
|
|
1631
2082
|
finally {
|
|
1632
|
-
ctx.
|
|
2083
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1633
2084
|
}
|
|
1634
2085
|
}
|
|
1635
2086
|
if (!isPromiseLike(iteration)) {
|
|
1636
2087
|
throw new Error("Mixed generator component");
|
|
1637
2088
|
}
|
|
1638
|
-
const
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
runAsyncGenComponent(ctx, Promise.resolve(iteration), hydrationData);
|
|
2089
|
+
const diff = iteration.then((iteration) => {
|
|
2090
|
+
if (getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
2091
|
+
// We have entered a for await...of loop, so we start pulling
|
|
2092
|
+
pullComponent(ctx, iteration);
|
|
1643
2093
|
}
|
|
1644
2094
|
else {
|
|
1645
|
-
if (
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
try {
|
|
1651
|
-
value = updateComponentChildren(ctx,
|
|
1652
|
-
// Children can be void so we eliminate that here
|
|
1653
|
-
iteration.value, hydrationData);
|
|
1654
|
-
if (isPromiseLike(value)) {
|
|
1655
|
-
value = value.catch((err) => handleChildError(ctx, err));
|
|
2095
|
+
if (getFlag(ctx.ret, IsInForOfLoop) &&
|
|
2096
|
+
!getFlag(ctx.ret, NeedsToYield) &&
|
|
2097
|
+
!getFlag(ctx.ret, IsUnmounted) &&
|
|
2098
|
+
!getFlag(ctx.ret, IsScheduling)) {
|
|
2099
|
+
console.error(`Component <${getTagName(ctx.ret.el.tag)}> yielded/returned more than once in for...of loop`);
|
|
1656
2100
|
}
|
|
1657
2101
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
2102
|
+
setFlag(ctx.ret, NeedsToYield, false);
|
|
2103
|
+
if (iteration.done) {
|
|
2104
|
+
setFlag(ctx.ret, IsAsyncGen, false);
|
|
2105
|
+
ctx.iterator = undefined;
|
|
1660
2106
|
}
|
|
1661
|
-
return
|
|
2107
|
+
return diffComponentChildren(ctx,
|
|
2108
|
+
// Children can be void so we eliminate that here
|
|
2109
|
+
iteration.value, !iteration.done);
|
|
1662
2110
|
}, (err) => {
|
|
1663
|
-
ctx.
|
|
2111
|
+
setFlag(ctx.ret, IsErrored);
|
|
1664
2112
|
throw err;
|
|
1665
2113
|
});
|
|
1666
|
-
return [
|
|
2114
|
+
return [diff.catch(NOOP), diff];
|
|
1667
2115
|
}
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
/**
|
|
2119
|
+
* Called to resume the props async iterator for async generator components.
|
|
2120
|
+
*
|
|
2121
|
+
* @returns {Promise<undefined> | undefined} A possible promise which
|
|
2122
|
+
* represents the duration during which the component is blocked.
|
|
2123
|
+
*/
|
|
2124
|
+
function resumePropsAsyncIterator(ctx) {
|
|
2125
|
+
if (ctx.onPropsProvided) {
|
|
2126
|
+
ctx.onPropsProvided(ctx.ret.el.props);
|
|
2127
|
+
ctx.onPropsProvided = undefined;
|
|
2128
|
+
setFlag(ctx.ret, PropsAvailable, false);
|
|
2129
|
+
}
|
|
2130
|
+
else {
|
|
2131
|
+
setFlag(ctx.ret, PropsAvailable);
|
|
2132
|
+
if (getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
2133
|
+
return new Promise((resolve) => (ctx.onPropsRequested = resolve));
|
|
1671
2134
|
}
|
|
1672
2135
|
}
|
|
2136
|
+
return (ctx.pull && ctx.pull.iterationP && ctx.pull.iterationP.then(NOOP, NOOP));
|
|
1673
2137
|
}
|
|
1674
|
-
|
|
2138
|
+
/**
|
|
2139
|
+
* The logic for pulling from async generator components when they are in a for
|
|
2140
|
+
* await...of loop is implemented here.
|
|
2141
|
+
*
|
|
2142
|
+
* It makes sense to group this logic in a single async loop to prevent race
|
|
2143
|
+
* conditions caused by calling next(), throw() and return() concurrently.
|
|
2144
|
+
*/
|
|
2145
|
+
async function pullComponent(ctx, iterationP) {
|
|
2146
|
+
if (!iterationP || ctx.pull) {
|
|
2147
|
+
return;
|
|
2148
|
+
}
|
|
2149
|
+
ctx.pull = { iterationP: undefined, diff: undefined, onChildError: undefined };
|
|
2150
|
+
// TODO: replace done with iteration
|
|
2151
|
+
//let iteration: ChildrenIteratorResult | undefined;
|
|
1675
2152
|
let done = false;
|
|
1676
2153
|
try {
|
|
2154
|
+
let childError;
|
|
1677
2155
|
while (!done) {
|
|
1678
|
-
if (
|
|
1679
|
-
|
|
1680
|
-
}
|
|
1681
|
-
// inflightValue must be set synchronously.
|
|
1682
|
-
let onValue;
|
|
1683
|
-
ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
|
|
1684
|
-
if (ctx.f & IsUpdating) {
|
|
1685
|
-
// We should not swallow unhandled promise rejections if the component is
|
|
1686
|
-
// updating independently.
|
|
1687
|
-
// TODO: Does this handle this.refresh() calls?
|
|
1688
|
-
ctx.inflightValue.catch(NOOP);
|
|
2156
|
+
if (isPromiseLike(iterationP)) {
|
|
2157
|
+
ctx.pull.iterationP = iterationP;
|
|
1689
2158
|
}
|
|
2159
|
+
let onDiff;
|
|
2160
|
+
ctx.pull.diff = new Promise((resolve) => (onDiff = resolve)).then(() => {
|
|
2161
|
+
if (!(getFlag(ctx.ret, IsUpdating) || getFlag(ctx.ret, IsRefreshing))) {
|
|
2162
|
+
commitComponent(ctx, []);
|
|
2163
|
+
}
|
|
2164
|
+
}, (err) => {
|
|
2165
|
+
if (!(getFlag(ctx.ret, IsUpdating) || getFlag(ctx.ret, IsRefreshing)) ||
|
|
2166
|
+
// TODO: is this flag necessary?
|
|
2167
|
+
!getFlag(ctx.ret, NeedsToYield)) {
|
|
2168
|
+
return propagateError(ctx, err, []);
|
|
2169
|
+
}
|
|
2170
|
+
throw err;
|
|
2171
|
+
});
|
|
1690
2172
|
let iteration;
|
|
1691
2173
|
try {
|
|
1692
2174
|
iteration = await iterationP;
|
|
1693
2175
|
}
|
|
1694
2176
|
catch (err) {
|
|
1695
2177
|
done = true;
|
|
1696
|
-
ctx.
|
|
1697
|
-
|
|
2178
|
+
setFlag(ctx.ret, IsErrored);
|
|
2179
|
+
setFlag(ctx.ret, NeedsToYield, false);
|
|
2180
|
+
onDiff(Promise.reject(err));
|
|
1698
2181
|
break;
|
|
1699
2182
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
2183
|
+
// this must be set after iterationP is awaited
|
|
2184
|
+
let oldResult;
|
|
2185
|
+
{
|
|
2186
|
+
// The 'floating' flag tracks whether the promise passed to the generator
|
|
2187
|
+
// is handled (via await, then, or catch). If handled, we reject the
|
|
2188
|
+
// promise so the user can catch errors. If not, we inject the error back
|
|
2189
|
+
// into the generator using throw, like for sync generator components.
|
|
2190
|
+
let floating = true;
|
|
2191
|
+
const oldResult1 = new Promise((resolve, reject) => {
|
|
2192
|
+
ctx.ctx.schedule(resolve);
|
|
2193
|
+
ctx.pull.onChildError = (err) => {
|
|
2194
|
+
reject(err);
|
|
2195
|
+
if (floating) {
|
|
2196
|
+
childError = err;
|
|
2197
|
+
resumePropsAsyncIterator(ctx);
|
|
2198
|
+
return ctx.pull.diff;
|
|
2199
|
+
}
|
|
2200
|
+
};
|
|
2201
|
+
});
|
|
2202
|
+
oldResult1.catch(NOOP);
|
|
2203
|
+
// We use Object.create() to clone the promise for float detection
|
|
2204
|
+
// because modern JS engines skip calling .then() on promises awaited
|
|
2205
|
+
// with await.
|
|
2206
|
+
oldResult = Object.create(oldResult1);
|
|
2207
|
+
oldResult.then = function (onfulfilled, onrejected) {
|
|
2208
|
+
floating = false;
|
|
2209
|
+
return oldResult1.then(onfulfilled, onrejected);
|
|
2210
|
+
};
|
|
2211
|
+
oldResult.catch = function (onrejected) {
|
|
2212
|
+
floating = false;
|
|
2213
|
+
return oldResult1.catch(onrejected);
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
if (childError != null) {
|
|
2217
|
+
try {
|
|
2218
|
+
setFlag(ctx.ret, IsExecuting);
|
|
2219
|
+
if (typeof ctx.iterator.throw !== "function") {
|
|
2220
|
+
throw childError;
|
|
2221
|
+
}
|
|
2222
|
+
iteration = await ctx.iterator.throw(childError);
|
|
2223
|
+
}
|
|
2224
|
+
catch (err) {
|
|
2225
|
+
done = true;
|
|
2226
|
+
setFlag(ctx.ret, IsErrored);
|
|
2227
|
+
setFlag(ctx.ret, NeedsToYield, false);
|
|
2228
|
+
onDiff(Promise.reject(err));
|
|
2229
|
+
break;
|
|
2230
|
+
}
|
|
2231
|
+
finally {
|
|
2232
|
+
childError = undefined;
|
|
2233
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
// this makes sure we pause before entering a loop if we yield before it
|
|
2237
|
+
if (!getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
2238
|
+
setFlag(ctx.ret, PropsAvailable, false);
|
|
1702
2239
|
}
|
|
1703
2240
|
done = !!iteration.done;
|
|
1704
|
-
let
|
|
2241
|
+
let diff;
|
|
1705
2242
|
try {
|
|
1706
|
-
if (!(
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
2243
|
+
if (!isPromiseLike(iterationP)) {
|
|
2244
|
+
// if iterationP is an iteration and not a promise, the component was
|
|
2245
|
+
// not in a for await...of loop when the iteration started, so we can
|
|
2246
|
+
// skip the diffing of children as it is handled elsewhere.
|
|
2247
|
+
diff = undefined;
|
|
2248
|
+
}
|
|
2249
|
+
else if (!getFlag(ctx.ret, NeedsToYield) &&
|
|
2250
|
+
getFlag(ctx.ret, PropsAvailable) &&
|
|
2251
|
+
getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
2252
|
+
// logic to skip yielded children in a stale for await of iteration.
|
|
2253
|
+
diff = undefined;
|
|
1713
2254
|
}
|
|
1714
2255
|
else {
|
|
1715
|
-
|
|
1716
|
-
hydrationData = undefined;
|
|
1717
|
-
if (isPromiseLike(value)) {
|
|
1718
|
-
value = value.catch((err) => handleChildError(ctx, err));
|
|
1719
|
-
}
|
|
2256
|
+
diff = diffComponentChildren(ctx, iteration.value, !iteration.done);
|
|
1720
2257
|
}
|
|
1721
|
-
ctx.f &= ~NeedsToYield;
|
|
1722
2258
|
}
|
|
1723
2259
|
catch (err) {
|
|
1724
|
-
|
|
1725
|
-
// promise rejections?
|
|
1726
|
-
value = handleChildError(ctx, err);
|
|
2260
|
+
onDiff(Promise.reject(err));
|
|
1727
2261
|
}
|
|
1728
2262
|
finally {
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
if (ctx.ret
|
|
1733
|
-
//
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
if (ctx.f & IsUpdating) {
|
|
1741
|
-
return;
|
|
2263
|
+
onDiff(diff);
|
|
2264
|
+
setFlag(ctx.ret, NeedsToYield, false);
|
|
2265
|
+
}
|
|
2266
|
+
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
2267
|
+
// TODO: move this unmounted branch outside the loop
|
|
2268
|
+
while ((!iteration || !iteration.done) &&
|
|
2269
|
+
ctx.iterator &&
|
|
2270
|
+
getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
2271
|
+
try {
|
|
2272
|
+
setFlag(ctx.ret, IsExecuting);
|
|
2273
|
+
iteration = await ctx.iterator.next(oldResult);
|
|
1742
2274
|
}
|
|
1743
|
-
|
|
2275
|
+
catch (err) {
|
|
2276
|
+
setFlag(ctx.ret, IsErrored);
|
|
2277
|
+
// we throw the error here to cause an unhandled rejection because
|
|
2278
|
+
// the promise returned from pullComponent is never awaited
|
|
1744
2279
|
throw err;
|
|
1745
2280
|
}
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
if (ctx.f & IsInForAwaitOfLoop) {
|
|
2281
|
+
finally {
|
|
2282
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
if ((!iteration || !iteration.done) &&
|
|
2286
|
+
ctx.iterator &&
|
|
2287
|
+
typeof ctx.iterator.return === "function") {
|
|
1754
2288
|
try {
|
|
1755
|
-
ctx.
|
|
1756
|
-
|
|
2289
|
+
setFlag(ctx.ret, IsExecuting);
|
|
2290
|
+
await ctx.iterator.return();
|
|
2291
|
+
}
|
|
2292
|
+
catch (err) {
|
|
2293
|
+
setFlag(ctx.ret, IsErrored);
|
|
2294
|
+
throw err;
|
|
1757
2295
|
}
|
|
1758
2296
|
finally {
|
|
1759
|
-
ctx.
|
|
2297
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1760
2298
|
}
|
|
1761
2299
|
}
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
2300
|
+
break;
|
|
2301
|
+
}
|
|
2302
|
+
else if (!getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
2303
|
+
// we have exited the for...await of, so updates will be handled by the
|
|
2304
|
+
// regular runComponent/enqueueComponent logic.
|
|
2305
|
+
break;
|
|
1766
2306
|
}
|
|
1767
|
-
else if (!done
|
|
2307
|
+
else if (!iteration.done) {
|
|
1768
2308
|
try {
|
|
1769
|
-
ctx.
|
|
2309
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1770
2310
|
iterationP = ctx.iterator.next(oldResult);
|
|
1771
2311
|
}
|
|
1772
2312
|
finally {
|
|
1773
|
-
ctx.
|
|
2313
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1774
2314
|
}
|
|
1775
2315
|
}
|
|
1776
|
-
initial = false;
|
|
1777
2316
|
}
|
|
1778
2317
|
}
|
|
1779
2318
|
finally {
|
|
1780
2319
|
if (done) {
|
|
1781
|
-
ctx.
|
|
2320
|
+
setFlag(ctx.ret, IsAsyncGen, false);
|
|
1782
2321
|
ctx.iterator = undefined;
|
|
1783
2322
|
}
|
|
2323
|
+
ctx.pull = undefined;
|
|
1784
2324
|
}
|
|
1785
2325
|
}
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
ctx.
|
|
1793
|
-
|
|
2326
|
+
function commitComponent(ctx, schedulePromises, hydrationNodes) {
|
|
2327
|
+
if (ctx.schedule) {
|
|
2328
|
+
ctx.schedule.promise.then(() => {
|
|
2329
|
+
commitComponent(ctx, []);
|
|
2330
|
+
propagateComponent(ctx);
|
|
2331
|
+
});
|
|
2332
|
+
return getValue(ctx.ret);
|
|
2333
|
+
}
|
|
2334
|
+
const wasScheduling = getFlag(ctx.ret, IsScheduling);
|
|
2335
|
+
const values = commitChildren(ctx.adapter, ctx.host, ctx, ctx.scope, ctx.ret, ctx.index, schedulePromises, hydrationNodes);
|
|
2336
|
+
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
addEventTargetDelegates(ctx.ctx, values);
|
|
2340
|
+
// Execute schedule callbacks early to check for async deferral
|
|
2341
|
+
const callbacks = scheduleMap.get(ctx);
|
|
2342
|
+
let schedulePromises1;
|
|
2343
|
+
if (callbacks) {
|
|
2344
|
+
scheduleMap.delete(ctx);
|
|
2345
|
+
// TODO: think about error handling for schedule callbacks
|
|
2346
|
+
setFlag(ctx.ret, IsScheduling);
|
|
2347
|
+
const result = ctx.adapter.read(unwrap(values));
|
|
2348
|
+
for (const callback of callbacks) {
|
|
2349
|
+
const scheduleResult = callback(result);
|
|
2350
|
+
if (isPromiseLike(scheduleResult)) {
|
|
2351
|
+
(schedulePromises1 = schedulePromises1 || []).push(scheduleResult);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
if (schedulePromises1 && !getFlag(ctx.ret, DidCommit)) {
|
|
2355
|
+
const scheduleCallbacksP = Promise.all(schedulePromises1).then(() => {
|
|
2356
|
+
setFlag(ctx.ret, IsScheduling, false);
|
|
2357
|
+
propagateComponent(ctx);
|
|
2358
|
+
if (ctx.ret.fallback) {
|
|
2359
|
+
unmount(ctx.adapter, ctx.host, ctx.parent, ctx.ret.fallback, false);
|
|
2360
|
+
}
|
|
2361
|
+
ctx.ret.fallback = undefined;
|
|
2362
|
+
});
|
|
2363
|
+
let onAbort;
|
|
2364
|
+
const scheduleP = safeRace([
|
|
2365
|
+
scheduleCallbacksP,
|
|
2366
|
+
new Promise((resolve) => (onAbort = resolve)),
|
|
2367
|
+
]).finally(() => {
|
|
2368
|
+
ctx.schedule = undefined;
|
|
2369
|
+
});
|
|
2370
|
+
ctx.schedule = { promise: scheduleP, onAbort };
|
|
2371
|
+
schedulePromises.push(scheduleP);
|
|
2372
|
+
}
|
|
2373
|
+
else {
|
|
2374
|
+
setFlag(ctx.ret, IsScheduling, wasScheduling);
|
|
2375
|
+
}
|
|
1794
2376
|
}
|
|
1795
2377
|
else {
|
|
1796
|
-
ctx.
|
|
2378
|
+
setFlag(ctx.ret, IsScheduling, wasScheduling);
|
|
2379
|
+
}
|
|
2380
|
+
if (!getFlag(ctx.ret, IsScheduling)) {
|
|
2381
|
+
if (!getFlag(ctx.ret, IsUpdating)) {
|
|
2382
|
+
propagateComponent(ctx);
|
|
2383
|
+
}
|
|
2384
|
+
if (ctx.ret.fallback) {
|
|
2385
|
+
unmount(ctx.adapter, ctx.host, ctx.parent, ctx.ret.fallback, false);
|
|
2386
|
+
}
|
|
2387
|
+
ctx.ret.fallback = undefined;
|
|
2388
|
+
setFlag(ctx.ret, IsUpdating, false);
|
|
1797
2389
|
}
|
|
2390
|
+
setFlag(ctx.ret, DidCommit);
|
|
2391
|
+
// We always use getValue() instead of the unwrapping values because there
|
|
2392
|
+
// are various ways in which the values could have been updated, especially
|
|
2393
|
+
// if schedule callbacks call refresh() or async mounting is happening.
|
|
2394
|
+
return getValue(ctx.ret, true);
|
|
1798
2395
|
}
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
2396
|
+
/**
|
|
2397
|
+
* Propagates component changes up to ancestors when rendering starts from a
|
|
2398
|
+
* component via refresh() or multiple for await...of renders. This handles
|
|
2399
|
+
* event listeners and DOM arrangement that would normally happen during
|
|
2400
|
+
* top-down rendering.
|
|
2401
|
+
*/
|
|
2402
|
+
function propagateComponent(ctx) {
|
|
2403
|
+
const values = getChildValues(ctx.ret, ctx.index);
|
|
2404
|
+
addEventTargetDelegates(ctx.ctx, values, (ctx1) => ctx1[_ContextState].host === ctx.host);
|
|
2405
|
+
const host = ctx.host;
|
|
2406
|
+
const props = stripSpecialProps(host.el.props);
|
|
2407
|
+
ctx.adapter.arrange({
|
|
2408
|
+
tag: host.el.tag,
|
|
2409
|
+
tagName: getTagName(host.el.tag),
|
|
2410
|
+
node: host.value,
|
|
2411
|
+
props,
|
|
2412
|
+
oldProps: props,
|
|
2413
|
+
children: getChildValues(host, 0),
|
|
2414
|
+
});
|
|
2415
|
+
flush(ctx.adapter, ctx.root, ctx);
|
|
2416
|
+
}
|
|
2417
|
+
async function unmountComponent(ctx, isNested) {
|
|
2418
|
+
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
1802
2419
|
return;
|
|
1803
2420
|
}
|
|
1804
|
-
|
|
2421
|
+
let cleanupPromises;
|
|
2422
|
+
// TODO: think about errror handling for callbacks
|
|
1805
2423
|
const callbacks = cleanupMap.get(ctx);
|
|
1806
2424
|
if (callbacks) {
|
|
2425
|
+
const oldResult = ctx.adapter.read(getValue(ctx.ret));
|
|
1807
2426
|
cleanupMap.delete(ctx);
|
|
1808
|
-
const value = ctx.renderer.read(getValue(ctx.ret));
|
|
1809
2427
|
for (const callback of callbacks) {
|
|
1810
|
-
callback(
|
|
2428
|
+
const cleanup = callback(oldResult);
|
|
2429
|
+
if (isPromiseLike(cleanup)) {
|
|
2430
|
+
(cleanupPromises = cleanupPromises || []).push(cleanup);
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
let didLinger = false;
|
|
2435
|
+
if (!isNested && cleanupPromises && getChildValues(ctx.ret).length > 0) {
|
|
2436
|
+
didLinger = true;
|
|
2437
|
+
const index = ctx.index;
|
|
2438
|
+
const lingerers = ctx.host.lingerers || (ctx.host.lingerers = []);
|
|
2439
|
+
let set = lingerers[index];
|
|
2440
|
+
if (set == null) {
|
|
2441
|
+
set = new Set();
|
|
2442
|
+
lingerers[index] = set;
|
|
2443
|
+
}
|
|
2444
|
+
set.add(ctx.ret);
|
|
2445
|
+
await Promise.all(cleanupPromises);
|
|
2446
|
+
set.delete(ctx.ret);
|
|
2447
|
+
if (set.size === 0) {
|
|
2448
|
+
lingerers[index] = undefined;
|
|
2449
|
+
}
|
|
2450
|
+
if (!lingerers.some(Boolean)) {
|
|
2451
|
+
// If there are no lingerers remaining, we can remove the lingerers array
|
|
2452
|
+
ctx.host.lingerers = undefined;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
2456
|
+
// If the component was unmounted while awaiting the cleanup callbacks,
|
|
2457
|
+
// we do not need to continue unmounting.
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
setFlag(ctx.ret, IsUnmounted);
|
|
2461
|
+
// If component has pending schedule promises, resolve them since component
|
|
2462
|
+
// is unmounting
|
|
2463
|
+
if (ctx.schedule) {
|
|
2464
|
+
ctx.schedule.onAbort();
|
|
2465
|
+
ctx.schedule = undefined;
|
|
2466
|
+
}
|
|
2467
|
+
clearEventListeners(ctx.ctx);
|
|
2468
|
+
unmountChildren(ctx.adapter, ctx.host, ctx, ctx.ret, isNested);
|
|
2469
|
+
if (didLinger) {
|
|
2470
|
+
// If we lingered, we call finalize to ensure rendering is finalized
|
|
2471
|
+
if (ctx.root != null) {
|
|
2472
|
+
ctx.adapter.finalize(ctx.root);
|
|
1811
2473
|
}
|
|
1812
2474
|
}
|
|
1813
|
-
ctx.f |= IsUnmounted;
|
|
1814
2475
|
if (ctx.iterator) {
|
|
1815
|
-
if (ctx.
|
|
1816
|
-
let
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
2476
|
+
if (ctx.pull) {
|
|
2477
|
+
// we let pullComponent handle unmounting
|
|
2478
|
+
resumePropsAsyncIterator(ctx);
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
// we wait for inflight value so yields resume with the most up to date
|
|
2482
|
+
// props
|
|
2483
|
+
if (ctx.inflight) {
|
|
2484
|
+
await ctx.inflight[1];
|
|
2485
|
+
}
|
|
2486
|
+
let iteration;
|
|
2487
|
+
if (getFlag(ctx.ret, IsInForOfLoop)) {
|
|
2488
|
+
try {
|
|
2489
|
+
setFlag(ctx.ret, IsExecuting);
|
|
2490
|
+
const oldResult = ctx.adapter.read(getValue(ctx.ret));
|
|
2491
|
+
const iterationP = ctx.iterator.next(oldResult);
|
|
2492
|
+
if (isPromiseLike(iterationP)) {
|
|
2493
|
+
if (!getFlag(ctx.ret, IsAsyncGen)) {
|
|
2494
|
+
throw new Error("Mixed generator component");
|
|
1831
2495
|
}
|
|
1832
|
-
|
|
1833
|
-
});
|
|
1834
|
-
}
|
|
1835
|
-
else {
|
|
1836
|
-
if (ctx.f & IsInForOfLoop) {
|
|
1837
|
-
unmountComponent(ctx);
|
|
2496
|
+
iteration = await iterationP;
|
|
1838
2497
|
}
|
|
1839
2498
|
else {
|
|
1840
|
-
|
|
2499
|
+
if (!getFlag(ctx.ret, IsSyncGen)) {
|
|
2500
|
+
throw new Error("Mixed generator component");
|
|
2501
|
+
}
|
|
2502
|
+
iteration = iterationP;
|
|
1841
2503
|
}
|
|
1842
2504
|
}
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
const value = enqueueComponentRun(ctx);
|
|
1847
|
-
value.then(() => {
|
|
1848
|
-
if (ctx.f & IsInForOfLoop) {
|
|
1849
|
-
unmountComponent(ctx);
|
|
1850
|
-
}
|
|
1851
|
-
else {
|
|
1852
|
-
returnComponent(ctx);
|
|
1853
|
-
}
|
|
1854
|
-
}, (err) => {
|
|
1855
|
-
if (!ctx.parent) {
|
|
1856
|
-
throw err;
|
|
1857
|
-
}
|
|
1858
|
-
return propagateError(ctx.parent, err);
|
|
1859
|
-
});
|
|
2505
|
+
catch (err) {
|
|
2506
|
+
setFlag(ctx.ret, IsErrored);
|
|
2507
|
+
throw err;
|
|
1860
2508
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
// runAsyncGenComponent function.
|
|
1864
|
-
resumePropsAsyncIterator(ctx);
|
|
2509
|
+
finally {
|
|
2510
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1865
2511
|
}
|
|
1866
2512
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
iteration.catch((err) => {
|
|
1877
|
-
if (!ctx.parent) {
|
|
1878
|
-
throw err;
|
|
2513
|
+
if ((!iteration || !iteration.done) &&
|
|
2514
|
+
ctx.iterator &&
|
|
2515
|
+
typeof ctx.iterator.return === "function") {
|
|
2516
|
+
try {
|
|
2517
|
+
setFlag(ctx.ret, IsExecuting);
|
|
2518
|
+
const iterationP = ctx.iterator.return();
|
|
2519
|
+
if (isPromiseLike(iterationP)) {
|
|
2520
|
+
if (!getFlag(ctx.ret, IsAsyncGen)) {
|
|
2521
|
+
throw new Error("Mixed generator component");
|
|
1879
2522
|
}
|
|
1880
|
-
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
/*** EVENT TARGET UTILITIES ***/
|
|
1890
|
-
// EVENT PHASE CONSTANTS
|
|
1891
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
|
|
1892
|
-
const NONE = 0;
|
|
1893
|
-
const CAPTURING_PHASE = 1;
|
|
1894
|
-
const AT_TARGET = 2;
|
|
1895
|
-
const BUBBLING_PHASE = 3;
|
|
1896
|
-
const listenersMap = new WeakMap();
|
|
1897
|
-
function isListenerOrListenerObject(value) {
|
|
1898
|
-
return (typeof value === "function" ||
|
|
1899
|
-
(value !== null &&
|
|
1900
|
-
typeof value === "object" &&
|
|
1901
|
-
typeof value.handleEvent === "function"));
|
|
1902
|
-
}
|
|
1903
|
-
function normalizeListenerOptions(options) {
|
|
1904
|
-
if (typeof options === "boolean") {
|
|
1905
|
-
return { capture: options };
|
|
1906
|
-
}
|
|
1907
|
-
else if (options == null) {
|
|
1908
|
-
return {};
|
|
1909
|
-
}
|
|
1910
|
-
return options;
|
|
1911
|
-
}
|
|
1912
|
-
function isEventTarget(value) {
|
|
1913
|
-
return (value != null &&
|
|
1914
|
-
typeof value.addEventListener === "function" &&
|
|
1915
|
-
typeof value.removeEventListener === "function" &&
|
|
1916
|
-
typeof value.dispatchEvent === "function");
|
|
1917
|
-
}
|
|
1918
|
-
function setEventProperty(ev, key, value) {
|
|
1919
|
-
Object.defineProperty(ev, key, { value, writable: false, configurable: true });
|
|
1920
|
-
}
|
|
1921
|
-
// TODO: Maybe we can pass in the current context directly, rather than
|
|
1922
|
-
// starting from the parent?
|
|
1923
|
-
/**
|
|
1924
|
-
* A function to reconstruct an array of every listener given a context and a
|
|
1925
|
-
* host element.
|
|
1926
|
-
*
|
|
1927
|
-
* This function exploits the fact that contexts retain their nearest ancestor
|
|
1928
|
-
* host element. We can determine all the contexts which are directly listening
|
|
1929
|
-
* to an element by traversing up the context tree and checking that the host
|
|
1930
|
-
* element passed in matches the parent context’s host element.
|
|
1931
|
-
*/
|
|
1932
|
-
function getListenerRecords(ctx, ret) {
|
|
1933
|
-
let listeners = [];
|
|
1934
|
-
while (ctx !== undefined && ctx.host === ret) {
|
|
1935
|
-
const listeners1 = listenersMap.get(ctx);
|
|
1936
|
-
if (listeners1) {
|
|
1937
|
-
listeners = listeners.concat(listeners1);
|
|
1938
|
-
}
|
|
1939
|
-
ctx = ctx.parent;
|
|
1940
|
-
}
|
|
1941
|
-
return listeners;
|
|
1942
|
-
}
|
|
1943
|
-
function clearEventListeners(ctx) {
|
|
1944
|
-
const listeners = listenersMap.get(ctx);
|
|
1945
|
-
if (listeners && listeners.length) {
|
|
1946
|
-
for (const value of getChildValues(ctx.ret)) {
|
|
1947
|
-
if (isEventTarget(value)) {
|
|
1948
|
-
for (const record of listeners) {
|
|
1949
|
-
value.removeEventListener(record.type, record.callback, record.options);
|
|
2523
|
+
iteration = await iterationP;
|
|
2524
|
+
}
|
|
2525
|
+
else {
|
|
2526
|
+
if (!getFlag(ctx.ret, IsSyncGen)) {
|
|
2527
|
+
throw new Error("Mixed generator component");
|
|
2528
|
+
}
|
|
2529
|
+
iteration = iterationP;
|
|
1950
2530
|
}
|
|
1951
2531
|
}
|
|
2532
|
+
catch (err) {
|
|
2533
|
+
setFlag(ctx.ret, IsErrored);
|
|
2534
|
+
throw err;
|
|
2535
|
+
}
|
|
2536
|
+
finally {
|
|
2537
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
2538
|
+
}
|
|
1952
2539
|
}
|
|
1953
|
-
listeners.length = 0;
|
|
1954
2540
|
}
|
|
1955
2541
|
}
|
|
1956
2542
|
/*** ERROR HANDLING UTILITIES ***/
|
|
1957
2543
|
function handleChildError(ctx, err) {
|
|
1958
|
-
if (!ctx.iterator
|
|
2544
|
+
if (!ctx.iterator) {
|
|
2545
|
+
throw err;
|
|
2546
|
+
}
|
|
2547
|
+
if (ctx.pull) {
|
|
2548
|
+
// we let pullComponent handle child errors
|
|
2549
|
+
ctx.pull.onChildError(err);
|
|
2550
|
+
return ctx.pull.diff;
|
|
2551
|
+
}
|
|
2552
|
+
if (!ctx.iterator.throw) {
|
|
1959
2553
|
throw err;
|
|
1960
2554
|
}
|
|
1961
2555
|
resumePropsAsyncIterator(ctx);
|
|
1962
2556
|
let iteration;
|
|
1963
2557
|
try {
|
|
1964
|
-
ctx.
|
|
2558
|
+
setFlag(ctx.ret, IsExecuting);
|
|
1965
2559
|
iteration = ctx.iterator.throw(err);
|
|
1966
2560
|
}
|
|
1967
2561
|
catch (err) {
|
|
1968
|
-
ctx.
|
|
2562
|
+
setFlag(ctx.ret, IsErrored);
|
|
1969
2563
|
throw err;
|
|
1970
2564
|
}
|
|
1971
2565
|
finally {
|
|
1972
|
-
ctx.
|
|
2566
|
+
setFlag(ctx.ret, IsExecuting, false);
|
|
1973
2567
|
}
|
|
1974
2568
|
if (isPromiseLike(iteration)) {
|
|
1975
2569
|
return iteration.then((iteration) => {
|
|
1976
2570
|
if (iteration.done) {
|
|
1977
|
-
ctx.
|
|
2571
|
+
setFlag(ctx.ret, IsSyncGen, false);
|
|
2572
|
+
setFlag(ctx.ret, IsAsyncGen, false);
|
|
1978
2573
|
ctx.iterator = undefined;
|
|
1979
2574
|
}
|
|
1980
|
-
return
|
|
2575
|
+
return diffComponentChildren(ctx, iteration.value, !iteration.done);
|
|
1981
2576
|
}, (err) => {
|
|
1982
|
-
ctx.
|
|
2577
|
+
setFlag(ctx.ret, IsErrored);
|
|
1983
2578
|
throw err;
|
|
1984
2579
|
});
|
|
1985
2580
|
}
|
|
1986
2581
|
if (iteration.done) {
|
|
1987
|
-
ctx.
|
|
1988
|
-
ctx.
|
|
2582
|
+
setFlag(ctx.ret, IsSyncGen, false);
|
|
2583
|
+
setFlag(ctx.ret, IsAsyncGen, false);
|
|
1989
2584
|
ctx.iterator = undefined;
|
|
1990
2585
|
}
|
|
1991
|
-
return
|
|
2586
|
+
return diffComponentChildren(ctx, iteration.value, !iteration.done);
|
|
1992
2587
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
2588
|
+
/**
|
|
2589
|
+
* Propagates an error up the context tree by calling handleChildError with
|
|
2590
|
+
* each parent.
|
|
2591
|
+
*
|
|
2592
|
+
* @returns A promise which resolves to undefined when the error has been
|
|
2593
|
+
* handled, or undefined if the error was handled synchronously.
|
|
2594
|
+
*/
|
|
2595
|
+
function propagateError(ctx, err, schedulePromises) {
|
|
2596
|
+
const parent = ctx.parent;
|
|
2597
|
+
if (!parent) {
|
|
2598
|
+
throw err;
|
|
2599
|
+
}
|
|
2600
|
+
let diff;
|
|
1995
2601
|
try {
|
|
1996
|
-
|
|
2602
|
+
diff = handleChildError(parent, err);
|
|
1997
2603
|
}
|
|
1998
2604
|
catch (err) {
|
|
1999
|
-
|
|
2000
|
-
throw err;
|
|
2001
|
-
}
|
|
2002
|
-
return propagateError(ctx.parent, err);
|
|
2605
|
+
return propagateError(parent, err, schedulePromises);
|
|
2003
2606
|
}
|
|
2004
|
-
if (isPromiseLike(
|
|
2005
|
-
return
|
|
2006
|
-
if (!ctx.parent) {
|
|
2007
|
-
throw err;
|
|
2008
|
-
}
|
|
2009
|
-
return propagateError(ctx.parent, err);
|
|
2010
|
-
});
|
|
2607
|
+
if (isPromiseLike(diff)) {
|
|
2608
|
+
return diff.then(() => void commitComponent(parent, schedulePromises), (err) => propagateError(parent, err, schedulePromises));
|
|
2011
2609
|
}
|
|
2012
|
-
|
|
2610
|
+
commitComponent(parent, schedulePromises);
|
|
2013
2611
|
}
|
|
2014
2612
|
|
|
2015
2613
|
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
2016
|
-
|
|
2017
|
-
|
|
2614
|
+
function isWritableProperty(element, name) {
|
|
2615
|
+
// walk up the object's prototype chain to find the owner
|
|
2616
|
+
let propOwner = element;
|
|
2617
|
+
do {
|
|
2618
|
+
if (Object.prototype.hasOwnProperty.call(propOwner, name)) {
|
|
2619
|
+
break;
|
|
2620
|
+
}
|
|
2621
|
+
} while ((propOwner = Object.getPrototypeOf(propOwner)));
|
|
2622
|
+
if (propOwner === null) {
|
|
2623
|
+
return false;
|
|
2624
|
+
}
|
|
2625
|
+
// get the descriptor for the named property and check whether it implies
|
|
2626
|
+
// that the property is writable
|
|
2627
|
+
const descriptor = Object.getOwnPropertyDescriptor(propOwner, name);
|
|
2628
|
+
if (descriptor != null &&
|
|
2629
|
+
(descriptor.writable === true || descriptor.set !== undefined)) {
|
|
2630
|
+
return true;
|
|
2631
|
+
}
|
|
2632
|
+
return false;
|
|
2633
|
+
}
|
|
2634
|
+
function emitHydrationWarning(propName, quietProps, expectedValue, actualValue, element, displayName) {
|
|
2635
|
+
const checkName = propName;
|
|
2636
|
+
const showName = displayName || propName;
|
|
2637
|
+
if (!quietProps || !quietProps.has(checkName)) {
|
|
2638
|
+
if (expectedValue === null || expectedValue === false) {
|
|
2639
|
+
console.warn(`Expected "${showName}" to be missing but found ${String(actualValue)} while hydrating:`, element);
|
|
2640
|
+
}
|
|
2641
|
+
else if (expectedValue === true || expectedValue === "") {
|
|
2642
|
+
console.warn(`Expected "${showName}" to be ${expectedValue === true ? "present" : '""'} but found ${String(actualValue)} while hydrating:`, element);
|
|
2643
|
+
}
|
|
2644
|
+
else if (typeof window !== "undefined" &&
|
|
2645
|
+
window.location &&
|
|
2646
|
+
new URL(expectedValue, window.location.origin).href ===
|
|
2647
|
+
new URL(actualValue, window.location.origin).href) ;
|
|
2648
|
+
else {
|
|
2649
|
+
console.warn(`Expected "${showName}" to be "${String(expectedValue)}" but found ${String(actualValue)} while hydrating:`, element);
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
const adapter = {
|
|
2654
|
+
scope({ scope: xmlns, tag, props, }) {
|
|
2018
2655
|
switch (tag) {
|
|
2019
2656
|
case Portal:
|
|
2657
|
+
// TODO: read the namespace from the portal root element
|
|
2020
2658
|
xmlns = undefined;
|
|
2021
2659
|
break;
|
|
2022
2660
|
case "svg":
|
|
@@ -2025,9 +2663,9 @@
|
|
|
2025
2663
|
}
|
|
2026
2664
|
return props.xmlns || xmlns;
|
|
2027
2665
|
},
|
|
2028
|
-
create(tag,
|
|
2666
|
+
create({ tag, tagName, scope: xmlns, }) {
|
|
2029
2667
|
if (typeof tag !== "string") {
|
|
2030
|
-
throw new Error(`Unknown tag: ${
|
|
2668
|
+
throw new Error(`Unknown tag: ${tagName}`);
|
|
2031
2669
|
}
|
|
2032
2670
|
else if (tag.toLowerCase() === "svg") {
|
|
2033
2671
|
xmlns = SVG_NAMESPACE;
|
|
@@ -2036,262 +2674,362 @@
|
|
|
2036
2674
|
? document.createElementNS(xmlns, tag)
|
|
2037
2675
|
: document.createElement(tag);
|
|
2038
2676
|
},
|
|
2039
|
-
|
|
2677
|
+
adopt({ tag, tagName, node, }) {
|
|
2040
2678
|
if (typeof tag !== "string" && tag !== Portal) {
|
|
2041
|
-
throw new Error(`Unknown tag: ${
|
|
2679
|
+
throw new Error(`Unknown tag: ${tagName}`);
|
|
2680
|
+
}
|
|
2681
|
+
if (node === document.body ||
|
|
2682
|
+
node === document.head ||
|
|
2683
|
+
node === document.documentElement ||
|
|
2684
|
+
node === document) {
|
|
2685
|
+
console.warn(`Hydrating ${node.nodeName.toLowerCase()} is discouraged as it is destructive and may remove unknown nodes.`);
|
|
2686
|
+
}
|
|
2687
|
+
if (node == null ||
|
|
2688
|
+
(typeof tag === "string" &&
|
|
2689
|
+
(node.nodeType !== Node.ELEMENT_NODE ||
|
|
2690
|
+
tag.toLowerCase() !== node.tagName.toLowerCase()))) {
|
|
2691
|
+
console.warn(`Expected <${tagName}> while hydrating but found: `, node);
|
|
2692
|
+
return;
|
|
2042
2693
|
}
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2694
|
+
return Array.from(node.childNodes);
|
|
2695
|
+
},
|
|
2696
|
+
patch({ tagName, node, props, oldProps, scope: xmlns, copyProps, quietProps, isHydrating, }) {
|
|
2697
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
2698
|
+
throw new TypeError(`Cannot patch node: ${String(node)}`);
|
|
2048
2699
|
}
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
const child = node.childNodes[i];
|
|
2052
|
-
if (child.nodeType === Node.TEXT_NODE) {
|
|
2053
|
-
children.push(child.data);
|
|
2054
|
-
}
|
|
2055
|
-
else if (child.nodeType === Node.ELEMENT_NODE) {
|
|
2056
|
-
children.push(child);
|
|
2057
|
-
}
|
|
2700
|
+
else if (props.class && props.className) {
|
|
2701
|
+
console.error(`Both "class" and "className" set in props for <${tagName}>. Use one or the other.`);
|
|
2058
2702
|
}
|
|
2059
|
-
|
|
2060
|
-
return { props, children };
|
|
2061
|
-
},
|
|
2062
|
-
patch(_tag,
|
|
2063
|
-
// TODO: Why does this assignment work?
|
|
2064
|
-
node, name,
|
|
2065
|
-
// TODO: Stricter typings?
|
|
2066
|
-
value, oldValue, xmlns) {
|
|
2703
|
+
const element = node;
|
|
2067
2704
|
const isSVG = xmlns === SVG_NAMESPACE;
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
else if (value == null || value === false) {
|
|
2075
|
-
node.removeAttribute("style");
|
|
2076
|
-
}
|
|
2077
|
-
else if (value === true) {
|
|
2078
|
-
node.setAttribute("style", "");
|
|
2705
|
+
for (let name in { ...oldProps, ...props }) {
|
|
2706
|
+
let value = props[name];
|
|
2707
|
+
const oldValue = oldProps ? oldProps[name] : undefined;
|
|
2708
|
+
{
|
|
2709
|
+
if (copyProps != null && copyProps.has(name)) {
|
|
2710
|
+
continue;
|
|
2079
2711
|
}
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2712
|
+
// handle prop:name or attr:name properties
|
|
2713
|
+
const colonIndex = name.indexOf(":");
|
|
2714
|
+
if (colonIndex !== -1) {
|
|
2715
|
+
const [ns, name1] = [
|
|
2716
|
+
name.slice(0, colonIndex),
|
|
2717
|
+
name.slice(colonIndex + 1),
|
|
2718
|
+
];
|
|
2719
|
+
switch (ns) {
|
|
2720
|
+
case "prop":
|
|
2721
|
+
node[name1] = value;
|
|
2722
|
+
continue;
|
|
2723
|
+
case "attr":
|
|
2724
|
+
if (value == null || value === false) {
|
|
2725
|
+
if (isHydrating && element.hasAttribute(name1)) {
|
|
2726
|
+
emitHydrationWarning(name, quietProps, value, element.getAttribute(name1), element);
|
|
2727
|
+
}
|
|
2728
|
+
element.removeAttribute(name1);
|
|
2729
|
+
}
|
|
2730
|
+
else if (value === true) {
|
|
2731
|
+
if (isHydrating && !element.hasAttribute(name1)) {
|
|
2732
|
+
emitHydrationWarning(name, quietProps, value, null, element);
|
|
2733
|
+
}
|
|
2734
|
+
element.setAttribute(name1, "");
|
|
2735
|
+
}
|
|
2736
|
+
else if (typeof value !== "string") {
|
|
2737
|
+
value = String(value);
|
|
2738
|
+
}
|
|
2739
|
+
if (isHydrating && element.getAttribute(name1) !== value) {
|
|
2740
|
+
emitHydrationWarning(name, quietProps, value, element.getAttribute(name1), element);
|
|
2741
|
+
}
|
|
2742
|
+
element.setAttribute(name1, String(value));
|
|
2743
|
+
continue;
|
|
2083
2744
|
}
|
|
2084
2745
|
}
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2746
|
+
}
|
|
2747
|
+
switch (name) {
|
|
2748
|
+
// TODO: fix hydration warnings for the style prop
|
|
2749
|
+
case "style": {
|
|
2750
|
+
const style = element.style;
|
|
2751
|
+
if (value == null || value === false) {
|
|
2752
|
+
if (isHydrating && style.cssText !== "") {
|
|
2753
|
+
emitHydrationWarning(name, quietProps, value, style.cssText, element);
|
|
2754
|
+
}
|
|
2755
|
+
element.removeAttribute("style");
|
|
2088
2756
|
}
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
style.removeProperty(styleName);
|
|
2757
|
+
else if (value === true) {
|
|
2758
|
+
if (isHydrating && style.cssText !== "") {
|
|
2759
|
+
emitHydrationWarning(name, quietProps, "", style.cssText, element);
|
|
2093
2760
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2761
|
+
element.setAttribute("style", "");
|
|
2762
|
+
}
|
|
2763
|
+
else if (typeof value === "string") {
|
|
2764
|
+
if (style.cssText !== value) {
|
|
2765
|
+
// TODO: Fix hydration warnings for styles
|
|
2766
|
+
//if (isHydrating) {
|
|
2767
|
+
// emitHydrationWarning(
|
|
2768
|
+
// name,
|
|
2769
|
+
// quietProps,
|
|
2770
|
+
// value,
|
|
2771
|
+
// style.cssText,
|
|
2772
|
+
// element,
|
|
2773
|
+
// );
|
|
2774
|
+
//}
|
|
2775
|
+
style.cssText = value;
|
|
2096
2776
|
}
|
|
2097
2777
|
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2778
|
+
else {
|
|
2779
|
+
if (typeof oldValue === "string") {
|
|
2780
|
+
// if the old value was a string, we need to clear the style
|
|
2781
|
+
// TODO: only clear the styles enumerated in the old value
|
|
2782
|
+
style.cssText = "";
|
|
2783
|
+
}
|
|
2784
|
+
for (const styleName in { ...oldValue, ...value }) {
|
|
2785
|
+
const styleValue = value && value[styleName];
|
|
2786
|
+
if (styleValue == null) {
|
|
2787
|
+
if (isHydrating && style.getPropertyValue(styleName) !== "") {
|
|
2788
|
+
emitHydrationWarning(name, quietProps, null, style.getPropertyValue(styleName), element, `style.${styleName}`);
|
|
2789
|
+
}
|
|
2790
|
+
style.removeProperty(styleName);
|
|
2791
|
+
}
|
|
2792
|
+
else if (style.getPropertyValue(styleName) !== styleValue) {
|
|
2793
|
+
// TODO: hydration warnings for style props
|
|
2794
|
+
//if (isHydrating) {
|
|
2795
|
+
// emitHydrationWarning(
|
|
2796
|
+
// name,
|
|
2797
|
+
// quietProps,
|
|
2798
|
+
// styleValue,
|
|
2799
|
+
// style.getPropertyValue(styleName),
|
|
2800
|
+
// element,
|
|
2801
|
+
// `style.${styleName}`,
|
|
2802
|
+
// );
|
|
2803
|
+
//}
|
|
2804
|
+
style.setProperty(styleName, styleValue);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2112
2807
|
}
|
|
2808
|
+
break;
|
|
2113
2809
|
}
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
if (value !== oldValue) {
|
|
2120
|
-
node.innerHTML = value;
|
|
2121
|
-
}
|
|
2122
|
-
break;
|
|
2123
|
-
default: {
|
|
2124
|
-
if (name[0] === "o" &&
|
|
2125
|
-
name[1] === "n" &&
|
|
2126
|
-
name[2] === name[2].toUpperCase() &&
|
|
2127
|
-
typeof value === "function") {
|
|
2128
|
-
// Support React-style event names (onClick, onChange, etc.)
|
|
2129
|
-
name = name.toLowerCase();
|
|
2130
|
-
}
|
|
2131
|
-
if (name in node &&
|
|
2132
|
-
// boolean properties will coerce strings, but sometimes they map to
|
|
2133
|
-
// enumerated attributes, where truthy strings ("false", "no") map to
|
|
2134
|
-
// falsy properties, so we use attributes in this case.
|
|
2135
|
-
!(typeof value === "string" &&
|
|
2136
|
-
typeof node[name] === "boolean")) {
|
|
2137
|
-
// walk up the object's prototype chain to find the owner of the
|
|
2138
|
-
// named property
|
|
2139
|
-
let obj = node;
|
|
2140
|
-
do {
|
|
2141
|
-
if (Object.prototype.hasOwnProperty.call(obj, name)) {
|
|
2142
|
-
break;
|
|
2810
|
+
case "class":
|
|
2811
|
+
case "className":
|
|
2812
|
+
if (value === true) {
|
|
2813
|
+
if (isHydrating && element.getAttribute("class") !== "") {
|
|
2814
|
+
emitHydrationWarning(name, quietProps, "", element.getAttribute("class"), element);
|
|
2143
2815
|
}
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
(descriptor.writable === true || descriptor.set !== undefined)) {
|
|
2150
|
-
if (node[name] !== value || oldValue === undefined) {
|
|
2151
|
-
node[name] = value;
|
|
2816
|
+
element.setAttribute("class", "");
|
|
2817
|
+
}
|
|
2818
|
+
else if (value == null) {
|
|
2819
|
+
if (isHydrating && element.hasAttribute("class")) {
|
|
2820
|
+
emitHydrationWarning(name, quietProps, value, element.getAttribute("class"), element);
|
|
2152
2821
|
}
|
|
2153
|
-
|
|
2822
|
+
element.removeAttribute("class");
|
|
2154
2823
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
while (oldChild !== null && i < children.length) {
|
|
2190
|
-
const newChild = children[i];
|
|
2191
|
-
if (oldChild === newChild) {
|
|
2192
|
-
oldChild = oldChild.nextSibling;
|
|
2193
|
-
i++;
|
|
2824
|
+
else if (typeof value === "object") {
|
|
2825
|
+
// class={{"included-class": true, "excluded-class": false}} syntax
|
|
2826
|
+
if (typeof oldValue === "string") {
|
|
2827
|
+
// if the old value was a string, we need to clear all classes
|
|
2828
|
+
element.setAttribute("class", "");
|
|
2829
|
+
}
|
|
2830
|
+
let shouldIssueWarning = false;
|
|
2831
|
+
const hydratingClasses = isHydrating
|
|
2832
|
+
? new Set(Array.from(element.classList))
|
|
2833
|
+
: undefined;
|
|
2834
|
+
const hydratingClassName = isHydrating
|
|
2835
|
+
? element.getAttribute("class")
|
|
2836
|
+
: undefined;
|
|
2837
|
+
for (const className in { ...oldValue, ...value }) {
|
|
2838
|
+
const classValue = value && value[className];
|
|
2839
|
+
if (classValue) {
|
|
2840
|
+
element.classList.add(className);
|
|
2841
|
+
if (hydratingClasses && hydratingClasses.has(className)) {
|
|
2842
|
+
hydratingClasses.delete(className);
|
|
2843
|
+
}
|
|
2844
|
+
else if (isHydrating) {
|
|
2845
|
+
shouldIssueWarning = true;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
else {
|
|
2849
|
+
element.classList.remove(className);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
if (shouldIssueWarning ||
|
|
2853
|
+
(hydratingClasses && hydratingClasses.size > 0)) {
|
|
2854
|
+
emitHydrationWarning(name, quietProps, Object.keys(value)
|
|
2855
|
+
.filter((k) => value[k])
|
|
2856
|
+
.join(" "), hydratingClassName || "", element);
|
|
2857
|
+
}
|
|
2194
2858
|
}
|
|
2195
|
-
else if (
|
|
2196
|
-
if (
|
|
2197
|
-
if (
|
|
2198
|
-
|
|
2859
|
+
else if (!isSVG) {
|
|
2860
|
+
if (element.className !== value) {
|
|
2861
|
+
if (isHydrating) {
|
|
2862
|
+
emitHydrationWarning(name, quietProps, value, element.className, element);
|
|
2199
2863
|
}
|
|
2200
|
-
|
|
2864
|
+
element.className = value;
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
else if (element.getAttribute("class") !== value) {
|
|
2868
|
+
if (isHydrating) {
|
|
2869
|
+
emitHydrationWarning(name, quietProps, value, element.getAttribute("class"), element);
|
|
2201
2870
|
}
|
|
2202
|
-
|
|
2203
|
-
|
|
2871
|
+
element.setAttribute("class", value);
|
|
2872
|
+
}
|
|
2873
|
+
break;
|
|
2874
|
+
case "innerHTML":
|
|
2875
|
+
if (value !== oldValue) {
|
|
2876
|
+
if (isHydrating) {
|
|
2877
|
+
emitHydrationWarning(name, quietProps, value, element.innerHTML, element);
|
|
2204
2878
|
}
|
|
2205
|
-
|
|
2879
|
+
element.innerHTML = value;
|
|
2206
2880
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2881
|
+
break;
|
|
2882
|
+
default: {
|
|
2883
|
+
if (name[0] === "o" &&
|
|
2884
|
+
name[1] === "n" &&
|
|
2885
|
+
name[2] === name[2].toUpperCase() &&
|
|
2886
|
+
typeof value === "function") {
|
|
2887
|
+
// Support React-style event names (onClick, onChange, etc.)
|
|
2888
|
+
name = name.toLowerCase();
|
|
2211
2889
|
}
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
//
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2890
|
+
// try to set the property directly
|
|
2891
|
+
if (name in element &&
|
|
2892
|
+
// boolean properties will coerce strings, but sometimes they map to
|
|
2893
|
+
// enumerated attributes, where truthy strings ("false", "no") map to
|
|
2894
|
+
// falsy properties, so we force using setAttribute.
|
|
2895
|
+
!(typeof value === "string" &&
|
|
2896
|
+
typeof element[name] === "boolean") &&
|
|
2897
|
+
isWritableProperty(element, name)) {
|
|
2898
|
+
if (element[name] !== value || oldValue === undefined) {
|
|
2899
|
+
if (isHydrating &&
|
|
2900
|
+
typeof element[name] === "string" &&
|
|
2901
|
+
element[name] !== value) {
|
|
2902
|
+
emitHydrationWarning(name, quietProps, value, element[name], element);
|
|
2903
|
+
}
|
|
2904
|
+
// if the property is writable, assign it directly
|
|
2905
|
+
element[name] = value;
|
|
2906
|
+
}
|
|
2907
|
+
continue;
|
|
2908
|
+
}
|
|
2909
|
+
if (value === true) {
|
|
2910
|
+
value = "";
|
|
2911
|
+
}
|
|
2912
|
+
else if (value == null || value === false) {
|
|
2913
|
+
if (isHydrating && element.hasAttribute(name)) {
|
|
2914
|
+
emitHydrationWarning(name, quietProps, value, element.getAttribute(name), element);
|
|
2220
2915
|
}
|
|
2916
|
+
element.removeAttribute(name);
|
|
2917
|
+
continue;
|
|
2918
|
+
}
|
|
2919
|
+
else if (typeof value !== "string") {
|
|
2920
|
+
value = String(value);
|
|
2921
|
+
}
|
|
2922
|
+
if (element.getAttribute(name) !== value) {
|
|
2923
|
+
if (isHydrating) {
|
|
2924
|
+
emitHydrationWarning(name, quietProps, value, element.getAttribute(name), element);
|
|
2925
|
+
}
|
|
2926
|
+
element.setAttribute(name, value);
|
|
2221
2927
|
}
|
|
2222
2928
|
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
},
|
|
2932
|
+
arrange({ tag, node, props, children, }) {
|
|
2933
|
+
if (tag === Portal && (node == null || typeof node.nodeType !== "number")) {
|
|
2934
|
+
throw new TypeError(`<Portal> root is not a node. Received: ${String(node)}`);
|
|
2935
|
+
}
|
|
2936
|
+
if (!("innerHTML" in props)) {
|
|
2937
|
+
let oldChild = node.firstChild;
|
|
2938
|
+
for (let i = 0; i < children.length; i++) {
|
|
2939
|
+
const newChild = children[i];
|
|
2940
|
+
if (oldChild === newChild) {
|
|
2941
|
+
// the child is already in the right place, so we can skip it
|
|
2942
|
+
oldChild = oldChild.nextSibling;
|
|
2228
2943
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2944
|
+
else {
|
|
2945
|
+
node.insertBefore(newChild, oldChild);
|
|
2946
|
+
if (tag !== Portal &&
|
|
2947
|
+
oldChild &&
|
|
2948
|
+
i + 1 < children.length &&
|
|
2949
|
+
oldChild !== children[i + 1]) {
|
|
2950
|
+
oldChild = oldChild.nextSibling;
|
|
2951
|
+
}
|
|
2235
2952
|
}
|
|
2236
2953
|
}
|
|
2237
2954
|
}
|
|
2238
2955
|
},
|
|
2239
|
-
|
|
2240
|
-
if (
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2956
|
+
remove({ node, parentNode, isNested, }) {
|
|
2957
|
+
if (!isNested && node.parentNode === parentNode) {
|
|
2958
|
+
parentNode.removeChild(node);
|
|
2959
|
+
}
|
|
2960
|
+
},
|
|
2961
|
+
text({ value, oldNode, hydrationNodes, }) {
|
|
2962
|
+
if (hydrationNodes != null) {
|
|
2963
|
+
let node = hydrationNodes.shift();
|
|
2964
|
+
if (!node || node.nodeType !== Node.TEXT_NODE) {
|
|
2965
|
+
console.warn(`Expected "${value}" while hydrating but found:`, node);
|
|
2966
|
+
}
|
|
2967
|
+
else {
|
|
2968
|
+
// value is a text node, check if it matches the expected text
|
|
2969
|
+
const textData = node.data;
|
|
2970
|
+
if (textData.length > value.length) {
|
|
2971
|
+
if (textData.startsWith(value)) {
|
|
2972
|
+
// the text node is longer than the expected text, so we
|
|
2973
|
+
// reuse the existing text node, but truncate it and unshift the rest
|
|
2974
|
+
node.data = value;
|
|
2975
|
+
hydrationNodes.unshift(document.createTextNode(textData.slice(value.length)));
|
|
2976
|
+
return node;
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
else if (textData === value) {
|
|
2980
|
+
return node;
|
|
2981
|
+
}
|
|
2982
|
+
// We log textData and not node because node will be mutated
|
|
2983
|
+
console.warn(`Expected "${value}" while hydrating but found:`, textData);
|
|
2984
|
+
oldNode = node;
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
if (oldNode != null) {
|
|
2988
|
+
if (oldNode.data !== value) {
|
|
2989
|
+
oldNode.data = value;
|
|
2246
2990
|
}
|
|
2991
|
+
return oldNode;
|
|
2247
2992
|
}
|
|
2248
|
-
return
|
|
2993
|
+
return document.createTextNode(value);
|
|
2249
2994
|
},
|
|
2250
|
-
raw(value, xmlns,
|
|
2251
|
-
let
|
|
2995
|
+
raw({ value, scope: xmlns, hydrationNodes, }) {
|
|
2996
|
+
let nodes;
|
|
2252
2997
|
if (typeof value === "string") {
|
|
2253
2998
|
const el = xmlns == null
|
|
2254
2999
|
? document.createElement("div")
|
|
2255
3000
|
: document.createElementNS(xmlns, "svg");
|
|
2256
3001
|
el.innerHTML = value;
|
|
2257
|
-
|
|
2258
|
-
result = undefined;
|
|
2259
|
-
}
|
|
2260
|
-
else if (el.childNodes.length === 1) {
|
|
2261
|
-
result = el.childNodes[0];
|
|
2262
|
-
}
|
|
2263
|
-
else {
|
|
2264
|
-
result = Array.from(el.childNodes);
|
|
2265
|
-
}
|
|
3002
|
+
nodes = Array.from(el.childNodes);
|
|
2266
3003
|
}
|
|
2267
3004
|
else {
|
|
2268
|
-
|
|
2269
|
-
}
|
|
2270
|
-
if (
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
3005
|
+
nodes = value == null ? [] : Array.isArray(value) ? [...value] : [value];
|
|
3006
|
+
}
|
|
3007
|
+
if (hydrationNodes != null) {
|
|
3008
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
3009
|
+
const node = nodes[i];
|
|
3010
|
+
// check if node is equal to the next node in the hydration array
|
|
3011
|
+
const hydrationNode = hydrationNodes.shift();
|
|
3012
|
+
if (hydrationNode &&
|
|
3013
|
+
typeof hydrationNode === "object" &&
|
|
3014
|
+
typeof hydrationNode.nodeType === "number" &&
|
|
3015
|
+
node.isEqualNode(hydrationNode)) {
|
|
3016
|
+
nodes[i] = hydrationNode;
|
|
2280
3017
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
if (result.nodeType === Node.ELEMENT_NODE ||
|
|
2284
|
-
result.nodeType === Node.TEXT_NODE) {
|
|
2285
|
-
hydrationData.children.shift();
|
|
3018
|
+
else {
|
|
3019
|
+
console.warn(`Expected <Raw value="${String(value)}"> while hydrating but found:`, hydrationNode);
|
|
2286
3020
|
}
|
|
2287
3021
|
}
|
|
2288
3022
|
}
|
|
2289
|
-
return
|
|
3023
|
+
return nodes.length === 0
|
|
3024
|
+
? undefined
|
|
3025
|
+
: nodes.length === 1
|
|
3026
|
+
? nodes[0]
|
|
3027
|
+
: nodes;
|
|
2290
3028
|
},
|
|
2291
3029
|
};
|
|
2292
3030
|
class DOMRenderer extends Renderer {
|
|
2293
3031
|
constructor() {
|
|
2294
|
-
super(
|
|
3032
|
+
super(adapter);
|
|
2295
3033
|
}
|
|
2296
3034
|
render(children, root, ctx) {
|
|
2297
3035
|
validateRoot(root);
|
|
@@ -2303,9 +3041,12 @@
|
|
|
2303
3041
|
}
|
|
2304
3042
|
}
|
|
2305
3043
|
function validateRoot(root) {
|
|
2306
|
-
if (root
|
|
3044
|
+
if (root == null ||
|
|
2307
3045
|
(typeof root === "object" && typeof root.nodeType !== "number")) {
|
|
2308
|
-
throw new TypeError(`Render root is not a node. Received: ${
|
|
3046
|
+
throw new TypeError(`Render root is not a node. Received: ${String(root)}`);
|
|
3047
|
+
}
|
|
3048
|
+
else if (root.nodeType !== Node.ELEMENT_NODE) {
|
|
3049
|
+
throw new TypeError(`Render root must be an element node. Received: ${String(root)}`);
|
|
2309
3050
|
}
|
|
2310
3051
|
}
|
|
2311
3052
|
const renderer$1 = new DOMRenderer();
|
|
@@ -2313,7 +3054,7 @@
|
|
|
2313
3054
|
var dom = /*#__PURE__*/Object.freeze({
|
|
2314
3055
|
__proto__: null,
|
|
2315
3056
|
DOMRenderer: DOMRenderer,
|
|
2316
|
-
|
|
3057
|
+
adapter: adapter,
|
|
2317
3058
|
renderer: renderer$1
|
|
2318
3059
|
});
|
|
2319
3060
|
|
|
@@ -2364,49 +3105,37 @@
|
|
|
2364
3105
|
}
|
|
2365
3106
|
function printAttrs(props) {
|
|
2366
3107
|
const attrs = [];
|
|
2367
|
-
for (
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
case name === "crank-key":
|
|
2375
|
-
case name === "crank-ref":
|
|
2376
|
-
case name === "crank-static":
|
|
2377
|
-
case name === "c-key":
|
|
2378
|
-
case name === "c-ref":
|
|
2379
|
-
case name === "c-static":
|
|
2380
|
-
case name === "$key":
|
|
2381
|
-
case name === "$ref":
|
|
2382
|
-
case name === "$static":
|
|
2383
|
-
// TODO: Remove deprecated special props
|
|
2384
|
-
break;
|
|
2385
|
-
case name === "style": {
|
|
2386
|
-
if (typeof value === "string") {
|
|
2387
|
-
attrs.push(`style="${escape(value)}"`);
|
|
2388
|
-
}
|
|
2389
|
-
else if (typeof value === "object") {
|
|
2390
|
-
attrs.push(`style="${escape(printStyleObject(value))}"`);
|
|
2391
|
-
}
|
|
2392
|
-
break;
|
|
3108
|
+
for (let [name, value] of Object.entries(props)) {
|
|
3109
|
+
if (name === "innerHTML" || name.startsWith("prop:")) {
|
|
3110
|
+
continue;
|
|
3111
|
+
}
|
|
3112
|
+
else if (name === "style") {
|
|
3113
|
+
if (typeof value === "string") {
|
|
3114
|
+
attrs.push(`style="${escape(value)}"`);
|
|
2393
3115
|
}
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
3116
|
+
else if (typeof value === "object") {
|
|
3117
|
+
attrs.push(`style="${escape(printStyleObject(value))}"`);
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
else if (name === "className") {
|
|
3121
|
+
if ("class" in props || typeof value !== "string") {
|
|
3122
|
+
continue;
|
|
3123
|
+
}
|
|
3124
|
+
attrs.push(`class="${escape(value)}"`);
|
|
3125
|
+
}
|
|
3126
|
+
else {
|
|
3127
|
+
if (name.startsWith("attr:")) {
|
|
3128
|
+
name = name.slice("attr:".length);
|
|
2400
3129
|
}
|
|
2401
|
-
|
|
3130
|
+
if (typeof value === "string") {
|
|
2402
3131
|
attrs.push(`${escape(name)}="${escape(value)}"`);
|
|
2403
|
-
|
|
2404
|
-
|
|
3132
|
+
}
|
|
3133
|
+
else if (typeof value === "number") {
|
|
2405
3134
|
attrs.push(`${escape(name)}="${value}"`);
|
|
2406
|
-
|
|
2407
|
-
|
|
3135
|
+
}
|
|
3136
|
+
else if (value === true) {
|
|
2408
3137
|
attrs.push(`${escape(name)}`);
|
|
2409
|
-
|
|
3138
|
+
}
|
|
2410
3139
|
}
|
|
2411
3140
|
}
|
|
2412
3141
|
return attrs.join(" ");
|
|
@@ -2423,8 +3152,8 @@
|
|
|
2423
3152
|
create() {
|
|
2424
3153
|
return { value: "" };
|
|
2425
3154
|
},
|
|
2426
|
-
text(
|
|
2427
|
-
return escape(
|
|
3155
|
+
text({ value }) {
|
|
3156
|
+
return { value: escape(value) };
|
|
2428
3157
|
},
|
|
2429
3158
|
read(value) {
|
|
2430
3159
|
if (Array.isArray(value)) {
|
|
@@ -2437,15 +3166,15 @@
|
|
|
2437
3166
|
return value;
|
|
2438
3167
|
}
|
|
2439
3168
|
else {
|
|
2440
|
-
return value.value;
|
|
3169
|
+
return value.value || "";
|
|
2441
3170
|
}
|
|
2442
3171
|
},
|
|
2443
|
-
arrange(tag, node, props, children) {
|
|
3172
|
+
arrange({ tag, tagName, node, props, children, }) {
|
|
2444
3173
|
if (tag === Portal) {
|
|
2445
3174
|
return;
|
|
2446
3175
|
}
|
|
2447
3176
|
else if (typeof tag !== "string") {
|
|
2448
|
-
throw new Error(`Unknown tag: ${
|
|
3177
|
+
throw new Error(`Unknown tag: ${tagName}`);
|
|
2449
3178
|
}
|
|
2450
3179
|
const attrs = printAttrs(props);
|
|
2451
3180
|
const open = `<${tag}${attrs.length ? " " : ""}${attrs}>`;
|
|
@@ -2482,6 +3211,7 @@
|
|
|
2482
3211
|
exports.Portal = Portal;
|
|
2483
3212
|
exports.Raw = Raw;
|
|
2484
3213
|
exports.Renderer = Renderer;
|
|
3214
|
+
exports.Text = Text;
|
|
2485
3215
|
exports.cloneElement = cloneElement;
|
|
2486
3216
|
exports.createElement = createElement;
|
|
2487
3217
|
exports.dom = dom;
|