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