ripple 0.4.3 → 0.4.5
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 +81 -0
- package/package.json +3 -3
- package/src/constants.js +6 -0
- package/src/runtime/internal/client/attributes.js +259 -126
- package/src/runtime/internal/client/blocks.js +16 -6
- package/src/runtime/internal/client/composite.js +292 -100
- package/src/runtime/internal/client/constants.js +8 -0
- package/src/runtime/internal/client/events.js +15 -1
- package/src/runtime/internal/client/expression.js +4 -4
- package/src/runtime/internal/client/for.js +13 -38
- package/src/runtime/internal/client/if.js +56 -1
- package/src/runtime/internal/client/index.js +8 -6
- package/src/runtime/internal/client/operations.js +9 -4
- package/src/runtime/internal/client/render.js +37 -0
- package/src/runtime/internal/client/runtime.js +94 -20
- package/src/runtime/internal/client/template-ns.js +45 -4
- package/src/runtime/internal/client/template.js +103 -0
- package/tests/client/composite/composite.dynamic-components.test.tsrx +40 -0
- package/tests/client/css/scoped-styles.test.tsrx +3 -1
- package/tests/client/svg.test.tsrx +273 -1
- package/tests/hydration/compiled/client/basic.js +79 -51
- package/tests/hydration/compiled/client/composite.js +13 -4
- package/tests/hydration/compiled/client/events.js +22 -7
- package/tests/hydration/compiled/client/for.js +110 -71
- package/tests/hydration/compiled/client/fragment-cursor.js +342 -156
- package/tests/hydration/compiled/client/head.js +13 -13
- package/tests/hydration/compiled/client/hmr.js +16 -4
- package/tests/hydration/compiled/client/html-in-template.js +1 -1
- package/tests/hydration/compiled/client/html.js +144 -87
- package/tests/hydration/compiled/client/if-children.js +118 -33
- package/tests/hydration/compiled/client/if-fragment-controlflow.js +41 -21
- package/tests/hydration/compiled/client/if.js +70 -18
- package/tests/hydration/compiled/client/mixed-control-flow.js +50 -20
- package/tests/hydration/compiled/client/nested-control-flow.js +191 -100
- package/tests/hydration/compiled/client/portal.js +29 -9
- package/tests/hydration/compiled/client/reactivity.js +9 -5
- package/tests/hydration/compiled/client/streaming.js +21 -18
- package/tests/hydration/compiled/client/switch.js +17 -17
- package/tests/hydration/compiled/client/track-async-serialization.js +29 -21
- package/tests/hydration/compiled/client/try.js +13 -10
- package/tests/hydration/compiled/client/typed-text.js +3 -2
- package/tests/utils/inline-drift.test.js +83 -0
|
@@ -18,6 +18,8 @@ export {
|
|
|
18
18
|
set_value,
|
|
19
19
|
set_checked,
|
|
20
20
|
set_selected,
|
|
21
|
+
set_attribute_value,
|
|
22
|
+
set_property_value,
|
|
21
23
|
} from './render.js';
|
|
22
24
|
|
|
23
25
|
export {
|
|
@@ -40,7 +42,7 @@ export {
|
|
|
40
42
|
RENDER_ENTRY as $r,
|
|
41
43
|
} from './constants.js';
|
|
42
44
|
export { event, render_event, delegate, listen } from './events.js';
|
|
43
|
-
export { set_style, set_attribute, render_spread } from './attributes.js';
|
|
45
|
+
export { set_style, set_attribute, render_spread, spread } from './attributes.js';
|
|
44
46
|
export { portal } from './portal.js';
|
|
45
47
|
|
|
46
48
|
export {
|
|
@@ -73,7 +75,7 @@ export {
|
|
|
73
75
|
derived,
|
|
74
76
|
tick,
|
|
75
77
|
with_block,
|
|
76
|
-
|
|
78
|
+
set_ns,
|
|
77
79
|
handle_error,
|
|
78
80
|
queue_post_block_flush_callback,
|
|
79
81
|
schedule_update,
|
|
@@ -81,7 +83,7 @@ export {
|
|
|
81
83
|
|
|
82
84
|
export { track_async } from './track-async.js';
|
|
83
85
|
|
|
84
|
-
export { composite } from './composite.js';
|
|
86
|
+
export { composite, dynamic, dynamic_init } from './composite.js';
|
|
85
87
|
|
|
86
88
|
export { render_component } from './component.js';
|
|
87
89
|
|
|
@@ -89,15 +91,15 @@ export { for_block as for, for_block_keyed as for_keyed, item } from './for.js';
|
|
|
89
91
|
|
|
90
92
|
export { selector, selector_match } from './selector.js';
|
|
91
93
|
|
|
92
|
-
export { if_block as if } from './if.js';
|
|
94
|
+
export { if_block as if, if_static, if_update } from './if.js';
|
|
93
95
|
|
|
94
96
|
export { try_block as try, get_pending_boundary } from './try.js';
|
|
95
97
|
|
|
96
98
|
export { if_block as switch } from './if.js';
|
|
97
99
|
|
|
98
|
-
export { template, append, text } from './template.js';
|
|
100
|
+
export { template, template_el, append, text } from './template.js';
|
|
99
101
|
|
|
100
|
-
export { template_ns } from './template-ns.js';
|
|
102
|
+
export { template_ns, with_ns, render_component_ns } from './template-ns.js';
|
|
101
103
|
|
|
102
104
|
export {
|
|
103
105
|
ripple_array,
|
|
@@ -32,9 +32,12 @@ export var is_firefox;
|
|
|
32
32
|
export function init_operations() {
|
|
33
33
|
var node_prototype = Node.prototype;
|
|
34
34
|
var element_prototype = Element.prototype;
|
|
35
|
-
var event_target_prototype = Event.prototype;
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
// Firefox prefers `importNode` for template content (see `template.js`).
|
|
37
|
+
// Its Firefox-only `window` property is the test: the first read of
|
|
38
|
+
// `navigator` creates its wrapper, about 0.1 ms on a fresh page, paid by
|
|
39
|
+
// the first mount.
|
|
40
|
+
is_firefox = 'mozInnerScreenX' in window;
|
|
38
41
|
document = window.document;
|
|
39
42
|
|
|
40
43
|
first_child_getter = /** @type {(() => Node | null)} */ (
|
|
@@ -47,9 +50,11 @@ export function init_operations() {
|
|
|
47
50
|
get_descriptor(node_prototype, 'lastChild')?.get
|
|
48
51
|
);
|
|
49
52
|
|
|
50
|
-
// the
|
|
53
|
+
// Declared on the prototype so a delegated handler lookup on an element
|
|
54
|
+
// stops at Element instead of walking to Object. `Event.prototype.__root`
|
|
55
|
+
// is declared with the first delegated listener (`events.js`): touching
|
|
56
|
+
// `Event.prototype` costs about 0.05 ms on a fresh page.
|
|
51
57
|
element_prototype.__click = undefined;
|
|
52
|
-
event_target_prototype.__root = undefined;
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
/**
|
|
@@ -165,3 +165,40 @@ export function set_selected(element, selected) {
|
|
|
165
165
|
element.removeAttribute('selected');
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Writes an attribute the compiler proved is not a property of its element
|
|
171
|
+
* (`dom-setters.js` in the compiler), so no setter is looked for: `null` and
|
|
172
|
+
* `undefined` remove it.
|
|
173
|
+
* @param {Element} element
|
|
174
|
+
* @param {string} attribute
|
|
175
|
+
* @param {any} value
|
|
176
|
+
* @returns {void}
|
|
177
|
+
*/
|
|
178
|
+
export function set_attribute_value(element, attribute, value) {
|
|
179
|
+
if (value == null) {
|
|
180
|
+
element.removeAttribute(attribute);
|
|
181
|
+
} else {
|
|
182
|
+
element.setAttribute(attribute, value);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Writes a name the compiler proved is a settable property of its element:
|
|
188
|
+
* a string sets the attribute, any other value the property, and `null` or
|
|
189
|
+
* `undefined` remove the attribute, as `set_attribute` decides at runtime
|
|
190
|
+
* after discovering the element's setters.
|
|
191
|
+
* @param {Element} element
|
|
192
|
+
* @param {string} attribute
|
|
193
|
+
* @param {any} value
|
|
194
|
+
* @returns {void}
|
|
195
|
+
*/
|
|
196
|
+
export function set_property_value(element, attribute, value) {
|
|
197
|
+
if (value == null) {
|
|
198
|
+
element.removeAttribute(attribute);
|
|
199
|
+
} else if (typeof value !== 'string') {
|
|
200
|
+
/** @type {any} */ (element)[attribute] = value;
|
|
201
|
+
} else {
|
|
202
|
+
element.setAttribute(attribute, value);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
/** @typedef {DerivedValue} Derived */
|
|
5
5
|
|
|
6
6
|
import { DEV } from 'esm-env';
|
|
7
|
-
import { destroy_block, destroy_non_branch_children
|
|
7
|
+
import { block, destroy_block, destroy_non_branch_children } from './blocks.js';
|
|
8
8
|
import {
|
|
9
9
|
ASYNC_DERIVED_READ_THROWN,
|
|
10
10
|
BLOCK_HAS_RUN,
|
|
@@ -27,10 +27,13 @@ import {
|
|
|
27
27
|
SCHEDULED,
|
|
28
28
|
SELECTOR,
|
|
29
29
|
IF_BLOCK,
|
|
30
|
+
COMPOSITE_BLOCK,
|
|
30
31
|
ITEM_BLOCK,
|
|
31
32
|
RELEASED,
|
|
32
33
|
RENDER_ENTRY,
|
|
33
34
|
CREATES_DERIVEDS,
|
|
35
|
+
NAMESPACE_BLOCK,
|
|
36
|
+
SVG_BLOCK,
|
|
34
37
|
} from './constants.js';
|
|
35
38
|
import { is_ripple_object } from './utils.js';
|
|
36
39
|
import { scope_orphan, set_in_derived, track_orphan, update_depth_exceeded } from './errors.js';
|
|
@@ -367,9 +370,10 @@ function handle_run_error(error, block) {
|
|
|
367
370
|
* @returns {Derived[] | null}
|
|
368
371
|
*/
|
|
369
372
|
function prepare_rerun(block) {
|
|
370
|
-
// A list, an if, and a list item re-run only their own logic;
|
|
371
|
-
// blocks (items, the branch's blocks,
|
|
372
|
-
|
|
373
|
+
// A list, an if, a composite and a list item re-run only their own logic;
|
|
374
|
+
// their child blocks (items, the branch's blocks, a dynamic element's
|
|
375
|
+
// children, an item body's nested blocks) stay.
|
|
376
|
+
if ((block.f & (FOR_BLOCK | IF_BLOCK | ITEM_BLOCK | COMPOSITE_BLOCK)) === 0) {
|
|
373
377
|
destroy_non_branch_children(block);
|
|
374
378
|
}
|
|
375
379
|
run_teardown(block);
|
|
@@ -393,17 +397,18 @@ function register_teardown(block, teardown) {
|
|
|
393
397
|
|
|
394
398
|
/**
|
|
395
399
|
* Runs the body of a branch block that was just created, in place of a first
|
|
396
|
-
* `run_block`: a branch renders untracked
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
* `run_block` handles them.
|
|
400
|
+
* `run_block`: a branch renders untracked, so the only bookkeeping its first
|
|
401
|
+
* run needs is the globals the body reads and the dependencies a nested
|
|
402
|
+
* `item` records on it (a list item re-runs through `run_block`, see
|
|
403
|
+
* `run_item`). Errors are handled as `run_block` handles them.
|
|
400
404
|
* @param {Block} block
|
|
401
405
|
* @param {(anchor: any, value: any, index?: any, key?: any) => void} fn
|
|
402
406
|
* @param {any} anchor
|
|
403
|
-
* @param {any} value
|
|
404
|
-
* @param {any} [
|
|
407
|
+
* @param {any} value a list item's value, or its tracked
|
|
408
|
+
* @param {any} [index] a list item's tracked index (see `create_item`)
|
|
409
|
+
* @param {any} [key] a keyed list item's key
|
|
405
410
|
*/
|
|
406
|
-
export function run_branch(block, fn, anchor, value, key) {
|
|
411
|
+
export function run_branch(block, fn, anchor, value, index, key) {
|
|
407
412
|
var previous_block = active_block;
|
|
408
413
|
var previous_reaction = active_reaction;
|
|
409
414
|
var previous_tracking = tracking;
|
|
@@ -416,7 +421,7 @@ export function run_branch(block, fn, anchor, value, key) {
|
|
|
416
421
|
active_component = block.co;
|
|
417
422
|
tracking = false;
|
|
418
423
|
active_dependency = null;
|
|
419
|
-
fn(anchor, value,
|
|
424
|
+
fn(anchor, value, index, key);
|
|
420
425
|
if (active_dependency !== null) {
|
|
421
426
|
block.d = active_dependency;
|
|
422
427
|
}
|
|
@@ -431,6 +436,43 @@ export function run_branch(block, fn, anchor, value, key) {
|
|
|
431
436
|
}
|
|
432
437
|
}
|
|
433
438
|
|
|
439
|
+
/**
|
|
440
|
+
* Runs `fn(block.s, arg)` as a rerun of `block` driven from outside it: an if
|
|
441
|
+
* whose condition the enclosing render function evaluates renders its branch
|
|
442
|
+
* this way. The function runs under the block, in the block's namespace, and
|
|
443
|
+
* the deriveds the block's previous run created are released afterwards
|
|
444
|
+
* unless still read, as `run_block` does for a rerun. An error propagates to
|
|
445
|
+
* the caller's block.
|
|
446
|
+
* @template S, A
|
|
447
|
+
* @param {Block} block
|
|
448
|
+
* @param {(state: S, arg: A) => void} fn
|
|
449
|
+
* @param {A} arg
|
|
450
|
+
*/
|
|
451
|
+
export function run_in_block(block, fn, arg) {
|
|
452
|
+
var previous_block = active_block;
|
|
453
|
+
var previous_reaction = active_reaction;
|
|
454
|
+
var previous_component = active_component;
|
|
455
|
+
var previous_namespace = active_namespace;
|
|
456
|
+
var previous_deriveds = (block.f & CREATES_DERIVEDS) !== 0 ? take_created_deriveds(block) : null;
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
active_block = block;
|
|
460
|
+
active_reaction = block;
|
|
461
|
+
active_component = block.co;
|
|
462
|
+
var ns = block.f & NAMESPACE_BLOCK;
|
|
463
|
+
active_namespace = ns === 0 ? DEFAULT_NAMESPACE : ns === SVG_BLOCK ? 'svg' : 'mathml';
|
|
464
|
+
fn(/** @type {S} */ (block.s), arg);
|
|
465
|
+
} finally {
|
|
466
|
+
active_block = previous_block;
|
|
467
|
+
active_reaction = previous_reaction;
|
|
468
|
+
active_component = previous_component;
|
|
469
|
+
active_namespace = previous_namespace;
|
|
470
|
+
if (previous_deriveds !== null) {
|
|
471
|
+
keep_deriveds(block, release_deriveds(previous_deriveds));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
434
476
|
/**
|
|
435
477
|
* @param {Block} block
|
|
436
478
|
* @param {boolean} [first_run] true when the block has no children, teardown,
|
|
@@ -442,6 +484,7 @@ export function run_block(block, first_run = false) {
|
|
|
442
484
|
var previous_tracking = tracking;
|
|
443
485
|
var previous_dependency = active_dependency;
|
|
444
486
|
var previous_component = active_component;
|
|
487
|
+
var previous_namespace = active_namespace;
|
|
445
488
|
/** @type {Derived[] | null} */
|
|
446
489
|
var previous_deriveds = null;
|
|
447
490
|
|
|
@@ -449,6 +492,10 @@ export function run_block(block, first_run = false) {
|
|
|
449
492
|
active_block = block;
|
|
450
493
|
active_reaction = block;
|
|
451
494
|
active_component = block.co;
|
|
495
|
+
// The namespace the block was created in (see `create_block`): a rerun
|
|
496
|
+
// from a flush is outside the `with_ns()` call that established it.
|
|
497
|
+
var ns = block.f & NAMESPACE_BLOCK;
|
|
498
|
+
active_namespace = ns === 0 ? DEFAULT_NAMESPACE : ns === SVG_BLOCK ? 'svg' : 'mathml';
|
|
452
499
|
|
|
453
500
|
if (!first_run) {
|
|
454
501
|
previous_deriveds = prepare_rerun(block);
|
|
@@ -478,6 +525,7 @@ export function run_block(block, first_run = false) {
|
|
|
478
525
|
tracking = previous_tracking;
|
|
479
526
|
active_dependency = previous_dependency;
|
|
480
527
|
active_component = previous_component;
|
|
528
|
+
active_namespace = previous_namespace;
|
|
481
529
|
|
|
482
530
|
// The previous run's deriveds, whether this run finished or threw, and
|
|
483
531
|
// after this run's dependencies replaced the old ones (so a derived the
|
|
@@ -748,7 +796,9 @@ export function track_read_only(value, block) {
|
|
|
748
796
|
* @returns {Tracked | Derived}
|
|
749
797
|
*/
|
|
750
798
|
export function track(v, b, hash, get, set) {
|
|
751
|
-
|
|
799
|
+
// is_ripple_object(), inline: track() is on every component's cold path
|
|
800
|
+
// (kept identical by tests/utils/inline-drift.test.js).
|
|
801
|
+
if (typeof v === 'object' && v !== null && typeof v.f === 'number') {
|
|
752
802
|
return v;
|
|
753
803
|
}
|
|
754
804
|
if (b === null) {
|
|
@@ -1078,21 +1128,29 @@ function flush_queue(pending) {
|
|
|
1078
1128
|
blocks.sort(by_block_id);
|
|
1079
1129
|
}
|
|
1080
1130
|
|
|
1081
|
-
var
|
|
1131
|
+
var effect_kinds = 0;
|
|
1132
|
+
var has_render_blocks = false;
|
|
1082
1133
|
|
|
1083
1134
|
for (var i = 0; i < length; i++) {
|
|
1084
1135
|
var block = blocks[i];
|
|
1085
1136
|
var flags = block.f;
|
|
1086
1137
|
block.f = flags & ~SCHEDULED;
|
|
1087
|
-
|
|
1088
|
-
|
|
1138
|
+
var kind = flags & (PRE_EFFECT_BLOCK | EFFECT_BLOCK);
|
|
1139
|
+
if (kind !== 0) {
|
|
1140
|
+
effect_kinds |= kind;
|
|
1141
|
+
} else {
|
|
1142
|
+
has_render_blocks = true;
|
|
1089
1143
|
}
|
|
1090
1144
|
}
|
|
1091
1145
|
|
|
1092
1146
|
// New schedules land in the queue that replaced `pending`, so the array
|
|
1093
|
-
// can be run in place.
|
|
1094
|
-
//
|
|
1095
|
-
|
|
1147
|
+
// can be run in place. A queue mixing kinds needs the three-phase
|
|
1148
|
+
// ordering; one of a single kind (the common flush of render blocks,
|
|
1149
|
+
// the first flush of a mounted app's effects) runs straight through.
|
|
1150
|
+
if (
|
|
1151
|
+
effect_kinds === (PRE_EFFECT_BLOCK | EFFECT_BLOCK) ||
|
|
1152
|
+
(effect_kinds !== 0 && has_render_blocks)
|
|
1153
|
+
) {
|
|
1096
1154
|
run_phases(blocks, length);
|
|
1097
1155
|
} else {
|
|
1098
1156
|
run_phase(blocks);
|
|
@@ -1805,7 +1863,9 @@ function create_deferred_effects(effects) {
|
|
|
1805
1863
|
for (var i = 0; i < length; i += 3) {
|
|
1806
1864
|
active_block = /** @type {Block} */ (effects[i + 1]);
|
|
1807
1865
|
active_reaction = /** @type {Block | Derived | null} */ (effects[i + 2]);
|
|
1808
|
-
effect(
|
|
1866
|
+
// effect(), inline: one function less on every component's cold path
|
|
1867
|
+
// (kept identical by tests/utils/inline-drift.test.js).
|
|
1868
|
+
block(EFFECT_BLOCK, /** @type {Function} */ (effects[i]));
|
|
1809
1869
|
}
|
|
1810
1870
|
active_block = previous_block;
|
|
1811
1871
|
active_reaction = previous_reaction;
|
|
@@ -1827,6 +1887,20 @@ export function with_ns(namespace, fn) {
|
|
|
1827
1887
|
}
|
|
1828
1888
|
}
|
|
1829
1889
|
|
|
1890
|
+
/**
|
|
1891
|
+
* Sets the active namespace and returns the previous one, for setup code that
|
|
1892
|
+
* switches namespace part-way through a render function (the children of a
|
|
1893
|
+
* `foreignObject`), where a `with_ns()` thunk would hide its declarations.
|
|
1894
|
+
* `run_block` restores the namespace after the render function either way.
|
|
1895
|
+
* @param {keyof typeof NAMESPACE_URI} namespace
|
|
1896
|
+
* @returns {keyof typeof NAMESPACE_URI}
|
|
1897
|
+
*/
|
|
1898
|
+
export function set_ns(namespace) {
|
|
1899
|
+
var previous_namespace = active_namespace;
|
|
1900
|
+
active_namespace = namespace;
|
|
1901
|
+
return previous_namespace;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1830
1904
|
/**
|
|
1831
1905
|
* @returns {symbol}
|
|
1832
1906
|
*/
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
/** @import { NAMESPACE_URI } from './constants.js' */
|
|
1
2
|
import { get_first_child } from './operations.js';
|
|
3
|
+
import { render_component, set_ns, with_ns as run_with_ns } from './runtime.js';
|
|
2
4
|
import { set_ns_parser, template } from './template.js';
|
|
3
5
|
|
|
4
6
|
/**
|
|
@@ -26,12 +28,20 @@ function from_namespace(content, ns) {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
31
|
+
* Runs `fn` with `namespace` active: content rendered inside it (children
|
|
32
|
+
* passed into an `<svg>`, a component rendered there, a dynamic element's
|
|
33
|
+
* children) creates its DOM in that namespace, and every block created
|
|
34
|
+
* inside records it (see `create_block`) so its reruns do too. Templates
|
|
35
|
+
* cloned in the namespace parse through `from_namespace`, so it is installed
|
|
36
|
+
* here: the compiler only emits this call for the SVG and MathML namespaces.
|
|
37
|
+
* @template T
|
|
38
|
+
* @param {keyof typeof NAMESPACE_URI} namespace
|
|
39
|
+
* @param {() => T} fn
|
|
40
|
+
* @returns {T}
|
|
32
41
|
*/
|
|
33
|
-
export function
|
|
42
|
+
export function with_ns(namespace, fn) {
|
|
34
43
|
set_ns_parser(from_namespace);
|
|
44
|
+
return run_with_ns(namespace, fn);
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
/**
|
|
@@ -45,3 +55,34 @@ export function template_ns(content, flags, count) {
|
|
|
45
55
|
set_ns_parser(from_namespace);
|
|
46
56
|
return template(content, flags, count);
|
|
47
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Enters `namespace` for the rest of a setup function, as `with_ns` does for
|
|
61
|
+
* a thunk: the compiler emits this where the content that follows is a
|
|
62
|
+
* dynamic element or a component called inside an `<svg>` or `<math>`
|
|
63
|
+
* template. Returns the namespace to restore with `set_ns`.
|
|
64
|
+
* @param {keyof typeof NAMESPACE_URI} namespace
|
|
65
|
+
* @returns {keyof typeof NAMESPACE_URI}
|
|
66
|
+
*/
|
|
67
|
+
export function push_ns(namespace) {
|
|
68
|
+
set_ns_parser(from_namespace);
|
|
69
|
+
return set_ns(namespace);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* `render_component` for a component called inside an `<svg>` or `<math>`
|
|
74
|
+
* template: its content renders in that namespace.
|
|
75
|
+
* @param {keyof typeof NAMESPACE_URI} namespace
|
|
76
|
+
* @param {Function} fn
|
|
77
|
+
* @param {Node | import('#client').AppendIntoAnchor} anchor
|
|
78
|
+
* @param {Record<string, any>} props
|
|
79
|
+
* @returns {void}
|
|
80
|
+
*/
|
|
81
|
+
export function render_component_ns(namespace, fn, anchor, props) {
|
|
82
|
+
var previous = push_ns(namespace);
|
|
83
|
+
try {
|
|
84
|
+
render_component(fn, anchor, props);
|
|
85
|
+
} finally {
|
|
86
|
+
set_ns(previous);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { H, hydrate_node, hydrating } from './hydration.js';
|
|
10
10
|
import { create_text, get_first_child, is_firefox } from './operations.js';
|
|
11
11
|
import { active_block, active_namespace } from './runtime.js';
|
|
12
|
+
import { DEFAULT_NAMESPACE, NAMESPACE_URI } from './constants.js';
|
|
12
13
|
import { HYDRATION } from 'ripple/internal/client/hydration-enabled';
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -142,6 +143,108 @@ export function template(content, flags = 0, count = 1) {
|
|
|
142
143
|
: clone_template(t);
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
/**
|
|
147
|
+
* The document that owns parsed template content: it has no browsing
|
|
148
|
+
* context, so an element created in it does not load its `src` (an image,
|
|
149
|
+
* a video) until a clone is adopted into the page, exactly like a node
|
|
150
|
+
* parsed from a `<template>`.
|
|
151
|
+
* @type {Document | undefined}
|
|
152
|
+
*/
|
|
153
|
+
var inert_document;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* A nested element of a DOM-built template: its tag, attributes and children,
|
|
157
|
+
* trailing absences left out.
|
|
158
|
+
* @typedef {[string, (string[] | null)?, (string | TemplateChildren)?]} TemplateElementSpec
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The children of a DOM-built template element in order: a nested element,
|
|
163
|
+
* a text string, or `null` for a placeholder comment.
|
|
164
|
+
* @typedef {Array<string | null | TemplateElementSpec>} TemplateChildren
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Builds one element of a DOM-built template, in the inert document and
|
|
169
|
+
* the active namespace, as a parsed template would create it.
|
|
170
|
+
* @param {Document} doc
|
|
171
|
+
* @param {keyof typeof NAMESPACE_URI} ns
|
|
172
|
+
* @param {string} tag
|
|
173
|
+
* @param {string[] | null | undefined} attributes
|
|
174
|
+
* @param {string | TemplateChildren | undefined} children
|
|
175
|
+
* @returns {Element}
|
|
176
|
+
*/
|
|
177
|
+
function build_template_element(doc, ns, tag, attributes, children) {
|
|
178
|
+
var node =
|
|
179
|
+
ns === DEFAULT_NAMESPACE ? doc.createElement(tag) : doc.createElementNS(NAMESPACE_URI[ns], tag);
|
|
180
|
+
if (attributes != null) {
|
|
181
|
+
for (var i = 0; i < attributes.length; i += 2) {
|
|
182
|
+
node.setAttribute(attributes[i], attributes[i + 1]);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (children === undefined || typeof children === 'string') {
|
|
186
|
+
if (children !== undefined && children !== '') {
|
|
187
|
+
node.textContent = children;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
for (var j = 0; j < children.length; j++) {
|
|
191
|
+
var child = children[j];
|
|
192
|
+
node.appendChild(
|
|
193
|
+
child === null
|
|
194
|
+
? doc.createComment('')
|
|
195
|
+
: typeof child === 'string'
|
|
196
|
+
? doc.createTextNode(child)
|
|
197
|
+
: build_template_element(doc, ns, child[0], child[1], child[2]),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return node;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* A template of a few HTML elements with static attributes, built with DOM
|
|
206
|
+
* calls instead of parsed: a `<template>` parse has a fixed cost that dwarfs
|
|
207
|
+
* the elements themselves, and an app's first render pays it once per
|
|
208
|
+
* distinct template, plus the parser's own start-up on a fresh page. The
|
|
209
|
+
* compiler decides which templates qualify (`dom_built_template` in the
|
|
210
|
+
* client transform); every other shape still parses. The nodes are created
|
|
211
|
+
* in the active namespace, as a parsed template would be, and in the inert
|
|
212
|
+
* template document when one of them could load a resource (`inert`), also
|
|
213
|
+
* as parsed; any other master is built in the document itself, which skips
|
|
214
|
+
* creating the inert document on a fresh page.
|
|
215
|
+
* @param {string} tag
|
|
216
|
+
* @param {string[] | null} [attributes] - flat name/value pairs
|
|
217
|
+
* @param {string | TemplateChildren} [children] - one text child, or the
|
|
218
|
+
* child nodes in order
|
|
219
|
+
* @param {number} [inert] - 1 when an element could load a resource from an
|
|
220
|
+
* attribute the master carries
|
|
221
|
+
* @returns {() => Node}
|
|
222
|
+
*/
|
|
223
|
+
export function template_el(tag, attributes = null, children = '', inert = 0) {
|
|
224
|
+
/** @type {Element | undefined} */
|
|
225
|
+
var node;
|
|
226
|
+
/** @type {string | undefined} */
|
|
227
|
+
var node_ns;
|
|
228
|
+
|
|
229
|
+
return () => {
|
|
230
|
+
if (HYDRATION && hydrating) {
|
|
231
|
+
return /** @type {import('./hydrate.js').HydrationRuntime} */ (H).t(false, 1);
|
|
232
|
+
}
|
|
233
|
+
var ns = active_namespace;
|
|
234
|
+
if (node === undefined || node_ns !== ns) {
|
|
235
|
+
var doc =
|
|
236
|
+
inert === 0
|
|
237
|
+
? document
|
|
238
|
+
: (inert_document ??= document.createElement('template').content.ownerDocument);
|
|
239
|
+
node = build_template_element(doc, ns, tag, attributes, children);
|
|
240
|
+
node_ns = ns;
|
|
241
|
+
}
|
|
242
|
+
var clone = is_firefox ? document.importNode(node, true) : node.cloneNode(true);
|
|
243
|
+
assign_nodes(clone, clone);
|
|
244
|
+
return clone;
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
145
248
|
/**
|
|
146
249
|
* Appends a DOM node before the anchor node.
|
|
147
250
|
* @param {ChildNode | AppendIntoAnchor} anchor - The anchor node.
|
|
@@ -76,6 +76,46 @@ describe('composite > dynamic components', () => {
|
|
|
76
76
|
expect(container.textContent).toBe('hidden');
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
+
it(
|
|
80
|
+
'remounts a childless dynamic component with fresh props when its tag expression re-evaluates',
|
|
81
|
+
() => {
|
|
82
|
+
let bump: () => void = () => {};
|
|
83
|
+
let poke: () => void = () => {};
|
|
84
|
+
|
|
85
|
+
function Child(props: { n: number }) @{
|
|
86
|
+
<span>{props.n}</span>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function App() @{
|
|
90
|
+
const n = track(0);
|
|
91
|
+
const flag = track(true);
|
|
92
|
+
bump = () => {
|
|
93
|
+
n.value++;
|
|
94
|
+
};
|
|
95
|
+
poke = () => {
|
|
96
|
+
flag.value = !flag.value;
|
|
97
|
+
};
|
|
98
|
+
// The tag expression reads `flag`, but always resolves to `Child`.
|
|
99
|
+
<{flag.value || !flag.value ? Child : null} n={n.value} />
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
render(App);
|
|
103
|
+
flushSync();
|
|
104
|
+
const span = () => container.querySelector('span');
|
|
105
|
+
expect(span()?.textContent).toBe('0');
|
|
106
|
+
|
|
107
|
+
// A prop is a value: the instance keeps the number it got.
|
|
108
|
+
bump();
|
|
109
|
+
flushSync();
|
|
110
|
+
expect(span()?.textContent).toBe('0');
|
|
111
|
+
|
|
112
|
+
// The tag expression re-evaluated: a new instance reads the literal again.
|
|
113
|
+
poke();
|
|
114
|
+
flushSync();
|
|
115
|
+
expect(span()?.textContent).toBe('1');
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
|
|
79
119
|
it('handles dynamic component switching', () => {
|
|
80
120
|
function Child1() @{
|
|
81
121
|
<div>{'I am child 1'}</div>
|
|
@@ -645,8 +645,10 @@ export function Precedence() @{
|
|
|
645
645
|
// block is written after the nested scope.
|
|
646
646
|
expect(black).toBeLessThan(italic);
|
|
647
647
|
expect(italic).toBeLessThan(blue);
|
|
648
|
+
// The outer div shares a fragment template with the placeholder that
|
|
649
|
+
// follows it; the inner div is a flat element template of its own.
|
|
648
650
|
expect(code).toContain(`class="outer ${outer} ${theme_hash}"`);
|
|
649
|
-
expect(code).toContain(`class
|
|
651
|
+
expect(code).toContain(`'class', 'inner ${outer} ${inner} ${theme_hash}'`);
|
|
650
652
|
},
|
|
651
653
|
);
|
|
652
654
|
});
|