diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f7d15f905e..0e2628f84f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,7 +51,7 @@ We use [GitHub issues](https://github.com/sveltejs/svelte/issues) for our public If you have questions about using Svelte, contact us on Discord at [svelte.dev/chat](https://svelte.dev/chat), and we will do our best to answer your questions. -If you see anything you'd like to be implemented, create a [feature request issue](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml) +If you see anything you'd like to be implemented, create a [feature request issue](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml). ### Reporting new issues diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index ba912c1dd8..3b42ad6927 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,15 @@ # svelte +## 5.19.4 + +### Patch Changes + +- fix: Add `bind:focused` property to `HTMLAttributes` type ([#15122](https://github.com/sveltejs/svelte/pull/15122)) + +- fix: lazily connect derievds (in deriveds) to their parent ([#15129](https://github.com/sveltejs/svelte/pull/15129)) + +- fix: disallow $state/$derived in const tags ([#15115](https://github.com/sveltejs/svelte/pull/15115)) + ## 5.19.3 ### Patch Changes diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index 96f1589800..6d256b5620 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -839,6 +839,7 @@ export interface HTMLAttributes extends AriaAttributes, D readonly 'bind:contentBoxSize'?: Array | undefined | null; readonly 'bind:borderBoxSize'?: Array | undefined | null; readonly 'bind:devicePixelContentBoxSize'?: Array | undefined | null; + readonly 'bind:focused'?: boolean | undefined | null; // SvelteKit 'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null; diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 76ce768cb5..f94992e42e 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.19.3", + "version": "5.19.4", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js index 41a167d35d..6b45c9af1b 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -80,7 +80,8 @@ export function CallExpression(node, context) { case '$derived': case '$derived.by': if ( - parent.type !== 'VariableDeclarator' && + (parent.type !== 'VariableDeclarator' || + get_parent(context.path, -3).type === 'ConstTag') && !(parent.type === 'PropertyDefinition' && !parent.static && !parent.computed) ) { e.state_invalid_placement(node, rune); diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index a5f5420968..459b670db1 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -72,10 +72,6 @@ export function derived(fn) { signal.created = get_stack('CreatedAt'); } - if (parent_derived !== null) { - (parent_derived.children ??= []).push(signal); - } - return signal; } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 40a52a4aec..a29802dbb9 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1018,26 +1018,30 @@ export function get(signal) { signal.reactions.push(active_reaction); } } - } else if (is_derived && /** @type {Derived} */ (signal).deps === null) { + } + + if ( + is_derived && + /** @type {Derived} */ (signal).deps === null && + (active_reaction === null || untracking || (active_reaction.f & DERIVED) !== 0) + ) { var derived = /** @type {Derived} */ (signal); var parent = derived.parent; - var target = derived; - while (parent !== null) { - // Attach the derived to the nearest parent effect, if there are deriveds - // in between then we also need to attach them too + if (parent !== null) { + // Attach the derived to the nearest parent effect or derived if ((parent.f & DERIVED) !== 0) { var parent_derived = /** @type {Derived} */ (parent); - target = parent_derived; - parent = parent_derived.parent; + if (!parent_derived.children?.includes(derived)) { + (parent_derived.children ??= []).push(derived); + } } else { var parent_effect = /** @type {Effect} */ (parent); - if (!parent_effect.deriveds?.includes(target)) { - (parent_effect.deriveds ??= []).push(target); + if (!parent_effect.deriveds?.includes(derived)) { + (parent_effect.deriveds ??= []).push(derived); } - break; } } } diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 0e5fec3b81..b45197aa9c 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.19.3'; +export const VERSION = '5.19.4'; export const PUBLIC_VERSION = '5'; diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index e147fd1d0d..5f0b93e136 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -803,6 +803,33 @@ describe('signals', () => { }; }); + test('nested deriveds do not connect inside parent deriveds if unused', () => { + return () => { + let a = render_effect(() => {}); + let b: Derived | undefined; + + const destroy = effect_root(() => { + a = render_effect(() => { + $.untrack(() => { + b = derived(() => { + derived(() => {}); + derived(() => {}); + derived(() => {}); + }); + $.get(b); + }); + }); + }); + + assert.deepEqual(a.deriveds?.length, 1); + assert.deepEqual(b?.children, null); + + destroy(); + + assert.deepEqual(a.deriveds, null); + }; + }); + test('deriveds containing effects work correctly when used with untrack', () => { return () => { let a = render_effect(() => {}); diff --git a/packages/svelte/tests/types/bindings.svelte b/packages/svelte/tests/types/bindings.svelte new file mode 100644 index 0000000000..ce99b2c296 --- /dev/null +++ b/packages/svelte/tests/types/bindings.svelte @@ -0,0 +1,8 @@ + + + + + +
diff --git a/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/errors.json b/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/errors.json new file mode 100644 index 0000000000..32594e4268 --- /dev/null +++ b/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "state_invalid_placement", + "message": "`$derived(...)` can only be used as a variable declaration initializer or a class field", + "start": { + "line": 2, + "column": 15 + }, + "end": { + "line": 2, + "column": 26 + } + } +] diff --git a/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/input.svelte b/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/input.svelte new file mode 100644 index 0000000000..a056058cc5 --- /dev/null +++ b/packages/svelte/tests/validator/samples/const-tag-invalid-rune-usage/input.svelte @@ -0,0 +1,3 @@ +{#snippet test()} + {@const der = $derived(0)} +{/snippet} \ No newline at end of file