From 8126ff1344eea19df73ad05b4a87328cf31b0440 Mon Sep 17 00:00:00 2001 From: Uzair-Ahmed-Shah Date: Thu, 4 Dec 2025 23:27:41 +0530 Subject: [PATCH 1/2] fix: handle dynamic multiple attribute on select --- .changeset/fix-select-multiple-dynamic.md | 5 ++++ .../2-analyze/visitors/BindDirective.js | 12 +-------- .../client/dom/elements/bindings/select.js | 2 +- .../main.svelte | 12 +++++++++ .../select-multiple-dynamic-attribute/test.ts | 26 +++++++++++++++++++ .../errors.json | 14 ---------- .../input.svelte | 18 ------------- 7 files changed, 45 insertions(+), 44 deletions(-) create mode 100644 .changeset/fix-select-multiple-dynamic.md create mode 100644 packages/svelte/tests/runtime-runes/samples/select-multiple-dynamic-attribute/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/select-multiple-dynamic-attribute/test.ts delete mode 100644 packages/svelte/tests/validator/samples/binding-select-multiple-dynamic/errors.json delete mode 100644 packages/svelte/tests/validator/samples/binding-select-multiple-dynamic/input.svelte diff --git a/.changeset/fix-select-multiple-dynamic.md b/.changeset/fix-select-multiple-dynamic.md new file mode 100644 index 000000000000..5371de9e33d4 --- /dev/null +++ b/.changeset/fix-select-multiple-dynamic.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: handle dynamic multiple attribute on select diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js index ab541703a0c9..a2ba14be1128 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js @@ -81,17 +81,7 @@ export function BindDirective(node, context) { } if (parent.name === 'select' && node.name !== 'this') { - const multiple = parent.attributes.find( - (a) => - a.type === 'Attribute' && - a.name === 'multiple' && - !is_text_attribute(a) && - a.value !== true - ); - - if (multiple) { - e.attribute_invalid_multiple(multiple); - } + // We used to forbid dynamic multiple, but we now support it } if (node.name === 'offsetWidth' && is_svg(parent.name)) { diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/select.js b/packages/svelte/src/internal/client/dom/elements/bindings/select.js index 46e8f524f8f3..03a7c021279c 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/select.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/select.js @@ -69,7 +69,7 @@ export function init_select(select) { // (doesn't get notified of select value changes, // because that property is not reflected as an attribute) attributes: true, - attributeFilter: ['value'] + attributeFilter: ['value', 'multiple'] }); teardown(() => { diff --git a/packages/svelte/tests/runtime-runes/samples/select-multiple-dynamic-attribute/main.svelte b/packages/svelte/tests/runtime-runes/samples/select-multiple-dynamic-attribute/main.svelte new file mode 100644 index 000000000000..83870b6ab5fa --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/select-multiple-dynamic-attribute/main.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/select-multiple-dynamic-attribute/test.ts b/packages/svelte/tests/runtime-runes/samples/select-multiple-dynamic-attribute/test.ts new file mode 100644 index 000000000000..aefd6de6da17 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/select-multiple-dynamic-attribute/test.ts @@ -0,0 +1,26 @@ +export async function test({ assert, target, logs }: { assert: any; target: any; logs: any }) { + const select = target.querySelector('select'); + const options = target.querySelectorAll('option'); + const [btn] = target.querySelectorAll('button'); + + assert.equal(select.multiple, false); + assert.equal(options[0].selected, false); + assert.equal(options[1].selected, true); + assert.equal(options[2].selected, false); + + btn.click(); + await Promise.resolve(); + + assert.equal(select.multiple, true); + assert.equal(options[0].selected, false); + assert.equal(options[1].selected, true); + assert.equal(options[2].selected, false); + + btn.click(); + await Promise.resolve(); + + assert.equal(select.multiple, false); + assert.equal(options[0].selected, false); + assert.equal(options[1].selected, true); + assert.equal(options[2].selected, false); +} diff --git a/packages/svelte/tests/validator/samples/binding-select-multiple-dynamic/errors.json b/packages/svelte/tests/validator/samples/binding-select-multiple-dynamic/errors.json deleted file mode 100644 index 8430d40d6199..000000000000 --- a/packages/svelte/tests/validator/samples/binding-select-multiple-dynamic/errors.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "code": "attribute_invalid_multiple", - "message": "'multiple' attribute must be static if select uses two-way binding", - "start": { - "line": 14, - "column": 19 - }, - "end": { - "line": 14, - "column": 29 - } - } -] diff --git a/packages/svelte/tests/validator/samples/binding-select-multiple-dynamic/input.svelte b/packages/svelte/tests/validator/samples/binding-select-multiple-dynamic/input.svelte deleted file mode 100644 index 4908672985ea..000000000000 --- a/packages/svelte/tests/validator/samples/binding-select-multiple-dynamic/input.svelte +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - \ No newline at end of file From 0e5d3a2eb4b3a9df7b4ce4e8f9e9cf6726f3e938 Mon Sep 17 00:00:00 2001 From: Uzair-Ahmed-Shah Date: Fri, 5 Dec 2025 01:22:21 +0530 Subject: [PATCH 2/2] fix: format file for Prettier compliance --- .../select-multiple-dynamic-attribute/test.ts | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/svelte/tests/runtime-runes/samples/select-multiple-dynamic-attribute/test.ts b/packages/svelte/tests/runtime-runes/samples/select-multiple-dynamic-attribute/test.ts index aefd6de6da17..9454e43340da 100644 --- a/packages/svelte/tests/runtime-runes/samples/select-multiple-dynamic-attribute/test.ts +++ b/packages/svelte/tests/runtime-runes/samples/select-multiple-dynamic-attribute/test.ts @@ -1,26 +1,26 @@ export async function test({ assert, target, logs }: { assert: any; target: any; logs: any }) { - const select = target.querySelector('select'); - const options = target.querySelectorAll('option'); - const [btn] = target.querySelectorAll('button'); + const select = target.querySelector('select'); + const options = target.querySelectorAll('option'); + const [btn] = target.querySelectorAll('button'); - assert.equal(select.multiple, false); - assert.equal(options[0].selected, false); - assert.equal(options[1].selected, true); - assert.equal(options[2].selected, false); + assert.equal(select.multiple, false); + assert.equal(options[0].selected, false); + assert.equal(options[1].selected, true); + assert.equal(options[2].selected, false); - btn.click(); - await Promise.resolve(); + btn.click(); + await Promise.resolve(); - assert.equal(select.multiple, true); - assert.equal(options[0].selected, false); - assert.equal(options[1].selected, true); - assert.equal(options[2].selected, false); + assert.equal(select.multiple, true); + assert.equal(options[0].selected, false); + assert.equal(options[1].selected, true); + assert.equal(options[2].selected, false); - btn.click(); - await Promise.resolve(); + btn.click(); + await Promise.resolve(); - assert.equal(select.multiple, false); - assert.equal(options[0].selected, false); - assert.equal(options[1].selected, true); - assert.equal(options[2].selected, false); + assert.equal(select.multiple, false); + assert.equal(options[0].selected, false); + assert.equal(options[1].selected, true); + assert.equal(options[2].selected, false); }