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

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

@ -1,5 +1,29 @@
# svelte # 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 ## 5.0.0-next.115
### 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.0.0-next.115", "version": "5.0.0-next.116",
"type": "module", "type": "module",
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"engines": { "engines": {

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

@ -42,7 +42,7 @@ const analysis_visitors = {
node.metadata.rule = context.state.rule; node.metadata.rule = context.state.rule;
node.metadata.used = node.children.every( 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) { RelativeSelector(node, context) {
@ -57,10 +57,19 @@ const analysis_visitors = {
if (node.selectors.length === 1) { if (node.selectors.length === 1) {
const first = node.selectors[0]; const first = node.selectors[0];
node.metadata.is_host = first.type === 'PseudoClassSelector' && first.name === 'host'; 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_root = !!node.selectors.find( node.metadata.is_global_like ||= !!node.selectors.find(
(child) => child.type === 'PseudoClassSelector' && child.name === 'root' (child) => child.type === 'PseudoClassSelector' && child.name === 'root'
); );
@ -87,7 +96,7 @@ const analysis_visitors = {
node.metadata.has_local_selectors = node.prelude.children.some((selector) => { node.metadata.has_local_selectors = node.prelude.children.some((selector) => {
return selector.children.some( 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: { metadata: {
is_global: false, is_global: false,
is_host: false, is_global_like: false,
is_root: false,
scoped: false scoped: false
} }
}; };
@ -109,7 +108,7 @@ const visitors = {
*/ */
function truncate(node) { function truncate(node) {
const i = node.children.findLastIndex(({ metadata }) => { 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); 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 * 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 * is an `:is(...)` or `:where(...)` selector that contains
* a global selector * a global selector
* @param {import('#compiler').Css.RelativeSelector} selector * @param {import('#compiler').Css.RelativeSelector} selector
* @param {import('#compiler').Css.Rule} rule * @param {import('#compiler').Css.Rule} rule
*/ */
function is_global(selector, 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; return true;
} }

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

@ -456,27 +456,30 @@ function get_hoistable_params(node, context) {
/** @type {import('estree').Identifier[]} */ /** @type {import('estree').Identifier[]} */
const params = []; 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 * @param {import('estree').Identifier} id
*/ */
function safe_push(id) { function push_unique(id) {
if (!params.find((param) => param.name === id.name)) { if (!params.find((param) => param.name === id.name)) {
params.push(id); params.push(id);
} }
} }
for (const [reference] of scope.references) { 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 !== null && !scope.declarations.has(reference) && binding.initial !== node) {
if (binding.kind === 'store_sub') { if (binding.kind === 'store_sub') {
// We need both the subscription for getting the value and the store for updating // We need both the subscription for getting the value and the store for updating
safe_push(b.id(binding.node.name.slice(1))); push_unique(b.id(binding.node.name));
safe_push(b.id(binding.node.name)); binding = /** @type {import('#compiler').Binding} */ (
} else if ( 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. // If it's a destructured derived binding, then we can extract the derived signal reference and use that.
binding.expression !== null && binding.expression !== null &&
typeof binding.expression !== 'function' && typeof binding.expression !== 'function' &&
@ -486,7 +489,7 @@ function get_hoistable_params(node, context) {
binding.expression.object.callee.name === '$.get' && binding.expression.object.callee.name === '$.get' &&
binding.expression.object.arguments[0].type === 'Identifier' 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 ( } else if (
// If we are referencing a simple $$props value, then we need to reference the object property instead // If we are referencing a simple $$props value, then we need to reference the object property instead
(binding.kind === 'prop' || binding.kind === 'bindable_prop') && (binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
@ -494,14 +497,10 @@ function get_hoistable_params(node, context) {
binding.initial === null && binding.initial === null &&
!context.state.analysis.accessors !context.state.analysis.accessors
) { ) {
// Handle $$props.something use-cases push_unique(b.id('$$props'));
if (!added_props) {
added_props = true;
safe_push(b.id('$$props'));
}
} else { } else {
// create a copy to remove start/end tags which would mess up source maps // 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 * as assert from '../../../../utils/assert.js';
import { get_prop_source, is_state_source, should_proxy_or_freeze } from '../utils.js'; import { get_prop_source, is_state_source, should_proxy_or_freeze } from '../utils.js';
import { extract_paths } from '../../../../utils/ast.js'; import { extract_paths } from '../../../../utils/ast.js';
import { regex_invalid_identifier_chars } from '../../../patterns.js';
/** @type {import('../types.js').ComponentVisitors} */ /** @type {import('../types.js').ComponentVisitors} */
export const javascript_visitors_runes = { export const javascript_visitors_runes = {
@ -20,9 +21,13 @@ export const javascript_visitors_runes = {
for (const definition of node.body) { for (const definition of node.body) {
if ( if (
definition.type === 'PropertyDefinition' && 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'; const is_private = type === 'PrivateIdentifier';
if (is_private) private_ids.push(name); if (is_private) private_ids.push(name);
@ -79,9 +84,12 @@ export const javascript_visitors_runes = {
for (const definition of node.body) { for (const definition of node.body) {
if ( if (
definition.type === 'PropertyDefinition' && 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 is_private = definition.key.type === 'PrivateIdentifier';
const field = (is_private ? private_state : public_state).get(name); const field = (is_private ? private_state : public_state).get(name);
@ -160,7 +168,6 @@ export const javascript_visitors_runes = {
); );
} }
} }
continue; continue;
} }
} }
@ -437,3 +444,14 @@ export const javascript_visitors_runes = {
context.next(); 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 * @param {import('../types.js').ComponentContext} context
*/ */
function serialize_event_handler(node, { state, visit }) { function serialize_event_handler(node, { state, visit }) {
/** @type {import('estree').Expression} */
let handler;
if (node.expression) { if (node.expression) {
let handler = node.expression; handler = node.expression;
// Event handlers can be dynamic (source/store/prop/conditional etc) // Event handlers can be dynamic (source/store/prop/conditional etc)
const dynamic_handler = () => const dynamic_handler = () =>
@ -1225,34 +1228,34 @@ function serialize_event_handler(node, { state, visit }) {
} else { } else {
handler = /** @type {import('estree').Expression} */ (visit(handler)); handler = /** @type {import('estree').Expression} */ (visit(handler));
} }
if (node.modifiers.includes('stopPropagation')) {
handler = b.call('$.stopPropagation', handler);
}
if (node.modifiers.includes('stopImmediatePropagation')) {
handler = b.call('$.stopImmediatePropagation', handler);
}
if (node.modifiers.includes('preventDefault')) {
handler = b.call('$.preventDefault', handler);
}
if (node.modifiers.includes('self')) {
handler = b.call('$.self', handler);
}
if (node.modifiers.includes('trusted')) {
handler = b.call('$.trusted', handler);
}
return handler;
} else { } else {
state.analysis.needs_props = true; state.analysis.needs_props = true;
// Function + .call to preserve "this" context as much as possible // Function + .call to preserve "this" context as much as possible
return b.function( handler = b.function(
null, null,
[b.id('$$arg')], [b.id('$$arg')],
b.block([b.stmt(b.call('$.bubble_event.call', b.this, b.id('$$props'), 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);
}
if (node.modifiers.includes('stopImmediatePropagation')) {
handler = b.call('$.stopImmediatePropagation', handler);
}
if (node.modifiers.includes('preventDefault')) {
handler = b.call('$.preventDefault', handler);
}
if (node.modifiers.includes('self')) {
handler = b.call('$.self', handler);
}
if (node.modifiers.includes('trusted')) {
handler = b.call('$.trusted', handler);
}
return handler;
} }
/** /**

@ -15,6 +15,8 @@ export const regex_only_whitespaces = /^[ \t\n\r\f]+$/;
export const regex_not_newline_characters = /[^\n]/g; export const regex_not_newline_characters = /[^\n]/g;
export const regex_is_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/; 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_starts_with_vowel = /^[aeiou]/;
export const regex_heading_tags = /^h[1-6]$/; export const regex_heading_tags = /^h[1-6]$/;

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

@ -780,7 +780,7 @@ export function invalidate_inner_signals(fn) {
/** /**
* @param {import('#client').Value} signal * @param {import('#client').Value} signal
* @param {number} to_status * @param {number} to_status should be DIRTY or MAYBE_DIRTY
* @param {boolean} force_schedule * @param {boolean} force_schedule
* @returns {void} * @returns {void}
*/ */
@ -793,15 +793,15 @@ export function mark_reactions(signal, to_status, force_schedule) {
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
var reaction = reactions[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 // 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). // are in force schedule mode).
if ((!force_schedule || !runes) && reaction === current_effect) { if ((flags & DIRTY) !== 0 || ((!force_schedule || !runes) && reaction === current_effect)) {
continue; continue;
} }
var flags = reaction.f;
set_signal_status(reaction, to_status); set_signal_status(reaction, to_status);
// If the signal is not clean, then skip over it with the exception of unowned signals that // 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 * https://svelte.dev/docs/svelte-compiler#svelte-version
* @type {string} * @type {string}
*/ */
export const VERSION = '5.0.0-next.115'; export const VERSION = '5.0.0-next.116';
export const PUBLIC_VERSION = '5'; 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", "code": "attribute_invalid_multiple",
"message": "'multiple' attribute must be static if select uses two-way binding", "message": "'multiple' attribute must be static if select uses two-way binding",
"start": { "start": {
"line": 6, "line": 14,
"column": 19 "column": 19
}, },
"end": { "end": {
"line": 6, "line": 14,
"column": 29 "column": 29
} }
} }

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

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

Loading…
Cancel
Save