@b9g/crank 0.5.0-debug.0 → 0.5.1

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
@@ -22,7 +22,8 @@ function arrayify(value) {
22
22
  : typeof value === "string" ||
23
23
  typeof value[Symbol.iterator] !== "function"
24
24
  ? [value]
25
- : [...value];
25
+ : // TODO: inference broke in TypeScript 3.9.
26
+ [...value];
26
27
  }
27
28
  function isIteratorLike(value) {
28
29
  return value != null && typeof value.next === "function";
@@ -71,8 +72,7 @@ const Copy = Symbol.for("crank.Copy");
71
72
  /**
72
73
  * A special tag for injecting raw nodes or strings via a value prop.
73
74
  *
74
- * If the value prop is a string, Renderer.prototype.parse() will be called on
75
- * the string and the result will be set as the element’s value.
75
+ * Renderer.prototype.raw() is called with the value prop.
76
76
  */
77
77
  const Raw = Symbol.for("crank.Raw");
78
78
  const ElementSymbol = Symbol.for("crank.Element");
@@ -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
  }
@@ -308,16 +292,19 @@ const defaultRendererImpl = {
308
292
  create() {
309
293
  throw new Error("Not implemented");
310
294
  },
295
+ hydrate() {
296
+ throw new Error("Not implemented");
297
+ },
311
298
  scope: IDENTITY,
312
299
  read: IDENTITY,
313
- escape: IDENTITY,
314
- parse: IDENTITY,
300
+ text: IDENTITY,
301
+ raw: IDENTITY,
315
302
  patch: NOOP,
316
303
  arrange: NOOP,
317
304
  dispose: NOOP,
318
305
  flush: NOOP,
319
306
  };
320
- const $RendererImpl = Symbol.for("crank.RendererImpl");
307
+ const _RendererImpl = Symbol.for("crank.RendererImpl");
321
308
  /**
322
309
  * An abstract class which is subclassed to render to different target
323
310
  * environments. This class is responsible for kicking off the rendering
@@ -331,7 +318,7 @@ const $RendererImpl = Symbol.for("crank.RendererImpl");
331
318
  class Renderer {
332
319
  constructor(impl) {
333
320
  this.cache = new WeakMap();
334
- this[$RendererImpl] = {
321
+ this[_RendererImpl] = {
335
322
  ...defaultRendererImpl,
336
323
  ...impl,
337
324
  };
@@ -343,7 +330,7 @@ class Renderer {
343
330
  * used root to delete the previously rendered element tree from the cache.
344
331
  * @param root - The node to be rendered into. The renderer will cache
345
332
  * element trees per root.
346
- * @param ctx - An optional context that will be the ancestor context of all
333
+ * @param bridge - An optional context that will be the ancestor context of all
347
334
  * elements in the tree. Useful for connecting different renderers so that
348
335
  * events/provisions properly propagate. The context for a given root must be
349
336
  * the same or an error will be thrown.
@@ -353,7 +340,7 @@ class Renderer {
353
340
  */
354
341
  render(children, root, bridge) {
355
342
  let ret;
356
- const ctx = bridge && bridge[$ContextImpl];
343
+ const ctx = bridge && bridge[_ContextImpl];
357
344
  if (typeof root === "object" && root !== null) {
358
345
  ret = this.cache.get(root);
359
346
  }
@@ -376,8 +363,32 @@ class Renderer {
376
363
  this.cache.delete(root);
377
364
  }
378
365
  }
379
- const impl = this[$RendererImpl];
380
- const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children);
366
+ const impl = this[_RendererImpl];
367
+ const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children, undefined);
368
+ // We return the child values of the portal because portal elements
369
+ // themselves have no readable value.
370
+ if (isPromiseLike(childValues)) {
371
+ return childValues.then((childValues) => commitRootRender(impl, root, ctx, ret, childValues, oldProps));
372
+ }
373
+ return commitRootRender(impl, root, ctx, ret, childValues, oldProps);
374
+ }
375
+ hydrate(children, root, bridge) {
376
+ const impl = this[_RendererImpl];
377
+ const ctx = bridge && bridge[_ContextImpl];
378
+ let ret;
379
+ ret = this.cache.get(root);
380
+ if (ret !== undefined) {
381
+ // If there is a retainer for the root, hydration is not necessary.
382
+ return this.render(children, root, bridge);
383
+ }
384
+ let oldProps;
385
+ ret = new Retainer(createElement(Portal, { children, root }));
386
+ ret.value = root;
387
+ if (typeof root === "object" && root !== null && children != null) {
388
+ this.cache.set(root, ret);
389
+ }
390
+ const hydrationData = impl.hydrate(Portal, root, {});
391
+ const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children, hydrationData);
381
392
  // We return the child values of the portal because portal elements
382
393
  // themselves have no readable value.
383
394
  if (isPromiseLike(childValues)) {
@@ -389,17 +400,17 @@ class Renderer {
389
400
  /*** PRIVATE RENDERER FUNCTIONS ***/
390
401
  function commitRootRender(renderer, root, ctx, ret, childValues, oldProps) {
391
402
  // element is a host or portal element
392
- if (root !== undefined) {
393
- renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.cached));
403
+ if (root != null) {
404
+ renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.cachedChildValues));
394
405
  flush(renderer, root);
395
406
  }
396
- ret.cached = unwrap(childValues);
407
+ ret.cachedChildValues = unwrap(childValues);
397
408
  if (root == null) {
398
409
  unmount(renderer, ret, ctx, ret);
399
410
  }
400
- return renderer.read(ret.cached);
411
+ return renderer.read(ret.cachedChildValues);
401
412
  }
402
- function diffChildren(renderer, root, host, ctx, scope, parent, children) {
413
+ function diffChildren(renderer, root, host, ctx, scope, parent, children, hydrationData) {
403
414
  const oldRetained = wrap(parent.children);
404
415
  const newRetained = [];
405
416
  const newChildren = arrayify(children);
@@ -408,14 +419,15 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
408
419
  let childrenByKey;
409
420
  let seenKeys;
410
421
  let isAsync = false;
411
- let oi = 0, oldLength = oldRetained.length;
422
+ let hydrationBlock;
423
+ let oi = 0;
424
+ let oldLength = oldRetained.length;
412
425
  for (let ni = 0, newLength = newChildren.length; ni < newLength; ni++) {
413
- // We make sure we don’t access indices out of bounds to prevent
414
- // deoptimizations.
426
+ // length checks to prevent index out of bounds deoptimizations.
415
427
  let ret = oi >= oldLength ? undefined : oldRetained[oi];
416
428
  let child = narrow(newChildren[ni]);
417
429
  {
418
- // Aligning new children with old retainers
430
+ // aligning new children with old retainers
419
431
  let oldKey = typeof ret === "object" ? ret.el.key : undefined;
420
432
  let newKey = typeof child === "object" ? child.key : undefined;
421
433
  if (newKey !== undefined && seenKeys && seenKeys.has(newKey)) {
@@ -450,18 +462,19 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
450
462
  // Updating
451
463
  let value;
452
464
  if (typeof child === "object") {
453
- if (typeof ret === "object" && child.static_) {
454
- ret.el = child;
455
- value = getInflightValue(ret);
456
- }
457
- else if (child.tag === Copy) {
465
+ if (child.tag === Copy) {
458
466
  value = getInflightValue(ret);
459
467
  }
460
468
  else {
461
469
  let oldProps;
470
+ let static_ = false;
462
471
  if (typeof ret === "object" && ret.el.tag === child.tag) {
463
472
  oldProps = ret.el.props;
464
473
  ret.el = child;
474
+ if (child.static_) {
475
+ value = getInflightValue(ret);
476
+ static_ = true;
477
+ }
465
478
  }
466
479
  else {
467
480
  if (typeof ret === "object") {
@@ -469,19 +482,28 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
469
482
  }
470
483
  const fallback = ret;
471
484
  ret = new Retainer(child);
472
- ret.fallback = fallback;
485
+ ret.fallbackValue = fallback;
473
486
  }
474
- if (child.tag === Raw) {
475
- value = updateRaw(renderer, ret, scope, oldProps);
487
+ if (static_) ;
488
+ else if (child.tag === Raw) {
489
+ value = hydrationBlock
490
+ ? hydrationBlock.then(() => updateRaw(renderer, ret, scope, oldProps, hydrationData))
491
+ : updateRaw(renderer, ret, scope, oldProps, hydrationData);
476
492
  }
477
493
  else if (child.tag === Fragment) {
478
- value = updateFragment(renderer, root, host, ctx, scope, ret);
494
+ value = hydrationBlock
495
+ ? hydrationBlock.then(() => updateFragment(renderer, root, host, ctx, scope, ret, hydrationData))
496
+ : updateFragment(renderer, root, host, ctx, scope, ret, hydrationData);
479
497
  }
480
498
  else if (typeof child.tag === "function") {
481
- value = updateComponent(renderer, root, host, ctx, scope, ret, oldProps);
499
+ value = hydrationBlock
500
+ ? hydrationBlock.then(() => updateComponent(renderer, root, host, ctx, scope, ret, oldProps, hydrationData))
501
+ : updateComponent(renderer, root, host, ctx, scope, ret, oldProps, hydrationData);
482
502
  }
483
503
  else {
484
- value = updateHost(renderer, root, ctx, scope, ret, oldProps);
504
+ value = hydrationBlock
505
+ ? hydrationBlock.then(() => updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData))
506
+ : updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData);
485
507
  }
486
508
  }
487
509
  const ref = child.ref;
@@ -493,9 +515,14 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
493
515
  return value;
494
516
  });
495
517
  }
518
+ if (hydrationData !== undefined) {
519
+ hydrationBlock = value;
520
+ }
496
521
  }
497
- else if (typeof ref === "function") {
498
- ref(renderer.read(value));
522
+ else {
523
+ if (typeof ref === "function") {
524
+ ref(renderer.read(value));
525
+ }
499
526
  }
500
527
  }
501
528
  else {
@@ -504,7 +531,7 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
504
531
  (graveyard = graveyard || []).push(ret);
505
532
  }
506
533
  if (typeof child === "string") {
507
- value = ret = renderer.escape(child, scope);
534
+ value = ret = renderer.text(child, scope, hydrationData);
508
535
  }
509
536
  else {
510
537
  ret = undefined;
@@ -537,27 +564,29 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
537
564
  childValues1,
538
565
  new Promise((resolve) => (onChildValues = resolve)),
539
566
  ]);
540
- if (parent.onCommit) {
541
- parent.onCommit(childValues1);
567
+ if (parent.onNextValues) {
568
+ parent.onNextValues(childValues1);
542
569
  }
543
- parent.onCommit = onChildValues;
570
+ parent.onNextValues = onChildValues;
544
571
  return childValues1.then((childValues) => {
545
- parent.inflight = parent.fallback = undefined;
572
+ parent.inflightValue = parent.fallbackValue = undefined;
546
573
  return normalize(childValues);
547
574
  });
548
575
  }
549
- if (graveyard) {
550
- for (let i = 0; i < graveyard.length; i++) {
551
- unmount(renderer, host, ctx, graveyard[i]);
576
+ else {
577
+ if (graveyard) {
578
+ for (let i = 0; i < graveyard.length; i++) {
579
+ unmount(renderer, host, ctx, graveyard[i]);
580
+ }
552
581
  }
582
+ if (parent.onNextValues) {
583
+ parent.onNextValues(values);
584
+ parent.onNextValues = undefined;
585
+ }
586
+ parent.inflightValue = parent.fallbackValue = undefined;
587
+ // We can assert there are no promises in the array because isAsync is false
588
+ return normalize(values);
553
589
  }
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
590
  }
562
591
  function createChildrenByKey(children, offset) {
563
592
  const childrenByKey = new Map();
@@ -577,58 +606,72 @@ function getInflightValue(child) {
577
606
  if (ctx && ctx.f & IsUpdating && ctx.inflightValue) {
578
607
  return ctx.inflightValue;
579
608
  }
580
- else if (child.inflight) {
581
- return child.inflight;
609
+ else if (child.inflightValue) {
610
+ return child.inflightValue;
582
611
  }
583
612
  return getValue(child);
584
613
  }
585
- function updateRaw(renderer, ret, scope, oldProps) {
614
+ function updateRaw(renderer, ret, scope, oldProps, hydrationData) {
586
615
  const props = ret.el.props;
587
- if (typeof props.value === "string") {
588
- if (!oldProps || oldProps.value !== props.value) {
589
- ret.value = renderer.parse(props.value, scope);
590
- }
591
- }
592
- else {
593
- ret.value = props.value;
616
+ if (!oldProps || oldProps.value !== props.value) {
617
+ ret.value = renderer.raw(props.value, scope, hydrationData);
594
618
  }
595
619
  return ret.value;
596
620
  }
597
- function updateFragment(renderer, root, host, ctx, scope, ret) {
598
- const childValues = diffChildren(renderer, root, host, ctx, scope, ret, ret.el.props.children);
621
+ function updateFragment(renderer, root, host, ctx, scope, ret, hydrationData) {
622
+ const childValues = diffChildren(renderer, root, host, ctx, scope, ret, ret.el.props.children, hydrationData);
599
623
  if (isPromiseLike(childValues)) {
600
- ret.inflight = childValues.then((childValues) => unwrap(childValues));
601
- return ret.inflight;
624
+ ret.inflightValue = childValues.then((childValues) => unwrap(childValues));
625
+ return ret.inflightValue;
602
626
  }
603
627
  return unwrap(childValues);
604
628
  }
605
- function updateHost(renderer, root, ctx, scope, ret, oldProps) {
629
+ function updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData) {
606
630
  const el = ret.el;
607
631
  const tag = el.tag;
632
+ let hydrationValue;
608
633
  if (el.tag === Portal) {
609
634
  root = ret.value = el.props.root;
610
635
  }
611
- else if (!oldProps) {
612
- // We use the truthiness of oldProps to determine if this the first render.
613
- ret.value = renderer.create(tag, el.props, scope);
636
+ else {
637
+ if (hydrationData !== undefined) {
638
+ const value = hydrationData.children.shift();
639
+ hydrationValue = value;
640
+ }
614
641
  }
615
642
  scope = renderer.scope(scope, tag, el.props);
616
- const childValues = diffChildren(renderer, root, ret, ctx, scope, ret, ret.el.props.children);
643
+ let childHydrationData;
644
+ if (hydrationValue != null && typeof hydrationValue !== "string") {
645
+ childHydrationData = renderer.hydrate(tag, hydrationValue, el.props);
646
+ if (childHydrationData === undefined) {
647
+ hydrationValue = undefined;
648
+ }
649
+ }
650
+ const childValues = diffChildren(renderer, root, ret, ctx, scope, ret, ret.el.props.children, childHydrationData);
617
651
  if (isPromiseLike(childValues)) {
618
- ret.inflight = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps));
619
- return ret.inflight;
652
+ ret.inflightValue = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue));
653
+ return ret.inflightValue;
620
654
  }
621
- return commitHost(renderer, scope, ret, childValues, oldProps);
655
+ return commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue);
622
656
  }
623
- function commitHost(renderer, scope, ret, childValues, oldProps) {
657
+ function commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue) {
624
658
  const tag = ret.el.tag;
625
- const value = ret.value;
659
+ let value = ret.value;
660
+ if (hydrationValue != null) {
661
+ value = ret.value = hydrationValue;
662
+ }
626
663
  let props = ret.el.props;
627
664
  let copied;
628
665
  if (tag !== Portal) {
666
+ if (value == null) {
667
+ // This assumes that renderer.create does not return nullish values.
668
+ value = ret.value = renderer.create(tag, props, scope);
669
+ }
629
670
  for (const propName in { ...oldProps, ...props }) {
630
671
  const propValue = props[propName];
631
672
  if (propValue === Copy) {
673
+ // TODO: The Copy tag doubles as a way to skip the patching of a prop.
674
+ // Not sure about this feature. Should probably be removed.
632
675
  (copied = copied || new Set()).add(propName);
633
676
  }
634
677
  else if (propName !== "children") {
@@ -643,8 +686,8 @@ function commitHost(renderer, scope, ret, childValues, oldProps) {
643
686
  }
644
687
  ret.el = new Element(tag, props, ret.el.key, ret.el.ref);
645
688
  }
646
- renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.cached));
647
- ret.cached = unwrap(childValues);
689
+ renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.cachedChildValues));
690
+ ret.cachedChildValues = unwrap(childValues);
648
691
  if (tag === Portal) {
649
692
  flush(renderer, ret.value);
650
693
  return;
@@ -691,7 +734,7 @@ function unmount(renderer, host, ctx, ret) {
691
734
  }
692
735
  else if (ret.el.tag === Portal) {
693
736
  host = ret;
694
- renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.cached));
737
+ renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.cachedChildValues));
695
738
  flush(renderer, host.value);
696
739
  }
697
740
  else if (ret.el.tag !== Fragment) {
@@ -715,38 +758,43 @@ function unmount(renderer, host, ctx, ret) {
715
758
  }
716
759
  /*** CONTEXT FLAGS ***/
717
760
  /**
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.
761
+ * A flag which is true when the component is initialized or updated by an
762
+ * ancestor component or the root render call.
763
+ *
764
+ * Used to determine things like whether the nearest host ancestor needs to be
765
+ * rearranged.
721
766
  */
722
767
  const IsUpdating = 1 << 0;
723
768
  /**
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.
769
+ * A flag which is true when the component is synchronously executing.
770
+ *
771
+ * Used to guard against components triggering stack overflow or generator error.
728
772
  */
729
- const IsExecuting = 1 << 1;
773
+ const IsSyncExecuting = 1 << 1;
730
774
  /**
731
- * A flag used to make sure multiple values are not pulled from context prop
732
- * iterators without a yield.
775
+ * A flag which is true when the component is in a for...of loop.
733
776
  */
734
- const IsIterating = 1 << 2;
777
+ const IsInForOfLoop = 1 << 2;
735
778
  /**
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.
779
+ * A flag which is true when the component is in a for await...of loop.
780
+ */
781
+ const IsInForAwaitOfLoop = 1 << 3;
782
+ /**
783
+ * A flag which is true when the component starts the render loop but has not
784
+ * yielded yet.
785
+ *
786
+ * Used to make sure that components yield at least once per loop.
740
787
  */
741
- const IsAvailable = 1 << 3;
788
+ const NeedsToYield = 1 << 4;
742
789
  /**
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.
790
+ * A flag used by async generator components in conjunction with the
791
+ * onAvailable callback to mark whether new props can be pulled via the context
792
+ * async iterator. See the Symbol.asyncIterator method and the
793
+ * resumeCtxIterator function.
746
794
  */
747
- const IsDone = 1 << 4;
795
+ const PropsAvailable = 1 << 5;
748
796
  /**
749
- * A flag which is set when a generator component errors.
797
+ * A flag which is set when a component errors.
750
798
  *
751
799
  * NOTE: This is mainly used to prevent some false positives in component
752
800
  * yields or returns undefined warnings. The reason we’re using this versus
@@ -754,28 +802,28 @@ const IsDone = 1 << 4;
754
802
  * sync generator child) where synchronous code causes a stack overflow error
755
803
  * in a non-deterministic way. Deeply disturbing stuff.
756
804
  */
757
- const IsErrored = 1 << 5;
805
+ const IsErrored = 1 << 6;
758
806
  /**
759
807
  * A flag which is set when the component is unmounted. Unmounted components
760
808
  * are no longer in the element tree and cannot refresh or rerender.
761
809
  */
762
- const IsUnmounted = 1 << 6;
810
+ const IsUnmounted = 1 << 7;
763
811
  /**
764
812
  * A flag which indicates that the component is a sync generator component.
765
813
  */
766
- const IsSyncGen = 1 << 7;
814
+ const IsSyncGen = 1 << 8;
767
815
  /**
768
816
  * A flag which indicates that the component is an async generator component.
769
817
  */
770
- const IsAsyncGen = 1 << 8;
818
+ const IsAsyncGen = 1 << 9;
771
819
  /**
772
820
  * A flag which is set while schedule callbacks are called.
773
821
  */
774
- const IsScheduling = 1 << 9;
822
+ const IsScheduling = 1 << 10;
775
823
  /**
776
824
  * A flag which is set when a schedule callback calls refresh.
777
825
  */
778
- const IsSchedulingRefresh = 1 << 10;
826
+ const IsSchedulingRefresh = 1 << 11;
779
827
  const provisionMaps = new WeakMap();
780
828
  const scheduleMap = new WeakMap();
781
829
  const cleanupMap = new WeakMap();
@@ -783,12 +831,12 @@ const cleanupMap = new WeakMap();
783
831
  const flushMaps = new WeakMap();
784
832
  /**
785
833
  * @internal
786
- * The internal class which holds all context data.
834
+ * The internal class which holds context data.
787
835
  */
788
836
  class ContextImpl {
789
837
  constructor(renderer, root, host, parent, scope, ret) {
790
838
  this.f = 0;
791
- this.ctx = new Context(this);
839
+ this.owner = new Context(this);
792
840
  this.renderer = renderer;
793
841
  this.root = root;
794
842
  this.host = host;
@@ -800,10 +848,11 @@ class ContextImpl {
800
848
  this.inflightValue = undefined;
801
849
  this.enqueuedBlock = undefined;
802
850
  this.enqueuedValue = undefined;
803
- this.onAvailable = undefined;
851
+ this.onProps = undefined;
852
+ this.onPropsRequested = undefined;
804
853
  }
805
854
  }
806
- const $ContextImpl = Symbol.for("crank.ContextImpl");
855
+ const _ContextImpl = Symbol.for("crank.ContextImpl");
807
856
  /**
808
857
  * A class which is instantiated and passed to every component as its this
809
858
  * value. Contexts form a tree just like elements and all components in the
@@ -817,8 +866,10 @@ const $ContextImpl = Symbol.for("crank.ContextImpl");
817
866
  * schedule and cleanup callbacks.
818
867
  */
819
868
  class Context {
869
+ // TODO: If we could make the constructor function take a nicer value, it
870
+ // would be useful for testing purposes.
820
871
  constructor(impl) {
821
- this[$ContextImpl] = impl;
872
+ this[_ContextImpl] = impl;
822
873
  }
823
874
  /**
824
875
  * The current props of the associated element.
@@ -828,7 +879,7 @@ class Context {
828
879
  * plugins or utilities which wrap contexts.
829
880
  */
830
881
  get props() {
831
- return this[$ContextImpl].ret.el.props;
882
+ return this[_ContextImpl].ret.el.props;
832
883
  }
833
884
  // TODO: Should we rename this???
834
885
  /**
@@ -839,45 +890,64 @@ class Context {
839
890
  * mainly for plugins or utilities which wrap contexts.
840
891
  */
841
892
  get value() {
842
- return this[$ContextImpl].renderer.read(getValue(this[$ContextImpl].ret));
893
+ return this[_ContextImpl].renderer.read(getValue(this[_ContextImpl].ret));
843
894
  }
844
895
  *[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");
896
+ const ctx = this[_ContextImpl];
897
+ try {
898
+ ctx.f |= IsInForOfLoop;
899
+ while (!(ctx.f & IsUnmounted)) {
900
+ if (ctx.f & NeedsToYield) {
901
+ throw new Error("Context iterated twice without a yield");
902
+ }
903
+ else {
904
+ ctx.f |= NeedsToYield;
905
+ }
906
+ yield ctx.ret.el.props;
852
907
  }
853
- impl.f |= IsIterating;
854
- yield impl.ret.el.props;
908
+ }
909
+ finally {
910
+ ctx.f &= ~IsInForOfLoop;
855
911
  }
856
912
  }
857
913
  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;
914
+ const ctx = this[_ContextImpl];
915
+ if (ctx.f & IsSyncGen) {
916
+ throw new Error("Use for...of in sync generator components");
917
+ }
918
+ try {
919
+ ctx.f |= IsInForAwaitOfLoop;
920
+ while (!(ctx.f & IsUnmounted)) {
921
+ if (ctx.f & NeedsToYield) {
922
+ throw new Error("Context iterated twice without a yield");
923
+ }
924
+ else {
925
+ ctx.f |= NeedsToYield;
926
+ }
927
+ if (ctx.f & PropsAvailable) {
928
+ ctx.f &= ~PropsAvailable;
929
+ yield ctx.ret.el.props;
930
+ }
931
+ else {
932
+ const props = await new Promise((resolve) => (ctx.onProps = resolve));
933
+ if (ctx.f & IsUnmounted) {
934
+ break;
935
+ }
936
+ yield props;
877
937
  }
938
+ if (ctx.onPropsRequested) {
939
+ ctx.onPropsRequested();
940
+ ctx.onPropsRequested = undefined;
941
+ }
942
+ }
943
+ }
944
+ finally {
945
+ ctx.f &= ~IsInForAwaitOfLoop;
946
+ if (ctx.onPropsRequested) {
947
+ ctx.onPropsRequested();
948
+ ctx.onPropsRequested = undefined;
878
949
  }
879
- yield impl.ret.el.props;
880
- } while (!(impl.f & IsDone));
950
+ }
881
951
  }
882
952
  /**
883
953
  * Re-executes a component.
@@ -892,32 +962,31 @@ class Context {
892
962
  * async iterator to suspend.
893
963
  */
894
964
  refresh() {
895
- const impl = this[$ContextImpl];
896
- if (impl.f & IsUnmounted) {
965
+ const ctx = this[_ContextImpl];
966
+ if (ctx.f & IsUnmounted) {
897
967
  console.error("Component is unmounted");
898
- return impl.renderer.read(undefined);
968
+ return ctx.renderer.read(undefined);
899
969
  }
900
- else if (impl.f & IsExecuting) {
970
+ else if (ctx.f & IsSyncExecuting) {
901
971
  console.error("Component is already executing");
902
972
  return this.value;
903
973
  }
904
- resumeCtxIterator(impl);
905
- const value = runComponent(impl);
974
+ const value = enqueueComponentRun(ctx);
906
975
  if (isPromiseLike(value)) {
907
- return value.then((value) => impl.renderer.read(value));
976
+ return value.then((value) => ctx.renderer.read(value));
908
977
  }
909
- return impl.renderer.read(value);
978
+ return ctx.renderer.read(value);
910
979
  }
911
980
  /**
912
981
  * Registers a callback which fires when the component commits. Will only
913
982
  * fire once per callback and update.
914
983
  */
915
984
  schedule(callback) {
916
- const impl = this[$ContextImpl];
917
- let callbacks = scheduleMap.get(impl);
985
+ const ctx = this[_ContextImpl];
986
+ let callbacks = scheduleMap.get(ctx);
918
987
  if (!callbacks) {
919
988
  callbacks = new Set();
920
- scheduleMap.set(impl, callbacks);
989
+ scheduleMap.set(ctx, callbacks);
921
990
  }
922
991
  callbacks.add(callback);
923
992
  }
@@ -926,19 +995,19 @@ class Context {
926
995
  * rendered into the root. Will only fire once per callback and render.
927
996
  */
928
997
  flush(callback) {
929
- const impl = this[$ContextImpl];
930
- if (typeof impl.root !== "object" || impl.root === null) {
998
+ const ctx = this[_ContextImpl];
999
+ if (typeof ctx.root !== "object" || ctx.root === null) {
931
1000
  return;
932
1001
  }
933
- let flushMap = flushMaps.get(impl.root);
1002
+ let flushMap = flushMaps.get(ctx.root);
934
1003
  if (!flushMap) {
935
1004
  flushMap = new Map();
936
- flushMaps.set(impl.root, flushMap);
1005
+ flushMaps.set(ctx.root, flushMap);
937
1006
  }
938
- let callbacks = flushMap.get(impl);
1007
+ let callbacks = flushMap.get(ctx);
939
1008
  if (!callbacks) {
940
1009
  callbacks = new Set();
941
- flushMap.set(impl, callbacks);
1010
+ flushMap.set(ctx, callbacks);
942
1011
  }
943
1012
  callbacks.add(callback);
944
1013
  }
@@ -947,45 +1016,45 @@ class Context {
947
1016
  * fire once per callback.
948
1017
  */
949
1018
  cleanup(callback) {
950
- const impl = this[$ContextImpl];
951
- let callbacks = cleanupMap.get(impl);
1019
+ const ctx = this[_ContextImpl];
1020
+ let callbacks = cleanupMap.get(ctx);
952
1021
  if (!callbacks) {
953
1022
  callbacks = new Set();
954
- cleanupMap.set(impl, callbacks);
1023
+ cleanupMap.set(ctx, callbacks);
955
1024
  }
956
1025
  callbacks.add(callback);
957
1026
  }
958
1027
  consume(key) {
959
- for (let parent = this[$ContextImpl].parent; parent !== undefined; parent = parent.parent) {
960
- const provisions = provisionMaps.get(parent);
1028
+ for (let ctx = this[_ContextImpl].parent; ctx !== undefined; ctx = ctx.parent) {
1029
+ const provisions = provisionMaps.get(ctx);
961
1030
  if (provisions && provisions.has(key)) {
962
1031
  return provisions.get(key);
963
1032
  }
964
1033
  }
965
1034
  }
966
1035
  provide(key, value) {
967
- const impl = this[$ContextImpl];
968
- let provisions = provisionMaps.get(impl);
1036
+ const ctx = this[_ContextImpl];
1037
+ let provisions = provisionMaps.get(ctx);
969
1038
  if (!provisions) {
970
1039
  provisions = new Map();
971
- provisionMaps.set(impl, provisions);
1040
+ provisionMaps.set(ctx, provisions);
972
1041
  }
973
1042
  provisions.set(key, value);
974
1043
  }
975
1044
  addEventListener(type, listener, options) {
976
- const impl = this[$ContextImpl];
1045
+ const ctx = this[_ContextImpl];
977
1046
  let listeners;
978
1047
  if (!isListenerOrListenerObject(listener)) {
979
1048
  return;
980
1049
  }
981
1050
  else {
982
- const listeners1 = listenersMap.get(impl);
1051
+ const listeners1 = listenersMap.get(ctx);
983
1052
  if (listeners1) {
984
1053
  listeners = listeners1;
985
1054
  }
986
1055
  else {
987
1056
  listeners = [];
988
- listenersMap.set(impl, listeners);
1057
+ listenersMap.set(ctx, listeners);
989
1058
  }
990
1059
  }
991
1060
  options = normalizeListenerOptions(options);
@@ -996,7 +1065,7 @@ class Context {
996
1065
  else {
997
1066
  callback = listener;
998
1067
  }
999
- const record = { type, callback, listener, options };
1068
+ const record = { type, listener, callback, options };
1000
1069
  if (options.once) {
1001
1070
  record.callback = function () {
1002
1071
  const i = listeners.indexOf(record);
@@ -1013,15 +1082,15 @@ class Context {
1013
1082
  }
1014
1083
  listeners.push(record);
1015
1084
  // TODO: is it possible to separate out the EventTarget delegation logic
1016
- for (const value of getChildValues(impl.ret)) {
1085
+ for (const value of getChildValues(ctx.ret)) {
1017
1086
  if (isEventTarget(value)) {
1018
1087
  value.addEventListener(record.type, record.callback, record.options);
1019
1088
  }
1020
1089
  }
1021
1090
  }
1022
1091
  removeEventListener(type, listener, options) {
1023
- const impl = this[$ContextImpl];
1024
- const listeners = listenersMap.get(impl);
1092
+ const ctx = this[_ContextImpl];
1093
+ const listeners = listenersMap.get(ctx);
1025
1094
  if (listeners == null || !isListenerOrListenerObject(listener)) {
1026
1095
  return;
1027
1096
  }
@@ -1035,16 +1104,16 @@ class Context {
1035
1104
  const record = listeners[i];
1036
1105
  listeners.splice(i, 1);
1037
1106
  // TODO: is it possible to separate out the EventTarget delegation logic
1038
- for (const value of getChildValues(impl.ret)) {
1107
+ for (const value of getChildValues(ctx.ret)) {
1039
1108
  if (isEventTarget(value)) {
1040
1109
  value.removeEventListener(record.type, record.callback, record.options);
1041
1110
  }
1042
1111
  }
1043
1112
  }
1044
1113
  dispatchEvent(ev) {
1045
- const impl = this[$ContextImpl];
1114
+ const ctx = this[_ContextImpl];
1046
1115
  const path = [];
1047
- for (let parent = impl.parent; parent !== undefined; parent = parent.parent) {
1116
+ for (let parent = ctx.parent; parent !== undefined; parent = parent.parent) {
1048
1117
  path.push(parent);
1049
1118
  }
1050
1119
  // We patch the stopImmediatePropagation method because ev.cancelBubble
@@ -1056,7 +1125,7 @@ class Context {
1056
1125
  immediateCancelBubble = true;
1057
1126
  return stopImmediatePropagation.call(ev);
1058
1127
  });
1059
- setEventProperty(ev, "target", impl.ctx);
1128
+ setEventProperty(ev, "target", ctx.owner);
1060
1129
  // The only possible errors in this block are errors thrown by callbacks,
1061
1130
  // and dispatchEvent will only log these errors rather than throwing
1062
1131
  // them. Therefore, we place all code in a try block, log errors in the
@@ -1065,18 +1134,21 @@ class Context {
1065
1134
  // Each early return within the try block returns true because while the
1066
1135
  // return value is overridden in the finally block, TypeScript
1067
1136
  // (justifiably) does not recognize the unsafe return statement.
1068
- //
1069
- // TODO: Run all callbacks even if one of them errors
1070
1137
  try {
1071
1138
  setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
1072
1139
  for (let i = path.length - 1; i >= 0; i--) {
1073
1140
  const target = path[i];
1074
1141
  const listeners = listenersMap.get(target);
1075
1142
  if (listeners) {
1076
- setEventProperty(ev, "currentTarget", target.ctx);
1143
+ setEventProperty(ev, "currentTarget", target.owner);
1077
1144
  for (const record of listeners) {
1078
1145
  if (record.type === ev.type && record.options.capture) {
1079
- record.callback.call(target.ctx, ev);
1146
+ try {
1147
+ record.callback.call(target.owner, ev);
1148
+ }
1149
+ catch (err) {
1150
+ console.error(err);
1151
+ }
1080
1152
  if (immediateCancelBubble) {
1081
1153
  return true;
1082
1154
  }
@@ -1088,13 +1160,25 @@ class Context {
1088
1160
  }
1089
1161
  }
1090
1162
  {
1091
- const listeners = listenersMap.get(impl);
1163
+ setEventProperty(ev, "eventPhase", AT_TARGET);
1164
+ setEventProperty(ev, "currentTarget", ctx.owner);
1165
+ const propCallback = ctx.ret.el.props["on" + ev.type];
1166
+ if (propCallback != null) {
1167
+ propCallback(ev);
1168
+ if (immediateCancelBubble || ev.cancelBubble) {
1169
+ return true;
1170
+ }
1171
+ }
1172
+ const listeners = listenersMap.get(ctx);
1092
1173
  if (listeners) {
1093
- setEventProperty(ev, "eventPhase", AT_TARGET);
1094
- setEventProperty(ev, "currentTarget", impl.ctx);
1095
1174
  for (const record of listeners) {
1096
1175
  if (record.type === ev.type) {
1097
- record.callback.call(impl.ctx, ev);
1176
+ try {
1177
+ record.callback.call(ctx.owner, ev);
1178
+ }
1179
+ catch (err) {
1180
+ console.error(err);
1181
+ }
1098
1182
  if (immediateCancelBubble) {
1099
1183
  return true;
1100
1184
  }
@@ -1111,10 +1195,15 @@ class Context {
1111
1195
  const target = path[i];
1112
1196
  const listeners = listenersMap.get(target);
1113
1197
  if (listeners) {
1114
- setEventProperty(ev, "currentTarget", target.ctx);
1198
+ setEventProperty(ev, "currentTarget", target.owner);
1115
1199
  for (const record of listeners) {
1116
1200
  if (record.type === ev.type && !record.options.capture) {
1117
- record.callback.call(target.ctx, ev);
1201
+ try {
1202
+ record.callback.call(target.owner, ev);
1203
+ }
1204
+ catch (err) {
1205
+ console.error(err);
1206
+ }
1118
1207
  if (immediateCancelBubble) {
1119
1208
  return true;
1120
1209
  }
@@ -1127,10 +1216,6 @@ class Context {
1127
1216
  }
1128
1217
  }
1129
1218
  }
1130
- catch (err) {
1131
- // TODO: Use setTimeout to rethrow the error.
1132
- console.error(err);
1133
- }
1134
1219
  finally {
1135
1220
  setEventProperty(ev, "eventPhase", NONE);
1136
1221
  setEventProperty(ev, "currentTarget", null);
@@ -1148,42 +1233,47 @@ function ctxContains(parent, child) {
1148
1233
  }
1149
1234
  return false;
1150
1235
  }
1151
- function updateComponent(renderer, root, host, parent, scope, ret, oldProps) {
1236
+ function updateComponent(renderer, root, host, parent, scope, ret, oldProps, hydrationData) {
1152
1237
  let ctx;
1153
1238
  if (oldProps) {
1154
1239
  ctx = ret.ctx;
1155
- if (ctx.f & IsExecuting) {
1240
+ if (ctx.f & IsSyncExecuting) {
1156
1241
  console.error("Component is already executing");
1157
- return ret.cached;
1242
+ return ret.cachedChildValues;
1158
1243
  }
1159
1244
  }
1160
1245
  else {
1161
1246
  ctx = ret.ctx = new ContextImpl(renderer, root, host, parent, scope, ret);
1162
1247
  }
1163
1248
  ctx.f |= IsUpdating;
1164
- resumeCtxIterator(ctx);
1165
- return runComponent(ctx);
1249
+ return enqueueComponentRun(ctx, hydrationData);
1166
1250
  }
1167
- function updateComponentChildren(ctx, children) {
1168
- if (ctx.f & IsUnmounted || ctx.f & IsErrored) {
1251
+ function updateComponentChildren(ctx, children, hydrationData) {
1252
+ if (ctx.f & IsUnmounted) {
1253
+ return;
1254
+ }
1255
+ else if (ctx.f & IsErrored) {
1256
+ // This branch is necessary for some race conditions where this function is
1257
+ // called after iterator.throw() in async generator components.
1169
1258
  return;
1170
1259
  }
1171
1260
  else if (children === undefined) {
1172
1261
  console.error("A component has returned or yielded undefined. If this was intentional, return or yield null instead.");
1173
1262
  }
1174
1263
  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
1264
  try {
1179
- childValues = diffChildren(ctx.renderer, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children));
1265
+ // TODO: WAT
1266
+ // We set the isExecuting flag in case a child component dispatches an event
1267
+ // which bubbles to this component and causes a synchronous refresh().
1268
+ ctx.f |= IsSyncExecuting;
1269
+ childValues = diffChildren(ctx.renderer, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children), hydrationData);
1180
1270
  }
1181
1271
  finally {
1182
- ctx.f &= ~IsExecuting;
1272
+ ctx.f &= ~IsSyncExecuting;
1183
1273
  }
1184
1274
  if (isPromiseLike(childValues)) {
1185
- ctx.ret.inflight = childValues.then((childValues) => commitComponent(ctx, childValues));
1186
- return ctx.ret.inflight;
1275
+ ctx.ret.inflightValue = childValues.then((childValues) => commitComponent(ctx, childValues));
1276
+ return ctx.ret.inflightValue;
1187
1277
  }
1188
1278
  return commitComponent(ctx, childValues);
1189
1279
  }
@@ -1203,8 +1293,8 @@ function commitComponent(ctx, values) {
1203
1293
  }
1204
1294
  }
1205
1295
  }
1206
- const oldValues = wrap(ctx.ret.cached);
1207
- let value = (ctx.ret.cached = unwrap(values));
1296
+ const oldValues = wrap(ctx.ret.cachedChildValues);
1297
+ let value = (ctx.ret.cachedChildValues = unwrap(values));
1208
1298
  if (ctx.f & IsScheduling) {
1209
1299
  ctx.f |= IsSchedulingRefresh;
1210
1300
  }
@@ -1212,7 +1302,7 @@ function commitComponent(ctx, values) {
1212
1302
  // If we’re not updating the component, which happens when components are
1213
1303
  // refreshed, or when async generator components iterate, we have to do a
1214
1304
  // little bit housekeeping when a component’s child values have changed.
1215
- if (!valuesEqual(oldValues, values)) {
1305
+ if (!arrayEqual(oldValues, values)) {
1216
1306
  const records = getListenerRecords(ctx.parent, ctx.host);
1217
1307
  if (records.length) {
1218
1308
  for (let i = 0; i < values.length; i++) {
@@ -1227,7 +1317,7 @@ function commitComponent(ctx, values) {
1227
1317
  }
1228
1318
  // rearranging the nearest ancestor host element
1229
1319
  const host = ctx.host;
1230
- const oldHostValues = wrap(host.cached);
1320
+ const oldHostValues = wrap(host.cachedChildValues);
1231
1321
  invalidate(ctx, host);
1232
1322
  const hostValues = getChildValues(host);
1233
1323
  ctx.renderer.arrange(host.el.tag, host.value, host.el.props, hostValues,
@@ -1256,50 +1346,79 @@ function commitComponent(ctx, values) {
1256
1346
  }
1257
1347
  function invalidate(ctx, host) {
1258
1348
  for (let parent = ctx.parent; parent !== undefined && parent.host === host; parent = parent.parent) {
1259
- parent.ret.cached = undefined;
1349
+ parent.ret.cachedChildValues = undefined;
1260
1350
  }
1261
- host.cached = undefined;
1351
+ host.cachedChildValues = undefined;
1262
1352
  }
1263
- function valuesEqual(values1, values2) {
1264
- if (values1.length !== values2.length) {
1353
+ function arrayEqual(arr1, arr2) {
1354
+ if (arr1.length !== arr2.length) {
1265
1355
  return false;
1266
1356
  }
1267
- for (let i = 0; i < values1.length; i++) {
1268
- const value1 = values1[i];
1269
- const value2 = values2[i];
1357
+ for (let i = 0; i < arr1.length; i++) {
1358
+ const value1 = arr1[i];
1359
+ const value2 = arr2[i];
1270
1360
  if (value1 !== value2) {
1271
1361
  return false;
1272
1362
  }
1273
1363
  }
1274
1364
  return true;
1275
1365
  }
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) {
1366
+ /** Enqueues and executes the component associated with the context. */
1367
+ function enqueueComponentRun(ctx, hydrationData) {
1368
+ if (ctx.f & IsAsyncGen && !(ctx.f & IsInForOfLoop)) {
1369
+ if (hydrationData !== undefined) {
1370
+ throw new Error("Hydration error");
1371
+ }
1372
+ // This branch will run for non-initial renders of async generator
1373
+ // components when they are not in for...of loops. When in a for...of loop,
1374
+ // async generator components will behave normally.
1375
+ //
1376
+ // Async gen componennts can be in one of three states:
1377
+ //
1378
+ // 1. propsAvailable flag is true: "available"
1379
+ //
1380
+ // The component is suspended somewhere in the loop. When the component
1381
+ // reaches the bottom of the loop, it will run again with the next props.
1382
+ //
1383
+ // 2. onAvailable callback is defined: "suspended"
1384
+ //
1385
+ // The component has suspended at the bottom of the loop and is waiting
1386
+ // for new props.
1387
+ //
1388
+ // 3. neither 1 or 2: "Running"
1389
+ //
1390
+ // The component is suspended somewhere in the loop. When the component
1391
+ // reaches the bottom of the loop, it will suspend.
1392
+ //
1393
+ // Components will never be both available and suspended at
1394
+ // the same time.
1395
+ //
1396
+ // If the component is at the loop bottom, this means that the next value
1397
+ // produced by the component will have the most up to date props, so we can
1398
+ // simply return the current inflight value. Otherwise, we have to wait for
1399
+ // the bottom of the loop to be reached before returning the inflight
1400
+ // value.
1401
+ const isAtLoopbottom = ctx.f & IsInForAwaitOfLoop && !ctx.onProps;
1402
+ resumePropsIterator(ctx);
1403
+ if (isAtLoopbottom) {
1404
+ if (ctx.inflightBlock == null) {
1405
+ ctx.inflightBlock = new Promise((resolve) => (ctx.onPropsRequested = resolve));
1406
+ }
1407
+ return ctx.inflightBlock.then(() => {
1408
+ ctx.inflightBlock = undefined;
1409
+ return ctx.inflightValue;
1410
+ });
1411
+ }
1412
+ return ctx.inflightValue;
1413
+ }
1414
+ else if (!ctx.inflightBlock) {
1294
1415
  try {
1295
- const [block, value] = stepComponent(ctx);
1416
+ const [block, value] = runComponent(ctx, hydrationData);
1296
1417
  if (block) {
1297
1418
  ctx.inflightBlock = block
1298
- .catch((err) => {
1299
- if (!(ctx.f & IsUpdating)) {
1300
- return propagateError(ctx.parent, err);
1301
- }
1302
- })
1419
+ // TODO: there is some fuckery going on here related to async
1420
+ // generator components resuming when they’re meant to be returned.
1421
+ .then((v) => v)
1303
1422
  .finally(() => advanceComponent(ctx));
1304
1423
  // stepComponent will only return a block if the value is asynchronous
1305
1424
  ctx.inflightValue = value;
@@ -1313,38 +1432,46 @@ function runComponent(ctx) {
1313
1432
  throw err;
1314
1433
  }
1315
1434
  }
1316
- else if (ctx.f & IsAsyncGen) {
1317
- return ctx.inflightValue;
1318
- }
1319
1435
  else if (!ctx.enqueuedBlock) {
1320
- let resolve;
1321
- ctx.enqueuedBlock = ctx.inflightBlock
1322
- .then(() => {
1436
+ if (hydrationData !== undefined) {
1437
+ throw new Error("Hydration error");
1438
+ }
1439
+ // We need to assign enqueuedBlock and enqueuedValue synchronously, hence
1440
+ // the Promise constructor call here.
1441
+ let resolveEnqueuedBlock;
1442
+ ctx.enqueuedBlock = new Promise((resolve) => (resolveEnqueuedBlock = resolve));
1443
+ ctx.enqueuedValue = ctx.inflightBlock.then(() => {
1323
1444
  try {
1324
- const [block, value] = stepComponent(ctx);
1325
- resolve(value);
1445
+ const [block, value] = runComponent(ctx);
1326
1446
  if (block) {
1327
- return block.catch((err) => {
1328
- if (!(ctx.f & IsUpdating)) {
1329
- return propagateError(ctx.parent, err);
1330
- }
1331
- });
1447
+ resolveEnqueuedBlock(block.finally(() => advanceComponent(ctx)));
1332
1448
  }
1449
+ return value;
1333
1450
  }
1334
1451
  catch (err) {
1335
1452
  if (!(ctx.f & IsUpdating)) {
1336
1453
  return propagateError(ctx.parent, err);
1337
1454
  }
1455
+ throw err;
1338
1456
  }
1339
- })
1340
- .finally(() => advanceComponent(ctx));
1341
- ctx.enqueuedValue = new Promise((resolve1) => (resolve = resolve1));
1457
+ });
1342
1458
  }
1343
1459
  return ctx.enqueuedValue;
1344
1460
  }
1461
+ /** Called when the inflight block promise settles. */
1462
+ function advanceComponent(ctx) {
1463
+ if (ctx.f & IsAsyncGen && !(ctx.f & IsInForOfLoop)) {
1464
+ return;
1465
+ }
1466
+ ctx.inflightBlock = ctx.enqueuedBlock;
1467
+ ctx.inflightValue = ctx.enqueuedValue;
1468
+ ctx.enqueuedBlock = undefined;
1469
+ ctx.enqueuedValue = undefined;
1470
+ }
1345
1471
  /**
1346
1472
  * This function is responsible for executing the component and handling all
1347
- * the different component types.
1473
+ * the different component types. We cannot identify whether a component is a
1474
+ * generator or async without calling it and inspecting the return value.
1348
1475
  *
1349
1476
  * @returns {[block, value]} A tuple where
1350
1477
  * block - A possible promise which represents the duration during which the
@@ -1359,25 +1486,23 @@ function runComponent(ctx) {
1359
1486
  * - Sync generator components block while any children are executing, because
1360
1487
  * they are expected to only resume when they’ve actually rendered.
1361
1488
  */
1362
- function stepComponent(ctx) {
1489
+ function runComponent(ctx, hydrationData) {
1363
1490
  const ret = ctx.ret;
1364
- if (ctx.f & IsDone) {
1365
- return [undefined, getValue(ret)];
1366
- }
1367
1491
  const initial = !ctx.iterator;
1368
1492
  if (initial) {
1369
- ctx.f |= IsExecuting;
1493
+ resumePropsIterator(ctx);
1494
+ ctx.f |= IsSyncExecuting;
1370
1495
  clearEventListeners(ctx);
1371
1496
  let result;
1372
1497
  try {
1373
- result = ret.el.tag.call(ctx.ctx, ret.el.props);
1498
+ result = ret.el.tag.call(ctx.owner, ret.el.props);
1374
1499
  }
1375
1500
  catch (err) {
1376
1501
  ctx.f |= IsErrored;
1377
1502
  throw err;
1378
1503
  }
1379
1504
  finally {
1380
- ctx.f &= ~IsExecuting;
1505
+ ctx.f &= ~IsSyncExecuting;
1381
1506
  }
1382
1507
  if (isIteratorLike(result)) {
1383
1508
  ctx.iterator = result;
@@ -1385,124 +1510,241 @@ function stepComponent(ctx) {
1385
1510
  else if (isPromiseLike(result)) {
1386
1511
  // async function component
1387
1512
  const result1 = result instanceof Promise ? result : Promise.resolve(result);
1388
- const value = result1.then((result) => updateComponentChildren(ctx, result), (err) => {
1513
+ const value = result1.then((result) => updateComponentChildren(ctx, result, hydrationData), (err) => {
1389
1514
  ctx.f |= IsErrored;
1390
1515
  throw err;
1391
1516
  });
1392
- return [result1, value];
1517
+ return [result1.catch(NOOP), value];
1393
1518
  }
1394
1519
  else {
1395
1520
  // sync function component
1396
- return [undefined, updateComponentChildren(ctx, result)];
1521
+ return [
1522
+ undefined,
1523
+ updateComponentChildren(ctx, result, hydrationData),
1524
+ ];
1397
1525
  }
1398
1526
  }
1399
- let oldValue;
1400
- if (initial) {
1401
- // The argument passed to the first call to next is ignored.
1402
- oldValue = undefined;
1403
- }
1404
- else if (ctx.ret.inflight) {
1405
- // The value passed back into the generator as the argument to the next
1406
- // method is a promise if an async generator component has async children.
1407
- // Sync generator components only resume when their children have fulfilled
1408
- // so the element’s inflight child values will never be defined.
1409
- oldValue = ctx.ret.inflight.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
1410
- }
1411
- else {
1412
- oldValue = ctx.renderer.read(getValue(ret));
1527
+ else if (hydrationData !== undefined) {
1528
+ throw new Error("Hydration error");
1413
1529
  }
1414
1530
  let iteration;
1415
- ctx.f |= IsExecuting;
1416
- try {
1417
- iteration = ctx.iterator.next(oldValue);
1418
- }
1419
- catch (err) {
1420
- ctx.f |= IsDone | IsErrored;
1421
- throw err;
1531
+ if (initial) {
1532
+ try {
1533
+ ctx.f |= IsSyncExecuting;
1534
+ iteration = ctx.iterator.next();
1535
+ }
1536
+ catch (err) {
1537
+ ctx.f |= IsErrored;
1538
+ throw err;
1539
+ }
1540
+ finally {
1541
+ ctx.f &= ~IsSyncExecuting;
1542
+ }
1543
+ if (isPromiseLike(iteration)) {
1544
+ ctx.f |= IsAsyncGen;
1545
+ }
1546
+ else {
1547
+ ctx.f |= IsSyncGen;
1548
+ }
1422
1549
  }
1423
- finally {
1424
- ctx.f &= ~IsExecuting;
1550
+ if (ctx.f & IsSyncGen) {
1551
+ ctx.f &= ~NeedsToYield;
1552
+ // sync generator component
1553
+ if (!initial) {
1554
+ try {
1555
+ ctx.f |= IsSyncExecuting;
1556
+ iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
1557
+ }
1558
+ catch (err) {
1559
+ ctx.f |= IsErrored;
1560
+ throw err;
1561
+ }
1562
+ finally {
1563
+ ctx.f &= ~IsSyncExecuting;
1564
+ }
1565
+ }
1566
+ if (isPromiseLike(iteration)) {
1567
+ throw new Error("Mixed generator component");
1568
+ }
1569
+ if (iteration.done) {
1570
+ ctx.f &= ~IsSyncGen;
1571
+ ctx.iterator = undefined;
1572
+ }
1573
+ let value;
1574
+ try {
1575
+ value = updateComponentChildren(ctx,
1576
+ // Children can be void so we eliminate that here
1577
+ iteration.value, hydrationData);
1578
+ if (isPromiseLike(value)) {
1579
+ value = value.catch((err) => handleChildError(ctx, err));
1580
+ }
1581
+ }
1582
+ catch (err) {
1583
+ value = handleChildError(ctx, err);
1584
+ }
1585
+ const block = isPromiseLike(value) ? value.catch(NOOP) : undefined;
1586
+ return [block, value];
1425
1587
  }
1426
- if (isPromiseLike(iteration)) {
1427
- // async generator component
1428
- if (initial) {
1429
- ctx.f |= IsAsyncGen;
1588
+ else if (ctx.f & IsInForOfLoop) {
1589
+ // TODO: does this need to be done async?
1590
+ ctx.f &= ~NeedsToYield;
1591
+ // we are in a for...of loop for async generator
1592
+ if (!initial) {
1593
+ try {
1594
+ ctx.f |= IsSyncExecuting;
1595
+ iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
1596
+ }
1597
+ catch (err) {
1598
+ ctx.f |= IsErrored;
1599
+ throw err;
1600
+ }
1601
+ finally {
1602
+ ctx.f &= ~IsSyncExecuting;
1603
+ }
1604
+ }
1605
+ if (!isPromiseLike(iteration)) {
1606
+ throw new Error("Mixed generator component");
1430
1607
  }
1608
+ const block = iteration.catch(NOOP);
1431
1609
  const value = iteration.then((iteration) => {
1432
- if (!(ctx.f & IsIterating)) {
1433
- ctx.f &= ~IsAvailable;
1434
- }
1435
- ctx.f &= ~IsIterating;
1436
- if (iteration.done) {
1437
- ctx.f |= IsDone;
1610
+ let value;
1611
+ if (!(ctx.f & IsInForOfLoop)) {
1612
+ runAsyncGenComponent(ctx, Promise.resolve(iteration), hydrationData);
1438
1613
  }
1439
1614
  try {
1440
- const value = updateComponentChildren(ctx, iteration.value);
1615
+ value = updateComponentChildren(ctx,
1616
+ // Children can be void so we eliminate that here
1617
+ iteration.value, hydrationData);
1441
1618
  if (isPromiseLike(value)) {
1442
- return value.catch((err) => handleChildError(ctx, err));
1619
+ value = value.catch((err) => handleChildError(ctx, err));
1443
1620
  }
1444
- return value;
1445
1621
  }
1446
1622
  catch (err) {
1447
- return handleChildError(ctx, err);
1623
+ value = handleChildError(ctx, err);
1448
1624
  }
1625
+ return value;
1449
1626
  }, (err) => {
1450
- ctx.f |= IsDone | IsErrored;
1627
+ ctx.f |= IsErrored;
1451
1628
  throw err;
1452
1629
  });
1453
- return [iteration, value];
1454
- }
1455
- // sync generator component
1456
- if (initial) {
1457
- ctx.f |= IsSyncGen;
1630
+ return [block, value];
1458
1631
  }
1459
- ctx.f &= ~IsIterating;
1460
- if (iteration.done) {
1461
- ctx.f |= IsDone;
1632
+ else {
1633
+ runAsyncGenComponent(ctx, iteration, hydrationData);
1634
+ // async generator component
1635
+ return [ctx.inflightBlock, ctx.inflightValue];
1462
1636
  }
1463
- let value;
1637
+ }
1638
+ async function runAsyncGenComponent(ctx, iterationP, hydrationData) {
1639
+ let done = false;
1464
1640
  try {
1465
- value = updateComponentChildren(ctx, iteration.value);
1466
- if (isPromiseLike(value)) {
1467
- value = value.catch((err) => handleChildError(ctx, err));
1641
+ while (!done) {
1642
+ if (ctx.f & IsInForOfLoop) {
1643
+ break;
1644
+ }
1645
+ // inflightValue must be set synchronously.
1646
+ let onValue;
1647
+ ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
1648
+ if (ctx.f & IsUpdating) {
1649
+ // We should not swallow unhandled promise rejections if the component is
1650
+ // updating independently.
1651
+ // TODO: Does this handle this.refresh() calls?
1652
+ ctx.inflightValue.catch(NOOP);
1653
+ }
1654
+ let iteration;
1655
+ try {
1656
+ iteration = await iterationP;
1657
+ }
1658
+ catch (err) {
1659
+ done = true;
1660
+ ctx.f |= IsErrored;
1661
+ onValue(Promise.reject(err));
1662
+ break;
1663
+ }
1664
+ finally {
1665
+ ctx.f &= ~NeedsToYield;
1666
+ if (!(ctx.f & IsInForAwaitOfLoop)) {
1667
+ ctx.f &= ~PropsAvailable;
1668
+ }
1669
+ }
1670
+ done = !!iteration.done;
1671
+ let value;
1672
+ try {
1673
+ value = updateComponentChildren(ctx, iteration.value, hydrationData);
1674
+ hydrationData = undefined;
1675
+ if (isPromiseLike(value)) {
1676
+ value = value.catch((err) => handleChildError(ctx, err));
1677
+ }
1678
+ }
1679
+ catch (err) {
1680
+ // Do we need to catch potential errors here in the case of unhandled
1681
+ // promise rejections?
1682
+ value = handleChildError(ctx, err);
1683
+ }
1684
+ finally {
1685
+ onValue(value);
1686
+ }
1687
+ // TODO: this can be done more elegantly
1688
+ let oldValue;
1689
+ if (ctx.ret.inflightValue) {
1690
+ // The value passed back into the generator as the argument to the next
1691
+ // method is a promise if an async generator component has async
1692
+ // children. Sync generator components only resume when their children
1693
+ // have fulfilled so the element’s inflight child values will never be
1694
+ // defined.
1695
+ oldValue = ctx.ret.inflightValue.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
1696
+ }
1697
+ else {
1698
+ oldValue = ctx.renderer.read(getValue(ctx.ret));
1699
+ }
1700
+ if (ctx.f & IsUnmounted) {
1701
+ if (ctx.f & IsInForAwaitOfLoop) {
1702
+ try {
1703
+ ctx.f |= IsSyncExecuting;
1704
+ iterationP = ctx.iterator.next(oldValue);
1705
+ }
1706
+ finally {
1707
+ ctx.f &= ~IsSyncExecuting;
1708
+ }
1709
+ }
1710
+ else {
1711
+ returnComponent(ctx);
1712
+ break;
1713
+ }
1714
+ }
1715
+ else if (!done && !(ctx.f & IsInForOfLoop)) {
1716
+ try {
1717
+ ctx.f |= IsSyncExecuting;
1718
+ iterationP = ctx.iterator.next(oldValue);
1719
+ }
1720
+ finally {
1721
+ ctx.f &= ~IsSyncExecuting;
1722
+ }
1723
+ }
1468
1724
  }
1469
1725
  }
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);
1726
+ finally {
1727
+ if (done) {
1728
+ ctx.f &= ~IsAsyncGen;
1729
+ ctx.iterator = undefined;
1730
+ }
1488
1731
  }
1489
1732
  }
1490
1733
  /**
1491
- * Called to make props available to the props async iterator for async
1492
- * generator components.
1734
+ * Called to resume the props async iterator for async generator components.
1493
1735
  */
1494
- function resumeCtxIterator(ctx) {
1495
- if (ctx.onAvailable) {
1496
- ctx.onAvailable();
1497
- ctx.onAvailable = undefined;
1736
+ function resumePropsIterator(ctx) {
1737
+ if (ctx.onProps) {
1738
+ ctx.onProps(ctx.ret.el.props);
1739
+ ctx.onProps = undefined;
1740
+ ctx.f &= ~PropsAvailable;
1498
1741
  }
1499
1742
  else {
1500
- ctx.f |= IsAvailable;
1743
+ ctx.f |= PropsAvailable;
1501
1744
  }
1502
1745
  }
1503
1746
  // TODO: async unmounting
1504
1747
  function unmountComponent(ctx) {
1505
- ctx.f |= IsUnmounted;
1506
1748
  clearEventListeners(ctx);
1507
1749
  const callbacks = cleanupMap.get(ctx);
1508
1750
  if (callbacks) {
@@ -1512,23 +1754,71 @@ function unmountComponent(ctx) {
1512
1754
  callback(value);
1513
1755
  }
1514
1756
  }
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));
1757
+ ctx.f |= IsUnmounted;
1758
+ if (ctx.iterator) {
1759
+ if (ctx.f & IsSyncGen) {
1760
+ let value;
1761
+ if (ctx.f & IsInForOfLoop) {
1762
+ value = enqueueComponentRun(ctx);
1763
+ }
1764
+ if (isPromiseLike(value)) {
1765
+ value.then(() => {
1766
+ if (ctx.f & IsInForOfLoop) {
1767
+ unmountComponent(ctx);
1768
+ }
1769
+ else {
1770
+ returnComponent(ctx);
1771
+ }
1772
+ }, (err) => {
1773
+ propagateError(ctx.parent, err);
1774
+ });
1775
+ }
1776
+ else {
1777
+ if (ctx.f & IsInForOfLoop) {
1778
+ unmountComponent(ctx);
1779
+ }
1780
+ else {
1781
+ returnComponent(ctx);
1524
1782
  }
1525
1783
  }
1526
- finally {
1527
- ctx.f &= ~IsExecuting;
1784
+ }
1785
+ else if (ctx.f & IsAsyncGen) {
1786
+ if (ctx.f & IsInForOfLoop) {
1787
+ const value = enqueueComponentRun(ctx);
1788
+ value.then(() => {
1789
+ if (ctx.f & IsInForOfLoop) {
1790
+ unmountComponent(ctx);
1791
+ }
1792
+ else {
1793
+ returnComponent(ctx);
1794
+ }
1795
+ }, (err) => {
1796
+ propagateError(ctx.parent, err);
1797
+ });
1798
+ }
1799
+ else {
1800
+ // The logic for unmounting async generator components is in the
1801
+ // runAsyncGenComponent function.
1802
+ resumePropsIterator(ctx);
1528
1803
  }
1529
1804
  }
1530
1805
  }
1531
1806
  }
1807
+ function returnComponent(ctx) {
1808
+ resumePropsIterator(ctx);
1809
+ if (ctx.iterator && typeof ctx.iterator.return === "function") {
1810
+ try {
1811
+ ctx.f |= IsSyncExecuting;
1812
+ const iteration = ctx.iterator.return();
1813
+ if (isPromiseLike(iteration)) {
1814
+ iteration.catch((err) => propagateError(ctx.parent, err));
1815
+ }
1816
+ }
1817
+ finally {
1818
+ ctx.f &= ~IsSyncExecuting;
1819
+ }
1820
+ }
1821
+ }
1532
1822
  /*** EVENT TARGET UTILITIES ***/
1533
1823
  // EVENT PHASE CONSTANTS
1534
1824
  // https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
@@ -1597,39 +1887,39 @@ function clearEventListeners(ctx) {
1597
1887
  }
1598
1888
  }
1599
1889
  /*** ERROR HANDLING UTILITIES ***/
1600
- // TODO: generator components which throw errors should be recoverable
1601
1890
  function handleChildError(ctx, err) {
1602
- if (ctx.f & IsDone ||
1603
- !ctx.iterator ||
1604
- typeof ctx.iterator.throw !== "function") {
1891
+ if (!ctx.iterator || typeof ctx.iterator.throw !== "function") {
1605
1892
  throw err;
1606
1893
  }
1607
- resumeCtxIterator(ctx);
1894
+ resumePropsIterator(ctx);
1608
1895
  let iteration;
1609
1896
  try {
1610
- ctx.f |= IsExecuting;
1897
+ ctx.f |= IsSyncExecuting;
1611
1898
  iteration = ctx.iterator.throw(err);
1612
1899
  }
1613
1900
  catch (err) {
1614
- ctx.f |= IsDone | IsErrored;
1901
+ ctx.f |= IsErrored;
1615
1902
  throw err;
1616
1903
  }
1617
1904
  finally {
1618
- ctx.f &= ~IsExecuting;
1905
+ ctx.f &= ~IsSyncExecuting;
1619
1906
  }
1620
1907
  if (isPromiseLike(iteration)) {
1621
1908
  return iteration.then((iteration) => {
1622
1909
  if (iteration.done) {
1623
- ctx.f |= IsDone;
1910
+ ctx.f &= ~IsAsyncGen;
1911
+ ctx.iterator = undefined;
1624
1912
  }
1625
1913
  return updateComponentChildren(ctx, iteration.value);
1626
1914
  }, (err) => {
1627
- ctx.f |= IsDone | IsErrored;
1915
+ ctx.f |= IsErrored;
1628
1916
  throw err;
1629
1917
  });
1630
1918
  }
1631
1919
  if (iteration.done) {
1632
- ctx.f |= IsDone;
1920
+ ctx.f &= ~IsSyncGen;
1921
+ ctx.f &= ~IsAsyncGen;
1922
+ ctx.iterator = undefined;
1633
1923
  }
1634
1924
  return updateComponentChildren(ctx, iteration.value);
1635
1925
  }