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