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

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