ripple 0.3.127 → 0.3.128

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/package.json +2 -2
  3. package/src/runtime/internal/client/blocks.js +20 -24
  4. package/src/runtime/internal/client/constants.js +5 -5
  5. package/src/runtime/internal/client/expression.js +142 -101
  6. package/src/runtime/internal/client/for.js +217 -145
  7. package/src/runtime/internal/client/index.js +5 -1
  8. package/src/runtime/internal/client/operations.js +24 -10
  9. package/src/runtime/internal/client/render.js +22 -3
  10. package/src/runtime/internal/client/runtime.js +246 -182
  11. package/src/runtime/internal/client/selector.js +74 -0
  12. package/src/runtime/internal/client/types.d.ts +11 -0
  13. package/src/runtime/internal/server/index.js +2 -0
  14. package/tests/client/basic/basic.reactivity.test.tsrx +33 -0
  15. package/tests/client/for-selector.test.tsrx +221 -0
  16. package/tests/hydration/compiled/client/basic.js +44 -44
  17. package/tests/hydration/compiled/client/composite.js +3 -3
  18. package/tests/hydration/compiled/client/events.js +56 -29
  19. package/tests/hydration/compiled/client/for.js +76 -70
  20. package/tests/hydration/compiled/client/head.js +11 -8
  21. package/tests/hydration/compiled/client/hmr.js +4 -4
  22. package/tests/hydration/compiled/client/html-in-template.js +3 -3
  23. package/tests/hydration/compiled/client/html.js +141 -141
  24. package/tests/hydration/compiled/client/if-children.js +29 -29
  25. package/tests/hydration/compiled/client/if-fragment-controlflow.js +16 -16
  26. package/tests/hydration/compiled/client/if.js +10 -10
  27. package/tests/hydration/compiled/client/mixed-control-flow.js +26 -22
  28. package/tests/hydration/compiled/client/nested-control-flow.js +114 -92
  29. package/tests/hydration/compiled/client/portal.js +8 -8
  30. package/tests/hydration/compiled/client/reactivity.js +38 -15
  31. package/tests/hydration/compiled/client/streaming.js +16 -16
  32. package/tests/hydration/compiled/client/switch.js +4 -4
  33. package/tests/hydration/compiled/client/track-async-serialization.js +18 -18
  34. package/tests/hydration/compiled/client/try.js +8 -8
  35. package/tests/hydration/compiled/server/events.js +5 -53
  36. package/tests/hydration/compiled/server/for.js +2 -18
  37. package/tests/hydration/compiled/server/head.js +1 -9
  38. package/tests/hydration/compiled/server/html.js +1 -9
  39. package/tests/hydration/compiled/server/reactivity.js +2 -34
@@ -27,42 +27,46 @@ import { array_from, is_array } from '@tsrx/core/runtime/language-helpers';
27
27
  * @returns {Block}
28
28
  */
29
29
  function create_item(anchor, value, index, render_fn, is_indexed, is_keyed) {
30
- var b = branch(() => {
31
- var tracked_index;
32
- /** @type {V | Tracked} */
33
- var tracked_value = value;
30
+ var block = /** @type {Block} */ (active_block);
31
+ var tracked_index = is_indexed ? tracked(index, block) : undefined;
32
+ var tracked_value = is_keyed ? tracked(value, block) : value;
33
+ var state = {
34
+ start: null,
35
+ end: null,
36
+ i: tracked_index,
37
+ v: tracked_value,
38
+ };
39
+
40
+ // Passed through module state rather than a per-item closure; run_item
41
+ // reads them before rendering, so nested loops cannot observe a stale pair.
42
+ item_anchor = anchor;
43
+ item_render_fn = render_fn;
44
+ var b = branch(run_item, 0, state);
45
+
46
+ // The item's tracked value and index are owned by the item block itself.
47
+ if (is_keyed) {
48
+ /** @type {Tracked} */ (tracked_value).b = b;
49
+ }
50
+ if (is_indexed) {
51
+ /** @type {Tracked} */ (tracked_index).b = b;
52
+ }
53
+ return b;
54
+ }
34
55
 
35
- if (is_indexed || is_keyed) {
36
- var block = /** @type {Block} */ (active_block);
56
+ /** @type {Node | null} */
57
+ var item_anchor = null;
58
+ /** @type {((anchor: Node, value: any, index?: any) => Block) | null} */
59
+ var item_render_fn = null;
37
60
 
38
- if (block.s === null) {
39
- if (is_indexed) {
40
- tracked_index = tracked(index, block);
41
- }
42
- if (is_keyed) {
43
- tracked_value = tracked(value, block);
44
- }
45
-
46
- block.s = {
47
- start: null,
48
- end: null,
49
- i: tracked_index,
50
- v: tracked_value,
51
- };
52
- } else {
53
- if (is_indexed) {
54
- tracked_index = block.s.i;
55
- }
56
- if (is_keyed) {
57
- tracked_index = block.s.v;
58
- }
59
- }
60
- render_fn(anchor, tracked_value, tracked_index);
61
- } else {
62
- render_fn(anchor, tracked_value);
63
- }
64
- });
65
- return b;
61
+ /**
62
+ * @param {{ i: Tracked | undefined, v: any }} state
63
+ */
64
+ function run_item(state) {
65
+ var render_fn = /** @type {(anchor: Node, value: any, index?: any) => Block} */ (item_render_fn);
66
+ var anchor = /** @type {Node} */ (item_anchor);
67
+ item_render_fn = null;
68
+ item_anchor = null;
69
+ render_fn(anchor, state.v, state.i);
66
70
  }
67
71
 
68
72
  /**
@@ -316,7 +320,10 @@ function update_index(block, index) {
316
320
  * @returns {void}
317
321
  */
318
322
  function update_value(block, value) {
319
- set(block.s.v, value);
323
+ var tracked_value = block.s.v;
324
+ if (tracked_value.__v !== value) {
325
+ set(tracked_value, value);
326
+ }
320
327
  }
321
328
 
322
329
  /**
@@ -346,10 +353,6 @@ function reconcile_by_key(
346
353
 
347
354
  // Variables used in conditional branches - declare with initial values
348
355
  /** @type {number} */
349
- var a_start = 0;
350
- /** @type {number} */
351
- var b_start = 0;
352
- /** @type {number} */
353
356
  var a_left = 0;
354
357
  /** @type {number} */
355
358
  var b_left = 0;
@@ -425,39 +428,31 @@ function reconcile_by_key(
425
428
 
426
429
  var a_blocks = state.blocks;
427
430
  var a_keys = state.keys;
428
- var a_val = a[j];
429
- var b_val = b[j];
430
- var a_key = a_keys[j];
431
- var b_key = b_keys[j];
431
+ var a_start = 0;
432
+ var b_start = 0;
432
433
  var a_end = a_length - 1;
433
434
  var b_end = b_length - 1;
435
+ var b_val;
434
436
  var b_block;
435
437
 
436
- outer: {
437
- while (a_key === b_key) {
438
- a[j] = b_val;
439
- b_block = b_blocks[j] = a_blocks[j];
438
+ // Match from both ends first, including the two end-crossing cases: an old
439
+ // item that moved to the far end of the new list, and a run whose ends were
440
+ // exchanged (a reversal, or a swap of two items). Those complete with plain
441
+ // moves; only what is left afterwards needs the map and LIS below.
442
+ while (a_start <= a_end && b_start <= b_end) {
443
+ if (a_keys[a_start] === b_keys[b_start]) {
444
+ b_val = b[b_start];
445
+ b_block = b_blocks[b_start] = a_blocks[a_start];
440
446
  if (is_indexed) {
441
- update_index(b_block, j);
447
+ update_index(b_block, b_start);
442
448
  }
443
449
  update_value(b_block, b_val);
444
- ++j;
445
- if (j > a_end || j > b_end) {
446
- break outer;
447
- }
448
- a_val = a[j];
449
- b_val = b[j];
450
- a_key = a_keys[j];
451
- b_key = b_keys[j];
450
+ a_start++;
451
+ b_start++;
452
+ continue;
452
453
  }
453
-
454
- a_val = a[a_end];
455
- b_val = b[b_end];
456
- a_key = a_keys[a_end];
457
- b_key = b_keys[b_end];
458
-
459
- while (a_key === b_key) {
460
- a[a_end] = b_val;
454
+ if (a_keys[a_end] === b_keys[b_end]) {
455
+ b_val = b[b_end];
461
456
  b_block = b_blocks[b_end] = a_blocks[a_end];
462
457
  if (is_indexed) {
463
458
  update_index(b_block, b_end);
@@ -465,36 +460,64 @@ function reconcile_by_key(
465
460
  update_value(b_block, b_val);
466
461
  a_end--;
467
462
  b_end--;
468
- if (j > a_end || j > b_end) {
469
- break outer;
463
+ continue;
464
+ }
465
+ if (a_start === a_end || a_blocks[a_start].s.start === null) {
466
+ break;
467
+ }
468
+ if (a_keys[a_end] === b_keys[b_start]) {
469
+ // Last old item is the next new one: move it in front of the old run.
470
+ b_val = b[b_start];
471
+ b_block = b_blocks[b_start] = a_blocks[a_end];
472
+ if (is_indexed) {
473
+ update_index(b_block, b_start);
470
474
  }
471
- a_val = a[a_end];
475
+ update_value(b_block, b_val);
476
+ move(b_block, /** @type {ChildNode} */ (a_blocks[a_start].s.start));
477
+ a_end--;
478
+ b_start++;
479
+ continue;
480
+ }
481
+ if (a_keys[a_start] === b_keys[b_end]) {
482
+ // First old item is the last new one: move it behind the old run.
472
483
  b_val = b[b_end];
473
- a_key = a_keys[a_end];
474
- b_key = b_keys[b_end];
484
+ b_block = b_blocks[b_end] = a_blocks[a_start];
485
+ if (is_indexed) {
486
+ update_index(b_block, b_end);
487
+ }
488
+ update_value(b_block, b_val);
489
+ move(b_block, block_start(b_blocks, b_end + 1, b_length, anchor));
490
+ a_start++;
491
+ b_end--;
492
+ continue;
475
493
  }
494
+ break;
476
495
  }
477
496
 
478
497
  var fast_path_removal = false;
479
498
 
480
- if (j > a_end) {
481
- if (j <= b_end) {
482
- var insert_target = block_start(a_blocks, a_end + 1, a_length, anchor);
483
- while (j <= b_end) {
484
- b_val = b[j];
485
- b_blocks[j] = create_item(insert_target, b_val, j, render_fn, is_indexed, true);
486
- j++;
499
+ if (a_start > a_end) {
500
+ if (b_start <= b_end) {
501
+ var target_node = block_start(b_blocks, b_end + 1, b_length, anchor);
502
+ while (b_start <= b_end) {
503
+ b_blocks[b_start] = create_item(
504
+ target_node,
505
+ b[b_start],
506
+ b_start,
507
+ render_fn,
508
+ is_indexed,
509
+ true,
510
+ );
511
+ b_start++;
487
512
  }
488
513
  }
489
- } else if (j > b_end) {
490
- while (j <= a_end) {
491
- destroy_block(a_blocks[j++]);
514
+ } else if (b_start > b_end) {
515
+ while (a_start <= a_end) {
516
+ destroy_block(a_blocks[a_start++]);
492
517
  }
493
518
  } else {
494
- a_start = j;
495
- b_start = j;
496
- a_left = a_end - j + 1;
497
- b_left = b_end - j + 1;
519
+ a_left = a_end - a_start + 1;
520
+ b_left = b_end - b_start + 1;
498
521
  sources = new Int32Array(b_left + 1);
499
522
  moved = false;
500
523
  pos = 0;
@@ -506,13 +529,9 @@ function reconcile_by_key(
506
529
  // When sizes are small, just loop them through
507
530
  if (b_length < 4 || (a_left | b_left) < 32) {
508
531
  for (i = a_start; i <= a_end; ++i) {
509
- a_val = a[i];
510
- a_key = a_keys[i];
511
532
  if (patched < b_left) {
512
533
  for (j = b_start; j <= b_end; j++) {
513
- b_val = b[j];
514
- b_key = b_keys[j];
515
- if (a_key === b_key) {
534
+ if (a_keys[i] === b_keys[j]) {
516
535
  sources[j - b_start] = i + 1;
517
536
  if (fast_path_removal) {
518
537
  fast_path_removal = false;
@@ -525,6 +544,7 @@ function reconcile_by_key(
525
544
  } else {
526
545
  pos = j;
527
546
  }
547
+ b_val = b[j];
528
548
  b_block = b_blocks[j] = a_blocks[i];
529
549
  if (is_indexed) {
530
550
  update_index(b_block, j);
@@ -549,11 +569,8 @@ function reconcile_by_key(
549
569
  }
550
570
 
551
571
  for (i = a_start; i <= a_end; ++i) {
552
- a_val = a[i];
553
- a_key = a_keys[i];
554
-
555
572
  if (patched < b_left) {
556
- j = map.get(a_key);
573
+ j = map.get(a_keys[i]);
557
574
 
558
575
  if (j !== undefined) {
559
576
  if (fast_path_removal) {
@@ -568,12 +585,12 @@ function reconcile_by_key(
568
585
  } else {
569
586
  pos = j;
570
587
  }
571
- block = b_blocks[j] = a_blocks[i];
572
588
  b_val = b[j];
589
+ b_block = b_blocks[j] = a_blocks[i];
573
590
  if (is_indexed) {
574
- update_index(block, j);
591
+ update_index(b_block, j);
575
592
  }
576
- update_value(block, b_val);
593
+ update_value(b_block, b_val);
577
594
  ++patched;
578
595
  } else if (!fast_path_removal) {
579
596
  destroy_block(a_blocks[i]);
@@ -594,6 +611,25 @@ function reconcile_by_key(
594
611
  var seq = lis_algorithm(sources);
595
612
  j = seq.length - 1;
596
613
 
614
+ // When most surviving items have to move anyway, re-lay the whole range
615
+ // in order before a fixed target: inserting before the same node is
616
+ // noticeably cheaper per item than moving into arbitrary positions.
617
+ if ((patched - seq.length) * 3 > b_left * 2) {
618
+ var relay_target = block_start(b_blocks, b_end + 1, b_length, anchor);
619
+ for (i = 0; i < b_left; i++) {
620
+ pos = i + b_start;
621
+ if (sources[i] === 0) {
622
+ b_blocks[pos] = create_item(relay_target, b[pos], pos, render_fn, is_indexed, true);
623
+ } else {
624
+ move(b_blocks[pos], relay_target);
625
+ }
626
+ }
627
+ state.array = b;
628
+ state.blocks = b_blocks;
629
+ state.keys = b_keys;
630
+ return;
631
+ }
632
+
597
633
  for (i = b_left - 1; i >= 0; i--) {
598
634
  if (sources[i] === 0) {
599
635
  pos = i + b_start;
@@ -604,7 +640,6 @@ function reconcile_by_key(
604
640
  b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed, true);
605
641
  } else if (j < 0 || i !== seq[j]) {
606
642
  pos = i + b_start;
607
- b_val = b[pos];
608
643
  next_pos = pos + 1;
609
644
 
610
645
  var target = block_start(b_blocks, next_pos, b_length, anchor);
@@ -647,10 +682,6 @@ function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed
647
682
 
648
683
  // Variables used in conditional branches - declare with initial values
649
684
  /** @type {number} */
650
- var a_start = 0;
651
- /** @type {number} */
652
- var b_start = 0;
653
- /** @type {number} */
654
685
  var a_left = 0;
655
686
  /** @type {number} */
656
687
  var b_left = 0;
@@ -722,66 +753,92 @@ function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed
722
753
  }
723
754
 
724
755
  var a_blocks = state.blocks;
725
- var a_val = a[j];
726
- var b_val = b[j];
756
+ var a_start = 0;
757
+ var b_start = 0;
727
758
  var a_end = a_length - 1;
728
759
  var b_end = b_length - 1;
760
+ var b_val;
729
761
  var b_block;
730
762
 
731
- outer: {
732
- while (a_val === b_val) {
733
- a[j] = b_val;
734
- b_block = b_blocks[j] = a_blocks[j];
763
+ // Match from both ends first, including the two end-crossing cases: an old
764
+ // item that moved to the far end of the new list, and a run whose ends were
765
+ // exchanged (a reversal, or a swap of two items). Those complete with plain
766
+ // moves; only what is left afterwards needs the map and LIS below.
767
+ while (a_start <= a_end && b_start <= b_end) {
768
+ if (a[a_start] === b[b_start]) {
769
+ b_val = b[b_start];
770
+ b_block = b_blocks[b_start] = a_blocks[a_start];
735
771
  if (is_indexed) {
736
- update_index(b_block, j);
737
- }
738
- ++j;
739
- if (j > a_end || j > b_end) {
740
- break outer;
772
+ update_index(b_block, b_start);
741
773
  }
742
- a_val = a[j];
743
- b_val = b[j];
774
+ a_start++;
775
+ b_start++;
776
+ continue;
744
777
  }
745
-
746
- a_val = a[a_end];
747
- b_val = b[b_end];
748
-
749
- while (a_val === b_val) {
750
- a[a_end] = b_val;
778
+ if (a[a_end] === b[b_end]) {
779
+ b_val = b[b_end];
751
780
  b_block = b_blocks[b_end] = a_blocks[a_end];
752
781
  if (is_indexed) {
753
782
  update_index(b_block, b_end);
754
783
  }
755
784
  a_end--;
756
785
  b_end--;
757
- if (j > a_end || j > b_end) {
758
- break outer;
786
+ continue;
787
+ }
788
+ if (a_start === a_end || a_blocks[a_start].s.start === null) {
789
+ break;
790
+ }
791
+ if (a[a_end] === b[b_start]) {
792
+ // Last old item is the next new one: move it in front of the old run.
793
+ b_val = b[b_start];
794
+ b_block = b_blocks[b_start] = a_blocks[a_end];
795
+ if (is_indexed) {
796
+ update_index(b_block, b_start);
759
797
  }
760
- a_val = a[a_end];
798
+ move(b_block, /** @type {ChildNode} */ (a_blocks[a_start].s.start));
799
+ a_end--;
800
+ b_start++;
801
+ continue;
802
+ }
803
+ if (a[a_start] === b[b_end]) {
804
+ // First old item is the last new one: move it behind the old run.
761
805
  b_val = b[b_end];
806
+ b_block = b_blocks[b_end] = a_blocks[a_start];
807
+ if (is_indexed) {
808
+ update_index(b_block, b_end);
809
+ }
810
+ move(b_block, block_start(b_blocks, b_end + 1, b_length, anchor));
811
+ a_start++;
812
+ b_end--;
813
+ continue;
762
814
  }
815
+ break;
763
816
  }
764
817
 
765
818
  var fast_path_removal = false;
766
819
 
767
- if (j > a_end) {
768
- if (j <= b_end) {
769
- var insert_target = block_start(a_blocks, a_end + 1, a_length, anchor);
770
- while (j <= b_end) {
771
- b_val = b[j];
772
- b_blocks[j] = create_item(insert_target, b_val, j, render_fn, is_indexed, false);
773
- j++;
820
+ if (a_start > a_end) {
821
+ if (b_start <= b_end) {
822
+ var target_node = block_start(b_blocks, b_end + 1, b_length, anchor);
823
+ while (b_start <= b_end) {
824
+ b_blocks[b_start] = create_item(
825
+ target_node,
826
+ b[b_start],
827
+ b_start,
828
+ render_fn,
829
+ is_indexed,
830
+ false,
831
+ );
832
+ b_start++;
774
833
  }
775
834
  }
776
- } else if (j > b_end) {
777
- while (j <= a_end) {
778
- destroy_block(a_blocks[j++]);
835
+ } else if (b_start > b_end) {
836
+ while (a_start <= a_end) {
837
+ destroy_block(a_blocks[a_start++]);
779
838
  }
780
839
  } else {
781
- a_start = j;
782
- b_start = j;
783
- a_left = a_end - j + 1;
784
- b_left = b_end - j + 1;
840
+ a_left = a_end - a_start + 1;
841
+ b_left = b_end - b_start + 1;
785
842
  sources = new Int32Array(b_left + 1);
786
843
  moved = false;
787
844
  pos = 0;
@@ -793,11 +850,9 @@ function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed
793
850
  // When sizes are small, just loop them through
794
851
  if (b_length < 4 || (a_left | b_left) < 32) {
795
852
  for (i = a_start; i <= a_end; ++i) {
796
- a_val = a[i];
797
853
  if (patched < b_left) {
798
854
  for (j = b_start; j <= b_end; j++) {
799
- b_val = b[j];
800
- if (a_val === b_val) {
855
+ if (a[i] === b[j]) {
801
856
  sources[j - b_start] = i + 1;
802
857
  if (fast_path_removal) {
803
858
  fast_path_removal = false;
@@ -810,6 +865,7 @@ function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed
810
865
  } else {
811
866
  pos = j;
812
867
  }
868
+ b_val = b[j];
813
869
  b_block = b_blocks[j] = a_blocks[i];
814
870
  if (is_indexed) {
815
871
  update_index(b_block, j);
@@ -833,10 +889,8 @@ function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed
833
889
  }
834
890
 
835
891
  for (i = a_start; i <= a_end; ++i) {
836
- a_val = a[i];
837
-
838
892
  if (patched < b_left) {
839
- j = map.get(a_val);
893
+ j = map.get(a[i]);
840
894
 
841
895
  if (j !== undefined) {
842
896
  if (fast_path_removal) {
@@ -851,9 +905,10 @@ function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed
851
905
  } else {
852
906
  pos = j;
853
907
  }
854
- block = b_blocks[j] = a_blocks[i];
908
+ b_val = b[j];
909
+ b_block = b_blocks[j] = a_blocks[i];
855
910
  if (is_indexed) {
856
- update_index(block, j);
911
+ update_index(b_block, j);
857
912
  }
858
913
  ++patched;
859
914
  } else if (!fast_path_removal) {
@@ -875,6 +930,24 @@ function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed
875
930
  var seq = lis_algorithm(sources);
876
931
  j = seq.length - 1;
877
932
 
933
+ // When most surviving items have to move anyway, re-lay the whole range
934
+ // in order before a fixed target: inserting before the same node is
935
+ // noticeably cheaper per item than moving into arbitrary positions.
936
+ if ((patched - seq.length) * 3 > b_left * 2) {
937
+ var relay_target = block_start(b_blocks, b_end + 1, b_length, anchor);
938
+ for (i = 0; i < b_left; i++) {
939
+ pos = i + b_start;
940
+ if (sources[i] === 0) {
941
+ b_blocks[pos] = create_item(relay_target, b[pos], pos, render_fn, is_indexed, false);
942
+ } else {
943
+ move(b_blocks[pos], relay_target);
944
+ }
945
+ }
946
+ state.array = b;
947
+ state.blocks = b_blocks;
948
+ return;
949
+ }
950
+
878
951
  for (i = b_left - 1; i >= 0; i--) {
879
952
  if (sources[i] === 0) {
880
953
  pos = i + b_start;
@@ -885,7 +958,6 @@ function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed
885
958
  b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed, false);
886
959
  } else if (j < 0 || i !== seq[j]) {
887
960
  pos = i + b_start;
888
- b_val = b[pos];
889
961
  next_pos = pos + 1;
890
962
 
891
963
  var target = block_start(b_blocks, next_pos, b_length, anchor);
@@ -2,6 +2,8 @@ export {
2
2
  first_child as child,
3
3
  first_child_frag,
4
4
  next_sibling as sibling,
5
+ hydrate_first_child as hydrate_child,
6
+ hydrate_next_sibling as hydrate_sibling,
5
7
  append_into,
6
8
  document,
7
9
  create_text,
@@ -90,6 +92,8 @@ export { render_component } from './component.js';
90
92
 
91
93
  export { for_block as for, for_block_keyed as for_keyed } from './for.js';
92
94
 
95
+ export { selector, selector_match } from './selector.js';
96
+
93
97
  export { if_block as if } from './if.js';
94
98
 
95
99
  export { try_block as try, get_pending_boundary } from './try.js';
@@ -134,6 +138,6 @@ export { TRY_BLOCK, HMR } from './constants.js';
134
138
 
135
139
  export { hmr } from './hmr.js';
136
140
 
137
- export { pop, next } from './hydration.js';
141
+ export { pop, next, hydrating } from './hydration.js';
138
142
 
139
143
  export { is_tsrx_element, tsrx_element, normalize_children } from '../../element.js';
@@ -37,6 +37,8 @@ export function init_operations() {
37
37
 
38
38
  // the following assignments improve perf of lookups on DOM nodes
39
39
  element_prototype.__click = undefined;
40
+ element_prototype.__className = undefined;
41
+ Text.prototype.__t = undefined;
40
42
  event_target_prototype.__root = undefined;
41
43
  }
42
44
 
@@ -66,8 +68,16 @@ export function get_last_child(node) {
66
68
  */
67
69
  export function first_child(node, is_text) {
68
70
  if (!hydrating) {
69
- return get_first_child(node);
71
+ return node.firstChild;
70
72
  }
73
+ return hydrate_first_child(is_text);
74
+ }
75
+
76
+ /**
77
+ * @param {boolean} [is_text]
78
+ * @returns {Node | null}
79
+ */
80
+ export function hydrate_first_child(is_text) {
71
81
  var child = get_first_child(/** @type {Node} */ (hydrate_node));
72
82
 
73
83
  // Handles the case where we have `<p>{text}</p>`, where `text` is empty
@@ -144,17 +154,21 @@ export function get_next_sibling(node) {
144
154
  * @returns {Node | null}
145
155
  */
146
156
  export function next_sibling(node, is_text) {
147
- let next_sibling = hydrating ? hydrate_node : node;
148
- var last_sibling;
149
-
150
- next_sibling = /** @type {ChildNode | null} */ (
151
- get_next_sibling(/** @type {ChildNode} */ (next_sibling))
152
- );
153
- last_sibling = next_sibling;
154
-
155
157
  if (!hydrating) {
156
- return next_sibling;
158
+ return node.nextSibling;
157
159
  }
160
+ return hydrate_next_sibling(is_text);
161
+ }
162
+
163
+ /**
164
+ * @param {boolean} [is_text]
165
+ * @returns {Node | null}
166
+ */
167
+ export function hydrate_next_sibling(is_text) {
168
+ var next_sibling = /** @type {ChildNode | null} */ (
169
+ get_next_sibling(/** @type {ChildNode} */ (hydrate_node))
170
+ );
171
+ var last_sibling = next_sibling;
158
172
 
159
173
  // if a sibling {expression} is empty during SSR, there might be no
160
174
  // text node to hydrate — we must therefore create one
@@ -12,6 +12,7 @@ import {
12
12
  import { event } from './events.js';
13
13
  import { get_attribute_event_name, is_event_attribute } from '@tsrx/core/runtime/events';
14
14
  import { get } from './runtime.js';
15
+ import { hydrating } from './hydration.js';
15
16
  import { clsx } from 'clsx';
16
17
  import { normalize_css_property_name } from '@tsrx/core/runtime/html';
17
18
 
@@ -23,7 +24,13 @@ import { normalize_css_property_name } from '@tsrx/core/runtime/html';
23
24
  export function set_text(text, value) {
24
25
  // For objects, we apply string coercion
25
26
  var str = value == null ? '' : typeof value === 'object' ? value + '' : value;
26
- if (str !== (text.__t ??= text.nodeValue)) {
27
+ var previous = text.__t;
28
+ // Only server-rendered text can already hold the value; a fresh template
29
+ // text node never does, so skip reading it back.
30
+ if (previous === undefined && hydrating) {
31
+ previous = text.__t = text.nodeValue;
32
+ }
33
+ if (str !== previous) {
27
34
  text.__t = str;
28
35
  text.nodeValue = str + '';
29
36
  }
@@ -168,16 +175,28 @@ function set_attribute_helper(element, key, value, remove_listeners, prev) {
168
175
  export function set_class(dom, value, hash, is_html = true) {
169
176
  var class_value =
170
177
  value == null
171
- ? (hash ?? '')
178
+ ? hash === undefined
179
+ ? null
180
+ : hash
172
181
  : // Fast-path for string values
173
182
  typeof value === 'string'
174
183
  ? value + (hash ? ' ' + hash : '')
175
184
  : clsx([value, hash]);
176
185
 
186
+ // Skip the DOM write when the class we last applied is unchanged, or when
187
+ // an element that never had a class would only receive an empty one (the
188
+ // server omits an empty class attribute as well).
189
+ var previous = dom.__className;
190
+ if (previous === class_value || (previous === undefined && class_value === '')) {
191
+ dom.__className = class_value;
192
+ return;
193
+ }
194
+ dom.__className = class_value;
195
+
177
196
  // Removing the attribute when the value is only an empty string causes
178
197
  // performance issues vs simply making the className an empty string. So
179
198
  // we should only remove the class if the value is nullish.
180
- if (value == null && hash === undefined) {
199
+ if (class_value === null) {
181
200
  dom.removeAttribute('class');
182
201
  } else {
183
202
  if (is_html) {