@b9g/crank 0.5.0-beta.2 → 0.5.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/crank.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.cached = undefined;
259
- this.fallback = undefined;
260
- this.inflight = undefined;
261
- this.onCommit = undefined;
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.fallback !== "undefined") {
271
- return typeof ret.fallback === "object"
272
- ? getValue(ret.fallback)
273
- : ret.fallback;
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.cached) {
290
- return wrap(ret.cached);
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.cached = unwrap(values1);
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 $RendererImpl = Symbol.for("crank.RendererImpl");
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[$RendererImpl] = {
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 ctx - An optional context that will be the ancestor context of all
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[$ContextImpl];
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[$RendererImpl];
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 !== undefined) {
393
- renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.cached));
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.cached = unwrap(childValues);
380
+ ret.cachedChildValues = unwrap(childValues);
397
381
  if (root == null) {
398
382
  unmount(renderer, ret, ctx, ret);
399
383
  }
400
- return renderer.read(ret.cached);
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.fallback = fallback;
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.onCommit) {
541
- parent.onCommit(childValues1);
524
+ if (parent.onNextValues) {
525
+ parent.onNextValues(childValues1);
542
526
  }
543
- parent.onCommit = onChildValues;
527
+ parent.onNextValues = onChildValues;
544
528
  return childValues1.then((childValues) => {
545
- parent.inflight = parent.fallback = undefined;
529
+ parent.inflightValue = parent.fallbackValue = undefined;
546
530
  return normalize(childValues);
547
531
  });
548
532
  }
549
- if (graveyard) {
550
- for (let i = 0; i < graveyard.length; i++) {
551
- unmount(renderer, host, ctx, graveyard[i]);
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.inflight) {
581
- return child.inflight;
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.inflight = childValues.then((childValues) => unwrap(childValues));
601
- return ret.inflight;
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.inflight = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps));
619
- return ret.inflight;
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.cached));
647
- ret.cached = unwrap(childValues);
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.cached));
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 set when the component is being updated by the parent and
719
- * cleared when the component has committed. Used to determine things like
720
- * whether the nearest host ancestor needs to be rearranged.
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 set when the component function or generator is
725
- * synchronously executing. This flags is used to ensure that a component which
726
- * triggers a second update in the course of rendering does not cause a stack
727
- * overflow or a generator error.
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 IsExecuting = 1 << 1;
720
+ const IsInRenderLoop = 1 << 2;
730
721
  /**
731
- * A flag used to make sure multiple values are not pulled from context prop
732
- * iterators without a yield.
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 IsIterating = 1 << 2;
727
+ const NeedsToYield = 1 << 3;
735
728
  /**
736
729
  * A flag used by async generator components in conjunction with the
737
- * onavailable (_oa) callback to mark whether new props can be pulled via the
738
- * context async iterator. See the Symbol.asyncIterator method and the
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 IsAvailable = 1 << 3;
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 set to true. Generator components will stick to
745
- * their last rendered value and ignore further updates.
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 << 4;
740
+ const IsDone = 1 << 5;
748
741
  /**
749
- * A flag which is set when a generator component errors.
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 << 5;
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 << 6;
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 << 7;
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 << 8;
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 << 9;
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 << 10;
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 all context data.
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.onAvailable = undefined;
796
+ this.onProps = undefined;
797
+ this.onPropsRequested = undefined;
804
798
  }
805
799
  }
806
- const $ContextImpl = Symbol.for("crank.ContextImpl");
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[$ContextImpl] = impl;
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[$ContextImpl].ret.el.props;
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[$ContextImpl].renderer.read(getValue(this[$ContextImpl].ret));
838
+ return this[_ContextImpl].renderer.read(getValue(this[_ContextImpl].ret));
843
839
  }
844
840
  *[Symbol.iterator]() {
845
- const impl = this[$ContextImpl];
846
- while (!(impl.f & IsDone)) {
847
- if (impl.f & IsIterating) {
848
- throw new Error("Context iterated twice without a yield");
849
- }
850
- else if (impl.f & IsAsyncGen) {
851
- throw new Error("Use for await…of in async generator components");
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
- impl.f |= IsIterating;
854
- yield impl.ret.el.props;
856
+ }
857
+ finally {
858
+ impl.f &= ~IsInRenderLoop;
855
859
  }
856
860
  }
857
861
  async *[Symbol.asyncIterator]() {
858
- // We use a do while loop rather than a while loop to handle an edge case
859
- // where an async generator component is unmounted synchronously and
860
- // therefore “done” before it starts iterating over the context.
861
- const impl = this[$ContextImpl];
862
- do {
863
- if (impl.f & IsIterating) {
864
- throw new Error("Context iterated twice without a yield");
865
- }
866
- else if (impl.f & IsSyncGen) {
867
- throw new Error("Use for…of in sync generator components");
868
- }
869
- impl.f |= IsIterating;
870
- if (impl.f & IsAvailable) {
871
- impl.f &= ~IsAvailable;
872
- }
873
- else {
874
- await new Promise((resolve) => (impl.onAvailable = resolve));
875
- if (impl.f & IsDone) {
876
- break;
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
- yield impl.ret.el.props;
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[$ContextImpl];
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 & IsExecuting) {
921
+ else if (impl.f & IsSyncExecuting) {
901
922
  console.error("Component is already executing");
902
923
  return this.value;
903
924
  }
904
- resumeCtxIterator(impl);
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[$ContextImpl];
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[$ContextImpl];
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[$ContextImpl];
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[$ContextImpl].parent; parent !== undefined; parent = parent.parent) {
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[$ContextImpl];
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[$ContextImpl];
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[$ContextImpl];
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[$ContextImpl];
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 & IsExecuting) {
1175
+ if (ctx.f & IsSyncExecuting) {
1156
1176
  console.error("Component is already executing");
1157
- return ret.cached;
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
- resumeCtxIterator(ctx);
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 &= ~IsExecuting;
1202
+ ctx.f &= ~IsSyncExecuting;
1183
1203
  }
1184
1204
  if (isPromiseLike(childValues)) {
1185
- ctx.ret.inflight = childValues.then((childValues) => commitComponent(ctx, childValues));
1186
- return ctx.ret.inflight;
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.cached);
1207
- let value = (ctx.ret.cached = unwrap(values));
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 (!valuesEqual(oldValues, values)) {
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.cached);
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.cached = undefined;
1279
+ parent.ret.cachedChildValues = undefined;
1260
1280
  }
1261
- host.cached = undefined;
1281
+ host.cachedChildValues = undefined;
1262
1282
  }
1263
- function valuesEqual(values1, values2) {
1264
- if (values1.length !== values2.length) {
1283
+ function arrayEqual(arr1, arr2) {
1284
+ if (arr1.length !== arr2.length) {
1265
1285
  return false;
1266
1286
  }
1267
- for (let i = 0; i < values1.length; i++) {
1268
- const value1 = values1[i];
1269
- const value2 = values2[i];
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
- * Enqueues and executes the component associated with the context.
1278
- *
1279
- * The functions stepComponent and runComponent work together
1280
- * to implement the async queueing behavior of components. The runComponent
1281
- * function calls the stepComponent function, which returns two results in a
1282
- * tuple. The first result, called the “block,” is a possible promise which
1283
- * represents the duration for which the component is blocked from accepting
1284
- * new updates. The second result, called the “value,” is the actual result of
1285
- * the update. The runComponent function caches block/value from the
1286
- * stepComponent function on the context, according to whether the component
1287
- * blocks. The “inflight” block/value properties are the currently executing
1288
- * update, and the “enqueued” block/value properties represent an enqueued next
1289
- * stepComponent. Enqueued steps are dequeued every time the current block
1290
- * promise settles.
1291
- */
1292
- function runComponent(ctx) {
1293
- if (!ctx.inflightBlock) {
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] = stepComponent(ctx);
1342
+ const [block, value] = runComponent(ctx);
1296
1343
  if (block) {
1297
1344
  ctx.inflightBlock = block
1298
- .catch((err) => {
1299
- if (!(ctx.f & IsUpdating)) {
1300
- return propagateError(ctx.parent, err);
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
- let resolve;
1321
- ctx.enqueuedBlock = ctx.inflightBlock
1322
- .then(() => {
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] = stepComponent(ctx);
1325
- resolve(value);
1368
+ const [block, value] = runComponent(ctx);
1326
1369
  if (block) {
1327
- return block.catch((err) => {
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 stepComponent(ctx) {
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.f |= IsExecuting;
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 &= ~IsExecuting;
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
- ctx.f |= IsExecuting;
1416
- try {
1417
- iteration = ctx.iterator.next(oldValue);
1418
- }
1419
- catch (err) {
1420
- ctx.f |= IsDone | IsErrored;
1421
- throw err;
1422
- }
1423
- finally {
1424
- ctx.f &= ~IsExecuting;
1425
- }
1426
- if (isPromiseLike(iteration)) {
1427
- // async generator component
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
- const value = iteration.then((iteration) => {
1432
- if (!(ctx.f & IsIterating)) {
1433
- ctx.f &= ~IsAvailable;
1434
- }
1435
- ctx.f &= ~IsIterating;
1436
- if (iteration.done) {
1437
- ctx.f |= IsDone;
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
- const value = updateComponentChildren(ctx, iteration.value);
1441
- if (isPromiseLike(value)) {
1442
- return value.catch((err) => handleChildError(ctx, err));
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
- return handleChildError(ctx, err);
1481
+ ctx.f |= IsDone | IsErrored;
1482
+ throw err;
1448
1483
  }
1449
- }, (err) => {
1450
- ctx.f |= IsDone | IsErrored;
1451
- throw err;
1452
- });
1453
- return [iteration, value];
1454
- }
1455
- // sync generator component
1456
- if (initial) {
1457
- ctx.f |= IsSyncGen;
1458
- }
1459
- ctx.f &= ~IsIterating;
1460
- if (iteration.done) {
1461
- ctx.f |= IsDone;
1462
- }
1463
- let value;
1464
- try {
1465
- value = updateComponentChildren(ctx, iteration.value);
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
- value = value.catch((err) => handleChildError(ctx, err));
1507
+ return [value.catch(NOOP), value];
1468
1508
  }
1509
+ return [undefined, value];
1469
1510
  }
1470
- catch (err) {
1471
- value = handleChildError(ctx, err);
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
- * Called when the inflight block promise settles.
1480
- */
1481
- function advanceComponent(ctx) {
1482
- ctx.inflightBlock = ctx.enqueuedBlock;
1483
- ctx.inflightValue = ctx.enqueuedValue;
1484
- ctx.enqueuedBlock = undefined;
1485
- ctx.enqueuedValue = undefined;
1486
- if (ctx.f & IsAsyncGen && !(ctx.f & IsDone) && !(ctx.f & IsUnmounted)) {
1487
- runComponent(ctx);
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 resumeCtxIterator(ctx) {
1495
- if (ctx.onAvailable) {
1496
- ctx.onAvailable();
1497
- ctx.onAvailable = undefined;
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 |= IsAvailable;
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
- if (!(ctx.f & IsDone)) {
1516
- ctx.f |= IsDone;
1517
- resumeCtxIterator(ctx);
1518
- if (ctx.iterator && typeof ctx.iterator.return === "function") {
1519
- ctx.f |= IsExecuting;
1520
- try {
1521
- const iteration = ctx.iterator.return();
1522
- if (isPromiseLike(iteration)) {
1523
- iteration.catch((err) => propagateError(ctx.parent, err));
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
- finally {
1527
- ctx.f &= ~IsExecuting;
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
- resumeCtxIterator(ctx);
1750
+ resumePropsIterator(ctx);
1608
1751
  let iteration;
1609
1752
  try {
1610
- ctx.f |= IsExecuting;
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 &= ~IsExecuting;
1761
+ ctx.f &= ~IsSyncExecuting;
1619
1762
  }
1620
1763
  if (isPromiseLike(iteration)) {
1621
1764
  return iteration.then((iteration) => {