diff --git a/.changeset/real-guests-do.md b/.changeset/real-guests-do.md new file mode 100644 index 0000000000..3f0c26a477 --- /dev/null +++ b/.changeset/real-guests-do.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve event delegation handler hoisting diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/html.js b/packages/svelte/src/compiler/phases/1-parse/utils/html.js index de2776942b..bb45f9d6db 100644 --- a/packages/svelte/src/compiler/phases/1-parse/utils/html.js +++ b/packages/svelte/src/compiler/phases/1-parse/utils/html.js @@ -121,6 +121,16 @@ function validate_code(code) { // based on http://developers.whatwg.org/syntax.html#syntax-tag-omission +const interactive_elements = new Set([ + 'a', + 'button', + 'iframe', + 'embed', + 'input', + 'select', + 'textarea' +]); + /** @type {Record>} */ const disallowed_contents = { li: new Set(['li']), @@ -143,6 +153,10 @@ const disallowed_contents = { th: new Set(['td', 'th', 'tr']) }; +for (const interactive_element of interactive_elements) { + disallowed_contents[interactive_element] = interactive_elements; +} + // can this be a child of the parent element, or does it implicitly // close it, like `
  • one
  • two`? diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index bd43aca398..35b7bbc170 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -166,6 +166,7 @@ function get_delegated_event(node, context) { return non_hoistable; } + const visited_references = new Set(); const scope = target_function.metadata.scope; for (const [reference] of scope.references) { // Bail-out if the arguments keyword is used @@ -174,6 +175,15 @@ function get_delegated_event(node, context) { } const binding = scope.get(reference); + // If we have multiple references to the same store using $ prefix, bail out. + if ( + binding !== null && + binding.kind === 'store_sub' && + visited_references.has(reference.slice(1)) + ) { + return non_hoistable; + } + if ( binding !== null && // Bail-out if the the binding is a rest param @@ -188,6 +198,7 @@ function get_delegated_event(node, context) { ) { return non_hoistable; } + visited_references.add(reference); } return { type: 'hoistable', function: target_function }; } 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 305dbcd543..7a0f8af075 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -363,8 +363,7 @@ function get_hoistable_params(node, context) { binding.kind === 'prop' && !binding.reassigned && binding.initial === null && - !context.state.analysis.accessors && - context.state.analysis.runes + !context.state.analysis.accessors ) { // Handle $$props.something use-cases if (!added_props) { diff --git a/packages/svelte/tests/runtime-legacy/samples/store-reference/_config.js b/packages/svelte/tests/runtime-legacy/samples/store-reference/_config.js new file mode 100644 index 0000000000..eceba7a93f --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/store-reference/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { dev: true }, // tests `@validate_store` code generation + + html: `` +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/store-reference/main.svelte b/packages/svelte/tests/runtime-legacy/samples/store-reference/main.svelte new file mode 100644 index 0000000000..2afc46d257 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/store-reference/main.svelte @@ -0,0 +1,12 @@ + + +