diff --git a/.changeset/full-waves-tease.md b/.changeset/full-waves-tease.md new file mode 100644 index 0000000000..3915334bf7 --- /dev/null +++ b/.changeset/full-waves-tease.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: account for proxified instance when updating `bind:this` diff --git a/.changeset/hip-flowers-give.md b/.changeset/hip-flowers-give.md deleted file mode 100644 index 77f4dd8892..0000000000 --- a/.changeset/hip-flowers-give.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: do not dispatch introstart event with animation of animate directive diff --git a/.changeset/many-pandas-add.md b/.changeset/many-pandas-add.md new file mode 100644 index 0000000000..85de7acb35 --- /dev/null +++ b/.changeset/many-pandas-add.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure scheduled batch is flushed if not obsolete diff --git a/.changeset/small-tools-walk.md b/.changeset/small-tools-walk.md new file mode 100644 index 0000000000..2b275368b3 --- /dev/null +++ b/.changeset/small-tools-walk.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: allow `@debug` tags to reference awaited variables diff --git a/.changeset/smooth-poems-tap.md b/.changeset/smooth-poems-tap.md new file mode 100644 index 0000000000..ac160656cf --- /dev/null +++ b/.changeset/smooth-poems-tap.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: re-run fallback props if dependencies update diff --git a/.changeset/three-pears-build.md b/.changeset/three-pears-build.md new file mode 100644 index 0000000000..8a638149a3 --- /dev/null +++ b/.changeset/three-pears-build.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ignore comments when reading CSS values diff --git a/documentation/docs/02-runes/02-$state.md b/documentation/docs/02-runes/02-$state.md index d763b6578f..b90c71366a 100644 --- a/documentation/docs/02-runes/02-$state.md +++ b/documentation/docs/02-runes/02-$state.md @@ -167,6 +167,8 @@ To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snaps This is handy when you want to pass some state to an external library or API that doesn't expect a proxy, such as `structuredClone`. +If a value has a `toJSON` method, the snapshot will clone the value returned from `toJSON` instead of the original object. + ## `$state.eager` When state changes, it may not be reflected in the UI immediately if it is used by an `await` expression, because [updates are synchronized](await-expressions#Synchronized-updates). diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 7aa8f9818e..8e0f8a0916 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.55.5 + +### Patch Changes + +- fix: don't mark deriveds while an effect is updating ([#18124](https://github.com/sveltejs/svelte/pull/18124)) + +- fix: do not dispatch introstart event with animation of animate directive ([#18122](https://github.com/sveltejs/svelte/pull/18122)) + ## 5.55.4 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 12aa895ecf..a4ae208913 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.55.4", + "version": "5.55.5", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/ambient.d.ts b/packages/svelte/src/ambient.d.ts index 159a568477..bbbc86c997 100644 --- a/packages/svelte/src/ambient.d.ts +++ b/packages/svelte/src/ambient.d.ts @@ -147,6 +147,8 @@ declare namespace $state { * * ``` * + * If `state` has a `toJSON` method, the snapshot will clone the value returned from `toJSON` instead of the original object. + * * @see {@link https://svelte.dev/docs/svelte/$state#$state.snapshot Documentation} * * @param state The value to snapshot diff --git a/packages/svelte/src/compiler/phases/1-parse/read/style.js b/packages/svelte/src/compiler/phases/1-parse/read/style.js index 8cb1d54d54..160e5da277 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/style.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js @@ -524,6 +524,21 @@ function read_value(parser) { in_url = true; } else if ((char === ';' || char === '{' || char === '}') && !in_url && !quote_mark) { return value.trim(); + } else if ( + char === '/' && + !in_url && + !quote_mark && + parser.template[parser.index + 1] === '*' + ) { + parser.index += 2; + while (parser.index < parser.template.length) { + if (parser.template[parser.index] === '*' && parser.template[parser.index + 1] === '/') { + parser.index += 2; + break; + } + parser.index++; + } + continue; } value += char; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/DebugTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/DebugTag.js index ef9a070859..01a7e0e872 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/DebugTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/DebugTag.js @@ -8,6 +8,10 @@ import * as b from '#compiler/builders'; * @param {ComponentContext} context */ export function DebugTag(node, context) { + const blockers = node.identifiers + .map((identifier) => context.state.scope.get(identifier.name)?.blocker) + .filter((blocker) => blocker != null); + const object = b.object( node.identifiers.map((identifier) => { const visited = b.call('$.snapshot', /** @type {Expression} */ (context.visit(identifier))); @@ -20,9 +24,11 @@ export function DebugTag(node, context) { }) ); - const call = b.call('console.log', object); + const args = [b.thunk(b.block([b.stmt(b.call('console.log', object)), b.debugger]))]; - context.state.init.push( - b.stmt(b.call('$.template_effect', b.thunk(b.block([b.stmt(call), b.debugger])))) - ); + if (blockers.length > 0) { + args.push(b.array([]), b.array([]), b.array(blockers)); + } + + context.state.init.push(b.stmt(b.call('$.template_effect', ...args))); } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/DebugTag.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/DebugTag.js index 31b53fd3eb..3c4af2fe04 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/DebugTag.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/DebugTag.js @@ -2,23 +2,34 @@ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types.js' */ import * as b from '#compiler/builders'; +import { create_child_block } from './shared/utils.js'; /** * @param {AST.DebugTag} node * @param {ComponentContext} context */ export function DebugTag(node, context) { + const blockers = node.identifiers + .map((identifier) => context.state.scope.get(identifier.name)?.blocker) + .filter((blocker) => blocker != null); + context.state.template.push( - b.stmt( - b.call( - 'console.log', - b.object( - node.identifiers.map((identifier) => - b.prop('init', identifier, /** @type {Expression} */ (context.visit(identifier))) + ...create_child_block( + [ + b.stmt( + b.call( + 'console.log', + b.object( + node.identifiers.map((identifier) => + b.prop('init', identifier, /** @type {Expression} */ (context.visit(identifier))) + ) + ) ) - ) - ) - ), - b.debugger + ), + b.debugger + ], + b.array(blockers), + false + ) ); } diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/this.js b/packages/svelte/src/internal/client/dom/elements/bindings/this.js index c39ca34062..52f0c213d3 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/this.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/this.js @@ -40,7 +40,7 @@ export function bind_this(element_or_component = {}, update, get_value, get_part parts = get_parts?.() || []; untrack(() => { - if (element_or_component !== get_value(...parts)) { + if (!is_bound_this(get_value(...parts), element_or_component)) { update(element_or_component, ...parts); // If this is an effect rerun (cause: each block context changes), then nullify the binding at // the previous position if it isn't already taken over by a different effect. diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 82be1d1e8d..7adf3be00c 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -716,7 +716,7 @@ export class Batch { if (!is_flushing_sync) { queue_micro_task(() => { - if (current_batch !== batch) { + if (!batches.has(batch) || batch.#pending.size > 0) { // a flushSync happened in the meantime return; } diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js index e208d3b6f6..5626639a84 100644 --- a/packages/svelte/src/internal/client/reactivity/props.js +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -1,4 +1,4 @@ -/** @import { Effect, Source } from './types.js' */ +/** @import { Derived, Effect, Source } from './types.js' */ import { DEV } from 'esm-env'; import { PROPS_IS_BINDABLE, @@ -283,8 +283,14 @@ export function prop(props, key, flags, fallback) { var fallback_value = /** @type {V} */ (fallback); var fallback_dirty = true; + var fallback_signal = /** @type {Derived | undefined} */ (undefined); var get_fallback = () => { + if (lazy && runes) { + fallback_signal ??= derived(/** @type {() => V} */ (fallback)); + return get(fallback_signal); + } + if (fallback_dirty) { fallback_dirty = false; diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index cb8d3f76ab..04b0b0398a 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.55.4'; +export const VERSION = '5.55.5'; export const PUBLIC_VERSION = '5'; diff --git a/packages/svelte/tests/css/samples/comment-with-apostrophe/expected.css b/packages/svelte/tests/css/samples/comment-with-apostrophe/expected.css new file mode 100644 index 0000000000..a196d53cc8 --- /dev/null +++ b/packages/svelte/tests/css/samples/comment-with-apostrophe/expected.css @@ -0,0 +1,4 @@ + + p.svelte-xyz { + padding: 0 /* it's a comment */ 1em; + } diff --git a/packages/svelte/tests/css/samples/comment-with-apostrophe/input.svelte b/packages/svelte/tests/css/samples/comment-with-apostrophe/input.svelte new file mode 100644 index 0000000000..0f9e0d2355 --- /dev/null +++ b/packages/svelte/tests/css/samples/comment-with-apostrophe/input.svelte @@ -0,0 +1,7 @@ +

red

+ + diff --git a/packages/svelte/tests/runtime-runes/samples/async-debug-awaited-expression/_config.js b/packages/svelte/tests/runtime-runes/samples/async-debug-awaited-expression/_config.js new file mode 100644 index 0000000000..304f65cd0a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-debug-awaited-expression/_config.js @@ -0,0 +1,18 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + mode: ['client', 'async-server'], + + async test({ assert, logs }) { + await tick(); + + assert.deepEqual(logs, [{ data: 'works' }]); + }, + test_ssr({ assert, logs }) { + assert.deepEqual(logs, [{ data: 'works' }]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-debug-awaited-expression/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-debug-awaited-expression/main.svelte new file mode 100644 index 0000000000..92b69df8fb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-debug-awaited-expression/main.svelte @@ -0,0 +1,4 @@ + + {@const data = await Promise.resolve("works")} + {@debug data} + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-updates-microtask-separated/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-updates-microtask-separated/_config.js new file mode 100644 index 0000000000..dea121c456 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-state-updates-microtask-separated/_config.js @@ -0,0 +1,15 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +// Ensure that microtask timing doesn't influence whether or not a scheduled batch is flushed. +// Timing can be such that the current_batch is reset before the scheduled flush runs, which +// would cause the flush to skip without the fix. +export default test({ + async test({ assert, target }) { + const [btn] = target.querySelectorAll('button'); + + btn.click(); + await tick(); + assert.htmlEqual(target.innerHTML, '1 1'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-updates-microtask-separated/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-updates-microtask-separated/main.svelte new file mode 100644 index 0000000000..ebfbf4ca4e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-state-updates-microtask-separated/main.svelte @@ -0,0 +1,18 @@ + + +{#if a} + {@const toShow = await a} + {toShow} + {b} +{:else} + +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/bind-this-proxy-deep/Component.svelte b/packages/svelte/tests/runtime-runes/samples/bind-this-proxy-deep/Component.svelte new file mode 100644 index 0000000000..c43810b6bf --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-this-proxy-deep/Component.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/bind-this-proxy-deep/_config.js b/packages/svelte/tests/runtime-runes/samples/bind-this-proxy-deep/_config.js new file mode 100644 index 0000000000..2f9e60e017 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-this-proxy-deep/_config.js @@ -0,0 +1,22 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const btn = target.querySelector('button'); + + flushSync(() => { + btn?.click(); + }); + + flushSync(() => { + btn?.click(); + }); + + assert.deepEqual(logs, [ + {}, + { 0: { name: 'Row 0' } }, + { 0: { name: 'Row 0' }, 1: { name: 'Row 1' } } + ]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/bind-this-proxy-deep/main.svelte b/packages/svelte/tests/runtime-runes/samples/bind-this-proxy-deep/main.svelte new file mode 100644 index 0000000000..2e2a49b3f6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-this-proxy-deep/main.svelte @@ -0,0 +1,16 @@ + + + +{#each rows as row (row.id)} + +{/each} diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-lazy-accessors/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-default-value-lazy-accessors/main.svelte index fe2ac37bd3..f6437d6589 100644 --- a/packages/svelte/tests/runtime-runes/samples/props-default-value-lazy-accessors/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-lazy-accessors/main.svelte @@ -1,5 +1,5 @@ + + + + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-reactivity/sub.svelte b/packages/svelte/tests/runtime-runes/samples/props-default-value-reactivity/sub.svelte new file mode 100644 index 0000000000..b3cd3fae36 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-reactivity/sub.svelte @@ -0,0 +1,9 @@ + + +

greeting: {p0}

\ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-reactivity/translations.svelte.js b/packages/svelte/tests/runtime-runes/samples/props-default-value-reactivity/translations.svelte.js new file mode 100644 index 0000000000..4aa4dc9999 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-reactivity/translations.svelte.js @@ -0,0 +1,9 @@ +let greeting = $state('Hello'); + +export function get_translation() { + return greeting; +} + +export function set_translation(value) { + greeting = value; +} diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 019baf45dd..3f71d44177 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -3345,6 +3345,8 @@ declare namespace $state { * * ``` * + * If `state` has a `toJSON` method, the snapshot will clone the value returned from `toJSON` instead of the original object. + * * @see {@link https://svelte.dev/docs/svelte/$state#$state.snapshot Documentation} * * @param state The value to snapshot