@b9g/crank 0.5.0-beta.2 → 0.5.0-beta.3
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/crank.cjs +440 -297
- package/crank.cjs.map +1 -1
- package/crank.d.ts +13 -24
- package/crank.js +440 -297
- package/crank.js.map +1 -1
- package/mod.cjs +2 -2
- package/mod.d.ts +1 -1
- package/mod.js +1 -1
- package/package.json +7 -7
- package/{xm.cjs → tags.cjs} +8 -8
- package/tags.cjs.map +1 -0
- package/tags.d.ts +2 -0
- package/{xm.js → tags.js} +9 -9
- package/tags.js.map +1 -0
- package/umd.js +440 -297
- package/umd.js.map +1 -1
- package/xm.cjs.map +0 -1
- package/xm.d.ts +0 -2
- package/xm.js.map +0 -1
package/crank.cjs
CHANGED
|
@@ -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
|
}
|
|
@@ -320,7 +304,7 @@ const defaultRendererImpl = {
|
|
|
320
304
|
dispose: NOOP,
|
|
321
305
|
flush: NOOP,
|
|
322
306
|
};
|
|
323
|
-
const
|
|
307
|
+
const _RendererImpl = Symbol.for("crank.RendererImpl");
|
|
324
308
|
/**
|
|
325
309
|
* An abstract class which is subclassed to render to different target
|
|
326
310
|
* environments. This class is responsible for kicking off the rendering
|
|
@@ -334,7 +318,7 @@ const $RendererImpl = Symbol.for("crank.RendererImpl");
|
|
|
334
318
|
class Renderer {
|
|
335
319
|
constructor(impl) {
|
|
336
320
|
this.cache = new WeakMap();
|
|
337
|
-
this[
|
|
321
|
+
this[_RendererImpl] = {
|
|
338
322
|
...defaultRendererImpl,
|
|
339
323
|
...impl,
|
|
340
324
|
};
|
|
@@ -346,7 +330,7 @@ class Renderer {
|
|
|
346
330
|
* used root to delete the previously rendered element tree from the cache.
|
|
347
331
|
* @param root - The node to be rendered into. The renderer will cache
|
|
348
332
|
* element trees per root.
|
|
349
|
-
* @param
|
|
333
|
+
* @param bridge - An optional context that will be the ancestor context of all
|
|
350
334
|
* elements in the tree. Useful for connecting different renderers so that
|
|
351
335
|
* events/provisions properly propagate. The context for a given root must be
|
|
352
336
|
* the same or an error will be thrown.
|
|
@@ -356,7 +340,7 @@ class Renderer {
|
|
|
356
340
|
*/
|
|
357
341
|
render(children, root, bridge) {
|
|
358
342
|
let ret;
|
|
359
|
-
const ctx = bridge && bridge[
|
|
343
|
+
const ctx = bridge && bridge[_ContextImpl];
|
|
360
344
|
if (typeof root === "object" && root !== null) {
|
|
361
345
|
ret = this.cache.get(root);
|
|
362
346
|
}
|
|
@@ -379,7 +363,7 @@ class Renderer {
|
|
|
379
363
|
this.cache.delete(root);
|
|
380
364
|
}
|
|
381
365
|
}
|
|
382
|
-
const impl = this[
|
|
366
|
+
const impl = this[_RendererImpl];
|
|
383
367
|
const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children);
|
|
384
368
|
// We return the child values of the portal because portal elements
|
|
385
369
|
// themselves have no readable value.
|
|
@@ -392,15 +376,15 @@ class Renderer {
|
|
|
392
376
|
/*** PRIVATE RENDERER FUNCTIONS ***/
|
|
393
377
|
function commitRootRender(renderer, root, ctx, ret, childValues, oldProps) {
|
|
394
378
|
// element is a host or portal element
|
|
395
|
-
if (root
|
|
396
|
-
renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.
|
|
379
|
+
if (root != null) {
|
|
380
|
+
renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.cachedChildValues));
|
|
397
381
|
flush(renderer, root);
|
|
398
382
|
}
|
|
399
|
-
ret.
|
|
383
|
+
ret.cachedChildValues = unwrap(childValues);
|
|
400
384
|
if (root == null) {
|
|
401
385
|
unmount(renderer, ret, ctx, ret);
|
|
402
386
|
}
|
|
403
|
-
return renderer.read(ret.
|
|
387
|
+
return renderer.read(ret.cachedChildValues);
|
|
404
388
|
}
|
|
405
389
|
function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
406
390
|
const oldRetained = wrap(parent.children);
|
|
@@ -472,7 +456,7 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
472
456
|
}
|
|
473
457
|
const fallback = ret;
|
|
474
458
|
ret = new Retainer(child);
|
|
475
|
-
ret.
|
|
459
|
+
ret.fallbackValue = fallback;
|
|
476
460
|
}
|
|
477
461
|
if (child.tag === Raw) {
|
|
478
462
|
value = updateRaw(renderer, ret, scope, oldProps);
|
|
@@ -540,27 +524,29 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
540
524
|
childValues1,
|
|
541
525
|
new Promise((resolve) => (onChildValues = resolve)),
|
|
542
526
|
]);
|
|
543
|
-
if (parent.
|
|
544
|
-
parent.
|
|
527
|
+
if (parent.onNextValues) {
|
|
528
|
+
parent.onNextValues(childValues1);
|
|
545
529
|
}
|
|
546
|
-
parent.
|
|
530
|
+
parent.onNextValues = onChildValues;
|
|
547
531
|
return childValues1.then((childValues) => {
|
|
548
|
-
parent.
|
|
532
|
+
parent.inflightValue = parent.fallbackValue = undefined;
|
|
549
533
|
return normalize(childValues);
|
|
550
534
|
});
|
|
551
535
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
536
|
+
else {
|
|
537
|
+
if (graveyard) {
|
|
538
|
+
for (let i = 0; i < graveyard.length; i++) {
|
|
539
|
+
unmount(renderer, host, ctx, graveyard[i]);
|
|
540
|
+
}
|
|
555
541
|
}
|
|
542
|
+
if (parent.onNextValues) {
|
|
543
|
+
parent.onNextValues(values);
|
|
544
|
+
parent.onNextValues = undefined;
|
|
545
|
+
}
|
|
546
|
+
parent.inflightValue = parent.fallbackValue = undefined;
|
|
547
|
+
// We can assert there are no promises in the array because isAsync is false
|
|
548
|
+
return normalize(values);
|
|
556
549
|
}
|
|
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
550
|
}
|
|
565
551
|
function createChildrenByKey(children, offset) {
|
|
566
552
|
const childrenByKey = new Map();
|
|
@@ -580,8 +566,8 @@ function getInflightValue(child) {
|
|
|
580
566
|
if (ctx && ctx.f & IsUpdating && ctx.inflightValue) {
|
|
581
567
|
return ctx.inflightValue;
|
|
582
568
|
}
|
|
583
|
-
else if (child.
|
|
584
|
-
return child.
|
|
569
|
+
else if (child.inflightValue) {
|
|
570
|
+
return child.inflightValue;
|
|
585
571
|
}
|
|
586
572
|
return getValue(child);
|
|
587
573
|
}
|
|
@@ -600,8 +586,8 @@ function updateRaw(renderer, ret, scope, oldProps) {
|
|
|
600
586
|
function updateFragment(renderer, root, host, ctx, scope, ret) {
|
|
601
587
|
const childValues = diffChildren(renderer, root, host, ctx, scope, ret, ret.el.props.children);
|
|
602
588
|
if (isPromiseLike(childValues)) {
|
|
603
|
-
ret.
|
|
604
|
-
return ret.
|
|
589
|
+
ret.inflightValue = childValues.then((childValues) => unwrap(childValues));
|
|
590
|
+
return ret.inflightValue;
|
|
605
591
|
}
|
|
606
592
|
return unwrap(childValues);
|
|
607
593
|
}
|
|
@@ -618,8 +604,8 @@ function updateHost(renderer, root, ctx, scope, ret, oldProps) {
|
|
|
618
604
|
scope = renderer.scope(scope, tag, el.props);
|
|
619
605
|
const childValues = diffChildren(renderer, root, ret, ctx, scope, ret, ret.el.props.children);
|
|
620
606
|
if (isPromiseLike(childValues)) {
|
|
621
|
-
ret.
|
|
622
|
-
return ret.
|
|
607
|
+
ret.inflightValue = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps));
|
|
608
|
+
return ret.inflightValue;
|
|
623
609
|
}
|
|
624
610
|
return commitHost(renderer, scope, ret, childValues, oldProps);
|
|
625
611
|
}
|
|
@@ -646,8 +632,8 @@ function commitHost(renderer, scope, ret, childValues, oldProps) {
|
|
|
646
632
|
}
|
|
647
633
|
ret.el = new Element(tag, props, ret.el.key, ret.el.ref);
|
|
648
634
|
}
|
|
649
|
-
renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.
|
|
650
|
-
ret.
|
|
635
|
+
renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.cachedChildValues));
|
|
636
|
+
ret.cachedChildValues = unwrap(childValues);
|
|
651
637
|
if (tag === Portal) {
|
|
652
638
|
flush(renderer, ret.value);
|
|
653
639
|
return;
|
|
@@ -694,7 +680,7 @@ function unmount(renderer, host, ctx, ret) {
|
|
|
694
680
|
}
|
|
695
681
|
else if (ret.el.tag === Portal) {
|
|
696
682
|
host = ret;
|
|
697
|
-
renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.
|
|
683
|
+
renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.cachedChildValues));
|
|
698
684
|
flush(renderer, host.value);
|
|
699
685
|
}
|
|
700
686
|
else if (ret.el.tag !== Fragment) {
|
|
@@ -718,38 +704,45 @@ function unmount(renderer, host, ctx, ret) {
|
|
|
718
704
|
}
|
|
719
705
|
/*** CONTEXT FLAGS ***/
|
|
720
706
|
/**
|
|
721
|
-
* A flag which is
|
|
722
|
-
*
|
|
723
|
-
*
|
|
707
|
+
* A flag which is true when the component is initialized or updated by an
|
|
708
|
+
* ancestor component or the root render call.
|
|
709
|
+
*
|
|
710
|
+
* Used to determine things like whether the nearest host ancestor needs to be
|
|
711
|
+
* rearranged.
|
|
724
712
|
*/
|
|
725
713
|
const IsUpdating = 1 << 0;
|
|
726
714
|
/**
|
|
727
|
-
* A flag which is
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
|
|
715
|
+
* A flag which is true when the component is synchronously executing.
|
|
716
|
+
*
|
|
717
|
+
* Used to guard against components triggering stack overflow or generator error.
|
|
718
|
+
*/
|
|
719
|
+
const IsSyncExecuting = 1 << 1;
|
|
720
|
+
/**
|
|
721
|
+
* A flag which is true when the component is in the render loop.
|
|
731
722
|
*/
|
|
732
|
-
const
|
|
723
|
+
const IsInRenderLoop = 1 << 2;
|
|
733
724
|
/**
|
|
734
|
-
* A flag
|
|
735
|
-
*
|
|
725
|
+
* A flag which is true when the component starts the render loop but has not
|
|
726
|
+
* yielded yet.
|
|
727
|
+
*
|
|
728
|
+
* Used to make sure that components yield at least once per loop.
|
|
736
729
|
*/
|
|
737
|
-
const
|
|
730
|
+
const NeedsToYield = 1 << 3;
|
|
738
731
|
/**
|
|
739
732
|
* A flag used by async generator components in conjunction with the
|
|
740
|
-
*
|
|
741
|
-
*
|
|
733
|
+
* onAvailable callback to mark whether new props can be pulled via the context
|
|
734
|
+
* async iterator. See the Symbol.asyncIterator method and the
|
|
742
735
|
* resumeCtxIterator function.
|
|
743
736
|
*/
|
|
744
|
-
const
|
|
737
|
+
const PropsAvailable = 1 << 4;
|
|
745
738
|
/**
|
|
746
739
|
* A flag which is set when a generator components returns, i.e. the done
|
|
747
|
-
* property on the iteration is
|
|
748
|
-
*
|
|
740
|
+
* property on the iteration is true. Generator components will stick to their
|
|
741
|
+
* last rendered value and ignore further updates.
|
|
749
742
|
*/
|
|
750
|
-
const IsDone = 1 <<
|
|
743
|
+
const IsDone = 1 << 5;
|
|
751
744
|
/**
|
|
752
|
-
* A flag which is set when a
|
|
745
|
+
* A flag which is set when a component errors.
|
|
753
746
|
*
|
|
754
747
|
* NOTE: This is mainly used to prevent some false positives in component
|
|
755
748
|
* yields or returns undefined warnings. The reason we’re using this versus
|
|
@@ -757,28 +750,28 @@ const IsDone = 1 << 4;
|
|
|
757
750
|
* sync generator child) where synchronous code causes a stack overflow error
|
|
758
751
|
* in a non-deterministic way. Deeply disturbing stuff.
|
|
759
752
|
*/
|
|
760
|
-
const IsErrored = 1 <<
|
|
753
|
+
const IsErrored = 1 << 6;
|
|
761
754
|
/**
|
|
762
755
|
* A flag which is set when the component is unmounted. Unmounted components
|
|
763
756
|
* are no longer in the element tree and cannot refresh or rerender.
|
|
764
757
|
*/
|
|
765
|
-
const IsUnmounted = 1 <<
|
|
758
|
+
const IsUnmounted = 1 << 7;
|
|
766
759
|
/**
|
|
767
760
|
* A flag which indicates that the component is a sync generator component.
|
|
768
761
|
*/
|
|
769
|
-
const IsSyncGen = 1 <<
|
|
762
|
+
const IsSyncGen = 1 << 8;
|
|
770
763
|
/**
|
|
771
764
|
* A flag which indicates that the component is an async generator component.
|
|
772
765
|
*/
|
|
773
|
-
const IsAsyncGen = 1 <<
|
|
766
|
+
const IsAsyncGen = 1 << 9;
|
|
774
767
|
/**
|
|
775
768
|
* A flag which is set while schedule callbacks are called.
|
|
776
769
|
*/
|
|
777
|
-
const IsScheduling = 1 <<
|
|
770
|
+
const IsScheduling = 1 << 10;
|
|
778
771
|
/**
|
|
779
772
|
* A flag which is set when a schedule callback calls refresh.
|
|
780
773
|
*/
|
|
781
|
-
const IsSchedulingRefresh = 1 <<
|
|
774
|
+
const IsSchedulingRefresh = 1 << 11;
|
|
782
775
|
const provisionMaps = new WeakMap();
|
|
783
776
|
const scheduleMap = new WeakMap();
|
|
784
777
|
const cleanupMap = new WeakMap();
|
|
@@ -786,7 +779,7 @@ const cleanupMap = new WeakMap();
|
|
|
786
779
|
const flushMaps = new WeakMap();
|
|
787
780
|
/**
|
|
788
781
|
* @internal
|
|
789
|
-
* The internal class which holds
|
|
782
|
+
* The internal class which holds context data.
|
|
790
783
|
*/
|
|
791
784
|
class ContextImpl {
|
|
792
785
|
constructor(renderer, root, host, parent, scope, ret) {
|
|
@@ -803,10 +796,11 @@ class ContextImpl {
|
|
|
803
796
|
this.inflightValue = undefined;
|
|
804
797
|
this.enqueuedBlock = undefined;
|
|
805
798
|
this.enqueuedValue = undefined;
|
|
806
|
-
this.
|
|
799
|
+
this.onProps = undefined;
|
|
800
|
+
this.onPropsRequested = undefined;
|
|
807
801
|
}
|
|
808
802
|
}
|
|
809
|
-
const
|
|
803
|
+
const _ContextImpl = Symbol.for("crank.ContextImpl");
|
|
810
804
|
/**
|
|
811
805
|
* A class which is instantiated and passed to every component as its this
|
|
812
806
|
* value. Contexts form a tree just like elements and all components in the
|
|
@@ -820,8 +814,10 @@ const $ContextImpl = Symbol.for("crank.ContextImpl");
|
|
|
820
814
|
* schedule and cleanup callbacks.
|
|
821
815
|
*/
|
|
822
816
|
class Context {
|
|
817
|
+
// TODO: If we could make the constructor function take a nicer value, it
|
|
818
|
+
// would be useful for testing purposes.
|
|
823
819
|
constructor(impl) {
|
|
824
|
-
this[
|
|
820
|
+
this[_ContextImpl] = impl;
|
|
825
821
|
}
|
|
826
822
|
/**
|
|
827
823
|
* The current props of the associated element.
|
|
@@ -831,7 +827,7 @@ class Context {
|
|
|
831
827
|
* plugins or utilities which wrap contexts.
|
|
832
828
|
*/
|
|
833
829
|
get props() {
|
|
834
|
-
return this[
|
|
830
|
+
return this[_ContextImpl].ret.el.props;
|
|
835
831
|
}
|
|
836
832
|
// TODO: Should we rename this???
|
|
837
833
|
/**
|
|
@@ -842,45 +838,70 @@ class Context {
|
|
|
842
838
|
* mainly for plugins or utilities which wrap contexts.
|
|
843
839
|
*/
|
|
844
840
|
get value() {
|
|
845
|
-
return this[
|
|
841
|
+
return this[_ContextImpl].renderer.read(getValue(this[_ContextImpl].ret));
|
|
846
842
|
}
|
|
847
843
|
*[Symbol.iterator]() {
|
|
848
|
-
const impl = this[
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
844
|
+
const impl = this[_ContextImpl];
|
|
845
|
+
if (impl.f & IsAsyncGen) {
|
|
846
|
+
throw new Error("Use for await…of in async generator components");
|
|
847
|
+
}
|
|
848
|
+
try {
|
|
849
|
+
impl.f |= IsInRenderLoop;
|
|
850
|
+
while (!(impl.f & IsUnmounted)) {
|
|
851
|
+
if (impl.f & NeedsToYield) {
|
|
852
|
+
throw new Error("Context iterated twice without a yield");
|
|
853
|
+
}
|
|
854
|
+
else {
|
|
855
|
+
impl.f |= NeedsToYield;
|
|
856
|
+
}
|
|
857
|
+
yield impl.ret.el.props;
|
|
855
858
|
}
|
|
856
|
-
|
|
857
|
-
|
|
859
|
+
}
|
|
860
|
+
finally {
|
|
861
|
+
impl.f &= ~IsInRenderLoop;
|
|
858
862
|
}
|
|
859
863
|
}
|
|
860
864
|
async *[Symbol.asyncIterator]() {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
865
|
+
const impl = this[_ContextImpl];
|
|
866
|
+
if (impl.f & IsSyncGen) {
|
|
867
|
+
throw new Error("Use for…of in sync generator components");
|
|
868
|
+
}
|
|
869
|
+
try {
|
|
870
|
+
// await an empty promise to prevent the IsInRenderLoop flag from
|
|
871
|
+
// returning false positives in the case of async generator components
|
|
872
|
+
// which immediately enter the loop
|
|
873
|
+
impl.f |= IsInRenderLoop;
|
|
874
|
+
while (!(impl.f & IsUnmounted)) {
|
|
875
|
+
if (impl.f & NeedsToYield) {
|
|
876
|
+
throw new Error("Context iterated twice without a yield");
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
impl.f |= NeedsToYield;
|
|
880
|
+
}
|
|
881
|
+
if (impl.f & PropsAvailable) {
|
|
882
|
+
impl.f &= ~PropsAvailable;
|
|
883
|
+
yield impl.ret.el.props;
|
|
884
|
+
}
|
|
885
|
+
else {
|
|
886
|
+
const props = await new Promise((resolve) => (impl.onProps = resolve));
|
|
887
|
+
if (impl.f & IsUnmounted) {
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
yield props;
|
|
880
891
|
}
|
|
892
|
+
if (impl.onPropsRequested) {
|
|
893
|
+
impl.onPropsRequested();
|
|
894
|
+
impl.onPropsRequested = undefined;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
finally {
|
|
899
|
+
impl.f &= ~IsInRenderLoop;
|
|
900
|
+
if (impl.onPropsRequested) {
|
|
901
|
+
impl.onPropsRequested();
|
|
902
|
+
impl.onPropsRequested = undefined;
|
|
881
903
|
}
|
|
882
|
-
|
|
883
|
-
} while (!(impl.f & IsDone));
|
|
904
|
+
}
|
|
884
905
|
}
|
|
885
906
|
/**
|
|
886
907
|
* Re-executes a component.
|
|
@@ -895,17 +916,16 @@ class Context {
|
|
|
895
916
|
* async iterator to suspend.
|
|
896
917
|
*/
|
|
897
918
|
refresh() {
|
|
898
|
-
const impl = this[
|
|
919
|
+
const impl = this[_ContextImpl];
|
|
899
920
|
if (impl.f & IsUnmounted) {
|
|
900
921
|
console.error("Component is unmounted");
|
|
901
922
|
return impl.renderer.read(undefined);
|
|
902
923
|
}
|
|
903
|
-
else if (impl.f &
|
|
924
|
+
else if (impl.f & IsSyncExecuting) {
|
|
904
925
|
console.error("Component is already executing");
|
|
905
926
|
return this.value;
|
|
906
927
|
}
|
|
907
|
-
|
|
908
|
-
const value = runComponent(impl);
|
|
928
|
+
const value = enqueueComponentRun(impl);
|
|
909
929
|
if (isPromiseLike(value)) {
|
|
910
930
|
return value.then((value) => impl.renderer.read(value));
|
|
911
931
|
}
|
|
@@ -916,7 +936,7 @@ class Context {
|
|
|
916
936
|
* fire once per callback and update.
|
|
917
937
|
*/
|
|
918
938
|
schedule(callback) {
|
|
919
|
-
const impl = this[
|
|
939
|
+
const impl = this[_ContextImpl];
|
|
920
940
|
let callbacks = scheduleMap.get(impl);
|
|
921
941
|
if (!callbacks) {
|
|
922
942
|
callbacks = new Set();
|
|
@@ -929,7 +949,7 @@ class Context {
|
|
|
929
949
|
* rendered into the root. Will only fire once per callback and render.
|
|
930
950
|
*/
|
|
931
951
|
flush(callback) {
|
|
932
|
-
const impl = this[
|
|
952
|
+
const impl = this[_ContextImpl];
|
|
933
953
|
if (typeof impl.root !== "object" || impl.root === null) {
|
|
934
954
|
return;
|
|
935
955
|
}
|
|
@@ -950,7 +970,7 @@ class Context {
|
|
|
950
970
|
* fire once per callback.
|
|
951
971
|
*/
|
|
952
972
|
cleanup(callback) {
|
|
953
|
-
const impl = this[
|
|
973
|
+
const impl = this[_ContextImpl];
|
|
954
974
|
let callbacks = cleanupMap.get(impl);
|
|
955
975
|
if (!callbacks) {
|
|
956
976
|
callbacks = new Set();
|
|
@@ -959,7 +979,7 @@ class Context {
|
|
|
959
979
|
callbacks.add(callback);
|
|
960
980
|
}
|
|
961
981
|
consume(key) {
|
|
962
|
-
for (let parent = this[
|
|
982
|
+
for (let parent = this[_ContextImpl].parent; parent !== undefined; parent = parent.parent) {
|
|
963
983
|
const provisions = provisionMaps.get(parent);
|
|
964
984
|
if (provisions && provisions.has(key)) {
|
|
965
985
|
return provisions.get(key);
|
|
@@ -967,7 +987,7 @@ class Context {
|
|
|
967
987
|
}
|
|
968
988
|
}
|
|
969
989
|
provide(key, value) {
|
|
970
|
-
const impl = this[
|
|
990
|
+
const impl = this[_ContextImpl];
|
|
971
991
|
let provisions = provisionMaps.get(impl);
|
|
972
992
|
if (!provisions) {
|
|
973
993
|
provisions = new Map();
|
|
@@ -976,7 +996,7 @@ class Context {
|
|
|
976
996
|
provisions.set(key, value);
|
|
977
997
|
}
|
|
978
998
|
addEventListener(type, listener, options) {
|
|
979
|
-
const impl = this[
|
|
999
|
+
const impl = this[_ContextImpl];
|
|
980
1000
|
let listeners;
|
|
981
1001
|
if (!isListenerOrListenerObject(listener)) {
|
|
982
1002
|
return;
|
|
@@ -1023,7 +1043,7 @@ class Context {
|
|
|
1023
1043
|
}
|
|
1024
1044
|
}
|
|
1025
1045
|
removeEventListener(type, listener, options) {
|
|
1026
|
-
const impl = this[
|
|
1046
|
+
const impl = this[_ContextImpl];
|
|
1027
1047
|
const listeners = listenersMap.get(impl);
|
|
1028
1048
|
if (listeners == null || !isListenerOrListenerObject(listener)) {
|
|
1029
1049
|
return;
|
|
@@ -1045,7 +1065,7 @@ class Context {
|
|
|
1045
1065
|
}
|
|
1046
1066
|
}
|
|
1047
1067
|
dispatchEvent(ev) {
|
|
1048
|
-
const impl = this[
|
|
1068
|
+
const impl = this[_ContextImpl];
|
|
1049
1069
|
const path = [];
|
|
1050
1070
|
for (let parent = impl.parent; parent !== undefined; parent = parent.parent) {
|
|
1051
1071
|
path.push(parent);
|
|
@@ -1155,17 +1175,16 @@ function updateComponent(renderer, root, host, parent, scope, ret, oldProps) {
|
|
|
1155
1175
|
let ctx;
|
|
1156
1176
|
if (oldProps) {
|
|
1157
1177
|
ctx = ret.ctx;
|
|
1158
|
-
if (ctx.f &
|
|
1178
|
+
if (ctx.f & IsSyncExecuting) {
|
|
1159
1179
|
console.error("Component is already executing");
|
|
1160
|
-
return ret.
|
|
1180
|
+
return ret.cachedChildValues;
|
|
1161
1181
|
}
|
|
1162
1182
|
}
|
|
1163
1183
|
else {
|
|
1164
1184
|
ctx = ret.ctx = new ContextImpl(renderer, root, host, parent, scope, ret);
|
|
1165
1185
|
}
|
|
1166
1186
|
ctx.f |= IsUpdating;
|
|
1167
|
-
|
|
1168
|
-
return runComponent(ctx);
|
|
1187
|
+
return enqueueComponentRun(ctx);
|
|
1169
1188
|
}
|
|
1170
1189
|
function updateComponentChildren(ctx, children) {
|
|
1171
1190
|
if (ctx.f & IsUnmounted || ctx.f & IsErrored) {
|
|
@@ -1175,18 +1194,19 @@ function updateComponentChildren(ctx, children) {
|
|
|
1175
1194
|
console.error("A component has returned or yielded undefined. If this was intentional, return or yield null instead.");
|
|
1176
1195
|
}
|
|
1177
1196
|
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
1197
|
try {
|
|
1198
|
+
// TODO: WAT
|
|
1199
|
+
// We set the isExecuting flag in case a child component dispatches an event
|
|
1200
|
+
// which bubbles to this component and causes a synchronous refresh().
|
|
1201
|
+
ctx.f |= IsSyncExecuting;
|
|
1182
1202
|
childValues = diffChildren(ctx.renderer, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children));
|
|
1183
1203
|
}
|
|
1184
1204
|
finally {
|
|
1185
|
-
ctx.f &= ~
|
|
1205
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1186
1206
|
}
|
|
1187
1207
|
if (isPromiseLike(childValues)) {
|
|
1188
|
-
ctx.ret.
|
|
1189
|
-
return ctx.ret.
|
|
1208
|
+
ctx.ret.inflightValue = childValues.then((childValues) => commitComponent(ctx, childValues));
|
|
1209
|
+
return ctx.ret.inflightValue;
|
|
1190
1210
|
}
|
|
1191
1211
|
return commitComponent(ctx, childValues);
|
|
1192
1212
|
}
|
|
@@ -1206,8 +1226,8 @@ function commitComponent(ctx, values) {
|
|
|
1206
1226
|
}
|
|
1207
1227
|
}
|
|
1208
1228
|
}
|
|
1209
|
-
const oldValues = wrap(ctx.ret.
|
|
1210
|
-
let value = (ctx.ret.
|
|
1229
|
+
const oldValues = wrap(ctx.ret.cachedChildValues);
|
|
1230
|
+
let value = (ctx.ret.cachedChildValues = unwrap(values));
|
|
1211
1231
|
if (ctx.f & IsScheduling) {
|
|
1212
1232
|
ctx.f |= IsSchedulingRefresh;
|
|
1213
1233
|
}
|
|
@@ -1215,7 +1235,7 @@ function commitComponent(ctx, values) {
|
|
|
1215
1235
|
// If we’re not updating the component, which happens when components are
|
|
1216
1236
|
// refreshed, or when async generator components iterate, we have to do a
|
|
1217
1237
|
// little bit housekeeping when a component’s child values have changed.
|
|
1218
|
-
if (!
|
|
1238
|
+
if (!arrayEqual(oldValues, values)) {
|
|
1219
1239
|
const records = getListenerRecords(ctx.parent, ctx.host);
|
|
1220
1240
|
if (records.length) {
|
|
1221
1241
|
for (let i = 0; i < values.length; i++) {
|
|
@@ -1230,7 +1250,7 @@ function commitComponent(ctx, values) {
|
|
|
1230
1250
|
}
|
|
1231
1251
|
// rearranging the nearest ancestor host element
|
|
1232
1252
|
const host = ctx.host;
|
|
1233
|
-
const oldHostValues = wrap(host.
|
|
1253
|
+
const oldHostValues = wrap(host.cachedChildValues);
|
|
1234
1254
|
invalidate(ctx, host);
|
|
1235
1255
|
const hostValues = getChildValues(host);
|
|
1236
1256
|
ctx.renderer.arrange(host.el.tag, host.value, host.el.props, hostValues,
|
|
@@ -1259,50 +1279,75 @@ function commitComponent(ctx, values) {
|
|
|
1259
1279
|
}
|
|
1260
1280
|
function invalidate(ctx, host) {
|
|
1261
1281
|
for (let parent = ctx.parent; parent !== undefined && parent.host === host; parent = parent.parent) {
|
|
1262
|
-
parent.ret.
|
|
1282
|
+
parent.ret.cachedChildValues = undefined;
|
|
1263
1283
|
}
|
|
1264
|
-
host.
|
|
1284
|
+
host.cachedChildValues = undefined;
|
|
1265
1285
|
}
|
|
1266
|
-
function
|
|
1267
|
-
if (
|
|
1286
|
+
function arrayEqual(arr1, arr2) {
|
|
1287
|
+
if (arr1.length !== arr2.length) {
|
|
1268
1288
|
return false;
|
|
1269
1289
|
}
|
|
1270
|
-
for (let i = 0; i <
|
|
1271
|
-
const value1 =
|
|
1272
|
-
const value2 =
|
|
1290
|
+
for (let i = 0; i < arr1.length; i++) {
|
|
1291
|
+
const value1 = arr1[i];
|
|
1292
|
+
const value2 = arr2[i];
|
|
1273
1293
|
if (value1 !== value2) {
|
|
1274
1294
|
return false;
|
|
1275
1295
|
}
|
|
1276
1296
|
}
|
|
1277
1297
|
return true;
|
|
1278
1298
|
}
|
|
1279
|
-
/**
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1299
|
+
/** Enqueues and executes the component associated with the context. */
|
|
1300
|
+
function enqueueComponentRun(ctx) {
|
|
1301
|
+
if (ctx.f & IsAsyncGen) {
|
|
1302
|
+
// This branch will only run for async generator components after the
|
|
1303
|
+
// initial render.
|
|
1304
|
+
//
|
|
1305
|
+
// Async generator components which are in the props loop can be in one of
|
|
1306
|
+
// three states:
|
|
1307
|
+
//
|
|
1308
|
+
// 1. propsAvailable flag is true: "available"
|
|
1309
|
+
//
|
|
1310
|
+
// The component is paused somewhere in the loop. When the component
|
|
1311
|
+
// reaches the bottom of the loop, it will run again with the next props.
|
|
1312
|
+
//
|
|
1313
|
+
// 2. onAvailable callback is defined: "suspended"
|
|
1314
|
+
//
|
|
1315
|
+
// The component has reached the bottom of the loop and is waiting for
|
|
1316
|
+
// new props.
|
|
1317
|
+
//
|
|
1318
|
+
// 3. neither 1 or 2: "Running"
|
|
1319
|
+
//
|
|
1320
|
+
// The component is paused somewhere in the loop. When the component
|
|
1321
|
+
// reaches the bottom of the loop, it will suspend.
|
|
1322
|
+
//
|
|
1323
|
+
// By definition, components will never be both available and suspended at
|
|
1324
|
+
// the same time.
|
|
1325
|
+
//
|
|
1326
|
+
// If the component is at the loop bottom, this means that the next value
|
|
1327
|
+
// produced by the component will have the most up to date props, so we can
|
|
1328
|
+
// simply return the current inflight value. Otherwise, we have to wait for
|
|
1329
|
+
// the bottom of the loop before returning the inflight value.
|
|
1330
|
+
const isAtLoopbottom = ctx.f & IsInRenderLoop && !ctx.onProps;
|
|
1331
|
+
resumePropsIterator(ctx);
|
|
1332
|
+
if (isAtLoopbottom) {
|
|
1333
|
+
if (ctx.inflightBlock == null) {
|
|
1334
|
+
ctx.inflightBlock = new Promise((resolve) => (ctx.onPropsRequested = resolve));
|
|
1335
|
+
}
|
|
1336
|
+
return ctx.inflightBlock.then(() => {
|
|
1337
|
+
ctx.inflightBlock = undefined;
|
|
1338
|
+
return ctx.inflightValue;
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
return ctx.inflightValue;
|
|
1342
|
+
}
|
|
1343
|
+
else if (!ctx.inflightBlock) {
|
|
1297
1344
|
try {
|
|
1298
|
-
const [block, value] =
|
|
1345
|
+
const [block, value] = runComponent(ctx);
|
|
1299
1346
|
if (block) {
|
|
1300
1347
|
ctx.inflightBlock = block
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
}
|
|
1305
|
-
})
|
|
1348
|
+
// TODO: there is some fuckery going on here related to async
|
|
1349
|
+
// generator components resuming when they’re meant to be returned.
|
|
1350
|
+
.then((v) => v)
|
|
1306
1351
|
.finally(() => advanceComponent(ctx));
|
|
1307
1352
|
// stepComponent will only return a block if the value is asynchronous
|
|
1308
1353
|
ctx.inflightValue = value;
|
|
@@ -1316,38 +1361,43 @@ function runComponent(ctx) {
|
|
|
1316
1361
|
throw err;
|
|
1317
1362
|
}
|
|
1318
1363
|
}
|
|
1319
|
-
else if (ctx.f & IsAsyncGen) {
|
|
1320
|
-
return ctx.inflightValue;
|
|
1321
|
-
}
|
|
1322
1364
|
else if (!ctx.enqueuedBlock) {
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1365
|
+
// We need to assign enqueuedBlock and enqueuedValue synchronously, hence
|
|
1366
|
+
// the Promise constructor call.
|
|
1367
|
+
let resolveEnqueuedBlock;
|
|
1368
|
+
ctx.enqueuedBlock = new Promise((resolve) => (resolveEnqueuedBlock = resolve));
|
|
1369
|
+
ctx.enqueuedValue = ctx.inflightBlock.then(() => {
|
|
1326
1370
|
try {
|
|
1327
|
-
const [block, value] =
|
|
1328
|
-
resolve(value);
|
|
1371
|
+
const [block, value] = runComponent(ctx);
|
|
1329
1372
|
if (block) {
|
|
1330
|
-
|
|
1331
|
-
if (!(ctx.f & IsUpdating)) {
|
|
1332
|
-
return propagateError(ctx.parent, err);
|
|
1333
|
-
}
|
|
1334
|
-
});
|
|
1373
|
+
resolveEnqueuedBlock(block.finally(() => advanceComponent(ctx)));
|
|
1335
1374
|
}
|
|
1375
|
+
return value;
|
|
1336
1376
|
}
|
|
1337
1377
|
catch (err) {
|
|
1338
1378
|
if (!(ctx.f & IsUpdating)) {
|
|
1339
1379
|
return propagateError(ctx.parent, err);
|
|
1340
1380
|
}
|
|
1381
|
+
throw err;
|
|
1341
1382
|
}
|
|
1342
|
-
})
|
|
1343
|
-
.finally(() => advanceComponent(ctx));
|
|
1344
|
-
ctx.enqueuedValue = new Promise((resolve1) => (resolve = resolve1));
|
|
1383
|
+
});
|
|
1345
1384
|
}
|
|
1346
1385
|
return ctx.enqueuedValue;
|
|
1347
1386
|
}
|
|
1387
|
+
/** Called when the inflight block promise settles. */
|
|
1388
|
+
function advanceComponent(ctx) {
|
|
1389
|
+
if (ctx.f & IsAsyncGen) {
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
ctx.inflightBlock = ctx.enqueuedBlock;
|
|
1393
|
+
ctx.inflightValue = ctx.enqueuedValue;
|
|
1394
|
+
ctx.enqueuedBlock = undefined;
|
|
1395
|
+
ctx.enqueuedValue = undefined;
|
|
1396
|
+
}
|
|
1348
1397
|
/**
|
|
1349
1398
|
* This function is responsible for executing the component and handling all
|
|
1350
|
-
* the different component types.
|
|
1399
|
+
* the different component types. We cannot identify whether a component is a
|
|
1400
|
+
* generator or async without calling it and inspecting the return value.
|
|
1351
1401
|
*
|
|
1352
1402
|
* @returns {[block, value]} A tuple where
|
|
1353
1403
|
* block - A possible promise which represents the duration during which the
|
|
@@ -1362,14 +1412,15 @@ function runComponent(ctx) {
|
|
|
1362
1412
|
* - Sync generator components block while any children are executing, because
|
|
1363
1413
|
* they are expected to only resume when they’ve actually rendered.
|
|
1364
1414
|
*/
|
|
1365
|
-
function
|
|
1415
|
+
function runComponent(ctx) {
|
|
1366
1416
|
const ret = ctx.ret;
|
|
1367
1417
|
if (ctx.f & IsDone) {
|
|
1368
1418
|
return [undefined, getValue(ret)];
|
|
1369
1419
|
}
|
|
1370
1420
|
const initial = !ctx.iterator;
|
|
1371
1421
|
if (initial) {
|
|
1372
|
-
ctx
|
|
1422
|
+
resumePropsIterator(ctx);
|
|
1423
|
+
ctx.f |= IsSyncExecuting;
|
|
1373
1424
|
clearEventListeners(ctx);
|
|
1374
1425
|
let result;
|
|
1375
1426
|
try {
|
|
@@ -1380,7 +1431,7 @@ function stepComponent(ctx) {
|
|
|
1380
1431
|
throw err;
|
|
1381
1432
|
}
|
|
1382
1433
|
finally {
|
|
1383
|
-
ctx.f &= ~
|
|
1434
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1384
1435
|
}
|
|
1385
1436
|
if (isIteratorLike(result)) {
|
|
1386
1437
|
ctx.iterator = result;
|
|
@@ -1392,120 +1443,179 @@ function stepComponent(ctx) {
|
|
|
1392
1443
|
ctx.f |= IsErrored;
|
|
1393
1444
|
throw err;
|
|
1394
1445
|
});
|
|
1395
|
-
return [result1, value];
|
|
1446
|
+
return [result1.catch(NOOP), value];
|
|
1396
1447
|
}
|
|
1397
1448
|
else {
|
|
1398
1449
|
// sync function component
|
|
1399
1450
|
return [undefined, updateComponentChildren(ctx, result)];
|
|
1400
1451
|
}
|
|
1401
1452
|
}
|
|
1402
|
-
let oldValue;
|
|
1403
|
-
if (initial) {
|
|
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));
|
|
1416
|
-
}
|
|
1417
1453
|
let iteration;
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
if (initial) {
|
|
1454
|
+
if (initial) {
|
|
1455
|
+
try {
|
|
1456
|
+
ctx.f |= IsSyncExecuting;
|
|
1457
|
+
iteration = ctx.iterator.next();
|
|
1458
|
+
}
|
|
1459
|
+
catch (err) {
|
|
1460
|
+
ctx.f |= IsDone | IsErrored;
|
|
1461
|
+
throw err;
|
|
1462
|
+
}
|
|
1463
|
+
finally {
|
|
1464
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1465
|
+
}
|
|
1466
|
+
if (isPromiseLike(iteration)) {
|
|
1432
1467
|
ctx.f |= IsAsyncGen;
|
|
1468
|
+
runAsyncGenComponent(ctx, iteration);
|
|
1433
1469
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1470
|
+
else {
|
|
1471
|
+
ctx.f |= IsSyncGen;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
if (ctx.f & IsSyncGen) {
|
|
1475
|
+
// sync generator component
|
|
1476
|
+
ctx.f &= ~NeedsToYield;
|
|
1477
|
+
if (!initial) {
|
|
1442
1478
|
try {
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
}
|
|
1447
|
-
return value;
|
|
1479
|
+
ctx.f |= IsSyncExecuting;
|
|
1480
|
+
const oldValue = ctx.renderer.read(getValue(ret));
|
|
1481
|
+
iteration = ctx.iterator.next(oldValue);
|
|
1448
1482
|
}
|
|
1449
1483
|
catch (err) {
|
|
1450
|
-
|
|
1484
|
+
ctx.f |= IsDone | IsErrored;
|
|
1485
|
+
throw err;
|
|
1451
1486
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1487
|
+
finally {
|
|
1488
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
if (isPromiseLike(iteration)) {
|
|
1492
|
+
throw new Error("Sync generator component returned an async iteration");
|
|
1493
|
+
}
|
|
1494
|
+
if (iteration.done) {
|
|
1495
|
+
ctx.f |= IsDone;
|
|
1496
|
+
}
|
|
1497
|
+
let value;
|
|
1498
|
+
try {
|
|
1499
|
+
value = updateComponentChildren(ctx,
|
|
1500
|
+
// Children can be void so we eliminate that here
|
|
1501
|
+
iteration.value);
|
|
1502
|
+
if (isPromiseLike(value)) {
|
|
1503
|
+
value = value.catch((err) => handleChildError(ctx, err));
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
catch (err) {
|
|
1507
|
+
value = handleChildError(ctx, err);
|
|
1508
|
+
}
|
|
1469
1509
|
if (isPromiseLike(value)) {
|
|
1470
|
-
|
|
1510
|
+
return [value.catch(NOOP), value];
|
|
1471
1511
|
}
|
|
1512
|
+
return [undefined, value];
|
|
1472
1513
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
if (isPromiseLike(value)) {
|
|
1477
|
-
return [value.catch(NOOP), value];
|
|
1514
|
+
else {
|
|
1515
|
+
// async generator component
|
|
1516
|
+
return [undefined, ctx.inflightValue];
|
|
1478
1517
|
}
|
|
1479
|
-
return [undefined, value];
|
|
1480
1518
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1519
|
+
async function runAsyncGenComponent(ctx, iterationP) {
|
|
1520
|
+
do {
|
|
1521
|
+
// block and value must be assigned at the same time.
|
|
1522
|
+
let onValue;
|
|
1523
|
+
ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
|
|
1524
|
+
if (ctx.f & IsUpdating) {
|
|
1525
|
+
// We should not swallow unhandled promise rejections if the component is
|
|
1526
|
+
// updating independently.
|
|
1527
|
+
// TODO: Does this handle this.refresh() calls?
|
|
1528
|
+
ctx.inflightValue.catch(NOOP);
|
|
1529
|
+
}
|
|
1530
|
+
let iteration;
|
|
1531
|
+
try {
|
|
1532
|
+
iteration = await iterationP;
|
|
1533
|
+
}
|
|
1534
|
+
catch (err) {
|
|
1535
|
+
ctx.f |= IsDone;
|
|
1536
|
+
ctx.f |= IsErrored;
|
|
1537
|
+
onValue(Promise.reject(err));
|
|
1538
|
+
break;
|
|
1539
|
+
}
|
|
1540
|
+
finally {
|
|
1541
|
+
if (!(ctx.f & IsInRenderLoop)) {
|
|
1542
|
+
ctx.f &= ~PropsAvailable;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
if (ctx.f & NeedsToYield) {
|
|
1546
|
+
ctx.f &= ~NeedsToYield;
|
|
1547
|
+
}
|
|
1548
|
+
if (iteration.done) {
|
|
1549
|
+
ctx.f |= IsDone;
|
|
1550
|
+
}
|
|
1551
|
+
let value;
|
|
1552
|
+
try {
|
|
1553
|
+
value = updateComponentChildren(ctx, iteration.value);
|
|
1554
|
+
if (isPromiseLike(value)) {
|
|
1555
|
+
value = value.catch((err) => handleChildError(ctx, err));
|
|
1556
|
+
}
|
|
1557
|
+
onValue(value);
|
|
1558
|
+
}
|
|
1559
|
+
catch (err) {
|
|
1560
|
+
// Do we need to catch potential errors here in the case of unhandled
|
|
1561
|
+
// promise rejections?
|
|
1562
|
+
value = handleChildError(ctx, err);
|
|
1563
|
+
}
|
|
1564
|
+
// TODO: this can be done more elegantly
|
|
1565
|
+
let oldValue;
|
|
1566
|
+
if (ctx.ret.inflightValue) {
|
|
1567
|
+
// The value passed back into the generator as the argument to the next
|
|
1568
|
+
// method is a promise if an async generator component has async
|
|
1569
|
+
// children. Sync generator components only resume when their children
|
|
1570
|
+
// have fulfilled so the element’s inflight child values will never be
|
|
1571
|
+
// defined.
|
|
1572
|
+
oldValue = ctx.ret.inflightValue.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
|
|
1573
|
+
}
|
|
1574
|
+
else {
|
|
1575
|
+
oldValue = ctx.renderer.read(getValue(ctx.ret));
|
|
1576
|
+
}
|
|
1577
|
+
if (ctx.f & IsUnmounted) {
|
|
1578
|
+
if (ctx.f & IsInRenderLoop) {
|
|
1579
|
+
try {
|
|
1580
|
+
ctx.f |= IsSyncExecuting;
|
|
1581
|
+
iterationP = ctx.iterator.next(oldValue);
|
|
1582
|
+
}
|
|
1583
|
+
finally {
|
|
1584
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
else {
|
|
1588
|
+
returnComponent(ctx);
|
|
1589
|
+
break;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
else if (!(ctx.f & IsDone)) {
|
|
1593
|
+
try {
|
|
1594
|
+
ctx.f |= IsSyncExecuting;
|
|
1595
|
+
iterationP = ctx.iterator.next(oldValue);
|
|
1596
|
+
}
|
|
1597
|
+
finally {
|
|
1598
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
} while (!(ctx.f & IsDone));
|
|
1492
1602
|
}
|
|
1493
1603
|
/**
|
|
1494
1604
|
* Called to make props available to the props async iterator for async
|
|
1495
1605
|
* generator components.
|
|
1496
1606
|
*/
|
|
1497
|
-
function
|
|
1498
|
-
if (ctx.
|
|
1499
|
-
ctx.
|
|
1500
|
-
ctx.
|
|
1607
|
+
function resumePropsIterator(ctx) {
|
|
1608
|
+
if (ctx.onProps) {
|
|
1609
|
+
ctx.onProps(ctx.ret.el.props);
|
|
1610
|
+
ctx.onProps = undefined;
|
|
1611
|
+
ctx.f &= ~PropsAvailable;
|
|
1501
1612
|
}
|
|
1502
1613
|
else {
|
|
1503
|
-
ctx.f |=
|
|
1614
|
+
ctx.f |= PropsAvailable;
|
|
1504
1615
|
}
|
|
1505
1616
|
}
|
|
1506
1617
|
// TODO: async unmounting
|
|
1507
1618
|
function unmountComponent(ctx) {
|
|
1508
|
-
ctx.f |= IsUnmounted;
|
|
1509
1619
|
clearEventListeners(ctx);
|
|
1510
1620
|
const callbacks = cleanupMap.get(ctx);
|
|
1511
1621
|
if (callbacks) {
|
|
@@ -1515,22 +1625,55 @@ function unmountComponent(ctx) {
|
|
|
1515
1625
|
callback(value);
|
|
1516
1626
|
}
|
|
1517
1627
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
ctx.f
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1628
|
+
ctx.f |= IsUnmounted;
|
|
1629
|
+
if (ctx.iterator && !(ctx.f & IsDone)) {
|
|
1630
|
+
if (ctx.f & IsSyncGen) {
|
|
1631
|
+
let value;
|
|
1632
|
+
if (ctx.f & IsInRenderLoop) {
|
|
1633
|
+
value = enqueueComponentRun(ctx);
|
|
1634
|
+
}
|
|
1635
|
+
if (isPromiseLike(value)) {
|
|
1636
|
+
value.then(() => {
|
|
1637
|
+
if (ctx.f & IsInRenderLoop) {
|
|
1638
|
+
unmountComponent(ctx);
|
|
1639
|
+
}
|
|
1640
|
+
else {
|
|
1641
|
+
returnComponent(ctx);
|
|
1642
|
+
}
|
|
1643
|
+
}, (err) => {
|
|
1644
|
+
propagateError(ctx.parent, err);
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
else {
|
|
1648
|
+
if (ctx.f & IsInRenderLoop) {
|
|
1649
|
+
unmountComponent(ctx);
|
|
1650
|
+
}
|
|
1651
|
+
else {
|
|
1652
|
+
returnComponent(ctx);
|
|
1527
1653
|
}
|
|
1528
1654
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1655
|
+
}
|
|
1656
|
+
else {
|
|
1657
|
+
// async generator component
|
|
1658
|
+
resumePropsIterator(ctx);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
function returnComponent(ctx) {
|
|
1663
|
+
resumePropsIterator(ctx);
|
|
1664
|
+
if (!(ctx.f & IsDone) && typeof ctx.iterator.return === "function") {
|
|
1665
|
+
ctx.f |= IsSyncExecuting;
|
|
1666
|
+
try {
|
|
1667
|
+
const iteration = ctx.iterator.return();
|
|
1668
|
+
if (isPromiseLike(iteration)) {
|
|
1669
|
+
iteration.catch((err) => propagateError(ctx.parent, err));
|
|
1531
1670
|
}
|
|
1532
1671
|
}
|
|
1672
|
+
finally {
|
|
1673
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1674
|
+
}
|
|
1533
1675
|
}
|
|
1676
|
+
ctx.f |= IsDone;
|
|
1534
1677
|
}
|
|
1535
1678
|
/*** EVENT TARGET UTILITIES ***/
|
|
1536
1679
|
// EVENT PHASE CONSTANTS
|
|
@@ -1607,10 +1750,10 @@ function handleChildError(ctx, err) {
|
|
|
1607
1750
|
typeof ctx.iterator.throw !== "function") {
|
|
1608
1751
|
throw err;
|
|
1609
1752
|
}
|
|
1610
|
-
|
|
1753
|
+
resumePropsIterator(ctx);
|
|
1611
1754
|
let iteration;
|
|
1612
1755
|
try {
|
|
1613
|
-
ctx.f |=
|
|
1756
|
+
ctx.f |= IsSyncExecuting;
|
|
1614
1757
|
iteration = ctx.iterator.throw(err);
|
|
1615
1758
|
}
|
|
1616
1759
|
catch (err) {
|
|
@@ -1618,7 +1761,7 @@ function handleChildError(ctx, err) {
|
|
|
1618
1761
|
throw err;
|
|
1619
1762
|
}
|
|
1620
1763
|
finally {
|
|
1621
|
-
ctx.f &= ~
|
|
1764
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1622
1765
|
}
|
|
1623
1766
|
if (isPromiseLike(iteration)) {
|
|
1624
1767
|
return iteration.then((iteration) => {
|