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