From 522557559f22b47cbe2b1c782e5a363b72ea146a Mon Sep 17 00:00:00 2001 From: 7nik Date: Thu, 30 Jan 2025 16:51:25 +0200 Subject: [PATCH 01/13] fix: do not prune selectors like `:global(.foo):has(.scoped)` (#15140) Fixes #14910 The issue occurs only when :has() targets at a component's root element and because include_self is false. I came to the conclusion that this is the same case as :root:has(.scoped). --- .changeset/famous-bulldogs-tan.md | 5 +++++ .../src/compiler/phases/2-analyze/css/css-prune.js | 9 +++++++-- packages/svelte/tests/css/samples/has/_config.js | 14 ++++++++++++++ packages/svelte/tests/css/samples/has/expected.css | 7 +++++++ packages/svelte/tests/css/samples/has/input.svelte | 7 +++++++ 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 .changeset/famous-bulldogs-tan.md diff --git a/.changeset/famous-bulldogs-tan.md b/.changeset/famous-bulldogs-tan.md new file mode 100644 index 0000000000..a5cc14b7f2 --- /dev/null +++ b/.changeset/famous-bulldogs-tan.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: do not prune selectors like `:global(.foo):has(.scoped)` diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js index 109010e88c..e719895798 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js @@ -339,13 +339,18 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element) let sibling_elements; // do them lazy because it's rarely used and expensive to calculate // If this is a :has inside a global selector, we gotta include the element itself, too, - // because the global selector might be for an element that's outside the component (e.g. :root). + // because the global selector might be for an element that's outside the component, + // e.g. :root:has(.scoped), :global(.foo):has(.scoped), or :root { &:has(.scoped) {} } const rules = get_parent_rules(rule); const include_self = rules.some((r) => r.prelude.children.some((c) => c.children.some((s) => is_global(s, r)))) || rules[rules.length - 1].prelude.children.some((c) => c.children.some((r) => - r.selectors.some((s) => s.type === 'PseudoClassSelector' && s.name === 'root') + r.selectors.some( + (s) => + s.type === 'PseudoClassSelector' && + (s.name === 'root' || (s.name === 'global' && s.args)) + ) ) ); if (include_self) { diff --git a/packages/svelte/tests/css/samples/has/_config.js b/packages/svelte/tests/css/samples/has/_config.js index 33bbe74949..8d89d98cbd 100644 --- a/packages/svelte/tests/css/samples/has/_config.js +++ b/packages/svelte/tests/css/samples/has/_config.js @@ -197,6 +197,20 @@ export default test({ column: 16, character: 1614 } + }, + { + code: 'css_unused_selector', + message: 'Unused CSS selector ":global(.foo):has(.unused)"', + start: { + line: 155, + column: 1, + character: 1684 + }, + end: { + line: 155, + column: 27, + character: 1710 + } } ] }); diff --git a/packages/svelte/tests/css/samples/has/expected.css b/packages/svelte/tests/css/samples/has/expected.css index 9627bf730c..b257370d61 100644 --- a/packages/svelte/tests/css/samples/has/expected.css +++ b/packages/svelte/tests/css/samples/has/expected.css @@ -136,3 +136,10 @@ color: red; }*/ } + + .foo:has(x.svelte-xyz) { + color: green; + } + /* (unused) :global(.foo):has(.unused) { + color: red; + }*/ diff --git a/packages/svelte/tests/css/samples/has/input.svelte b/packages/svelte/tests/css/samples/has/input.svelte index 946cf2df90..9b254996bf 100644 --- a/packages/svelte/tests/css/samples/has/input.svelte +++ b/packages/svelte/tests/css/samples/has/input.svelte @@ -148,4 +148,11 @@ color: red; } } + + :global(.foo):has(x) { + color: green; + } + :global(.foo):has(.unused) { + color: red; + } From f5406c952ed446c131eeef3da152c345bd952568 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:14:03 +0100 Subject: [PATCH 02/13] fix: widen ownership when calling setContext (#15153) Instead of doing ownership addition at each `getContext` call site, we do it once as the `setContext` call site and make the state basically global. - avoids potential false positives in edge cases - makes the whole thing more performant, especially around things like calling `getContext` inside a list item component - false negatives are unlikely from this fixes #15072 --- .changeset/unlucky-gorillas-hunt.md | 5 ++++ .../svelte/src/internal/client/context.js | 30 +++++++------------ .../src/internal/client/dev/ownership.js | 12 +++++--- 3 files changed, 24 insertions(+), 23 deletions(-) create mode 100644 .changeset/unlucky-gorillas-hunt.md diff --git a/.changeset/unlucky-gorillas-hunt.md b/.changeset/unlucky-gorillas-hunt.md new file mode 100644 index 0000000000..055fc120a6 --- /dev/null +++ b/.changeset/unlucky-gorillas-hunt.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: widen ownership when calling setContext diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index e1088edf30..bd94d5ad8a 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -8,7 +8,8 @@ import { active_effect, active_reaction, set_active_effect, - set_active_reaction + set_active_reaction, + untrack } from './runtime.js'; import { effect } from './reactivity/effects.js'; import { legacy_mode_flag } from '../flags/index.js'; @@ -49,14 +50,6 @@ export function set_dev_current_component_function(fn) { export function getContext(key) { const context_map = get_or_init_context_map('getContext'); const result = /** @type {T} */ (context_map.get(key)); - - if (DEV) { - const fn = /** @type {ComponentContext} */ (component_context).function; - if (fn) { - add_owner(result, fn, true); - } - } - return result; } @@ -74,6 +67,15 @@ export function getContext(key) { */ export function setContext(key, context) { const context_map = get_or_init_context_map('setContext'); + + if (DEV) { + // When state is put into context, we treat as if it's global from now on. + // We do for performance reasons (it's for example very expensive to call + // getContext on a big object many times when part of a list component) + // and danger of false positives. + untrack(() => add_owner(context, null, true)); + } + context_map.set(key, context); return context; } @@ -100,16 +102,6 @@ export function hasContext(key) { */ export function getAllContexts() { const context_map = get_or_init_context_map('getAllContexts'); - - if (DEV) { - const fn = component_context?.function; - if (fn) { - for (const value of context_map.values()) { - add_owner(value, fn, true); - } - } - } - return /** @type {T} */ (context_map); } diff --git a/packages/svelte/src/internal/client/dev/ownership.js b/packages/svelte/src/internal/client/dev/ownership.js index 70cfbb47f3..a9506cfdc0 100644 --- a/packages/svelte/src/internal/client/dev/ownership.js +++ b/packages/svelte/src/internal/client/dev/ownership.js @@ -109,7 +109,7 @@ export function mark_module_end(component) { /** * @param {any} object - * @param {any} owner + * @param {any | null} owner * @param {boolean} [global] * @param {boolean} [skip_warning] */ @@ -120,7 +120,7 @@ export function add_owner(object, owner, global = false, skip_warning = false) { if (metadata && !has_owner(metadata, component)) { let original = get_owner(metadata); - if (owner[FILENAME] !== component[FILENAME] && !skip_warning) { + if (owner && owner[FILENAME] !== component[FILENAME] && !skip_warning) { w.ownership_invalid_binding(component[FILENAME], owner[FILENAME], original[FILENAME]); } } @@ -165,7 +165,7 @@ export function widen_ownership(from, to) { /** * @param {any} object - * @param {Function} owner + * @param {Function | null} owner If `null`, then the object is globally owned and will not be checked * @param {Set} seen */ function add_owner_to_object(object, owner, seen) { @@ -174,7 +174,11 @@ function add_owner_to_object(object, owner, seen) { if (metadata) { // this is a state proxy, add owner directly, if not globally shared if ('owners' in metadata && metadata.owners != null) { - metadata.owners.add(owner); + if (owner) { + metadata.owners.add(owner); + } else { + metadata.owners = null; + } } } else if (object && typeof object === 'object') { if (seen.has(object)) return; From b8607f876577614b1a5f798165343526bbb74b0b Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:14:22 +0100 Subject: [PATCH 03/13] fix: silence a11y attribute warnings when spread attributes present (#15150) Fixes #15067 --- .changeset/odd-rules-hear.md | 5 +++ .../phases/2-analyze/visitors/shared/a11y.js | 27 +++++++++------ .../input.svelte | 1 + .../input.svelte | 18 +++++----- .../warnings.json | 34 +++---------------- 5 files changed, 36 insertions(+), 49 deletions(-) create mode 100644 .changeset/odd-rules-hear.md diff --git a/.changeset/odd-rules-hear.md b/.changeset/odd-rules-hear.md new file mode 100644 index 0000000000..325b8ddf96 --- /dev/null +++ b/.changeset/odd-rules-hear.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: silence a11y attribute warnings when spread attributes present diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y.js index a5ca8463a4..24a8e5122d 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y.js @@ -756,7 +756,8 @@ export function check_element(node, context) { name === 'aria-activedescendant' && !is_dynamic_element && !is_interactive_element(node.name, attribute_map) && - !attribute_map.has('tabindex') + !attribute_map.has('tabindex') && + !has_spread ) { w.a11y_aria_activedescendant_has_tabindex(attribute); } @@ -810,9 +811,9 @@ export function check_element(node, context) { const role = roles_map.get(current_role); if (role) { const required_role_props = Object.keys(role.requiredProps); - const has_missing_props = required_role_props.some( - (prop) => !attributes.find((a) => a.name === prop) - ); + const has_missing_props = + !has_spread && + required_role_props.some((prop) => !attributes.find((a) => a.name === prop)); if (has_missing_props) { w.a11y_role_has_required_aria_props( attribute, @@ -828,6 +829,7 @@ export function check_element(node, context) { // interactive-supports-focus if ( + !has_spread && !has_disabled_attribute(attribute_map) && !is_hidden_from_screen_reader(node.name, attribute_map) && !is_presentation_role(current_role) && @@ -845,6 +847,7 @@ export function check_element(node, context) { // no-interactive-element-to-noninteractive-role if ( + !has_spread && is_interactive_element(node.name, attribute_map) && (is_non_interactive_roles(current_role) || is_presentation_role(current_role)) ) { @@ -853,6 +856,7 @@ export function check_element(node, context) { // no-noninteractive-element-to-interactive-role if ( + !has_spread && is_non_interactive_element(node.name, attribute_map) && is_interactive_roles(current_role) && !a11y_non_interactive_element_to_interactive_role_exceptions[node.name]?.includes( @@ -947,6 +951,7 @@ export function check_element(node, context) { // no-noninteractive-element-interactions if ( + !has_spread && !has_contenteditable_attr && !is_hidden_from_screen_reader(node.name, attribute_map) && !is_presentation_role(role_static_value) && @@ -964,6 +969,7 @@ export function check_element(node, context) { // no-static-element-interactions if ( + !has_spread && (!role || role_static_value !== null) && !is_hidden_from_screen_reader(node.name, attribute_map) && !is_presentation_role(role_static_value) && @@ -981,11 +987,11 @@ export function check_element(node, context) { } } - if (handlers.has('mouseover') && !handlers.has('focus')) { + if (!has_spread && handlers.has('mouseover') && !handlers.has('focus')) { w.a11y_mouse_events_have_key_events(node, 'mouseover', 'focus'); } - if (handlers.has('mouseout') && !handlers.has('blur')) { + if (!has_spread && handlers.has('mouseout') && !handlers.has('blur')) { w.a11y_mouse_events_have_key_events(node, 'mouseout', 'blur'); } @@ -995,7 +1001,7 @@ export function check_element(node, context) { if (node.name === 'a' || node.name === 'button') { const is_hidden = get_static_value(attribute_map.get('aria-hidden')) === 'true'; - if (!is_hidden && !is_labelled && !has_content(node)) { + if (!has_spread && !is_hidden && !is_labelled && !has_content(node)) { w.a11y_consider_explicit_label(node); } } @@ -1054,7 +1060,7 @@ export function check_element(node, context) { if (node.name === 'img') { const alt_attribute = get_static_text_value(attribute_map.get('alt')); const aria_hidden = get_static_value(attribute_map.get('aria-hidden')); - if (alt_attribute && !aria_hidden) { + if (alt_attribute && !aria_hidden && !has_spread) { if (/\b(image|picture|photo)\b/i.test(alt_attribute)) { w.a11y_img_redundant_alt(node); } @@ -1087,7 +1093,7 @@ export function check_element(node, context) { ); return has; }; - if (!attribute_map.has('for') && !has_input_child(node)) { + if (!has_spread && !attribute_map.has('for') && !has_input_child(node)) { w.a11y_label_has_associated_control(node); } } @@ -1095,7 +1101,7 @@ export function check_element(node, context) { if (node.name === 'video') { const aria_hidden_attribute = attribute_map.get('aria-hidden'); const aria_hidden_exist = aria_hidden_attribute && get_static_value(aria_hidden_attribute); - if (attribute_map.has('muted') || aria_hidden_exist === 'true') { + if (attribute_map.has('muted') || aria_hidden_exist === 'true' || has_spread) { return; } let has_caption = false; @@ -1141,6 +1147,7 @@ export function check_element(node, context) { // Check content if ( + !has_spread && !is_labelled && !has_contenteditable_binding && a11y_required_content.includes(node.name) && diff --git a/packages/svelte/tests/validator/samples/a11y-label-has-associated-control/input.svelte b/packages/svelte/tests/validator/samples/a11y-label-has-associated-control/input.svelte index 124888c089..f47743b33b 100644 --- a/packages/svelte/tests/validator/samples/a11y-label-has-associated-control/input.svelte +++ b/packages/svelte/tests/validator/samples/a11y-label-has-associated-control/input.svelte @@ -10,3 +10,4 @@ G + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/input.svelte b/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/input.svelte index 613b80e6d9..f9fe4f15c1 100644 --- a/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/input.svelte +++ b/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/input.svelte @@ -1,21 +1,19 @@ -
void 0}>
+
{}}>
-
void 0} on:focus={() => void 0}>
+
{}} onfocus={() => {}}>
-
void 0} {...otherProps}>
+
{}} {...otherProps}>
-
void 0}>
+
{}}>
-
void 0} on:blur={() => void 0}>
+
{}} onblur={() => {}}>
-
void 0} {...otherProps}>
+
{}} {...otherProps}>
diff --git a/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/warnings.json b/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/warnings.json index 574b019e0f..3dee4e9673 100644 --- a/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/warnings.json +++ b/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/warnings.json @@ -2,49 +2,25 @@ { "code": "a11y_mouse_events_have_key_events", "end": { - "column": 39, - "line": 11 + "column": 34, + "line": 9 }, "message": "'mouseover' event must be accompanied by 'focus' event", "start": { "column": 0, - "line": 11 + "line": 9 } }, { "code": "a11y_mouse_events_have_key_events", "end": { - "column": 55, + "column": 33, "line": 15 }, - "message": "'mouseover' event must be accompanied by 'focus' event", - "start": { - "column": 0, - "line": 15 - } - }, - { - "code": "a11y_mouse_events_have_key_events", - "end": { - "column": 38, - "line": 17 - }, "message": "'mouseout' event must be accompanied by 'blur' event", "start": { "column": 0, - "line": 17 - } - }, - { - "code": "a11y_mouse_events_have_key_events", - "end": { - "column": 54, - "line": 21 - }, - "message": "'mouseout' event must be accompanied by 'blur' event", - "start": { - "column": 0, - "line": 21 + "line": 15 } } ] From 83f00ebbd6e3400bf059690d132b31afa177c85a Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:14:46 +0100 Subject: [PATCH 04/13] fix: don't error on slot prop inside block inside other component (#15148) `slot` is treated as a regular prop if it is not used directly inside another component, but we were running validations on such regular props that should only be run on real slots. Fixes #15125 --- .changeset/good-rocks-talk.md | 5 +++ .../2-analyze/visitors/shared/attribute.js | 44 ++++++++++--------- .../slot-attribute-component/input.svelte | 6 +++ 3 files changed, 34 insertions(+), 21 deletions(-) create mode 100644 .changeset/good-rocks-talk.md diff --git a/.changeset/good-rocks-talk.md b/.changeset/good-rocks-talk.md new file mode 100644 index 0000000000..59af86c686 --- /dev/null +++ b/.changeset/good-rocks-talk.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't error on slot prop inside block inside other component diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/attribute.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/attribute.js index 198e464ac7..19bd7b6e54 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/attribute.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/attribute.js @@ -80,40 +80,42 @@ export function validate_slot_attribute(context, attribute, is_component = false } if (owner) { - if (!is_text_attribute(attribute)) { - e.slot_attribute_invalid(attribute); - } - if ( owner.type === 'Component' || owner.type === 'SvelteComponent' || owner.type === 'SvelteSelf' ) { if (owner !== parent) { - e.slot_attribute_invalid_placement(attribute); - } + if (!is_component) { + e.slot_attribute_invalid_placement(attribute); + } + } else { + if (!is_text_attribute(attribute)) { + e.slot_attribute_invalid(attribute); + } - const name = attribute.value[0].data; + const name = attribute.value[0].data; - if (context.state.component_slots.has(name)) { - e.slot_attribute_duplicate(attribute, name, owner.name); - } - - context.state.component_slots.add(name); + if (context.state.component_slots.has(name)) { + e.slot_attribute_duplicate(attribute, name, owner.name); + } - if (name === 'default') { - for (const node of owner.fragment.nodes) { - if (node.type === 'Text' && regex_only_whitespaces.test(node.data)) { - continue; - } + context.state.component_slots.add(name); - if (node.type === 'RegularElement' || node.type === 'SvelteFragment') { - if (node.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot')) { + if (name === 'default') { + for (const node of owner.fragment.nodes) { + if (node.type === 'Text' && regex_only_whitespaces.test(node.data)) { continue; } - } - e.slot_default_duplicate(node); + if (node.type === 'RegularElement' || node.type === 'SvelteFragment') { + if (node.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot')) { + continue; + } + } + + e.slot_default_duplicate(node); + } } } } diff --git a/packages/svelte/tests/validator/samples/slot-attribute-component/input.svelte b/packages/svelte/tests/validator/samples/slot-attribute-component/input.svelte index 5acb14e409..5d559e614e 100644 --- a/packages/svelte/tests/validator/samples/slot-attribute-component/input.svelte +++ b/packages/svelte/tests/validator/samples/slot-attribute-component/input.svelte @@ -1,2 +1,8 @@ valid valid + + {#if true} + valid + valid + {/if} + \ No newline at end of file From 970aa7cfaa70707da1b87c3ca0af264c7589db9d Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:15:04 +0100 Subject: [PATCH 05/13] fix: prevent false-positive ownership validations due to hot reload (#15154) The component identity could change due to HMR, so we fall back to checking the filenames as well fixes #14746 --- .changeset/ten-cougars-look.md | 5 +++++ packages/svelte/src/internal/client/dev/ownership.js | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 .changeset/ten-cougars-look.md diff --git a/.changeset/ten-cougars-look.md b/.changeset/ten-cougars-look.md new file mode 100644 index 0000000000..fe20d057dd --- /dev/null +++ b/.changeset/ten-cougars-look.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent false-positive ownership validations due to hot reload diff --git a/packages/svelte/src/internal/client/dev/ownership.js b/packages/svelte/src/internal/client/dev/ownership.js index a9506cfdc0..2a2527803a 100644 --- a/packages/svelte/src/internal/client/dev/ownership.js +++ b/packages/svelte/src/internal/client/dev/ownership.js @@ -220,6 +220,10 @@ function has_owner(metadata, component) { return ( metadata.owners.has(component) || + // This helps avoid false positives when using HMR, where the component function is replaced + [...metadata.owners].some( + (owner) => /** @type {any} */ (owner)[FILENAME] === /** @type {any} */ (component)?.[FILENAME] + ) || (metadata.parent !== null && has_owner(metadata.parent, component)) ); } From 7bef5963bd3c5eb05f7722e09b4e6b806c2da337 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 30 Jan 2025 18:17:27 +0000 Subject: [PATCH 06/13] fix: ensure reactions are correctly attached for unowned deriveds (#15158) * fix: ensure reactions are correctly attached for unowned deriveds * tune --- .changeset/loud-cars-scream.md | 5 +++++ packages/svelte/src/internal/client/runtime.js | 13 +++++++++---- .../samples/derived-unowned-11/Child.svelte | 5 +++++ .../samples/derived-unowned-11/Child2.svelte | 5 +++++ .../samples/derived-unowned-11/_config.js | 16 ++++++++++++++++ .../samples/derived-unowned-11/main.svelte | 11 +++++++++++ 6 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 .changeset/loud-cars-scream.md create mode 100644 packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child2.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/derived-unowned-11/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/derived-unowned-11/main.svelte diff --git a/.changeset/loud-cars-scream.md b/.changeset/loud-cars-scream.md new file mode 100644 index 0000000000..2b61bc4453 --- /dev/null +++ b/.changeset/loud-cars-scream.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure reactions are correctly attached for unowned deriveds diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index bf9d17fa23..a572e27bf4 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -802,12 +802,19 @@ function process_effects(effect, collected_effects) { if (is_branch) { current_effect.f ^= CLEAN; } else { + // Ensure we set the effect to be the active reaction + // to ensure that unowned deriveds are correctly tracked + // because we're flushing the current effect + var previous_active_reaction = active_reaction; try { + active_reaction = current_effect; if (check_dirtiness(current_effect)) { update_effect(current_effect); } } catch (error) { handle_error(error, current_effect, null, current_effect.ctx); + } finally { + active_reaction = previous_active_reaction; } } @@ -952,13 +959,11 @@ export function get(signal) { var derived = /** @type {Derived} */ (signal); var parent = derived.parent; - if (parent !== null) { + if (parent !== null && (parent.f & UNOWNED) === 0) { // If the derived is owned by another derived then mark it as unowned // as the derived value might have been referenced in a different context // since and thus its parent might not be its true owner anymore - if ((parent.f & UNOWNED) === 0) { - derived.f ^= UNOWNED; - } + derived.f ^= UNOWNED; } } diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child.svelte b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child.svelte new file mode 100644 index 0000000000..cd215304a3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child2.svelte b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child2.svelte new file mode 100644 index 0000000000..a1d9f93bec --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/Child2.svelte @@ -0,0 +1,5 @@ + + +{disabled} diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/_config.js new file mode 100644 index 0000000000..9948f91966 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + let [btn1, btn2] = target.querySelectorAll('button'); + + btn1?.click(); + flushSync(); + + btn2?.click(); + flushSync(); + + assert.htmlEqual(target.innerHTML, `\nfalse`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/main.svelte new file mode 100644 index 0000000000..0219acdf7f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-11/main.svelte @@ -0,0 +1,11 @@ + + + + + + From 6df59055e7f67328b3b3c3495120a14db4d8544b Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:46:53 +0100 Subject: [PATCH 07/13] docs: more examples on what is outside the rendering process (#15157) closes #15151 --- documentation/docs/05-special-elements/01-svelte-boundary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/05-special-elements/01-svelte-boundary.md b/documentation/docs/05-special-elements/01-svelte-boundary.md index 15f249a771..f5439b4b83 100644 --- a/documentation/docs/05-special-elements/01-svelte-boundary.md +++ b/documentation/docs/05-special-elements/01-svelte-boundary.md @@ -13,7 +13,7 @@ Boundaries allow you to guard against errors in part of your app from breaking t If an error occurs while rendering or updating the children of a ``, or running any [`$effect`]($effect) functions contained therein, the contents will be removed. -Errors occurring outside the rendering process (for example, in event handlers) are _not_ caught by error boundaries. +Errors occurring outside the rendering process (for example, in event handlers or after a `setTimeout` or async work) are _not_ caught by error boundaries. ## Properties From 674f81b5ceaa1968263b74547b7b2887f74f02fd Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Fri, 31 Jan 2025 02:48:35 +0800 Subject: [PATCH 08/13] [docs] clarify that `$effect` analyzes functions deeply (#15144) * Update 04-$effect.md * Update documentation/docs/02-runes/04-$effect.md --------- Co-authored-by: Rich Harris --- documentation/docs/02-runes/04-$effect.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md index 1ea960de70..da24084d4d 100644 --- a/documentation/docs/02-runes/04-$effect.md +++ b/documentation/docs/02-runes/04-$effect.md @@ -66,7 +66,7 @@ You can return a function from `$effect`, which will run immediately before the ### Understanding dependencies -`$effect` automatically picks up any reactive values (`$state`, `$derived`, `$props`) that are _synchronously_ read inside its function body and registers them as dependencies. When those dependencies change, the `$effect` schedules a rerun. +`$effect` automatically picks up any reactive values (`$state`, `$derived`, `$props`) that are _synchronously_ read inside its function body (including indirectly, via function calls) and registers them as dependencies. When those dependencies change, the `$effect` schedules a rerun. Values that are read _asynchronously_ — after an `await` or inside a `setTimeout`, for example — will not be tracked. Here, the canvas will be repainted when `color` changes, but not when `size` changes ([demo](/playground/untitled#H4sIAAAAAAAAE31T246bMBD9lZF3pWSlBEirfaEQqdo_2PatVIpjBrDkGGQPJGnEv1e2IZfVal-wfHzmzJyZ4cIqqdCy9M-F0blDlnqArZjmB3f72XWRHVCRw_bc4me4aDWhJstSlllhZEfbQhekkMDKfwg5PFvihMvX5OXH_CJa1Zrb0-Kpqr5jkiwC48rieuDWQbqgZ6wqFLRcvkC-hYvnkWi1dWqa8ESQTxFRjfQWsOXiWzmr0sSLhEJu3p1YsoJkNUcdZUnN9dagrBu6FVRQHAM10sJRKgUG16bXcGxQ44AGdt7SDkTDdY02iqLHnJVU6hedlWuIp94JW6Tf8oBt_8GdTxlF0b4n0C35ZLBzXb3mmYn3ae6cOW74zj0YVzDNYXRHFt9mprNgHfZSl6mzml8CMoLvTV6wTZIUDEJv5us2iwMtiJRyAKG4tXnhl8O0yhbML0Wm-B7VNlSSSd31BG7z8oIZZ6dgIffAVY_5xdU9Qrz1Bnx8fCfwtZ7v8Qc9j3nB8PqgmMWlHIID6-bkVaPZwDySfWtKNGtquxQ23Qlsq2QJT0KIqb8dL0up6xQ2eIBkAg_c1FI_YqW0neLnFCqFpwmreedJYT7XX8FVOBfwWRhXstZrSXiwKQjUhOZeMIleb5JZfHWn2Yq5pWEpmR7Hv-N_wEqT8hEEAAA=)): From 2be3823e3aaaad753d65d21a160e342bd9b1c514 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Jan 2025 13:49:01 -0500 Subject: [PATCH 09/13] chore: remove inert check from each block reconciliation (#15143) --- .../src/internal/client/dom/blocks/each.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 040e585215..3baa03a917 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -219,17 +219,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f } if (!hydrating) { - var effect = /** @type {Effect} */ (active_reaction); - reconcile( - array, - state, - anchor, - render_fn, - flags, - (effect.f & INERT) !== 0, - get_key, - get_collection - ); + reconcile(array, state, anchor, render_fn, flags, get_key, get_collection); } if (fallback_fn !== null) { @@ -273,12 +263,11 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f * @param {Element | Comment | Text} anchor * @param {(anchor: Node, item: MaybeSource, index: number | Source, collection: () => V[]) => void} render_fn * @param {number} flags - * @param {boolean} is_inert * @param {(value: V, index: number) => any} get_key * @param {() => V[]} get_collection * @returns {void} */ -function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key, get_collection) { +function reconcile(array, state, anchor, render_fn, flags, get_key, get_collection) { var is_animated = (flags & EACH_IS_ANIMATED) !== 0; var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0; @@ -420,7 +409,7 @@ function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key, ge while (current !== null && current.k !== key) { // If the each block isn't inert and an item has an effect that is already inert, // skip over adding it to our seen Set as the item is already being handled - if (is_inert || (current.e.f & INERT) === 0) { + if ((current.e.f & INERT) === 0) { (seen ??= new Set()).add(current); } stashed.push(current); @@ -444,7 +433,7 @@ function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key, ge while (current !== null) { // If the each block isn't inert, then inert effects are currently outroing and will be removed once the transition is finished - if (is_inert || (current.e.f & INERT) === 0) { + if ((current.e.f & INERT) === 0) { to_destroy.push(current); } current = current.next; From 04addca428411088045b5a1774511593b5acc040 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 13:50:00 -0500 Subject: [PATCH 10/13] Version Packages (#15156) Co-authored-by: github-actions[bot] --- .changeset/famous-bulldogs-tan.md | 5 ----- .changeset/good-rocks-talk.md | 5 ----- .changeset/loud-cars-scream.md | 5 ----- .changeset/odd-rules-hear.md | 5 ----- .changeset/ten-cougars-look.md | 5 ----- .changeset/unlucky-gorillas-hunt.md | 5 ----- packages/svelte/CHANGELOG.md | 16 ++++++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 9 files changed, 18 insertions(+), 32 deletions(-) delete mode 100644 .changeset/famous-bulldogs-tan.md delete mode 100644 .changeset/good-rocks-talk.md delete mode 100644 .changeset/loud-cars-scream.md delete mode 100644 .changeset/odd-rules-hear.md delete mode 100644 .changeset/ten-cougars-look.md delete mode 100644 .changeset/unlucky-gorillas-hunt.md diff --git a/.changeset/famous-bulldogs-tan.md b/.changeset/famous-bulldogs-tan.md deleted file mode 100644 index a5cc14b7f2..0000000000 --- a/.changeset/famous-bulldogs-tan.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: do not prune selectors like `:global(.foo):has(.scoped)` diff --git a/.changeset/good-rocks-talk.md b/.changeset/good-rocks-talk.md deleted file mode 100644 index 59af86c686..0000000000 --- a/.changeset/good-rocks-talk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't error on slot prop inside block inside other component diff --git a/.changeset/loud-cars-scream.md b/.changeset/loud-cars-scream.md deleted file mode 100644 index 2b61bc4453..0000000000 --- a/.changeset/loud-cars-scream.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: ensure reactions are correctly attached for unowned deriveds diff --git a/.changeset/odd-rules-hear.md b/.changeset/odd-rules-hear.md deleted file mode 100644 index 325b8ddf96..0000000000 --- a/.changeset/odd-rules-hear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: silence a11y attribute warnings when spread attributes present diff --git a/.changeset/ten-cougars-look.md b/.changeset/ten-cougars-look.md deleted file mode 100644 index fe20d057dd..0000000000 --- a/.changeset/ten-cougars-look.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: prevent false-positive ownership validations due to hot reload diff --git a/.changeset/unlucky-gorillas-hunt.md b/.changeset/unlucky-gorillas-hunt.md deleted file mode 100644 index 055fc120a6..0000000000 --- a/.changeset/unlucky-gorillas-hunt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: widen ownership when calling setContext diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index bb48391e0e..0e8073cc5d 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,21 @@ # svelte +## 5.19.6 + +### Patch Changes + +- fix: do not prune selectors like `:global(.foo):has(.scoped)` ([#15140](https://github.com/sveltejs/svelte/pull/15140)) + +- fix: don't error on slot prop inside block inside other component ([#15148](https://github.com/sveltejs/svelte/pull/15148)) + +- fix: ensure reactions are correctly attached for unowned deriveds ([#15158](https://github.com/sveltejs/svelte/pull/15158)) + +- fix: silence a11y attribute warnings when spread attributes present ([#15150](https://github.com/sveltejs/svelte/pull/15150)) + +- fix: prevent false-positive ownership validations due to hot reload ([#15154](https://github.com/sveltejs/svelte/pull/15154)) + +- fix: widen ownership when calling setContext ([#15153](https://github.com/sveltejs/svelte/pull/15153)) + ## 5.19.5 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index a44ba43364..a286fa119d 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.5", + "version": "5.19.6", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index a509ef3fec..03ab37b583 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.5'; +export const VERSION = '5.19.6'; export const PUBLIC_VERSION = '5'; From 6117037b649b708bf0855c20dbd39233f442989f Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 30 Jan 2025 18:55:54 +0000 Subject: [PATCH 11/13] fix HMR bug --- packages/svelte/src/internal/client/dom/blocks/boundary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index b601955c52..bd82727629 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -312,7 +312,7 @@ export function suspend() { return function unsuspend() { // @ts-ignore - boundary?.fn(ASYNC_DECREMENT); + boundary?.fn?.(ASYNC_DECREMENT); }; } From e83ab1c0382199b50b0d2255668cd3f13cc603a2 Mon Sep 17 00:00:00 2001 From: tomoam <29677552+tomoam@users.noreply.github.com> Date: Fri, 31 Jan 2025 05:12:31 +0900 Subject: [PATCH 13/13] docs: fix typos and a link (#15135) --- documentation/docs/03-template-syntax/09-@const.md | 2 +- documentation/docs/03-template-syntax/11-bind.md | 2 +- documentation/docs/04-styling/01-scoped-styles.md | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/documentation/docs/03-template-syntax/09-@const.md b/documentation/docs/03-template-syntax/09-@const.md index c42d3560fd..2a587b7a3d 100644 --- a/documentation/docs/03-template-syntax/09-@const.md +++ b/documentation/docs/03-template-syntax/09-@const.md @@ -11,4 +11,4 @@ The `{@const ...}` tag defines a local constant. {/each} ``` -`{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — a `` or a `` or a ``. diff --git a/documentation/docs/03-template-syntax/11-bind.md b/documentation/docs/03-template-syntax/11-bind.md index 90046c8c45..e56c2b4f77 100644 --- a/documentation/docs/03-template-syntax/11-bind.md +++ b/documentation/docs/03-template-syntax/11-bind.md @@ -235,7 +235,7 @@ You can give the `