ripple 0.4.0 → 0.4.2
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/CHANGELOG.md +82 -0
- package/package.json +3 -2
- package/src/constants.js +4 -0
- package/src/jsx-runtime.d.ts +12 -2
- package/src/runtime/internal/client/constants.js +6 -0
- package/src/runtime/internal/client/for.js +224 -50
- package/src/runtime/internal/client/hydration.js +0 -16
- package/src/runtime/internal/client/index.js +2 -2
- package/src/runtime/internal/client/operations.js +1 -2
- package/src/runtime/internal/client/render.js +42 -4
- package/src/runtime/internal/client/runtime.js +45 -2
- package/src/runtime/internal/server/index.js +11 -6
- package/tests/client/basic/basic.attributes.test.tsrx +37 -0
- package/tests/client/compiler/compiler.basic.test.tsrx +2 -2
- package/tests/client/for-item.test.tsrx +404 -0
- package/tests/hydration/compiled/client/basic.js +29 -55
- package/tests/hydration/compiled/client/events.js +80 -67
- package/tests/hydration/compiled/client/for.js +95 -80
- package/tests/hydration/compiled/client/fragment-cursor.js +412 -344
- package/tests/hydration/compiled/client/head.js +4 -7
- package/tests/hydration/compiled/client/html.js +40 -64
- package/tests/hydration/compiled/client/if-children.js +30 -34
- package/tests/hydration/compiled/client/if-fragment-controlflow.js +7 -7
- package/tests/hydration/compiled/client/if.js +4 -4
- package/tests/hydration/compiled/client/mixed-control-flow.js +20 -50
- package/tests/hydration/compiled/client/nested-control-flow.js +97 -185
- package/tests/hydration/compiled/client/portal.js +2 -2
- package/tests/hydration/compiled/client/reactivity.js +17 -31
- package/tests/hydration/compiled/client/streaming.js +4 -8
- package/tests/hydration/compiled/client/typed-text.js +4 -15
- package/tests/hydration/compiled/server/events.js +5 -53
- package/tests/hydration/compiled/server/for.js +1 -9
- package/tests/hydration/compiled/server/fragment-cursor.js +36 -324
- package/tests/server/basic.attributes.test.tsrx +28 -0
- package/tests/server/basic.test.tsrx +23 -0
- package/tests/utils/jsx-runtime-types.test.js +67 -2
- package/tests/utils/tracked-types.test.js +117 -0
- package/types/index.d.ts +7 -8
- package/tests/client/compiler/__snapshots__/compiler.assignments.test.rsrx.snap +0 -12
- package/tests/client/compiler/__snapshots__/compiler.typescript.test.rsrx.snap +0 -46
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/** @import { AppendIntoAnchor, Block, Tracked } from '#client' */
|
|
2
2
|
|
|
3
|
-
import { IS_CONTROLLED, IS_INDEXED, ROOT_CONTROLLED } from '../../../constants.js';
|
|
3
|
+
import { IS_CONTROLLED, IS_INDEXED, LOCAL_ITEMS, ROOT_CONTROLLED } from '../../../constants.js';
|
|
4
4
|
import {
|
|
5
5
|
block as create_block,
|
|
6
|
+
create_block as allocate_block,
|
|
6
7
|
branch,
|
|
7
8
|
destroy_block,
|
|
8
9
|
destroy_block_children,
|
|
@@ -11,11 +12,18 @@ import {
|
|
|
11
12
|
own_anchor,
|
|
12
13
|
render,
|
|
13
14
|
} from './blocks.js';
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
BLOCK_HAS_RUN,
|
|
17
|
+
BRANCH_BLOCK,
|
|
18
|
+
DESTROYED,
|
|
19
|
+
FOR_BLOCK,
|
|
20
|
+
ITEM_BLOCK,
|
|
21
|
+
TRACKED_ARRAY,
|
|
22
|
+
} from './constants.js';
|
|
15
23
|
import { hydrate_first_child, hydrate_next, hydrate_node, hydrating } from './hydration.js';
|
|
16
24
|
import { get_last_child, next_sibling, resolve_anchor } from './operations.js';
|
|
17
25
|
import { append } from './template.js';
|
|
18
|
-
import { active_block, set, set_tracking, tracked } from './runtime.js';
|
|
26
|
+
import { active_block, run_block, run_branch, set, set_tracking, tracked } from './runtime.js';
|
|
19
27
|
import { array_from, is_array } from '@tsrx/core/runtime/language-helpers';
|
|
20
28
|
|
|
21
29
|
/**
|
|
@@ -42,8 +50,21 @@ function create_item(anchor, value, index, render_fn, is_indexed, is_keyed, map_
|
|
|
42
50
|
v: tracked_value,
|
|
43
51
|
// The item as the collection holds it; a keyed diff compares against it.
|
|
44
52
|
r: value,
|
|
53
|
+
// The render state of a body that carries its render block on the item
|
|
54
|
+
// block itself (see `item`), or null.
|
|
55
|
+
p: null,
|
|
45
56
|
};
|
|
46
57
|
|
|
58
|
+
// An item without a tracked of its own (a plain list's, or a local one)
|
|
59
|
+
// renders in place: allocated, linked and run through `run_branch`, with
|
|
60
|
+
// none of the first-run bookkeeping a block that may re-run needs.
|
|
61
|
+
if (tracked_value === value && tracked_index === undefined) {
|
|
62
|
+
var lean = allocate_block(BRANCH_BLOCK, run_item, state);
|
|
63
|
+
run_branch(lean, render_fn, anchor, value);
|
|
64
|
+
lean.f ^= BLOCK_HAS_RUN;
|
|
65
|
+
return lean;
|
|
66
|
+
}
|
|
67
|
+
|
|
47
68
|
// Passed through module state rather than a per-item closure; run_item
|
|
48
69
|
// reads them before rendering, so nested loops cannot observe a stale pair.
|
|
49
70
|
item_anchor = anchor;
|
|
@@ -81,9 +102,19 @@ var item_anchor = null;
|
|
|
81
102
|
var item_render_fn = null;
|
|
82
103
|
|
|
83
104
|
/**
|
|
84
|
-
* @param {{ i: Tracked | undefined, v: any }} state
|
|
105
|
+
* @param {{ i: Tracked | undefined, v: any, p: any }} state
|
|
85
106
|
*/
|
|
86
107
|
function run_item(state) {
|
|
108
|
+
var p = state.p;
|
|
109
|
+
if (p !== null) {
|
|
110
|
+
// A re-run of an item that carries its render block: only the body's
|
|
111
|
+
// update function, tracked (see `item`).
|
|
112
|
+
set_tracking(true);
|
|
113
|
+
/** @type {(prev: any) => void} */ (
|
|
114
|
+
/** @type {ListState} */ (/** @type {Block} */ (/** @type {Block} */ (active_block).p).s).u
|
|
115
|
+
)(p);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
87
118
|
var render_fn = /** @type {(anchor: Node, value: any, index?: any) => Block} */ (item_render_fn);
|
|
88
119
|
var anchor = /** @type {Node} */ (item_anchor);
|
|
89
120
|
item_render_fn = null;
|
|
@@ -91,6 +122,27 @@ function run_item(state) {
|
|
|
91
122
|
render_fn(anchor, state.v, state.i);
|
|
92
123
|
}
|
|
93
124
|
|
|
125
|
+
/**
|
|
126
|
+
* The compiled body of a list item whose updates all live in one render block
|
|
127
|
+
* calls this where it would have created that block: the item block carries
|
|
128
|
+
* the render block instead. The update function is the list's (passed to
|
|
129
|
+
* `for`/`for_keyed`, the same for every item) and `prev` its state; it runs
|
|
130
|
+
* tracked here, so its dependencies are the item block's own, and a change
|
|
131
|
+
* re-runs it alone through `run_item`. One block, one run, and one state
|
|
132
|
+
* object less per item.
|
|
133
|
+
* @param {any} prev
|
|
134
|
+
*/
|
|
135
|
+
export function item(prev) {
|
|
136
|
+
var block = /** @type {Block} */ (active_block);
|
|
137
|
+
block.s.p = prev;
|
|
138
|
+
block.f |= ITEM_BLOCK;
|
|
139
|
+
set_tracking(true);
|
|
140
|
+
/** @type {(prev: any) => void} */ (
|
|
141
|
+
/** @type {ListState} */ (/** @type {Block} */ (block.p).s).u
|
|
142
|
+
)(prev);
|
|
143
|
+
set_tracking(false);
|
|
144
|
+
}
|
|
145
|
+
|
|
94
146
|
/**
|
|
95
147
|
* @param {ListAnchor} anchor
|
|
96
148
|
* @param {(anchor: Node) => void} render_empty
|
|
@@ -236,7 +288,10 @@ class ListState {
|
|
|
236
288
|
* @param {boolean} is_indexed
|
|
237
289
|
* @param {((item: any) => any) | undefined} get_key
|
|
238
290
|
* @param {((anchor: Node) => void) | undefined} render_empty
|
|
239
|
-
* @param {((item: any) => any) | undefined}
|
|
291
|
+
* @param {((item: any) => any) | undefined} map_item
|
|
292
|
+
* @param {((prev: any) => void) | undefined} update_fn the update function
|
|
293
|
+
* of an item body that carries its render block (see `item`)
|
|
294
|
+
* @param {boolean} is_local items are held as they are (`LOCAL_ITEMS`)
|
|
240
295
|
*/
|
|
241
296
|
constructor(
|
|
242
297
|
anchor,
|
|
@@ -247,6 +302,8 @@ class ListState {
|
|
|
247
302
|
get_key,
|
|
248
303
|
render_empty,
|
|
249
304
|
map_item,
|
|
305
|
+
update_fn,
|
|
306
|
+
is_local,
|
|
250
307
|
) {
|
|
251
308
|
this.a = anchor;
|
|
252
309
|
this.g = get_collection;
|
|
@@ -256,6 +313,8 @@ class ListState {
|
|
|
256
313
|
this.k = get_key;
|
|
257
314
|
this.e = render_empty;
|
|
258
315
|
this.m = map_item;
|
|
316
|
+
this.u = update_fn;
|
|
317
|
+
this.l = is_local;
|
|
259
318
|
}
|
|
260
319
|
}
|
|
261
320
|
|
|
@@ -287,7 +346,18 @@ function run_for_keyed(state) {
|
|
|
287
346
|
var array = collection_to_array(state.g());
|
|
288
347
|
|
|
289
348
|
set_tracking(false);
|
|
290
|
-
reconcile_by_key(
|
|
349
|
+
reconcile_by_key(
|
|
350
|
+
state.a,
|
|
351
|
+
block,
|
|
352
|
+
array,
|
|
353
|
+
state.r,
|
|
354
|
+
state.c,
|
|
355
|
+
state.x,
|
|
356
|
+
state.k,
|
|
357
|
+
state.e,
|
|
358
|
+
state.m,
|
|
359
|
+
state.l,
|
|
360
|
+
);
|
|
291
361
|
set_tracking(true);
|
|
292
362
|
|
|
293
363
|
if (hydrating) {
|
|
@@ -302,9 +372,11 @@ function run_for_keyed(state) {
|
|
|
302
372
|
* @param {(anchor: Node, value: V | Tracked, index?: any) => Block} render_fn
|
|
303
373
|
* @param {number} flags
|
|
304
374
|
* @param {(anchor: Node) => void} [render_empty]
|
|
375
|
+
* @param {(prev: any) => void} [update_fn] for an item body that carries its
|
|
376
|
+
* render block: its update function (see `item`)
|
|
305
377
|
* @returns {void}
|
|
306
378
|
*/
|
|
307
|
-
export function for_block(node, get_collection, render_fn, flags, render_empty) {
|
|
379
|
+
export function for_block(node, get_collection, render_fn, flags, render_empty, update_fn) {
|
|
308
380
|
var is_controlled = (flags & IS_CONTROLLED) !== 0;
|
|
309
381
|
var is_indexed = (flags & IS_INDEXED) !== 0;
|
|
310
382
|
var root_controlled = (flags & ROOT_CONTROLLED) !== 0;
|
|
@@ -340,6 +412,9 @@ export function for_block(node, get_collection, render_fn, flags, render_empty)
|
|
|
340
412
|
is_indexed,
|
|
341
413
|
undefined,
|
|
342
414
|
render_empty,
|
|
415
|
+
undefined,
|
|
416
|
+
update_fn,
|
|
417
|
+
false,
|
|
343
418
|
),
|
|
344
419
|
FOR_BLOCK,
|
|
345
420
|
);
|
|
@@ -365,6 +440,8 @@ export function for_block(node, get_collection, render_fn, flags, render_empty)
|
|
|
365
440
|
* @param {(item: V) => any} [map_item] for a destructuring pattern with a rest
|
|
366
441
|
* element or a default: destructures the item once, natively, into an object
|
|
367
442
|
* of the pattern's names, which is what the item's tracked then holds
|
|
443
|
+
* @param {(prev: any) => void} [update_fn] for an item body that carries its
|
|
444
|
+
* render block: its update function (see `item`)
|
|
368
445
|
* @returns {void}
|
|
369
446
|
*/
|
|
370
447
|
export function for_block_keyed(
|
|
@@ -375,6 +452,7 @@ export function for_block_keyed(
|
|
|
375
452
|
get_key,
|
|
376
453
|
render_empty,
|
|
377
454
|
map_item,
|
|
455
|
+
update_fn,
|
|
378
456
|
) {
|
|
379
457
|
var is_controlled = (flags & IS_CONTROLLED) !== 0;
|
|
380
458
|
var is_indexed = (flags & IS_INDEXED) !== 0;
|
|
@@ -412,6 +490,8 @@ export function for_block_keyed(
|
|
|
412
490
|
get_key,
|
|
413
491
|
render_empty,
|
|
414
492
|
map_item,
|
|
493
|
+
update_fn,
|
|
494
|
+
(flags & LOCAL_ITEMS) !== 0,
|
|
415
495
|
),
|
|
416
496
|
FOR_BLOCK,
|
|
417
497
|
);
|
|
@@ -459,13 +539,23 @@ function update_index(block, index) {
|
|
|
459
539
|
* @param {Block} block
|
|
460
540
|
* @param {any} value
|
|
461
541
|
* @param {((item: any) => any) | undefined} map_item
|
|
542
|
+
* @param {boolean} is_local
|
|
462
543
|
* @returns {void}
|
|
463
544
|
*/
|
|
464
|
-
function update_value(block, value, map_item) {
|
|
545
|
+
function update_value(block, value, map_item, is_local) {
|
|
465
546
|
var state = block.s;
|
|
466
547
|
if (state.r !== value) {
|
|
467
548
|
state.r = value;
|
|
468
|
-
|
|
549
|
+
var next = map_item === undefined ? value : map_item(value);
|
|
550
|
+
if (is_local) {
|
|
551
|
+
// Held as it is, read by the item block's update function alone
|
|
552
|
+
// (`LOCAL_ITEMS`) off its state's `$item` slot: the block re-runs here.
|
|
553
|
+
state.v = next;
|
|
554
|
+
state.p.$item = next;
|
|
555
|
+
run_block(block);
|
|
556
|
+
} else {
|
|
557
|
+
set(state.v, next);
|
|
558
|
+
}
|
|
469
559
|
}
|
|
470
560
|
}
|
|
471
561
|
|
|
@@ -481,6 +571,7 @@ function update_value(block, value, map_item) {
|
|
|
481
571
|
* @param {((item: V) => K) | undefined} get_key identity keys when omitted
|
|
482
572
|
* @param {((anchor: Node) => void) | undefined} render_empty
|
|
483
573
|
* @param {((item: V) => any) | undefined} map_item
|
|
574
|
+
* @param {boolean} is_local items are held as they are (`LOCAL_ITEMS`)
|
|
484
575
|
* @returns {void}
|
|
485
576
|
*
|
|
486
577
|
* The first run only creates items, so it lives in this small function and
|
|
@@ -496,6 +587,7 @@ function reconcile_by_key(
|
|
|
496
587
|
get_key,
|
|
497
588
|
render_empty,
|
|
498
589
|
map_item,
|
|
590
|
+
is_local,
|
|
499
591
|
) {
|
|
500
592
|
var b_length = b.length;
|
|
501
593
|
var state = /** @type {ListState} */ (block.s);
|
|
@@ -512,7 +604,7 @@ function reconcile_by_key(
|
|
|
512
604
|
if (get_key !== undefined) {
|
|
513
605
|
b_keys[j] = get_key(value);
|
|
514
606
|
}
|
|
515
|
-
b_blocks[j] = create_item(anchor, value, j, render_fn, is_indexed,
|
|
607
|
+
b_blocks[j] = create_item(anchor, value, j, render_fn, is_indexed, !is_local, map_item);
|
|
516
608
|
}
|
|
517
609
|
|
|
518
610
|
state.array = b;
|
|
@@ -531,6 +623,7 @@ function reconcile_by_key(
|
|
|
531
623
|
get_key,
|
|
532
624
|
render_empty,
|
|
533
625
|
map_item,
|
|
626
|
+
is_local,
|
|
534
627
|
);
|
|
535
628
|
}
|
|
536
629
|
|
|
@@ -547,6 +640,7 @@ function reconcile_by_key(
|
|
|
547
640
|
* @param {((item: V) => K) | undefined} get_key
|
|
548
641
|
* @param {((anchor: Node) => void) | undefined} render_empty
|
|
549
642
|
* @param {((item: V) => any) | undefined} map_item
|
|
643
|
+
* @param {boolean} is_local items are held as they are (`LOCAL_ITEMS`)
|
|
550
644
|
* @returns {void}
|
|
551
645
|
*/
|
|
552
646
|
function reconcile_by_key_diff(
|
|
@@ -559,7 +653,9 @@ function reconcile_by_key_diff(
|
|
|
559
653
|
get_key,
|
|
560
654
|
render_empty,
|
|
561
655
|
map_item,
|
|
656
|
+
is_local,
|
|
562
657
|
) {
|
|
658
|
+
var is_keyed = !is_local;
|
|
563
659
|
var state = /** @type {ListState} */ (block.s);
|
|
564
660
|
|
|
565
661
|
// Variables used in conditional branches - declare with initial values
|
|
@@ -627,7 +723,7 @@ function reconcile_by_key_diff(
|
|
|
627
723
|
// Fast-path for create
|
|
628
724
|
if (a_length === 0) {
|
|
629
725
|
for (; j < b_length; j++) {
|
|
630
|
-
b_blocks[j] = create_item(anchor, b[j], j, render_fn, is_indexed,
|
|
726
|
+
b_blocks[j] = create_item(anchor, b[j], j, render_fn, is_indexed, is_keyed, map_item);
|
|
631
727
|
}
|
|
632
728
|
state.array = b;
|
|
633
729
|
state.blocks = b_blocks;
|
|
@@ -675,7 +771,7 @@ function reconcile_by_key_diff(
|
|
|
675
771
|
update_index(b_block, b_start);
|
|
676
772
|
}
|
|
677
773
|
if (get_key !== undefined) {
|
|
678
|
-
update_value(b_block, b_val, map_item);
|
|
774
|
+
update_value(b_block, b_val, map_item, is_local);
|
|
679
775
|
}
|
|
680
776
|
a_start++;
|
|
681
777
|
b_start++;
|
|
@@ -688,7 +784,7 @@ function reconcile_by_key_diff(
|
|
|
688
784
|
update_index(b_block, b_end);
|
|
689
785
|
}
|
|
690
786
|
if (get_key !== undefined) {
|
|
691
|
-
update_value(b_block, b_val, map_item);
|
|
787
|
+
update_value(b_block, b_val, map_item, is_local);
|
|
692
788
|
}
|
|
693
789
|
a_end--;
|
|
694
790
|
b_end--;
|
|
@@ -710,7 +806,7 @@ function reconcile_by_key_diff(
|
|
|
710
806
|
update_index(b_block, b_start);
|
|
711
807
|
}
|
|
712
808
|
if (get_key !== undefined) {
|
|
713
|
-
update_value(b_block, b_val, map_item);
|
|
809
|
+
update_value(b_block, b_val, map_item, is_local);
|
|
714
810
|
}
|
|
715
811
|
move(b_block, /** @type {ChildNode} */ (a_blocks[a_start].s.start));
|
|
716
812
|
a_end--;
|
|
@@ -725,7 +821,7 @@ function reconcile_by_key_diff(
|
|
|
725
821
|
update_index(b_block, b_end);
|
|
726
822
|
}
|
|
727
823
|
if (get_key !== undefined) {
|
|
728
|
-
update_value(b_block, b_val, map_item);
|
|
824
|
+
update_value(b_block, b_val, map_item, is_local);
|
|
729
825
|
}
|
|
730
826
|
move(b_block, block_start(b_blocks, b_end + 1, b_length, anchor));
|
|
731
827
|
a_start++;
|
|
@@ -747,7 +843,7 @@ function reconcile_by_key_diff(
|
|
|
747
843
|
b_start,
|
|
748
844
|
render_fn,
|
|
749
845
|
is_indexed,
|
|
750
|
-
|
|
846
|
+
is_keyed,
|
|
751
847
|
map_item,
|
|
752
848
|
);
|
|
753
849
|
b_start++;
|
|
@@ -796,7 +892,7 @@ function reconcile_by_key_diff(
|
|
|
796
892
|
update_index(b_block, j);
|
|
797
893
|
}
|
|
798
894
|
if (get_key !== undefined) {
|
|
799
|
-
update_value(b_block, b_val, map_item);
|
|
895
|
+
update_value(b_block, b_val, map_item, is_local);
|
|
800
896
|
}
|
|
801
897
|
++patched;
|
|
802
898
|
break;
|
|
@@ -839,7 +935,7 @@ function reconcile_by_key_diff(
|
|
|
839
935
|
update_index(b_block, j);
|
|
840
936
|
}
|
|
841
937
|
if (get_key !== undefined) {
|
|
842
|
-
update_value(b_block, b_val, map_item);
|
|
938
|
+
update_value(b_block, b_val, map_item, is_local);
|
|
843
939
|
}
|
|
844
940
|
++patched;
|
|
845
941
|
} else if (!fast_path_removal) {
|
|
@@ -864,6 +960,7 @@ function reconcile_by_key_diff(
|
|
|
864
960
|
get_key,
|
|
865
961
|
render_empty,
|
|
866
962
|
map_item,
|
|
963
|
+
is_local,
|
|
867
964
|
);
|
|
868
965
|
return;
|
|
869
966
|
} else if (moved) {
|
|
@@ -872,26 +969,23 @@ function reconcile_by_key_diff(
|
|
|
872
969
|
j = seq.length - 1;
|
|
873
970
|
|
|
874
971
|
// When most surviving items have to move anyway, re-lay the whole range
|
|
875
|
-
// in order
|
|
876
|
-
// noticeably cheaper per item than moving into arbitrary positions.
|
|
972
|
+
// in order (see `relay`).
|
|
877
973
|
if ((patched - seq.length) * 3 > b_left * 2) {
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
}
|
|
894
|
-
}
|
|
974
|
+
relay(
|
|
975
|
+
anchor,
|
|
976
|
+
a_blocks,
|
|
977
|
+
a_start,
|
|
978
|
+
a_end,
|
|
979
|
+
b,
|
|
980
|
+
b_blocks,
|
|
981
|
+
b_start,
|
|
982
|
+
b_end,
|
|
983
|
+
sources,
|
|
984
|
+
render_fn,
|
|
985
|
+
is_indexed,
|
|
986
|
+
is_keyed,
|
|
987
|
+
map_item,
|
|
988
|
+
);
|
|
895
989
|
state.array = b;
|
|
896
990
|
state.blocks = b_blocks;
|
|
897
991
|
state.keys = b_keys;
|
|
@@ -905,7 +999,7 @@ function reconcile_by_key_diff(
|
|
|
905
999
|
next_pos = pos + 1;
|
|
906
1000
|
|
|
907
1001
|
var target = block_start(b_blocks, next_pos, b_length, anchor);
|
|
908
|
-
b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed,
|
|
1002
|
+
b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed, is_keyed, map_item);
|
|
909
1003
|
} else if (j < 0 || i !== seq[j]) {
|
|
910
1004
|
pos = i + b_start;
|
|
911
1005
|
next_pos = pos + 1;
|
|
@@ -924,7 +1018,7 @@ function reconcile_by_key_diff(
|
|
|
924
1018
|
next_pos = pos + 1;
|
|
925
1019
|
|
|
926
1020
|
var target = block_start(b_blocks, next_pos, b_length, anchor);
|
|
927
|
-
b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed,
|
|
1021
|
+
b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed, is_keyed, map_item);
|
|
928
1022
|
}
|
|
929
1023
|
}
|
|
930
1024
|
}
|
|
@@ -934,6 +1028,81 @@ function reconcile_by_key_diff(
|
|
|
934
1028
|
state.keys = b_keys;
|
|
935
1029
|
}
|
|
936
1030
|
|
|
1031
|
+
/**
|
|
1032
|
+
* Re-lays the unmatched range `b_start..b_end` in its new order, walking it
|
|
1033
|
+
* front to back: `cursor` is the first node of the range that is not in place
|
|
1034
|
+
* yet, so a block already there costs a compare and a sibling read, and every
|
|
1035
|
+
* other block (or a new item) is inserted before `cursor`. Inserting each item
|
|
1036
|
+
* toward the front is what the reverse fast path does too; appending every
|
|
1037
|
+
* item at the end of the range instead cost about twice as much in Blink's
|
|
1038
|
+
* reorder floor (500 rows: 0.55 ms against 0.29), and several times more
|
|
1039
|
+
* right after a layout flush, so this holds even when most items move.
|
|
1040
|
+
* @template V
|
|
1041
|
+
* @param {ListAnchor} anchor
|
|
1042
|
+
* @param {Block[]} a_blocks the old blocks; destroyed ones are skipped
|
|
1043
|
+
* @param {number} a_start
|
|
1044
|
+
* @param {number} a_end
|
|
1045
|
+
* @param {V[]} b
|
|
1046
|
+
* @param {Block[]} b_blocks matched blocks at their new index, holes for new items
|
|
1047
|
+
* @param {number} b_start
|
|
1048
|
+
* @param {number} b_end
|
|
1049
|
+
* @param {Int32Array} sources zero at the index of a new item
|
|
1050
|
+
* @param {(anchor: Node, value: V | Tracked, index?: any) => Block} render_fn
|
|
1051
|
+
* @param {boolean} is_indexed
|
|
1052
|
+
* @param {boolean} is_keyed
|
|
1053
|
+
* @param {((item: V) => any) | undefined} map_item
|
|
1054
|
+
*/
|
|
1055
|
+
function relay(
|
|
1056
|
+
anchor,
|
|
1057
|
+
a_blocks,
|
|
1058
|
+
a_start,
|
|
1059
|
+
a_end,
|
|
1060
|
+
b,
|
|
1061
|
+
b_blocks,
|
|
1062
|
+
b_start,
|
|
1063
|
+
b_end,
|
|
1064
|
+
sources,
|
|
1065
|
+
render_fn,
|
|
1066
|
+
is_indexed,
|
|
1067
|
+
is_keyed,
|
|
1068
|
+
map_item,
|
|
1069
|
+
) {
|
|
1070
|
+
var b_length = b.length;
|
|
1071
|
+
var end_target = block_start(b_blocks, b_end + 1, b_length, anchor);
|
|
1072
|
+
/** @type {ChildNode | AppendIntoAnchor | null} */
|
|
1073
|
+
var cursor = null;
|
|
1074
|
+
|
|
1075
|
+
// The range starts at the first surviving old block.
|
|
1076
|
+
for (var i = a_start; i <= a_end && cursor === null; i++) {
|
|
1077
|
+
var old_block = a_blocks[i];
|
|
1078
|
+
if ((old_block.f & DESTROYED) === 0) {
|
|
1079
|
+
cursor = /** @type {ChildNode | null} */ (get_first_node(old_block));
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (cursor === null) {
|
|
1083
|
+
cursor = end_target;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
for (var pos = b_start; pos <= b_end; pos++) {
|
|
1087
|
+
if (sources[pos - b_start] === 0) {
|
|
1088
|
+
b_blocks[pos] = create_item(cursor, b[pos], pos, render_fn, is_indexed, is_keyed, map_item);
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
var block = b_blocks[pos];
|
|
1092
|
+
var first = get_first_node(block);
|
|
1093
|
+
if (first === null) {
|
|
1094
|
+
// Renders nothing: it has no place to take.
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
if (first !== cursor) {
|
|
1098
|
+
move(block, cursor);
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
var after = next_sibling(/** @type {Node} */ (get_last_node(block)));
|
|
1102
|
+
cursor = after === null ? end_target : /** @type {ChildNode} */ (after);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
937
1106
|
/**
|
|
938
1107
|
* @template V
|
|
939
1108
|
* @param {ListAnchor} anchor
|
|
@@ -1211,18 +1380,23 @@ function reconcile_by_ref_diff(anchor, block, b, b_blocks, render_fn, is_control
|
|
|
1211
1380
|
j = seq.length - 1;
|
|
1212
1381
|
|
|
1213
1382
|
// When most surviving items have to move anyway, re-lay the whole range
|
|
1214
|
-
// in order
|
|
1215
|
-
// noticeably cheaper per item than moving into arbitrary positions.
|
|
1383
|
+
// in order (see `relay`).
|
|
1216
1384
|
if ((patched - seq.length) * 3 > b_left * 2) {
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1385
|
+
relay(
|
|
1386
|
+
anchor,
|
|
1387
|
+
a_blocks,
|
|
1388
|
+
a_start,
|
|
1389
|
+
a_end,
|
|
1390
|
+
b,
|
|
1391
|
+
b_blocks,
|
|
1392
|
+
b_start,
|
|
1393
|
+
b_end,
|
|
1394
|
+
sources,
|
|
1395
|
+
render_fn,
|
|
1396
|
+
is_indexed,
|
|
1397
|
+
false,
|
|
1398
|
+
undefined,
|
|
1399
|
+
);
|
|
1226
1400
|
state.array = b;
|
|
1227
1401
|
state.blocks = b_blocks;
|
|
1228
1402
|
return;
|
|
@@ -76,22 +76,6 @@ export function hydrate_first_child(is_text) {
|
|
|
76
76
|
return (hydrate_node = child);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
/**
|
|
80
|
-
* Adopts the text node that is an element's only child, leaving the cursor on
|
|
81
|
-
* the element: nothing else inside it navigates, so the parent's sibling
|
|
82
|
-
* traversal continues from the element with no pop().
|
|
83
|
-
* @returns {Node}
|
|
84
|
-
*/
|
|
85
|
-
export function hydrate_text_child() {
|
|
86
|
-
var child = first_child_getter.call(/** @type {Node} */ (hydrate_node));
|
|
87
|
-
|
|
88
|
-
if (child === null || child.nodeType !== TEXT_NODE) {
|
|
89
|
-
child = repair_first_child(child);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return child;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
79
|
/**
|
|
96
80
|
* Handles `<p>{text}</p>` where `text` rendered empty on the server: there is
|
|
97
81
|
* no text node to adopt (or, with siblings, the next node is not one), so one
|
|
@@ -4,7 +4,6 @@ export {
|
|
|
4
4
|
next_sibling as sibling,
|
|
5
5
|
hydrate_first_child as hydrate_child,
|
|
6
6
|
hydrate_next_sibling as hydrate_sibling,
|
|
7
|
-
hydrate_text_child as hydrate_text,
|
|
8
7
|
append_into,
|
|
9
8
|
document,
|
|
10
9
|
create_text,
|
|
@@ -13,6 +12,7 @@ export {
|
|
|
13
12
|
|
|
14
13
|
export {
|
|
15
14
|
set_text,
|
|
15
|
+
set_text_content,
|
|
16
16
|
set_class,
|
|
17
17
|
set_style,
|
|
18
18
|
set_attribute,
|
|
@@ -84,7 +84,7 @@ export { composite } from './composite.js';
|
|
|
84
84
|
|
|
85
85
|
export { render_component } from './component.js';
|
|
86
86
|
|
|
87
|
-
export { for_block as for, for_block_keyed as for_keyed } from './for.js';
|
|
87
|
+
export { for_block as for, for_block_keyed as for_keyed, item } from './for.js';
|
|
88
88
|
|
|
89
89
|
export { selector, selector_match } from './selector.js';
|
|
90
90
|
|
|
@@ -4,13 +4,12 @@ import {
|
|
|
4
4
|
hydrate_first_child,
|
|
5
5
|
hydrate_next_sibling,
|
|
6
6
|
hydrate_node,
|
|
7
|
-
hydrate_text_child,
|
|
8
7
|
hydrating,
|
|
9
8
|
set_hydrate_node,
|
|
10
9
|
} from './hydration.js';
|
|
11
10
|
import { get_descriptor } from '@tsrx/core/runtime/language-helpers';
|
|
12
11
|
|
|
13
|
-
export { hydrate_first_child, hydrate_next_sibling
|
|
12
|
+
export { hydrate_first_child, hydrate_next_sibling };
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* The `firstChild` / `nextSibling` getters, called directly so a traversal
|
|
@@ -13,9 +13,46 @@ import { event_listener } 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
15
|
import { hydrating } from './hydration.js';
|
|
16
|
+
import { TEXT_NODE } from '../../../constants.js';
|
|
16
17
|
import { clsx } from 'clsx';
|
|
17
18
|
import { normalize_css_property_name } from '@tsrx/core/runtime/html';
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Sets the text of an element whose only child is that text. The template
|
|
22
|
+
* leaves such an element empty, so the first write creates the text node and
|
|
23
|
+
* later writes update it in place; server-rendered text is adopted as is.
|
|
24
|
+
* The element has a text node exactly when the value last written did not
|
|
25
|
+
* read as empty (an empty write drops the node again), so `prev`, the value
|
|
26
|
+
* the render block wrote before, says whether one exists without a DOM read.
|
|
27
|
+
* @param {Element} element
|
|
28
|
+
* @param {any} value
|
|
29
|
+
* @param {any} prev the value of the previous write (`''` before the first)
|
|
30
|
+
* @returns {void}
|
|
31
|
+
*/
|
|
32
|
+
export function set_text_content(element, value, prev) {
|
|
33
|
+
var str = value == null ? '' : value + '';
|
|
34
|
+
if (hydrating) {
|
|
35
|
+
var text = element.firstChild;
|
|
36
|
+
if (text === null) {
|
|
37
|
+
if (str !== '') {
|
|
38
|
+
element.textContent = str;
|
|
39
|
+
}
|
|
40
|
+
} else if (text.nodeType !== TEXT_NODE || str === '') {
|
|
41
|
+
element.textContent = str;
|
|
42
|
+
} else if (text.nodeValue !== str) {
|
|
43
|
+
text.nodeValue = str;
|
|
44
|
+
}
|
|
45
|
+
} else if (prev == null || prev === '') {
|
|
46
|
+
if (str !== '') {
|
|
47
|
+
element.textContent = str;
|
|
48
|
+
}
|
|
49
|
+
} else if (str === '') {
|
|
50
|
+
element.textContent = '';
|
|
51
|
+
} else {
|
|
52
|
+
/** @type {Text} */ (element.firstChild).nodeValue = str;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
19
56
|
/**
|
|
20
57
|
* @param {Text} text
|
|
21
58
|
* @param {any} value
|
|
@@ -68,7 +105,7 @@ function get_setters(element) {
|
|
|
68
105
|
/**
|
|
69
106
|
* @param {Element} element
|
|
70
107
|
* @param {any} value
|
|
71
|
-
* @param {Record<string, string> | undefined} prev
|
|
108
|
+
* @param {Record<string, string | number | null | undefined> | undefined} prev
|
|
72
109
|
* @returns {void}
|
|
73
110
|
*/
|
|
74
111
|
export function set_style(element, value, prev = {}) {
|
|
@@ -100,8 +137,8 @@ export function set_attribute(element, attribute, value) {
|
|
|
100
137
|
|
|
101
138
|
/**
|
|
102
139
|
* @param {HTMLElement} element
|
|
103
|
-
* @param {Record<string, string | number>} new_styles
|
|
104
|
-
* @param {Record<string, string>} prev
|
|
140
|
+
* @param {Record<string, string | number | null | undefined>} new_styles
|
|
141
|
+
* @param {Record<string, string | number | null | undefined>} prev
|
|
105
142
|
*/
|
|
106
143
|
function apply_styles(element, new_styles, prev) {
|
|
107
144
|
const style = element.style;
|
|
@@ -109,7 +146,8 @@ function apply_styles(element, new_styles, prev) {
|
|
|
109
146
|
// Apply new styles
|
|
110
147
|
for (const key in new_styles) {
|
|
111
148
|
const css_prop = normalize_css_property_name(key);
|
|
112
|
-
const
|
|
149
|
+
const raw_value = new_styles[key];
|
|
150
|
+
const value = raw_value == null ? null : String(raw_value);
|
|
113
151
|
|
|
114
152
|
if (!(key in prev) || prev[key] !== value) {
|
|
115
153
|
style.setProperty(css_prop, value);
|