@b9g/crank 0.5.0-beta.2 → 0.5.0-beta.4
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 +506 -349
- package/crank.cjs.map +1 -1
- package/crank.d.ts +29 -54
- package/crank.js +506 -349
- 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 +8 -8
- 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 +506 -349
- 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/umd.js
CHANGED
|
@@ -163,22 +163,6 @@
|
|
|
163
163
|
else if (children.length === 1) {
|
|
164
164
|
props1.children = children[0];
|
|
165
165
|
}
|
|
166
|
-
// string aliases for the special tags
|
|
167
|
-
// TODO: Does this logic belong here, or in the Element constructor
|
|
168
|
-
switch (tag) {
|
|
169
|
-
case "$FRAGMENT":
|
|
170
|
-
tag = Fragment;
|
|
171
|
-
break;
|
|
172
|
-
case "$PORTAL":
|
|
173
|
-
tag = Portal;
|
|
174
|
-
break;
|
|
175
|
-
case "$COPY":
|
|
176
|
-
tag = Copy;
|
|
177
|
-
break;
|
|
178
|
-
case "$RAW":
|
|
179
|
-
tag = Raw;
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
166
|
return new Element(tag, props1, key, ref, static_);
|
|
183
167
|
}
|
|
184
168
|
/** Clones a given element, shallowly copying the props object. */
|
|
@@ -257,13 +241,13 @@
|
|
|
257
241
|
class Retainer {
|
|
258
242
|
constructor(el) {
|
|
259
243
|
this.el = el;
|
|
260
|
-
this.value = undefined;
|
|
261
244
|
this.ctx = undefined;
|
|
262
245
|
this.children = undefined;
|
|
263
|
-
this.
|
|
264
|
-
this.
|
|
265
|
-
this.
|
|
266
|
-
this.
|
|
246
|
+
this.value = undefined;
|
|
247
|
+
this.cachedChildValues = undefined;
|
|
248
|
+
this.fallbackValue = undefined;
|
|
249
|
+
this.inflightValue = undefined;
|
|
250
|
+
this.onNextValues = undefined;
|
|
267
251
|
}
|
|
268
252
|
}
|
|
269
253
|
/**
|
|
@@ -272,10 +256,10 @@
|
|
|
272
256
|
* @returns The value of the element.
|
|
273
257
|
*/
|
|
274
258
|
function getValue(ret) {
|
|
275
|
-
if (typeof ret.
|
|
276
|
-
return typeof ret.
|
|
277
|
-
? getValue(ret.
|
|
278
|
-
: ret.
|
|
259
|
+
if (typeof ret.fallbackValue !== "undefined") {
|
|
260
|
+
return typeof ret.fallbackValue === "object"
|
|
261
|
+
? getValue(ret.fallbackValue)
|
|
262
|
+
: ret.fallbackValue;
|
|
279
263
|
}
|
|
280
264
|
else if (ret.el.tag === Portal) {
|
|
281
265
|
return;
|
|
@@ -291,8 +275,8 @@
|
|
|
291
275
|
* @returns A normalized array of nodes and strings.
|
|
292
276
|
*/
|
|
293
277
|
function getChildValues(ret) {
|
|
294
|
-
if (ret.
|
|
295
|
-
return wrap(ret.
|
|
278
|
+
if (ret.cachedChildValues) {
|
|
279
|
+
return wrap(ret.cachedChildValues);
|
|
296
280
|
}
|
|
297
281
|
const values = [];
|
|
298
282
|
const children = wrap(ret.children);
|
|
@@ -305,7 +289,7 @@
|
|
|
305
289
|
const values1 = normalize(values);
|
|
306
290
|
const tag = ret.el.tag;
|
|
307
291
|
if (typeof tag === "function" || (tag !== Fragment && tag !== Raw)) {
|
|
308
|
-
ret.
|
|
292
|
+
ret.cachedChildValues = unwrap(values1);
|
|
309
293
|
}
|
|
310
294
|
return values1;
|
|
311
295
|
}
|
|
@@ -322,7 +306,7 @@
|
|
|
322
306
|
dispose: NOOP,
|
|
323
307
|
flush: NOOP,
|
|
324
308
|
};
|
|
325
|
-
const
|
|
309
|
+
const _RendererImpl = Symbol.for("crank.RendererImpl");
|
|
326
310
|
/**
|
|
327
311
|
* An abstract class which is subclassed to render to different target
|
|
328
312
|
* environments. This class is responsible for kicking off the rendering
|
|
@@ -336,7 +320,7 @@
|
|
|
336
320
|
class Renderer {
|
|
337
321
|
constructor(impl) {
|
|
338
322
|
this.cache = new WeakMap();
|
|
339
|
-
this[
|
|
323
|
+
this[_RendererImpl] = {
|
|
340
324
|
...defaultRendererImpl,
|
|
341
325
|
...impl,
|
|
342
326
|
};
|
|
@@ -348,7 +332,7 @@
|
|
|
348
332
|
* used root to delete the previously rendered element tree from the cache.
|
|
349
333
|
* @param root - The node to be rendered into. The renderer will cache
|
|
350
334
|
* element trees per root.
|
|
351
|
-
* @param
|
|
335
|
+
* @param bridge - An optional context that will be the ancestor context of all
|
|
352
336
|
* elements in the tree. Useful for connecting different renderers so that
|
|
353
337
|
* events/provisions properly propagate. The context for a given root must be
|
|
354
338
|
* the same or an error will be thrown.
|
|
@@ -358,7 +342,7 @@
|
|
|
358
342
|
*/
|
|
359
343
|
render(children, root, bridge) {
|
|
360
344
|
let ret;
|
|
361
|
-
const ctx = bridge && bridge[
|
|
345
|
+
const ctx = bridge && bridge[_ContextImpl];
|
|
362
346
|
if (typeof root === "object" && root !== null) {
|
|
363
347
|
ret = this.cache.get(root);
|
|
364
348
|
}
|
|
@@ -381,7 +365,7 @@
|
|
|
381
365
|
this.cache.delete(root);
|
|
382
366
|
}
|
|
383
367
|
}
|
|
384
|
-
const impl = this[
|
|
368
|
+
const impl = this[_RendererImpl];
|
|
385
369
|
const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children);
|
|
386
370
|
// We return the child values of the portal because portal elements
|
|
387
371
|
// themselves have no readable value.
|
|
@@ -394,15 +378,15 @@
|
|
|
394
378
|
/*** PRIVATE RENDERER FUNCTIONS ***/
|
|
395
379
|
function commitRootRender(renderer, root, ctx, ret, childValues, oldProps) {
|
|
396
380
|
// element is a host or portal element
|
|
397
|
-
if (root
|
|
398
|
-
renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.
|
|
381
|
+
if (root != null) {
|
|
382
|
+
renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.cachedChildValues));
|
|
399
383
|
flush(renderer, root);
|
|
400
384
|
}
|
|
401
|
-
ret.
|
|
385
|
+
ret.cachedChildValues = unwrap(childValues);
|
|
402
386
|
if (root == null) {
|
|
403
387
|
unmount(renderer, ret, ctx, ret);
|
|
404
388
|
}
|
|
405
|
-
return renderer.read(ret.
|
|
389
|
+
return renderer.read(ret.cachedChildValues);
|
|
406
390
|
}
|
|
407
391
|
function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
408
392
|
const oldRetained = wrap(parent.children);
|
|
@@ -474,7 +458,7 @@
|
|
|
474
458
|
}
|
|
475
459
|
const fallback = ret;
|
|
476
460
|
ret = new Retainer(child);
|
|
477
|
-
ret.
|
|
461
|
+
ret.fallbackValue = fallback;
|
|
478
462
|
}
|
|
479
463
|
if (child.tag === Raw) {
|
|
480
464
|
value = updateRaw(renderer, ret, scope, oldProps);
|
|
@@ -542,27 +526,29 @@
|
|
|
542
526
|
childValues1,
|
|
543
527
|
new Promise((resolve) => (onChildValues = resolve)),
|
|
544
528
|
]);
|
|
545
|
-
if (parent.
|
|
546
|
-
parent.
|
|
529
|
+
if (parent.onNextValues) {
|
|
530
|
+
parent.onNextValues(childValues1);
|
|
547
531
|
}
|
|
548
|
-
parent.
|
|
532
|
+
parent.onNextValues = onChildValues;
|
|
549
533
|
return childValues1.then((childValues) => {
|
|
550
|
-
parent.
|
|
534
|
+
parent.inflightValue = parent.fallbackValue = undefined;
|
|
551
535
|
return normalize(childValues);
|
|
552
536
|
});
|
|
553
537
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
538
|
+
else {
|
|
539
|
+
if (graveyard) {
|
|
540
|
+
for (let i = 0; i < graveyard.length; i++) {
|
|
541
|
+
unmount(renderer, host, ctx, graveyard[i]);
|
|
542
|
+
}
|
|
557
543
|
}
|
|
544
|
+
if (parent.onNextValues) {
|
|
545
|
+
parent.onNextValues(values);
|
|
546
|
+
parent.onNextValues = undefined;
|
|
547
|
+
}
|
|
548
|
+
parent.inflightValue = parent.fallbackValue = undefined;
|
|
549
|
+
// We can assert there are no promises in the array because isAsync is false
|
|
550
|
+
return normalize(values);
|
|
558
551
|
}
|
|
559
|
-
if (parent.onCommit) {
|
|
560
|
-
parent.onCommit(values);
|
|
561
|
-
parent.onCommit = undefined;
|
|
562
|
-
}
|
|
563
|
-
parent.inflight = parent.fallback = undefined;
|
|
564
|
-
// We can assert there are no promises in the array because isAsync is false
|
|
565
|
-
return normalize(values);
|
|
566
552
|
}
|
|
567
553
|
function createChildrenByKey(children, offset) {
|
|
568
554
|
const childrenByKey = new Map();
|
|
@@ -582,8 +568,8 @@
|
|
|
582
568
|
if (ctx && ctx.f & IsUpdating && ctx.inflightValue) {
|
|
583
569
|
return ctx.inflightValue;
|
|
584
570
|
}
|
|
585
|
-
else if (child.
|
|
586
|
-
return child.
|
|
571
|
+
else if (child.inflightValue) {
|
|
572
|
+
return child.inflightValue;
|
|
587
573
|
}
|
|
588
574
|
return getValue(child);
|
|
589
575
|
}
|
|
@@ -602,8 +588,8 @@
|
|
|
602
588
|
function updateFragment(renderer, root, host, ctx, scope, ret) {
|
|
603
589
|
const childValues = diffChildren(renderer, root, host, ctx, scope, ret, ret.el.props.children);
|
|
604
590
|
if (isPromiseLike(childValues)) {
|
|
605
|
-
ret.
|
|
606
|
-
return ret.
|
|
591
|
+
ret.inflightValue = childValues.then((childValues) => unwrap(childValues));
|
|
592
|
+
return ret.inflightValue;
|
|
607
593
|
}
|
|
608
594
|
return unwrap(childValues);
|
|
609
595
|
}
|
|
@@ -620,8 +606,8 @@
|
|
|
620
606
|
scope = renderer.scope(scope, tag, el.props);
|
|
621
607
|
const childValues = diffChildren(renderer, root, ret, ctx, scope, ret, ret.el.props.children);
|
|
622
608
|
if (isPromiseLike(childValues)) {
|
|
623
|
-
ret.
|
|
624
|
-
return ret.
|
|
609
|
+
ret.inflightValue = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps));
|
|
610
|
+
return ret.inflightValue;
|
|
625
611
|
}
|
|
626
612
|
return commitHost(renderer, scope, ret, childValues, oldProps);
|
|
627
613
|
}
|
|
@@ -648,8 +634,8 @@
|
|
|
648
634
|
}
|
|
649
635
|
ret.el = new Element(tag, props, ret.el.key, ret.el.ref);
|
|
650
636
|
}
|
|
651
|
-
renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.
|
|
652
|
-
ret.
|
|
637
|
+
renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.cachedChildValues));
|
|
638
|
+
ret.cachedChildValues = unwrap(childValues);
|
|
653
639
|
if (tag === Portal) {
|
|
654
640
|
flush(renderer, ret.value);
|
|
655
641
|
return;
|
|
@@ -696,7 +682,7 @@
|
|
|
696
682
|
}
|
|
697
683
|
else if (ret.el.tag === Portal) {
|
|
698
684
|
host = ret;
|
|
699
|
-
renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.
|
|
685
|
+
renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.cachedChildValues));
|
|
700
686
|
flush(renderer, host.value);
|
|
701
687
|
}
|
|
702
688
|
else if (ret.el.tag !== Fragment) {
|
|
@@ -720,38 +706,39 @@
|
|
|
720
706
|
}
|
|
721
707
|
/*** CONTEXT FLAGS ***/
|
|
722
708
|
/**
|
|
723
|
-
* A flag which is
|
|
724
|
-
*
|
|
725
|
-
*
|
|
709
|
+
* A flag which is true when the component is initialized or updated by an
|
|
710
|
+
* ancestor component or the root render call.
|
|
711
|
+
*
|
|
712
|
+
* Used to determine things like whether the nearest host ancestor needs to be
|
|
713
|
+
* rearranged.
|
|
726
714
|
*/
|
|
727
715
|
const IsUpdating = 1 << 0;
|
|
728
716
|
/**
|
|
729
|
-
* A flag which is
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
* overflow or a generator error.
|
|
717
|
+
* A flag which is true when the component is synchronously executing.
|
|
718
|
+
*
|
|
719
|
+
* Used to guard against components triggering stack overflow or generator error.
|
|
733
720
|
*/
|
|
734
|
-
const
|
|
721
|
+
const IsSyncExecuting = 1 << 1;
|
|
735
722
|
/**
|
|
736
|
-
* A flag
|
|
737
|
-
* iterators without a yield.
|
|
723
|
+
* A flag which is true when the component is in the render loop.
|
|
738
724
|
*/
|
|
739
|
-
const
|
|
725
|
+
const IsInRenderLoop = 1 << 2;
|
|
740
726
|
/**
|
|
741
|
-
* A flag
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
*
|
|
727
|
+
* A flag which is true when the component starts the render loop but has not
|
|
728
|
+
* yielded yet.
|
|
729
|
+
*
|
|
730
|
+
* Used to make sure that components yield at least once per loop.
|
|
745
731
|
*/
|
|
746
|
-
const
|
|
732
|
+
const NeedsToYield = 1 << 3;
|
|
747
733
|
/**
|
|
748
|
-
* A flag
|
|
749
|
-
*
|
|
750
|
-
*
|
|
734
|
+
* A flag used by async generator components in conjunction with the
|
|
735
|
+
* onAvailable callback to mark whether new props can be pulled via the context
|
|
736
|
+
* async iterator. See the Symbol.asyncIterator method and the
|
|
737
|
+
* resumeCtxIterator function.
|
|
751
738
|
*/
|
|
752
|
-
const
|
|
739
|
+
const PropsAvailable = 1 << 4;
|
|
753
740
|
/**
|
|
754
|
-
* A flag which is set when a
|
|
741
|
+
* A flag which is set when a component errors.
|
|
755
742
|
*
|
|
756
743
|
* NOTE: This is mainly used to prevent some false positives in component
|
|
757
744
|
* yields or returns undefined warnings. The reason we’re using this versus
|
|
@@ -759,28 +746,28 @@
|
|
|
759
746
|
* sync generator child) where synchronous code causes a stack overflow error
|
|
760
747
|
* in a non-deterministic way. Deeply disturbing stuff.
|
|
761
748
|
*/
|
|
762
|
-
const IsErrored = 1 <<
|
|
749
|
+
const IsErrored = 1 << 6;
|
|
763
750
|
/**
|
|
764
751
|
* A flag which is set when the component is unmounted. Unmounted components
|
|
765
752
|
* are no longer in the element tree and cannot refresh or rerender.
|
|
766
753
|
*/
|
|
767
|
-
const IsUnmounted = 1 <<
|
|
754
|
+
const IsUnmounted = 1 << 7;
|
|
768
755
|
/**
|
|
769
756
|
* A flag which indicates that the component is a sync generator component.
|
|
770
757
|
*/
|
|
771
|
-
const IsSyncGen = 1 <<
|
|
758
|
+
const IsSyncGen = 1 << 8;
|
|
772
759
|
/**
|
|
773
760
|
* A flag which indicates that the component is an async generator component.
|
|
774
761
|
*/
|
|
775
|
-
const IsAsyncGen = 1 <<
|
|
762
|
+
const IsAsyncGen = 1 << 9;
|
|
776
763
|
/**
|
|
777
764
|
* A flag which is set while schedule callbacks are called.
|
|
778
765
|
*/
|
|
779
|
-
const IsScheduling = 1 <<
|
|
766
|
+
const IsScheduling = 1 << 10;
|
|
780
767
|
/**
|
|
781
768
|
* A flag which is set when a schedule callback calls refresh.
|
|
782
769
|
*/
|
|
783
|
-
const IsSchedulingRefresh = 1 <<
|
|
770
|
+
const IsSchedulingRefresh = 1 << 11;
|
|
784
771
|
const provisionMaps = new WeakMap();
|
|
785
772
|
const scheduleMap = new WeakMap();
|
|
786
773
|
const cleanupMap = new WeakMap();
|
|
@@ -788,12 +775,12 @@
|
|
|
788
775
|
const flushMaps = new WeakMap();
|
|
789
776
|
/**
|
|
790
777
|
* @internal
|
|
791
|
-
* The internal class which holds
|
|
778
|
+
* The internal class which holds context data.
|
|
792
779
|
*/
|
|
793
780
|
class ContextImpl {
|
|
794
781
|
constructor(renderer, root, host, parent, scope, ret) {
|
|
795
782
|
this.f = 0;
|
|
796
|
-
this.
|
|
783
|
+
this.owner = new Context(this);
|
|
797
784
|
this.renderer = renderer;
|
|
798
785
|
this.root = root;
|
|
799
786
|
this.host = host;
|
|
@@ -805,10 +792,11 @@
|
|
|
805
792
|
this.inflightValue = undefined;
|
|
806
793
|
this.enqueuedBlock = undefined;
|
|
807
794
|
this.enqueuedValue = undefined;
|
|
808
|
-
this.
|
|
795
|
+
this.onProps = undefined;
|
|
796
|
+
this.onPropsRequested = undefined;
|
|
809
797
|
}
|
|
810
798
|
}
|
|
811
|
-
const
|
|
799
|
+
const _ContextImpl = Symbol.for("crank.ContextImpl");
|
|
812
800
|
/**
|
|
813
801
|
* A class which is instantiated and passed to every component as its this
|
|
814
802
|
* value. Contexts form a tree just like elements and all components in the
|
|
@@ -822,8 +810,10 @@
|
|
|
822
810
|
* schedule and cleanup callbacks.
|
|
823
811
|
*/
|
|
824
812
|
class Context {
|
|
813
|
+
// TODO: If we could make the constructor function take a nicer value, it
|
|
814
|
+
// would be useful for testing purposes.
|
|
825
815
|
constructor(impl) {
|
|
826
|
-
this[
|
|
816
|
+
this[_ContextImpl] = impl;
|
|
827
817
|
}
|
|
828
818
|
/**
|
|
829
819
|
* The current props of the associated element.
|
|
@@ -833,7 +823,7 @@
|
|
|
833
823
|
* plugins or utilities which wrap contexts.
|
|
834
824
|
*/
|
|
835
825
|
get props() {
|
|
836
|
-
return this[
|
|
826
|
+
return this[_ContextImpl].ret.el.props;
|
|
837
827
|
}
|
|
838
828
|
// TODO: Should we rename this???
|
|
839
829
|
/**
|
|
@@ -844,45 +834,70 @@
|
|
|
844
834
|
* mainly for plugins or utilities which wrap contexts.
|
|
845
835
|
*/
|
|
846
836
|
get value() {
|
|
847
|
-
return this[
|
|
837
|
+
return this[_ContextImpl].renderer.read(getValue(this[_ContextImpl].ret));
|
|
848
838
|
}
|
|
849
839
|
*[Symbol.iterator]() {
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
840
|
+
const ctx = this[_ContextImpl];
|
|
841
|
+
if (ctx.f & IsAsyncGen) {
|
|
842
|
+
throw new Error("Use for await…of in async generator components");
|
|
843
|
+
}
|
|
844
|
+
try {
|
|
845
|
+
ctx.f |= IsInRenderLoop;
|
|
846
|
+
while (!(ctx.f & IsUnmounted)) {
|
|
847
|
+
if (ctx.f & NeedsToYield) {
|
|
848
|
+
throw new Error("Context iterated twice without a yield");
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
ctx.f |= NeedsToYield;
|
|
852
|
+
}
|
|
853
|
+
yield ctx.ret.el.props;
|
|
857
854
|
}
|
|
858
|
-
|
|
859
|
-
|
|
855
|
+
}
|
|
856
|
+
finally {
|
|
857
|
+
ctx.f &= ~IsInRenderLoop;
|
|
860
858
|
}
|
|
861
859
|
}
|
|
862
860
|
async *[Symbol.asyncIterator]() {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
861
|
+
const ctx = this[_ContextImpl];
|
|
862
|
+
if (ctx.f & IsSyncGen) {
|
|
863
|
+
throw new Error("Use for…of in sync generator components");
|
|
864
|
+
}
|
|
865
|
+
try {
|
|
866
|
+
// await an empty promise to prevent the IsInRenderLoop flag from
|
|
867
|
+
// returning false positives in the case of async generator components
|
|
868
|
+
// which immediately enter the loop
|
|
869
|
+
ctx.f |= IsInRenderLoop;
|
|
870
|
+
while (!(ctx.f & IsUnmounted)) {
|
|
871
|
+
if (ctx.f & NeedsToYield) {
|
|
872
|
+
throw new Error("Context iterated twice without a yield");
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
ctx.f |= NeedsToYield;
|
|
876
|
+
}
|
|
877
|
+
if (ctx.f & PropsAvailable) {
|
|
878
|
+
ctx.f &= ~PropsAvailable;
|
|
879
|
+
yield ctx.ret.el.props;
|
|
882
880
|
}
|
|
881
|
+
else {
|
|
882
|
+
const props = await new Promise((resolve) => (ctx.onProps = resolve));
|
|
883
|
+
if (ctx.f & IsUnmounted) {
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
yield props;
|
|
887
|
+
}
|
|
888
|
+
if (ctx.onPropsRequested) {
|
|
889
|
+
ctx.onPropsRequested();
|
|
890
|
+
ctx.onPropsRequested = undefined;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
finally {
|
|
895
|
+
ctx.f &= ~IsInRenderLoop;
|
|
896
|
+
if (ctx.onPropsRequested) {
|
|
897
|
+
ctx.onPropsRequested();
|
|
898
|
+
ctx.onPropsRequested = undefined;
|
|
883
899
|
}
|
|
884
|
-
|
|
885
|
-
} while (!(impl.f & IsDone));
|
|
900
|
+
}
|
|
886
901
|
}
|
|
887
902
|
/**
|
|
888
903
|
* Re-executes a component.
|
|
@@ -897,32 +912,31 @@
|
|
|
897
912
|
* async iterator to suspend.
|
|
898
913
|
*/
|
|
899
914
|
refresh() {
|
|
900
|
-
const
|
|
901
|
-
if (
|
|
915
|
+
const ctx = this[_ContextImpl];
|
|
916
|
+
if (ctx.f & IsUnmounted) {
|
|
902
917
|
console.error("Component is unmounted");
|
|
903
|
-
return
|
|
918
|
+
return ctx.renderer.read(undefined);
|
|
904
919
|
}
|
|
905
|
-
else if (
|
|
920
|
+
else if (ctx.f & IsSyncExecuting) {
|
|
906
921
|
console.error("Component is already executing");
|
|
907
922
|
return this.value;
|
|
908
923
|
}
|
|
909
|
-
|
|
910
|
-
const value = runComponent(impl);
|
|
924
|
+
const value = enqueueComponentRun(ctx);
|
|
911
925
|
if (isPromiseLike(value)) {
|
|
912
|
-
return value.then((value) =>
|
|
926
|
+
return value.then((value) => ctx.renderer.read(value));
|
|
913
927
|
}
|
|
914
|
-
return
|
|
928
|
+
return ctx.renderer.read(value);
|
|
915
929
|
}
|
|
916
930
|
/**
|
|
917
931
|
* Registers a callback which fires when the component commits. Will only
|
|
918
932
|
* fire once per callback and update.
|
|
919
933
|
*/
|
|
920
934
|
schedule(callback) {
|
|
921
|
-
const
|
|
922
|
-
let callbacks = scheduleMap.get(
|
|
935
|
+
const ctx = this[_ContextImpl];
|
|
936
|
+
let callbacks = scheduleMap.get(ctx);
|
|
923
937
|
if (!callbacks) {
|
|
924
938
|
callbacks = new Set();
|
|
925
|
-
scheduleMap.set(
|
|
939
|
+
scheduleMap.set(ctx, callbacks);
|
|
926
940
|
}
|
|
927
941
|
callbacks.add(callback);
|
|
928
942
|
}
|
|
@@ -931,19 +945,19 @@
|
|
|
931
945
|
* rendered into the root. Will only fire once per callback and render.
|
|
932
946
|
*/
|
|
933
947
|
flush(callback) {
|
|
934
|
-
const
|
|
935
|
-
if (typeof
|
|
948
|
+
const ctx = this[_ContextImpl];
|
|
949
|
+
if (typeof ctx.root !== "object" || ctx.root === null) {
|
|
936
950
|
return;
|
|
937
951
|
}
|
|
938
|
-
let flushMap = flushMaps.get(
|
|
952
|
+
let flushMap = flushMaps.get(ctx.root);
|
|
939
953
|
if (!flushMap) {
|
|
940
954
|
flushMap = new Map();
|
|
941
|
-
flushMaps.set(
|
|
955
|
+
flushMaps.set(ctx.root, flushMap);
|
|
942
956
|
}
|
|
943
|
-
let callbacks = flushMap.get(
|
|
957
|
+
let callbacks = flushMap.get(ctx);
|
|
944
958
|
if (!callbacks) {
|
|
945
959
|
callbacks = new Set();
|
|
946
|
-
flushMap.set(
|
|
960
|
+
flushMap.set(ctx, callbacks);
|
|
947
961
|
}
|
|
948
962
|
callbacks.add(callback);
|
|
949
963
|
}
|
|
@@ -952,45 +966,45 @@
|
|
|
952
966
|
* fire once per callback.
|
|
953
967
|
*/
|
|
954
968
|
cleanup(callback) {
|
|
955
|
-
const
|
|
956
|
-
let callbacks = cleanupMap.get(
|
|
969
|
+
const ctx = this[_ContextImpl];
|
|
970
|
+
let callbacks = cleanupMap.get(ctx);
|
|
957
971
|
if (!callbacks) {
|
|
958
972
|
callbacks = new Set();
|
|
959
|
-
cleanupMap.set(
|
|
973
|
+
cleanupMap.set(ctx, callbacks);
|
|
960
974
|
}
|
|
961
975
|
callbacks.add(callback);
|
|
962
976
|
}
|
|
963
977
|
consume(key) {
|
|
964
|
-
for (let
|
|
965
|
-
const provisions = provisionMaps.get(
|
|
978
|
+
for (let ctx = this[_ContextImpl].parent; ctx !== undefined; ctx = ctx.parent) {
|
|
979
|
+
const provisions = provisionMaps.get(ctx);
|
|
966
980
|
if (provisions && provisions.has(key)) {
|
|
967
981
|
return provisions.get(key);
|
|
968
982
|
}
|
|
969
983
|
}
|
|
970
984
|
}
|
|
971
985
|
provide(key, value) {
|
|
972
|
-
const
|
|
973
|
-
let provisions = provisionMaps.get(
|
|
986
|
+
const ctx = this[_ContextImpl];
|
|
987
|
+
let provisions = provisionMaps.get(ctx);
|
|
974
988
|
if (!provisions) {
|
|
975
989
|
provisions = new Map();
|
|
976
|
-
provisionMaps.set(
|
|
990
|
+
provisionMaps.set(ctx, provisions);
|
|
977
991
|
}
|
|
978
992
|
provisions.set(key, value);
|
|
979
993
|
}
|
|
980
994
|
addEventListener(type, listener, options) {
|
|
981
|
-
const
|
|
995
|
+
const ctx = this[_ContextImpl];
|
|
982
996
|
let listeners;
|
|
983
997
|
if (!isListenerOrListenerObject(listener)) {
|
|
984
998
|
return;
|
|
985
999
|
}
|
|
986
1000
|
else {
|
|
987
|
-
const listeners1 = listenersMap.get(
|
|
1001
|
+
const listeners1 = listenersMap.get(ctx);
|
|
988
1002
|
if (listeners1) {
|
|
989
1003
|
listeners = listeners1;
|
|
990
1004
|
}
|
|
991
1005
|
else {
|
|
992
1006
|
listeners = [];
|
|
993
|
-
listenersMap.set(
|
|
1007
|
+
listenersMap.set(ctx, listeners);
|
|
994
1008
|
}
|
|
995
1009
|
}
|
|
996
1010
|
options = normalizeListenerOptions(options);
|
|
@@ -1001,7 +1015,7 @@
|
|
|
1001
1015
|
else {
|
|
1002
1016
|
callback = listener;
|
|
1003
1017
|
}
|
|
1004
|
-
const record = { type,
|
|
1018
|
+
const record = { type, listener, callback, options };
|
|
1005
1019
|
if (options.once) {
|
|
1006
1020
|
record.callback = function () {
|
|
1007
1021
|
const i = listeners.indexOf(record);
|
|
@@ -1018,15 +1032,15 @@
|
|
|
1018
1032
|
}
|
|
1019
1033
|
listeners.push(record);
|
|
1020
1034
|
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1021
|
-
for (const value of getChildValues(
|
|
1035
|
+
for (const value of getChildValues(ctx.ret)) {
|
|
1022
1036
|
if (isEventTarget(value)) {
|
|
1023
1037
|
value.addEventListener(record.type, record.callback, record.options);
|
|
1024
1038
|
}
|
|
1025
1039
|
}
|
|
1026
1040
|
}
|
|
1027
1041
|
removeEventListener(type, listener, options) {
|
|
1028
|
-
const
|
|
1029
|
-
const listeners = listenersMap.get(
|
|
1042
|
+
const ctx = this[_ContextImpl];
|
|
1043
|
+
const listeners = listenersMap.get(ctx);
|
|
1030
1044
|
if (listeners == null || !isListenerOrListenerObject(listener)) {
|
|
1031
1045
|
return;
|
|
1032
1046
|
}
|
|
@@ -1040,16 +1054,16 @@
|
|
|
1040
1054
|
const record = listeners[i];
|
|
1041
1055
|
listeners.splice(i, 1);
|
|
1042
1056
|
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1043
|
-
for (const value of getChildValues(
|
|
1057
|
+
for (const value of getChildValues(ctx.ret)) {
|
|
1044
1058
|
if (isEventTarget(value)) {
|
|
1045
1059
|
value.removeEventListener(record.type, record.callback, record.options);
|
|
1046
1060
|
}
|
|
1047
1061
|
}
|
|
1048
1062
|
}
|
|
1049
1063
|
dispatchEvent(ev) {
|
|
1050
|
-
const
|
|
1064
|
+
const ctx = this[_ContextImpl];
|
|
1051
1065
|
const path = [];
|
|
1052
|
-
for (let parent =
|
|
1066
|
+
for (let parent = ctx.parent; parent !== undefined; parent = parent.parent) {
|
|
1053
1067
|
path.push(parent);
|
|
1054
1068
|
}
|
|
1055
1069
|
// We patch the stopImmediatePropagation method because ev.cancelBubble
|
|
@@ -1061,7 +1075,7 @@
|
|
|
1061
1075
|
immediateCancelBubble = true;
|
|
1062
1076
|
return stopImmediatePropagation.call(ev);
|
|
1063
1077
|
});
|
|
1064
|
-
setEventProperty(ev, "target",
|
|
1078
|
+
setEventProperty(ev, "target", ctx.owner);
|
|
1065
1079
|
// The only possible errors in this block are errors thrown by callbacks,
|
|
1066
1080
|
// and dispatchEvent will only log these errors rather than throwing
|
|
1067
1081
|
// them. Therefore, we place all code in a try block, log errors in the
|
|
@@ -1070,18 +1084,21 @@
|
|
|
1070
1084
|
// Each early return within the try block returns true because while the
|
|
1071
1085
|
// return value is overridden in the finally block, TypeScript
|
|
1072
1086
|
// (justifiably) does not recognize the unsafe return statement.
|
|
1073
|
-
//
|
|
1074
|
-
// TODO: Run all callbacks even if one of them errors
|
|
1075
1087
|
try {
|
|
1076
1088
|
setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
|
|
1077
1089
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
1078
1090
|
const target = path[i];
|
|
1079
1091
|
const listeners = listenersMap.get(target);
|
|
1080
1092
|
if (listeners) {
|
|
1081
|
-
setEventProperty(ev, "currentTarget", target.
|
|
1093
|
+
setEventProperty(ev, "currentTarget", target.owner);
|
|
1082
1094
|
for (const record of listeners) {
|
|
1083
1095
|
if (record.type === ev.type && record.options.capture) {
|
|
1084
|
-
|
|
1096
|
+
try {
|
|
1097
|
+
record.callback.call(target.owner, ev);
|
|
1098
|
+
}
|
|
1099
|
+
catch (err) {
|
|
1100
|
+
console.error(err);
|
|
1101
|
+
}
|
|
1085
1102
|
if (immediateCancelBubble) {
|
|
1086
1103
|
return true;
|
|
1087
1104
|
}
|
|
@@ -1093,13 +1110,25 @@
|
|
|
1093
1110
|
}
|
|
1094
1111
|
}
|
|
1095
1112
|
{
|
|
1096
|
-
|
|
1113
|
+
setEventProperty(ev, "eventPhase", AT_TARGET);
|
|
1114
|
+
setEventProperty(ev, "currentTarget", ctx.owner);
|
|
1115
|
+
const propCallback = ctx.ret.el.props["on" + ev.type];
|
|
1116
|
+
if (propCallback != null) {
|
|
1117
|
+
propCallback(ev);
|
|
1118
|
+
if (immediateCancelBubble || ev.cancelBubble) {
|
|
1119
|
+
return true;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
const listeners = listenersMap.get(ctx);
|
|
1097
1123
|
if (listeners) {
|
|
1098
|
-
setEventProperty(ev, "eventPhase", AT_TARGET);
|
|
1099
|
-
setEventProperty(ev, "currentTarget", impl.ctx);
|
|
1100
1124
|
for (const record of listeners) {
|
|
1101
1125
|
if (record.type === ev.type) {
|
|
1102
|
-
|
|
1126
|
+
try {
|
|
1127
|
+
record.callback.call(ctx.owner, ev);
|
|
1128
|
+
}
|
|
1129
|
+
catch (err) {
|
|
1130
|
+
console.error(err);
|
|
1131
|
+
}
|
|
1103
1132
|
if (immediateCancelBubble) {
|
|
1104
1133
|
return true;
|
|
1105
1134
|
}
|
|
@@ -1116,10 +1145,15 @@
|
|
|
1116
1145
|
const target = path[i];
|
|
1117
1146
|
const listeners = listenersMap.get(target);
|
|
1118
1147
|
if (listeners) {
|
|
1119
|
-
setEventProperty(ev, "currentTarget", target.
|
|
1148
|
+
setEventProperty(ev, "currentTarget", target.owner);
|
|
1120
1149
|
for (const record of listeners) {
|
|
1121
1150
|
if (record.type === ev.type && !record.options.capture) {
|
|
1122
|
-
|
|
1151
|
+
try {
|
|
1152
|
+
record.callback.call(target.owner, ev);
|
|
1153
|
+
}
|
|
1154
|
+
catch (err) {
|
|
1155
|
+
console.error(err);
|
|
1156
|
+
}
|
|
1123
1157
|
if (immediateCancelBubble) {
|
|
1124
1158
|
return true;
|
|
1125
1159
|
}
|
|
@@ -1132,10 +1166,6 @@
|
|
|
1132
1166
|
}
|
|
1133
1167
|
}
|
|
1134
1168
|
}
|
|
1135
|
-
catch (err) {
|
|
1136
|
-
// TODO: Use setTimeout to rethrow the error.
|
|
1137
|
-
console.error(err);
|
|
1138
|
-
}
|
|
1139
1169
|
finally {
|
|
1140
1170
|
setEventProperty(ev, "eventPhase", NONE);
|
|
1141
1171
|
setEventProperty(ev, "currentTarget", null);
|
|
@@ -1157,38 +1187,43 @@
|
|
|
1157
1187
|
let ctx;
|
|
1158
1188
|
if (oldProps) {
|
|
1159
1189
|
ctx = ret.ctx;
|
|
1160
|
-
if (ctx.f &
|
|
1190
|
+
if (ctx.f & IsSyncExecuting) {
|
|
1161
1191
|
console.error("Component is already executing");
|
|
1162
|
-
return ret.
|
|
1192
|
+
return ret.cachedChildValues;
|
|
1163
1193
|
}
|
|
1164
1194
|
}
|
|
1165
1195
|
else {
|
|
1166
1196
|
ctx = ret.ctx = new ContextImpl(renderer, root, host, parent, scope, ret);
|
|
1167
1197
|
}
|
|
1168
1198
|
ctx.f |= IsUpdating;
|
|
1169
|
-
|
|
1170
|
-
return runComponent(ctx);
|
|
1199
|
+
return enqueueComponentRun(ctx);
|
|
1171
1200
|
}
|
|
1172
1201
|
function updateComponentChildren(ctx, children) {
|
|
1173
|
-
if (ctx.f & IsUnmounted
|
|
1202
|
+
if (ctx.f & IsUnmounted) {
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
else if (ctx.f & IsErrored) {
|
|
1206
|
+
// This branch is necessary for some race conditions where this function is
|
|
1207
|
+
// called after iterator.throw() in async generator components.
|
|
1174
1208
|
return;
|
|
1175
1209
|
}
|
|
1176
1210
|
else if (children === undefined) {
|
|
1177
1211
|
console.error("A component has returned or yielded undefined. If this was intentional, return or yield null instead.");
|
|
1178
1212
|
}
|
|
1179
1213
|
let childValues;
|
|
1180
|
-
// We set the isExecuting flag in case a child component dispatches an event
|
|
1181
|
-
// which bubbles to this component and causes a synchronous refresh().
|
|
1182
|
-
ctx.f |= IsExecuting;
|
|
1183
1214
|
try {
|
|
1215
|
+
// TODO: WAT
|
|
1216
|
+
// We set the isExecuting flag in case a child component dispatches an event
|
|
1217
|
+
// which bubbles to this component and causes a synchronous refresh().
|
|
1218
|
+
ctx.f |= IsSyncExecuting;
|
|
1184
1219
|
childValues = diffChildren(ctx.renderer, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children));
|
|
1185
1220
|
}
|
|
1186
1221
|
finally {
|
|
1187
|
-
ctx.f &= ~
|
|
1222
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1188
1223
|
}
|
|
1189
1224
|
if (isPromiseLike(childValues)) {
|
|
1190
|
-
ctx.ret.
|
|
1191
|
-
return ctx.ret.
|
|
1225
|
+
ctx.ret.inflightValue = childValues.then((childValues) => commitComponent(ctx, childValues));
|
|
1226
|
+
return ctx.ret.inflightValue;
|
|
1192
1227
|
}
|
|
1193
1228
|
return commitComponent(ctx, childValues);
|
|
1194
1229
|
}
|
|
@@ -1208,8 +1243,8 @@
|
|
|
1208
1243
|
}
|
|
1209
1244
|
}
|
|
1210
1245
|
}
|
|
1211
|
-
const oldValues = wrap(ctx.ret.
|
|
1212
|
-
let value = (ctx.ret.
|
|
1246
|
+
const oldValues = wrap(ctx.ret.cachedChildValues);
|
|
1247
|
+
let value = (ctx.ret.cachedChildValues = unwrap(values));
|
|
1213
1248
|
if (ctx.f & IsScheduling) {
|
|
1214
1249
|
ctx.f |= IsSchedulingRefresh;
|
|
1215
1250
|
}
|
|
@@ -1217,7 +1252,7 @@
|
|
|
1217
1252
|
// If we’re not updating the component, which happens when components are
|
|
1218
1253
|
// refreshed, or when async generator components iterate, we have to do a
|
|
1219
1254
|
// little bit housekeeping when a component’s child values have changed.
|
|
1220
|
-
if (!
|
|
1255
|
+
if (!arrayEqual(oldValues, values)) {
|
|
1221
1256
|
const records = getListenerRecords(ctx.parent, ctx.host);
|
|
1222
1257
|
if (records.length) {
|
|
1223
1258
|
for (let i = 0; i < values.length; i++) {
|
|
@@ -1232,7 +1267,7 @@
|
|
|
1232
1267
|
}
|
|
1233
1268
|
// rearranging the nearest ancestor host element
|
|
1234
1269
|
const host = ctx.host;
|
|
1235
|
-
const oldHostValues = wrap(host.
|
|
1270
|
+
const oldHostValues = wrap(host.cachedChildValues);
|
|
1236
1271
|
invalidate(ctx, host);
|
|
1237
1272
|
const hostValues = getChildValues(host);
|
|
1238
1273
|
ctx.renderer.arrange(host.el.tag, host.value, host.el.props, hostValues,
|
|
@@ -1261,50 +1296,75 @@
|
|
|
1261
1296
|
}
|
|
1262
1297
|
function invalidate(ctx, host) {
|
|
1263
1298
|
for (let parent = ctx.parent; parent !== undefined && parent.host === host; parent = parent.parent) {
|
|
1264
|
-
parent.ret.
|
|
1299
|
+
parent.ret.cachedChildValues = undefined;
|
|
1265
1300
|
}
|
|
1266
|
-
host.
|
|
1301
|
+
host.cachedChildValues = undefined;
|
|
1267
1302
|
}
|
|
1268
|
-
function
|
|
1269
|
-
if (
|
|
1303
|
+
function arrayEqual(arr1, arr2) {
|
|
1304
|
+
if (arr1.length !== arr2.length) {
|
|
1270
1305
|
return false;
|
|
1271
1306
|
}
|
|
1272
|
-
for (let i = 0; i <
|
|
1273
|
-
const value1 =
|
|
1274
|
-
const value2 =
|
|
1307
|
+
for (let i = 0; i < arr1.length; i++) {
|
|
1308
|
+
const value1 = arr1[i];
|
|
1309
|
+
const value2 = arr2[i];
|
|
1275
1310
|
if (value1 !== value2) {
|
|
1276
1311
|
return false;
|
|
1277
1312
|
}
|
|
1278
1313
|
}
|
|
1279
1314
|
return true;
|
|
1280
1315
|
}
|
|
1281
|
-
/**
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1316
|
+
/** Enqueues and executes the component associated with the context. */
|
|
1317
|
+
function enqueueComponentRun(ctx) {
|
|
1318
|
+
if (ctx.f & IsAsyncGen) {
|
|
1319
|
+
// This branch will only run for async generator components after the
|
|
1320
|
+
// initial render.
|
|
1321
|
+
//
|
|
1322
|
+
// Async generator components which are in the props loop can be in one of
|
|
1323
|
+
// three states:
|
|
1324
|
+
//
|
|
1325
|
+
// 1. propsAvailable flag is true: "available"
|
|
1326
|
+
//
|
|
1327
|
+
// The component is paused somewhere in the loop. When the component
|
|
1328
|
+
// reaches the bottom of the loop, it will run again with the next props.
|
|
1329
|
+
//
|
|
1330
|
+
// 2. onAvailable callback is defined: "suspended"
|
|
1331
|
+
//
|
|
1332
|
+
// The component has reached the bottom of the loop and is waiting for
|
|
1333
|
+
// new props.
|
|
1334
|
+
//
|
|
1335
|
+
// 3. neither 1 or 2: "Running"
|
|
1336
|
+
//
|
|
1337
|
+
// The component is paused somewhere in the loop. When the component
|
|
1338
|
+
// reaches the bottom of the loop, it will suspend.
|
|
1339
|
+
//
|
|
1340
|
+
// By definition, components will never be both available and suspended at
|
|
1341
|
+
// the same time.
|
|
1342
|
+
//
|
|
1343
|
+
// If the component is at the loop bottom, this means that the next value
|
|
1344
|
+
// produced by the component will have the most up to date props, so we can
|
|
1345
|
+
// simply return the current inflight value. Otherwise, we have to wait for
|
|
1346
|
+
// the bottom of the loop before returning the inflight value.
|
|
1347
|
+
const isAtLoopbottom = ctx.f & IsInRenderLoop && !ctx.onProps;
|
|
1348
|
+
resumePropsIterator(ctx);
|
|
1349
|
+
if (isAtLoopbottom) {
|
|
1350
|
+
if (ctx.inflightBlock == null) {
|
|
1351
|
+
ctx.inflightBlock = new Promise((resolve) => (ctx.onPropsRequested = resolve));
|
|
1352
|
+
}
|
|
1353
|
+
return ctx.inflightBlock.then(() => {
|
|
1354
|
+
ctx.inflightBlock = undefined;
|
|
1355
|
+
return ctx.inflightValue;
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
return ctx.inflightValue;
|
|
1359
|
+
}
|
|
1360
|
+
else if (!ctx.inflightBlock) {
|
|
1299
1361
|
try {
|
|
1300
|
-
const [block, value] =
|
|
1362
|
+
const [block, value] = runComponent(ctx);
|
|
1301
1363
|
if (block) {
|
|
1302
1364
|
ctx.inflightBlock = block
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
}
|
|
1307
|
-
})
|
|
1365
|
+
// TODO: there is some fuckery going on here related to async
|
|
1366
|
+
// generator components resuming when they’re meant to be returned.
|
|
1367
|
+
.then((v) => v)
|
|
1308
1368
|
.finally(() => advanceComponent(ctx));
|
|
1309
1369
|
// stepComponent will only return a block if the value is asynchronous
|
|
1310
1370
|
ctx.inflightValue = value;
|
|
@@ -1318,38 +1378,43 @@
|
|
|
1318
1378
|
throw err;
|
|
1319
1379
|
}
|
|
1320
1380
|
}
|
|
1321
|
-
else if (ctx.f & IsAsyncGen) {
|
|
1322
|
-
return ctx.inflightValue;
|
|
1323
|
-
}
|
|
1324
1381
|
else if (!ctx.enqueuedBlock) {
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1382
|
+
// We need to assign enqueuedBlock and enqueuedValue synchronously, hence
|
|
1383
|
+
// the Promise constructor call.
|
|
1384
|
+
let resolveEnqueuedBlock;
|
|
1385
|
+
ctx.enqueuedBlock = new Promise((resolve) => (resolveEnqueuedBlock = resolve));
|
|
1386
|
+
ctx.enqueuedValue = ctx.inflightBlock.then(() => {
|
|
1328
1387
|
try {
|
|
1329
|
-
const [block, value] =
|
|
1330
|
-
resolve(value);
|
|
1388
|
+
const [block, value] = runComponent(ctx);
|
|
1331
1389
|
if (block) {
|
|
1332
|
-
|
|
1333
|
-
if (!(ctx.f & IsUpdating)) {
|
|
1334
|
-
return propagateError(ctx.parent, err);
|
|
1335
|
-
}
|
|
1336
|
-
});
|
|
1390
|
+
resolveEnqueuedBlock(block.finally(() => advanceComponent(ctx)));
|
|
1337
1391
|
}
|
|
1392
|
+
return value;
|
|
1338
1393
|
}
|
|
1339
1394
|
catch (err) {
|
|
1340
1395
|
if (!(ctx.f & IsUpdating)) {
|
|
1341
1396
|
return propagateError(ctx.parent, err);
|
|
1342
1397
|
}
|
|
1398
|
+
throw err;
|
|
1343
1399
|
}
|
|
1344
|
-
})
|
|
1345
|
-
.finally(() => advanceComponent(ctx));
|
|
1346
|
-
ctx.enqueuedValue = new Promise((resolve1) => (resolve = resolve1));
|
|
1400
|
+
});
|
|
1347
1401
|
}
|
|
1348
1402
|
return ctx.enqueuedValue;
|
|
1349
1403
|
}
|
|
1404
|
+
/** Called when the inflight block promise settles. */
|
|
1405
|
+
function advanceComponent(ctx) {
|
|
1406
|
+
if (ctx.f & IsAsyncGen) {
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
ctx.inflightBlock = ctx.enqueuedBlock;
|
|
1410
|
+
ctx.inflightValue = ctx.enqueuedValue;
|
|
1411
|
+
ctx.enqueuedBlock = undefined;
|
|
1412
|
+
ctx.enqueuedValue = undefined;
|
|
1413
|
+
}
|
|
1350
1414
|
/**
|
|
1351
1415
|
* This function is responsible for executing the component and handling all
|
|
1352
|
-
* the different component types.
|
|
1416
|
+
* the different component types. We cannot identify whether a component is a
|
|
1417
|
+
* generator or async without calling it and inspecting the return value.
|
|
1353
1418
|
*
|
|
1354
1419
|
* @returns {[block, value]} A tuple where
|
|
1355
1420
|
* block - A possible promise which represents the duration during which the
|
|
@@ -1364,25 +1429,23 @@
|
|
|
1364
1429
|
* - Sync generator components block while any children are executing, because
|
|
1365
1430
|
* they are expected to only resume when they’ve actually rendered.
|
|
1366
1431
|
*/
|
|
1367
|
-
function
|
|
1432
|
+
function runComponent(ctx) {
|
|
1368
1433
|
const ret = ctx.ret;
|
|
1369
|
-
if (ctx.f & IsDone) {
|
|
1370
|
-
return [undefined, getValue(ret)];
|
|
1371
|
-
}
|
|
1372
1434
|
const initial = !ctx.iterator;
|
|
1373
1435
|
if (initial) {
|
|
1374
|
-
ctx
|
|
1436
|
+
resumePropsIterator(ctx);
|
|
1437
|
+
ctx.f |= IsSyncExecuting;
|
|
1375
1438
|
clearEventListeners(ctx);
|
|
1376
1439
|
let result;
|
|
1377
1440
|
try {
|
|
1378
|
-
result = ret.el.tag.call(ctx.
|
|
1441
|
+
result = ret.el.tag.call(ctx.owner, ret.el.props);
|
|
1379
1442
|
}
|
|
1380
1443
|
catch (err) {
|
|
1381
1444
|
ctx.f |= IsErrored;
|
|
1382
1445
|
throw err;
|
|
1383
1446
|
}
|
|
1384
1447
|
finally {
|
|
1385
|
-
ctx.f &= ~
|
|
1448
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1386
1449
|
}
|
|
1387
1450
|
if (isIteratorLike(result)) {
|
|
1388
1451
|
ctx.iterator = result;
|
|
@@ -1394,120 +1457,182 @@
|
|
|
1394
1457
|
ctx.f |= IsErrored;
|
|
1395
1458
|
throw err;
|
|
1396
1459
|
});
|
|
1397
|
-
return [result1, value];
|
|
1460
|
+
return [result1.catch(NOOP), value];
|
|
1398
1461
|
}
|
|
1399
1462
|
else {
|
|
1400
1463
|
// sync function component
|
|
1401
1464
|
return [undefined, updateComponentChildren(ctx, result)];
|
|
1402
1465
|
}
|
|
1403
1466
|
}
|
|
1404
|
-
let
|
|
1467
|
+
let iteration;
|
|
1405
1468
|
if (initial) {
|
|
1406
|
-
|
|
1407
|
-
|
|
1469
|
+
try {
|
|
1470
|
+
ctx.f |= IsSyncExecuting;
|
|
1471
|
+
iteration = ctx.iterator.next();
|
|
1472
|
+
}
|
|
1473
|
+
catch (err) {
|
|
1474
|
+
ctx.f |= IsErrored;
|
|
1475
|
+
throw err;
|
|
1476
|
+
}
|
|
1477
|
+
finally {
|
|
1478
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1479
|
+
}
|
|
1480
|
+
if (isPromiseLike(iteration)) {
|
|
1481
|
+
ctx.f |= IsAsyncGen;
|
|
1482
|
+
runAsyncGenComponent(ctx, iteration);
|
|
1483
|
+
}
|
|
1484
|
+
else {
|
|
1485
|
+
ctx.f |= IsSyncGen;
|
|
1486
|
+
}
|
|
1408
1487
|
}
|
|
1409
|
-
|
|
1410
|
-
//
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1488
|
+
if (ctx.f & IsSyncGen) {
|
|
1489
|
+
// sync generator component
|
|
1490
|
+
ctx.f &= ~NeedsToYield;
|
|
1491
|
+
if (!initial) {
|
|
1492
|
+
try {
|
|
1493
|
+
ctx.f |= IsSyncExecuting;
|
|
1494
|
+
iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
|
|
1495
|
+
}
|
|
1496
|
+
catch (err) {
|
|
1497
|
+
ctx.f |= IsErrored;
|
|
1498
|
+
throw err;
|
|
1499
|
+
}
|
|
1500
|
+
finally {
|
|
1501
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
if (isPromiseLike(iteration)) {
|
|
1505
|
+
throw new Error("Sync generator component returned an async iteration");
|
|
1506
|
+
}
|
|
1507
|
+
if (iteration.done) {
|
|
1508
|
+
ctx.f &= ~IsSyncGen;
|
|
1509
|
+
ctx.iterator = undefined;
|
|
1510
|
+
}
|
|
1511
|
+
let value;
|
|
1512
|
+
try {
|
|
1513
|
+
value = updateComponentChildren(ctx,
|
|
1514
|
+
// Children can be void so we eliminate that here
|
|
1515
|
+
iteration.value);
|
|
1516
|
+
if (isPromiseLike(value)) {
|
|
1517
|
+
value = value.catch((err) => handleChildError(ctx, err));
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
catch (err) {
|
|
1521
|
+
value = handleChildError(ctx, err);
|
|
1522
|
+
}
|
|
1523
|
+
const block = isPromiseLike(value) ? value.catch(NOOP) : undefined;
|
|
1524
|
+
return [block, value];
|
|
1415
1525
|
}
|
|
1416
1526
|
else {
|
|
1417
|
-
|
|
1527
|
+
// async generator component
|
|
1528
|
+
return [undefined, ctx.inflightValue];
|
|
1418
1529
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1530
|
+
}
|
|
1531
|
+
async function runAsyncGenComponent(ctx, iterationP) {
|
|
1532
|
+
let done = false;
|
|
1421
1533
|
try {
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
if (isPromiseLike(iteration)) {
|
|
1432
|
-
// async generator component
|
|
1433
|
-
if (initial) {
|
|
1434
|
-
ctx.f |= IsAsyncGen;
|
|
1435
|
-
}
|
|
1436
|
-
const value = iteration.then((iteration) => {
|
|
1437
|
-
if (!(ctx.f & IsIterating)) {
|
|
1438
|
-
ctx.f &= ~IsAvailable;
|
|
1534
|
+
while (!done) {
|
|
1535
|
+
// inflightValue must be set synchronously.
|
|
1536
|
+
let onValue;
|
|
1537
|
+
ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
|
|
1538
|
+
if (ctx.f & IsUpdating) {
|
|
1539
|
+
// We should not swallow unhandled promise rejections if the component is
|
|
1540
|
+
// updating independently.
|
|
1541
|
+
// TODO: Does this handle this.refresh() calls?
|
|
1542
|
+
ctx.inflightValue.catch(NOOP);
|
|
1439
1543
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1544
|
+
let iteration;
|
|
1545
|
+
try {
|
|
1546
|
+
iteration = await iterationP;
|
|
1547
|
+
}
|
|
1548
|
+
catch (err) {
|
|
1549
|
+
done = true;
|
|
1550
|
+
ctx.f |= IsErrored;
|
|
1551
|
+
onValue(Promise.reject(err));
|
|
1552
|
+
break;
|
|
1443
1553
|
}
|
|
1554
|
+
finally {
|
|
1555
|
+
ctx.f &= ~NeedsToYield;
|
|
1556
|
+
if (!(ctx.f & IsInRenderLoop)) {
|
|
1557
|
+
ctx.f &= ~PropsAvailable;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
done = !!iteration.done;
|
|
1561
|
+
let value;
|
|
1444
1562
|
try {
|
|
1445
|
-
|
|
1563
|
+
value = updateComponentChildren(ctx, iteration.value);
|
|
1446
1564
|
if (isPromiseLike(value)) {
|
|
1447
|
-
|
|
1565
|
+
value = value.catch((err) => handleChildError(ctx, err));
|
|
1448
1566
|
}
|
|
1449
|
-
return value;
|
|
1450
1567
|
}
|
|
1451
1568
|
catch (err) {
|
|
1452
|
-
|
|
1569
|
+
done = true;
|
|
1570
|
+
// Do we need to catch potential errors here in the case of unhandled
|
|
1571
|
+
// promise rejections?
|
|
1572
|
+
value = handleChildError(ctx, err);
|
|
1573
|
+
}
|
|
1574
|
+
finally {
|
|
1575
|
+
onValue(value);
|
|
1576
|
+
}
|
|
1577
|
+
// TODO: this can be done more elegantly
|
|
1578
|
+
let oldValue;
|
|
1579
|
+
if (ctx.ret.inflightValue) {
|
|
1580
|
+
// The value passed back into the generator as the argument to the next
|
|
1581
|
+
// method is a promise if an async generator component has async
|
|
1582
|
+
// children. Sync generator components only resume when their children
|
|
1583
|
+
// have fulfilled so the element’s inflight child values will never be
|
|
1584
|
+
// defined.
|
|
1585
|
+
oldValue = ctx.ret.inflightValue.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
|
|
1586
|
+
}
|
|
1587
|
+
else {
|
|
1588
|
+
oldValue = ctx.renderer.read(getValue(ctx.ret));
|
|
1589
|
+
}
|
|
1590
|
+
if (ctx.f & IsUnmounted) {
|
|
1591
|
+
if (ctx.f & IsInRenderLoop) {
|
|
1592
|
+
try {
|
|
1593
|
+
ctx.f |= IsSyncExecuting;
|
|
1594
|
+
iterationP = ctx.iterator.next(oldValue);
|
|
1595
|
+
}
|
|
1596
|
+
finally {
|
|
1597
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
else {
|
|
1601
|
+
returnComponent(ctx);
|
|
1602
|
+
break;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
else if (!done) {
|
|
1606
|
+
try {
|
|
1607
|
+
ctx.f |= IsSyncExecuting;
|
|
1608
|
+
iterationP = ctx.iterator.next(oldValue);
|
|
1609
|
+
}
|
|
1610
|
+
finally {
|
|
1611
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1612
|
+
}
|
|
1453
1613
|
}
|
|
1454
|
-
}, (err) => {
|
|
1455
|
-
ctx.f |= IsDone | IsErrored;
|
|
1456
|
-
throw err;
|
|
1457
|
-
});
|
|
1458
|
-
return [iteration, value];
|
|
1459
|
-
}
|
|
1460
|
-
// sync generator component
|
|
1461
|
-
if (initial) {
|
|
1462
|
-
ctx.f |= IsSyncGen;
|
|
1463
|
-
}
|
|
1464
|
-
ctx.f &= ~IsIterating;
|
|
1465
|
-
if (iteration.done) {
|
|
1466
|
-
ctx.f |= IsDone;
|
|
1467
|
-
}
|
|
1468
|
-
let value;
|
|
1469
|
-
try {
|
|
1470
|
-
value = updateComponentChildren(ctx, iteration.value);
|
|
1471
|
-
if (isPromiseLike(value)) {
|
|
1472
|
-
value = value.catch((err) => handleChildError(ctx, err));
|
|
1473
1614
|
}
|
|
1474
1615
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
if (isPromiseLike(value)) {
|
|
1479
|
-
return [value.catch(NOOP), value];
|
|
1480
|
-
}
|
|
1481
|
-
return [undefined, value];
|
|
1482
|
-
}
|
|
1483
|
-
/**
|
|
1484
|
-
* Called when the inflight block promise settles.
|
|
1485
|
-
*/
|
|
1486
|
-
function advanceComponent(ctx) {
|
|
1487
|
-
ctx.inflightBlock = ctx.enqueuedBlock;
|
|
1488
|
-
ctx.inflightValue = ctx.enqueuedValue;
|
|
1489
|
-
ctx.enqueuedBlock = undefined;
|
|
1490
|
-
ctx.enqueuedValue = undefined;
|
|
1491
|
-
if (ctx.f & IsAsyncGen && !(ctx.f & IsDone) && !(ctx.f & IsUnmounted)) {
|
|
1492
|
-
runComponent(ctx);
|
|
1616
|
+
finally {
|
|
1617
|
+
ctx.f &= ~IsAsyncGen;
|
|
1618
|
+
ctx.iterator = undefined;
|
|
1493
1619
|
}
|
|
1494
1620
|
}
|
|
1495
1621
|
/**
|
|
1496
|
-
* Called to
|
|
1497
|
-
* generator components.
|
|
1622
|
+
* Called to resume the props async iterator for async generator components.
|
|
1498
1623
|
*/
|
|
1499
|
-
function
|
|
1500
|
-
if (ctx.
|
|
1501
|
-
ctx.
|
|
1502
|
-
ctx.
|
|
1624
|
+
function resumePropsIterator(ctx) {
|
|
1625
|
+
if (ctx.onProps) {
|
|
1626
|
+
ctx.onProps(ctx.ret.el.props);
|
|
1627
|
+
ctx.onProps = undefined;
|
|
1628
|
+
ctx.f &= ~PropsAvailable;
|
|
1503
1629
|
}
|
|
1504
1630
|
else {
|
|
1505
|
-
ctx.f |=
|
|
1631
|
+
ctx.f |= PropsAvailable;
|
|
1506
1632
|
}
|
|
1507
1633
|
}
|
|
1508
1634
|
// TODO: async unmounting
|
|
1509
1635
|
function unmountComponent(ctx) {
|
|
1510
|
-
ctx.f |= IsUnmounted;
|
|
1511
1636
|
clearEventListeners(ctx);
|
|
1512
1637
|
const callbacks = cleanupMap.get(ctx);
|
|
1513
1638
|
if (callbacks) {
|
|
@@ -1517,21 +1642,54 @@
|
|
|
1517
1642
|
callback(value);
|
|
1518
1643
|
}
|
|
1519
1644
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
ctx.f
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1645
|
+
ctx.f |= IsUnmounted;
|
|
1646
|
+
if (ctx.iterator) {
|
|
1647
|
+
if (ctx.f & IsSyncGen) {
|
|
1648
|
+
let value;
|
|
1649
|
+
if (ctx.f & IsInRenderLoop) {
|
|
1650
|
+
value = enqueueComponentRun(ctx);
|
|
1651
|
+
}
|
|
1652
|
+
if (isPromiseLike(value)) {
|
|
1653
|
+
value.then(() => {
|
|
1654
|
+
if (ctx.f & IsInRenderLoop) {
|
|
1655
|
+
unmountComponent(ctx);
|
|
1656
|
+
}
|
|
1657
|
+
else {
|
|
1658
|
+
returnComponent(ctx);
|
|
1659
|
+
}
|
|
1660
|
+
}, (err) => {
|
|
1661
|
+
propagateError(ctx.parent, err);
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
else {
|
|
1665
|
+
if (ctx.f & IsInRenderLoop) {
|
|
1666
|
+
unmountComponent(ctx);
|
|
1667
|
+
}
|
|
1668
|
+
else {
|
|
1669
|
+
returnComponent(ctx);
|
|
1529
1670
|
}
|
|
1530
1671
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1672
|
+
}
|
|
1673
|
+
else if (ctx.f & IsAsyncGen) {
|
|
1674
|
+
// The logic for unmounting async generator components is in the
|
|
1675
|
+
// runAsyncGenComponent function.
|
|
1676
|
+
resumePropsIterator(ctx);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
function returnComponent(ctx) {
|
|
1681
|
+
resumePropsIterator(ctx);
|
|
1682
|
+
if (ctx.iterator && typeof ctx.iterator.return === "function") {
|
|
1683
|
+
try {
|
|
1684
|
+
ctx.f |= IsSyncExecuting;
|
|
1685
|
+
const iteration = ctx.iterator.return();
|
|
1686
|
+
if (isPromiseLike(iteration)) {
|
|
1687
|
+
iteration.catch((err) => propagateError(ctx.parent, err));
|
|
1533
1688
|
}
|
|
1534
1689
|
}
|
|
1690
|
+
finally {
|
|
1691
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1692
|
+
}
|
|
1535
1693
|
}
|
|
1536
1694
|
}
|
|
1537
1695
|
/*** EVENT TARGET UTILITIES ***/
|
|
@@ -1602,39 +1760,38 @@
|
|
|
1602
1760
|
}
|
|
1603
1761
|
}
|
|
1604
1762
|
/*** ERROR HANDLING UTILITIES ***/
|
|
1605
|
-
// TODO: generator components which throw errors should be recoverable
|
|
1606
1763
|
function handleChildError(ctx, err) {
|
|
1607
|
-
if (ctx.
|
|
1608
|
-
!ctx.iterator ||
|
|
1609
|
-
typeof ctx.iterator.throw !== "function") {
|
|
1764
|
+
if (!ctx.iterator || typeof ctx.iterator.throw !== "function") {
|
|
1610
1765
|
throw err;
|
|
1611
1766
|
}
|
|
1612
|
-
|
|
1767
|
+
resumePropsIterator(ctx);
|
|
1613
1768
|
let iteration;
|
|
1614
1769
|
try {
|
|
1615
|
-
ctx.f |=
|
|
1770
|
+
ctx.f |= IsSyncExecuting;
|
|
1616
1771
|
iteration = ctx.iterator.throw(err);
|
|
1617
1772
|
}
|
|
1618
1773
|
catch (err) {
|
|
1619
|
-
ctx.f |=
|
|
1774
|
+
ctx.f |= IsErrored;
|
|
1620
1775
|
throw err;
|
|
1621
1776
|
}
|
|
1622
1777
|
finally {
|
|
1623
|
-
ctx.f &= ~
|
|
1778
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1624
1779
|
}
|
|
1625
1780
|
if (isPromiseLike(iteration)) {
|
|
1626
1781
|
return iteration.then((iteration) => {
|
|
1627
1782
|
if (iteration.done) {
|
|
1628
|
-
ctx.f
|
|
1783
|
+
ctx.f &= ~IsAsyncGen;
|
|
1784
|
+
ctx.iterator = undefined;
|
|
1629
1785
|
}
|
|
1630
1786
|
return updateComponentChildren(ctx, iteration.value);
|
|
1631
1787
|
}, (err) => {
|
|
1632
|
-
ctx.f |=
|
|
1788
|
+
ctx.f |= IsErrored;
|
|
1633
1789
|
throw err;
|
|
1634
1790
|
});
|
|
1635
1791
|
}
|
|
1636
1792
|
if (iteration.done) {
|
|
1637
|
-
ctx.f
|
|
1793
|
+
ctx.f &= ~IsSyncGen;
|
|
1794
|
+
ctx.iterator = undefined;
|
|
1638
1795
|
}
|
|
1639
1796
|
return updateComponentChildren(ctx, iteration.value);
|
|
1640
1797
|
}
|