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.
Files changed (38) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/package.json +3 -3
  3. package/src/constants.js +6 -0
  4. package/src/runtime/internal/client/attributes.js +251 -125
  5. package/src/runtime/internal/client/blocks.js +16 -6
  6. package/src/runtime/internal/client/composite.js +292 -100
  7. package/src/runtime/internal/client/constants.js +8 -0
  8. package/src/runtime/internal/client/expression.js +4 -4
  9. package/src/runtime/internal/client/for.js +13 -38
  10. package/src/runtime/internal/client/if.js +56 -1
  11. package/src/runtime/internal/client/index.js +6 -6
  12. package/src/runtime/internal/client/runtime.js +73 -11
  13. package/src/runtime/internal/client/template-ns.js +45 -4
  14. package/src/runtime/internal/client/template.js +56 -0
  15. package/tests/client/composite/composite.dynamic-components.test.tsrx +40 -0
  16. package/tests/client/css/scoped-styles.test.tsrx +3 -1
  17. package/tests/client/svg.test.tsrx +273 -1
  18. package/tests/hydration/compiled/client/basic.js +36 -35
  19. package/tests/hydration/compiled/client/composite.js +11 -2
  20. package/tests/hydration/compiled/client/events.js +1 -1
  21. package/tests/hydration/compiled/client/for.js +73 -62
  22. package/tests/hydration/compiled/client/fragment-cursor.js +182 -115
  23. package/tests/hydration/compiled/client/head.js +10 -10
  24. package/tests/hydration/compiled/client/hmr.js +11 -2
  25. package/tests/hydration/compiled/client/html-in-template.js +1 -1
  26. package/tests/hydration/compiled/client/html.js +78 -50
  27. package/tests/hydration/compiled/client/if-children.js +94 -26
  28. package/tests/hydration/compiled/client/if-fragment-controlflow.js +34 -14
  29. package/tests/hydration/compiled/client/if.js +65 -17
  30. package/tests/hydration/compiled/client/mixed-control-flow.js +50 -20
  31. package/tests/hydration/compiled/client/nested-control-flow.js +188 -97
  32. package/tests/hydration/compiled/client/portal.js +14 -5
  33. package/tests/hydration/compiled/client/reactivity.js +3 -3
  34. package/tests/hydration/compiled/client/streaming.js +17 -17
  35. package/tests/hydration/compiled/client/switch.js +17 -17
  36. package/tests/hydration/compiled/client/track-async-serialization.js +19 -19
  37. package/tests/hydration/compiled/client/try.js +9 -9
  38. package/tests/hydration/compiled/client/typed-text.js +3 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # ripple
2
2
 
3
+ ## 0.4.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1496](https://github.com/Ripple-TS/ripple/pull/1496)
8
+ [`0473e93`](https://github.com/Ripple-TS/ripple/commit/0473e93006e53cd15fcf6d851443d427ccf13e8b)
9
+ Thanks [@leonidaz](https://github.com/leonidaz)! - Update `@tsrx/core` to ^0.2.3
10
+ and use its SVG and MathML tag-name predicates for dynamic class handling,
11
+ removing the compiler's duplicate tag-name list.
12
+
13
+ - [#1490](https://github.com/Ripple-TS/ripple/pull/1490)
14
+ [`ff3a26a`](https://github.com/Ripple-TS/ripple/commit/ff3a26acef0c9584cf41d32bc8cbcc39d863d74c)
15
+ Thanks [@leonidaz](https://github.com/leonidaz)! - Blocks keep the namespace
16
+ they were created in when they rerun. A dynamic element whose tag changes, an
17
+ `@if` that toggles, or an `@for` that grows inside an SVG that is only known at
18
+ runtime (children rendered into a component's `<svg>`, a leaf component rendered
19
+ there) now creates its elements in the SVG namespace instead of HTML, and a
20
+ nested template cloned in that context no longer throws before a namespaced
21
+ template has been parsed. A dynamic element whose tag becomes `foreignObject`
22
+ renders its children as HTML.
23
+
24
+ - [#1488](https://github.com/Ripple-TS/ripple/pull/1488)
25
+ [`b3bf787`](https://github.com/Ripple-TS/ripple/commit/b3bf78756d9235412a2af264e42897652e8f911e)
26
+ Thanks [@leonidaz](https://github.com/leonidaz)! - Build a template that is one
27
+ element with static attributes and at most a text child with DOM calls instead
28
+ of parsing it. Parsing a `<template>` has a fixed cost that dwarfs such an
29
+ element, and an app's first render pays it once per distinct template; a chain
30
+ of 100 single-element components mounts about 35% faster.
31
+
32
+ - [#1495](https://github.com/Ripple-TS/ripple/pull/1495)
33
+ [`1b8381f`](https://github.com/Ripple-TS/ripple/commit/1b8381f31f98f84049764ca56676fc320721e844)
34
+ Thanks [@leonidaz](https://github.com/leonidaz)! - Faster element spreads,
35
+ dynamic elements and list items. An element spread is applied from the element's
36
+ own render function instead of a render block of its own, and a spread object
37
+ identical to the one applied last is skipped without a diff (a spread of a
38
+ tracked object or of tracked values is always diffed). A dynamic element
39
+ (`<{tag}>`) is one block that owns its element and applies its attributes
40
+ itself; a tag that does not change keeps its element and diffs the attributes,
41
+ so a swap between variants with the same tag no longer recreates the element. An
42
+ `@if` whose condition reads tracked state is evaluated by the render function of
43
+ the enclosing content (a list item's, an element's), so the if keeps a block for
44
+ its branch but not one that runs the condition. A dynamic element or component
45
+ called inside an `<svg>` or `<math>` template receives the namespace as an
46
+ argument instead of a `with_ns` closure. A dynamic element without children is
47
+ driven by the render function of the content it sits in, with no block or
48
+ closures of its own (`_$_.dynamic`); a build that can hydrate lets a composite
49
+ block claim the server element while hydrating. With `textTypes`, an attribute
50
+ value TypeScript proves to be a string is set with a direct `setAttribute` call,
51
+ and a proven `class` takes the string-only path. On the SVG dashboard benchmark,
52
+ mounting is about 20% faster and swapping 150 dynamic icons 40% faster.
53
+ - Updated dependencies
54
+ [[`0473e93`](https://github.com/Ripple-TS/ripple/commit/0473e93006e53cd15fcf6d851443d427ccf13e8b),
55
+ [`b3bf787`](https://github.com/Ripple-TS/ripple/commit/b3bf78756d9235412a2af264e42897652e8f911e),
56
+ [`1b8381f`](https://github.com/Ripple-TS/ripple/commit/1b8381f31f98f84049764ca56676fc320721e844),
57
+ [`ff3a26a`](https://github.com/Ripple-TS/ripple/commit/ff3a26acef0c9584cf41d32bc8cbcc39d863d74c)]:
58
+ - @tsrx/ripple@0.2.3
59
+
3
60
  ## 0.4.3
4
61
 
5
62
  ### Patch Changes
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Ripple is an elegant TypeScript UI framework",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.4.3",
6
+ "version": "0.4.4",
7
7
  "type": "module",
8
8
  "sideEffects": false,
9
9
  "module": "src/runtime/index-client.js",
@@ -86,8 +86,8 @@
86
86
  "csstype": "^3.2.3",
87
87
  "devalue": "^5.9.2",
88
88
  "esm-env": "^1.2.2",
89
- "@tsrx/core": "^0.2.0",
90
- "@tsrx/ripple": "0.2.2"
89
+ "@tsrx/core": "^0.2.3",
90
+ "@tsrx/ripple": "0.2.3"
91
91
  },
92
92
  "devDependencies": {
93
93
  "@types/estree": "^1.0.8",
package/src/constants.js CHANGED
@@ -38,3 +38,9 @@ export const ELEMENT_NODE = 1;
38
38
  export const TEXT_NODE = 3;
39
39
  export const COMMENT_NODE = 8;
40
40
  export const DOCUMENT_FRAGMENT_NODE = 11;
41
+
42
+ // `if_static` flags (see the runtime's if module): the if renders directly
43
+ // before the component's `__anchor`, and the compiler proved its condition
44
+ // reads tracked state.
45
+ export const IF_ROOT_CONTROLLED = 1;
46
+ export const IF_TRACKED = 1 << 1;
@@ -1,7 +1,7 @@
1
1
  /** @import { Block } from '#client' */
2
2
 
3
3
  import { block, branch, destroy_block, ref } from './blocks.js';
4
- import { DESTROYED, REF_PROP, RENDER_BLOCK } from './constants.js';
4
+ import { DESTROYED, REF_PROP, RENDER_BLOCK, TRACKED_OBJECT } from './constants.js';
5
5
  import { isRefProp as is_ref_prop } from '@tsrx/core/runtime/ref';
6
6
  import { is_ripple_object } from './utils.js';
7
7
  import {
@@ -120,10 +120,9 @@ function apply_styles(element, new_styles, prev) {
120
120
  * @param {Element} element
121
121
  * @param {string} key
122
122
  * @param {any} value
123
- * @param {Record<string, (() => void) | undefined>} remove_listeners
124
- * @param {Record<string | symbol, any>} prev
123
+ * @param {SpreadState} state
125
124
  */
126
- function set_attribute_helper(element, key, value, remove_listeners, prev) {
125
+ function set_attribute_helper(element, key, value, state) {
127
126
  if (key === 'class') {
128
127
  const is_html = element.namespaceURI === 'http://www.w3.org/1999/xhtml';
129
128
  set_class_value(/** @type {HTMLElement} */ (element), value, undefined, is_html);
@@ -131,20 +130,21 @@ function set_attribute_helper(element, key, value, remove_listeners, prev) {
131
130
  element.removeAttribute('innerhtml');
132
131
  /** @type {HTMLElement} */ (element).innerHTML = value == null ? '' : String(value);
133
132
  } else if (key === 'style') {
134
- set_style(element, value, prev.style);
133
+ set_style(element, value, state.p.style);
135
134
  } else if (key === '#class') {
136
135
  // Special case for the scope classes of an element that spreads props:
137
136
  // one token per scope hash and applied theme.
138
137
  element.classList.add(...String(value).split(' ').filter(Boolean));
139
- } else if (typeof key === 'string' && is_event_attribute(key)) {
138
+ } else if (is_event_attribute(key)) {
140
139
  // Handle event handlers in spread props
141
- if (remove_listeners[key]) {
142
- remove_listeners[key]();
143
- remove_listeners[key] = undefined;
140
+ var listeners = (state.l ??= {});
141
+ if (listeners[key]) {
142
+ listeners[key]();
143
+ listeners[key] = undefined;
144
144
  }
145
145
  if (value != null) {
146
146
  const event_name = get_attribute_event_name(key, value);
147
- remove_listeners[key] = event_listener(event_name, element, value);
147
+ listeners[key] = event_listener(event_name, element, value);
148
148
  }
149
149
  } else {
150
150
  set_attribute(element, key, value);
@@ -152,146 +152,257 @@ function set_attribute_helper(element, key, value, remove_listeners, prev) {
152
152
  }
153
153
 
154
154
  /**
155
- * @param {Element} element
156
- * @param {() => Record<string | symbol, any>} fn
157
- * @returns {() => void}
155
+ * The state of one element spread: the object and values it last applied,
156
+ * and the listeners and ref effects it owns. `spread()` creates it on its
157
+ * first call and is handed it back by every later one, so the compiled render
158
+ * function keeps it in its own state object, and a `render_spread` block in
159
+ * its block state.
158
160
  */
159
- export function apply_element_spread(element, fn) {
160
- /** @type {Record<string | symbol, any>} */
161
- var prev = {};
162
- /** @type {Record<string | symbol, Block | undefined>} */
163
- var effects = {};
164
- /** @type {Record<string | symbol, (() => void) | undefined>} */
165
- var remove_listeners = {};
166
-
167
- /** @type {Record<symbol, any>} */
168
- var prev_symbols = {};
169
- /** @type {Record<string, any>} */
170
- var prev_ref_props = {};
161
+ export class SpreadState {
162
+ /** @param {Element} element */
163
+ constructor(element) {
164
+ this.e = element;
165
+ /** @type {Record<string | symbol, any> | null} the object last applied, compared by identity */
166
+ this.n = null;
167
+ /** @type {Record<string, any>} the values last applied, by key */
168
+ this.p = {};
169
+ /**
170
+ * The object last applied was a tracked object, or held a tracked value:
171
+ * the same object can carry new values, so it is never skipped on identity.
172
+ */
173
+ this.t = false;
174
+ /** @type {Record<string, (() => void) | undefined> | null} listeners by event key */
175
+ this.l = null;
176
+ /** @type {Record<string | symbol, any> | null} the ref props last applied */
177
+ this.r = null;
178
+ /** @type {Record<string | symbol, Block | undefined> | null} the effects of those ref props */
179
+ this.b = null;
180
+ }
181
+ }
171
182
 
172
- return () => {
173
- var next = fn();
174
- var current_symbols = /** @type {Record<symbol, any>} */ ({});
183
+ /** What a nullish or primitive spread value applies. */
184
+ var EMPTY = {};
175
185
 
176
- for (const symbol of get_own_property_symbols(next)) {
177
- if (symbol.description !== REF_PROP) {
178
- continue;
179
- }
180
- const ref_fn = next[symbol];
181
- current_symbols[symbol] = ref_fn;
182
-
183
- if (
184
- !(symbol in prev_symbols) ||
185
- ref_fn !== prev_symbols[symbol] ||
186
- (effects[symbol] && (effects[symbol].f & DESTROYED) !== 0)
187
- ) {
188
- if (effects[symbol] && (effects[symbol].f & DESTROYED) === 0) {
189
- destroy_block(effects[symbol]);
190
- }
191
- effects[symbol] = create_spread_ref_effect(element, ref_fn);
192
- }
186
+ /**
187
+ * Applies a spread of attributes to an element, diffing against what the
188
+ * previous call applied: attributes, `class`, `style` objects, event
189
+ * listeners and ref props. An object identical to the last one applied is
190
+ * skipped outright, unless that object was a tracked object or held a
191
+ * tracked value (its contents can change while its identity does not).
192
+ * Called from a compiled render function with the state it returned before
193
+ * (`undefined` the first time), which the render function stores.
194
+ * @param {Element} element
195
+ * @param {any} next
196
+ * @param {SpreadState | undefined} state
197
+ * @returns {SpreadState}
198
+ */
199
+ export function spread(element, next, state) {
200
+ if (next == null || (typeof next !== 'object' && typeof next !== 'function')) {
201
+ next = EMPTY;
202
+ }
203
+ if (state === undefined) {
204
+ return apply_spread(new SpreadState(element), next);
205
+ }
206
+ if (state.n === next && !state.t) {
207
+ return state;
208
+ }
209
+
210
+ var prev = state.p;
211
+ /** @type {Record<string, any>} */
212
+ var current = {};
213
+ /** @type {Record<string | symbol, any> | null} */
214
+ var refs = null;
215
+ var tracked = TRACKED_OBJECT in next;
216
+ var key;
217
+
218
+ // A parent's ref prop travels through a spread on a symbol key.
219
+ var symbols = get_own_property_symbols(next);
220
+ for (var i = 0; i < symbols.length; i++) {
221
+ var symbol = symbols[i];
222
+ if (symbol.description === REF_PROP) {
223
+ refs = apply_ref_prop(state, symbol, next[symbol], refs);
193
224
  }
225
+ }
194
226
 
195
- for (const symbol of get_own_property_symbols(prev_symbols)) {
196
- if (!(symbol in current_symbols) && effects[symbol]) {
197
- destroy_block(/** @type {Block} */ (effects[symbol]));
198
- effects[symbol] = undefined;
227
+ // Listeners and attributes that are gone, or now carry a ref prop, are
228
+ // removed before anything is applied: removing `class` drops the scope
229
+ // classes too, which `#class` then adds back.
230
+ var listeners = state.l;
231
+ if (listeners !== null) {
232
+ for (key in listeners) {
233
+ if (listeners[key] !== undefined && (!(key in next) || is_ref_prop(next[key]))) {
234
+ /** @type {() => void} */ (listeners[key])();
235
+ listeners[key] = undefined;
199
236
  }
200
237
  }
238
+ }
239
+ for (key in prev) {
240
+ if (key !== '#class' && (!(key in next) || is_ref_prop(next[key]))) {
241
+ set_attribute_helper(element, key, null, state);
242
+ }
243
+ }
201
244
 
202
- prev_symbols = current_symbols;
203
-
204
- /** @type {Record<string, any>} */
205
- var current_ref_props = {};
206
- var keys = spread_keys(next);
207
-
208
- for (const key of keys) {
209
- const ref_fn = next[key];
210
- if (!is_ref_prop(ref_fn)) {
211
- continue;
212
- }
245
+ for (key in next) {
246
+ if (key === 'children') continue;
213
247
 
214
- current_ref_props[key] = ref_fn;
215
-
216
- if (
217
- !(key in prev_ref_props) ||
218
- ref_fn !== prev_ref_props[key] ||
219
- (effects[key] && (effects[key].f & DESTROYED) !== 0)
220
- ) {
221
- if (effects[key] && (effects[key].f & DESTROYED) === 0) {
222
- destroy_block(effects[key]);
223
- }
224
- effects[key] = create_spread_ref_effect(element, ref_fn);
225
- }
248
+ var value = next[key];
249
+ if (typeof value === 'function' && is_ref_prop(value)) {
250
+ refs = apply_ref_prop(state, key, value, refs);
251
+ continue;
252
+ }
253
+ if (is_ripple_object(value)) {
254
+ value = get(value);
255
+ tracked = true;
226
256
  }
257
+ current[key] = value;
227
258
 
228
- for (const key in prev_ref_props) {
229
- if (!(key in current_ref_props) && effects[key]) {
230
- destroy_block(/** @type {Block} */ (effects[key]));
231
- effects[key] = undefined;
232
- }
259
+ // The scope classes are added after every run: a `class` write may
260
+ // have replaced them.
261
+ if (key !== '#class' && key in prev && prev[key] === value) {
262
+ continue;
233
263
  }
264
+ set_attribute_helper(element, key, value, state);
265
+ }
234
266
 
235
- prev_ref_props = current_ref_props;
267
+ if (state.r !== null) {
268
+ release_ref_props(state, refs);
269
+ }
270
+ state.r = refs;
271
+ state.p = current;
272
+ state.n = next;
273
+ state.t = tracked;
274
+ return state;
275
+ }
236
276
 
237
- for (let key in remove_listeners) {
238
- // Remove event listeners that are no longer present
239
- if ((!keys.includes(key) || is_ref_prop(next[key])) && remove_listeners[key]) {
240
- remove_listeners[key]();
241
- remove_listeners[key] = undefined;
242
- }
277
+ /**
278
+ * The first run of a spread: nothing to remove or compare, every value is
279
+ * applied.
280
+ * @param {SpreadState} state
281
+ * @param {Record<string | symbol, any>} next
282
+ * @returns {SpreadState}
283
+ */
284
+ function apply_spread(state, next) {
285
+ var element = state.e;
286
+ /** @type {Record<string, any>} */
287
+ var current = {};
288
+ /** @type {Record<string | symbol, any> | null} */
289
+ var refs = null;
290
+ var tracked = TRACKED_OBJECT in next;
291
+
292
+ var symbols = get_own_property_symbols(next);
293
+ for (var i = 0; i < symbols.length; i++) {
294
+ var symbol = symbols[i];
295
+ if (symbol.description === REF_PROP) {
296
+ refs = apply_ref_prop(state, symbol, next[symbol], refs);
243
297
  }
298
+ }
244
299
 
245
- for (const key in prev) {
246
- if (!keys.includes(key) || is_ref_prop(next[key])) {
247
- if (key === '#class') {
248
- continue;
249
- }
250
- set_attribute_helper(element, key, null, remove_listeners, prev);
251
- }
252
- }
300
+ for (var key in next) {
301
+ if (key === 'children') continue;
253
302
 
254
- /** @type {typeof prev} */
255
- const current = {};
256
- for (const key of keys) {
257
- if (key === 'children') continue;
303
+ var value = next[key];
304
+ if (typeof value === 'function' && is_ref_prop(value)) {
305
+ refs = apply_ref_prop(state, key, value, refs);
306
+ continue;
307
+ }
308
+ if (is_ripple_object(value)) {
309
+ value = get(value);
310
+ tracked = true;
311
+ }
312
+ current[key] = value;
313
+ set_attribute_helper(element, key, value, state);
314
+ }
258
315
 
259
- let value = next[key];
260
- if (is_ref_prop(value)) {
261
- continue;
262
- }
263
- if (is_ripple_object(value)) {
264
- value = get(value);
265
- }
266
- current[key] = value;
316
+ state.r = refs;
317
+ state.p = current;
318
+ state.n = next;
319
+ state.t = tracked;
320
+ return state;
321
+ }
267
322
 
268
- if (key in prev && prev[key] === value && key !== '#class') {
269
- continue;
270
- }
323
+ /**
324
+ * Keeps the effect of a ref prop the spread carried before, or creates one
325
+ * for a ref prop that is new or changed.
326
+ * @param {SpreadState} state
327
+ * @param {string | symbol} key
328
+ * @param {any} ref_fn
329
+ * @param {Record<string | symbol, any> | null} refs the ref props of this run
330
+ * @returns {Record<string | symbol, any>}
331
+ */
332
+ function apply_ref_prop(state, key, ref_fn, refs) {
333
+ if (refs === null) {
334
+ refs = {};
335
+ }
336
+ refs[key] = ref_fn;
337
+
338
+ var previous = state.r;
339
+ var blocks = (state.b ??= {});
340
+ var block = blocks[key];
341
+ if (
342
+ previous === null ||
343
+ !(key in previous) ||
344
+ previous[key] !== ref_fn ||
345
+ (block !== undefined && (block.f & DESTROYED) !== 0)
346
+ ) {
347
+ if (block !== undefined && (block.f & DESTROYED) === 0) {
348
+ destroy_block(block);
349
+ }
350
+ blocks[key] = create_spread_ref_effect(state.e, ref_fn);
351
+ }
352
+ return refs;
353
+ }
271
354
 
272
- set_attribute_helper(element, key, value, remove_listeners, prev);
355
+ /**
356
+ * Destroys the effects of the ref props a spread no longer carries.
357
+ * @param {SpreadState} state
358
+ * @param {Record<string | symbol, any> | null} refs the ref props of this run
359
+ */
360
+ function release_ref_props(state, refs) {
361
+ var previous = /** @type {Record<string | symbol, any>} */ (state.r);
362
+ var blocks = /** @type {Record<string | symbol, Block | undefined>} */ (state.b);
363
+ /** @type {(string | symbol)[]} */
364
+ var keys = get_own_property_symbols(previous);
365
+ for (var key in previous) {
366
+ keys.push(key);
367
+ }
368
+ for (var i = 0; i < keys.length; i++) {
369
+ var k = keys[i];
370
+ var block = blocks[k];
371
+ if ((refs === null || !(k in refs)) && block !== undefined) {
372
+ destroy_block(block);
373
+ blocks[k] = undefined;
273
374
  }
274
- prev = current;
275
- };
375
+ }
276
376
  }
277
377
 
278
378
  /**
279
- * The keys an element spread applies: every prop of a props instance, or the
280
- * enumerable keys of a plain object.
281
- * @param {Record<string | symbol, any>} next
282
- * @returns {string[]}
379
+ * Releases what a spread owns when its element is dropped by something other
380
+ * than its block (a dynamic element replaced by a tag change): its ref
381
+ * effects, so refs receive null, and its listeners.
382
+ * @param {SpreadState | undefined} state
283
383
  */
284
- function spread_keys(next) {
285
- /** @type {string[]} */
286
- var keys = [];
287
- for (var key in next) keys.push(key);
288
- return keys;
384
+ export function release_spread(state) {
385
+ if (state === undefined) return;
386
+ var blocks = state.b;
387
+ if (blocks !== null) {
388
+ release_ref_props(state, null);
389
+ }
390
+ var listeners = state.l;
391
+ if (listeners !== null) {
392
+ for (var key in listeners) {
393
+ var remove = listeners[key];
394
+ if (remove !== undefined) {
395
+ remove();
396
+ listeners[key] = undefined;
397
+ }
398
+ }
399
+ }
289
400
  }
290
401
 
291
402
  /**
292
403
  * Keep spread refs in a branch block so ordinary spread updates do not destroy
293
- * and recreate the ref block before `apply_element_spread` can compare the
294
- * previous and current ref values.
404
+ * and recreate the ref block before `spread` can compare the previous and
405
+ * current ref values.
295
406
  *
296
407
  * @param {Element} element
297
408
  * @param {any} ref_fn
@@ -302,12 +413,27 @@ function create_spread_ref_effect(element, ref_fn) {
302
413
  ref(element, () => ref_fn);
303
414
  });
304
415
  }
416
+
305
417
  /**
306
- * A render block that applies a spread of attributes to an element.
307
- * @param {any} element
308
- * @param {any} fn
418
+ * @typedef {{ e: Element; f: () => any; s: SpreadState | undefined }} RenderSpreadState
419
+ */
420
+
421
+ /**
422
+ * @param {RenderSpreadState} s
423
+ */
424
+ function run_spread(s) {
425
+ s.s = spread(s.e, s.f(), s.s);
426
+ }
427
+
428
+ /**
429
+ * A render block that applies a spread of attributes to an element: for a
430
+ * dynamic element, whose attributes are its props literal. Compiled
431
+ * elements apply their spread from their own render function instead.
432
+ * @param {Element} element
433
+ * @param {() => any} fn
309
434
  * @param {number} [flags]
435
+ * @returns {Block}
310
436
  */
311
437
  export function render_spread(element, fn, flags = 0) {
312
- return block(RENDER_BLOCK | flags, apply_element_spread(element, fn));
438
+ return block(RENDER_BLOCK | flags, run_spread, { e: element, f: fn, s: undefined });
313
439
  }
@@ -15,6 +15,10 @@ import {
15
15
  HEAD_BLOCK,
16
16
  IF_BLOCK,
17
17
  CREATES_DERIVEDS,
18
+ COMPOSITE_BLOCK,
19
+ DEFAULT_NAMESPACE,
20
+ SVG_BLOCK,
21
+ MATHML_BLOCK,
18
22
  } from './constants.js';
19
23
  import { hydrating } from './hydration.js';
20
24
  import { next_sibling } from './operations.js';
@@ -31,6 +35,7 @@ import {
31
35
  run_teardown,
32
36
  schedule_update,
33
37
  untrack,
38
+ active_namespace,
34
39
  } from './runtime.js';
35
40
  import { is_ripple_object } from './utils.js';
36
41
  import { effect_orphan } from './errors.js';
@@ -263,7 +268,10 @@ export function create_block(flags, fn, state = null, co) {
263
268
  co: co || active_component,
264
269
  d: null,
265
270
  first: null,
266
- f: flags,
271
+ f:
272
+ active_namespace === DEFAULT_NAMESPACE
273
+ ? flags
274
+ : flags | (active_namespace === 'svg' ? SVG_BLOCK : MATHML_BLOCK),
267
275
  fn,
268
276
  i: ++block_id,
269
277
  last: null,
@@ -449,8 +457,8 @@ export function remove_block_dom(node, end) {
449
457
  export function move_block(block, target) {
450
458
  var f = block.f;
451
459
 
452
- // Only branch and if blocks (excluding TRY_BLOCK) can have DOM state to move
453
- if ((f & (BRANCH_BLOCK | IF_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) {
460
+ // Only branch, if and composite blocks (excluding TRY_BLOCK) can have DOM state to move
461
+ if ((f & (BRANCH_BLOCK | IF_BLOCK | COMPOSITE_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) {
454
462
  var s = block.s;
455
463
  if (s !== null && s.start !== null) {
456
464
  var node = s.start;
@@ -493,7 +501,7 @@ export function move_block(block, target) {
493
501
  */
494
502
  export function get_first_node(block) {
495
503
  var f = block.f;
496
- if ((f & (BRANCH_BLOCK | IF_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) {
504
+ if ((f & (BRANCH_BLOCK | IF_BLOCK | COMPOSITE_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) {
497
505
  var s = block.s;
498
506
  if (s !== null && s.start !== null) {
499
507
  return s.start;
@@ -517,7 +525,7 @@ export function get_first_node(block) {
517
525
  */
518
526
  export function get_last_node(block) {
519
527
  var f = block.f;
520
- if ((f & (BRANCH_BLOCK | IF_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) {
528
+ if ((f & (BRANCH_BLOCK | IF_BLOCK | COMPOSITE_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) {
521
529
  var s = block.s;
522
530
  if (s !== null && s.start !== null) {
523
531
  // An if's materialized anchor is its last node.
@@ -551,7 +559,9 @@ export function destroy_block(block, remove_dom = true) {
551
559
  }
552
560
 
553
561
  if (
554
- (remove_dom && (f & (BRANCH_BLOCK | ROOT_BLOCK | IF_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) ||
562
+ (remove_dom &&
563
+ (f & (BRANCH_BLOCK | ROOT_BLOCK | IF_BLOCK | COMPOSITE_BLOCK)) !== 0 &&
564
+ (f & TRY_BLOCK) === 0) ||
555
565
  (f & HEAD_BLOCK) !== 0
556
566
  ) {
557
567
  var s = block.s;