Skip to content

Commit

Permalink
Merge branch 'aa' into aa-coordination
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris committed Jan 30, 2025
2 parents 9d9198a + 4588ad6 commit 3763ff3
Show file tree
Hide file tree
Showing 27 changed files with 192 additions and 125 deletions.
2 changes: 1 addition & 1 deletion documentation/docs/02-runes/04-$effect.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=)):

Expand Down
2 changes: 1 addition & 1 deletion documentation/docs/03-template-syntax/[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<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>`.
2 changes: 1 addition & 1 deletion documentation/docs/03-template-syntax/11-bind.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ You can give the `<select>` a default value by adding a `selected` attribute to

## `<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>`

Expand Down
5 changes: 1 addition & 4 deletions documentation/docs/04-styling/01-scoped-styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
@keyframes bounce {
/* ... *.
/* ... */
}
</style>
```



Original file line number Diff line number Diff line change
Expand Up @@ -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.

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

Expand Down
16 changes: 16 additions & 0 deletions packages/svelte/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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,
Expand All @@ -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) &&
Expand All @@ -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))
) {
Expand All @@ -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(
Expand Down Expand Up @@ -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) &&
Expand All @@ -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) &&
Expand All @@ -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');
}

Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -1087,15 +1093,15 @@ 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);
}
}

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;
Expand Down Expand Up @@ -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) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
}
Expand Down
30 changes: 11 additions & 19 deletions packages/svelte/src/internal/client/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}
Expand All @@ -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);
}

Expand Down
16 changes: 12 additions & 4 deletions packages/svelte/src/internal/client/dev/ownership.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
*/
Expand All @@ -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]);
}
}
Expand Down Expand Up @@ -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<any>} seen
*/
function add_owner_to_object(object, owner, seen) {
Expand All @@ -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;
Expand Down Expand Up @@ -216,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))
);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/dom/blocks/boundary.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ export function suspend() {

return function unsuspend() {
// @ts-ignore
boundary?.fn(ASYNC_DECREMENT);
boundary?.fn?.(ASYNC_DECREMENT);
};
}

Expand Down
Loading

0 comments on commit 3763ff3

Please sign in to comment.