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