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

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