@b9g/crank 0.5.0-debug.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -17
- package/crank.cjs +682 -392
- package/crank.cjs.map +1 -1
- package/crank.d.ts +57 -77
- package/crank.js +681 -391
- package/crank.js.map +1 -1
- package/dom.cjs +103 -27
- package/dom.cjs.map +1 -1
- package/dom.d.ts +1 -0
- package/dom.js +103 -25
- package/dom.js.map +1 -1
- package/html.cjs +1 -3
- package/html.cjs.map +1 -1
- package/html.d.ts +2 -1
- package/html.js +1 -1
- package/html.js.map +1 -1
- package/jsx-runtime.cjs +18 -0
- package/jsx-runtime.cjs.map +1 -0
- package/jsx-runtime.d.ts +5 -0
- package/jsx-runtime.js +15 -0
- package/jsx-runtime.js.map +1 -0
- package/{xm.cjs → jsx-tag.cjs} +8 -10
- package/jsx-tag.cjs.map +1 -0
- package/jsx-tag.d.ts +2 -0
- package/{xm.js → jsx-tag.js} +9 -9
- package/jsx-tag.js.map +1 -0
- package/package.json +41 -33
- package/{mod.cjs → standalone.cjs} +3 -5
- package/standalone.cjs.map +1 -0
- package/standalone.d.ts +2 -0
- package/{mod.js → standalone.js} +3 -3
- package/standalone.js.map +1 -0
- package/umd.js +787 -421
- package/umd.js.map +1 -1
- package/mod.cjs.map +0 -1
- package/mod.d.ts +0 -2
- package/mod.js.map +0 -1
- package/xm.cjs.map +0 -1
- package/xm.d.ts +0 -2
- package/xm.js.map +0 -1
package/crank.cjs
CHANGED
|
@@ -25,7 +25,8 @@ function arrayify(value) {
|
|
|
25
25
|
: typeof value === "string" ||
|
|
26
26
|
typeof value[Symbol.iterator] !== "function"
|
|
27
27
|
? [value]
|
|
28
|
-
:
|
|
28
|
+
: // TODO: inference broke in TypeScript 3.9.
|
|
29
|
+
[...value];
|
|
29
30
|
}
|
|
30
31
|
function isIteratorLike(value) {
|
|
31
32
|
return value != null && typeof value.next === "function";
|
|
@@ -74,8 +75,7 @@ const Copy = Symbol.for("crank.Copy");
|
|
|
74
75
|
/**
|
|
75
76
|
* A special tag for injecting raw nodes or strings via a value prop.
|
|
76
77
|
*
|
|
77
|
-
*
|
|
78
|
-
* the string and the result will be set as the element’s value.
|
|
78
|
+
* Renderer.prototype.raw() is called with the value prop.
|
|
79
79
|
*/
|
|
80
80
|
const Raw = Symbol.for("crank.Raw");
|
|
81
81
|
const ElementSymbol = Symbol.for("crank.Element");
|
|
@@ -161,22 +161,6 @@ function createElement(tag, props, ...children) {
|
|
|
161
161
|
else if (children.length === 1) {
|
|
162
162
|
props1.children = children[0];
|
|
163
163
|
}
|
|
164
|
-
// string aliases for the special tags
|
|
165
|
-
// TODO: Does this logic belong here, or in the Element constructor
|
|
166
|
-
switch (tag) {
|
|
167
|
-
case "$FRAGMENT":
|
|
168
|
-
tag = Fragment;
|
|
169
|
-
break;
|
|
170
|
-
case "$PORTAL":
|
|
171
|
-
tag = Portal;
|
|
172
|
-
break;
|
|
173
|
-
case "$COPY":
|
|
174
|
-
tag = Copy;
|
|
175
|
-
break;
|
|
176
|
-
case "$RAW":
|
|
177
|
-
tag = Raw;
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
164
|
return new Element(tag, props1, key, ref, static_);
|
|
181
165
|
}
|
|
182
166
|
/** Clones a given element, shallowly copying the props object. */
|
|
@@ -255,13 +239,13 @@ function normalize(values) {
|
|
|
255
239
|
class Retainer {
|
|
256
240
|
constructor(el) {
|
|
257
241
|
this.el = el;
|
|
258
|
-
this.value = undefined;
|
|
259
242
|
this.ctx = undefined;
|
|
260
243
|
this.children = undefined;
|
|
261
|
-
this.
|
|
262
|
-
this.
|
|
263
|
-
this.
|
|
264
|
-
this.
|
|
244
|
+
this.value = undefined;
|
|
245
|
+
this.cachedChildValues = undefined;
|
|
246
|
+
this.fallbackValue = undefined;
|
|
247
|
+
this.inflightValue = undefined;
|
|
248
|
+
this.onNextValues = undefined;
|
|
265
249
|
}
|
|
266
250
|
}
|
|
267
251
|
/**
|
|
@@ -270,10 +254,10 @@ class Retainer {
|
|
|
270
254
|
* @returns The value of the element.
|
|
271
255
|
*/
|
|
272
256
|
function getValue(ret) {
|
|
273
|
-
if (typeof ret.
|
|
274
|
-
return typeof ret.
|
|
275
|
-
? getValue(ret.
|
|
276
|
-
: ret.
|
|
257
|
+
if (typeof ret.fallbackValue !== "undefined") {
|
|
258
|
+
return typeof ret.fallbackValue === "object"
|
|
259
|
+
? getValue(ret.fallbackValue)
|
|
260
|
+
: ret.fallbackValue;
|
|
277
261
|
}
|
|
278
262
|
else if (ret.el.tag === Portal) {
|
|
279
263
|
return;
|
|
@@ -289,8 +273,8 @@ function getValue(ret) {
|
|
|
289
273
|
* @returns A normalized array of nodes and strings.
|
|
290
274
|
*/
|
|
291
275
|
function getChildValues(ret) {
|
|
292
|
-
if (ret.
|
|
293
|
-
return wrap(ret.
|
|
276
|
+
if (ret.cachedChildValues) {
|
|
277
|
+
return wrap(ret.cachedChildValues);
|
|
294
278
|
}
|
|
295
279
|
const values = [];
|
|
296
280
|
const children = wrap(ret.children);
|
|
@@ -303,7 +287,7 @@ function getChildValues(ret) {
|
|
|
303
287
|
const values1 = normalize(values);
|
|
304
288
|
const tag = ret.el.tag;
|
|
305
289
|
if (typeof tag === "function" || (tag !== Fragment && tag !== Raw)) {
|
|
306
|
-
ret.
|
|
290
|
+
ret.cachedChildValues = unwrap(values1);
|
|
307
291
|
}
|
|
308
292
|
return values1;
|
|
309
293
|
}
|
|
@@ -311,16 +295,19 @@ const defaultRendererImpl = {
|
|
|
311
295
|
create() {
|
|
312
296
|
throw new Error("Not implemented");
|
|
313
297
|
},
|
|
298
|
+
hydrate() {
|
|
299
|
+
throw new Error("Not implemented");
|
|
300
|
+
},
|
|
314
301
|
scope: IDENTITY,
|
|
315
302
|
read: IDENTITY,
|
|
316
|
-
|
|
317
|
-
|
|
303
|
+
text: IDENTITY,
|
|
304
|
+
raw: IDENTITY,
|
|
318
305
|
patch: NOOP,
|
|
319
306
|
arrange: NOOP,
|
|
320
307
|
dispose: NOOP,
|
|
321
308
|
flush: NOOP,
|
|
322
309
|
};
|
|
323
|
-
const
|
|
310
|
+
const _RendererImpl = Symbol.for("crank.RendererImpl");
|
|
324
311
|
/**
|
|
325
312
|
* An abstract class which is subclassed to render to different target
|
|
326
313
|
* environments. This class is responsible for kicking off the rendering
|
|
@@ -334,7 +321,7 @@ const $RendererImpl = Symbol.for("crank.RendererImpl");
|
|
|
334
321
|
class Renderer {
|
|
335
322
|
constructor(impl) {
|
|
336
323
|
this.cache = new WeakMap();
|
|
337
|
-
this[
|
|
324
|
+
this[_RendererImpl] = {
|
|
338
325
|
...defaultRendererImpl,
|
|
339
326
|
...impl,
|
|
340
327
|
};
|
|
@@ -346,7 +333,7 @@ class Renderer {
|
|
|
346
333
|
* used root to delete the previously rendered element tree from the cache.
|
|
347
334
|
* @param root - The node to be rendered into. The renderer will cache
|
|
348
335
|
* element trees per root.
|
|
349
|
-
* @param
|
|
336
|
+
* @param bridge - An optional context that will be the ancestor context of all
|
|
350
337
|
* elements in the tree. Useful for connecting different renderers so that
|
|
351
338
|
* events/provisions properly propagate. The context for a given root must be
|
|
352
339
|
* the same or an error will be thrown.
|
|
@@ -356,7 +343,7 @@ class Renderer {
|
|
|
356
343
|
*/
|
|
357
344
|
render(children, root, bridge) {
|
|
358
345
|
let ret;
|
|
359
|
-
const ctx = bridge && bridge[
|
|
346
|
+
const ctx = bridge && bridge[_ContextImpl];
|
|
360
347
|
if (typeof root === "object" && root !== null) {
|
|
361
348
|
ret = this.cache.get(root);
|
|
362
349
|
}
|
|
@@ -379,8 +366,32 @@ class Renderer {
|
|
|
379
366
|
this.cache.delete(root);
|
|
380
367
|
}
|
|
381
368
|
}
|
|
382
|
-
const impl = this[
|
|
383
|
-
const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children);
|
|
369
|
+
const impl = this[_RendererImpl];
|
|
370
|
+
const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children, undefined);
|
|
371
|
+
// We return the child values of the portal because portal elements
|
|
372
|
+
// themselves have no readable value.
|
|
373
|
+
if (isPromiseLike(childValues)) {
|
|
374
|
+
return childValues.then((childValues) => commitRootRender(impl, root, ctx, ret, childValues, oldProps));
|
|
375
|
+
}
|
|
376
|
+
return commitRootRender(impl, root, ctx, ret, childValues, oldProps);
|
|
377
|
+
}
|
|
378
|
+
hydrate(children, root, bridge) {
|
|
379
|
+
const impl = this[_RendererImpl];
|
|
380
|
+
const ctx = bridge && bridge[_ContextImpl];
|
|
381
|
+
let ret;
|
|
382
|
+
ret = this.cache.get(root);
|
|
383
|
+
if (ret !== undefined) {
|
|
384
|
+
// If there is a retainer for the root, hydration is not necessary.
|
|
385
|
+
return this.render(children, root, bridge);
|
|
386
|
+
}
|
|
387
|
+
let oldProps;
|
|
388
|
+
ret = new Retainer(createElement(Portal, { children, root }));
|
|
389
|
+
ret.value = root;
|
|
390
|
+
if (typeof root === "object" && root !== null && children != null) {
|
|
391
|
+
this.cache.set(root, ret);
|
|
392
|
+
}
|
|
393
|
+
const hydrationData = impl.hydrate(Portal, root, {});
|
|
394
|
+
const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children, hydrationData);
|
|
384
395
|
// We return the child values of the portal because portal elements
|
|
385
396
|
// themselves have no readable value.
|
|
386
397
|
if (isPromiseLike(childValues)) {
|
|
@@ -392,17 +403,17 @@ class Renderer {
|
|
|
392
403
|
/*** PRIVATE RENDERER FUNCTIONS ***/
|
|
393
404
|
function commitRootRender(renderer, root, ctx, ret, childValues, oldProps) {
|
|
394
405
|
// element is a host or portal element
|
|
395
|
-
if (root
|
|
396
|
-
renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.
|
|
406
|
+
if (root != null) {
|
|
407
|
+
renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.cachedChildValues));
|
|
397
408
|
flush(renderer, root);
|
|
398
409
|
}
|
|
399
|
-
ret.
|
|
410
|
+
ret.cachedChildValues = unwrap(childValues);
|
|
400
411
|
if (root == null) {
|
|
401
412
|
unmount(renderer, ret, ctx, ret);
|
|
402
413
|
}
|
|
403
|
-
return renderer.read(ret.
|
|
414
|
+
return renderer.read(ret.cachedChildValues);
|
|
404
415
|
}
|
|
405
|
-
function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
416
|
+
function diffChildren(renderer, root, host, ctx, scope, parent, children, hydrationData) {
|
|
406
417
|
const oldRetained = wrap(parent.children);
|
|
407
418
|
const newRetained = [];
|
|
408
419
|
const newChildren = arrayify(children);
|
|
@@ -411,14 +422,15 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
411
422
|
let childrenByKey;
|
|
412
423
|
let seenKeys;
|
|
413
424
|
let isAsync = false;
|
|
414
|
-
let
|
|
425
|
+
let hydrationBlock;
|
|
426
|
+
let oi = 0;
|
|
427
|
+
let oldLength = oldRetained.length;
|
|
415
428
|
for (let ni = 0, newLength = newChildren.length; ni < newLength; ni++) {
|
|
416
|
-
//
|
|
417
|
-
// deoptimizations.
|
|
429
|
+
// length checks to prevent index out of bounds deoptimizations.
|
|
418
430
|
let ret = oi >= oldLength ? undefined : oldRetained[oi];
|
|
419
431
|
let child = narrow(newChildren[ni]);
|
|
420
432
|
{
|
|
421
|
-
//
|
|
433
|
+
// aligning new children with old retainers
|
|
422
434
|
let oldKey = typeof ret === "object" ? ret.el.key : undefined;
|
|
423
435
|
let newKey = typeof child === "object" ? child.key : undefined;
|
|
424
436
|
if (newKey !== undefined && seenKeys && seenKeys.has(newKey)) {
|
|
@@ -453,18 +465,19 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
453
465
|
// Updating
|
|
454
466
|
let value;
|
|
455
467
|
if (typeof child === "object") {
|
|
456
|
-
if (
|
|
457
|
-
ret.el = child;
|
|
458
|
-
value = getInflightValue(ret);
|
|
459
|
-
}
|
|
460
|
-
else if (child.tag === Copy) {
|
|
468
|
+
if (child.tag === Copy) {
|
|
461
469
|
value = getInflightValue(ret);
|
|
462
470
|
}
|
|
463
471
|
else {
|
|
464
472
|
let oldProps;
|
|
473
|
+
let static_ = false;
|
|
465
474
|
if (typeof ret === "object" && ret.el.tag === child.tag) {
|
|
466
475
|
oldProps = ret.el.props;
|
|
467
476
|
ret.el = child;
|
|
477
|
+
if (child.static_) {
|
|
478
|
+
value = getInflightValue(ret);
|
|
479
|
+
static_ = true;
|
|
480
|
+
}
|
|
468
481
|
}
|
|
469
482
|
else {
|
|
470
483
|
if (typeof ret === "object") {
|
|
@@ -472,19 +485,28 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
472
485
|
}
|
|
473
486
|
const fallback = ret;
|
|
474
487
|
ret = new Retainer(child);
|
|
475
|
-
ret.
|
|
488
|
+
ret.fallbackValue = fallback;
|
|
476
489
|
}
|
|
477
|
-
if (
|
|
478
|
-
|
|
490
|
+
if (static_) ;
|
|
491
|
+
else if (child.tag === Raw) {
|
|
492
|
+
value = hydrationBlock
|
|
493
|
+
? hydrationBlock.then(() => updateRaw(renderer, ret, scope, oldProps, hydrationData))
|
|
494
|
+
: updateRaw(renderer, ret, scope, oldProps, hydrationData);
|
|
479
495
|
}
|
|
480
496
|
else if (child.tag === Fragment) {
|
|
481
|
-
value =
|
|
497
|
+
value = hydrationBlock
|
|
498
|
+
? hydrationBlock.then(() => updateFragment(renderer, root, host, ctx, scope, ret, hydrationData))
|
|
499
|
+
: updateFragment(renderer, root, host, ctx, scope, ret, hydrationData);
|
|
482
500
|
}
|
|
483
501
|
else if (typeof child.tag === "function") {
|
|
484
|
-
value =
|
|
502
|
+
value = hydrationBlock
|
|
503
|
+
? hydrationBlock.then(() => updateComponent(renderer, root, host, ctx, scope, ret, oldProps, hydrationData))
|
|
504
|
+
: updateComponent(renderer, root, host, ctx, scope, ret, oldProps, hydrationData);
|
|
485
505
|
}
|
|
486
506
|
else {
|
|
487
|
-
value =
|
|
507
|
+
value = hydrationBlock
|
|
508
|
+
? hydrationBlock.then(() => updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData))
|
|
509
|
+
: updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData);
|
|
488
510
|
}
|
|
489
511
|
}
|
|
490
512
|
const ref = child.ref;
|
|
@@ -496,9 +518,14 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
496
518
|
return value;
|
|
497
519
|
});
|
|
498
520
|
}
|
|
521
|
+
if (hydrationData !== undefined) {
|
|
522
|
+
hydrationBlock = value;
|
|
523
|
+
}
|
|
499
524
|
}
|
|
500
|
-
else
|
|
501
|
-
ref
|
|
525
|
+
else {
|
|
526
|
+
if (typeof ref === "function") {
|
|
527
|
+
ref(renderer.read(value));
|
|
528
|
+
}
|
|
502
529
|
}
|
|
503
530
|
}
|
|
504
531
|
else {
|
|
@@ -507,7 +534,7 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
507
534
|
(graveyard = graveyard || []).push(ret);
|
|
508
535
|
}
|
|
509
536
|
if (typeof child === "string") {
|
|
510
|
-
value = ret = renderer.
|
|
537
|
+
value = ret = renderer.text(child, scope, hydrationData);
|
|
511
538
|
}
|
|
512
539
|
else {
|
|
513
540
|
ret = undefined;
|
|
@@ -540,27 +567,29 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
540
567
|
childValues1,
|
|
541
568
|
new Promise((resolve) => (onChildValues = resolve)),
|
|
542
569
|
]);
|
|
543
|
-
if (parent.
|
|
544
|
-
parent.
|
|
570
|
+
if (parent.onNextValues) {
|
|
571
|
+
parent.onNextValues(childValues1);
|
|
545
572
|
}
|
|
546
|
-
parent.
|
|
573
|
+
parent.onNextValues = onChildValues;
|
|
547
574
|
return childValues1.then((childValues) => {
|
|
548
|
-
parent.
|
|
575
|
+
parent.inflightValue = parent.fallbackValue = undefined;
|
|
549
576
|
return normalize(childValues);
|
|
550
577
|
});
|
|
551
578
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
579
|
+
else {
|
|
580
|
+
if (graveyard) {
|
|
581
|
+
for (let i = 0; i < graveyard.length; i++) {
|
|
582
|
+
unmount(renderer, host, ctx, graveyard[i]);
|
|
583
|
+
}
|
|
555
584
|
}
|
|
585
|
+
if (parent.onNextValues) {
|
|
586
|
+
parent.onNextValues(values);
|
|
587
|
+
parent.onNextValues = undefined;
|
|
588
|
+
}
|
|
589
|
+
parent.inflightValue = parent.fallbackValue = undefined;
|
|
590
|
+
// We can assert there are no promises in the array because isAsync is false
|
|
591
|
+
return normalize(values);
|
|
556
592
|
}
|
|
557
|
-
if (parent.onCommit) {
|
|
558
|
-
parent.onCommit(values);
|
|
559
|
-
parent.onCommit = undefined;
|
|
560
|
-
}
|
|
561
|
-
parent.inflight = parent.fallback = undefined;
|
|
562
|
-
// We can assert there are no promises in the array because isAsync is false
|
|
563
|
-
return normalize(values);
|
|
564
593
|
}
|
|
565
594
|
function createChildrenByKey(children, offset) {
|
|
566
595
|
const childrenByKey = new Map();
|
|
@@ -580,58 +609,72 @@ function getInflightValue(child) {
|
|
|
580
609
|
if (ctx && ctx.f & IsUpdating && ctx.inflightValue) {
|
|
581
610
|
return ctx.inflightValue;
|
|
582
611
|
}
|
|
583
|
-
else if (child.
|
|
584
|
-
return child.
|
|
612
|
+
else if (child.inflightValue) {
|
|
613
|
+
return child.inflightValue;
|
|
585
614
|
}
|
|
586
615
|
return getValue(child);
|
|
587
616
|
}
|
|
588
|
-
function updateRaw(renderer, ret, scope, oldProps) {
|
|
617
|
+
function updateRaw(renderer, ret, scope, oldProps, hydrationData) {
|
|
589
618
|
const props = ret.el.props;
|
|
590
|
-
if (
|
|
591
|
-
|
|
592
|
-
ret.value = renderer.parse(props.value, scope);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
else {
|
|
596
|
-
ret.value = props.value;
|
|
619
|
+
if (!oldProps || oldProps.value !== props.value) {
|
|
620
|
+
ret.value = renderer.raw(props.value, scope, hydrationData);
|
|
597
621
|
}
|
|
598
622
|
return ret.value;
|
|
599
623
|
}
|
|
600
|
-
function updateFragment(renderer, root, host, ctx, scope, ret) {
|
|
601
|
-
const childValues = diffChildren(renderer, root, host, ctx, scope, ret, ret.el.props.children);
|
|
624
|
+
function updateFragment(renderer, root, host, ctx, scope, ret, hydrationData) {
|
|
625
|
+
const childValues = diffChildren(renderer, root, host, ctx, scope, ret, ret.el.props.children, hydrationData);
|
|
602
626
|
if (isPromiseLike(childValues)) {
|
|
603
|
-
ret.
|
|
604
|
-
return ret.
|
|
627
|
+
ret.inflightValue = childValues.then((childValues) => unwrap(childValues));
|
|
628
|
+
return ret.inflightValue;
|
|
605
629
|
}
|
|
606
630
|
return unwrap(childValues);
|
|
607
631
|
}
|
|
608
|
-
function updateHost(renderer, root, ctx, scope, ret, oldProps) {
|
|
632
|
+
function updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData) {
|
|
609
633
|
const el = ret.el;
|
|
610
634
|
const tag = el.tag;
|
|
635
|
+
let hydrationValue;
|
|
611
636
|
if (el.tag === Portal) {
|
|
612
637
|
root = ret.value = el.props.root;
|
|
613
638
|
}
|
|
614
|
-
else
|
|
615
|
-
|
|
616
|
-
|
|
639
|
+
else {
|
|
640
|
+
if (hydrationData !== undefined) {
|
|
641
|
+
const value = hydrationData.children.shift();
|
|
642
|
+
hydrationValue = value;
|
|
643
|
+
}
|
|
617
644
|
}
|
|
618
645
|
scope = renderer.scope(scope, tag, el.props);
|
|
619
|
-
|
|
646
|
+
let childHydrationData;
|
|
647
|
+
if (hydrationValue != null && typeof hydrationValue !== "string") {
|
|
648
|
+
childHydrationData = renderer.hydrate(tag, hydrationValue, el.props);
|
|
649
|
+
if (childHydrationData === undefined) {
|
|
650
|
+
hydrationValue = undefined;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const childValues = diffChildren(renderer, root, ret, ctx, scope, ret, ret.el.props.children, childHydrationData);
|
|
620
654
|
if (isPromiseLike(childValues)) {
|
|
621
|
-
ret.
|
|
622
|
-
return ret.
|
|
655
|
+
ret.inflightValue = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue));
|
|
656
|
+
return ret.inflightValue;
|
|
623
657
|
}
|
|
624
|
-
return commitHost(renderer, scope, ret, childValues, oldProps);
|
|
658
|
+
return commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue);
|
|
625
659
|
}
|
|
626
|
-
function commitHost(renderer, scope, ret, childValues, oldProps) {
|
|
660
|
+
function commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue) {
|
|
627
661
|
const tag = ret.el.tag;
|
|
628
|
-
|
|
662
|
+
let value = ret.value;
|
|
663
|
+
if (hydrationValue != null) {
|
|
664
|
+
value = ret.value = hydrationValue;
|
|
665
|
+
}
|
|
629
666
|
let props = ret.el.props;
|
|
630
667
|
let copied;
|
|
631
668
|
if (tag !== Portal) {
|
|
669
|
+
if (value == null) {
|
|
670
|
+
// This assumes that renderer.create does not return nullish values.
|
|
671
|
+
value = ret.value = renderer.create(tag, props, scope);
|
|
672
|
+
}
|
|
632
673
|
for (const propName in { ...oldProps, ...props }) {
|
|
633
674
|
const propValue = props[propName];
|
|
634
675
|
if (propValue === Copy) {
|
|
676
|
+
// TODO: The Copy tag doubles as a way to skip the patching of a prop.
|
|
677
|
+
// Not sure about this feature. Should probably be removed.
|
|
635
678
|
(copied = copied || new Set()).add(propName);
|
|
636
679
|
}
|
|
637
680
|
else if (propName !== "children") {
|
|
@@ -646,8 +689,8 @@ function commitHost(renderer, scope, ret, childValues, oldProps) {
|
|
|
646
689
|
}
|
|
647
690
|
ret.el = new Element(tag, props, ret.el.key, ret.el.ref);
|
|
648
691
|
}
|
|
649
|
-
renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.
|
|
650
|
-
ret.
|
|
692
|
+
renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.cachedChildValues));
|
|
693
|
+
ret.cachedChildValues = unwrap(childValues);
|
|
651
694
|
if (tag === Portal) {
|
|
652
695
|
flush(renderer, ret.value);
|
|
653
696
|
return;
|
|
@@ -694,7 +737,7 @@ function unmount(renderer, host, ctx, ret) {
|
|
|
694
737
|
}
|
|
695
738
|
else if (ret.el.tag === Portal) {
|
|
696
739
|
host = ret;
|
|
697
|
-
renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.
|
|
740
|
+
renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.cachedChildValues));
|
|
698
741
|
flush(renderer, host.value);
|
|
699
742
|
}
|
|
700
743
|
else if (ret.el.tag !== Fragment) {
|
|
@@ -718,38 +761,43 @@ function unmount(renderer, host, ctx, ret) {
|
|
|
718
761
|
}
|
|
719
762
|
/*** CONTEXT FLAGS ***/
|
|
720
763
|
/**
|
|
721
|
-
* A flag which is
|
|
722
|
-
*
|
|
723
|
-
*
|
|
764
|
+
* A flag which is true when the component is initialized or updated by an
|
|
765
|
+
* ancestor component or the root render call.
|
|
766
|
+
*
|
|
767
|
+
* Used to determine things like whether the nearest host ancestor needs to be
|
|
768
|
+
* rearranged.
|
|
724
769
|
*/
|
|
725
770
|
const IsUpdating = 1 << 0;
|
|
726
771
|
/**
|
|
727
|
-
* A flag which is
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
* overflow or a generator error.
|
|
772
|
+
* A flag which is true when the component is synchronously executing.
|
|
773
|
+
*
|
|
774
|
+
* Used to guard against components triggering stack overflow or generator error.
|
|
731
775
|
*/
|
|
732
|
-
const
|
|
776
|
+
const IsSyncExecuting = 1 << 1;
|
|
733
777
|
/**
|
|
734
|
-
* A flag
|
|
735
|
-
* iterators without a yield.
|
|
778
|
+
* A flag which is true when the component is in a for...of loop.
|
|
736
779
|
*/
|
|
737
|
-
const
|
|
780
|
+
const IsInForOfLoop = 1 << 2;
|
|
738
781
|
/**
|
|
739
|
-
* A flag
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
782
|
+
* A flag which is true when the component is in a for await...of loop.
|
|
783
|
+
*/
|
|
784
|
+
const IsInForAwaitOfLoop = 1 << 3;
|
|
785
|
+
/**
|
|
786
|
+
* A flag which is true when the component starts the render loop but has not
|
|
787
|
+
* yielded yet.
|
|
788
|
+
*
|
|
789
|
+
* Used to make sure that components yield at least once per loop.
|
|
743
790
|
*/
|
|
744
|
-
const
|
|
791
|
+
const NeedsToYield = 1 << 4;
|
|
745
792
|
/**
|
|
746
|
-
* A flag
|
|
747
|
-
*
|
|
748
|
-
*
|
|
793
|
+
* A flag used by async generator components in conjunction with the
|
|
794
|
+
* onAvailable callback to mark whether new props can be pulled via the context
|
|
795
|
+
* async iterator. See the Symbol.asyncIterator method and the
|
|
796
|
+
* resumeCtxIterator function.
|
|
749
797
|
*/
|
|
750
|
-
const
|
|
798
|
+
const PropsAvailable = 1 << 5;
|
|
751
799
|
/**
|
|
752
|
-
* A flag which is set when a
|
|
800
|
+
* A flag which is set when a component errors.
|
|
753
801
|
*
|
|
754
802
|
* NOTE: This is mainly used to prevent some false positives in component
|
|
755
803
|
* yields or returns undefined warnings. The reason we’re using this versus
|
|
@@ -757,28 +805,28 @@ const IsDone = 1 << 4;
|
|
|
757
805
|
* sync generator child) where synchronous code causes a stack overflow error
|
|
758
806
|
* in a non-deterministic way. Deeply disturbing stuff.
|
|
759
807
|
*/
|
|
760
|
-
const IsErrored = 1 <<
|
|
808
|
+
const IsErrored = 1 << 6;
|
|
761
809
|
/**
|
|
762
810
|
* A flag which is set when the component is unmounted. Unmounted components
|
|
763
811
|
* are no longer in the element tree and cannot refresh or rerender.
|
|
764
812
|
*/
|
|
765
|
-
const IsUnmounted = 1 <<
|
|
813
|
+
const IsUnmounted = 1 << 7;
|
|
766
814
|
/**
|
|
767
815
|
* A flag which indicates that the component is a sync generator component.
|
|
768
816
|
*/
|
|
769
|
-
const IsSyncGen = 1 <<
|
|
817
|
+
const IsSyncGen = 1 << 8;
|
|
770
818
|
/**
|
|
771
819
|
* A flag which indicates that the component is an async generator component.
|
|
772
820
|
*/
|
|
773
|
-
const IsAsyncGen = 1 <<
|
|
821
|
+
const IsAsyncGen = 1 << 9;
|
|
774
822
|
/**
|
|
775
823
|
* A flag which is set while schedule callbacks are called.
|
|
776
824
|
*/
|
|
777
|
-
const IsScheduling = 1 <<
|
|
825
|
+
const IsScheduling = 1 << 10;
|
|
778
826
|
/**
|
|
779
827
|
* A flag which is set when a schedule callback calls refresh.
|
|
780
828
|
*/
|
|
781
|
-
const IsSchedulingRefresh = 1 <<
|
|
829
|
+
const IsSchedulingRefresh = 1 << 11;
|
|
782
830
|
const provisionMaps = new WeakMap();
|
|
783
831
|
const scheduleMap = new WeakMap();
|
|
784
832
|
const cleanupMap = new WeakMap();
|
|
@@ -786,12 +834,12 @@ const cleanupMap = new WeakMap();
|
|
|
786
834
|
const flushMaps = new WeakMap();
|
|
787
835
|
/**
|
|
788
836
|
* @internal
|
|
789
|
-
* The internal class which holds
|
|
837
|
+
* The internal class which holds context data.
|
|
790
838
|
*/
|
|
791
839
|
class ContextImpl {
|
|
792
840
|
constructor(renderer, root, host, parent, scope, ret) {
|
|
793
841
|
this.f = 0;
|
|
794
|
-
this.
|
|
842
|
+
this.owner = new Context(this);
|
|
795
843
|
this.renderer = renderer;
|
|
796
844
|
this.root = root;
|
|
797
845
|
this.host = host;
|
|
@@ -803,10 +851,11 @@ class ContextImpl {
|
|
|
803
851
|
this.inflightValue = undefined;
|
|
804
852
|
this.enqueuedBlock = undefined;
|
|
805
853
|
this.enqueuedValue = undefined;
|
|
806
|
-
this.
|
|
854
|
+
this.onProps = undefined;
|
|
855
|
+
this.onPropsRequested = undefined;
|
|
807
856
|
}
|
|
808
857
|
}
|
|
809
|
-
const
|
|
858
|
+
const _ContextImpl = Symbol.for("crank.ContextImpl");
|
|
810
859
|
/**
|
|
811
860
|
* A class which is instantiated and passed to every component as its this
|
|
812
861
|
* value. Contexts form a tree just like elements and all components in the
|
|
@@ -820,8 +869,10 @@ const $ContextImpl = Symbol.for("crank.ContextImpl");
|
|
|
820
869
|
* schedule and cleanup callbacks.
|
|
821
870
|
*/
|
|
822
871
|
class Context {
|
|
872
|
+
// TODO: If we could make the constructor function take a nicer value, it
|
|
873
|
+
// would be useful for testing purposes.
|
|
823
874
|
constructor(impl) {
|
|
824
|
-
this[
|
|
875
|
+
this[_ContextImpl] = impl;
|
|
825
876
|
}
|
|
826
877
|
/**
|
|
827
878
|
* The current props of the associated element.
|
|
@@ -831,7 +882,7 @@ class Context {
|
|
|
831
882
|
* plugins or utilities which wrap contexts.
|
|
832
883
|
*/
|
|
833
884
|
get props() {
|
|
834
|
-
return this[
|
|
885
|
+
return this[_ContextImpl].ret.el.props;
|
|
835
886
|
}
|
|
836
887
|
// TODO: Should we rename this???
|
|
837
888
|
/**
|
|
@@ -842,45 +893,64 @@ class Context {
|
|
|
842
893
|
* mainly for plugins or utilities which wrap contexts.
|
|
843
894
|
*/
|
|
844
895
|
get value() {
|
|
845
|
-
return this[
|
|
896
|
+
return this[_ContextImpl].renderer.read(getValue(this[_ContextImpl].ret));
|
|
846
897
|
}
|
|
847
898
|
*[Symbol.iterator]() {
|
|
848
|
-
const
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
899
|
+
const ctx = this[_ContextImpl];
|
|
900
|
+
try {
|
|
901
|
+
ctx.f |= IsInForOfLoop;
|
|
902
|
+
while (!(ctx.f & IsUnmounted)) {
|
|
903
|
+
if (ctx.f & NeedsToYield) {
|
|
904
|
+
throw new Error("Context iterated twice without a yield");
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
ctx.f |= NeedsToYield;
|
|
908
|
+
}
|
|
909
|
+
yield ctx.ret.el.props;
|
|
855
910
|
}
|
|
856
|
-
|
|
857
|
-
|
|
911
|
+
}
|
|
912
|
+
finally {
|
|
913
|
+
ctx.f &= ~IsInForOfLoop;
|
|
858
914
|
}
|
|
859
915
|
}
|
|
860
916
|
async *[Symbol.asyncIterator]() {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
917
|
+
const ctx = this[_ContextImpl];
|
|
918
|
+
if (ctx.f & IsSyncGen) {
|
|
919
|
+
throw new Error("Use for...of in sync generator components");
|
|
920
|
+
}
|
|
921
|
+
try {
|
|
922
|
+
ctx.f |= IsInForAwaitOfLoop;
|
|
923
|
+
while (!(ctx.f & IsUnmounted)) {
|
|
924
|
+
if (ctx.f & NeedsToYield) {
|
|
925
|
+
throw new Error("Context iterated twice without a yield");
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
ctx.f |= NeedsToYield;
|
|
929
|
+
}
|
|
930
|
+
if (ctx.f & PropsAvailable) {
|
|
931
|
+
ctx.f &= ~PropsAvailable;
|
|
932
|
+
yield ctx.ret.el.props;
|
|
933
|
+
}
|
|
934
|
+
else {
|
|
935
|
+
const props = await new Promise((resolve) => (ctx.onProps = resolve));
|
|
936
|
+
if (ctx.f & IsUnmounted) {
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
yield props;
|
|
880
940
|
}
|
|
941
|
+
if (ctx.onPropsRequested) {
|
|
942
|
+
ctx.onPropsRequested();
|
|
943
|
+
ctx.onPropsRequested = undefined;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
finally {
|
|
948
|
+
ctx.f &= ~IsInForAwaitOfLoop;
|
|
949
|
+
if (ctx.onPropsRequested) {
|
|
950
|
+
ctx.onPropsRequested();
|
|
951
|
+
ctx.onPropsRequested = undefined;
|
|
881
952
|
}
|
|
882
|
-
|
|
883
|
-
} while (!(impl.f & IsDone));
|
|
953
|
+
}
|
|
884
954
|
}
|
|
885
955
|
/**
|
|
886
956
|
* Re-executes a component.
|
|
@@ -895,32 +965,31 @@ class Context {
|
|
|
895
965
|
* async iterator to suspend.
|
|
896
966
|
*/
|
|
897
967
|
refresh() {
|
|
898
|
-
const
|
|
899
|
-
if (
|
|
968
|
+
const ctx = this[_ContextImpl];
|
|
969
|
+
if (ctx.f & IsUnmounted) {
|
|
900
970
|
console.error("Component is unmounted");
|
|
901
|
-
return
|
|
971
|
+
return ctx.renderer.read(undefined);
|
|
902
972
|
}
|
|
903
|
-
else if (
|
|
973
|
+
else if (ctx.f & IsSyncExecuting) {
|
|
904
974
|
console.error("Component is already executing");
|
|
905
975
|
return this.value;
|
|
906
976
|
}
|
|
907
|
-
|
|
908
|
-
const value = runComponent(impl);
|
|
977
|
+
const value = enqueueComponentRun(ctx);
|
|
909
978
|
if (isPromiseLike(value)) {
|
|
910
|
-
return value.then((value) =>
|
|
979
|
+
return value.then((value) => ctx.renderer.read(value));
|
|
911
980
|
}
|
|
912
|
-
return
|
|
981
|
+
return ctx.renderer.read(value);
|
|
913
982
|
}
|
|
914
983
|
/**
|
|
915
984
|
* Registers a callback which fires when the component commits. Will only
|
|
916
985
|
* fire once per callback and update.
|
|
917
986
|
*/
|
|
918
987
|
schedule(callback) {
|
|
919
|
-
const
|
|
920
|
-
let callbacks = scheduleMap.get(
|
|
988
|
+
const ctx = this[_ContextImpl];
|
|
989
|
+
let callbacks = scheduleMap.get(ctx);
|
|
921
990
|
if (!callbacks) {
|
|
922
991
|
callbacks = new Set();
|
|
923
|
-
scheduleMap.set(
|
|
992
|
+
scheduleMap.set(ctx, callbacks);
|
|
924
993
|
}
|
|
925
994
|
callbacks.add(callback);
|
|
926
995
|
}
|
|
@@ -929,19 +998,19 @@ class Context {
|
|
|
929
998
|
* rendered into the root. Will only fire once per callback and render.
|
|
930
999
|
*/
|
|
931
1000
|
flush(callback) {
|
|
932
|
-
const
|
|
933
|
-
if (typeof
|
|
1001
|
+
const ctx = this[_ContextImpl];
|
|
1002
|
+
if (typeof ctx.root !== "object" || ctx.root === null) {
|
|
934
1003
|
return;
|
|
935
1004
|
}
|
|
936
|
-
let flushMap = flushMaps.get(
|
|
1005
|
+
let flushMap = flushMaps.get(ctx.root);
|
|
937
1006
|
if (!flushMap) {
|
|
938
1007
|
flushMap = new Map();
|
|
939
|
-
flushMaps.set(
|
|
1008
|
+
flushMaps.set(ctx.root, flushMap);
|
|
940
1009
|
}
|
|
941
|
-
let callbacks = flushMap.get(
|
|
1010
|
+
let callbacks = flushMap.get(ctx);
|
|
942
1011
|
if (!callbacks) {
|
|
943
1012
|
callbacks = new Set();
|
|
944
|
-
flushMap.set(
|
|
1013
|
+
flushMap.set(ctx, callbacks);
|
|
945
1014
|
}
|
|
946
1015
|
callbacks.add(callback);
|
|
947
1016
|
}
|
|
@@ -950,45 +1019,45 @@ class Context {
|
|
|
950
1019
|
* fire once per callback.
|
|
951
1020
|
*/
|
|
952
1021
|
cleanup(callback) {
|
|
953
|
-
const
|
|
954
|
-
let callbacks = cleanupMap.get(
|
|
1022
|
+
const ctx = this[_ContextImpl];
|
|
1023
|
+
let callbacks = cleanupMap.get(ctx);
|
|
955
1024
|
if (!callbacks) {
|
|
956
1025
|
callbacks = new Set();
|
|
957
|
-
cleanupMap.set(
|
|
1026
|
+
cleanupMap.set(ctx, callbacks);
|
|
958
1027
|
}
|
|
959
1028
|
callbacks.add(callback);
|
|
960
1029
|
}
|
|
961
1030
|
consume(key) {
|
|
962
|
-
for (let
|
|
963
|
-
const provisions = provisionMaps.get(
|
|
1031
|
+
for (let ctx = this[_ContextImpl].parent; ctx !== undefined; ctx = ctx.parent) {
|
|
1032
|
+
const provisions = provisionMaps.get(ctx);
|
|
964
1033
|
if (provisions && provisions.has(key)) {
|
|
965
1034
|
return provisions.get(key);
|
|
966
1035
|
}
|
|
967
1036
|
}
|
|
968
1037
|
}
|
|
969
1038
|
provide(key, value) {
|
|
970
|
-
const
|
|
971
|
-
let provisions = provisionMaps.get(
|
|
1039
|
+
const ctx = this[_ContextImpl];
|
|
1040
|
+
let provisions = provisionMaps.get(ctx);
|
|
972
1041
|
if (!provisions) {
|
|
973
1042
|
provisions = new Map();
|
|
974
|
-
provisionMaps.set(
|
|
1043
|
+
provisionMaps.set(ctx, provisions);
|
|
975
1044
|
}
|
|
976
1045
|
provisions.set(key, value);
|
|
977
1046
|
}
|
|
978
1047
|
addEventListener(type, listener, options) {
|
|
979
|
-
const
|
|
1048
|
+
const ctx = this[_ContextImpl];
|
|
980
1049
|
let listeners;
|
|
981
1050
|
if (!isListenerOrListenerObject(listener)) {
|
|
982
1051
|
return;
|
|
983
1052
|
}
|
|
984
1053
|
else {
|
|
985
|
-
const listeners1 = listenersMap.get(
|
|
1054
|
+
const listeners1 = listenersMap.get(ctx);
|
|
986
1055
|
if (listeners1) {
|
|
987
1056
|
listeners = listeners1;
|
|
988
1057
|
}
|
|
989
1058
|
else {
|
|
990
1059
|
listeners = [];
|
|
991
|
-
listenersMap.set(
|
|
1060
|
+
listenersMap.set(ctx, listeners);
|
|
992
1061
|
}
|
|
993
1062
|
}
|
|
994
1063
|
options = normalizeListenerOptions(options);
|
|
@@ -999,7 +1068,7 @@ class Context {
|
|
|
999
1068
|
else {
|
|
1000
1069
|
callback = listener;
|
|
1001
1070
|
}
|
|
1002
|
-
const record = { type,
|
|
1071
|
+
const record = { type, listener, callback, options };
|
|
1003
1072
|
if (options.once) {
|
|
1004
1073
|
record.callback = function () {
|
|
1005
1074
|
const i = listeners.indexOf(record);
|
|
@@ -1016,15 +1085,15 @@ class Context {
|
|
|
1016
1085
|
}
|
|
1017
1086
|
listeners.push(record);
|
|
1018
1087
|
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1019
|
-
for (const value of getChildValues(
|
|
1088
|
+
for (const value of getChildValues(ctx.ret)) {
|
|
1020
1089
|
if (isEventTarget(value)) {
|
|
1021
1090
|
value.addEventListener(record.type, record.callback, record.options);
|
|
1022
1091
|
}
|
|
1023
1092
|
}
|
|
1024
1093
|
}
|
|
1025
1094
|
removeEventListener(type, listener, options) {
|
|
1026
|
-
const
|
|
1027
|
-
const listeners = listenersMap.get(
|
|
1095
|
+
const ctx = this[_ContextImpl];
|
|
1096
|
+
const listeners = listenersMap.get(ctx);
|
|
1028
1097
|
if (listeners == null || !isListenerOrListenerObject(listener)) {
|
|
1029
1098
|
return;
|
|
1030
1099
|
}
|
|
@@ -1038,16 +1107,16 @@ class Context {
|
|
|
1038
1107
|
const record = listeners[i];
|
|
1039
1108
|
listeners.splice(i, 1);
|
|
1040
1109
|
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1041
|
-
for (const value of getChildValues(
|
|
1110
|
+
for (const value of getChildValues(ctx.ret)) {
|
|
1042
1111
|
if (isEventTarget(value)) {
|
|
1043
1112
|
value.removeEventListener(record.type, record.callback, record.options);
|
|
1044
1113
|
}
|
|
1045
1114
|
}
|
|
1046
1115
|
}
|
|
1047
1116
|
dispatchEvent(ev) {
|
|
1048
|
-
const
|
|
1117
|
+
const ctx = this[_ContextImpl];
|
|
1049
1118
|
const path = [];
|
|
1050
|
-
for (let parent =
|
|
1119
|
+
for (let parent = ctx.parent; parent !== undefined; parent = parent.parent) {
|
|
1051
1120
|
path.push(parent);
|
|
1052
1121
|
}
|
|
1053
1122
|
// We patch the stopImmediatePropagation method because ev.cancelBubble
|
|
@@ -1059,7 +1128,7 @@ class Context {
|
|
|
1059
1128
|
immediateCancelBubble = true;
|
|
1060
1129
|
return stopImmediatePropagation.call(ev);
|
|
1061
1130
|
});
|
|
1062
|
-
setEventProperty(ev, "target",
|
|
1131
|
+
setEventProperty(ev, "target", ctx.owner);
|
|
1063
1132
|
// The only possible errors in this block are errors thrown by callbacks,
|
|
1064
1133
|
// and dispatchEvent will only log these errors rather than throwing
|
|
1065
1134
|
// them. Therefore, we place all code in a try block, log errors in the
|
|
@@ -1068,18 +1137,21 @@ class Context {
|
|
|
1068
1137
|
// Each early return within the try block returns true because while the
|
|
1069
1138
|
// return value is overridden in the finally block, TypeScript
|
|
1070
1139
|
// (justifiably) does not recognize the unsafe return statement.
|
|
1071
|
-
//
|
|
1072
|
-
// TODO: Run all callbacks even if one of them errors
|
|
1073
1140
|
try {
|
|
1074
1141
|
setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
|
|
1075
1142
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
1076
1143
|
const target = path[i];
|
|
1077
1144
|
const listeners = listenersMap.get(target);
|
|
1078
1145
|
if (listeners) {
|
|
1079
|
-
setEventProperty(ev, "currentTarget", target.
|
|
1146
|
+
setEventProperty(ev, "currentTarget", target.owner);
|
|
1080
1147
|
for (const record of listeners) {
|
|
1081
1148
|
if (record.type === ev.type && record.options.capture) {
|
|
1082
|
-
|
|
1149
|
+
try {
|
|
1150
|
+
record.callback.call(target.owner, ev);
|
|
1151
|
+
}
|
|
1152
|
+
catch (err) {
|
|
1153
|
+
console.error(err);
|
|
1154
|
+
}
|
|
1083
1155
|
if (immediateCancelBubble) {
|
|
1084
1156
|
return true;
|
|
1085
1157
|
}
|
|
@@ -1091,13 +1163,25 @@ class Context {
|
|
|
1091
1163
|
}
|
|
1092
1164
|
}
|
|
1093
1165
|
{
|
|
1094
|
-
|
|
1166
|
+
setEventProperty(ev, "eventPhase", AT_TARGET);
|
|
1167
|
+
setEventProperty(ev, "currentTarget", ctx.owner);
|
|
1168
|
+
const propCallback = ctx.ret.el.props["on" + ev.type];
|
|
1169
|
+
if (propCallback != null) {
|
|
1170
|
+
propCallback(ev);
|
|
1171
|
+
if (immediateCancelBubble || ev.cancelBubble) {
|
|
1172
|
+
return true;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
const listeners = listenersMap.get(ctx);
|
|
1095
1176
|
if (listeners) {
|
|
1096
|
-
setEventProperty(ev, "eventPhase", AT_TARGET);
|
|
1097
|
-
setEventProperty(ev, "currentTarget", impl.ctx);
|
|
1098
1177
|
for (const record of listeners) {
|
|
1099
1178
|
if (record.type === ev.type) {
|
|
1100
|
-
|
|
1179
|
+
try {
|
|
1180
|
+
record.callback.call(ctx.owner, ev);
|
|
1181
|
+
}
|
|
1182
|
+
catch (err) {
|
|
1183
|
+
console.error(err);
|
|
1184
|
+
}
|
|
1101
1185
|
if (immediateCancelBubble) {
|
|
1102
1186
|
return true;
|
|
1103
1187
|
}
|
|
@@ -1114,10 +1198,15 @@ class Context {
|
|
|
1114
1198
|
const target = path[i];
|
|
1115
1199
|
const listeners = listenersMap.get(target);
|
|
1116
1200
|
if (listeners) {
|
|
1117
|
-
setEventProperty(ev, "currentTarget", target.
|
|
1201
|
+
setEventProperty(ev, "currentTarget", target.owner);
|
|
1118
1202
|
for (const record of listeners) {
|
|
1119
1203
|
if (record.type === ev.type && !record.options.capture) {
|
|
1120
|
-
|
|
1204
|
+
try {
|
|
1205
|
+
record.callback.call(target.owner, ev);
|
|
1206
|
+
}
|
|
1207
|
+
catch (err) {
|
|
1208
|
+
console.error(err);
|
|
1209
|
+
}
|
|
1121
1210
|
if (immediateCancelBubble) {
|
|
1122
1211
|
return true;
|
|
1123
1212
|
}
|
|
@@ -1130,10 +1219,6 @@ class Context {
|
|
|
1130
1219
|
}
|
|
1131
1220
|
}
|
|
1132
1221
|
}
|
|
1133
|
-
catch (err) {
|
|
1134
|
-
// TODO: Use setTimeout to rethrow the error.
|
|
1135
|
-
console.error(err);
|
|
1136
|
-
}
|
|
1137
1222
|
finally {
|
|
1138
1223
|
setEventProperty(ev, "eventPhase", NONE);
|
|
1139
1224
|
setEventProperty(ev, "currentTarget", null);
|
|
@@ -1151,42 +1236,47 @@ function ctxContains(parent, child) {
|
|
|
1151
1236
|
}
|
|
1152
1237
|
return false;
|
|
1153
1238
|
}
|
|
1154
|
-
function updateComponent(renderer, root, host, parent, scope, ret, oldProps) {
|
|
1239
|
+
function updateComponent(renderer, root, host, parent, scope, ret, oldProps, hydrationData) {
|
|
1155
1240
|
let ctx;
|
|
1156
1241
|
if (oldProps) {
|
|
1157
1242
|
ctx = ret.ctx;
|
|
1158
|
-
if (ctx.f &
|
|
1243
|
+
if (ctx.f & IsSyncExecuting) {
|
|
1159
1244
|
console.error("Component is already executing");
|
|
1160
|
-
return ret.
|
|
1245
|
+
return ret.cachedChildValues;
|
|
1161
1246
|
}
|
|
1162
1247
|
}
|
|
1163
1248
|
else {
|
|
1164
1249
|
ctx = ret.ctx = new ContextImpl(renderer, root, host, parent, scope, ret);
|
|
1165
1250
|
}
|
|
1166
1251
|
ctx.f |= IsUpdating;
|
|
1167
|
-
|
|
1168
|
-
return runComponent(ctx);
|
|
1252
|
+
return enqueueComponentRun(ctx, hydrationData);
|
|
1169
1253
|
}
|
|
1170
|
-
function updateComponentChildren(ctx, children) {
|
|
1171
|
-
if (ctx.f & IsUnmounted
|
|
1254
|
+
function updateComponentChildren(ctx, children, hydrationData) {
|
|
1255
|
+
if (ctx.f & IsUnmounted) {
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
else if (ctx.f & IsErrored) {
|
|
1259
|
+
// This branch is necessary for some race conditions where this function is
|
|
1260
|
+
// called after iterator.throw() in async generator components.
|
|
1172
1261
|
return;
|
|
1173
1262
|
}
|
|
1174
1263
|
else if (children === undefined) {
|
|
1175
1264
|
console.error("A component has returned or yielded undefined. If this was intentional, return or yield null instead.");
|
|
1176
1265
|
}
|
|
1177
1266
|
let childValues;
|
|
1178
|
-
// We set the isExecuting flag in case a child component dispatches an event
|
|
1179
|
-
// which bubbles to this component and causes a synchronous refresh().
|
|
1180
|
-
ctx.f |= IsExecuting;
|
|
1181
1267
|
try {
|
|
1182
|
-
|
|
1268
|
+
// TODO: WAT
|
|
1269
|
+
// We set the isExecuting flag in case a child component dispatches an event
|
|
1270
|
+
// which bubbles to this component and causes a synchronous refresh().
|
|
1271
|
+
ctx.f |= IsSyncExecuting;
|
|
1272
|
+
childValues = diffChildren(ctx.renderer, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children), hydrationData);
|
|
1183
1273
|
}
|
|
1184
1274
|
finally {
|
|
1185
|
-
ctx.f &= ~
|
|
1275
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1186
1276
|
}
|
|
1187
1277
|
if (isPromiseLike(childValues)) {
|
|
1188
|
-
ctx.ret.
|
|
1189
|
-
return ctx.ret.
|
|
1278
|
+
ctx.ret.inflightValue = childValues.then((childValues) => commitComponent(ctx, childValues));
|
|
1279
|
+
return ctx.ret.inflightValue;
|
|
1190
1280
|
}
|
|
1191
1281
|
return commitComponent(ctx, childValues);
|
|
1192
1282
|
}
|
|
@@ -1206,8 +1296,8 @@ function commitComponent(ctx, values) {
|
|
|
1206
1296
|
}
|
|
1207
1297
|
}
|
|
1208
1298
|
}
|
|
1209
|
-
const oldValues = wrap(ctx.ret.
|
|
1210
|
-
let value = (ctx.ret.
|
|
1299
|
+
const oldValues = wrap(ctx.ret.cachedChildValues);
|
|
1300
|
+
let value = (ctx.ret.cachedChildValues = unwrap(values));
|
|
1211
1301
|
if (ctx.f & IsScheduling) {
|
|
1212
1302
|
ctx.f |= IsSchedulingRefresh;
|
|
1213
1303
|
}
|
|
@@ -1215,7 +1305,7 @@ function commitComponent(ctx, values) {
|
|
|
1215
1305
|
// If we’re not updating the component, which happens when components are
|
|
1216
1306
|
// refreshed, or when async generator components iterate, we have to do a
|
|
1217
1307
|
// little bit housekeeping when a component’s child values have changed.
|
|
1218
|
-
if (!
|
|
1308
|
+
if (!arrayEqual(oldValues, values)) {
|
|
1219
1309
|
const records = getListenerRecords(ctx.parent, ctx.host);
|
|
1220
1310
|
if (records.length) {
|
|
1221
1311
|
for (let i = 0; i < values.length; i++) {
|
|
@@ -1230,7 +1320,7 @@ function commitComponent(ctx, values) {
|
|
|
1230
1320
|
}
|
|
1231
1321
|
// rearranging the nearest ancestor host element
|
|
1232
1322
|
const host = ctx.host;
|
|
1233
|
-
const oldHostValues = wrap(host.
|
|
1323
|
+
const oldHostValues = wrap(host.cachedChildValues);
|
|
1234
1324
|
invalidate(ctx, host);
|
|
1235
1325
|
const hostValues = getChildValues(host);
|
|
1236
1326
|
ctx.renderer.arrange(host.el.tag, host.value, host.el.props, hostValues,
|
|
@@ -1259,50 +1349,79 @@ function commitComponent(ctx, values) {
|
|
|
1259
1349
|
}
|
|
1260
1350
|
function invalidate(ctx, host) {
|
|
1261
1351
|
for (let parent = ctx.parent; parent !== undefined && parent.host === host; parent = parent.parent) {
|
|
1262
|
-
parent.ret.
|
|
1352
|
+
parent.ret.cachedChildValues = undefined;
|
|
1263
1353
|
}
|
|
1264
|
-
host.
|
|
1354
|
+
host.cachedChildValues = undefined;
|
|
1265
1355
|
}
|
|
1266
|
-
function
|
|
1267
|
-
if (
|
|
1356
|
+
function arrayEqual(arr1, arr2) {
|
|
1357
|
+
if (arr1.length !== arr2.length) {
|
|
1268
1358
|
return false;
|
|
1269
1359
|
}
|
|
1270
|
-
for (let i = 0; i <
|
|
1271
|
-
const value1 =
|
|
1272
|
-
const value2 =
|
|
1360
|
+
for (let i = 0; i < arr1.length; i++) {
|
|
1361
|
+
const value1 = arr1[i];
|
|
1362
|
+
const value2 = arr2[i];
|
|
1273
1363
|
if (value1 !== value2) {
|
|
1274
1364
|
return false;
|
|
1275
1365
|
}
|
|
1276
1366
|
}
|
|
1277
1367
|
return true;
|
|
1278
1368
|
}
|
|
1279
|
-
/**
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1369
|
+
/** Enqueues and executes the component associated with the context. */
|
|
1370
|
+
function enqueueComponentRun(ctx, hydrationData) {
|
|
1371
|
+
if (ctx.f & IsAsyncGen && !(ctx.f & IsInForOfLoop)) {
|
|
1372
|
+
if (hydrationData !== undefined) {
|
|
1373
|
+
throw new Error("Hydration error");
|
|
1374
|
+
}
|
|
1375
|
+
// This branch will run for non-initial renders of async generator
|
|
1376
|
+
// components when they are not in for...of loops. When in a for...of loop,
|
|
1377
|
+
// async generator components will behave normally.
|
|
1378
|
+
//
|
|
1379
|
+
// Async gen componennts can be in one of three states:
|
|
1380
|
+
//
|
|
1381
|
+
// 1. propsAvailable flag is true: "available"
|
|
1382
|
+
//
|
|
1383
|
+
// The component is suspended somewhere in the loop. When the component
|
|
1384
|
+
// reaches the bottom of the loop, it will run again with the next props.
|
|
1385
|
+
//
|
|
1386
|
+
// 2. onAvailable callback is defined: "suspended"
|
|
1387
|
+
//
|
|
1388
|
+
// The component has suspended at the bottom of the loop and is waiting
|
|
1389
|
+
// for new props.
|
|
1390
|
+
//
|
|
1391
|
+
// 3. neither 1 or 2: "Running"
|
|
1392
|
+
//
|
|
1393
|
+
// The component is suspended somewhere in the loop. When the component
|
|
1394
|
+
// reaches the bottom of the loop, it will suspend.
|
|
1395
|
+
//
|
|
1396
|
+
// Components will never be both available and suspended at
|
|
1397
|
+
// the same time.
|
|
1398
|
+
//
|
|
1399
|
+
// If the component is at the loop bottom, this means that the next value
|
|
1400
|
+
// produced by the component will have the most up to date props, so we can
|
|
1401
|
+
// simply return the current inflight value. Otherwise, we have to wait for
|
|
1402
|
+
// the bottom of the loop to be reached before returning the inflight
|
|
1403
|
+
// value.
|
|
1404
|
+
const isAtLoopbottom = ctx.f & IsInForAwaitOfLoop && !ctx.onProps;
|
|
1405
|
+
resumePropsIterator(ctx);
|
|
1406
|
+
if (isAtLoopbottom) {
|
|
1407
|
+
if (ctx.inflightBlock == null) {
|
|
1408
|
+
ctx.inflightBlock = new Promise((resolve) => (ctx.onPropsRequested = resolve));
|
|
1409
|
+
}
|
|
1410
|
+
return ctx.inflightBlock.then(() => {
|
|
1411
|
+
ctx.inflightBlock = undefined;
|
|
1412
|
+
return ctx.inflightValue;
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
return ctx.inflightValue;
|
|
1416
|
+
}
|
|
1417
|
+
else if (!ctx.inflightBlock) {
|
|
1297
1418
|
try {
|
|
1298
|
-
const [block, value] =
|
|
1419
|
+
const [block, value] = runComponent(ctx, hydrationData);
|
|
1299
1420
|
if (block) {
|
|
1300
1421
|
ctx.inflightBlock = block
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
}
|
|
1305
|
-
})
|
|
1422
|
+
// TODO: there is some fuckery going on here related to async
|
|
1423
|
+
// generator components resuming when they’re meant to be returned.
|
|
1424
|
+
.then((v) => v)
|
|
1306
1425
|
.finally(() => advanceComponent(ctx));
|
|
1307
1426
|
// stepComponent will only return a block if the value is asynchronous
|
|
1308
1427
|
ctx.inflightValue = value;
|
|
@@ -1316,38 +1435,46 @@ function runComponent(ctx) {
|
|
|
1316
1435
|
throw err;
|
|
1317
1436
|
}
|
|
1318
1437
|
}
|
|
1319
|
-
else if (ctx.f & IsAsyncGen) {
|
|
1320
|
-
return ctx.inflightValue;
|
|
1321
|
-
}
|
|
1322
1438
|
else if (!ctx.enqueuedBlock) {
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1439
|
+
if (hydrationData !== undefined) {
|
|
1440
|
+
throw new Error("Hydration error");
|
|
1441
|
+
}
|
|
1442
|
+
// We need to assign enqueuedBlock and enqueuedValue synchronously, hence
|
|
1443
|
+
// the Promise constructor call here.
|
|
1444
|
+
let resolveEnqueuedBlock;
|
|
1445
|
+
ctx.enqueuedBlock = new Promise((resolve) => (resolveEnqueuedBlock = resolve));
|
|
1446
|
+
ctx.enqueuedValue = ctx.inflightBlock.then(() => {
|
|
1326
1447
|
try {
|
|
1327
|
-
const [block, value] =
|
|
1328
|
-
resolve(value);
|
|
1448
|
+
const [block, value] = runComponent(ctx);
|
|
1329
1449
|
if (block) {
|
|
1330
|
-
|
|
1331
|
-
if (!(ctx.f & IsUpdating)) {
|
|
1332
|
-
return propagateError(ctx.parent, err);
|
|
1333
|
-
}
|
|
1334
|
-
});
|
|
1450
|
+
resolveEnqueuedBlock(block.finally(() => advanceComponent(ctx)));
|
|
1335
1451
|
}
|
|
1452
|
+
return value;
|
|
1336
1453
|
}
|
|
1337
1454
|
catch (err) {
|
|
1338
1455
|
if (!(ctx.f & IsUpdating)) {
|
|
1339
1456
|
return propagateError(ctx.parent, err);
|
|
1340
1457
|
}
|
|
1458
|
+
throw err;
|
|
1341
1459
|
}
|
|
1342
|
-
})
|
|
1343
|
-
.finally(() => advanceComponent(ctx));
|
|
1344
|
-
ctx.enqueuedValue = new Promise((resolve1) => (resolve = resolve1));
|
|
1460
|
+
});
|
|
1345
1461
|
}
|
|
1346
1462
|
return ctx.enqueuedValue;
|
|
1347
1463
|
}
|
|
1464
|
+
/** Called when the inflight block promise settles. */
|
|
1465
|
+
function advanceComponent(ctx) {
|
|
1466
|
+
if (ctx.f & IsAsyncGen && !(ctx.f & IsInForOfLoop)) {
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
ctx.inflightBlock = ctx.enqueuedBlock;
|
|
1470
|
+
ctx.inflightValue = ctx.enqueuedValue;
|
|
1471
|
+
ctx.enqueuedBlock = undefined;
|
|
1472
|
+
ctx.enqueuedValue = undefined;
|
|
1473
|
+
}
|
|
1348
1474
|
/**
|
|
1349
1475
|
* This function is responsible for executing the component and handling all
|
|
1350
|
-
* the different component types.
|
|
1476
|
+
* the different component types. We cannot identify whether a component is a
|
|
1477
|
+
* generator or async without calling it and inspecting the return value.
|
|
1351
1478
|
*
|
|
1352
1479
|
* @returns {[block, value]} A tuple where
|
|
1353
1480
|
* block - A possible promise which represents the duration during which the
|
|
@@ -1362,25 +1489,23 @@ function runComponent(ctx) {
|
|
|
1362
1489
|
* - Sync generator components block while any children are executing, because
|
|
1363
1490
|
* they are expected to only resume when they’ve actually rendered.
|
|
1364
1491
|
*/
|
|
1365
|
-
function
|
|
1492
|
+
function runComponent(ctx, hydrationData) {
|
|
1366
1493
|
const ret = ctx.ret;
|
|
1367
|
-
if (ctx.f & IsDone) {
|
|
1368
|
-
return [undefined, getValue(ret)];
|
|
1369
|
-
}
|
|
1370
1494
|
const initial = !ctx.iterator;
|
|
1371
1495
|
if (initial) {
|
|
1372
|
-
ctx
|
|
1496
|
+
resumePropsIterator(ctx);
|
|
1497
|
+
ctx.f |= IsSyncExecuting;
|
|
1373
1498
|
clearEventListeners(ctx);
|
|
1374
1499
|
let result;
|
|
1375
1500
|
try {
|
|
1376
|
-
result = ret.el.tag.call(ctx.
|
|
1501
|
+
result = ret.el.tag.call(ctx.owner, ret.el.props);
|
|
1377
1502
|
}
|
|
1378
1503
|
catch (err) {
|
|
1379
1504
|
ctx.f |= IsErrored;
|
|
1380
1505
|
throw err;
|
|
1381
1506
|
}
|
|
1382
1507
|
finally {
|
|
1383
|
-
ctx.f &= ~
|
|
1508
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1384
1509
|
}
|
|
1385
1510
|
if (isIteratorLike(result)) {
|
|
1386
1511
|
ctx.iterator = result;
|
|
@@ -1388,124 +1513,241 @@ function stepComponent(ctx) {
|
|
|
1388
1513
|
else if (isPromiseLike(result)) {
|
|
1389
1514
|
// async function component
|
|
1390
1515
|
const result1 = result instanceof Promise ? result : Promise.resolve(result);
|
|
1391
|
-
const value = result1.then((result) => updateComponentChildren(ctx, result), (err) => {
|
|
1516
|
+
const value = result1.then((result) => updateComponentChildren(ctx, result, hydrationData), (err) => {
|
|
1392
1517
|
ctx.f |= IsErrored;
|
|
1393
1518
|
throw err;
|
|
1394
1519
|
});
|
|
1395
|
-
return [result1, value];
|
|
1520
|
+
return [result1.catch(NOOP), value];
|
|
1396
1521
|
}
|
|
1397
1522
|
else {
|
|
1398
1523
|
// sync function component
|
|
1399
|
-
return [
|
|
1524
|
+
return [
|
|
1525
|
+
undefined,
|
|
1526
|
+
updateComponentChildren(ctx, result, hydrationData),
|
|
1527
|
+
];
|
|
1400
1528
|
}
|
|
1401
1529
|
}
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
// The argument passed to the first call to next is ignored.
|
|
1405
|
-
oldValue = undefined;
|
|
1406
|
-
}
|
|
1407
|
-
else if (ctx.ret.inflight) {
|
|
1408
|
-
// The value passed back into the generator as the argument to the next
|
|
1409
|
-
// method is a promise if an async generator component has async children.
|
|
1410
|
-
// Sync generator components only resume when their children have fulfilled
|
|
1411
|
-
// so the element’s inflight child values will never be defined.
|
|
1412
|
-
oldValue = ctx.ret.inflight.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
|
|
1413
|
-
}
|
|
1414
|
-
else {
|
|
1415
|
-
oldValue = ctx.renderer.read(getValue(ret));
|
|
1530
|
+
else if (hydrationData !== undefined) {
|
|
1531
|
+
throw new Error("Hydration error");
|
|
1416
1532
|
}
|
|
1417
1533
|
let iteration;
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1534
|
+
if (initial) {
|
|
1535
|
+
try {
|
|
1536
|
+
ctx.f |= IsSyncExecuting;
|
|
1537
|
+
iteration = ctx.iterator.next();
|
|
1538
|
+
}
|
|
1539
|
+
catch (err) {
|
|
1540
|
+
ctx.f |= IsErrored;
|
|
1541
|
+
throw err;
|
|
1542
|
+
}
|
|
1543
|
+
finally {
|
|
1544
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1545
|
+
}
|
|
1546
|
+
if (isPromiseLike(iteration)) {
|
|
1547
|
+
ctx.f |= IsAsyncGen;
|
|
1548
|
+
}
|
|
1549
|
+
else {
|
|
1550
|
+
ctx.f |= IsSyncGen;
|
|
1551
|
+
}
|
|
1425
1552
|
}
|
|
1426
|
-
|
|
1427
|
-
ctx.f &= ~
|
|
1553
|
+
if (ctx.f & IsSyncGen) {
|
|
1554
|
+
ctx.f &= ~NeedsToYield;
|
|
1555
|
+
// sync generator component
|
|
1556
|
+
if (!initial) {
|
|
1557
|
+
try {
|
|
1558
|
+
ctx.f |= IsSyncExecuting;
|
|
1559
|
+
iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
|
|
1560
|
+
}
|
|
1561
|
+
catch (err) {
|
|
1562
|
+
ctx.f |= IsErrored;
|
|
1563
|
+
throw err;
|
|
1564
|
+
}
|
|
1565
|
+
finally {
|
|
1566
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
if (isPromiseLike(iteration)) {
|
|
1570
|
+
throw new Error("Mixed generator component");
|
|
1571
|
+
}
|
|
1572
|
+
if (iteration.done) {
|
|
1573
|
+
ctx.f &= ~IsSyncGen;
|
|
1574
|
+
ctx.iterator = undefined;
|
|
1575
|
+
}
|
|
1576
|
+
let value;
|
|
1577
|
+
try {
|
|
1578
|
+
value = updateComponentChildren(ctx,
|
|
1579
|
+
// Children can be void so we eliminate that here
|
|
1580
|
+
iteration.value, hydrationData);
|
|
1581
|
+
if (isPromiseLike(value)) {
|
|
1582
|
+
value = value.catch((err) => handleChildError(ctx, err));
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
catch (err) {
|
|
1586
|
+
value = handleChildError(ctx, err);
|
|
1587
|
+
}
|
|
1588
|
+
const block = isPromiseLike(value) ? value.catch(NOOP) : undefined;
|
|
1589
|
+
return [block, value];
|
|
1428
1590
|
}
|
|
1429
|
-
if (
|
|
1430
|
-
//
|
|
1431
|
-
|
|
1432
|
-
|
|
1591
|
+
else if (ctx.f & IsInForOfLoop) {
|
|
1592
|
+
// TODO: does this need to be done async?
|
|
1593
|
+
ctx.f &= ~NeedsToYield;
|
|
1594
|
+
// we are in a for...of loop for async generator
|
|
1595
|
+
if (!initial) {
|
|
1596
|
+
try {
|
|
1597
|
+
ctx.f |= IsSyncExecuting;
|
|
1598
|
+
iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
|
|
1599
|
+
}
|
|
1600
|
+
catch (err) {
|
|
1601
|
+
ctx.f |= IsErrored;
|
|
1602
|
+
throw err;
|
|
1603
|
+
}
|
|
1604
|
+
finally {
|
|
1605
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
if (!isPromiseLike(iteration)) {
|
|
1609
|
+
throw new Error("Mixed generator component");
|
|
1433
1610
|
}
|
|
1611
|
+
const block = iteration.catch(NOOP);
|
|
1434
1612
|
const value = iteration.then((iteration) => {
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
ctx.f &= ~IsIterating;
|
|
1439
|
-
if (iteration.done) {
|
|
1440
|
-
ctx.f |= IsDone;
|
|
1613
|
+
let value;
|
|
1614
|
+
if (!(ctx.f & IsInForOfLoop)) {
|
|
1615
|
+
runAsyncGenComponent(ctx, Promise.resolve(iteration), hydrationData);
|
|
1441
1616
|
}
|
|
1442
1617
|
try {
|
|
1443
|
-
|
|
1618
|
+
value = updateComponentChildren(ctx,
|
|
1619
|
+
// Children can be void so we eliminate that here
|
|
1620
|
+
iteration.value, hydrationData);
|
|
1444
1621
|
if (isPromiseLike(value)) {
|
|
1445
|
-
|
|
1622
|
+
value = value.catch((err) => handleChildError(ctx, err));
|
|
1446
1623
|
}
|
|
1447
|
-
return value;
|
|
1448
1624
|
}
|
|
1449
1625
|
catch (err) {
|
|
1450
|
-
|
|
1626
|
+
value = handleChildError(ctx, err);
|
|
1451
1627
|
}
|
|
1628
|
+
return value;
|
|
1452
1629
|
}, (err) => {
|
|
1453
|
-
ctx.f |=
|
|
1630
|
+
ctx.f |= IsErrored;
|
|
1454
1631
|
throw err;
|
|
1455
1632
|
});
|
|
1456
|
-
return [
|
|
1457
|
-
}
|
|
1458
|
-
// sync generator component
|
|
1459
|
-
if (initial) {
|
|
1460
|
-
ctx.f |= IsSyncGen;
|
|
1633
|
+
return [block, value];
|
|
1461
1634
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1635
|
+
else {
|
|
1636
|
+
runAsyncGenComponent(ctx, iteration, hydrationData);
|
|
1637
|
+
// async generator component
|
|
1638
|
+
return [ctx.inflightBlock, ctx.inflightValue];
|
|
1465
1639
|
}
|
|
1466
|
-
|
|
1640
|
+
}
|
|
1641
|
+
async function runAsyncGenComponent(ctx, iterationP, hydrationData) {
|
|
1642
|
+
let done = false;
|
|
1467
1643
|
try {
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1644
|
+
while (!done) {
|
|
1645
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1646
|
+
break;
|
|
1647
|
+
}
|
|
1648
|
+
// inflightValue must be set synchronously.
|
|
1649
|
+
let onValue;
|
|
1650
|
+
ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
|
|
1651
|
+
if (ctx.f & IsUpdating) {
|
|
1652
|
+
// We should not swallow unhandled promise rejections if the component is
|
|
1653
|
+
// updating independently.
|
|
1654
|
+
// TODO: Does this handle this.refresh() calls?
|
|
1655
|
+
ctx.inflightValue.catch(NOOP);
|
|
1656
|
+
}
|
|
1657
|
+
let iteration;
|
|
1658
|
+
try {
|
|
1659
|
+
iteration = await iterationP;
|
|
1660
|
+
}
|
|
1661
|
+
catch (err) {
|
|
1662
|
+
done = true;
|
|
1663
|
+
ctx.f |= IsErrored;
|
|
1664
|
+
onValue(Promise.reject(err));
|
|
1665
|
+
break;
|
|
1666
|
+
}
|
|
1667
|
+
finally {
|
|
1668
|
+
ctx.f &= ~NeedsToYield;
|
|
1669
|
+
if (!(ctx.f & IsInForAwaitOfLoop)) {
|
|
1670
|
+
ctx.f &= ~PropsAvailable;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
done = !!iteration.done;
|
|
1674
|
+
let value;
|
|
1675
|
+
try {
|
|
1676
|
+
value = updateComponentChildren(ctx, iteration.value, hydrationData);
|
|
1677
|
+
hydrationData = undefined;
|
|
1678
|
+
if (isPromiseLike(value)) {
|
|
1679
|
+
value = value.catch((err) => handleChildError(ctx, err));
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
catch (err) {
|
|
1683
|
+
// Do we need to catch potential errors here in the case of unhandled
|
|
1684
|
+
// promise rejections?
|
|
1685
|
+
value = handleChildError(ctx, err);
|
|
1686
|
+
}
|
|
1687
|
+
finally {
|
|
1688
|
+
onValue(value);
|
|
1689
|
+
}
|
|
1690
|
+
// TODO: this can be done more elegantly
|
|
1691
|
+
let oldValue;
|
|
1692
|
+
if (ctx.ret.inflightValue) {
|
|
1693
|
+
// The value passed back into the generator as the argument to the next
|
|
1694
|
+
// method is a promise if an async generator component has async
|
|
1695
|
+
// children. Sync generator components only resume when their children
|
|
1696
|
+
// have fulfilled so the element’s inflight child values will never be
|
|
1697
|
+
// defined.
|
|
1698
|
+
oldValue = ctx.ret.inflightValue.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
|
|
1699
|
+
}
|
|
1700
|
+
else {
|
|
1701
|
+
oldValue = ctx.renderer.read(getValue(ctx.ret));
|
|
1702
|
+
}
|
|
1703
|
+
if (ctx.f & IsUnmounted) {
|
|
1704
|
+
if (ctx.f & IsInForAwaitOfLoop) {
|
|
1705
|
+
try {
|
|
1706
|
+
ctx.f |= IsSyncExecuting;
|
|
1707
|
+
iterationP = ctx.iterator.next(oldValue);
|
|
1708
|
+
}
|
|
1709
|
+
finally {
|
|
1710
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
else {
|
|
1714
|
+
returnComponent(ctx);
|
|
1715
|
+
break;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
else if (!done && !(ctx.f & IsInForOfLoop)) {
|
|
1719
|
+
try {
|
|
1720
|
+
ctx.f |= IsSyncExecuting;
|
|
1721
|
+
iterationP = ctx.iterator.next(oldValue);
|
|
1722
|
+
}
|
|
1723
|
+
finally {
|
|
1724
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1471
1727
|
}
|
|
1472
1728
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
}
|
|
1479
|
-
return [undefined, value];
|
|
1480
|
-
}
|
|
1481
|
-
/**
|
|
1482
|
-
* Called when the inflight block promise settles.
|
|
1483
|
-
*/
|
|
1484
|
-
function advanceComponent(ctx) {
|
|
1485
|
-
ctx.inflightBlock = ctx.enqueuedBlock;
|
|
1486
|
-
ctx.inflightValue = ctx.enqueuedValue;
|
|
1487
|
-
ctx.enqueuedBlock = undefined;
|
|
1488
|
-
ctx.enqueuedValue = undefined;
|
|
1489
|
-
if (ctx.f & IsAsyncGen && !(ctx.f & IsDone) && !(ctx.f & IsUnmounted)) {
|
|
1490
|
-
runComponent(ctx);
|
|
1729
|
+
finally {
|
|
1730
|
+
if (done) {
|
|
1731
|
+
ctx.f &= ~IsAsyncGen;
|
|
1732
|
+
ctx.iterator = undefined;
|
|
1733
|
+
}
|
|
1491
1734
|
}
|
|
1492
1735
|
}
|
|
1493
1736
|
/**
|
|
1494
|
-
* Called to
|
|
1495
|
-
* generator components.
|
|
1737
|
+
* Called to resume the props async iterator for async generator components.
|
|
1496
1738
|
*/
|
|
1497
|
-
function
|
|
1498
|
-
if (ctx.
|
|
1499
|
-
ctx.
|
|
1500
|
-
ctx.
|
|
1739
|
+
function resumePropsIterator(ctx) {
|
|
1740
|
+
if (ctx.onProps) {
|
|
1741
|
+
ctx.onProps(ctx.ret.el.props);
|
|
1742
|
+
ctx.onProps = undefined;
|
|
1743
|
+
ctx.f &= ~PropsAvailable;
|
|
1501
1744
|
}
|
|
1502
1745
|
else {
|
|
1503
|
-
ctx.f |=
|
|
1746
|
+
ctx.f |= PropsAvailable;
|
|
1504
1747
|
}
|
|
1505
1748
|
}
|
|
1506
1749
|
// TODO: async unmounting
|
|
1507
1750
|
function unmountComponent(ctx) {
|
|
1508
|
-
ctx.f |= IsUnmounted;
|
|
1509
1751
|
clearEventListeners(ctx);
|
|
1510
1752
|
const callbacks = cleanupMap.get(ctx);
|
|
1511
1753
|
if (callbacks) {
|
|
@@ -1515,23 +1757,71 @@ function unmountComponent(ctx) {
|
|
|
1515
1757
|
callback(value);
|
|
1516
1758
|
}
|
|
1517
1759
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
ctx.f
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1760
|
+
ctx.f |= IsUnmounted;
|
|
1761
|
+
if (ctx.iterator) {
|
|
1762
|
+
if (ctx.f & IsSyncGen) {
|
|
1763
|
+
let value;
|
|
1764
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1765
|
+
value = enqueueComponentRun(ctx);
|
|
1766
|
+
}
|
|
1767
|
+
if (isPromiseLike(value)) {
|
|
1768
|
+
value.then(() => {
|
|
1769
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1770
|
+
unmountComponent(ctx);
|
|
1771
|
+
}
|
|
1772
|
+
else {
|
|
1773
|
+
returnComponent(ctx);
|
|
1774
|
+
}
|
|
1775
|
+
}, (err) => {
|
|
1776
|
+
propagateError(ctx.parent, err);
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
else {
|
|
1780
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1781
|
+
unmountComponent(ctx);
|
|
1782
|
+
}
|
|
1783
|
+
else {
|
|
1784
|
+
returnComponent(ctx);
|
|
1527
1785
|
}
|
|
1528
1786
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1787
|
+
}
|
|
1788
|
+
else if (ctx.f & IsAsyncGen) {
|
|
1789
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1790
|
+
const value = enqueueComponentRun(ctx);
|
|
1791
|
+
value.then(() => {
|
|
1792
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1793
|
+
unmountComponent(ctx);
|
|
1794
|
+
}
|
|
1795
|
+
else {
|
|
1796
|
+
returnComponent(ctx);
|
|
1797
|
+
}
|
|
1798
|
+
}, (err) => {
|
|
1799
|
+
propagateError(ctx.parent, err);
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
else {
|
|
1803
|
+
// The logic for unmounting async generator components is in the
|
|
1804
|
+
// runAsyncGenComponent function.
|
|
1805
|
+
resumePropsIterator(ctx);
|
|
1531
1806
|
}
|
|
1532
1807
|
}
|
|
1533
1808
|
}
|
|
1534
1809
|
}
|
|
1810
|
+
function returnComponent(ctx) {
|
|
1811
|
+
resumePropsIterator(ctx);
|
|
1812
|
+
if (ctx.iterator && typeof ctx.iterator.return === "function") {
|
|
1813
|
+
try {
|
|
1814
|
+
ctx.f |= IsSyncExecuting;
|
|
1815
|
+
const iteration = ctx.iterator.return();
|
|
1816
|
+
if (isPromiseLike(iteration)) {
|
|
1817
|
+
iteration.catch((err) => propagateError(ctx.parent, err));
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
finally {
|
|
1821
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1535
1825
|
/*** EVENT TARGET UTILITIES ***/
|
|
1536
1826
|
// EVENT PHASE CONSTANTS
|
|
1537
1827
|
// https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
|
|
@@ -1600,39 +1890,39 @@ function clearEventListeners(ctx) {
|
|
|
1600
1890
|
}
|
|
1601
1891
|
}
|
|
1602
1892
|
/*** ERROR HANDLING UTILITIES ***/
|
|
1603
|
-
// TODO: generator components which throw errors should be recoverable
|
|
1604
1893
|
function handleChildError(ctx, err) {
|
|
1605
|
-
if (ctx.
|
|
1606
|
-
!ctx.iterator ||
|
|
1607
|
-
typeof ctx.iterator.throw !== "function") {
|
|
1894
|
+
if (!ctx.iterator || typeof ctx.iterator.throw !== "function") {
|
|
1608
1895
|
throw err;
|
|
1609
1896
|
}
|
|
1610
|
-
|
|
1897
|
+
resumePropsIterator(ctx);
|
|
1611
1898
|
let iteration;
|
|
1612
1899
|
try {
|
|
1613
|
-
ctx.f |=
|
|
1900
|
+
ctx.f |= IsSyncExecuting;
|
|
1614
1901
|
iteration = ctx.iterator.throw(err);
|
|
1615
1902
|
}
|
|
1616
1903
|
catch (err) {
|
|
1617
|
-
ctx.f |=
|
|
1904
|
+
ctx.f |= IsErrored;
|
|
1618
1905
|
throw err;
|
|
1619
1906
|
}
|
|
1620
1907
|
finally {
|
|
1621
|
-
ctx.f &= ~
|
|
1908
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1622
1909
|
}
|
|
1623
1910
|
if (isPromiseLike(iteration)) {
|
|
1624
1911
|
return iteration.then((iteration) => {
|
|
1625
1912
|
if (iteration.done) {
|
|
1626
|
-
ctx.f
|
|
1913
|
+
ctx.f &= ~IsAsyncGen;
|
|
1914
|
+
ctx.iterator = undefined;
|
|
1627
1915
|
}
|
|
1628
1916
|
return updateComponentChildren(ctx, iteration.value);
|
|
1629
1917
|
}, (err) => {
|
|
1630
|
-
ctx.f |=
|
|
1918
|
+
ctx.f |= IsErrored;
|
|
1631
1919
|
throw err;
|
|
1632
1920
|
});
|
|
1633
1921
|
}
|
|
1634
1922
|
if (iteration.done) {
|
|
1635
|
-
ctx.f
|
|
1923
|
+
ctx.f &= ~IsSyncGen;
|
|
1924
|
+
ctx.f &= ~IsAsyncGen;
|
|
1925
|
+
ctx.iterator = undefined;
|
|
1636
1926
|
}
|
|
1637
1927
|
return updateComponentChildren(ctx, iteration.value);
|
|
1638
1928
|
}
|
|
@@ -1665,6 +1955,6 @@ exports.Raw = Raw;
|
|
|
1665
1955
|
exports.Renderer = Renderer;
|
|
1666
1956
|
exports.cloneElement = cloneElement;
|
|
1667
1957
|
exports.createElement = createElement;
|
|
1668
|
-
exports
|
|
1958
|
+
exports.default = crank;
|
|
1669
1959
|
exports.isElement = isElement;
|
|
1670
1960
|
//# sourceMappingURL=crank.cjs.map
|