ripple 0.4.3 → 0.4.4
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 +57 -0
- package/package.json +3 -3
- package/src/constants.js +6 -0
- package/src/runtime/internal/client/attributes.js +251 -125
- 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/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 +6 -6
- package/src/runtime/internal/client/runtime.js +73 -11
- package/src/runtime/internal/client/template-ns.js +45 -4
- package/src/runtime/internal/client/template.js +56 -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 +36 -35
- package/tests/hydration/compiled/client/composite.js +11 -2
- package/tests/hydration/compiled/client/events.js +1 -1
- package/tests/hydration/compiled/client/for.js +73 -62
- package/tests/hydration/compiled/client/fragment-cursor.js +182 -115
- package/tests/hydration/compiled/client/head.js +10 -10
- package/tests/hydration/compiled/client/hmr.js +11 -2
- package/tests/hydration/compiled/client/html-in-template.js +1 -1
- package/tests/hydration/compiled/client/html.js +78 -50
- package/tests/hydration/compiled/client/if-children.js +94 -26
- package/tests/hydration/compiled/client/if-fragment-controlflow.js +34 -14
- package/tests/hydration/compiled/client/if.js +65 -17
- package/tests/hydration/compiled/client/mixed-control-flow.js +50 -20
- package/tests/hydration/compiled/client/nested-control-flow.js +188 -97
- package/tests/hydration/compiled/client/portal.js +14 -5
- package/tests/hydration/compiled/client/reactivity.js +3 -3
- package/tests/hydration/compiled/client/streaming.js +17 -17
- package/tests/hydration/compiled/client/switch.js +17 -17
- package/tests/hydration/compiled/client/track-async-serialization.js +19 -19
- package/tests/hydration/compiled/client/try.js +9 -9
- package/tests/hydration/compiled/client/typed-text.js +3 -2
|
@@ -1,131 +1,323 @@
|
|
|
1
|
-
/** @import { Block } from '#client' */
|
|
1
|
+
/** @import { AppendIntoAnchor, Block } from '#client' */
|
|
2
|
+
/** @import { SpreadState } from './attributes.js' */
|
|
2
3
|
import { component_invalid } from './errors.js';
|
|
3
4
|
|
|
4
|
-
import { branch, destroy_block, render } from './blocks.js';
|
|
5
|
-
import {
|
|
6
|
-
import { COMPOSITE_BLOCK, DEFAULT_NAMESPACE, NAMESPACE_URI } from './constants.js';
|
|
5
|
+
import { branch, destroy_block, destroy_block_children, render } from './blocks.js';
|
|
6
|
+
import { release_spread, spread } from './attributes.js';
|
|
7
|
+
import { COMPOSITE_BLOCK, DEFAULT_NAMESPACE, NAMESPACE_URI, UNINITIALIZED } from './constants.js';
|
|
7
8
|
import { hydrate_node, hydrate_next, hydrating, set_hydrate_node } from './hydration.js';
|
|
8
9
|
import { first_child } from './operations.js';
|
|
9
|
-
import { active_block, active_namespace, get,
|
|
10
|
+
import { active_block, active_namespace, get, set_ns, set_tracking, untrack } from './runtime.js';
|
|
10
11
|
import { top_element_to_ns } from './utils.js';
|
|
11
|
-
import {
|
|
12
|
+
import { push_ns, with_ns } from './template-ns.js';
|
|
13
|
+
import { append } from './template.js';
|
|
12
14
|
import { is_tsrx_element } from '../../element.js';
|
|
13
15
|
import { render_component } from './component.js';
|
|
14
16
|
import { HYDRATION } from 'ripple/internal/client/hydration-enabled';
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* @typedef {Function | string | null | undefined | false} CompositeTarget
|
|
20
|
+
* The state of a composite block. A tag target is the block's own DOM range
|
|
21
|
+
* (`start`/`end`, the element) and its attributes the block's own spread; a
|
|
22
|
+
* component target renders in a branch, a child of the block.
|
|
23
|
+
* @typedef {{
|
|
24
|
+
* start: Element | null;
|
|
25
|
+
* end: Element | null;
|
|
26
|
+
* g: () => CompositeTarget;
|
|
27
|
+
* a: Node | AppendIntoAnchor;
|
|
28
|
+
* p: () => Record<string, any>;
|
|
29
|
+
* c: CompositeTarget | typeof UNINITIALIZED;
|
|
30
|
+
* s: SpreadState | undefined;
|
|
31
|
+
* }} CompositeState
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {CompositeState} state
|
|
36
|
+
*/
|
|
37
|
+
function run_composite(state) {
|
|
38
|
+
var block = /** @type {Block} */ (active_block);
|
|
39
|
+
// @ts-ignore — get() handles non-tracked values via is_ripple_object() check
|
|
40
|
+
var component = get(state.g());
|
|
41
|
+
var current = state.c;
|
|
42
|
+
var old = state.start;
|
|
43
|
+
|
|
44
|
+
if (component === current && typeof component === 'string') {
|
|
45
|
+
// The same tag keeps its element: only the attributes follow the props.
|
|
46
|
+
state.s = spread(/** @type {Element} */ (old), state.p(), state.s);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (current !== UNINITIALIZED) {
|
|
51
|
+
// The previous target's blocks: the children rendered into the element,
|
|
52
|
+
// whose DOM goes with it, or a component's branch, which removes its own.
|
|
53
|
+
destroy_block_children(block, old === null);
|
|
54
|
+
state.s = undefined;
|
|
55
|
+
}
|
|
56
|
+
state.c = component;
|
|
57
|
+
|
|
58
|
+
if (typeof component === 'function') {
|
|
59
|
+
if (old !== null) {
|
|
60
|
+
old.remove();
|
|
61
|
+
state.start = state.end = null;
|
|
62
|
+
}
|
|
63
|
+
var anchor = state.a;
|
|
64
|
+
var get_props = state.p;
|
|
65
|
+
// Handle as regular component
|
|
66
|
+
branch(() => {
|
|
67
|
+
var props = untrack(get_props);
|
|
68
|
+
render_component(component, /** @type {Node} */ (anchor), props);
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (is_tsrx_element(component)) {
|
|
73
|
+
component_invalid(true);
|
|
74
|
+
}
|
|
75
|
+
if (component == null) {
|
|
76
|
+
if (old !== null) {
|
|
77
|
+
old.remove();
|
|
78
|
+
state.start = state.end = null;
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// A tag: the element is created in the namespace the tag resolves to.
|
|
84
|
+
var ns = top_element_to_ns(component, active_namespace);
|
|
85
|
+
/** @type {Element} */
|
|
86
|
+
var element;
|
|
87
|
+
if (HYDRATION && hydrating) {
|
|
88
|
+
// Claim the SSR-rendered element instead of creating a new one.
|
|
89
|
+
element = /** @type {Element} */ (hydrate_node);
|
|
90
|
+
} else {
|
|
91
|
+
element =
|
|
92
|
+
ns !== DEFAULT_NAMESPACE
|
|
93
|
+
? document.createElementNS(
|
|
94
|
+
NAMESPACE_URI[ns],
|
|
95
|
+
/** @type {keyof HTMLElementTagNameMap} */ (component),
|
|
96
|
+
)
|
|
97
|
+
: document.createElement(/** @type {keyof HTMLElementTagNameMap} */ (component));
|
|
98
|
+
if (old !== null) {
|
|
99
|
+
// A tag change takes the previous element's place.
|
|
100
|
+
old.before(element);
|
|
101
|
+
old.remove();
|
|
102
|
+
} else {
|
|
103
|
+
append(/** @type {ChildNode | AppendIntoAnchor} */ (state.a), element);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
state.start = state.end = element;
|
|
107
|
+
|
|
108
|
+
// The spread reads the props once, tracked: a props change re-runs the
|
|
109
|
+
// block, which diffs the attributes above. The children come from that
|
|
110
|
+
// same read.
|
|
111
|
+
var spread_state = (state.s = spread(element, state.p(), undefined));
|
|
112
|
+
var props = /** @type {Record<string, any>} */ (spread_state.n);
|
|
113
|
+
if (is_tsrx_element(props.children)) {
|
|
114
|
+
/** @type {Node} */
|
|
115
|
+
var child_anchor;
|
|
116
|
+
if (HYDRATION && hydrating) {
|
|
117
|
+
// The server renders children directly inside the element with no
|
|
118
|
+
// extra markers; descend the cursor so they claim those nodes.
|
|
119
|
+
child_anchor = /** @type {Node} */ (first_child(element));
|
|
120
|
+
} else {
|
|
121
|
+
child_anchor = document.createComment('');
|
|
122
|
+
element.appendChild(child_anchor);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// The children render untracked, as a branch renders its content. The
|
|
126
|
+
// children of a `foreignObject` are HTML whatever namespace the element
|
|
127
|
+
// itself takes.
|
|
128
|
+
var child_ns = component === 'foreignObject' ? DEFAULT_NAMESPACE : ns;
|
|
129
|
+
set_tracking(false);
|
|
130
|
+
if (child_ns !== active_namespace) {
|
|
131
|
+
with_ns(child_ns, () => props.children.render(child_anchor, block, props.children.p));
|
|
132
|
+
} else {
|
|
133
|
+
props.children.render(child_anchor, block, props.children.p);
|
|
134
|
+
}
|
|
135
|
+
set_tracking(true);
|
|
136
|
+
|
|
137
|
+
if (HYDRATION && hydrating) {
|
|
138
|
+
// Reset the cursor to the claimed element so sibling traversal
|
|
139
|
+
// continues after it.
|
|
140
|
+
set_hydrate_node(element);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Renders a dynamic element or component (`<{expr}>`): the block re-runs
|
|
147
|
+
* when the expression or the props change. A tag that stays the same keeps
|
|
148
|
+
* its element and diffs the attributes; a new tag or component replaces the
|
|
149
|
+
* content in place.
|
|
18
150
|
* @param {() => CompositeTarget} get_component
|
|
19
|
-
* @param {Node} node
|
|
151
|
+
* @param {Node | AppendIntoAnchor} node
|
|
20
152
|
* @param {() => Record<string, any>} get_props the props literal of the call
|
|
21
153
|
* site: read once per rendered component (props are plain values), and
|
|
22
154
|
* re-read as element attributes when the target is a tag
|
|
155
|
+
* @param {keyof typeof NAMESPACE_URI} [namespace] the namespace of the
|
|
156
|
+
* template the call site sits in, when it is not HTML
|
|
23
157
|
* @returns {void}
|
|
24
158
|
*/
|
|
25
|
-
export function composite(get_component, node, get_props) {
|
|
159
|
+
export function composite(get_component, node, get_props, namespace) {
|
|
26
160
|
if (HYDRATION && hydrating) {
|
|
27
161
|
// During hydration, `node` may already point at the first real SSR node
|
|
28
162
|
// (e.g. layout children). Only skip forward when we are on an empty
|
|
29
163
|
// comment anchor from a client template placeholder.
|
|
30
|
-
if (node.nodeType === 8 && /** @type {Comment} */ (node).data === '') {
|
|
164
|
+
if (/** @type {Node} */ (node).nodeType === 8 && /** @type {Comment} */ (node).data === '') {
|
|
31
165
|
hydrate_next();
|
|
32
166
|
}
|
|
33
167
|
}
|
|
34
168
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
169
|
+
/** @type {CompositeState} */
|
|
170
|
+
var state = {
|
|
171
|
+
start: null,
|
|
172
|
+
end: null,
|
|
173
|
+
g: get_component,
|
|
174
|
+
a: node,
|
|
175
|
+
p: get_props,
|
|
176
|
+
c: UNINITIALIZED,
|
|
177
|
+
s: undefined,
|
|
178
|
+
};
|
|
179
|
+
if (namespace === undefined) {
|
|
180
|
+
render(run_composite, state, COMPOSITE_BLOCK);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
var previous = push_ns(namespace);
|
|
184
|
+
try {
|
|
185
|
+
render(run_composite, state, COMPOSITE_BLOCK);
|
|
186
|
+
} finally {
|
|
187
|
+
set_ns(previous);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
38
190
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
191
|
+
/**
|
|
192
|
+
* The state of a dynamic element the enclosing render function drives (see
|
|
193
|
+
* `dynamic`): the element rendered for a tag, or the branch rendered for a
|
|
194
|
+
* component, and the tag or component itself.
|
|
195
|
+
* @typedef {{
|
|
196
|
+
* e: Element | null;
|
|
197
|
+
* b: Block | null;
|
|
198
|
+
* t: CompositeTarget | typeof UNINITIALIZED;
|
|
199
|
+
* s: SpreadState | undefined;
|
|
200
|
+
* g: (() => CompositeTarget) | null;
|
|
201
|
+
* p: (() => Record<string, any>) | null;
|
|
202
|
+
* }} DynamicState
|
|
203
|
+
*/
|
|
43
204
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
205
|
+
/**
|
|
206
|
+
* A dynamic element without children, applied from the render function of
|
|
207
|
+
* the content it sits in instead of a block of its own: `<{tag} {...attrs} />`
|
|
208
|
+
* compiles to `__prev.x = _$_.dynamic(__prev.x, anchor, tag, props)`, so the
|
|
209
|
+
* tag and the props are read by that render function, and a run whose tag is
|
|
210
|
+
* unchanged only diffs the attributes. The element sits in the enclosing
|
|
211
|
+
* template's range, which owns it. A component target is handed to a
|
|
212
|
+
* composite block of its own (in a branch the render function keeps), which
|
|
213
|
+
* reads the tag and the props through the thunks kept since creation and so
|
|
214
|
+
* remounts the component with fresh props whenever the tag expression
|
|
215
|
+
* re-evaluates, exactly as `<{expr}>` did before; from then on the slot is
|
|
216
|
+
* null and the calls are no-ops.
|
|
217
|
+
* @param {DynamicState | null | undefined} state null once a composite block
|
|
218
|
+
* owns the content (a component target, or the hydration claim, see
|
|
219
|
+
* `dynamic_init`)
|
|
220
|
+
* @param {Node | AppendIntoAnchor} anchor
|
|
221
|
+
* @param {CompositeTarget} tag
|
|
222
|
+
* @param {Record<string, any> | null | undefined} props
|
|
223
|
+
* @param {keyof typeof NAMESPACE_URI} [namespace] the namespace of the
|
|
224
|
+
* template the call site sits in, when it is not HTML
|
|
225
|
+
* @returns {DynamicState | null}
|
|
226
|
+
*/
|
|
227
|
+
export function dynamic(state, anchor, tag, props, namespace) {
|
|
228
|
+
if (state === null) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
// @ts-ignore — get() handles non-tracked values via is_ripple_object() check
|
|
232
|
+
tag = get(tag);
|
|
233
|
+
/** @type {DynamicState} */
|
|
234
|
+
var s;
|
|
235
|
+
if (state === undefined) {
|
|
236
|
+
s = { e: null, b: null, t: UNINITIALIZED, s: undefined, g: null, p: null };
|
|
237
|
+
} else {
|
|
238
|
+
s = state;
|
|
239
|
+
if (tag === s.t) {
|
|
240
|
+
if (s.e !== null) {
|
|
241
|
+
s.s = spread(s.e, props, s.s);
|
|
47
242
|
}
|
|
243
|
+
return s;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
48
246
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
render_spread(element, get_props, 0);
|
|
92
|
-
|
|
93
|
-
var props = untrack(get_props);
|
|
94
|
-
if (is_tsrx_element(props.children)) {
|
|
95
|
-
/** @type {Node} */
|
|
96
|
-
var child_anchor;
|
|
97
|
-
if (HYDRATION && hydrating) {
|
|
98
|
-
// The server renders children directly inside the element with no
|
|
99
|
-
// extra markers; descend the cursor so they claim those nodes.
|
|
100
|
-
child_anchor = /** @type {Node} */ (first_child(element));
|
|
101
|
-
} else {
|
|
102
|
-
child_anchor = document.createComment('');
|
|
103
|
-
element.appendChild(child_anchor);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (ns !== DEFAULT_NAMESPACE) {
|
|
107
|
-
with_ns(ns, () => props.children.render(child_anchor, block, props.children.p));
|
|
108
|
-
} else {
|
|
109
|
-
props.children.render(child_anchor, block, props.children.p);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (HYDRATION && hydrating) {
|
|
113
|
-
// Reset the cursor to the claimed element so sibling traversal
|
|
114
|
-
// continues after it.
|
|
115
|
-
set_hydrate_node(element);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
if (ns !== active_namespace) {
|
|
121
|
-
// support top-level dynamic element svg/math tags
|
|
122
|
-
b = branch(() => with_ns(ns, run));
|
|
123
|
-
} else {
|
|
124
|
-
b = branch(run);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
null,
|
|
129
|
-
COMPOSITE_BLOCK,
|
|
247
|
+
var old = s.e;
|
|
248
|
+
if (s.b !== null) {
|
|
249
|
+
destroy_block(s.b);
|
|
250
|
+
s.b = null;
|
|
251
|
+
}
|
|
252
|
+
// The previous element's refs and listeners go with it.
|
|
253
|
+
release_spread(s.s);
|
|
254
|
+
s.t = tag;
|
|
255
|
+
s.s = undefined;
|
|
256
|
+
|
|
257
|
+
if (typeof tag === 'function') {
|
|
258
|
+
if (old !== null) {
|
|
259
|
+
old.remove();
|
|
260
|
+
s.e = null;
|
|
261
|
+
}
|
|
262
|
+
const get_component = s.g;
|
|
263
|
+
const get_props = s.p;
|
|
264
|
+
if (get_component !== null && get_props !== null) {
|
|
265
|
+
branch(() => composite(get_component, anchor, get_props, namespace));
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
// No thunks (a call site compiled without `dynamic_init`): the
|
|
269
|
+
// component renders once with these props.
|
|
270
|
+
s.b = branch(() => {
|
|
271
|
+
render_component(tag, /** @type {Node} */ (anchor), props ?? {});
|
|
272
|
+
});
|
|
273
|
+
return s;
|
|
274
|
+
}
|
|
275
|
+
if (is_tsrx_element(tag)) {
|
|
276
|
+
component_invalid(true);
|
|
277
|
+
}
|
|
278
|
+
if (tag == null || tag === false) {
|
|
279
|
+
if (old !== null) {
|
|
280
|
+
old.remove();
|
|
281
|
+
s.e = null;
|
|
282
|
+
}
|
|
283
|
+
return s;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
var ns = top_element_to_ns(
|
|
287
|
+
/** @type {Parameters<typeof top_element_to_ns>[0]} */ (tag),
|
|
288
|
+
namespace === undefined ? active_namespace : namespace,
|
|
130
289
|
);
|
|
290
|
+
var element =
|
|
291
|
+
ns !== DEFAULT_NAMESPACE
|
|
292
|
+
? document.createElementNS(NAMESPACE_URI[ns], /** @type {string} */ (tag))
|
|
293
|
+
: document.createElement(/** @type {string} */ (tag));
|
|
294
|
+
if (old !== null) {
|
|
295
|
+
old.before(element);
|
|
296
|
+
old.remove();
|
|
297
|
+
} else {
|
|
298
|
+
append(/** @type {ChildNode | AppendIntoAnchor} */ (anchor), element);
|
|
299
|
+
}
|
|
300
|
+
s.e = element;
|
|
301
|
+
s.s = spread(element, props, undefined);
|
|
302
|
+
return s;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* The initial state of a `dynamic` slot: the tag and props thunks a
|
|
307
|
+
* component target is handed over with. While hydrating, the element is
|
|
308
|
+
* rendered here by a composite block of its own, which claims its server
|
|
309
|
+
* nodes in document order and owns the content from then on, so the render
|
|
310
|
+
* function's `dynamic` calls are no-ops (null).
|
|
311
|
+
* @param {Node | AppendIntoAnchor} node
|
|
312
|
+
* @param {() => CompositeTarget} get_component
|
|
313
|
+
* @param {() => Record<string, any>} get_props
|
|
314
|
+
* @param {keyof typeof NAMESPACE_URI} [namespace]
|
|
315
|
+
* @returns {DynamicState | null}
|
|
316
|
+
*/
|
|
317
|
+
export function dynamic_init(node, get_component, get_props, namespace) {
|
|
318
|
+
if (HYDRATION && hydrating) {
|
|
319
|
+
composite(get_component, node, get_props, namespace);
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
return { e: null, b: null, t: UNINITIALIZED, s: undefined, g: get_component, p: get_props };
|
|
131
323
|
}
|
|
@@ -35,6 +35,14 @@ export var CREATES_DERIVEDS = 1 << 25;
|
|
|
35
35
|
* re-runs the body's update function.
|
|
36
36
|
*/
|
|
37
37
|
export var ITEM_BLOCK = 1 << 26;
|
|
38
|
+
/**
|
|
39
|
+
* The namespace a block was created in, so a rerun outside the `with_ns()`
|
|
40
|
+
* call that established it (a scheduled flush) creates its DOM in the same
|
|
41
|
+
* namespace. Neither bit set means HTML.
|
|
42
|
+
*/
|
|
43
|
+
export var SVG_BLOCK = 1 << 27;
|
|
44
|
+
export var MATHML_BLOCK = 1 << 28;
|
|
45
|
+
export var NAMESPACE_BLOCK = SVG_BLOCK | MATHML_BLOCK;
|
|
38
46
|
|
|
39
47
|
export var CONTROL_FLOW_BLOCK = FOR_BLOCK | IF_BLOCK | SWITCH_BLOCK | TRY_BLOCK | COMPOSITE_BLOCK;
|
|
40
48
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { is_array } from '@tsrx/core/runtime/language-helpers';
|
|
4
4
|
import { branch, destroy_block, render } from './blocks.js';
|
|
5
|
-
import { BRANCH_BLOCK, IF_BLOCK, UNINITIALIZED } from './constants.js';
|
|
5
|
+
import { BRANCH_BLOCK, COMPOSITE_BLOCK, IF_BLOCK, UNINITIALIZED } from './constants.js';
|
|
6
6
|
import { create_text, get_next_sibling } from './operations.js';
|
|
7
7
|
import { assign_nodes } from './template.js';
|
|
8
8
|
import { active_block } from './runtime.js';
|
|
@@ -12,14 +12,14 @@ import { is_tsrx_element, TSRX_ELEMENT } from '../../element.js';
|
|
|
12
12
|
import { HYDRATION } from 'ripple/internal/client/hydration-enabled';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Finds the nearest enclosing block that owns a DOM range (a branch
|
|
16
|
-
* block) in the block hierarchy.
|
|
15
|
+
* Finds the nearest enclosing block that owns a DOM range (a branch, an if
|
|
16
|
+
* or a composite block) in the block hierarchy.
|
|
17
17
|
* @param {Block | null} block
|
|
18
18
|
* @returns {Block | null}
|
|
19
19
|
*/
|
|
20
20
|
function find_enclosing_branch(block) {
|
|
21
21
|
while (block !== null) {
|
|
22
|
-
if ((block.f & (BRANCH_BLOCK | IF_BLOCK)) !== 0) {
|
|
22
|
+
if ((block.f & (BRANCH_BLOCK | IF_BLOCK | COMPOSITE_BLOCK)) !== 0) {
|
|
23
23
|
return block;
|
|
24
24
|
}
|
|
25
25
|
block = block.p;
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { IS_CONTROLLED, IS_INDEXED, LOCAL_ITEMS, ROOT_CONTROLLED } from '../../../constants.js';
|
|
4
4
|
import {
|
|
5
|
-
block as create_block,
|
|
6
5
|
create_block as allocate_block,
|
|
7
6
|
branch,
|
|
8
7
|
destroy_block,
|
|
@@ -60,21 +59,10 @@ function create_item(anchor, value, index, key, state) {
|
|
|
60
59
|
p: null,
|
|
61
60
|
};
|
|
62
61
|
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
var lean = allocate_block(BRANCH_BLOCK, run_item, item_state);
|
|
68
|
-
run_branch(lean, render_fn, anchor, value, key);
|
|
69
|
-
lean.f ^= BLOCK_HAS_RUN;
|
|
70
|
-
return lean;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Passed through module state rather than a per-item closure; run_item
|
|
74
|
-
// reads them before rendering, so nested loops cannot observe a stale pair.
|
|
75
|
-
item_anchor = anchor;
|
|
76
|
-
item_render_fn = render_fn;
|
|
77
|
-
var b = create_block(BRANCH_BLOCK, run_item, item_state);
|
|
62
|
+
// The item renders in place: allocated, linked and run through
|
|
63
|
+
// `run_branch`, with none of the first-run bookkeeping a block that may
|
|
64
|
+
// re-run needs (a rerun is `run_item`'s, through `run_block`).
|
|
65
|
+
var b = allocate_block(BRANCH_BLOCK, run_item, item_state);
|
|
78
66
|
|
|
79
67
|
// The item's tracked value and index are owned by the item block itself.
|
|
80
68
|
if (tracked_value !== value) {
|
|
@@ -83,6 +71,8 @@ function create_item(anchor, value, index, key, state) {
|
|
|
83
71
|
if (tracked_index !== undefined) {
|
|
84
72
|
/** @type {Tracked} */ (tracked_index).b = b;
|
|
85
73
|
}
|
|
74
|
+
run_branch(b, render_fn, anchor, tracked_value, tracked_index, key);
|
|
75
|
+
b.f ^= BLOCK_HAS_RUN;
|
|
86
76
|
return b;
|
|
87
77
|
}
|
|
88
78
|
|
|
@@ -118,32 +108,17 @@ function controlled_anchor(parent) {
|
|
|
118
108
|
return { parent, into: true, tail: false };
|
|
119
109
|
}
|
|
120
110
|
|
|
121
|
-
/** @type {Node | AppendIntoAnchor | null} */
|
|
122
|
-
var item_anchor = null;
|
|
123
|
-
/** @type {((anchor: Node, value: any, index?: any, key?: any) => Block) | null} */
|
|
124
|
-
var item_render_fn = null;
|
|
125
|
-
|
|
126
111
|
/**
|
|
112
|
+
* A re-run of an item that carries its render block (see `item`): only the
|
|
113
|
+
* body's update function, tracked. The first run is `run_branch`'s, in
|
|
114
|
+
* `create_item`; an item block with no render block of its own never re-runs.
|
|
127
115
|
* @param {{ i: Tracked | undefined, v: any, k: any, p: any }} state
|
|
128
116
|
*/
|
|
129
117
|
function run_item(state) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
set_tracking(true);
|
|
135
|
-
/** @type {(prev: any) => void} */ (
|
|
136
|
-
/** @type {ListState} */ (/** @type {Block} */ (/** @type {Block} */ (active_block).p).s).u
|
|
137
|
-
)(p);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
var render_fn = /** @type {(anchor: Node, value: any, index?: any, key?: any) => Block} */ (
|
|
141
|
-
item_render_fn
|
|
142
|
-
);
|
|
143
|
-
var anchor = /** @type {Node} */ (item_anchor);
|
|
144
|
-
item_render_fn = null;
|
|
145
|
-
item_anchor = null;
|
|
146
|
-
render_fn(anchor, state.v, state.i, state.k);
|
|
118
|
+
set_tracking(true);
|
|
119
|
+
/** @type {(prev: any) => void} */ (
|
|
120
|
+
/** @type {ListState} */ (/** @type {Block} */ (/** @type {Block} */ (active_block).p).s).u
|
|
121
|
+
)(state.p);
|
|
147
122
|
}
|
|
148
123
|
|
|
149
124
|
/**
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
block,
|
|
5
5
|
branch,
|
|
6
|
+
create_block,
|
|
6
7
|
destroy_block,
|
|
7
8
|
get_first_node,
|
|
8
9
|
get_last_node,
|
|
@@ -10,9 +11,10 @@ import {
|
|
|
10
11
|
remove_block_dom,
|
|
11
12
|
} from './blocks.js';
|
|
12
13
|
import { DETACHED_BLOCK, IF_BLOCK, RENDER_BLOCK, UNINITIALIZED } from './constants.js';
|
|
14
|
+
import { IF_ROOT_CONTROLLED, IF_TRACKED } from '../../../constants.js';
|
|
13
15
|
import { hydrate_next, hydrate_node, hydrating } from './hydration.js';
|
|
14
16
|
import { create_text, resolve_anchor } from './operations.js';
|
|
15
|
-
import { active_block, probe_if, run_untracked } from './runtime.js';
|
|
17
|
+
import { active_block, probe_if, run_in_block, run_untracked } from './runtime.js';
|
|
16
18
|
import { append } from './template.js';
|
|
17
19
|
import { HYDRATION } from 'ripple/internal/client/hydration-enabled';
|
|
18
20
|
|
|
@@ -262,3 +264,56 @@ export function if_block(node, fn, root_controlled, x) {
|
|
|
262
264
|
append(/** @type {ChildNode} */ (node), /** @type {Node} */ (boundary));
|
|
263
265
|
}
|
|
264
266
|
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* An if whose condition the enclosing render function evaluates (see the
|
|
270
|
+
* compiler's if lowering): the block only owns the branch, its DOM range and
|
|
271
|
+
* its blocks, and never runs itself. The render function calls `if_update`
|
|
272
|
+
* with the branch the condition selected whenever that changes. As
|
|
273
|
+
* `if_block` does, a condition that read no tracked state has its branch
|
|
274
|
+
* rendered by the probe, with no block at all: null is returned and the
|
|
275
|
+
* updates are no-ops. While hydrating, the if keeps a block of its own that
|
|
276
|
+
* runs the condition, exactly as `if_block` creates: the branch claims its
|
|
277
|
+
* server nodes in document order, and that block owns the condition's
|
|
278
|
+
* subscription and a pending read it throws; null is returned then too.
|
|
279
|
+
* @param {Node | AppendIntoAnchor} node
|
|
280
|
+
* @param {IfState['fn']} fn
|
|
281
|
+
* @param {number} [flags] `IF_ROOT_CONTROLLED` (see `if_block`) and
|
|
282
|
+
* `IF_TRACKED`, set when the compiler proved the condition reads tracked
|
|
283
|
+
* state, so the probe is skipped
|
|
284
|
+
* @param {any} [x] see `if_block`
|
|
285
|
+
* @returns {Block | null}
|
|
286
|
+
*/
|
|
287
|
+
export function if_static(node, fn, flags = 0, x) {
|
|
288
|
+
var root_controlled = (flags & IF_ROOT_CONTROLLED) !== 0;
|
|
289
|
+
|
|
290
|
+
if (HYDRATION && hydrating) {
|
|
291
|
+
if_block(node, fn, root_controlled, x);
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
if ((flags & IF_TRACKED) === 0) {
|
|
295
|
+
var rendered;
|
|
296
|
+
try {
|
|
297
|
+
rendered = probe_if(fn, x, node);
|
|
298
|
+
} catch {
|
|
299
|
+
rendered = false;
|
|
300
|
+
}
|
|
301
|
+
if (rendered) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return create_block(IF_BLOCK, noop, if_block_state(node, fn, x));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Renders `fn` as the branch of an if created by `if_static`, when it is not
|
|
311
|
+
* the current one. The branch's blocks belong to the if block.
|
|
312
|
+
* @param {Block | null} block
|
|
313
|
+
* @param {Branch | undefined} fn
|
|
314
|
+
*/
|
|
315
|
+
export function if_update(block, fn) {
|
|
316
|
+
if (block !== null) {
|
|
317
|
+
run_in_block(block, update_branch, fn);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
@@ -40,7 +40,7 @@ export {
|
|
|
40
40
|
RENDER_ENTRY as $r,
|
|
41
41
|
} from './constants.js';
|
|
42
42
|
export { event, render_event, delegate, listen } from './events.js';
|
|
43
|
-
export { set_style, set_attribute, render_spread } from './attributes.js';
|
|
43
|
+
export { set_style, set_attribute, render_spread, spread } from './attributes.js';
|
|
44
44
|
export { portal } from './portal.js';
|
|
45
45
|
|
|
46
46
|
export {
|
|
@@ -73,7 +73,7 @@ export {
|
|
|
73
73
|
derived,
|
|
74
74
|
tick,
|
|
75
75
|
with_block,
|
|
76
|
-
|
|
76
|
+
set_ns,
|
|
77
77
|
handle_error,
|
|
78
78
|
queue_post_block_flush_callback,
|
|
79
79
|
schedule_update,
|
|
@@ -81,7 +81,7 @@ export {
|
|
|
81
81
|
|
|
82
82
|
export { track_async } from './track-async.js';
|
|
83
83
|
|
|
84
|
-
export { composite } from './composite.js';
|
|
84
|
+
export { composite, dynamic, dynamic_init } from './composite.js';
|
|
85
85
|
|
|
86
86
|
export { render_component } from './component.js';
|
|
87
87
|
|
|
@@ -89,15 +89,15 @@ export { for_block as for, for_block_keyed as for_keyed, item } from './for.js';
|
|
|
89
89
|
|
|
90
90
|
export { selector, selector_match } from './selector.js';
|
|
91
91
|
|
|
92
|
-
export { if_block as if } from './if.js';
|
|
92
|
+
export { if_block as if, if_static, if_update } from './if.js';
|
|
93
93
|
|
|
94
94
|
export { try_block as try, get_pending_boundary } from './try.js';
|
|
95
95
|
|
|
96
96
|
export { if_block as switch } from './if.js';
|
|
97
97
|
|
|
98
|
-
export { template, append, text } from './template.js';
|
|
98
|
+
export { template, template_el, append, text } from './template.js';
|
|
99
99
|
|
|
100
|
-
export { template_ns } from './template-ns.js';
|
|
100
|
+
export { template_ns, with_ns, render_component_ns } from './template-ns.js';
|
|
101
101
|
|
|
102
102
|
export {
|
|
103
103
|
ripple_array,
|