@b9g/crank 0.5.0-beta.5 → 0.5.0-beta.7

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");
@@ -297,10 +297,13 @@
297
297
  create() {
298
298
  throw new Error("Not implemented");
299
299
  },
300
+ hydrate() {
301
+ throw new Error("Not implemented");
302
+ },
300
303
  scope: IDENTITY,
301
304
  read: IDENTITY,
302
- escape: IDENTITY,
303
- parse: IDENTITY,
305
+ text: IDENTITY,
306
+ raw: IDENTITY,
304
307
  patch: NOOP,
305
308
  arrange: NOOP,
306
309
  dispose: NOOP,
@@ -366,7 +369,31 @@
366
369
  }
367
370
  }
368
371
  const impl = this[_RendererImpl];
369
- const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children);
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);
370
397
  // We return the child values of the portal because portal elements
371
398
  // themselves have no readable value.
372
399
  if (isPromiseLike(childValues)) {
@@ -388,7 +415,7 @@
388
415
  }
389
416
  return renderer.read(ret.cachedChildValues);
390
417
  }
391
- function diffChildren(renderer, root, host, ctx, scope, parent, children) {
418
+ function diffChildren(renderer, root, host, ctx, scope, parent, children, hydrationData) {
392
419
  const oldRetained = wrap(parent.children);
393
420
  const newRetained = [];
394
421
  const newChildren = arrayify(children);
@@ -397,14 +424,15 @@
397
424
  let childrenByKey;
398
425
  let seenKeys;
399
426
  let isAsync = false;
400
- let oi = 0, oldLength = oldRetained.length;
427
+ let hydrationBlock;
428
+ let oi = 0;
429
+ let oldLength = oldRetained.length;
401
430
  for (let ni = 0, newLength = newChildren.length; ni < newLength; ni++) {
402
- // We make sure we don’t access indices out of bounds to prevent
403
- // deoptimizations.
431
+ // length checks to prevent index out of bounds deoptimizations.
404
432
  let ret = oi >= oldLength ? undefined : oldRetained[oi];
405
433
  let child = narrow(newChildren[ni]);
406
434
  {
407
- // Aligning new children with old retainers
435
+ // aligning new children with old retainers
408
436
  let oldKey = typeof ret === "object" ? ret.el.key : undefined;
409
437
  let newKey = typeof child === "object" ? child.key : undefined;
410
438
  if (newKey !== undefined && seenKeys && seenKeys.has(newKey)) {
@@ -439,18 +467,19 @@
439
467
  // Updating
440
468
  let value;
441
469
  if (typeof child === "object") {
442
- if (typeof ret === "object" && child.static_) {
443
- ret.el = child;
444
- value = getInflightValue(ret);
445
- }
446
- else if (child.tag === Copy) {
470
+ if (child.tag === Copy) {
447
471
  value = getInflightValue(ret);
448
472
  }
449
473
  else {
450
474
  let oldProps;
475
+ let static_ = false;
451
476
  if (typeof ret === "object" && ret.el.tag === child.tag) {
452
477
  oldProps = ret.el.props;
453
478
  ret.el = child;
479
+ if (child.static_) {
480
+ value = getInflightValue(ret);
481
+ static_ = true;
482
+ }
454
483
  }
455
484
  else {
456
485
  if (typeof ret === "object") {
@@ -460,17 +489,26 @@
460
489
  ret = new Retainer(child);
461
490
  ret.fallbackValue = fallback;
462
491
  }
463
- if (child.tag === Raw) {
464
- 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);
465
497
  }
466
498
  else if (child.tag === Fragment) {
467
- 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);
468
502
  }
469
503
  else if (typeof child.tag === "function") {
470
- 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);
471
507
  }
472
508
  else {
473
- 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);
474
512
  }
475
513
  }
476
514
  const ref = child.ref;
@@ -482,9 +520,14 @@
482
520
  return value;
483
521
  });
484
522
  }
523
+ if (hydrationData !== undefined) {
524
+ hydrationBlock = value;
525
+ }
485
526
  }
486
- else if (typeof ref === "function") {
487
- ref(renderer.read(value));
527
+ else {
528
+ if (typeof ref === "function") {
529
+ ref(renderer.read(value));
530
+ }
488
531
  }
489
532
  }
490
533
  else {
@@ -493,7 +536,7 @@
493
536
  (graveyard = graveyard || []).push(ret);
494
537
  }
495
538
  if (typeof child === "string") {
496
- value = ret = renderer.escape(child, scope);
539
+ value = ret = renderer.text(child, scope, hydrationData);
497
540
  }
498
541
  else {
499
542
  ret = undefined;
@@ -573,53 +616,64 @@
573
616
  }
574
617
  return getValue(child);
575
618
  }
576
- function updateRaw(renderer, ret, scope, oldProps) {
619
+ function updateRaw(renderer, ret, scope, oldProps, hydrationData) {
577
620
  const props = ret.el.props;
578
- if (typeof props.value === "string") {
579
- if (!oldProps || oldProps.value !== props.value) {
580
- ret.value = renderer.parse(props.value, scope);
581
- }
582
- }
583
- else {
584
- ret.value = props.value;
621
+ if (!oldProps || oldProps.value !== props.value) {
622
+ ret.value = renderer.raw(props.value, scope, hydrationData);
585
623
  }
586
624
  return ret.value;
587
625
  }
588
- function updateFragment(renderer, root, host, ctx, scope, ret) {
589
- 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);
590
628
  if (isPromiseLike(childValues)) {
591
629
  ret.inflightValue = childValues.then((childValues) => unwrap(childValues));
592
630
  return ret.inflightValue;
593
631
  }
594
632
  return unwrap(childValues);
595
633
  }
596
- function updateHost(renderer, root, ctx, scope, ret, oldProps) {
634
+ function updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData) {
597
635
  const el = ret.el;
598
636
  const tag = el.tag;
637
+ let hydrationValue;
599
638
  if (el.tag === Portal) {
600
639
  root = ret.value = el.props.root;
601
640
  }
602
- else if (!oldProps) {
603
- // We use the truthiness of oldProps to determine if this the first render.
604
- ret.value = renderer.create(tag, el.props, scope);
641
+ else {
642
+ if (hydrationData !== undefined) {
643
+ const value = hydrationData.children.shift();
644
+ hydrationValue = value;
645
+ }
605
646
  }
606
647
  scope = renderer.scope(scope, tag, el.props);
607
- 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);
608
656
  if (isPromiseLike(childValues)) {
609
- ret.inflightValue = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps));
657
+ ret.inflightValue = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue));
610
658
  return ret.inflightValue;
611
659
  }
612
- return commitHost(renderer, scope, ret, childValues, oldProps);
660
+ return commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue);
613
661
  }
614
- function commitHost(renderer, scope, ret, childValues, oldProps) {
662
+ function commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue) {
615
663
  const tag = ret.el.tag;
616
- const value = ret.value;
664
+ let value = hydrationValue || ret.value;
617
665
  let props = ret.el.props;
618
666
  let copied;
619
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
+ }
620
672
  for (const propName in { ...oldProps, ...props }) {
621
673
  const propValue = props[propName];
622
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.
623
677
  (copied = copied || new Set()).add(propName);
624
678
  }
625
679
  else if (propName !== "children") {
@@ -720,23 +774,27 @@
720
774
  */
721
775
  const IsSyncExecuting = 1 << 1;
722
776
  /**
723
- * A flag which is true when the component is in the render loop.
777
+ * A flag which is true when the component is in a for...of loop.
724
778
  */
725
- const IsInRenderLoop = 1 << 2;
779
+ const IsInForOfLoop = 1 << 2;
780
+ /**
781
+ * A flag which is true when the component is in a for await...of loop.
782
+ */
783
+ const IsInForAwaitOfLoop = 1 << 3;
726
784
  /**
727
785
  * A flag which is true when the component starts the render loop but has not
728
786
  * yielded yet.
729
787
  *
730
788
  * Used to make sure that components yield at least once per loop.
731
789
  */
732
- const NeedsToYield = 1 << 3;
790
+ const NeedsToYield = 1 << 4;
733
791
  /**
734
792
  * A flag used by async generator components in conjunction with the
735
793
  * onAvailable callback to mark whether new props can be pulled via the context
736
794
  * async iterator. See the Symbol.asyncIterator method and the
737
795
  * resumeCtxIterator function.
738
796
  */
739
- const PropsAvailable = 1 << 4;
797
+ const PropsAvailable = 1 << 5;
740
798
  /**
741
799
  * A flag which is set when a component errors.
742
800
  *
@@ -838,11 +896,8 @@
838
896
  }
839
897
  *[Symbol.iterator]() {
840
898
  const ctx = this[_ContextImpl];
841
- if (ctx.f & IsAsyncGen) {
842
- throw new Error("Use for await…of in async generator components");
843
- }
844
899
  try {
845
- ctx.f |= IsInRenderLoop;
900
+ ctx.f |= IsInForOfLoop;
846
901
  while (!(ctx.f & IsUnmounted)) {
847
902
  if (ctx.f & NeedsToYield) {
848
903
  throw new Error("Context iterated twice without a yield");
@@ -854,19 +909,16 @@
854
909
  }
855
910
  }
856
911
  finally {
857
- ctx.f &= ~IsInRenderLoop;
912
+ ctx.f &= ~IsInForOfLoop;
858
913
  }
859
914
  }
860
915
  async *[Symbol.asyncIterator]() {
861
916
  const ctx = this[_ContextImpl];
862
917
  if (ctx.f & IsSyncGen) {
863
- throw new Error("Use forof in sync generator components");
918
+ throw new Error("Use for...of in sync generator components");
864
919
  }
865
920
  try {
866
- // await an empty promise to prevent the IsInRenderLoop flag from
867
- // returning false positives in the case of async generator components
868
- // which immediately enter the loop
869
- ctx.f |= IsInRenderLoop;
921
+ ctx.f |= IsInForAwaitOfLoop;
870
922
  while (!(ctx.f & IsUnmounted)) {
871
923
  if (ctx.f & NeedsToYield) {
872
924
  throw new Error("Context iterated twice without a yield");
@@ -892,7 +944,7 @@
892
944
  }
893
945
  }
894
946
  finally {
895
- ctx.f &= ~IsInRenderLoop;
947
+ ctx.f &= ~IsInForAwaitOfLoop;
896
948
  if (ctx.onPropsRequested) {
897
949
  ctx.onPropsRequested();
898
950
  ctx.onPropsRequested = undefined;
@@ -1183,9 +1235,13 @@
1183
1235
  }
1184
1236
  return false;
1185
1237
  }
1186
- function updateComponent(renderer, root, host, parent, scope, ret, oldProps) {
1238
+ function updateComponent(renderer, root, host, parent, scope, ret, oldProps, hydrationData) {
1187
1239
  let ctx;
1188
1240
  if (oldProps) {
1241
+ // TODO: we should probably use the existence of ret.ctx
1242
+ if (ret.ctx == null) {
1243
+ throw new Error("Hmmm");
1244
+ }
1189
1245
  ctx = ret.ctx;
1190
1246
  if (ctx.f & IsSyncExecuting) {
1191
1247
  console.error("Component is already executing");
@@ -1196,9 +1252,9 @@
1196
1252
  ctx = ret.ctx = new ContextImpl(renderer, root, host, parent, scope, ret);
1197
1253
  }
1198
1254
  ctx.f |= IsUpdating;
1199
- return enqueueComponentRun(ctx);
1255
+ return enqueueComponentRun(ctx, hydrationData);
1200
1256
  }
1201
- function updateComponentChildren(ctx, children) {
1257
+ function updateComponentChildren(ctx, children, hydrationData) {
1202
1258
  if (ctx.f & IsUnmounted) {
1203
1259
  return;
1204
1260
  }
@@ -1216,7 +1272,7 @@
1216
1272
  // We set the isExecuting flag in case a child component dispatches an event
1217
1273
  // which bubbles to this component and causes a synchronous refresh().
1218
1274
  ctx.f |= IsSyncExecuting;
1219
- childValues = diffChildren(ctx.renderer, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children));
1275
+ childValues = diffChildren(ctx.renderer, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children), hydrationData);
1220
1276
  }
1221
1277
  finally {
1222
1278
  ctx.f &= ~IsSyncExecuting;
@@ -1314,37 +1370,41 @@
1314
1370
  return true;
1315
1371
  }
1316
1372
  /** Enqueues and executes the component associated with the context. */
1317
- function enqueueComponentRun(ctx) {
1318
- if (ctx.f & IsAsyncGen) {
1319
- // This branch will only run for async generator components after the
1320
- // initial render.
1373
+ function enqueueComponentRun(ctx, hydrationData) {
1374
+ if (ctx.f & IsAsyncGen && !(ctx.f & IsInForOfLoop)) {
1375
+ if (hydrationData !== undefined) {
1376
+ throw new Error("Hydration error");
1377
+ }
1378
+ // This branch will run for non-initial renders of async generator
1379
+ // components when they are not in for...of loops. When in a for...of loop,
1380
+ // async generator components will behave normally.
1321
1381
  //
1322
- // Async generator components which are in the props loop can be in one of
1323
- // three states:
1382
+ // Async gen componennts can be in one of three states:
1324
1383
  //
1325
1384
  // 1. propsAvailable flag is true: "available"
1326
1385
  //
1327
- // The component is paused somewhere in the loop. When the component
1386
+ // The component is suspended somewhere in the loop. When the component
1328
1387
  // reaches the bottom of the loop, it will run again with the next props.
1329
1388
  //
1330
1389
  // 2. onAvailable callback is defined: "suspended"
1331
1390
  //
1332
- // The component has reached the bottom of the loop and is waiting for
1333
- // new props.
1391
+ // The component has suspended at the bottom of the loop and is waiting
1392
+ // for new props.
1334
1393
  //
1335
1394
  // 3. neither 1 or 2: "Running"
1336
1395
  //
1337
- // The component is paused somewhere in the loop. When the component
1396
+ // The component is suspended somewhere in the loop. When the component
1338
1397
  // reaches the bottom of the loop, it will suspend.
1339
1398
  //
1340
- // By definition, components will never be both available and suspended at
1399
+ // Components will never be both available and suspended at
1341
1400
  // the same time.
1342
1401
  //
1343
1402
  // If the component is at the loop bottom, this means that the next value
1344
1403
  // produced by the component will have the most up to date props, so we can
1345
1404
  // simply return the current inflight value. Otherwise, we have to wait for
1346
- // the bottom of the loop before returning the inflight value.
1347
- const isAtLoopbottom = ctx.f & IsInRenderLoop && !ctx.onProps;
1405
+ // the bottom of the loop to be reached before returning the inflight
1406
+ // value.
1407
+ const isAtLoopbottom = ctx.f & IsInForAwaitOfLoop && !ctx.onProps;
1348
1408
  resumePropsIterator(ctx);
1349
1409
  if (isAtLoopbottom) {
1350
1410
  if (ctx.inflightBlock == null) {
@@ -1359,7 +1419,7 @@
1359
1419
  }
1360
1420
  else if (!ctx.inflightBlock) {
1361
1421
  try {
1362
- const [block, value] = runComponent(ctx);
1422
+ const [block, value] = runComponent(ctx, hydrationData);
1363
1423
  if (block) {
1364
1424
  ctx.inflightBlock = block
1365
1425
  // TODO: there is some fuckery going on here related to async
@@ -1379,8 +1439,11 @@
1379
1439
  }
1380
1440
  }
1381
1441
  else if (!ctx.enqueuedBlock) {
1442
+ if (hydrationData !== undefined) {
1443
+ throw new Error("Hydration error");
1444
+ }
1382
1445
  // We need to assign enqueuedBlock and enqueuedValue synchronously, hence
1383
- // the Promise constructor call.
1446
+ // the Promise constructor call here.
1384
1447
  let resolveEnqueuedBlock;
1385
1448
  ctx.enqueuedBlock = new Promise((resolve) => (resolveEnqueuedBlock = resolve));
1386
1449
  ctx.enqueuedValue = ctx.inflightBlock.then(() => {
@@ -1403,7 +1466,7 @@
1403
1466
  }
1404
1467
  /** Called when the inflight block promise settles. */
1405
1468
  function advanceComponent(ctx) {
1406
- if (ctx.f & IsAsyncGen) {
1469
+ if (ctx.f & IsAsyncGen && !(ctx.f & IsInForOfLoop)) {
1407
1470
  return;
1408
1471
  }
1409
1472
  ctx.inflightBlock = ctx.enqueuedBlock;
@@ -1429,7 +1492,7 @@
1429
1492
  * - Sync generator components block while any children are executing, because
1430
1493
  * they are expected to only resume when they’ve actually rendered.
1431
1494
  */
1432
- function runComponent(ctx) {
1495
+ function runComponent(ctx, hydrationData) {
1433
1496
  const ret = ctx.ret;
1434
1497
  const initial = !ctx.iterator;
1435
1498
  if (initial) {
@@ -1453,7 +1516,7 @@
1453
1516
  else if (isPromiseLike(result)) {
1454
1517
  // async function component
1455
1518
  const result1 = result instanceof Promise ? result : Promise.resolve(result);
1456
- const value = result1.then((result) => updateComponentChildren(ctx, result), (err) => {
1519
+ const value = result1.then((result) => updateComponentChildren(ctx, result, hydrationData), (err) => {
1457
1520
  ctx.f |= IsErrored;
1458
1521
  throw err;
1459
1522
  });
@@ -1461,9 +1524,15 @@
1461
1524
  }
1462
1525
  else {
1463
1526
  // sync function component
1464
- return [undefined, updateComponentChildren(ctx, result)];
1527
+ return [
1528
+ undefined,
1529
+ updateComponentChildren(ctx, result, hydrationData),
1530
+ ];
1465
1531
  }
1466
1532
  }
1533
+ else if (hydrationData !== undefined) {
1534
+ throw new Error("Hydration error");
1535
+ }
1467
1536
  let iteration;
1468
1537
  if (initial) {
1469
1538
  try {
@@ -1479,15 +1548,14 @@
1479
1548
  }
1480
1549
  if (isPromiseLike(iteration)) {
1481
1550
  ctx.f |= IsAsyncGen;
1482
- runAsyncGenComponent(ctx, iteration);
1483
1551
  }
1484
1552
  else {
1485
1553
  ctx.f |= IsSyncGen;
1486
1554
  }
1487
1555
  }
1488
1556
  if (ctx.f & IsSyncGen) {
1489
- // sync generator component
1490
1557
  ctx.f &= ~NeedsToYield;
1558
+ // sync generator component
1491
1559
  if (!initial) {
1492
1560
  try {
1493
1561
  ctx.f |= IsSyncExecuting;
@@ -1502,7 +1570,7 @@
1502
1570
  }
1503
1571
  }
1504
1572
  if (isPromiseLike(iteration)) {
1505
- throw new Error("Sync generator component returned an async iteration");
1573
+ throw new Error("Mixed generator component");
1506
1574
  }
1507
1575
  if (iteration.done) {
1508
1576
  ctx.f &= ~IsSyncGen;
@@ -1512,7 +1580,7 @@
1512
1580
  try {
1513
1581
  value = updateComponentChildren(ctx,
1514
1582
  // Children can be void so we eliminate that here
1515
- iteration.value);
1583
+ iteration.value, hydrationData);
1516
1584
  if (isPromiseLike(value)) {
1517
1585
  value = value.catch((err) => handleChildError(ctx, err));
1518
1586
  }
@@ -1523,15 +1591,63 @@
1523
1591
  const block = isPromiseLike(value) ? value.catch(NOOP) : undefined;
1524
1592
  return [block, value];
1525
1593
  }
1594
+ else if (ctx.f & IsInForOfLoop) {
1595
+ // TODO: does this need to be done async?
1596
+ ctx.f &= ~NeedsToYield;
1597
+ // we are in a for...of loop for async generator
1598
+ if (!initial) {
1599
+ try {
1600
+ ctx.f |= IsSyncExecuting;
1601
+ iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
1602
+ }
1603
+ catch (err) {
1604
+ ctx.f |= IsErrored;
1605
+ throw err;
1606
+ }
1607
+ finally {
1608
+ ctx.f &= ~IsSyncExecuting;
1609
+ }
1610
+ }
1611
+ if (!isPromiseLike(iteration)) {
1612
+ throw new Error("Mixed generator component");
1613
+ }
1614
+ const block = iteration.catch(NOOP);
1615
+ const value = iteration.then((iteration) => {
1616
+ let value;
1617
+ if (!(ctx.f & IsInForOfLoop)) {
1618
+ runAsyncGenComponent(ctx, Promise.resolve(iteration), hydrationData);
1619
+ }
1620
+ try {
1621
+ value = updateComponentChildren(ctx,
1622
+ // Children can be void so we eliminate that here
1623
+ iteration.value, hydrationData);
1624
+ if (isPromiseLike(value)) {
1625
+ value = value.catch((err) => handleChildError(ctx, err));
1626
+ }
1627
+ }
1628
+ catch (err) {
1629
+ value = handleChildError(ctx, err);
1630
+ }
1631
+ return value;
1632
+ }, (err) => {
1633
+ ctx.f |= IsErrored;
1634
+ throw err;
1635
+ });
1636
+ return [block, value];
1637
+ }
1526
1638
  else {
1639
+ runAsyncGenComponent(ctx, iteration, hydrationData);
1527
1640
  // async generator component
1528
- return [undefined, ctx.inflightValue];
1641
+ return [ctx.inflightBlock, ctx.inflightValue];
1529
1642
  }
1530
1643
  }
1531
- async function runAsyncGenComponent(ctx, iterationP) {
1644
+ async function runAsyncGenComponent(ctx, iterationP, hydrationData) {
1532
1645
  let done = false;
1533
1646
  try {
1534
1647
  while (!done) {
1648
+ if (ctx.f & IsInForOfLoop) {
1649
+ break;
1650
+ }
1535
1651
  // inflightValue must be set synchronously.
1536
1652
  let onValue;
1537
1653
  ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
@@ -1553,20 +1669,20 @@
1553
1669
  }
1554
1670
  finally {
1555
1671
  ctx.f &= ~NeedsToYield;
1556
- if (!(ctx.f & IsInRenderLoop)) {
1672
+ if (!(ctx.f & IsInForAwaitOfLoop)) {
1557
1673
  ctx.f &= ~PropsAvailable;
1558
1674
  }
1559
1675
  }
1560
1676
  done = !!iteration.done;
1561
1677
  let value;
1562
1678
  try {
1563
- value = updateComponentChildren(ctx, iteration.value);
1679
+ value = updateComponentChildren(ctx, iteration.value, hydrationData);
1680
+ hydrationData = undefined;
1564
1681
  if (isPromiseLike(value)) {
1565
1682
  value = value.catch((err) => handleChildError(ctx, err));
1566
1683
  }
1567
1684
  }
1568
1685
  catch (err) {
1569
- done = true;
1570
1686
  // Do we need to catch potential errors here in the case of unhandled
1571
1687
  // promise rejections?
1572
1688
  value = handleChildError(ctx, err);
@@ -1588,7 +1704,7 @@
1588
1704
  oldValue = ctx.renderer.read(getValue(ctx.ret));
1589
1705
  }
1590
1706
  if (ctx.f & IsUnmounted) {
1591
- if (ctx.f & IsInRenderLoop) {
1707
+ if (ctx.f & IsInForAwaitOfLoop) {
1592
1708
  try {
1593
1709
  ctx.f |= IsSyncExecuting;
1594
1710
  iterationP = ctx.iterator.next(oldValue);
@@ -1602,7 +1718,7 @@
1602
1718
  break;
1603
1719
  }
1604
1720
  }
1605
- else if (!done) {
1721
+ else if (!done && !(ctx.f & IsInForOfLoop)) {
1606
1722
  try {
1607
1723
  ctx.f |= IsSyncExecuting;
1608
1724
  iterationP = ctx.iterator.next(oldValue);
@@ -1614,8 +1730,10 @@
1614
1730
  }
1615
1731
  }
1616
1732
  finally {
1617
- ctx.f &= ~IsAsyncGen;
1618
- ctx.iterator = undefined;
1733
+ if (done) {
1734
+ ctx.f &= ~IsAsyncGen;
1735
+ ctx.iterator = undefined;
1736
+ }
1619
1737
  }
1620
1738
  }
1621
1739
  /**
@@ -1646,12 +1764,12 @@
1646
1764
  if (ctx.iterator) {
1647
1765
  if (ctx.f & IsSyncGen) {
1648
1766
  let value;
1649
- if (ctx.f & IsInRenderLoop) {
1767
+ if (ctx.f & IsInForOfLoop) {
1650
1768
  value = enqueueComponentRun(ctx);
1651
1769
  }
1652
1770
  if (isPromiseLike(value)) {
1653
1771
  value.then(() => {
1654
- if (ctx.f & IsInRenderLoop) {
1772
+ if (ctx.f & IsInForOfLoop) {
1655
1773
  unmountComponent(ctx);
1656
1774
  }
1657
1775
  else {
@@ -1662,7 +1780,7 @@
1662
1780
  });
1663
1781
  }
1664
1782
  else {
1665
- if (ctx.f & IsInRenderLoop) {
1783
+ if (ctx.f & IsInForOfLoop) {
1666
1784
  unmountComponent(ctx);
1667
1785
  }
1668
1786
  else {
@@ -1671,9 +1789,24 @@
1671
1789
  }
1672
1790
  }
1673
1791
  else if (ctx.f & IsAsyncGen) {
1674
- // The logic for unmounting async generator components is in the
1675
- // runAsyncGenComponent function.
1676
- resumePropsIterator(ctx);
1792
+ if (ctx.f & IsInForOfLoop) {
1793
+ const value = enqueueComponentRun(ctx);
1794
+ value.then(() => {
1795
+ if (ctx.f & IsInForOfLoop) {
1796
+ unmountComponent(ctx);
1797
+ }
1798
+ else {
1799
+ returnComponent(ctx);
1800
+ }
1801
+ }, (err) => {
1802
+ propagateError(ctx.parent, err);
1803
+ });
1804
+ }
1805
+ else {
1806
+ // The logic for unmounting async generator components is in the
1807
+ // runAsyncGenComponent function.
1808
+ resumePropsIterator(ctx);
1809
+ }
1677
1810
  }
1678
1811
  }
1679
1812
  }
@@ -1791,6 +1924,7 @@
1791
1924
  }
1792
1925
  if (iteration.done) {
1793
1926
  ctx.f &= ~IsSyncGen;
1927
+ ctx.f &= ~IsAsyncGen;
1794
1928
  ctx.iterator = undefined;
1795
1929
  }
1796
1930
  return updateComponentChildren(ctx, iteration.value);
@@ -1812,469 +1946,60 @@
1812
1946
  return result;
1813
1947
  }
1814
1948
 
1815
- const cache = new Map();
1816
- function jsx(spans, ...expressions) {
1817
- const key = JSON.stringify(spans.raw);
1818
- let parseResult = cache.get(key);
1819
- if (parseResult == null) {
1820
- parseResult = parse(spans.raw);
1821
- cache.set(key, parseResult);
1822
- }
1823
- const { element, targets } = parseResult;
1824
- for (let i = 0; i < expressions.length; i++) {
1825
- const exp = expressions[i];
1826
- const target = targets[i];
1827
- if (target) {
1828
- if (target.type === "error") {
1829
- throw new SyntaxError(target.message.replace("${}", formatTagForError(exp)));
1830
- }
1831
- target.value = exp;
1832
- }
1833
- }
1834
- return build(element);
1835
- }
1836
- /**
1837
- * Matches first significant character in children mode.
1838
- *
1839
- * Group 1: newline
1840
- * Group 2: comment
1841
- * Group 3: tag
1842
- * Group 4: closing slash
1843
- * Group 5: tag name
1844
- *
1845
- * The comment group must appear first because the tag group can potentially
1846
- * match a comment, so that we can handle tag expressions where we’ve reached
1847
- * the end of a span.
1848
- */
1849
- const CHILDREN_RE = /((?:\r|\n|\r\n)\s*)|(<!--[\S\s]*?(?:-->|$))|(<\s*(\/{0,2})\s*([-_$\w]*))/g;
1850
- /**
1851
- * Matches props after element tags.
1852
- *
1853
- * Group 1: tag end
1854
- * Group 2: spread props
1855
- * Group 3: prop name
1856
- * Group 4: equals
1857
- * Group 5: prop value string
1858
- */
1859
- const PROPS_RE = /\s*(?:(\/?\s*>)|(\.\.\.\s*)|(?:([-_$\w]+)\s*(=)?\s*(?:("(\\"|[\S\s])*?(?:"|$)|'(?:\\'|[\S\s])*?(?:'|$)))?))/g;
1860
- const CLOSING_BRACKET_RE = />/g;
1861
- const CLOSING_SINGLE_QUOTE_RE = /[^\\]?'/g;
1862
- const CLOSING_DOUBLE_QUOTE_RE = /[^\\]?"/g;
1863
- const CLOSING_COMMENT_RE = /-->/g;
1864
- function parse(spans) {
1865
- let matcher = CHILDREN_RE;
1866
- const stack = [];
1867
- let element = {
1868
- type: "element",
1869
- open: { type: "tag", slash: "", value: "" },
1870
- close: null,
1871
- props: [],
1872
- children: [],
1873
- };
1874
- const targets = [];
1875
- let lineStart = true;
1876
- for (let s = 0; s < spans.length; s++) {
1877
- const span = spans[s];
1878
- // Whether or not an expression is upcoming. Used to provide better errors.
1879
- const expressing = s < spans.length - 1;
1880
- let expressionTarget = null;
1881
- for (let i = 0, end = i; i < span.length; i = end) {
1882
- matcher.lastIndex = i;
1883
- const match = matcher.exec(span);
1884
- end = match ? match.index + match[0].length : span.length;
1885
- switch (matcher) {
1886
- case CHILDREN_RE: {
1887
- if (match) {
1888
- const [, newline, comment, tag, closingSlash, tagName] = match;
1889
- if (i < match.index) {
1890
- let before = span.slice(i, match.index);
1891
- if (lineStart) {
1892
- before = before.replace(/^\s*/, "");
1893
- }
1894
- if (newline) {
1895
- if (span[Math.max(0, match.index - 1)] === "\\") {
1896
- // We preserve whitespace before escaped newlines and have to
1897
- // remove the backslash.
1898
- // jsx` \
1899
- // `
1900
- before = before.slice(0, -1);
1901
- }
1902
- else {
1903
- before = before.replace(/\s*$/, "");
1904
- }
1905
- }
1906
- if (before) {
1907
- element.children.push({ type: "value", value: before });
1908
- }
1909
- }
1910
- lineStart = !!newline;
1911
- if (comment) {
1912
- if (end === span.length) {
1913
- // Expression in a comment:
1914
- // jsx`<!-- ${exp} -->`
1915
- matcher = CLOSING_COMMENT_RE;
1916
- }
1917
- }
1918
- else if (tag) {
1919
- if (closingSlash) {
1920
- element.close = {
1921
- type: "tag",
1922
- slash: closingSlash,
1923
- value: tagName,
1924
- };
1925
- if (!stack.length) {
1926
- if (end !== span.length) {
1927
- throw new SyntaxError(`Unmatched closing tag "${tagName}"`);
1928
- }
1929
- // ERROR EXPRESSION
1930
- expressionTarget = {
1931
- type: "error",
1932
- message: "Unmatched closing tag ${}",
1933
- value: null,
1934
- };
1935
- }
1936
- else {
1937
- if (end === span.length) {
1938
- // TAG EXPRESSION
1939
- expressionTarget = element.close;
1940
- }
1941
- element = stack.pop();
1942
- matcher = CLOSING_BRACKET_RE;
1943
- }
1944
- }
1945
- else {
1946
- const next = {
1947
- type: "element",
1948
- open: {
1949
- type: "tag",
1950
- slash: "",
1951
- value: tagName,
1952
- },
1953
- close: null,
1954
- props: [],
1955
- children: [],
1956
- };
1957
- element.children.push(next);
1958
- stack.push(element);
1959
- element = next;
1960
- matcher = PROPS_RE;
1961
- if (end === span.length) {
1962
- // TAG EXPRESSION
1963
- expressionTarget = element.open;
1964
- }
1965
- }
1966
- }
1967
- }
1968
- else {
1969
- if (i < span.length) {
1970
- let after = span.slice(i);
1971
- if (!expressing) {
1972
- // trim trailing whitespace
1973
- after = after.replace(/\s*$/, "");
1974
- }
1975
- if (after) {
1976
- element.children.push({ type: "value", value: after });
1977
- }
1978
- }
1979
- }
1980
- break;
1981
- }
1982
- case PROPS_RE: {
1983
- if (match) {
1984
- const [, tagEnd, spread, name, equals, string] = match;
1985
- if (i < match.index) {
1986
- throw new SyntaxError(`Unexpected text \`${span.slice(i, match.index).trim()}\``);
1987
- }
1988
- if (tagEnd) {
1989
- if (tagEnd[0] === "/") {
1990
- // This is a self-closing element, so there will always be a
1991
- // result on the stack.
1992
- element = stack.pop();
1993
- }
1994
- matcher = CHILDREN_RE;
1995
- }
1996
- else if (spread) {
1997
- const value = {
1998
- type: "value",
1999
- value: null,
2000
- };
2001
- element.props.push(value);
2002
- // SPREAD PROP EXPRESSION
2003
- expressionTarget = value;
2004
- if (!(expressing && end === span.length)) {
2005
- throw new SyntaxError('Expression expected after "..."');
2006
- }
2007
- }
2008
- else if (name) {
2009
- let value;
2010
- if (string == null) {
2011
- if (!equals) {
2012
- value = { type: "value", value: true };
2013
- }
2014
- else if (end < span.length) {
2015
- throw new SyntaxError(`Unexpected text \`${span.slice(end, end + 20)}\``);
2016
- }
2017
- else {
2018
- value = { type: "value", value: null };
2019
- // PROP EXPRESSION
2020
- expressionTarget = value;
2021
- if (!(expressing && end === span.length)) {
2022
- throw new SyntaxError(`Expression expected for prop "${name}"`);
2023
- }
2024
- }
2025
- }
2026
- else {
2027
- const quote = string[0];
2028
- value = { type: "propString", parts: [] };
2029
- value.parts.push(string);
2030
- if (end === span.length) {
2031
- matcher =
2032
- quote === "'"
2033
- ? CLOSING_SINGLE_QUOTE_RE
2034
- : CLOSING_DOUBLE_QUOTE_RE;
2035
- }
2036
- }
2037
- const prop = {
2038
- type: "prop",
2039
- name,
2040
- value,
2041
- };
2042
- element.props.push(prop);
2043
- }
2044
- }
2045
- else {
2046
- if (!expressing) {
2047
- if (i === span.length) {
2048
- throw new SyntaxError(`Expected props but reached end of document`);
2049
- }
2050
- else {
2051
- throw new SyntaxError(`Unexpected text \`${span.slice(i, i + 20).trim()}\``);
2052
- }
2053
- }
2054
- // Unexpected expression errors are handled in the outer loop.
2055
- //
2056
- // This would most likely be the starting point for the logic of
2057
- // prop name expressions.
2058
- // jsx`<p ${name}=${value}>`
2059
- }
2060
- break;
2061
- }
2062
- case CLOSING_BRACKET_RE: {
2063
- // We’re in a closing tag and looking for the >.
2064
- if (match) {
2065
- if (i < match.index) {
2066
- throw new SyntaxError(`Unexpected text \`${span.slice(i, match.index).trim()}\``);
2067
- }
2068
- matcher = CHILDREN_RE;
2069
- }
2070
- else {
2071
- if (!expressing) {
2072
- throw new SyntaxError(`Unexpected text \`${span.slice(i, i + 20).trim()}\``);
2073
- }
2074
- }
2075
- break;
2076
- }
2077
- case CLOSING_SINGLE_QUOTE_RE:
2078
- case CLOSING_DOUBLE_QUOTE_RE: {
2079
- const string = span.slice(i, end);
2080
- const prop = element.props[element.props.length - 1];
2081
- const propString = prop.value;
2082
- propString.parts.push(string);
2083
- if (match) {
2084
- matcher = PROPS_RE;
2085
- }
2086
- else {
2087
- if (!expressing) {
2088
- throw new SyntaxError(`Missing \`${matcher === CLOSING_SINGLE_QUOTE_RE ? "'" : '"'}\``);
2089
- }
2090
- }
2091
- break;
2092
- }
2093
- case CLOSING_COMMENT_RE: {
2094
- if (match) {
2095
- matcher = CHILDREN_RE;
2096
- }
2097
- else {
2098
- if (!expressing) {
2099
- throw new SyntaxError("Expected `-->` but reached end of template");
2100
- }
2101
- }
2102
- break;
2103
- }
2104
- }
2105
- }
2106
- if (expressing) {
2107
- if (expressionTarget) {
2108
- targets.push(expressionTarget);
2109
- if (expressionTarget.type === "error") {
2110
- break;
2111
- }
2112
- continue;
2113
- }
2114
- switch (matcher) {
2115
- case CHILDREN_RE: {
2116
- const target = { type: "value", value: null };
2117
- element.children.push(target);
2118
- targets.push(target);
2119
- break;
2120
- }
2121
- case CLOSING_SINGLE_QUOTE_RE:
2122
- case CLOSING_DOUBLE_QUOTE_RE: {
2123
- const prop = element.props[element.props.length - 1];
2124
- const target = { type: "value", value: null };
2125
- prop.value.parts.push(target);
2126
- targets.push(target);
2127
- break;
2128
- }
2129
- case CLOSING_COMMENT_RE:
2130
- targets.push(null);
2131
- break;
2132
- default:
2133
- throw new SyntaxError("Unexpected expression");
2134
- }
2135
- }
2136
- else if (expressionTarget) {
2137
- throw new SyntaxError("Expression expected");
2138
- }
2139
- lineStart = false;
2140
- }
2141
- if (stack.length) {
2142
- const ti = targets.indexOf(element.open);
2143
- if (ti === -1) {
2144
- throw new SyntaxError(`Unmatched opening tag "${element.open.value}"`);
2145
- }
2146
- targets[ti] = {
2147
- type: "error",
2148
- message: "Unmatched opening tag ${}",
2149
- value: null,
2150
- };
2151
- }
2152
- if (element.children.length === 1 && element.children[0].type === "element") {
2153
- element = element.children[0];
2154
- }
2155
- return { element, targets };
2156
- }
2157
- function build(parsed) {
2158
- if (parsed.close !== null &&
2159
- parsed.close.slash !== "//" &&
2160
- parsed.open.value !== parsed.close.value) {
2161
- throw new SyntaxError(`Unmatched closing tag ${formatTagForError(parsed.close.value)}, expected ${formatTagForError(parsed.open.value)}`);
2162
- }
2163
- const children = [];
2164
- for (let i = 0; i < parsed.children.length; i++) {
2165
- const child = parsed.children[i];
2166
- children.push(child.type === "element" ? build(child) : child.value);
2167
- }
2168
- let props = parsed.props.length ? {} : null;
2169
- for (let i = 0; i < parsed.props.length; i++) {
2170
- const prop = parsed.props[i];
2171
- if (prop.type === "prop") {
2172
- let value;
2173
- if (prop.value.type === "value") {
2174
- value = prop.value.value;
2175
- }
2176
- else {
2177
- let string = "";
2178
- for (let i = 0; i < prop.value.parts.length; i++) {
2179
- const part = prop.value.parts[i];
2180
- if (typeof part === "string") {
2181
- string += part;
2182
- }
2183
- else if (typeof part.value !== "boolean" && part.value != null) {
2184
- string +=
2185
- typeof part.value === "string" ? part.value : String(part.value);
2186
- }
2187
- }
2188
- value = string
2189
- // remove quotes
2190
- .slice(1, -1)
2191
- // unescape things
2192
- // adapted from https://stackoverflow.com/a/57330383/1825413
2193
- .replace(/\\x[0-9a-f]{2}|\\u[0-9a-f]{4}|\\u\{[0-9a-f]+\}|\\./gi, (match) => {
2194
- switch (match[1]) {
2195
- case "b":
2196
- return "\b";
2197
- case "f":
2198
- return "\f";
2199
- case "n":
2200
- return "\n";
2201
- case "r":
2202
- return "\r";
2203
- case "t":
2204
- return "\t";
2205
- case "v":
2206
- return "\v";
2207
- case "x":
2208
- return String.fromCharCode(parseInt(match.slice(2), 16));
2209
- case "u":
2210
- if (match[2] === "{") {
2211
- return String.fromCodePoint(parseInt(match.slice(3, -1), 16));
2212
- }
2213
- return String.fromCharCode(parseInt(match.slice(2), 16));
2214
- case "0":
2215
- return "\0";
2216
- default:
2217
- return match.slice(1);
2218
- }
2219
- });
2220
- }
2221
- props[prop.name] = value;
2222
- }
2223
- else {
2224
- // spread prop
2225
- props = { ...props, ...prop.value };
2226
- }
2227
- }
2228
- return createElement(parsed.open.value, props, ...children);
2229
- }
2230
- function formatTagForError(tag) {
2231
- return typeof tag === "function"
2232
- ? tag.name + "()"
2233
- : typeof tag === "string"
2234
- ? `"${tag}"`
2235
- : JSON.stringify(tag);
2236
- }
2237
-
2238
1949
  const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
2239
1950
  const impl$1 = {
2240
- parse(text) {
2241
- if (typeof document.createRange === "function") {
2242
- const fragment = document.createRange().createContextualFragment(text);
2243
- return Array.from(fragment.childNodes);
2244
- }
2245
- else {
2246
- const childNodes = new DOMParser().parseFromString(text, "text/html").body
2247
- .childNodes;
2248
- return Array.from(childNodes);
2249
- }
2250
- },
2251
- scope(scope, tag) {
1951
+ scope(xmlns, tag) {
2252
1952
  // TODO: Should we handle xmlns???
2253
1953
  switch (tag) {
2254
1954
  case Portal:
2255
1955
  case "foreignObject":
2256
- return undefined;
1956
+ xmlns = undefined;
1957
+ break;
2257
1958
  case "svg":
2258
- return SVG_NAMESPACE;
2259
- default:
2260
- return scope;
1959
+ xmlns = SVG_NAMESPACE;
1960
+ break;
2261
1961
  }
1962
+ return xmlns;
2262
1963
  },
2263
- create(tag, _props, ns) {
1964
+ create(tag, _props, xmlns) {
2264
1965
  if (typeof tag !== "string") {
2265
1966
  throw new Error(`Unknown tag: ${tag.toString()}`);
2266
1967
  }
2267
1968
  else if (tag.toLowerCase() === "svg") {
2268
- ns = SVG_NAMESPACE;
1969
+ xmlns = SVG_NAMESPACE;
2269
1970
  }
2270
- return ns ? document.createElementNS(ns, tag) : document.createElement(tag);
1971
+ return xmlns
1972
+ ? document.createElementNS(xmlns, tag)
1973
+ : document.createElement(tag);
1974
+ },
1975
+ hydrate(tag, node, props) {
1976
+ if (typeof tag !== "string" && tag !== Portal) {
1977
+ throw new Error(`Unknown tag: ${tag.toString()}`);
1978
+ }
1979
+ if (typeof tag === "string" &&
1980
+ tag.toUpperCase() !== node.tagName) {
1981
+ console.error(`Expected <${tag}> while hydrating but found:`, node);
1982
+ return undefined;
1983
+ }
1984
+ const children = [];
1985
+ for (let i = 0; i < node.childNodes.length; i++) {
1986
+ const child = node.childNodes[i];
1987
+ if (child.nodeType === Node.TEXT_NODE) {
1988
+ children.push(child.data);
1989
+ }
1990
+ else if (child.nodeType === Node.ELEMENT_NODE) {
1991
+ children.push(child);
1992
+ }
1993
+ }
1994
+ // TODO: extract props from nodes
1995
+ return { props, children };
2271
1996
  },
2272
1997
  patch(_tag,
2273
1998
  // TODO: Why does this assignment work?
2274
1999
  node, name,
2275
2000
  // TODO: Stricter typings?
2276
- value, oldValue, scope) {
2277
- const isSVG = scope === SVG_NAMESPACE;
2001
+ value, oldValue, xmlns) {
2002
+ const isSVG = xmlns === SVG_NAMESPACE;
2278
2003
  switch (name) {
2279
2004
  case "style": {
2280
2005
  const style = node.style;
@@ -2350,7 +2075,9 @@
2350
2075
  const descriptor = Object.getOwnPropertyDescriptor(obj, name);
2351
2076
  if (descriptor != null &&
2352
2077
  (descriptor.writable === true || descriptor.set !== undefined)) {
2353
- node[name] = value;
2078
+ if (node[name] !== value) {
2079
+ node[name] = value;
2080
+ }
2354
2081
  return;
2355
2082
  }
2356
2083
  // if the property wasn't writable, fall through to the code below
@@ -2437,24 +2164,86 @@
2437
2164
  }
2438
2165
  }
2439
2166
  },
2167
+ text(text, _scope, hydrationData) {
2168
+ if (hydrationData != null) {
2169
+ let value = hydrationData.children.shift();
2170
+ if (typeof value !== "string" || !value.startsWith(text)) {
2171
+ console.error(`Expected "${text}" while hydrating but found:`, value);
2172
+ }
2173
+ else if (text.length < value.length) {
2174
+ value = value.slice(text.length);
2175
+ hydrationData.children.unshift(value);
2176
+ }
2177
+ }
2178
+ return text;
2179
+ },
2180
+ raw(value, xmlns, hydrationData) {
2181
+ let result;
2182
+ if (typeof value === "string") {
2183
+ const el = xmlns == null
2184
+ ? document.createElement("div")
2185
+ : document.createElementNS(xmlns, "svg");
2186
+ el.innerHTML = value;
2187
+ if (el.childNodes.length === 0) {
2188
+ result = undefined;
2189
+ }
2190
+ else if (el.childNodes.length === 1) {
2191
+ result = el.childNodes[0];
2192
+ }
2193
+ else {
2194
+ result = Array.from(el.childNodes);
2195
+ }
2196
+ }
2197
+ else {
2198
+ result = value;
2199
+ }
2200
+ if (hydrationData != null) {
2201
+ // TODO: maybe we should warn on incorrect values
2202
+ if (Array.isArray(result)) {
2203
+ for (let i = 0; i < result.length; i++) {
2204
+ const node = result[i];
2205
+ if (typeof node !== "string" &&
2206
+ (node.nodeType === Node.ELEMENT_NODE ||
2207
+ node.nodeType === Node.TEXT_NODE)) {
2208
+ hydrationData.children.shift();
2209
+ }
2210
+ }
2211
+ }
2212
+ else if (result != null && typeof result !== "string") {
2213
+ if (result.nodeType === Node.ELEMENT_NODE ||
2214
+ result.nodeType === Node.TEXT_NODE) {
2215
+ hydrationData.children.shift();
2216
+ }
2217
+ }
2218
+ }
2219
+ return result;
2220
+ },
2440
2221
  };
2441
2222
  class DOMRenderer extends Renderer {
2442
2223
  constructor() {
2443
2224
  super(impl$1);
2444
2225
  }
2445
2226
  render(children, root, ctx) {
2446
- if (root == null || typeof root.nodeType !== "number") {
2447
- throw new TypeError(`Render root is not a node. Received: ${JSON.stringify(root && root.toString())}`);
2448
- }
2227
+ validateRoot(root);
2449
2228
  return super.render(children, root, ctx);
2450
2229
  }
2230
+ hydrate(children, root, ctx) {
2231
+ validateRoot(root);
2232
+ return super.hydrate(children, root, ctx);
2233
+ }
2234
+ }
2235
+ function validateRoot(root) {
2236
+ if (root === null ||
2237
+ (typeof root === "object" && typeof root.nodeType !== "number")) {
2238
+ throw new TypeError(`Render root is not a node. Received: ${JSON.stringify(root && root.toString())}`);
2239
+ }
2451
2240
  }
2452
2241
  const renderer$1 = new DOMRenderer();
2453
2242
 
2454
2243
  var dom = /*#__PURE__*/Object.freeze({
2455
2244
  __proto__: null,
2456
- impl: impl$1,
2457
2245
  DOMRenderer: DOMRenderer,
2246
+ impl: impl$1,
2458
2247
  renderer: renderer$1
2459
2248
  });
2460
2249
 
@@ -2551,7 +2340,7 @@
2551
2340
  create() {
2552
2341
  return { value: "" };
2553
2342
  },
2554
- escape(text) {
2343
+ text(text) {
2555
2344
  return escape(text);
2556
2345
  },
2557
2346
  read(value) {
@@ -2598,8 +2387,8 @@
2598
2387
 
2599
2388
  var html = /*#__PURE__*/Object.freeze({
2600
2389
  __proto__: null,
2601
- impl: impl,
2602
2390
  HTMLRenderer: HTMLRenderer,
2391
+ impl: impl,
2603
2392
  renderer: renderer
2604
2393
  });
2605
2394
 
@@ -2615,9 +2404,6 @@
2615
2404
  exports.dom = dom;
2616
2405
  exports.html = html;
2617
2406
  exports.isElement = isElement;
2618
- exports.jsx = jsx;
2619
-
2620
- Object.defineProperty(exports, '__esModule', { value: true });
2621
2407
 
2622
2408
  }));
2623
2409
  //# sourceMappingURL=umd.js.map