@b9g/crank 0.5.0-debug.0 → 0.5.0

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,69 @@ 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 = hydrationValue || ret.value;
626
660
  let props = ret.el.props;
627
661
  let copied;
628
662
  if (tag !== Portal) {
663
+ if (value == null) {
664
+ // This assumes that renderer.create does not return nullish values.
665
+ value = ret.value = renderer.create(tag, props, scope);
666
+ }
629
667
  for (const propName in { ...oldProps, ...props }) {
630
668
  const propValue = props[propName];
631
669
  if (propValue === Copy) {
670
+ // TODO: The Copy tag doubles as a way to skip the patching of a prop.
671
+ // Not sure about this feature. Should probably be removed.
632
672
  (copied = copied || new Set()).add(propName);
633
673
  }
634
674
  else if (propName !== "children") {
@@ -643,8 +683,8 @@ function commitHost(renderer, scope, ret, childValues, oldProps) {
643
683
  }
644
684
  ret.el = new Element(tag, props, ret.el.key, ret.el.ref);
645
685
  }
646
- renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.cached));
647
- ret.cached = unwrap(childValues);
686
+ renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.cachedChildValues));
687
+ ret.cachedChildValues = unwrap(childValues);
648
688
  if (tag === Portal) {
649
689
  flush(renderer, ret.value);
650
690
  return;
@@ -691,7 +731,7 @@ function unmount(renderer, host, ctx, ret) {
691
731
  }
692
732
  else if (ret.el.tag === Portal) {
693
733
  host = ret;
694
- renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.cached));
734
+ renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.cachedChildValues));
695
735
  flush(renderer, host.value);
696
736
  }
697
737
  else if (ret.el.tag !== Fragment) {
@@ -715,38 +755,43 @@ function unmount(renderer, host, ctx, ret) {
715
755
  }
716
756
  /*** CONTEXT FLAGS ***/
717
757
  /**
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.
758
+ * A flag which is true when the component is initialized or updated by an
759
+ * ancestor component or the root render call.
760
+ *
761
+ * Used to determine things like whether the nearest host ancestor needs to be
762
+ * rearranged.
721
763
  */
722
764
  const IsUpdating = 1 << 0;
723
765
  /**
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.
766
+ * A flag which is true when the component is synchronously executing.
767
+ *
768
+ * Used to guard against components triggering stack overflow or generator error.
728
769
  */
729
- const IsExecuting = 1 << 1;
770
+ const IsSyncExecuting = 1 << 1;
730
771
  /**
731
- * A flag used to make sure multiple values are not pulled from context prop
732
- * iterators without a yield.
772
+ * A flag which is true when the component is in a for...of loop.
733
773
  */
734
- const IsIterating = 1 << 2;
774
+ const IsInForOfLoop = 1 << 2;
735
775
  /**
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.
776
+ * A flag which is true when the component is in a for await...of loop.
740
777
  */
741
- const IsAvailable = 1 << 3;
778
+ const IsInForAwaitOfLoop = 1 << 3;
742
779
  /**
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.
780
+ * A flag which is true when the component starts the render loop but has not
781
+ * yielded yet.
782
+ *
783
+ * Used to make sure that components yield at least once per loop.
784
+ */
785
+ const NeedsToYield = 1 << 4;
786
+ /**
787
+ * A flag used by async generator components in conjunction with the
788
+ * onAvailable callback to mark whether new props can be pulled via the context
789
+ * async iterator. See the Symbol.asyncIterator method and the
790
+ * resumeCtxIterator function.
746
791
  */
747
- const IsDone = 1 << 4;
792
+ const PropsAvailable = 1 << 5;
748
793
  /**
749
- * A flag which is set when a generator component errors.
794
+ * A flag which is set when a component errors.
750
795
  *
751
796
  * NOTE: This is mainly used to prevent some false positives in component
752
797
  * yields or returns undefined warnings. The reason we’re using this versus
@@ -754,28 +799,28 @@ const IsDone = 1 << 4;
754
799
  * sync generator child) where synchronous code causes a stack overflow error
755
800
  * in a non-deterministic way. Deeply disturbing stuff.
756
801
  */
757
- const IsErrored = 1 << 5;
802
+ const IsErrored = 1 << 6;
758
803
  /**
759
804
  * A flag which is set when the component is unmounted. Unmounted components
760
805
  * are no longer in the element tree and cannot refresh or rerender.
761
806
  */
762
- const IsUnmounted = 1 << 6;
807
+ const IsUnmounted = 1 << 7;
763
808
  /**
764
809
  * A flag which indicates that the component is a sync generator component.
765
810
  */
766
- const IsSyncGen = 1 << 7;
811
+ const IsSyncGen = 1 << 8;
767
812
  /**
768
813
  * A flag which indicates that the component is an async generator component.
769
814
  */
770
- const IsAsyncGen = 1 << 8;
815
+ const IsAsyncGen = 1 << 9;
771
816
  /**
772
817
  * A flag which is set while schedule callbacks are called.
773
818
  */
774
- const IsScheduling = 1 << 9;
819
+ const IsScheduling = 1 << 10;
775
820
  /**
776
821
  * A flag which is set when a schedule callback calls refresh.
777
822
  */
778
- const IsSchedulingRefresh = 1 << 10;
823
+ const IsSchedulingRefresh = 1 << 11;
779
824
  const provisionMaps = new WeakMap();
780
825
  const scheduleMap = new WeakMap();
781
826
  const cleanupMap = new WeakMap();
@@ -783,12 +828,12 @@ const cleanupMap = new WeakMap();
783
828
  const flushMaps = new WeakMap();
784
829
  /**
785
830
  * @internal
786
- * The internal class which holds all context data.
831
+ * The internal class which holds context data.
787
832
  */
788
833
  class ContextImpl {
789
834
  constructor(renderer, root, host, parent, scope, ret) {
790
835
  this.f = 0;
791
- this.ctx = new Context(this);
836
+ this.owner = new Context(this);
792
837
  this.renderer = renderer;
793
838
  this.root = root;
794
839
  this.host = host;
@@ -800,10 +845,11 @@ class ContextImpl {
800
845
  this.inflightValue = undefined;
801
846
  this.enqueuedBlock = undefined;
802
847
  this.enqueuedValue = undefined;
803
- this.onAvailable = undefined;
848
+ this.onProps = undefined;
849
+ this.onPropsRequested = undefined;
804
850
  }
805
851
  }
806
- const $ContextImpl = Symbol.for("crank.ContextImpl");
852
+ const _ContextImpl = Symbol.for("crank.ContextImpl");
807
853
  /**
808
854
  * A class which is instantiated and passed to every component as its this
809
855
  * value. Contexts form a tree just like elements and all components in the
@@ -817,8 +863,10 @@ const $ContextImpl = Symbol.for("crank.ContextImpl");
817
863
  * schedule and cleanup callbacks.
818
864
  */
819
865
  class Context {
866
+ // TODO: If we could make the constructor function take a nicer value, it
867
+ // would be useful for testing purposes.
820
868
  constructor(impl) {
821
- this[$ContextImpl] = impl;
869
+ this[_ContextImpl] = impl;
822
870
  }
823
871
  /**
824
872
  * The current props of the associated element.
@@ -828,7 +876,7 @@ class Context {
828
876
  * plugins or utilities which wrap contexts.
829
877
  */
830
878
  get props() {
831
- return this[$ContextImpl].ret.el.props;
879
+ return this[_ContextImpl].ret.el.props;
832
880
  }
833
881
  // TODO: Should we rename this???
834
882
  /**
@@ -839,45 +887,64 @@ class Context {
839
887
  * mainly for plugins or utilities which wrap contexts.
840
888
  */
841
889
  get value() {
842
- return this[$ContextImpl].renderer.read(getValue(this[$ContextImpl].ret));
890
+ return this[_ContextImpl].renderer.read(getValue(this[_ContextImpl].ret));
843
891
  }
844
892
  *[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");
893
+ const ctx = this[_ContextImpl];
894
+ try {
895
+ ctx.f |= IsInForOfLoop;
896
+ while (!(ctx.f & IsUnmounted)) {
897
+ if (ctx.f & NeedsToYield) {
898
+ throw new Error("Context iterated twice without a yield");
899
+ }
900
+ else {
901
+ ctx.f |= NeedsToYield;
902
+ }
903
+ yield ctx.ret.el.props;
852
904
  }
853
- impl.f |= IsIterating;
854
- yield impl.ret.el.props;
905
+ }
906
+ finally {
907
+ ctx.f &= ~IsInForOfLoop;
855
908
  }
856
909
  }
857
910
  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;
911
+ const ctx = this[_ContextImpl];
912
+ if (ctx.f & IsSyncGen) {
913
+ throw new Error("Use for...of in sync generator components");
914
+ }
915
+ try {
916
+ ctx.f |= IsInForAwaitOfLoop;
917
+ while (!(ctx.f & IsUnmounted)) {
918
+ if (ctx.f & NeedsToYield) {
919
+ throw new Error("Context iterated twice without a yield");
920
+ }
921
+ else {
922
+ ctx.f |= NeedsToYield;
923
+ }
924
+ if (ctx.f & PropsAvailable) {
925
+ ctx.f &= ~PropsAvailable;
926
+ yield ctx.ret.el.props;
877
927
  }
928
+ else {
929
+ const props = await new Promise((resolve) => (ctx.onProps = resolve));
930
+ if (ctx.f & IsUnmounted) {
931
+ break;
932
+ }
933
+ yield props;
934
+ }
935
+ if (ctx.onPropsRequested) {
936
+ ctx.onPropsRequested();
937
+ ctx.onPropsRequested = undefined;
938
+ }
939
+ }
940
+ }
941
+ finally {
942
+ ctx.f &= ~IsInForAwaitOfLoop;
943
+ if (ctx.onPropsRequested) {
944
+ ctx.onPropsRequested();
945
+ ctx.onPropsRequested = undefined;
878
946
  }
879
- yield impl.ret.el.props;
880
- } while (!(impl.f & IsDone));
947
+ }
881
948
  }
882
949
  /**
883
950
  * Re-executes a component.
@@ -892,32 +959,31 @@ class Context {
892
959
  * async iterator to suspend.
893
960
  */
894
961
  refresh() {
895
- const impl = this[$ContextImpl];
896
- if (impl.f & IsUnmounted) {
962
+ const ctx = this[_ContextImpl];
963
+ if (ctx.f & IsUnmounted) {
897
964
  console.error("Component is unmounted");
898
- return impl.renderer.read(undefined);
965
+ return ctx.renderer.read(undefined);
899
966
  }
900
- else if (impl.f & IsExecuting) {
967
+ else if (ctx.f & IsSyncExecuting) {
901
968
  console.error("Component is already executing");
902
969
  return this.value;
903
970
  }
904
- resumeCtxIterator(impl);
905
- const value = runComponent(impl);
971
+ const value = enqueueComponentRun(ctx);
906
972
  if (isPromiseLike(value)) {
907
- return value.then((value) => impl.renderer.read(value));
973
+ return value.then((value) => ctx.renderer.read(value));
908
974
  }
909
- return impl.renderer.read(value);
975
+ return ctx.renderer.read(value);
910
976
  }
911
977
  /**
912
978
  * Registers a callback which fires when the component commits. Will only
913
979
  * fire once per callback and update.
914
980
  */
915
981
  schedule(callback) {
916
- const impl = this[$ContextImpl];
917
- let callbacks = scheduleMap.get(impl);
982
+ const ctx = this[_ContextImpl];
983
+ let callbacks = scheduleMap.get(ctx);
918
984
  if (!callbacks) {
919
985
  callbacks = new Set();
920
- scheduleMap.set(impl, callbacks);
986
+ scheduleMap.set(ctx, callbacks);
921
987
  }
922
988
  callbacks.add(callback);
923
989
  }
@@ -926,19 +992,19 @@ class Context {
926
992
  * rendered into the root. Will only fire once per callback and render.
927
993
  */
928
994
  flush(callback) {
929
- const impl = this[$ContextImpl];
930
- if (typeof impl.root !== "object" || impl.root === null) {
995
+ const ctx = this[_ContextImpl];
996
+ if (typeof ctx.root !== "object" || ctx.root === null) {
931
997
  return;
932
998
  }
933
- let flushMap = flushMaps.get(impl.root);
999
+ let flushMap = flushMaps.get(ctx.root);
934
1000
  if (!flushMap) {
935
1001
  flushMap = new Map();
936
- flushMaps.set(impl.root, flushMap);
1002
+ flushMaps.set(ctx.root, flushMap);
937
1003
  }
938
- let callbacks = flushMap.get(impl);
1004
+ let callbacks = flushMap.get(ctx);
939
1005
  if (!callbacks) {
940
1006
  callbacks = new Set();
941
- flushMap.set(impl, callbacks);
1007
+ flushMap.set(ctx, callbacks);
942
1008
  }
943
1009
  callbacks.add(callback);
944
1010
  }
@@ -947,45 +1013,45 @@ class Context {
947
1013
  * fire once per callback.
948
1014
  */
949
1015
  cleanup(callback) {
950
- const impl = this[$ContextImpl];
951
- let callbacks = cleanupMap.get(impl);
1016
+ const ctx = this[_ContextImpl];
1017
+ let callbacks = cleanupMap.get(ctx);
952
1018
  if (!callbacks) {
953
1019
  callbacks = new Set();
954
- cleanupMap.set(impl, callbacks);
1020
+ cleanupMap.set(ctx, callbacks);
955
1021
  }
956
1022
  callbacks.add(callback);
957
1023
  }
958
1024
  consume(key) {
959
- for (let parent = this[$ContextImpl].parent; parent !== undefined; parent = parent.parent) {
960
- const provisions = provisionMaps.get(parent);
1025
+ for (let ctx = this[_ContextImpl].parent; ctx !== undefined; ctx = ctx.parent) {
1026
+ const provisions = provisionMaps.get(ctx);
961
1027
  if (provisions && provisions.has(key)) {
962
1028
  return provisions.get(key);
963
1029
  }
964
1030
  }
965
1031
  }
966
1032
  provide(key, value) {
967
- const impl = this[$ContextImpl];
968
- let provisions = provisionMaps.get(impl);
1033
+ const ctx = this[_ContextImpl];
1034
+ let provisions = provisionMaps.get(ctx);
969
1035
  if (!provisions) {
970
1036
  provisions = new Map();
971
- provisionMaps.set(impl, provisions);
1037
+ provisionMaps.set(ctx, provisions);
972
1038
  }
973
1039
  provisions.set(key, value);
974
1040
  }
975
1041
  addEventListener(type, listener, options) {
976
- const impl = this[$ContextImpl];
1042
+ const ctx = this[_ContextImpl];
977
1043
  let listeners;
978
1044
  if (!isListenerOrListenerObject(listener)) {
979
1045
  return;
980
1046
  }
981
1047
  else {
982
- const listeners1 = listenersMap.get(impl);
1048
+ const listeners1 = listenersMap.get(ctx);
983
1049
  if (listeners1) {
984
1050
  listeners = listeners1;
985
1051
  }
986
1052
  else {
987
1053
  listeners = [];
988
- listenersMap.set(impl, listeners);
1054
+ listenersMap.set(ctx, listeners);
989
1055
  }
990
1056
  }
991
1057
  options = normalizeListenerOptions(options);
@@ -996,7 +1062,7 @@ class Context {
996
1062
  else {
997
1063
  callback = listener;
998
1064
  }
999
- const record = { type, callback, listener, options };
1065
+ const record = { type, listener, callback, options };
1000
1066
  if (options.once) {
1001
1067
  record.callback = function () {
1002
1068
  const i = listeners.indexOf(record);
@@ -1013,15 +1079,15 @@ class Context {
1013
1079
  }
1014
1080
  listeners.push(record);
1015
1081
  // TODO: is it possible to separate out the EventTarget delegation logic
1016
- for (const value of getChildValues(impl.ret)) {
1082
+ for (const value of getChildValues(ctx.ret)) {
1017
1083
  if (isEventTarget(value)) {
1018
1084
  value.addEventListener(record.type, record.callback, record.options);
1019
1085
  }
1020
1086
  }
1021
1087
  }
1022
1088
  removeEventListener(type, listener, options) {
1023
- const impl = this[$ContextImpl];
1024
- const listeners = listenersMap.get(impl);
1089
+ const ctx = this[_ContextImpl];
1090
+ const listeners = listenersMap.get(ctx);
1025
1091
  if (listeners == null || !isListenerOrListenerObject(listener)) {
1026
1092
  return;
1027
1093
  }
@@ -1035,16 +1101,16 @@ class Context {
1035
1101
  const record = listeners[i];
1036
1102
  listeners.splice(i, 1);
1037
1103
  // TODO: is it possible to separate out the EventTarget delegation logic
1038
- for (const value of getChildValues(impl.ret)) {
1104
+ for (const value of getChildValues(ctx.ret)) {
1039
1105
  if (isEventTarget(value)) {
1040
1106
  value.removeEventListener(record.type, record.callback, record.options);
1041
1107
  }
1042
1108
  }
1043
1109
  }
1044
1110
  dispatchEvent(ev) {
1045
- const impl = this[$ContextImpl];
1111
+ const ctx = this[_ContextImpl];
1046
1112
  const path = [];
1047
- for (let parent = impl.parent; parent !== undefined; parent = parent.parent) {
1113
+ for (let parent = ctx.parent; parent !== undefined; parent = parent.parent) {
1048
1114
  path.push(parent);
1049
1115
  }
1050
1116
  // We patch the stopImmediatePropagation method because ev.cancelBubble
@@ -1056,7 +1122,7 @@ class Context {
1056
1122
  immediateCancelBubble = true;
1057
1123
  return stopImmediatePropagation.call(ev);
1058
1124
  });
1059
- setEventProperty(ev, "target", impl.ctx);
1125
+ setEventProperty(ev, "target", ctx.owner);
1060
1126
  // The only possible errors in this block are errors thrown by callbacks,
1061
1127
  // and dispatchEvent will only log these errors rather than throwing
1062
1128
  // them. Therefore, we place all code in a try block, log errors in the
@@ -1065,18 +1131,21 @@ class Context {
1065
1131
  // Each early return within the try block returns true because while the
1066
1132
  // return value is overridden in the finally block, TypeScript
1067
1133
  // (justifiably) does not recognize the unsafe return statement.
1068
- //
1069
- // TODO: Run all callbacks even if one of them errors
1070
1134
  try {
1071
1135
  setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
1072
1136
  for (let i = path.length - 1; i >= 0; i--) {
1073
1137
  const target = path[i];
1074
1138
  const listeners = listenersMap.get(target);
1075
1139
  if (listeners) {
1076
- setEventProperty(ev, "currentTarget", target.ctx);
1140
+ setEventProperty(ev, "currentTarget", target.owner);
1077
1141
  for (const record of listeners) {
1078
1142
  if (record.type === ev.type && record.options.capture) {
1079
- record.callback.call(target.ctx, ev);
1143
+ try {
1144
+ record.callback.call(target.owner, ev);
1145
+ }
1146
+ catch (err) {
1147
+ console.error(err);
1148
+ }
1080
1149
  if (immediateCancelBubble) {
1081
1150
  return true;
1082
1151
  }
@@ -1088,13 +1157,25 @@ class Context {
1088
1157
  }
1089
1158
  }
1090
1159
  {
1091
- const listeners = listenersMap.get(impl);
1160
+ setEventProperty(ev, "eventPhase", AT_TARGET);
1161
+ setEventProperty(ev, "currentTarget", ctx.owner);
1162
+ const propCallback = ctx.ret.el.props["on" + ev.type];
1163
+ if (propCallback != null) {
1164
+ propCallback(ev);
1165
+ if (immediateCancelBubble || ev.cancelBubble) {
1166
+ return true;
1167
+ }
1168
+ }
1169
+ const listeners = listenersMap.get(ctx);
1092
1170
  if (listeners) {
1093
- setEventProperty(ev, "eventPhase", AT_TARGET);
1094
- setEventProperty(ev, "currentTarget", impl.ctx);
1095
1171
  for (const record of listeners) {
1096
1172
  if (record.type === ev.type) {
1097
- record.callback.call(impl.ctx, ev);
1173
+ try {
1174
+ record.callback.call(ctx.owner, ev);
1175
+ }
1176
+ catch (err) {
1177
+ console.error(err);
1178
+ }
1098
1179
  if (immediateCancelBubble) {
1099
1180
  return true;
1100
1181
  }
@@ -1111,10 +1192,15 @@ class Context {
1111
1192
  const target = path[i];
1112
1193
  const listeners = listenersMap.get(target);
1113
1194
  if (listeners) {
1114
- setEventProperty(ev, "currentTarget", target.ctx);
1195
+ setEventProperty(ev, "currentTarget", target.owner);
1115
1196
  for (const record of listeners) {
1116
1197
  if (record.type === ev.type && !record.options.capture) {
1117
- record.callback.call(target.ctx, ev);
1198
+ try {
1199
+ record.callback.call(target.owner, ev);
1200
+ }
1201
+ catch (err) {
1202
+ console.error(err);
1203
+ }
1118
1204
  if (immediateCancelBubble) {
1119
1205
  return true;
1120
1206
  }
@@ -1127,10 +1213,6 @@ class Context {
1127
1213
  }
1128
1214
  }
1129
1215
  }
1130
- catch (err) {
1131
- // TODO: Use setTimeout to rethrow the error.
1132
- console.error(err);
1133
- }
1134
1216
  finally {
1135
1217
  setEventProperty(ev, "eventPhase", NONE);
1136
1218
  setEventProperty(ev, "currentTarget", null);
@@ -1148,42 +1230,47 @@ function ctxContains(parent, child) {
1148
1230
  }
1149
1231
  return false;
1150
1232
  }
1151
- function updateComponent(renderer, root, host, parent, scope, ret, oldProps) {
1233
+ function updateComponent(renderer, root, host, parent, scope, ret, oldProps, hydrationData) {
1152
1234
  let ctx;
1153
1235
  if (oldProps) {
1154
1236
  ctx = ret.ctx;
1155
- if (ctx.f & IsExecuting) {
1237
+ if (ctx.f & IsSyncExecuting) {
1156
1238
  console.error("Component is already executing");
1157
- return ret.cached;
1239
+ return ret.cachedChildValues;
1158
1240
  }
1159
1241
  }
1160
1242
  else {
1161
1243
  ctx = ret.ctx = new ContextImpl(renderer, root, host, parent, scope, ret);
1162
1244
  }
1163
1245
  ctx.f |= IsUpdating;
1164
- resumeCtxIterator(ctx);
1165
- return runComponent(ctx);
1246
+ return enqueueComponentRun(ctx, hydrationData);
1166
1247
  }
1167
- function updateComponentChildren(ctx, children) {
1168
- if (ctx.f & IsUnmounted || ctx.f & IsErrored) {
1248
+ function updateComponentChildren(ctx, children, hydrationData) {
1249
+ if (ctx.f & IsUnmounted) {
1250
+ return;
1251
+ }
1252
+ else if (ctx.f & IsErrored) {
1253
+ // This branch is necessary for some race conditions where this function is
1254
+ // called after iterator.throw() in async generator components.
1169
1255
  return;
1170
1256
  }
1171
1257
  else if (children === undefined) {
1172
1258
  console.error("A component has returned or yielded undefined. If this was intentional, return or yield null instead.");
1173
1259
  }
1174
1260
  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
1261
  try {
1179
- childValues = diffChildren(ctx.renderer, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children));
1262
+ // TODO: WAT
1263
+ // We set the isExecuting flag in case a child component dispatches an event
1264
+ // which bubbles to this component and causes a synchronous refresh().
1265
+ ctx.f |= IsSyncExecuting;
1266
+ childValues = diffChildren(ctx.renderer, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children), hydrationData);
1180
1267
  }
1181
1268
  finally {
1182
- ctx.f &= ~IsExecuting;
1269
+ ctx.f &= ~IsSyncExecuting;
1183
1270
  }
1184
1271
  if (isPromiseLike(childValues)) {
1185
- ctx.ret.inflight = childValues.then((childValues) => commitComponent(ctx, childValues));
1186
- return ctx.ret.inflight;
1272
+ ctx.ret.inflightValue = childValues.then((childValues) => commitComponent(ctx, childValues));
1273
+ return ctx.ret.inflightValue;
1187
1274
  }
1188
1275
  return commitComponent(ctx, childValues);
1189
1276
  }
@@ -1203,8 +1290,8 @@ function commitComponent(ctx, values) {
1203
1290
  }
1204
1291
  }
1205
1292
  }
1206
- const oldValues = wrap(ctx.ret.cached);
1207
- let value = (ctx.ret.cached = unwrap(values));
1293
+ const oldValues = wrap(ctx.ret.cachedChildValues);
1294
+ let value = (ctx.ret.cachedChildValues = unwrap(values));
1208
1295
  if (ctx.f & IsScheduling) {
1209
1296
  ctx.f |= IsSchedulingRefresh;
1210
1297
  }
@@ -1212,7 +1299,7 @@ function commitComponent(ctx, values) {
1212
1299
  // If we’re not updating the component, which happens when components are
1213
1300
  // refreshed, or when async generator components iterate, we have to do a
1214
1301
  // little bit housekeeping when a component’s child values have changed.
1215
- if (!valuesEqual(oldValues, values)) {
1302
+ if (!arrayEqual(oldValues, values)) {
1216
1303
  const records = getListenerRecords(ctx.parent, ctx.host);
1217
1304
  if (records.length) {
1218
1305
  for (let i = 0; i < values.length; i++) {
@@ -1227,7 +1314,7 @@ function commitComponent(ctx, values) {
1227
1314
  }
1228
1315
  // rearranging the nearest ancestor host element
1229
1316
  const host = ctx.host;
1230
- const oldHostValues = wrap(host.cached);
1317
+ const oldHostValues = wrap(host.cachedChildValues);
1231
1318
  invalidate(ctx, host);
1232
1319
  const hostValues = getChildValues(host);
1233
1320
  ctx.renderer.arrange(host.el.tag, host.value, host.el.props, hostValues,
@@ -1256,50 +1343,79 @@ function commitComponent(ctx, values) {
1256
1343
  }
1257
1344
  function invalidate(ctx, host) {
1258
1345
  for (let parent = ctx.parent; parent !== undefined && parent.host === host; parent = parent.parent) {
1259
- parent.ret.cached = undefined;
1346
+ parent.ret.cachedChildValues = undefined;
1260
1347
  }
1261
- host.cached = undefined;
1348
+ host.cachedChildValues = undefined;
1262
1349
  }
1263
- function valuesEqual(values1, values2) {
1264
- if (values1.length !== values2.length) {
1350
+ function arrayEqual(arr1, arr2) {
1351
+ if (arr1.length !== arr2.length) {
1265
1352
  return false;
1266
1353
  }
1267
- for (let i = 0; i < values1.length; i++) {
1268
- const value1 = values1[i];
1269
- const value2 = values2[i];
1354
+ for (let i = 0; i < arr1.length; i++) {
1355
+ const value1 = arr1[i];
1356
+ const value2 = arr2[i];
1270
1357
  if (value1 !== value2) {
1271
1358
  return false;
1272
1359
  }
1273
1360
  }
1274
1361
  return true;
1275
1362
  }
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) {
1363
+ /** Enqueues and executes the component associated with the context. */
1364
+ function enqueueComponentRun(ctx, hydrationData) {
1365
+ if (ctx.f & IsAsyncGen && !(ctx.f & IsInForOfLoop)) {
1366
+ if (hydrationData !== undefined) {
1367
+ throw new Error("Hydration error");
1368
+ }
1369
+ // This branch will run for non-initial renders of async generator
1370
+ // components when they are not in for...of loops. When in a for...of loop,
1371
+ // async generator components will behave normally.
1372
+ //
1373
+ // Async gen componennts can be in one of three states:
1374
+ //
1375
+ // 1. propsAvailable flag is true: "available"
1376
+ //
1377
+ // The component is suspended somewhere in the loop. When the component
1378
+ // reaches the bottom of the loop, it will run again with the next props.
1379
+ //
1380
+ // 2. onAvailable callback is defined: "suspended"
1381
+ //
1382
+ // The component has suspended at the bottom of the loop and is waiting
1383
+ // for new props.
1384
+ //
1385
+ // 3. neither 1 or 2: "Running"
1386
+ //
1387
+ // The component is suspended somewhere in the loop. When the component
1388
+ // reaches the bottom of the loop, it will suspend.
1389
+ //
1390
+ // Components will never be both available and suspended at
1391
+ // the same time.
1392
+ //
1393
+ // If the component is at the loop bottom, this means that the next value
1394
+ // produced by the component will have the most up to date props, so we can
1395
+ // simply return the current inflight value. Otherwise, we have to wait for
1396
+ // the bottom of the loop to be reached before returning the inflight
1397
+ // value.
1398
+ const isAtLoopbottom = ctx.f & IsInForAwaitOfLoop && !ctx.onProps;
1399
+ resumePropsIterator(ctx);
1400
+ if (isAtLoopbottom) {
1401
+ if (ctx.inflightBlock == null) {
1402
+ ctx.inflightBlock = new Promise((resolve) => (ctx.onPropsRequested = resolve));
1403
+ }
1404
+ return ctx.inflightBlock.then(() => {
1405
+ ctx.inflightBlock = undefined;
1406
+ return ctx.inflightValue;
1407
+ });
1408
+ }
1409
+ return ctx.inflightValue;
1410
+ }
1411
+ else if (!ctx.inflightBlock) {
1294
1412
  try {
1295
- const [block, value] = stepComponent(ctx);
1413
+ const [block, value] = runComponent(ctx, hydrationData);
1296
1414
  if (block) {
1297
1415
  ctx.inflightBlock = block
1298
- .catch((err) => {
1299
- if (!(ctx.f & IsUpdating)) {
1300
- return propagateError(ctx.parent, err);
1301
- }
1302
- })
1416
+ // TODO: there is some fuckery going on here related to async
1417
+ // generator components resuming when they’re meant to be returned.
1418
+ .then((v) => v)
1303
1419
  .finally(() => advanceComponent(ctx));
1304
1420
  // stepComponent will only return a block if the value is asynchronous
1305
1421
  ctx.inflightValue = value;
@@ -1313,38 +1429,46 @@ function runComponent(ctx) {
1313
1429
  throw err;
1314
1430
  }
1315
1431
  }
1316
- else if (ctx.f & IsAsyncGen) {
1317
- return ctx.inflightValue;
1318
- }
1319
1432
  else if (!ctx.enqueuedBlock) {
1320
- let resolve;
1321
- ctx.enqueuedBlock = ctx.inflightBlock
1322
- .then(() => {
1433
+ if (hydrationData !== undefined) {
1434
+ throw new Error("Hydration error");
1435
+ }
1436
+ // We need to assign enqueuedBlock and enqueuedValue synchronously, hence
1437
+ // the Promise constructor call here.
1438
+ let resolveEnqueuedBlock;
1439
+ ctx.enqueuedBlock = new Promise((resolve) => (resolveEnqueuedBlock = resolve));
1440
+ ctx.enqueuedValue = ctx.inflightBlock.then(() => {
1323
1441
  try {
1324
- const [block, value] = stepComponent(ctx);
1325
- resolve(value);
1442
+ const [block, value] = runComponent(ctx);
1326
1443
  if (block) {
1327
- return block.catch((err) => {
1328
- if (!(ctx.f & IsUpdating)) {
1329
- return propagateError(ctx.parent, err);
1330
- }
1331
- });
1444
+ resolveEnqueuedBlock(block.finally(() => advanceComponent(ctx)));
1332
1445
  }
1446
+ return value;
1333
1447
  }
1334
1448
  catch (err) {
1335
1449
  if (!(ctx.f & IsUpdating)) {
1336
1450
  return propagateError(ctx.parent, err);
1337
1451
  }
1452
+ throw err;
1338
1453
  }
1339
- })
1340
- .finally(() => advanceComponent(ctx));
1341
- ctx.enqueuedValue = new Promise((resolve1) => (resolve = resolve1));
1454
+ });
1342
1455
  }
1343
1456
  return ctx.enqueuedValue;
1344
1457
  }
1458
+ /** Called when the inflight block promise settles. */
1459
+ function advanceComponent(ctx) {
1460
+ if (ctx.f & IsAsyncGen && !(ctx.f & IsInForOfLoop)) {
1461
+ return;
1462
+ }
1463
+ ctx.inflightBlock = ctx.enqueuedBlock;
1464
+ ctx.inflightValue = ctx.enqueuedValue;
1465
+ ctx.enqueuedBlock = undefined;
1466
+ ctx.enqueuedValue = undefined;
1467
+ }
1345
1468
  /**
1346
1469
  * This function is responsible for executing the component and handling all
1347
- * the different component types.
1470
+ * the different component types. We cannot identify whether a component is a
1471
+ * generator or async without calling it and inspecting the return value.
1348
1472
  *
1349
1473
  * @returns {[block, value]} A tuple where
1350
1474
  * block - A possible promise which represents the duration during which the
@@ -1359,25 +1483,23 @@ function runComponent(ctx) {
1359
1483
  * - Sync generator components block while any children are executing, because
1360
1484
  * they are expected to only resume when they’ve actually rendered.
1361
1485
  */
1362
- function stepComponent(ctx) {
1486
+ function runComponent(ctx, hydrationData) {
1363
1487
  const ret = ctx.ret;
1364
- if (ctx.f & IsDone) {
1365
- return [undefined, getValue(ret)];
1366
- }
1367
1488
  const initial = !ctx.iterator;
1368
1489
  if (initial) {
1369
- ctx.f |= IsExecuting;
1490
+ resumePropsIterator(ctx);
1491
+ ctx.f |= IsSyncExecuting;
1370
1492
  clearEventListeners(ctx);
1371
1493
  let result;
1372
1494
  try {
1373
- result = ret.el.tag.call(ctx.ctx, ret.el.props);
1495
+ result = ret.el.tag.call(ctx.owner, ret.el.props);
1374
1496
  }
1375
1497
  catch (err) {
1376
1498
  ctx.f |= IsErrored;
1377
1499
  throw err;
1378
1500
  }
1379
1501
  finally {
1380
- ctx.f &= ~IsExecuting;
1502
+ ctx.f &= ~IsSyncExecuting;
1381
1503
  }
1382
1504
  if (isIteratorLike(result)) {
1383
1505
  ctx.iterator = result;
@@ -1385,124 +1507,241 @@ function stepComponent(ctx) {
1385
1507
  else if (isPromiseLike(result)) {
1386
1508
  // async function component
1387
1509
  const result1 = result instanceof Promise ? result : Promise.resolve(result);
1388
- const value = result1.then((result) => updateComponentChildren(ctx, result), (err) => {
1510
+ const value = result1.then((result) => updateComponentChildren(ctx, result, hydrationData), (err) => {
1389
1511
  ctx.f |= IsErrored;
1390
1512
  throw err;
1391
1513
  });
1392
- return [result1, value];
1514
+ return [result1.catch(NOOP), value];
1393
1515
  }
1394
1516
  else {
1395
1517
  // sync function component
1396
- return [undefined, updateComponentChildren(ctx, result)];
1518
+ return [
1519
+ undefined,
1520
+ updateComponentChildren(ctx, result, hydrationData),
1521
+ ];
1397
1522
  }
1398
1523
  }
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));
1524
+ else if (hydrationData !== undefined) {
1525
+ throw new Error("Hydration error");
1413
1526
  }
1414
1527
  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;
1528
+ if (initial) {
1529
+ try {
1530
+ ctx.f |= IsSyncExecuting;
1531
+ iteration = ctx.iterator.next();
1532
+ }
1533
+ catch (err) {
1534
+ ctx.f |= IsErrored;
1535
+ throw err;
1536
+ }
1537
+ finally {
1538
+ ctx.f &= ~IsSyncExecuting;
1539
+ }
1540
+ if (isPromiseLike(iteration)) {
1541
+ ctx.f |= IsAsyncGen;
1542
+ }
1543
+ else {
1544
+ ctx.f |= IsSyncGen;
1545
+ }
1422
1546
  }
1423
- finally {
1424
- ctx.f &= ~IsExecuting;
1547
+ if (ctx.f & IsSyncGen) {
1548
+ ctx.f &= ~NeedsToYield;
1549
+ // sync generator component
1550
+ if (!initial) {
1551
+ try {
1552
+ ctx.f |= IsSyncExecuting;
1553
+ iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
1554
+ }
1555
+ catch (err) {
1556
+ ctx.f |= IsErrored;
1557
+ throw err;
1558
+ }
1559
+ finally {
1560
+ ctx.f &= ~IsSyncExecuting;
1561
+ }
1562
+ }
1563
+ if (isPromiseLike(iteration)) {
1564
+ throw new Error("Mixed generator component");
1565
+ }
1566
+ if (iteration.done) {
1567
+ ctx.f &= ~IsSyncGen;
1568
+ ctx.iterator = undefined;
1569
+ }
1570
+ let value;
1571
+ try {
1572
+ value = updateComponentChildren(ctx,
1573
+ // Children can be void so we eliminate that here
1574
+ iteration.value, hydrationData);
1575
+ if (isPromiseLike(value)) {
1576
+ value = value.catch((err) => handleChildError(ctx, err));
1577
+ }
1578
+ }
1579
+ catch (err) {
1580
+ value = handleChildError(ctx, err);
1581
+ }
1582
+ const block = isPromiseLike(value) ? value.catch(NOOP) : undefined;
1583
+ return [block, value];
1425
1584
  }
1426
- if (isPromiseLike(iteration)) {
1427
- // async generator component
1428
- if (initial) {
1429
- ctx.f |= IsAsyncGen;
1585
+ else if (ctx.f & IsInForOfLoop) {
1586
+ // TODO: does this need to be done async?
1587
+ ctx.f &= ~NeedsToYield;
1588
+ // we are in a for...of loop for async generator
1589
+ if (!initial) {
1590
+ try {
1591
+ ctx.f |= IsSyncExecuting;
1592
+ iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
1593
+ }
1594
+ catch (err) {
1595
+ ctx.f |= IsErrored;
1596
+ throw err;
1597
+ }
1598
+ finally {
1599
+ ctx.f &= ~IsSyncExecuting;
1600
+ }
1601
+ }
1602
+ if (!isPromiseLike(iteration)) {
1603
+ throw new Error("Mixed generator component");
1430
1604
  }
1605
+ const block = iteration.catch(NOOP);
1431
1606
  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;
1607
+ let value;
1608
+ if (!(ctx.f & IsInForOfLoop)) {
1609
+ runAsyncGenComponent(ctx, Promise.resolve(iteration), hydrationData);
1438
1610
  }
1439
1611
  try {
1440
- const value = updateComponentChildren(ctx, iteration.value);
1612
+ value = updateComponentChildren(ctx,
1613
+ // Children can be void so we eliminate that here
1614
+ iteration.value, hydrationData);
1441
1615
  if (isPromiseLike(value)) {
1442
- return value.catch((err) => handleChildError(ctx, err));
1616
+ value = value.catch((err) => handleChildError(ctx, err));
1443
1617
  }
1444
- return value;
1445
1618
  }
1446
1619
  catch (err) {
1447
- return handleChildError(ctx, err);
1620
+ value = handleChildError(ctx, err);
1448
1621
  }
1622
+ return value;
1449
1623
  }, (err) => {
1450
- ctx.f |= IsDone | IsErrored;
1624
+ ctx.f |= IsErrored;
1451
1625
  throw err;
1452
1626
  });
1453
- return [iteration, value];
1454
- }
1455
- // sync generator component
1456
- if (initial) {
1457
- ctx.f |= IsSyncGen;
1627
+ return [block, value];
1458
1628
  }
1459
- ctx.f &= ~IsIterating;
1460
- if (iteration.done) {
1461
- ctx.f |= IsDone;
1629
+ else {
1630
+ runAsyncGenComponent(ctx, iteration, hydrationData);
1631
+ // async generator component
1632
+ return [ctx.inflightBlock, ctx.inflightValue];
1462
1633
  }
1463
- let value;
1634
+ }
1635
+ async function runAsyncGenComponent(ctx, iterationP, hydrationData) {
1636
+ let done = false;
1464
1637
  try {
1465
- value = updateComponentChildren(ctx, iteration.value);
1466
- if (isPromiseLike(value)) {
1467
- value = value.catch((err) => handleChildError(ctx, err));
1638
+ while (!done) {
1639
+ if (ctx.f & IsInForOfLoop) {
1640
+ break;
1641
+ }
1642
+ // inflightValue must be set synchronously.
1643
+ let onValue;
1644
+ ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
1645
+ if (ctx.f & IsUpdating) {
1646
+ // We should not swallow unhandled promise rejections if the component is
1647
+ // updating independently.
1648
+ // TODO: Does this handle this.refresh() calls?
1649
+ ctx.inflightValue.catch(NOOP);
1650
+ }
1651
+ let iteration;
1652
+ try {
1653
+ iteration = await iterationP;
1654
+ }
1655
+ catch (err) {
1656
+ done = true;
1657
+ ctx.f |= IsErrored;
1658
+ onValue(Promise.reject(err));
1659
+ break;
1660
+ }
1661
+ finally {
1662
+ ctx.f &= ~NeedsToYield;
1663
+ if (!(ctx.f & IsInForAwaitOfLoop)) {
1664
+ ctx.f &= ~PropsAvailable;
1665
+ }
1666
+ }
1667
+ done = !!iteration.done;
1668
+ let value;
1669
+ try {
1670
+ value = updateComponentChildren(ctx, iteration.value, hydrationData);
1671
+ hydrationData = undefined;
1672
+ if (isPromiseLike(value)) {
1673
+ value = value.catch((err) => handleChildError(ctx, err));
1674
+ }
1675
+ }
1676
+ catch (err) {
1677
+ // Do we need to catch potential errors here in the case of unhandled
1678
+ // promise rejections?
1679
+ value = handleChildError(ctx, err);
1680
+ }
1681
+ finally {
1682
+ onValue(value);
1683
+ }
1684
+ // TODO: this can be done more elegantly
1685
+ let oldValue;
1686
+ if (ctx.ret.inflightValue) {
1687
+ // The value passed back into the generator as the argument to the next
1688
+ // method is a promise if an async generator component has async
1689
+ // children. Sync generator components only resume when their children
1690
+ // have fulfilled so the element’s inflight child values will never be
1691
+ // defined.
1692
+ oldValue = ctx.ret.inflightValue.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
1693
+ }
1694
+ else {
1695
+ oldValue = ctx.renderer.read(getValue(ctx.ret));
1696
+ }
1697
+ if (ctx.f & IsUnmounted) {
1698
+ if (ctx.f & IsInForAwaitOfLoop) {
1699
+ try {
1700
+ ctx.f |= IsSyncExecuting;
1701
+ iterationP = ctx.iterator.next(oldValue);
1702
+ }
1703
+ finally {
1704
+ ctx.f &= ~IsSyncExecuting;
1705
+ }
1706
+ }
1707
+ else {
1708
+ returnComponent(ctx);
1709
+ break;
1710
+ }
1711
+ }
1712
+ else if (!done && !(ctx.f & IsInForOfLoop)) {
1713
+ try {
1714
+ ctx.f |= IsSyncExecuting;
1715
+ iterationP = ctx.iterator.next(oldValue);
1716
+ }
1717
+ finally {
1718
+ ctx.f &= ~IsSyncExecuting;
1719
+ }
1720
+ }
1468
1721
  }
1469
1722
  }
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);
1723
+ finally {
1724
+ if (done) {
1725
+ ctx.f &= ~IsAsyncGen;
1726
+ ctx.iterator = undefined;
1727
+ }
1488
1728
  }
1489
1729
  }
1490
1730
  /**
1491
- * Called to make props available to the props async iterator for async
1492
- * generator components.
1731
+ * Called to resume the props async iterator for async generator components.
1493
1732
  */
1494
- function resumeCtxIterator(ctx) {
1495
- if (ctx.onAvailable) {
1496
- ctx.onAvailable();
1497
- ctx.onAvailable = undefined;
1733
+ function resumePropsIterator(ctx) {
1734
+ if (ctx.onProps) {
1735
+ ctx.onProps(ctx.ret.el.props);
1736
+ ctx.onProps = undefined;
1737
+ ctx.f &= ~PropsAvailable;
1498
1738
  }
1499
1739
  else {
1500
- ctx.f |= IsAvailable;
1740
+ ctx.f |= PropsAvailable;
1501
1741
  }
1502
1742
  }
1503
1743
  // TODO: async unmounting
1504
1744
  function unmountComponent(ctx) {
1505
- ctx.f |= IsUnmounted;
1506
1745
  clearEventListeners(ctx);
1507
1746
  const callbacks = cleanupMap.get(ctx);
1508
1747
  if (callbacks) {
@@ -1512,23 +1751,71 @@ function unmountComponent(ctx) {
1512
1751
  callback(value);
1513
1752
  }
1514
1753
  }
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));
1754
+ ctx.f |= IsUnmounted;
1755
+ if (ctx.iterator) {
1756
+ if (ctx.f & IsSyncGen) {
1757
+ let value;
1758
+ if (ctx.f & IsInForOfLoop) {
1759
+ value = enqueueComponentRun(ctx);
1760
+ }
1761
+ if (isPromiseLike(value)) {
1762
+ value.then(() => {
1763
+ if (ctx.f & IsInForOfLoop) {
1764
+ unmountComponent(ctx);
1765
+ }
1766
+ else {
1767
+ returnComponent(ctx);
1768
+ }
1769
+ }, (err) => {
1770
+ propagateError(ctx.parent, err);
1771
+ });
1772
+ }
1773
+ else {
1774
+ if (ctx.f & IsInForOfLoop) {
1775
+ unmountComponent(ctx);
1776
+ }
1777
+ else {
1778
+ returnComponent(ctx);
1524
1779
  }
1525
1780
  }
1526
- finally {
1527
- ctx.f &= ~IsExecuting;
1781
+ }
1782
+ else if (ctx.f & IsAsyncGen) {
1783
+ if (ctx.f & IsInForOfLoop) {
1784
+ const value = enqueueComponentRun(ctx);
1785
+ value.then(() => {
1786
+ if (ctx.f & IsInForOfLoop) {
1787
+ unmountComponent(ctx);
1788
+ }
1789
+ else {
1790
+ returnComponent(ctx);
1791
+ }
1792
+ }, (err) => {
1793
+ propagateError(ctx.parent, err);
1794
+ });
1795
+ }
1796
+ else {
1797
+ // The logic for unmounting async generator components is in the
1798
+ // runAsyncGenComponent function.
1799
+ resumePropsIterator(ctx);
1528
1800
  }
1529
1801
  }
1530
1802
  }
1531
1803
  }
1804
+ function returnComponent(ctx) {
1805
+ resumePropsIterator(ctx);
1806
+ if (ctx.iterator && typeof ctx.iterator.return === "function") {
1807
+ try {
1808
+ ctx.f |= IsSyncExecuting;
1809
+ const iteration = ctx.iterator.return();
1810
+ if (isPromiseLike(iteration)) {
1811
+ iteration.catch((err) => propagateError(ctx.parent, err));
1812
+ }
1813
+ }
1814
+ finally {
1815
+ ctx.f &= ~IsSyncExecuting;
1816
+ }
1817
+ }
1818
+ }
1532
1819
  /*** EVENT TARGET UTILITIES ***/
1533
1820
  // EVENT PHASE CONSTANTS
1534
1821
  // https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
@@ -1597,39 +1884,39 @@ function clearEventListeners(ctx) {
1597
1884
  }
1598
1885
  }
1599
1886
  /*** ERROR HANDLING UTILITIES ***/
1600
- // TODO: generator components which throw errors should be recoverable
1601
1887
  function handleChildError(ctx, err) {
1602
- if (ctx.f & IsDone ||
1603
- !ctx.iterator ||
1604
- typeof ctx.iterator.throw !== "function") {
1888
+ if (!ctx.iterator || typeof ctx.iterator.throw !== "function") {
1605
1889
  throw err;
1606
1890
  }
1607
- resumeCtxIterator(ctx);
1891
+ resumePropsIterator(ctx);
1608
1892
  let iteration;
1609
1893
  try {
1610
- ctx.f |= IsExecuting;
1894
+ ctx.f |= IsSyncExecuting;
1611
1895
  iteration = ctx.iterator.throw(err);
1612
1896
  }
1613
1897
  catch (err) {
1614
- ctx.f |= IsDone | IsErrored;
1898
+ ctx.f |= IsErrored;
1615
1899
  throw err;
1616
1900
  }
1617
1901
  finally {
1618
- ctx.f &= ~IsExecuting;
1902
+ ctx.f &= ~IsSyncExecuting;
1619
1903
  }
1620
1904
  if (isPromiseLike(iteration)) {
1621
1905
  return iteration.then((iteration) => {
1622
1906
  if (iteration.done) {
1623
- ctx.f |= IsDone;
1907
+ ctx.f &= ~IsAsyncGen;
1908
+ ctx.iterator = undefined;
1624
1909
  }
1625
1910
  return updateComponentChildren(ctx, iteration.value);
1626
1911
  }, (err) => {
1627
- ctx.f |= IsDone | IsErrored;
1912
+ ctx.f |= IsErrored;
1628
1913
  throw err;
1629
1914
  });
1630
1915
  }
1631
1916
  if (iteration.done) {
1632
- ctx.f |= IsDone;
1917
+ ctx.f &= ~IsSyncGen;
1918
+ ctx.f &= ~IsAsyncGen;
1919
+ ctx.iterator = undefined;
1633
1920
  }
1634
1921
  return updateComponentChildren(ctx, iteration.value);
1635
1922
  }