diff --git a/.changeset/chilly-dolphins-lick.md b/.changeset/chilly-dolphins-lick.md new file mode 100644 index 0000000000..1941f0a6a0 --- /dev/null +++ b/.changeset/chilly-dolphins-lick.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: remove selector api diff --git a/.changeset/eight-steaks-shout.md b/.changeset/eight-steaks-shout.md new file mode 100644 index 0000000000..62dbebafaf --- /dev/null +++ b/.changeset/eight-steaks-shout.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correct update_block index type diff --git a/.changeset/khaki-mails-draw.md b/.changeset/khaki-mails-draw.md new file mode 100644 index 0000000000..cebc5770f4 --- /dev/null +++ b/.changeset/khaki-mails-draw.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: tighten up signals implementation diff --git a/.changeset/quiet-camels-mate.md b/.changeset/quiet-camels-mate.md new file mode 100644 index 0000000000..6e723bd4ab --- /dev/null +++ b/.changeset/quiet-camels-mate.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: improve keyblock treeshaking diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index eed663846b..1194cffcde 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -251,6 +251,8 @@ export const function_visitor = (node, context) => { const in_constructor = parent.type === 'MethodDefinition' && parent.kind === 'constructor'; state = { ...context.state, in_constructor }; + } else { + state = { ...context.state, in_constructor: false }; } if (metadata?.hoistable === true) { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index f73528d33a..66ec08d782 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -2234,19 +2234,34 @@ export const template_visitors = { declarations.push(b.let(node.index, index)); } - context.state.after_update.push( - b.stmt( - b.call( - '$.each', - context.state.node, - each_node_meta.array_name ? each_node_meta.array_name : b.thunk(collection), - b.literal(each_type), - key_function, - b.arrow([b.id('$$anchor'), item, index], b.block(declarations.concat(children))), - else_block + if ((each_type & EACH_KEYED) !== 0) { + context.state.after_update.push( + b.stmt( + b.call( + '$.each_keyed', + context.state.node, + each_node_meta.array_name ? each_node_meta.array_name : b.thunk(collection), + b.literal(each_type), + key_function, + b.arrow([b.id('$$anchor'), item, index], b.block(declarations.concat(children))), + else_block + ) ) - ) - ); + ); + } else { + context.state.after_update.push( + b.stmt( + b.call( + '$.each_indexed', + context.state.node, + each_node_meta.array_name ? each_node_meta.array_name : b.thunk(collection), + b.literal(each_type), + b.arrow([b.id('$$anchor'), item, index], b.block(declarations.concat(children))), + else_block + ) + ) + ); + } }, IfBlock(node, context) { context.state.template.push(''); diff --git a/packages/svelte/src/internal/client/operations.js b/packages/svelte/src/internal/client/operations.js index d3e515286d..d94e20dd3e 100644 --- a/packages/svelte/src/internal/client/operations.js +++ b/packages/svelte/src/internal/client/operations.js @@ -8,21 +8,19 @@ const has_browser_globals = typeof window !== 'undefined'; // than megamorphic. const node_prototype = /** @type {Node} */ (has_browser_globals ? Node.prototype : {}); const element_prototype = /** @type {Element} */ (has_browser_globals ? Element.prototype : {}); -const event_target_prototype = /** @type {EventTarget} */ ( - has_browser_globals ? EventTarget.prototype : {} -); +const text_prototype = /** @type {Text} */ (has_browser_globals ? Text.prototype : {}); const map_prototype = Map.prototype; const append_child_method = node_prototype.appendChild; const clone_node_method = node_prototype.cloneNode; const map_set_method = map_prototype.set; const map_get_method = map_prototype.get; const map_delete_method = map_prototype.delete; -// @ts-expect-error improve perf of expando on DOM nodes for events -event_target_prototype.__click = undefined; -// @ts-expect-error improve perf of expando on DOM textValue updates -event_target_prototype.__nodeValue = ' '; +// @ts-expect-error improve perf of expando on DOM events +element_prototype.__click = undefined; +// @ts-expect-error improve perf of expando on DOM text updates +text_prototype.__nodeValue = ' '; // @ts-expect-error improve perf of expando on DOM className updates -event_target_prototype.__className = ''; +element_prototype.__className = ''; const first_child_get = /** @type {(this: Node) => ChildNode | null} */ ( // @ts-ignore @@ -162,11 +160,10 @@ export function set_class_name(node, class_name) { /** * @template {Node} N * @param {N} node - * @param {string} text * @returns {void} */ -export function text_content(node, text) { - text_content_set.call(node, text); +export function clear_text_content(node) { + text_content_set.call(node, ''); } /** @param {string} name */ diff --git a/packages/svelte/src/internal/client/reconciler.js b/packages/svelte/src/internal/client/reconciler.js index f0b0fce7c2..131c85cbd1 100644 --- a/packages/svelte/src/internal/client/reconciler.js +++ b/packages/svelte/src/internal/client/reconciler.js @@ -1,4 +1,4 @@ -import { append_child, map_get, map_set, text_content } from './operations.js'; +import { append_child, map_get, map_set, clear_text_content } from './operations.js'; import { current_hydration_fragment, get_hydration_fragment, @@ -198,7 +198,7 @@ export function reconcile_indexed_array( b_blocks = []; // Remove old blocks if (is_controlled && a !== 0) { - text_content(dom, ''); + clear_text_content(dom); } while (index < length) { block = a_blocks[index++]; @@ -260,9 +260,9 @@ export function reconcile_indexed_array( * @param {Element | Comment | Text} dom * @param {boolean} is_controlled * @param {(anchor: null, item: V, index: number | import('./types.js').Signal) => void} render_fn - * @param {Array | null} keys * @param {number} flags * @param {boolean} apply_transitions + * @param {Array | null} keys * @returns {void} */ export function reconcile_tracked_array( @@ -271,9 +271,9 @@ export function reconcile_tracked_array( dom, is_controlled, render_fn, - keys, flags, - apply_transitions + apply_transitions, + keys ) { var a_blocks = each_block.items; const is_computed_key = keys !== null; @@ -295,7 +295,7 @@ export function reconcile_tracked_array( b_blocks = []; // Remove old blocks if (is_controlled && a !== 0) { - text_content(dom, ''); + clear_text_content(dom); } while (a > 0) { block = a_blocks[--a]; diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 045d3b5bf8..e1395666c5 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -68,7 +68,7 @@ import { hydrate_block_anchor, set_current_hydration_fragment } from './hydration.js'; -import { array_from, define_property, get_descriptor, get_descriptors, is_array } from './utils.js'; +import { array_from, define_property, get_descriptor, is_array } from './utils.js'; import { is_promise } from '../common.js'; import { bind_transition } from './transitions.js'; @@ -1270,15 +1270,15 @@ function handle_event_propagation(root_element, event) { } // composedPath contains list of nodes the event has propagated through. - // We check __handled_event_at to skip all nodes below it in case this is a - // parent of the __handled_event_at node, which indicates that there's nested + // We check __root to skip all nodes below it in case this is a + // parent of the __root node, which indicates that there's nested // mounted apps. In this case we don't want to trigger events multiple times. // We're deliberately not skipping if the index is the same or higher, because // someone could create an event programmatically and emit it multiple times, // in which case we want to handle the whole propagation chain properly each time. let path_idx = 0; // @ts-expect-error is added below - const handled_at = event.__handled_event_at; + const handled_at = event.__root; if (handled_at) { const at_idx = path.indexOf(handled_at); if (at_idx < path.indexOf(root_element)) { @@ -1317,7 +1317,7 @@ function handle_event_propagation(root_element, event) { } // @ts-expect-error is used above - event.__handled_event_at = root_element; + event.__root = root_element; } /** @@ -2078,7 +2078,7 @@ function get_first_element(block) { /** * @param {import('./types.js').EachItemBlock} block * @param {any} item - * @param {import('./types.js').MaybeSignal} index + * @param {number} index * @param {number} type * @returns {void} */ @@ -2093,7 +2093,6 @@ export function update_each_item_block(block, item, index, type) { let prev_index = block.index; if (index_is_reactive) { prev_index = /** @type {import('./types.js').Signal} */ (prev_index).value; - index = /** @type {import('./types.js').Signal} */ (index).value; } const items = block.parent.items; if (prev_index !== index && /** @type {number} */ (index) < items.length) { @@ -2263,9 +2262,10 @@ export function each_item_block(item, key, index, render_fn, flags) { * @param {null | ((item: V) => string)} key_fn * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn * @param {null | ((anchor: Node) => void)} fallback_fn + * @param {typeof reconcile_indexed_array | reconcile_tracked_array} reconcile_fn * @returns {void} */ -export function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn) { +function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_fn) { const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; const block = create_each_block(flags, anchor_node); @@ -2385,20 +2385,7 @@ export function each(anchor_node, collection, flags, key_fn, render_fn, fallback const flags = block.flags; const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; const anchor_node = block.anchor; - if ((flags & EACH_KEYED) !== 0) { - reconcile_tracked_array( - array, - block, - anchor_node, - is_controlled, - render_fn, - keys, - flags, - true - ); - } else { - reconcile_indexed_array(array, block, anchor_node, is_controlled, render_fn, flags, true); - } + reconcile_fn(array, block, anchor_node, is_controlled, render_fn, flags, true, keys); }, block, true @@ -2420,12 +2407,39 @@ export function each(anchor_node, collection, flags, key_fn, render_fn, fallback fallback = fallback.prev; } // Clear the array - reconcile_indexed_array([], block, anchor_node, is_controlled, render_fn, flags, false); + reconcile_fn([], block, anchor_node, is_controlled, render_fn, flags, false, keys); destroy_signal(/** @type {import('./types.js').EffectSignal} */ (render)); }); block.effect = each; } +/** + * @template V + * @param {Element | Comment} anchor_node + * @param {() => V[]} collection + * @param {number} flags + * @param {null | ((item: V) => string)} key_fn + * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn + * @param {null | ((anchor: Node) => void)} fallback_fn + * @returns {void} + */ +export function each_keyed(anchor_node, collection, flags, key_fn, render_fn, fallback_fn) { + each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_tracked_array); +} + +/** + * @template V + * @param {Element | Comment} anchor_node + * @param {() => V[]} collection + * @param {number} flags + * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn + * @param {null | ((anchor: Node) => void)} fallback_fn + * @returns {void} + */ +export function each_indexed(anchor_node, collection, flags, render_fn, fallback_fn) { + each(anchor_node, collection, flags, null, render_fn, fallback_fn, reconcile_indexed_array); +} + /** * @param {Element | Text | Comment} anchor * @param {boolean} is_html diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index b5fddc4489..a67f300b21 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -343,7 +343,13 @@ function destroy_references(signal) { if (references !== null) { let i; for (i = 0; i < references.length; i++) { - destroy_signal(references[i]); + const reference = references[i]; + if ((reference.flags & IS_EFFECT) !== 0) { + destroy_signal(reference); + } else { + remove_consumer(reference, 0, true); + reference.dependencies = null; + } } } } @@ -710,7 +716,7 @@ export function exposable(fn) { export function get(signal) { const flags = signal.flags; if ((flags & DESTROYED) !== 0) { - return /** @type {V} */ (UNINITIALIZED); + return signal.value; } if (is_signal_exposed && current_should_capture_signal) { @@ -1156,6 +1162,11 @@ export function managed_pre_effect(init, sync) { * @returns {import('./types.js').EffectSignal} */ export function pre_effect(init) { + if (current_effect === null) { + throw new Error( + 'The Svelte $effect.pre rune can only be used during component initialisation.' + ); + } const sync = current_effect !== null && (current_effect.flags & RENDER_EFFECT) !== 0; return internal_create_effect( PRE_EFFECT, @@ -1243,105 +1254,6 @@ export function set_signal_status(signal, status) { } } -/** @template V */ -class Selector { - /** @type {Map>} */ - #consumers_map = new Map(); - - /** @type {import('./types.js').Signal} */ - #active_key; - - /** @param {V | null} [key] */ - constructor(key) { - this.#active_key = source(key || null); - } - - get current() { - return get(this.#active_key); - } - - /** - * @param {V | null} key - * @returns {void} - */ - set(key) { - const active_key = this.#active_key; - const previous_key = active_key.value; - if (previous_key === key) { - return; - } - - set_signal_value(active_key, key); - - const consumers_map = this.#consumers_map; - let consumers = map_get(consumers_map, /** @type {V} */ (previous_key)); - if (consumers !== undefined) { - this.#update_consumers(consumers); - } - - consumers = map_get(consumers_map, /** @type {V} */ (key)); - if (consumers !== undefined) { - this.#update_consumers(consumers); - } - } - - /** - * @param {Set} consumers - * @returns {void} - */ - #update_consumers(consumers) { - let consumer; - for (consumer of consumers) { - set_signal_status(consumer, DIRTY); - if ((consumer.flags & IS_EFFECT) !== 0) { - schedule_effect(/** @type {import('./types.js').EffectSignal} */ (consumer), false); - } else { - mark_signal_consumers(consumer, DIRTY, true); - } - } - } - - /** - * @param {V} key - * @returns {boolean} - */ - is(key) { - const consumers_map = this.#consumers_map; - let consumers = map_get(consumers_map, key); - if (consumers === undefined) { - consumers = new Set(); - map_set(consumers_map, key, consumers); - } - - const consumer = current_consumer; - const effect = current_effect; - if (effect !== null && consumer !== null && !consumers.has(consumer)) { - consumers.add(consumer); - push_destroy_fn(effect, () => { - const consumers_set = /** @type {Set} */ (consumers); - consumers_set.delete(effect); - if (consumers_set.size === 0) { - map_delete(consumers_map, key); - } - }); - } - return this.#active_key.value === key; - } -} - -/** - * `selector` allows you to track the currently selected item in a list in a performance optimized manner - * that runs in constant time (O(1)) - this is only noticable for very large lists. - * - * https://svelte-5-preview.vercel.app/docs/functions#selector - * @template Key - * @param {Key | null} [key] - * @returns {Selector} - */ -export function selector(key) { - return new Selector(key); -} - /** * @template V * @param {V | import('./types.js').Signal} val diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index ab24dad496..a0796cd37d 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -13,7 +13,6 @@ export { user_effect, render_effect, pre_effect, - selector, flushSync, bubble_event, safe_equal, diff --git a/packages/svelte/src/main/main-client.js b/packages/svelte/src/main/main-client.js index c6a14f3408..0971ebdc14 100644 --- a/packages/svelte/src/main/main-client.js +++ b/packages/svelte/src/main/main-client.js @@ -255,12 +255,4 @@ export function afterUpdate(fn) { // TODO bring implementations in here // (except probably untrack — do we want to expose that, if there's also a rune?) -export { - flushSync, - createRoot, - mount, - tick, - untrack, - onDestroy, - selector -} from '../internal/index.js'; +export { flushSync, createRoot, mount, tick, untrack, onDestroy } from '../internal/index.js'; diff --git a/packages/svelte/src/main/main-server.js b/packages/svelte/src/main/main-server.js index 3ff8962cf4..b9f81c5253 100644 --- a/packages/svelte/src/main/main-server.js +++ b/packages/svelte/src/main/main-server.js @@ -7,7 +7,6 @@ export { hasContext, mount, onDestroy, - selector, setContext, tick, untrack diff --git a/packages/svelte/src/transition/index.js b/packages/svelte/src/transition/index.js index 7b7485d31a..3b28b16f9d 100644 --- a/packages/svelte/src/transition/index.js +++ b/packages/svelte/src/transition/index.js @@ -7,6 +7,48 @@ function cubic_out(t) { return f * f * f + 1.0; } +/** + * https://svelte.dev/docs/svelte-easing + * @param {number} t + * @returns {number} + */ +export function cubic_in_out(t) { + return t < 0.5 ? 4.0 * t * t * t : 0.5 * Math.pow(2.0 * t - 2.0, 3.0) + 1.0; +} + +/** @param {number | string} value + * @returns {[number, string]} + */ +export function split_css_unit(value) { + const split = typeof value === 'string' && value.match(/^\s*(-?[\d.]+)([^\s]*)\s*$/); + return split ? [parseFloat(split[1]), split[2] || 'px'] : [/** @type {number} */ (value), 'px']; +} + +/** + * Animates a `blur` filter alongside an element's opacity. + * + * https://svelte.dev/docs/svelte-transition#blur + * @param {Element} node + * @param {import('./public').BlurParams} [params] + * @returns {import('./public').TransitionConfig} + */ +export function blur( + node, + { delay = 0, duration = 400, easing = cubic_in_out, amount = 5, opacity = 0 } = {} +) { + const style = getComputedStyle(node); + const target_opacity = +style.opacity; + const f = style.filter === 'none' ? '' : style.filter; + const od = target_opacity * (1 - opacity); + const [value, unit] = split_css_unit(amount); + return { + delay, + duration, + easing, + css: (_t, u) => `opacity: ${target_opacity - od * u}; filter: ${f} blur(${u * value}${unit});` + }; +} + /** * Animates the opacity of an element from 0 to the current opacity for `in` transitions and from the current opacity to 0 for `out` transitions. * @@ -25,6 +67,34 @@ export function fade(node, { delay = 0, duration = 400, easing = linear } = {}) }; } +/** + * Animates the x and y positions and the opacity of an element. `in` transitions animate from the provided values, passed as parameters to the element's default values. `out` transitions animate from the element's default values to the provided values. + * + * https://svelte.dev/docs/svelte-transition#fly + * @param {Element} node + * @param {import('./public').FlyParams} [params] + * @returns {import('./public').TransitionConfig} + */ +export function fly( + node, + { delay = 0, duration = 400, easing = cubic_out, x = 0, y = 0, opacity = 0 } = {} +) { + const style = getComputedStyle(node); + const target_opacity = +style.opacity; + const transform = style.transform === 'none' ? '' : style.transform; + const od = target_opacity * (1 - opacity); + const [x_value, x_unit] = split_css_unit(x); + const [y_value, y_unit] = split_css_unit(y); + return { + delay, + duration, + easing, + css: (t, u) => ` + transform: ${transform} translate(${(1 - t) * x_value}${x_unit}, ${(1 - t) * y_value}${y_unit}); + opacity: ${target_opacity - od * u}` + }; +} + /** * Slides an element in and out. * @@ -69,6 +139,68 @@ export function slide(node, { delay = 0, duration = 400, easing = cubic_out, axi }; } +/** + * Animates the opacity and scale of an element. `in` transitions animate from an element's current (default) values to the provided values, passed as parameters. `out` transitions animate from the provided values to an element's default values. + * + * https://svelte.dev/docs/svelte-transition#scale + * @param {Element} node + * @param {import('./public').ScaleParams} [params] + * @returns {import('./public').TransitionConfig} + */ +export function scale( + node, + { delay = 0, duration = 400, easing = cubic_out, start = 0, opacity = 0 } = {} +) { + const style = getComputedStyle(node); + const target_opacity = +style.opacity; + const transform = style.transform === 'none' ? '' : style.transform; + const sd = 1 - start; + const od = target_opacity * (1 - opacity); + return { + delay, + duration, + easing, + css: (_t, u) => ` + transform: ${transform} scale(${1 - sd * u}); + opacity: ${target_opacity - od * u} + ` + }; +} + +/** + * Animates the stroke of an SVG element, like a snake in a tube. `in` transitions begin with the path invisible and draw the path to the screen over time. `out` transitions start in a visible state and gradually erase the path. `draw` only works with elements that have a `getTotalLength` method, like `` and ``. + * + * https://svelte.dev/docs/svelte-transition#draw + * @param {SVGElement & { getTotalLength(): number }} node + * @param {import('./public').DrawParams} [params] + * @returns {import('./public').TransitionConfig} + */ +export function draw(node, { delay = 0, speed, duration, easing = cubic_in_out } = {}) { + let len = node.getTotalLength(); + const style = getComputedStyle(node); + if (style.strokeLinecap !== 'butt') { + len += parseInt(style.strokeWidth); + } + if (duration === undefined) { + if (speed === undefined) { + duration = 800; + } else { + duration = len / speed; + } + } else if (typeof duration === 'function') { + duration = duration(len); + } + return { + delay, + duration, + easing, + css: (_, u) => ` + stroke-dasharray: ${len}; + stroke-dashoffset: ${u * len}; + ` + }; +} + /** * @template T * @template S diff --git a/packages/svelte/tests/runtime-runes/samples/class-state-constructor-closure-private/_config.js b/packages/svelte/tests/runtime-runes/samples/class-state-constructor-closure-private/_config.js new file mode 100644 index 0000000000..dd847ce2f2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-state-constructor-closure-private/_config.js @@ -0,0 +1,13 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ``, + ssrHtml: ``, + + async test({ assert, target }) { + flushSync(); + + assert.htmlEqual(target.innerHTML, ``); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/class-state-constructor-closure-private/main.svelte b/packages/svelte/tests/runtime-runes/samples/class-state-constructor-closure-private/main.svelte new file mode 100644 index 0000000000..88b0398943 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-state-constructor-closure-private/main.svelte @@ -0,0 +1,14 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/class-state-constructor-closure/_config.js b/packages/svelte/tests/runtime-runes/samples/class-state-constructor-closure/_config.js new file mode 100644 index 0000000000..dd847ce2f2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-state-constructor-closure/_config.js @@ -0,0 +1,13 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ``, + ssrHtml: ``, + + async test({ assert, target }) { + flushSync(); + + assert.htmlEqual(target.innerHTML, ``); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/class-state-constructor-closure/main.svelte b/packages/svelte/tests/runtime-runes/samples/class-state-constructor-closure/main.svelte new file mode 100644 index 0000000000..fe7fcf1aa3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-state-constructor-closure/main.svelte @@ -0,0 +1,22 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/effect-cleanup/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-cleanup/_config.js new file mode 100644 index 0000000000..ee690418d6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-cleanup/_config.js @@ -0,0 +1,19 @@ +import { test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + get props() { + return { log: [] }; + }, + + async test({ assert, target, component }) { + const [b1] = target.querySelectorAll('button'); + flushSync(() => { + b1.click(); + }); + flushSync(() => { + b1.click(); + }); + assert.deepEqual(component.log, ['init 0', 'cleanup 2', 'init 2', 'cleanup 4', 'init 4']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-cleanup/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-cleanup/main.svelte new file mode 100644 index 0000000000..7c84aeddff --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-cleanup/main.svelte @@ -0,0 +1,17 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/selector/_config.js b/packages/svelte/tests/runtime-runes/samples/selector/_config.js deleted file mode 100644 index f6c8c1620d..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/selector/_config.js +++ /dev/null @@ -1,47 +0,0 @@ -import { test } from '../../test'; - -export default test({ - html: ` - - - -

- `, - - async test({ assert, target }) { - const [b1, b2, b3] = target.querySelectorAll('button'); - - await b1.click(); - assert.htmlEqual( - target.innerHTML, - ` - - - -

1

- ` - ); - - await b3.click(); - assert.htmlEqual( - target.innerHTML, - ` - - - -

3

- ` - ); - - await b2.click(); - assert.htmlEqual( - target.innerHTML, - ` - - - -

2

- ` - ); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/selector/main.svelte b/packages/svelte/tests/runtime-runes/samples/selector/main.svelte deleted file mode 100644 index b48c63ff0f..0000000000 --- a/packages/svelte/tests/runtime-runes/samples/selector/main.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - -{#each array as item} - -{/each} - -

{selected.current}

diff --git a/packages/svelte/tsconfig.json b/packages/svelte/tsconfig.json index 06104c2525..2f90cc693e 100644 --- a/packages/svelte/tsconfig.json +++ b/packages/svelte/tsconfig.json @@ -19,7 +19,7 @@ "svelte/action": ["./src/action/public.d.ts"], "svelte/compiler": ["./src/compiler/public.d.ts"], "svelte/internal": ["./src/internal/index.js"], - "svelte/legacy": ["./src/legacy/public.d.ts"], + "svelte/legacy": ["./src/legacy/legacy-client.js"], "svelte/server": ["./src/server/index.js"], "svelte/store": ["./src/store/public.d.ts"], "#compiler": ["./src/compiler/types/index.d.ts"] diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-functions.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-functions.md index 6385c53a14..b529d05cfb 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-functions.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-functions.md @@ -22,22 +22,3 @@ To prevent something from being treated as an `$effect`/`$derived` dependency, u }); ``` - -## `selector` - -`selector` allows you to track the currently selected item in a list in a performance optimized manner that runs in constant time (`O(1)`). With `selector`, you can immediately determine if an item is selected: - -```svelte - - -{#each array as item} - -{/each} - -

{selected.current}

-```