Merge branch 'aa' into aa-coordination

aa-coordination
Rich Harris 8 months ago
commit 3763ff39af

@ -66,7 +66,7 @@ You can return a function from `$effect`, which will run immediately before the
### Understanding dependencies ### 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=)): 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=)):

@ -11,4 +11,4 @@ The `{@const ...}` tag defines a local constant.
{/each} {/each}
``` ```
`{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — a `<Component />` or a `<svelte:boundary`. `{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — a `<Component />` or a `<svelte:boundary>`.

@ -235,7 +235,7 @@ You can give the `<select>` a default value by adding a `selected` attribute to
## `<video>` ## `<video>`
`<video>` elements have all the same bindings as [#audio] elements, plus readonly [`videoWidth`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoWidth) and [`videoHeight`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoHeight) bindings. `<video>` elements have all the same bindings as [`<audio>`](#audio) elements, plus readonly [`videoWidth`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoWidth) and [`videoHeight`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoHeight) bindings.
## `<img>` ## `<img>`

@ -33,10 +33,7 @@ If a component defines `@keyframes`, the name is scoped to the component using t
/* these keyframes are only accessible inside this component */ /* these keyframes are only accessible inside this component */
@keyframes bounce { @keyframes bounce {
/* ... *. /* ... */
} }
</style> </style>
``` ```

@ -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 `<svelte:boundary>`, or running any [`$effect`]($effect) functions contained therein, the contents will be removed. If an error occurs while rendering or updating the children of a `<svelte:boundary>`, 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 ## Properties

@ -1,5 +1,21 @@
# svelte # 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 ## 5.19.5
### Patch Changes ### Patch Changes

@ -2,7 +2,7 @@
"name": "svelte", "name": "svelte",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"license": "MIT", "license": "MIT",
"version": "5.19.5", "version": "5.19.6",
"type": "module", "type": "module",
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"engines": { "engines": {

@ -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 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, // 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 rules = get_parent_rules(rule);
const include_self = const include_self =
rules.some((r) => r.prelude.children.some((c) => c.children.some((s) => is_global(s, r)))) || rules.some((r) => r.prelude.children.some((c) => c.children.some((s) => is_global(s, r)))) ||
rules[rules.length - 1].prelude.children.some((c) => rules[rules.length - 1].prelude.children.some((c) =>
c.children.some((r) => 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) { if (include_self) {

@ -756,7 +756,8 @@ export function check_element(node, context) {
name === 'aria-activedescendant' && name === 'aria-activedescendant' &&
!is_dynamic_element && !is_dynamic_element &&
!is_interactive_element(node.name, attribute_map) && !is_interactive_element(node.name, attribute_map) &&
!attribute_map.has('tabindex') !attribute_map.has('tabindex') &&
!has_spread
) { ) {
w.a11y_aria_activedescendant_has_tabindex(attribute); w.a11y_aria_activedescendant_has_tabindex(attribute);
} }
@ -810,9 +811,9 @@ export function check_element(node, context) {
const role = roles_map.get(current_role); const role = roles_map.get(current_role);
if (role) { if (role) {
const required_role_props = Object.keys(role.requiredProps); const required_role_props = Object.keys(role.requiredProps);
const has_missing_props = required_role_props.some( const has_missing_props =
(prop) => !attributes.find((a) => a.name === prop) !has_spread &&
); required_role_props.some((prop) => !attributes.find((a) => a.name === prop));
if (has_missing_props) { if (has_missing_props) {
w.a11y_role_has_required_aria_props( w.a11y_role_has_required_aria_props(
attribute, attribute,
@ -828,6 +829,7 @@ export function check_element(node, context) {
// interactive-supports-focus // interactive-supports-focus
if ( if (
!has_spread &&
!has_disabled_attribute(attribute_map) && !has_disabled_attribute(attribute_map) &&
!is_hidden_from_screen_reader(node.name, attribute_map) && !is_hidden_from_screen_reader(node.name, attribute_map) &&
!is_presentation_role(current_role) && !is_presentation_role(current_role) &&
@ -845,6 +847,7 @@ export function check_element(node, context) {
// no-interactive-element-to-noninteractive-role // no-interactive-element-to-noninteractive-role
if ( if (
!has_spread &&
is_interactive_element(node.name, attribute_map) && is_interactive_element(node.name, attribute_map) &&
(is_non_interactive_roles(current_role) || is_presentation_role(current_role)) (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 // no-noninteractive-element-to-interactive-role
if ( if (
!has_spread &&
is_non_interactive_element(node.name, attribute_map) && is_non_interactive_element(node.name, attribute_map) &&
is_interactive_roles(current_role) && is_interactive_roles(current_role) &&
!a11y_non_interactive_element_to_interactive_role_exceptions[node.name]?.includes( !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 // no-noninteractive-element-interactions
if ( if (
!has_spread &&
!has_contenteditable_attr && !has_contenteditable_attr &&
!is_hidden_from_screen_reader(node.name, attribute_map) && !is_hidden_from_screen_reader(node.name, attribute_map) &&
!is_presentation_role(role_static_value) && !is_presentation_role(role_static_value) &&
@ -964,6 +969,7 @@ export function check_element(node, context) {
// no-static-element-interactions // no-static-element-interactions
if ( if (
!has_spread &&
(!role || role_static_value !== null) && (!role || role_static_value !== null) &&
!is_hidden_from_screen_reader(node.name, attribute_map) && !is_hidden_from_screen_reader(node.name, attribute_map) &&
!is_presentation_role(role_static_value) && !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'); 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'); 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') { if (node.name === 'a' || node.name === 'button') {
const is_hidden = get_static_value(attribute_map.get('aria-hidden')) === 'true'; 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); w.a11y_consider_explicit_label(node);
} }
} }
@ -1054,7 +1060,7 @@ export function check_element(node, context) {
if (node.name === 'img') { if (node.name === 'img') {
const alt_attribute = get_static_text_value(attribute_map.get('alt')); const alt_attribute = get_static_text_value(attribute_map.get('alt'));
const aria_hidden = get_static_value(attribute_map.get('aria-hidden')); 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)) { if (/\b(image|picture|photo)\b/i.test(alt_attribute)) {
w.a11y_img_redundant_alt(node); w.a11y_img_redundant_alt(node);
} }
@ -1087,7 +1093,7 @@ export function check_element(node, context) {
); );
return has; 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); w.a11y_label_has_associated_control(node);
} }
} }
@ -1095,7 +1101,7 @@ export function check_element(node, context) {
if (node.name === 'video') { if (node.name === 'video') {
const aria_hidden_attribute = attribute_map.get('aria-hidden'); const aria_hidden_attribute = attribute_map.get('aria-hidden');
const aria_hidden_exist = aria_hidden_attribute && get_static_value(aria_hidden_attribute); 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; return;
} }
let has_caption = false; let has_caption = false;
@ -1141,6 +1147,7 @@ export function check_element(node, context) {
// Check content // Check content
if ( if (
!has_spread &&
!is_labelled && !is_labelled &&
!has_contenteditable_binding && !has_contenteditable_binding &&
a11y_required_content.includes(node.name) && a11y_required_content.includes(node.name) &&

@ -80,18 +80,19 @@ export function validate_slot_attribute(context, attribute, is_component = false
} }
if (owner) { if (owner) {
if (!is_text_attribute(attribute)) {
e.slot_attribute_invalid(attribute);
}
if ( if (
owner.type === 'Component' || owner.type === 'Component' ||
owner.type === 'SvelteComponent' || owner.type === 'SvelteComponent' ||
owner.type === 'SvelteSelf' owner.type === 'SvelteSelf'
) { ) {
if (owner !== parent) { if (owner !== parent) {
if (!is_component) {
e.slot_attribute_invalid_placement(attribute); 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;
@ -117,6 +118,7 @@ export function validate_slot_attribute(context, attribute, is_component = false
} }
} }
} }
}
} else if (!is_component) { } else if (!is_component) {
e.slot_attribute_invalid_placement(attribute); e.slot_attribute_invalid_placement(attribute);
} }

@ -8,7 +8,8 @@ import {
active_effect, active_effect,
active_reaction, active_reaction,
set_active_effect, set_active_effect,
set_active_reaction set_active_reaction,
untrack
} from './runtime.js'; } from './runtime.js';
import { effect } from './reactivity/effects.js'; import { effect } from './reactivity/effects.js';
import { legacy_mode_flag } from '../flags/index.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) { export function getContext(key) {
const context_map = get_or_init_context_map('getContext'); const context_map = get_or_init_context_map('getContext');
const result = /** @type {T} */ (context_map.get(key)); 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; return result;
} }
@ -74,6 +67,15 @@ export function getContext(key) {
*/ */
export function setContext(key, context) { export function setContext(key, context) {
const context_map = get_or_init_context_map('setContext'); 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); context_map.set(key, context);
return context; return context;
} }
@ -100,16 +102,6 @@ export function hasContext(key) {
*/ */
export function getAllContexts() { export function getAllContexts() {
const context_map = get_or_init_context_map('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); return /** @type {T} */ (context_map);
} }

@ -109,7 +109,7 @@ export function mark_module_end(component) {
/** /**
* @param {any} object * @param {any} object
* @param {any} owner * @param {any | null} owner
* @param {boolean} [global] * @param {boolean} [global]
* @param {boolean} [skip_warning] * @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)) { if (metadata && !has_owner(metadata, component)) {
let original = get_owner(metadata); 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]); w.ownership_invalid_binding(component[FILENAME], owner[FILENAME], original[FILENAME]);
} }
} }
@ -165,7 +165,7 @@ export function widen_ownership(from, to) {
/** /**
* @param {any} object * @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<any>} seen * @param {Set<any>} seen
*/ */
function add_owner_to_object(object, owner, seen) { function add_owner_to_object(object, owner, seen) {
@ -174,7 +174,11 @@ function add_owner_to_object(object, owner, seen) {
if (metadata) { if (metadata) {
// this is a state proxy, add owner directly, if not globally shared // this is a state proxy, add owner directly, if not globally shared
if ('owners' in metadata && metadata.owners != null) { if ('owners' in metadata && metadata.owners != null) {
if (owner) {
metadata.owners.add(owner); metadata.owners.add(owner);
} else {
metadata.owners = null;
}
} }
} else if (object && typeof object === 'object') { } else if (object && typeof object === 'object') {
if (seen.has(object)) return; if (seen.has(object)) return;
@ -216,6 +220,10 @@ function has_owner(metadata, component) {
return ( return (
metadata.owners.has(component) || 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)) (metadata.parent !== null && has_owner(metadata.parent, component))
); );
} }

@ -320,7 +320,7 @@ export function suspend() {
return function unsuspend() { return function unsuspend() {
// @ts-ignore // @ts-ignore
boundary?.fn(ASYNC_DECREMENT); boundary?.fn?.(ASYNC_DECREMENT);
}; };
} }

@ -219,17 +219,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
} }
if (!hydrating) { if (!hydrating) {
var effect = /** @type {Effect} */ (active_reaction); reconcile(array, state, anchor, render_fn, flags, get_key, get_collection);
reconcile(
array,
state,
anchor,
render_fn,
flags,
(effect.f & INERT) !== 0,
get_key,
get_collection
);
} }
if (fallback_fn !== null) { 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 {Element | Comment | Text} anchor
* @param {(anchor: Node, item: MaybeSource<V>, index: number | Source<number>, collection: () => V[]) => void} render_fn * @param {(anchor: Node, item: MaybeSource<V>, index: number | Source<number>, collection: () => V[]) => void} render_fn
* @param {number} flags * @param {number} flags
* @param {boolean} is_inert
* @param {(value: V, index: number) => any} get_key * @param {(value: V, index: number) => any} get_key
* @param {() => V[]} get_collection * @param {() => V[]} get_collection
* @returns {void} * @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 is_animated = (flags & EACH_IS_ANIMATED) !== 0;
var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 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) { while (current !== null && current.k !== key) {
// If the each block isn't inert and an item has an effect that is already inert, // 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 // 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); (seen ??= new Set()).add(current);
} }
stashed.push(current); stashed.push(current);
@ -444,7 +433,7 @@ function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key, ge
while (current !== null) { 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 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); to_destroy.push(current);
} }
current = current.next; current = current.next;

@ -838,12 +838,19 @@ function process_effects(effect, collected_effects) {
current_effect.f ^= CLEAN; current_effect.f ^= CLEAN;
} }
} else if (!skip_suspended) { } else if (!skip_suspended) {
// 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 { try {
active_reaction = current_effect;
if (check_dirtiness(current_effect)) { if (check_dirtiness(current_effect)) {
update_effect(current_effect); update_effect(current_effect);
} }
} catch (error) { } catch (error) {
handle_error(error, current_effect, null, current_effect.ctx); handle_error(error, current_effect, null, current_effect.ctx);
} finally {
active_reaction = previous_active_reaction;
} }
} }
@ -1022,15 +1029,13 @@ export function get(signal) {
var derived = /** @type {Derived} */ (signal); var derived = /** @type {Derived} */ (signal);
var parent = derived.parent; 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 // 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 // as the derived value might have been referenced in a different context
// since and thus its parent might not be its true owner anymore // since and thus its parent might not be its true owner anymore
if ((parent.f & UNOWNED) === 0) {
derived.f ^= UNOWNED; derived.f ^= UNOWNED;
} }
} }
}
if (is_derived) { if (is_derived) {
derived = /** @type {Derived} */ (signal); derived = /** @type {Derived} */ (signal);

@ -4,5 +4,5 @@
* The current version, as set in package.json. * The current version, as set in package.json.
* @type {string} * @type {string}
*/ */
export const VERSION = '5.19.5'; export const VERSION = '5.19.6';
export const PUBLIC_VERSION = '5'; export const PUBLIC_VERSION = '5';

@ -197,6 +197,20 @@ export default test({
column: 16, column: 16,
character: 1614 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
}
} }
] ]
}); });

@ -136,3 +136,10 @@
color: red; color: red;
}*/ }*/
} }
.foo:has(x.svelte-xyz) {
color: green;
}
/* (unused) :global(.foo):has(.unused) {
color: red;
}*/

@ -148,4 +148,11 @@
color: red; color: red;
} }
} }
:global(.foo):has(x) {
color: green;
}
:global(.foo):has(.unused) {
color: red;
}
</style> </style>

@ -0,0 +1,5 @@
<script>
let { value = $bindable() } = $props()
</script>
<button onclick={() => value = 'a'}>change</button>

@ -0,0 +1,5 @@
<script>
let {disabled = false} = $props()
</script>
{disabled}

@ -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, `<button>change</button><button>change</button>\nfalse`);
}
});

@ -0,0 +1,11 @@
<script>
import Child2 from './Child2.svelte'
import Child from './Child.svelte'
let loginname = $state('')
let password = $state('')
</script>
<Child bind:value={loginname} />
<Child bind:value={password} />
<Child2 disabled={!loginname || !password} />

@ -10,3 +10,4 @@
<label>E <span></span></label> <label>E <span></span></label>
<label>F {#if true}<input type="text" />{/if}</label> <label>F {#if true}<input type="text" />{/if}</label>
<LabelComponent>G <input type="text" /></LabelComponent> <LabelComponent>G <input type="text" /></LabelComponent>
<label {...forMightBeInHere}>E <span></span></label>

@ -1,21 +1,19 @@
<script> <script>
// Even if otherProps contains onBlur and/or onFocus, the rule will still fail.
// Props should be passed down explicitly for rule to pass.
const otherProps = { const otherProps = {
onBlur: () => void 0, onblur: () => {},
onFocus: () => void 0 onfocus: () => {}
}; };
</script> </script>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:mouseover={() => void 0}></div> <div onmouseover={() => {}}></div>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:mouseover={() => void 0} on:focus={() => void 0}></div> <div onmouseover={() => {}} onfocus={() => {}}></div>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:mouseover={() => void 0} {...otherProps}></div> <div onmouseover={() => {}} {...otherProps}></div>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:mouseout={() => void 0}></div> <div onmouseout={() => {}}></div>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:mouseout={() => void 0} on:blur={() => void 0}></div> <div onmouseout={() => {}} onblur={() => {}}></div>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:mouseout={() => void 0} {...otherProps}></div> <div onmouseout={() => {}} {...otherProps}></div>

@ -2,49 +2,25 @@
{ {
"code": "a11y_mouse_events_have_key_events", "code": "a11y_mouse_events_have_key_events",
"end": { "end": {
"column": 39, "column": 34,
"line": 11 "line": 9
}, },
"message": "'mouseover' event must be accompanied by 'focus' event", "message": "'mouseover' event must be accompanied by 'focus' event",
"start": { "start": {
"column": 0, "column": 0,
"line": 11 "line": 9
} }
}, },
{ {
"code": "a11y_mouse_events_have_key_events", "code": "a11y_mouse_events_have_key_events",
"end": { "end": {
"column": 55, "column": 33,
"line": 15 "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", "message": "'mouseout' event must be accompanied by 'blur' event",
"start": { "start": {
"column": 0, "column": 0,
"line": 17 "line": 15
}
},
{
"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
} }
} }
] ]

@ -1,2 +1,8 @@
<Foo slot="foo">valid</Foo> <Foo slot="foo">valid</Foo>
<Foo slot={foo}>valid</Foo> <Foo slot={foo}>valid</Foo>
<Foo>
{#if true}
<Foo slot="foo">valid</Foo>
<Foo slot={foo}>valid</Foo>
{/if}
</Foo>
Loading…
Cancel
Save