@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/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.cached = undefined;
264
- this.fallback = undefined;
265
- this.inflight = undefined;
266
- this.onCommit = undefined;
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.fallback !== "undefined") {
276
- return typeof ret.fallback === "object"
277
- ? getValue(ret.fallback)
278
- : ret.fallback;
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.cached) {
295
- return wrap(ret.cached);
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.cached = unwrap(values1);
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 $RendererImpl = Symbol.for("crank.RendererImpl");
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[$RendererImpl] = {
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 ctx - An optional context that will be the ancestor context of all
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[$ContextImpl];
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[$RendererImpl];
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 !== undefined) {
398
- renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.cached));
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.cached = unwrap(childValues);
385
+ ret.cachedChildValues = unwrap(childValues);
402
386
  if (root == null) {
403
387
  unmount(renderer, ret, ctx, ret);
404
388
  }
405
- return renderer.read(ret.cached);
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.fallback = fallback;
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.onCommit) {
546
- parent.onCommit(childValues1);
529
+ if (parent.onNextValues) {
530
+ parent.onNextValues(childValues1);
547
531
  }
548
- parent.onCommit = onChildValues;
532
+ parent.onNextValues = onChildValues;
549
533
  return childValues1.then((childValues) => {
550
- parent.inflight = parent.fallback = undefined;
534
+ parent.inflightValue = parent.fallbackValue = undefined;
551
535
  return normalize(childValues);
552
536
  });
553
537
  }
554
- if (graveyard) {
555
- for (let i = 0; i < graveyard.length; i++) {
556
- unmount(renderer, host, ctx, graveyard[i]);
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.inflight) {
586
- return child.inflight;
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.inflight = childValues.then((childValues) => unwrap(childValues));
606
- return ret.inflight;
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.inflight = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps));
624
- return ret.inflight;
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.cached));
652
- ret.cached = unwrap(childValues);
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.cached));
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 set when the component is being updated by the parent and
724
- * cleared when the component has committed. Used to determine things like
725
- * whether the nearest host ancestor needs to be rearranged.
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 set when the component function or generator is
730
- * synchronously executing. This flags is used to ensure that a component which
731
- * triggers a second update in the course of rendering does not cause a stack
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 IsExecuting = 1 << 1;
721
+ const IsSyncExecuting = 1 << 1;
735
722
  /**
736
- * A flag used to make sure multiple values are not pulled from context prop
737
- * iterators without a yield.
723
+ * A flag which is true when the component is in the render loop.
738
724
  */
739
- const IsIterating = 1 << 2;
725
+ const IsInRenderLoop = 1 << 2;
740
726
  /**
741
- * A flag used by async generator components in conjunction with the
742
- * onavailable (_oa) callback to mark whether new props can be pulled via the
743
- * context async iterator. See the Symbol.asyncIterator method and the
744
- * resumeCtxIterator function.
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 IsAvailable = 1 << 3;
732
+ const NeedsToYield = 1 << 3;
747
733
  /**
748
- * A flag which is set when a generator components returns, i.e. the done
749
- * property on the iteration is set to true. Generator components will stick to
750
- * their last rendered value and ignore further updates.
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 IsDone = 1 << 4;
739
+ const PropsAvailable = 1 << 4;
753
740
  /**
754
- * A flag which is set when a generator component errors.
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 << 5;
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 << 6;
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 << 7;
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 << 8;
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 << 9;
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 << 10;
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 all context data.
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.ctx = new Context(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.onAvailable = undefined;
795
+ this.onProps = undefined;
796
+ this.onPropsRequested = undefined;
809
797
  }
810
798
  }
811
- const $ContextImpl = Symbol.for("crank.ContextImpl");
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[$ContextImpl] = impl;
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[$ContextImpl].ret.el.props;
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[$ContextImpl].renderer.read(getValue(this[$ContextImpl].ret));
837
+ return this[_ContextImpl].renderer.read(getValue(this[_ContextImpl].ret));
848
838
  }
849
839
  *[Symbol.iterator]() {
850
- const impl = this[$ContextImpl];
851
- while (!(impl.f & IsDone)) {
852
- if (impl.f & IsIterating) {
853
- throw new Error("Context iterated twice without a yield");
854
- }
855
- else if (impl.f & IsAsyncGen) {
856
- throw new Error("Use for await…of in async generator components");
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
- impl.f |= IsIterating;
859
- yield impl.ret.el.props;
855
+ }
856
+ finally {
857
+ ctx.f &= ~IsInRenderLoop;
860
858
  }
861
859
  }
862
860
  async *[Symbol.asyncIterator]() {
863
- // We use a do while loop rather than a while loop to handle an edge case
864
- // where an async generator component is unmounted synchronously and
865
- // therefore “done” before it starts iterating over the context.
866
- const impl = this[$ContextImpl];
867
- do {
868
- if (impl.f & IsIterating) {
869
- throw new Error("Context iterated twice without a yield");
870
- }
871
- else if (impl.f & IsSyncGen) {
872
- throw new Error("Use for…of in sync generator components");
873
- }
874
- impl.f |= IsIterating;
875
- if (impl.f & IsAvailable) {
876
- impl.f &= ~IsAvailable;
877
- }
878
- else {
879
- await new Promise((resolve) => (impl.onAvailable = resolve));
880
- if (impl.f & IsDone) {
881
- break;
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
- yield impl.ret.el.props;
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 impl = this[$ContextImpl];
901
- if (impl.f & IsUnmounted) {
915
+ const ctx = this[_ContextImpl];
916
+ if (ctx.f & IsUnmounted) {
902
917
  console.error("Component is unmounted");
903
- return impl.renderer.read(undefined);
918
+ return ctx.renderer.read(undefined);
904
919
  }
905
- else if (impl.f & IsExecuting) {
920
+ else if (ctx.f & IsSyncExecuting) {
906
921
  console.error("Component is already executing");
907
922
  return this.value;
908
923
  }
909
- resumeCtxIterator(impl);
910
- const value = runComponent(impl);
924
+ const value = enqueueComponentRun(ctx);
911
925
  if (isPromiseLike(value)) {
912
- return value.then((value) => impl.renderer.read(value));
926
+ return value.then((value) => ctx.renderer.read(value));
913
927
  }
914
- return impl.renderer.read(value);
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 impl = this[$ContextImpl];
922
- let callbacks = scheduleMap.get(impl);
935
+ const ctx = this[_ContextImpl];
936
+ let callbacks = scheduleMap.get(ctx);
923
937
  if (!callbacks) {
924
938
  callbacks = new Set();
925
- scheduleMap.set(impl, callbacks);
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 impl = this[$ContextImpl];
935
- if (typeof impl.root !== "object" || impl.root === null) {
948
+ const ctx = this[_ContextImpl];
949
+ if (typeof ctx.root !== "object" || ctx.root === null) {
936
950
  return;
937
951
  }
938
- let flushMap = flushMaps.get(impl.root);
952
+ let flushMap = flushMaps.get(ctx.root);
939
953
  if (!flushMap) {
940
954
  flushMap = new Map();
941
- flushMaps.set(impl.root, flushMap);
955
+ flushMaps.set(ctx.root, flushMap);
942
956
  }
943
- let callbacks = flushMap.get(impl);
957
+ let callbacks = flushMap.get(ctx);
944
958
  if (!callbacks) {
945
959
  callbacks = new Set();
946
- flushMap.set(impl, callbacks);
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 impl = this[$ContextImpl];
956
- let callbacks = cleanupMap.get(impl);
969
+ const ctx = this[_ContextImpl];
970
+ let callbacks = cleanupMap.get(ctx);
957
971
  if (!callbacks) {
958
972
  callbacks = new Set();
959
- cleanupMap.set(impl, callbacks);
973
+ cleanupMap.set(ctx, callbacks);
960
974
  }
961
975
  callbacks.add(callback);
962
976
  }
963
977
  consume(key) {
964
- for (let parent = this[$ContextImpl].parent; parent !== undefined; parent = parent.parent) {
965
- const provisions = provisionMaps.get(parent);
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 impl = this[$ContextImpl];
973
- let provisions = provisionMaps.get(impl);
986
+ const ctx = this[_ContextImpl];
987
+ let provisions = provisionMaps.get(ctx);
974
988
  if (!provisions) {
975
989
  provisions = new Map();
976
- provisionMaps.set(impl, provisions);
990
+ provisionMaps.set(ctx, provisions);
977
991
  }
978
992
  provisions.set(key, value);
979
993
  }
980
994
  addEventListener(type, listener, options) {
981
- const impl = this[$ContextImpl];
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(impl);
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(impl, listeners);
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, callback, listener, options };
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(impl.ret)) {
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 impl = this[$ContextImpl];
1029
- const listeners = listenersMap.get(impl);
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(impl.ret)) {
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 impl = this[$ContextImpl];
1064
+ const ctx = this[_ContextImpl];
1051
1065
  const path = [];
1052
- for (let parent = impl.parent; parent !== undefined; parent = parent.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", impl.ctx);
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.ctx);
1093
+ setEventProperty(ev, "currentTarget", target.owner);
1082
1094
  for (const record of listeners) {
1083
1095
  if (record.type === ev.type && record.options.capture) {
1084
- record.callback.call(target.ctx, ev);
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
- const listeners = listenersMap.get(impl);
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
- record.callback.call(impl.ctx, ev);
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.ctx);
1148
+ setEventProperty(ev, "currentTarget", target.owner);
1120
1149
  for (const record of listeners) {
1121
1150
  if (record.type === ev.type && !record.options.capture) {
1122
- record.callback.call(target.ctx, ev);
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 & IsExecuting) {
1190
+ if (ctx.f & IsSyncExecuting) {
1161
1191
  console.error("Component is already executing");
1162
- return ret.cached;
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
- resumeCtxIterator(ctx);
1170
- return runComponent(ctx);
1199
+ return enqueueComponentRun(ctx);
1171
1200
  }
1172
1201
  function updateComponentChildren(ctx, children) {
1173
- if (ctx.f & IsUnmounted || ctx.f & IsErrored) {
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 &= ~IsExecuting;
1222
+ ctx.f &= ~IsSyncExecuting;
1188
1223
  }
1189
1224
  if (isPromiseLike(childValues)) {
1190
- ctx.ret.inflight = childValues.then((childValues) => commitComponent(ctx, childValues));
1191
- return ctx.ret.inflight;
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.cached);
1212
- let value = (ctx.ret.cached = unwrap(values));
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 (!valuesEqual(oldValues, values)) {
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.cached);
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.cached = undefined;
1299
+ parent.ret.cachedChildValues = undefined;
1265
1300
  }
1266
- host.cached = undefined;
1301
+ host.cachedChildValues = undefined;
1267
1302
  }
1268
- function valuesEqual(values1, values2) {
1269
- if (values1.length !== values2.length) {
1303
+ function arrayEqual(arr1, arr2) {
1304
+ if (arr1.length !== arr2.length) {
1270
1305
  return false;
1271
1306
  }
1272
- for (let i = 0; i < values1.length; i++) {
1273
- const value1 = values1[i];
1274
- const value2 = values2[i];
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
- * Enqueues and executes the component associated with the context.
1283
- *
1284
- * The functions stepComponent and runComponent work together
1285
- * to implement the async queueing behavior of components. The runComponent
1286
- * function calls the stepComponent function, which returns two results in a
1287
- * tuple. The first result, called the “block,” is a possible promise which
1288
- * represents the duration for which the component is blocked from accepting
1289
- * new updates. The second result, called the “value,” is the actual result of
1290
- * the update. The runComponent function caches block/value from the
1291
- * stepComponent function on the context, according to whether the component
1292
- * blocks. The “inflight” block/value properties are the currently executing
1293
- * update, and the “enqueued” block/value properties represent an enqueued next
1294
- * stepComponent. Enqueued steps are dequeued every time the current block
1295
- * promise settles.
1296
- */
1297
- function runComponent(ctx) {
1298
- if (!ctx.inflightBlock) {
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] = stepComponent(ctx);
1362
+ const [block, value] = runComponent(ctx);
1301
1363
  if (block) {
1302
1364
  ctx.inflightBlock = block
1303
- .catch((err) => {
1304
- if (!(ctx.f & IsUpdating)) {
1305
- return propagateError(ctx.parent, err);
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
- let resolve;
1326
- ctx.enqueuedBlock = ctx.inflightBlock
1327
- .then(() => {
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] = stepComponent(ctx);
1330
- resolve(value);
1388
+ const [block, value] = runComponent(ctx);
1331
1389
  if (block) {
1332
- return block.catch((err) => {
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 stepComponent(ctx) {
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.f |= IsExecuting;
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.ctx, ret.el.props);
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 &= ~IsExecuting;
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 oldValue;
1467
+ let iteration;
1405
1468
  if (initial) {
1406
- // The argument passed to the first call to next is ignored.
1407
- oldValue = undefined;
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
- else if (ctx.ret.inflight) {
1410
- // The value passed back into the generator as the argument to the next
1411
- // method is a promise if an async generator component has async children.
1412
- // Sync generator components only resume when their children have fulfilled
1413
- // so the element’s inflight child values will never be defined.
1414
- oldValue = ctx.ret.inflight.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
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
- oldValue = ctx.renderer.read(getValue(ret));
1527
+ // async generator component
1528
+ return [undefined, ctx.inflightValue];
1418
1529
  }
1419
- let iteration;
1420
- ctx.f |= IsExecuting;
1530
+ }
1531
+ async function runAsyncGenComponent(ctx, iterationP) {
1532
+ let done = false;
1421
1533
  try {
1422
- iteration = ctx.iterator.next(oldValue);
1423
- }
1424
- catch (err) {
1425
- ctx.f |= IsDone | IsErrored;
1426
- throw err;
1427
- }
1428
- finally {
1429
- ctx.f &= ~IsExecuting;
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
- ctx.f &= ~IsIterating;
1441
- if (iteration.done) {
1442
- ctx.f |= IsDone;
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
- const value = updateComponentChildren(ctx, iteration.value);
1563
+ value = updateComponentChildren(ctx, iteration.value);
1446
1564
  if (isPromiseLike(value)) {
1447
- return value.catch((err) => handleChildError(ctx, err));
1565
+ value = value.catch((err) => handleChildError(ctx, err));
1448
1566
  }
1449
- return value;
1450
1567
  }
1451
1568
  catch (err) {
1452
- return handleChildError(ctx, err);
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
- catch (err) {
1476
- value = handleChildError(ctx, err);
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 make props available to the props async iterator for async
1497
- * generator components.
1622
+ * Called to resume the props async iterator for async generator components.
1498
1623
  */
1499
- function resumeCtxIterator(ctx) {
1500
- if (ctx.onAvailable) {
1501
- ctx.onAvailable();
1502
- ctx.onAvailable = undefined;
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 |= IsAvailable;
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
- if (!(ctx.f & IsDone)) {
1521
- ctx.f |= IsDone;
1522
- resumeCtxIterator(ctx);
1523
- if (ctx.iterator && typeof ctx.iterator.return === "function") {
1524
- ctx.f |= IsExecuting;
1525
- try {
1526
- const iteration = ctx.iterator.return();
1527
- if (isPromiseLike(iteration)) {
1528
- iteration.catch((err) => propagateError(ctx.parent, err));
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
- finally {
1532
- ctx.f &= ~IsExecuting;
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.f & IsDone ||
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
- resumeCtxIterator(ctx);
1767
+ resumePropsIterator(ctx);
1613
1768
  let iteration;
1614
1769
  try {
1615
- ctx.f |= IsExecuting;
1770
+ ctx.f |= IsSyncExecuting;
1616
1771
  iteration = ctx.iterator.throw(err);
1617
1772
  }
1618
1773
  catch (err) {
1619
- ctx.f |= IsDone | IsErrored;
1774
+ ctx.f |= IsErrored;
1620
1775
  throw err;
1621
1776
  }
1622
1777
  finally {
1623
- ctx.f &= ~IsExecuting;
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 |= IsDone;
1783
+ ctx.f &= ~IsAsyncGen;
1784
+ ctx.iterator = undefined;
1629
1785
  }
1630
1786
  return updateComponentChildren(ctx, iteration.value);
1631
1787
  }, (err) => {
1632
- ctx.f |= IsDone | IsErrored;
1788
+ ctx.f |= IsErrored;
1633
1789
  throw err;
1634
1790
  });
1635
1791
  }
1636
1792
  if (iteration.done) {
1637
- ctx.f |= IsDone;
1793
+ ctx.f &= ~IsSyncGen;
1794
+ ctx.iterator = undefined;
1638
1795
  }
1639
1796
  return updateComponentChildren(ctx, iteration.value);
1640
1797
  }