From 05b69164d11aa241f16c9d3904d91fb4aa357970 Mon Sep 17 00:00:00 2001 From: Xia Chao Date: Thu, 27 Aug 2026 21:44:40 +0800 Subject: [PATCH 1/5] fix: omit bind:focused from SSR output (#18724) --------- Co-authored-by: Xia Chao <236466140+bun-unsafe@users.noreply.github.com> Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/omit-bind-focused-ssr.md | 5 +++++ packages/svelte/src/compiler/phases/bindings.js | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .changeset/omit-bind-focused-ssr.md diff --git a/.changeset/omit-bind-focused-ssr.md b/.changeset/omit-bind-focused-ssr.md new file mode 100644 index 0000000000..80200808aa --- /dev/null +++ b/.changeset/omit-bind-focused-ssr.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: omit `bind:focused` from SSR output (it has no HTML attribute) diff --git a/packages/svelte/src/compiler/phases/bindings.js b/packages/svelte/src/compiler/phases/bindings.js index 6714a6e77d..dfe78da98c 100644 --- a/packages/svelte/src/compiler/phases/bindings.js +++ b/packages/svelte/src/compiler/phases/bindings.js @@ -23,7 +23,9 @@ export const binding_properties = { event: 'durationchange', omit_in_ssr: true }, - focused: {}, + focused: { + omit_in_ssr: true // no corresponding HTML attribute + }, paused: { valid_elements: ['audio', 'video'], omit_in_ssr: true, From edbe11ebc93c26410867748a34c4ed311d128de0 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 27 Aug 2026 16:27:08 +0200 Subject: [PATCH 2/5] fix: properly apply static textarea value attribute during CSR (#18727) Fixes #18574 Simpler alternative to #18627 - we just special-case this in the "should this be treated as a dynamic attribute" branches so `$.set_value(...)` is always generated. Given how edge-case-y this is (why would you not just write it into the body of the textarea?) this is fine. --- .changeset/polite-zoos-melt.md | 5 +++++ .../client/visitors/RegularElement.js | 1 + .../samples/textarea-value/_config.js | 17 +++++++++++------ .../samples/textarea-value/main.svelte | 3 ++- 4 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 .changeset/polite-zoos-melt.md diff --git a/.changeset/polite-zoos-melt.md b/.changeset/polite-zoos-melt.md new file mode 100644 index 0000000000..cfff0e82e4 --- /dev/null +++ b/.changeset/polite-zoos-melt.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: properly apply static textarea value attribute during CSR diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index adc9ec72dd..0a32e0dd53 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -240,6 +240,7 @@ export function RegularElement(node, context) { if ( !is_custom_element && !cannot_be_set_statically(attribute.name) && + (name !== 'value' || node.name !== 'textarea') && (attribute.value === true || is_text_attribute(attribute)) && (name !== 'class' || class_directives.length === 0) && (name !== 'style' || style_directives.length === 0) diff --git a/packages/svelte/tests/runtime-legacy/samples/textarea-value/_config.js b/packages/svelte/tests/runtime-legacy/samples/textarea-value/_config.js index 06264b653b..30e2633154 100644 --- a/packages/svelte/tests/runtime-legacy/samples/textarea-value/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/textarea-value/_config.js @@ -5,14 +5,19 @@ export default test({ return { foo: 42 }; }, - html: '', - ssrHtml: '', + ssrHtml: ' ', - test({ assert, component, target }) { - const textarea = /** @type {HTMLTextAreaElement} */ (target.querySelector('textarea')); - assert.strictEqual(textarea.value, '42'); + test({ assert, component, target, variant }) { + assert.htmlEqual( + target.innerHTML, + ` ` + ); + + const [textarea1, textarea2] = target.querySelectorAll('textarea'); + assert.strictEqual(textarea1.value, '42'); + assert.strictEqual(textarea2.value, 'static'); component.foo = 43; - assert.strictEqual(textarea.value, '43'); + assert.strictEqual(textarea1.value, '43'); } }); diff --git a/packages/svelte/tests/runtime-legacy/samples/textarea-value/main.svelte b/packages/svelte/tests/runtime-legacy/samples/textarea-value/main.svelte index 7eaf783f7f..203a3ed039 100644 --- a/packages/svelte/tests/runtime-legacy/samples/textarea-value/main.svelte +++ b/packages/svelte/tests/runtime-legacy/samples/textarea-value/main.svelte @@ -2,4 +2,5 @@ export let foo; - From 74197ccbe1d84cab76c1eefbf3def43903661691 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:05:23 +0200 Subject: [PATCH 3/5] feat: add getOrInsert/getOrInsertComputed to SvelteMap (#18728) Closes #18014 --- .changeset/tasty-years-hammer.md | 5 ++ packages/svelte/src/reactivity/map.js | 22 ++++++ packages/svelte/src/reactivity/map.test.ts | 79 ++++++++++++++++++++++ packages/svelte/types/index.d.ts | 4 ++ 4 files changed, 110 insertions(+) create mode 100644 .changeset/tasty-years-hammer.md diff --git a/.changeset/tasty-years-hammer.md b/.changeset/tasty-years-hammer.md new file mode 100644 index 0000000000..42b559ac9f --- /dev/null +++ b/.changeset/tasty-years-hammer.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: add getOrInsert/getOrInsertComputed to SvelteMap diff --git a/packages/svelte/src/reactivity/map.js b/packages/svelte/src/reactivity/map.js index 48d06a05a7..935db8fcb1 100644 --- a/packages/svelte/src/reactivity/map.js +++ b/packages/svelte/src/reactivity/map.js @@ -153,6 +153,28 @@ export class SvelteMap extends Map { return super.get(key); } + /** + * @param {K} key + * @param {V} value + * */ + getOrInsert(key, value) { + if (!super.has(key)) { + this.set(key, value); + } + return /** @type {V} */ (this.get(key)); + } + + /** + * @param {K} key + * @param {(key: K) => V} callbackFn + */ + getOrInsertComputed(key, callbackFn) { + if (!super.has(key)) { + this.set(key, callbackFn(key)); + } + return /** @type {V} */ (this.get(key)); + } + /** * @param {K} key * @param {V} value diff --git a/packages/svelte/src/reactivity/map.test.ts b/packages/svelte/src/reactivity/map.test.ts index 8bb6f72f7b..f5cbb21923 100644 --- a/packages/svelte/src/reactivity/map.test.ts +++ b/packages/svelte/src/reactivity/map.test.ts @@ -100,6 +100,85 @@ test('map.get(...)', () => { cleanup(); }); +test('map.getOrInsert(...)', () => { + const map = new SvelteMap([ + [2, 2], + [3, 3] + ]); + + const log: any = []; + + const cleanup = effect_root(() => { + render_effect(() => { + log.push('get 1', map.getOrInsert(1, 1)); + }); + + render_effect(() => { + log.push('get 2', map.getOrInsert(2, 2)); + }); + + render_effect(() => { + log.push('get 3', map.getOrInsert(3, 4)); + }); + }); + + flushSync(() => { + map.delete(2); + }); + + flushSync(() => { + map.set(2, 2); + }); + + assert.deepEqual(log, ['get 1', 1, 'get 2', 2, 'get 3', 3, 'get 2', 2]); + + cleanup(); +}); + +test('map.getOrInsertComputed(...)', () => { + const map = new SvelteMap([ + [2, 2], + [3, 3] + ]); + + const log: any = []; + + const cleanup = effect_root(() => { + render_effect(() => { + log.push( + 'get 1', + map.getOrInsertComputed(1, (k) => k) + ); + }); + + render_effect(() => { + log.push( + 'get 2', + map.getOrInsertComputed(2, (k) => k) + ); + }); + + render_effect(() => { + log.push( + 'get 3', + map.getOrInsertComputed(3, () => 4) + ); + }); + }); + + flushSync(() => { + map.delete(2); + }); + + flushSync(() => { + map.set(2, 2); + }); + + assert.deepEqual(log, ['get 1', 1, 'get 2', 2, 'get 3', 3, 'get 2', 2]); + + cleanup(); +}); + test('map.has(...)', () => { const map = new SvelteMap([ [1, 1], diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index dcc6dc5914..9e69796691 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2466,6 +2466,10 @@ declare module 'svelte/reactivity' { constructor(value?: Iterable | null | undefined); + getOrInsert(key: K, value: V): V; + + getOrInsertComputed(key: K, callbackFn: (key: K) => V): V; + set(key: K, value: V): this; #private; } From 5034b59e9e6cff42451d9136650f5fbde4c25260 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 27 Aug 2026 22:38:50 +0200 Subject: [PATCH 4/5] chore: bump playwright (#18729) supports later ubuntu versions --- package.json | 2 +- packages/svelte/package.json | 2 +- pnpm-lock.yaml | 66 ++++++++++++++++++++++++++---------- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 16fc26bc32..8004698dd1 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "eslint-plugin-lube": "^0.5.1", "eslint-plugin-svelte": "^3.15.0", "jsdom": "25.0.1", - "playwright": "^1.60.0", + "playwright": "^1.62.0", "prettier": "^3.2.4", "prettier-plugin-svelte": "^3.4.0", "svelte": "workspace:^", diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 2da4ee428d..1f85001520 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -152,7 +152,7 @@ }, "devDependencies": { "@jridgewell/trace-mapping": "^0.3.25", - "@playwright/test": "^1.60.0", + "@playwright/test": "^1.62.0", "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-terser": "^0.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70dcf70e5b..9224650e7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,8 +42,8 @@ importers: specifier: 25.0.1 version: 25.0.1 playwright: - specifier: ^1.60.0 - version: 1.60.0 + specifier: ^1.62.0 + version: 1.62.1 prettier: specifier: ^3.2.4 version: 3.2.4 @@ -121,8 +121,8 @@ importers: specifier: ^0.3.25 version: 0.3.31 '@playwright/test': - specifier: ^1.60.0 - version: 1.60.0 + specifier: ^1.62.0 + version: 1.62.1 '@rollup/plugin-commonjs': specifier: ^28.0.1 version: 28.0.1(rollup@4.60.1) @@ -723,9 +723,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@playwright/test@1.60.0': - resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} - engines: {node: '>=18'} + '@playwright/test@1.62.1': + resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==} + engines: {node: '>=20'} hasBin: true '@polka/url@1.0.0-next.25': @@ -840,131 +840,157 @@ packages: resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-gnueabihf@4.62.2': resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.1': resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm-musleabihf@4.62.2': resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.1': resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-gnu@4.62.2': resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.1': resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-musl@4.62.2': resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.1': resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-gnu@4.62.2': resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.1': resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-musl@4.62.2': resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.1': resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.62.2': resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.1': resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-musl@4.62.2': resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.1': resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.62.2': resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.1': resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-musl@4.62.2': resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.1': resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.62.2': resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.1': resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.62.2': resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.1': resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-linux-x64-musl@4.62.2': resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.60.1': resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} @@ -1875,24 +1901,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.23.0: resolution: {integrity: sha512-cU00LGb6GUXCwof6ACgSMKo3q7XYbsyTj0WsKHLi1nw7pV0NCq8nFTn6ZRBYLoKiV8t+jWl0Hv8KkgymmK5L5g==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.23.0: resolution: {integrity: sha512-q4jdx5+5NfB0/qMbXbOmuC6oo7caPnFghJbIAV90cXZqgV8Am3miZhC4p+sQVdacqxfd+3nrle4C8icR3p1AYw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.23.0: resolution: {integrity: sha512-G9Ri3qpmF4qef2CV/80dADHKXRAQeQXpQTLx7AiQrBYQHqBjB75oxqj06FCIe5g4hNCqLPnM9fsO4CyiT1sFSQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-x64-msvc@1.23.0: resolution: {integrity: sha512-1rcBDJLU+obPPJM6qR5fgBUiCdZwZLafZM5f9kwjFLkb/UBNIzmae39uCSmh71nzPCTXZqHbvwu23OWnWEz+eg==} @@ -2077,14 +2107,14 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} - playwright-core@1.60.0: - resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} - engines: {node: '>=18'} + playwright-core@1.62.1: + resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==} + engines: {node: '>=20'} hasBin: true - playwright@1.60.0: - resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} - engines: {node: '>=18'} + playwright@1.62.1: + resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==} + engines: {node: '>=20'} hasBin: true polka@1.0.0-next.25: @@ -3069,9 +3099,9 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.16.0 - '@playwright/test@1.60.0': + '@playwright/test@1.62.1': dependencies: - playwright: 1.60.0 + playwright: 1.62.1 '@polka/url@1.0.0-next.25': {} @@ -4371,11 +4401,11 @@ snapshots: pify@4.0.1: {} - playwright-core@1.60.0: {} + playwright-core@1.62.1: {} - playwright@1.60.0: + playwright@1.62.1: dependencies: - playwright-core: 1.60.0 + playwright-core: 1.62.1 optionalDependencies: fsevents: 2.3.2 From 4ae5eb3f23df00de4d60bf40ff5a96580a0719dc Mon Sep 17 00:00:00 2001 From: Adnane Taghi Date: Thu, 27 Aug 2026 21:58:40 +0100 Subject: [PATCH 5/5] feat: add has function to createContext (#18472) closes #18471. --------- Co-authored-by: Simon Holthausen --- .changeset/short-feet-admire.md | 5 +++++ documentation/docs/06-runtime/02-context.md | 2 +- .../docs/98-reference/.generated/shared-errors.md | 2 +- packages/svelte/messages/shared-errors/errors.md | 2 +- packages/svelte/src/internal/client/context.js | 6 +++--- packages/svelte/src/internal/server/context.js | 4 ++-- packages/svelte/src/internal/shared/context.js | 5 +++-- .../create-context-programmatic/Child.svelte | 8 +++++++- .../samples/create-context-programmatic/_config.js | 2 +- .../create-context-programmatic/main.svelte | 14 +++++++++----- .../samples/create-context/Child.svelte | 8 +++++++- .../samples/create-context/_config.js | 2 +- .../samples/create-context/main.svelte | 6 ++++-- packages/svelte/types/index.d.ts | 4 ++-- 14 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 .changeset/short-feet-admire.md diff --git a/.changeset/short-feet-admire.md b/.changeset/short-feet-admire.md new file mode 100644 index 0000000000..fdf56477c9 --- /dev/null +++ b/.changeset/short-feet-admire.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: add `has` function to `createContext` diff --git a/documentation/docs/06-runtime/02-context.md b/documentation/docs/06-runtime/02-context.md index a1e0347b7b..f67339c6cf 100644 --- a/documentation/docs/06-runtime/02-context.md +++ b/documentation/docs/06-runtime/02-context.md @@ -4,7 +4,7 @@ title: Context Context allows components to access values owned by parent components without passing them down as props (potentially through many layers of intermediate components, known as 'prop-drilling'). -By creating a `[get, set]` pair of functions with `createContext`, you can set the context in a parent component and get it in a child component: +By creating a `[get, set, has]` triplet of functions with `createContext`, you can set the context in a parent component and get it in a child component: ```svelte diff --git a/documentation/docs/98-reference/.generated/shared-errors.md b/documentation/docs/98-reference/.generated/shared-errors.md index d7596fc068..44616d7c8d 100644 --- a/documentation/docs/98-reference/.generated/shared-errors.md +++ b/documentation/docs/98-reference/.generated/shared-errors.md @@ -78,7 +78,7 @@ Certain lifecycle methods can only be used during component initialisation. To f Context was not set in the current component or any of its ancestors ``` -The [`createContext()`](svelte#createContext) utility returns a `[get, set]` pair of functions. `get` will throw an error if `set` was not used to set the context in the current component or any of its ancestors. +The [`createContext()`](svelte#createContext) utility returns a `[get, set, has]` triplet of functions. `get` will throw an error if `set` was not used to set the context in the current component or any of its ancestors. ### snippet_without_render_tag diff --git a/packages/svelte/messages/shared-errors/errors.md b/packages/svelte/messages/shared-errors/errors.md index 43dca57ac8..e005e34fc8 100644 --- a/packages/svelte/messages/shared-errors/errors.md +++ b/packages/svelte/messages/shared-errors/errors.md @@ -64,7 +64,7 @@ Certain lifecycle methods can only be used during component initialisation. To f > Context was not set in the current component or any of its ancestors -The [`createContext()`](svelte#createContext) utility returns a `[get, set]` pair of functions. `get` will throw an error if `set` was not used to set the context in the current component or any of its ancestors. +The [`createContext()`](svelte#createContext) utility returns a `[get, set, has]` triplet of functions. `get` will throw an error if `set` was not used to set the context in the current component or any of its ancestors. ## snippet_without_render_tag diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index e1571862ed..c3bb660e8c 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -72,17 +72,17 @@ export function set_dev_current_component_function(fn) { } /** - * Returns a `[get, set]` pair of functions for working with context in a type-safe way. + * Returns a `[get, set, has]` triplet of functions for working with context in a type-safe way. * * `get` will throw an error if `set` has not yet been called in the current component or any of * its ancestors. * * @template T - * @returns {[() => T, (context: T) => T]} + * @returns {[() => T, (context: T) => T, () => boolean]} * @since 5.40.0 */ export function createContext() { - return /** @type {[() => T, (context: T) => T]} */ ( + return /** @type {[() => T, (context: T) => T, () => boolean]} */ ( create_context(getContext, setContext, hasContext) ); } diff --git a/packages/svelte/src/internal/server/context.js b/packages/svelte/src/internal/server/context.js index de11c11282..b204c4c138 100644 --- a/packages/svelte/src/internal/server/context.js +++ b/packages/svelte/src/internal/server/context.js @@ -12,11 +12,11 @@ export function set_ssr_context(v) { /** * @template T - * @returns {[() => T, (context: T) => T]} + * @returns {[() => T, (context: T) => T, () => boolean]} * @since 5.40.0 */ export function createContext() { - return /** @type {[() => T, (context: T) => T]} */ ( + return /** @type {[() => T, (context: T) => T, () => boolean]} */ ( create_context(getContext, setContext, hasContext) ); } diff --git a/packages/svelte/src/internal/shared/context.js b/packages/svelte/src/internal/shared/context.js index 943e15e415..005c009101 100644 --- a/packages/svelte/src/internal/shared/context.js +++ b/packages/svelte/src/internal/shared/context.js @@ -5,7 +5,7 @@ import { lifecycle_outside_component, missing_context } from './errors.js'; * @param {(key: object) => T} get_context * @param {(key: object, context: T) => T} set_context * @param {(key: object) => boolean} has_context - * @returns {[() => T, (context: T) => T]} + * @returns {[() => T, (context: T) => T, () => boolean]} */ export function create_context(get_context, set_context, has_context) { const key = {}; @@ -18,7 +18,8 @@ export function create_context(get_context, set_context, has_context) { return get_context(key); }, - (context) => set_context(key, context) + (context) => set_context(key, context), + () => has_context(key) ]; } diff --git a/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/Child.svelte b/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/Child.svelte index 3e39d5043e..0255132ad6 100644 --- a/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/Child.svelte +++ b/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/Child.svelte @@ -1,7 +1,13 @@

{message}

+{#if has()} +

it's me

+{/if} +{#if !has_unset()} +

or not

+{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/_config.js b/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/_config.js index f4374c8759..c2eeca0de8 100644 --- a/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/_config.js @@ -2,7 +2,7 @@ import { test } from '../../test'; export default test({ ssrHtml: `
`, - html: `

hello

`, + html: `

hello

it's me

or not

`, test() {} }); diff --git a/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/main.svelte b/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/main.svelte index 3c42dbf180..8f4fb67d23 100644 --- a/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/create-context-programmatic/main.svelte @@ -3,9 +3,11 @@ import Child from './Child.svelte'; /** @type {ReturnType>} */ - const [get, set] = createContext(); + const [get, set, has] = createContext(); + /** @type {ReturnType>} */ + const [, , has_unset] = createContext(); - export { get }; + export { get, has, has_unset }; function Wrapper(Component) { return (...args) => { @@ -15,6 +17,8 @@ } -
{ - mount(Wrapper(Child), { target }); -}}>
+
{ + mount(Wrapper(Child), { target }); + }} +>
diff --git a/packages/svelte/tests/runtime-runes/samples/create-context/Child.svelte b/packages/svelte/tests/runtime-runes/samples/create-context/Child.svelte index 3e39d5043e..0255132ad6 100644 --- a/packages/svelte/tests/runtime-runes/samples/create-context/Child.svelte +++ b/packages/svelte/tests/runtime-runes/samples/create-context/Child.svelte @@ -1,7 +1,13 @@

{message}

+{#if has()} +

it's me

+{/if} +{#if !has_unset()} +

or not

+{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/create-context/_config.js b/packages/svelte/tests/runtime-runes/samples/create-context/_config.js index 4ae28e68bd..fa782095de 100644 --- a/packages/svelte/tests/runtime-runes/samples/create-context/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/create-context/_config.js @@ -1,5 +1,5 @@ import { test } from '../../test'; export default test({ - html: `

hello

` + html: `

hello

it's me

or not

` }); diff --git a/packages/svelte/tests/runtime-runes/samples/create-context/main.svelte b/packages/svelte/tests/runtime-runes/samples/create-context/main.svelte index 8d3c50ba55..35093f4c4e 100644 --- a/packages/svelte/tests/runtime-runes/samples/create-context/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/create-context/main.svelte @@ -2,9 +2,11 @@ import { createContext } from 'svelte'; /** @type {ReturnType>} */ - const [get, set] = createContext(); + const [get, set, has] = createContext(); + /** @type {ReturnType>} */ + const [, , has_unset] = createContext(); - export { get }; + export { get, has, has_unset };