Merge remote-tracking branch 'upstream/main' into fix-spread-registering-dep

pull/11290/head
paoloricciuti 2 years ago
commit 214f8992d6

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: ensure reactions are kept dirty when marking them again

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: apply modifiers to bubbled events

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: allow `bind:this` on `<select>` with dynamic `multiple` attribute

@ -0,0 +1,5 @@
---
"svelte": patch
---
feat: allow for literal property definition with state on classes

@ -0,0 +1,5 @@
---
"svelte": patch
---
feat: leave view transition pseudo selectors untouched

@ -38,6 +38,7 @@
"breezy-rules-beg",
"bright-peas-juggle",
"bright-snakes-sing",
"brown-geckos-fry",
"brown-houses-obey",
"brown-months-fry",
"brown-spoons-boil",
@ -60,6 +61,7 @@
"cool-ants-leave",
"cool-comics-marry",
"cool-peas-lick",
"cool-poems-watch",
"cool-rabbits-tickle",
"cool-roses-trade",
"cuddly-pianos-drop",
@ -71,6 +73,7 @@
"curvy-ties-shout",
"cyan-flowers-destroy",
"cyan-spies-grin",
"cyan-squids-drive",
"dirty-bats-punch",
"dirty-donuts-yell",
"dirty-garlics-design",
@ -103,6 +106,7 @@
"famous-falcons-melt",
"famous-knives-sneeze",
"famous-pants-pay",
"fast-penguins-matter",
"fast-weeks-clean",
"few-clouds-shop",
"few-mugs-fail",
@ -152,6 +156,7 @@
"grumpy-jars-sparkle",
"happy-beds-scream",
"happy-suits-film",
"healthy-ants-film",
"healthy-planes-vanish",
"heavy-comics-move",
"heavy-ducks-leave",
@ -161,6 +166,7 @@
"honest-buses-add",
"honest-dragons-turn",
"honest-icons-change",
"honest-nails-share",
"hot-jobs-tap",
"hungry-boxes-relate",
"hungry-dots-fry",
@ -172,6 +178,7 @@
"itchy-eels-marry",
"itchy-kings-deliver",
"itchy-lions-wash",
"itchy-panthers-shave",
"itchy-terms-guess",
"khaki-cooks-develop",
"khaki-ligers-sing",
@ -235,6 +242,7 @@
"neat-boats-shake",
"neat-dingos-clap",
"neat-files-rescue",
"nervous-berries-boil",
"nervous-spoons-relax",
"nervous-turkeys-end",
"new-boats-wait",
@ -262,6 +270,7 @@
"orange-crews-rescue",
"orange-dingos-poke",
"orange-yaks-protect",
"orange-zoos-heal",
"pink-bikes-agree",
"pink-goats-promise",
"pink-mayflies-tie",
@ -434,6 +443,7 @@
"tidy-starfishes-allow",
"tiny-kings-whisper",
"tiny-meals-deliver",
"tiny-moose-kiss",
"tough-radios-punch",
"twelve-dragons-join",
"twelve-onions-juggle",

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: ensure store from props is hoisted correctly

@ -1,5 +1,29 @@
# svelte
## 5.0.0-next.116
### Patch Changes
- fix: correctly interpret empty aria- attribute ([#11325](https://github.com/sveltejs/svelte/pull/11325))
- fix: disallow mixing on:click and onclick syntax ([#11295](https://github.com/sveltejs/svelte/pull/11295))
- fix: make hr, script and template valid select children ([#11344](https://github.com/sveltejs/svelte/pull/11344))
- fix: apply modifiers to bubbled events ([#11369](https://github.com/sveltejs/svelte/pull/11369))
- fix: allow `bind:this` on `<select>` with dynamic `multiple` attribute ([#11378](https://github.com/sveltejs/svelte/pull/11378))
- feat: allow for literal property definition with state on classes ([#11326](https://github.com/sveltejs/svelte/pull/11326))
- fix: disallow mounting a snippet ([#11347](https://github.com/sveltejs/svelte/pull/11347))
- feat: only inject push/init/pop when necessary ([#11319](https://github.com/sveltejs/svelte/pull/11319))
- feat: provide migration helper ([#11334](https://github.com/sveltejs/svelte/pull/11334))
- fix: ensure store from props is hoisted correctly ([#11367](https://github.com/sveltejs/svelte/pull/11367))
## 5.0.0-next.115
### Patch Changes

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

@ -182,8 +182,7 @@ function read_selector(parser, inside_pseudo_class = false) {
end: -1,
metadata: {
is_global: false,
is_host: false,
is_root: false,
is_global_like: false,
scoped: false
}
};

@ -42,7 +42,7 @@ const analysis_visitors = {
node.metadata.rule = context.state.rule;
node.metadata.used = node.children.every(
({ metadata }) => metadata.is_global || metadata.is_host || metadata.is_root
({ metadata }) => metadata.is_global || metadata.is_global_like
);
},
RelativeSelector(node, context) {
@ -57,10 +57,19 @@ const analysis_visitors = {
if (node.selectors.length === 1) {
const first = node.selectors[0];
node.metadata.is_host = first.type === 'PseudoClassSelector' && first.name === 'host';
}
node.metadata.is_root = !!node.selectors.find(
node.metadata.is_global_like ||=
(first.type === 'PseudoClassSelector' && first.name === 'host') ||
(first.type === 'PseudoElementSelector' &&
[
'view-transition',
'view-transition-group',
'view-transition-old',
'view-transition-new',
'view-transition-image-pair'
].includes(first.name));
}
node.metadata.is_global_like ||= !!node.selectors.find(
(child) => child.type === 'PseudoClassSelector' && child.name === 'root'
);
@ -87,7 +96,7 @@ const analysis_visitors = {
node.metadata.has_local_selectors = node.prelude.children.some((selector) => {
return selector.children.some(
({ metadata }) => !metadata.is_global && !metadata.is_host && !metadata.is_root
({ metadata }) => !metadata.is_global && !metadata.is_global_like
);
});
}

@ -42,8 +42,7 @@ const nesting_selector = {
],
metadata: {
is_global: false,
is_host: false,
is_root: false,
is_global_like: false,
scoped: false
}
};
@ -109,7 +108,7 @@ const visitors = {
*/
function truncate(node) {
const i = node.children.findLastIndex(({ metadata }) => {
return !metadata.is_global && !metadata.is_host && !metadata.is_root;
return !metadata.is_global && !metadata.is_global_like;
});
return node.children.slice(0, i + 1);
@ -229,14 +228,14 @@ function mark(relative_selector, element) {
/**
* Returns `true` if the relative selector is global, meaning
* it's a `:global(...)` or `:host` or `:root` selector, or
* it's a `:global(...)` or unscopeable selector, or
* is an `:is(...)` or `:where(...)` selector that contains
* a global selector
* @param {import('#compiler').Css.RelativeSelector} selector
* @param {import('#compiler').Css.Rule} rule
*/
function is_global(selector, rule) {
if (selector.metadata.is_global || selector.metadata.is_host || selector.metadata.is_root) {
if (selector.metadata.is_global || selector.metadata.is_global_like) {
return true;
}

@ -413,7 +413,7 @@ const validation = {
}
}
if (parent.name === 'select') {
if (parent.name === 'select' && node.name !== 'this') {
const multiple = parent.attributes.find(
(a) =>
a.type === 'Attribute' &&

@ -456,27 +456,30 @@ function get_hoistable_params(node, context) {
/** @type {import('estree').Identifier[]} */
const params = [];
let added_props = false;
/**
* we only want to push if it's not already present to avoid name clashing
* We only want to push if it's not already present to avoid name clashing
* @param {import('estree').Identifier} id
*/
function safe_push(id) {
function push_unique(id) {
if (!params.find((param) => param.name === id.name)) {
params.push(id);
}
}
for (const [reference] of scope.references) {
const binding = scope.get(reference);
let binding = scope.get(reference);
if (binding !== null && !scope.declarations.has(reference) && binding.initial !== node) {
if (binding.kind === 'store_sub') {
// We need both the subscription for getting the value and the store for updating
safe_push(b.id(binding.node.name.slice(1)));
safe_push(b.id(binding.node.name));
} else if (
push_unique(b.id(binding.node.name));
binding = /** @type {import('#compiler').Binding} */ (
scope.get(binding.node.name.slice(1))
);
}
if (
// If it's a destructured derived binding, then we can extract the derived signal reference and use that.
binding.expression !== null &&
typeof binding.expression !== 'function' &&
@ -486,7 +489,7 @@ function get_hoistable_params(node, context) {
binding.expression.object.callee.name === '$.get' &&
binding.expression.object.arguments[0].type === 'Identifier'
) {
safe_push(b.id(binding.expression.object.arguments[0].name));
push_unique(b.id(binding.expression.object.arguments[0].name));
} else if (
// If we are referencing a simple $$props value, then we need to reference the object property instead
(binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
@ -494,14 +497,10 @@ function get_hoistable_params(node, context) {
binding.initial === null &&
!context.state.analysis.accessors
) {
// Handle $$props.something use-cases
if (!added_props) {
added_props = true;
safe_push(b.id('$$props'));
}
push_unique(b.id('$$props'));
} else {
// create a copy to remove start/end tags which would mess up source maps
safe_push(b.id(binding.node.name));
push_unique(b.id(binding.node.name));
}
}
}

@ -4,6 +4,7 @@ import * as b from '../../../../utils/builders.js';
import * as assert from '../../../../utils/assert.js';
import { get_prop_source, is_state_source, should_proxy_or_freeze } from '../utils.js';
import { extract_paths } from '../../../../utils/ast.js';
import { regex_invalid_identifier_chars } from '../../../patterns.js';
/** @type {import('../types.js').ComponentVisitors} */
export const javascript_visitors_runes = {
@ -20,9 +21,13 @@ export const javascript_visitors_runes = {
for (const definition of node.body) {
if (
definition.type === 'PropertyDefinition' &&
(definition.key.type === 'Identifier' || definition.key.type === 'PrivateIdentifier')
(definition.key.type === 'Identifier' ||
definition.key.type === 'PrivateIdentifier' ||
definition.key.type === 'Literal')
) {
const { type, name } = definition.key;
const type = definition.key.type;
const name = get_name(definition.key);
if (!name) continue;
const is_private = type === 'PrivateIdentifier';
if (is_private) private_ids.push(name);
@ -79,9 +84,12 @@ export const javascript_visitors_runes = {
for (const definition of node.body) {
if (
definition.type === 'PropertyDefinition' &&
(definition.key.type === 'Identifier' || definition.key.type === 'PrivateIdentifier')
(definition.key.type === 'Identifier' ||
definition.key.type === 'PrivateIdentifier' ||
definition.key.type === 'Literal')
) {
const name = definition.key.name;
const name = get_name(definition.key);
if (!name) continue;
const is_private = definition.key.type === 'PrivateIdentifier';
const field = (is_private ? private_state : public_state).get(name);
@ -160,7 +168,6 @@ export const javascript_visitors_runes = {
);
}
}
continue;
}
}
@ -437,3 +444,14 @@ export const javascript_visitors_runes = {
context.next();
}
};
/**
* @param {import('estree').Identifier | import('estree').PrivateIdentifier | import('estree').Literal} node
*/
function get_name(node) {
if (node.type === 'Literal') {
return node.value?.toString().replace(regex_invalid_identifier_chars, '_');
} else {
return node.name;
}
}

@ -1186,8 +1186,11 @@ function serialize_render_stmt(state) {
* @param {import('../types.js').ComponentContext} context
*/
function serialize_event_handler(node, { state, visit }) {
/** @type {import('estree').Expression} */
let handler;
if (node.expression) {
let handler = node.expression;
handler = node.expression;
// Event handlers can be dynamic (source/store/prop/conditional etc)
const dynamic_handler = () =>
@ -1225,6 +1228,16 @@ function serialize_event_handler(node, { state, visit }) {
} else {
handler = /** @type {import('estree').Expression} */ (visit(handler));
}
} else {
state.analysis.needs_props = true;
// Function + .call to preserve "this" context as much as possible
handler = b.function(
null,
[b.id('$$arg')],
b.block([b.stmt(b.call('$.bubble_event.call', b.this, b.id('$$props'), b.id('$$arg')))])
);
}
if (node.modifiers.includes('stopPropagation')) {
handler = b.call('$.stopPropagation', handler);
@ -1243,16 +1256,6 @@ function serialize_event_handler(node, { state, visit }) {
}
return handler;
} else {
state.analysis.needs_props = true;
// Function + .call to preserve "this" context as much as possible
return b.function(
null,
[b.id('$$arg')],
b.block([b.stmt(b.call('$.bubble_event.call', b.this, b.id('$$props'), b.id('$$arg')))])
);
}
}
/**

@ -15,6 +15,8 @@ export const regex_only_whitespaces = /^[ \t\n\r\f]+$/;
export const regex_not_newline_characters = /[^\n]/g;
export const regex_is_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
// used in replace all to remove all invalid chars from a literal identifier
export const regex_invalid_identifier_chars = /(^[^a-zA-Z_$]|[^a-zA-Z0-9_$])/g;
export const regex_starts_with_vowel = /^[aeiou]/;
export const regex_heading_tags = /^h[1-6]$/;

@ -56,9 +56,10 @@ export namespace Css {
combinator: null | Combinator;
selectors: SimpleSelector[];
metadata: {
/** :global(..) */
is_global: boolean;
is_host: boolean;
is_root: boolean;
/** :root, :host, ::view-transition */
is_global_like: boolean;
scoped: boolean;
};
}

@ -780,7 +780,7 @@ export function invalidate_inner_signals(fn) {
/**
* @param {import('#client').Value} signal
* @param {number} to_status
* @param {number} to_status should be DIRTY or MAYBE_DIRTY
* @param {boolean} force_schedule
* @returns {void}
*/
@ -793,15 +793,15 @@ export function mark_reactions(signal, to_status, force_schedule) {
for (var i = 0; i < length; i++) {
var reaction = reactions[i];
var flags = reaction.f;
// We skip any effects that are already dirty (but not unowned). Additionally, we also
// We skip any effects that are already dirty. Additionally, we also
// skip if the reaction is the same as the current effect (except if we're not in runes or we
// are in force schedule mode).
if ((!force_schedule || !runes) && reaction === current_effect) {
if ((flags & DIRTY) !== 0 || ((!force_schedule || !runes) && reaction === current_effect)) {
continue;
}
var flags = reaction.f;
set_signal_status(reaction, to_status);
// If the signal is not clean, then skip over it with the exception of unowned signals that

@ -6,5 +6,5 @@
* https://svelte.dev/docs/svelte-compiler#svelte-version
* @type {string}
*/
export const VERSION = '5.0.0-next.115';
export const VERSION = '5.0.0-next.116';
export const PUBLIC_VERSION = '5';

@ -0,0 +1,3 @@
import { test } from '../../test';
export default test({});

@ -0,0 +1,9 @@
@starting-style {
.card.svelte-xyz{
height: 0;
}
}
.card.svelte-xyz {
color: red;
}

@ -0,0 +1,12 @@
<div class="card"></div>
<style>
@starting-style {
.card{
height: 0;
}
}
.card {
color: red;
}
</style>

@ -0,0 +1,16 @@
::view-transition {
animation-duration: 0.5s;
}
::view-transition-group(foo) {
animation-duration: 0.5s;
}
::view-transition-old {
animation-duration: 0.5s;
}
::view-transition-new {
animation-duration: 0.5s;
}
::view-transition-image-pair {
animation-duration: 0.5s;
}

@ -0,0 +1,17 @@
<style>
::view-transition {
animation-duration: 0.5s;
}
::view-transition-group(foo) {
animation-duration: 0.5s;
}
::view-transition-old {
animation-duration: 0.5s;
}
::view-transition-new {
animation-duration: 0.5s;
}
::view-transition-image-pair {
animation-duration: 0.5s;
}
</style>

@ -0,0 +1,12 @@
import { ok, test } from '../../test';
export default test({
async test({ assert, component, target }) {
const button = target.querySelector('button');
ok(button);
await button.click();
assert.ok(component.default_was_prevented);
}
});

@ -0,0 +1,11 @@
<script>
import Button from "./button.svelte";
export let default_was_prevented;
function handle_click(event) {
default_was_prevented = event.defaultPrevented;
}
</script>
<Button on:click={handle_click} />

@ -0,0 +1,15 @@
import { test } from '../../test';
export default test({
html: `<button>false</button>`,
async test({ assert, target }) {
const btn = target.querySelector('button');
await btn?.click();
assert.htmlEqual(target.innerHTML, `<button>true</button>`);
await btn?.click();
assert.htmlEqual(target.innerHTML, `<button>false</button>`);
}
});

@ -0,0 +1,12 @@
<script>
class Toggle {
"aria-pressed" = $state(false);
toggle(){
this["aria-pressed"] = !this["aria-pressed"]
}
}
const toggle = new Toggle();
</script>
<button on:click={() => toggle.toggle()}>{toggle["aria-pressed"]}</button>

@ -0,0 +1,8 @@
import { test } from '../../test';
export default test({
async test({ assert, target }) {
await target.querySelector('button')?.click();
assert.htmlEqual(target.innerHTML, `<button>0</button>`);
}
});

@ -0,0 +1,13 @@
<script>
let x = $state(1);
let y = $state(1);
let z = $derived(x*y);
</script>
<button onclick={() => {
x = 0;
// reading a derived value and then setting another source contributing to the derived
// resulting in the same value should not prevent pending render effects from updating
z;
y = 0;
}}>{z}</button>

@ -0,0 +1,18 @@
import { test } from '../../test';
export default test({
compileOptions: {
dev: true
},
async test({ assert, target }) {
const button = target.querySelector('button');
await button?.click();
assert.htmlEqual(
target.innerHTML,
`
<button>1</button>
`
);
}
});

@ -0,0 +1,8 @@
<script>
const { attrs } = $props();
function increment() {
$attrs.count++;
}
</script>
<button onclick={increment}>{$attrs.count}</button>

@ -0,0 +1,7 @@
<script>
import { writable } from "svelte/store";
import Child from "./child.svelte";
const attrs = writable({ count: 0 });
</script>
<Child {attrs} />

@ -3,11 +3,11 @@
"code": "attribute_invalid_multiple",
"message": "'multiple' attribute must be static if select uses two-way binding",
"start": {
"line": 6,
"line": 14,
"column": 19
},
"end": {
"line": 6,
"line": 14,
"column": 29
}
}

@ -3,6 +3,14 @@
let multiple;
</script>
<!-- ok -->
<select bind:this={value} {multiple}>
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<!-- error -->
<select bind:value {multiple}>
<option>1</option>
<option>2</option>

@ -1202,9 +1202,10 @@ declare module 'svelte/compiler' {
combinator: null | Combinator;
selectors: SimpleSelector[];
metadata: {
/** :global(..) */
is_global: boolean;
is_host: boolean;
is_root: boolean;
/** :root, :host, ::view-transition */
is_global_like: boolean;
scoped: boolean;
};
}

Loading…
Cancel
Save