Merge remote-tracking branch 'origin' into elliott/variadic-snippets

pull/10320/head
S. Elliott Johnson 2 years ago
commit 33e58868b0

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: infer `svg` namespace correctly

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: keep intermediate number value representations

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: allow modifiying derived props

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: support async/await in destructuring assignments

@ -34,12 +34,14 @@
"dry-eggs-play",
"dry-eggs-retire",
"dull-mangos-wave",
"dull-roses-relate",
"early-ads-tie",
"eight-steaks-shout",
"eighty-bikes-camp",
"empty-crabs-think",
"fair-crabs-check",
"famous-knives-sneeze",
"fast-weeks-clean",
"few-mugs-fail",
"fifty-rice-wait",
"fifty-steaks-float",
@ -64,6 +66,7 @@
"honest-icons-change",
"hungry-dots-fry",
"hungry-tips-unite",
"hungry-trees-travel",
"itchy-beans-melt",
"itchy-kings-deliver",
"itchy-lions-wash",
@ -83,6 +86,7 @@
"light-pens-watch",
"long-buckets-lay",
"long-crews-return",
"loud-cheetahs-flow",
"lovely-carpets-lick",
"lovely-items-turn",
"lovely-rules-eat",
@ -90,6 +94,7 @@
"moody-frogs-exist",
"moody-owls-cry",
"nasty-lions-double",
"nasty-yaks-peel",
"neat-dingos-clap",
"new-boats-wait",
"ninety-dingos-walk",
@ -111,8 +116,10 @@
"pretty-ties-help",
"purple-dragons-peel",
"quiet-camels-mate",
"quiet-crabs-nail",
"rare-pears-whisper",
"real-guests-do",
"red-doors-own",
"rich-sheep-burn",
"rich-tables-sing",
"rotten-bags-type",
@ -127,6 +134,7 @@
"sharp-tomatoes-learn",
"shiny-baboons-play",
"shiny-shrimps-march",
"short-buses-camp",
"slimy-clouds-talk",
"slimy-walls-draw",
"slow-chefs-dream",
@ -138,6 +146,7 @@
"sour-forks-stare",
"sour-rules-march",
"spicy-plums-admire",
"spotty-pens-agree",
"stale-books-perform",
"stale-comics-look",
"strong-gifts-smoke",
@ -172,6 +181,7 @@
"wet-games-fly",
"wicked-clouds-exercise",
"wicked-doors-train",
"wicked-hairs-cheer",
"wild-foxes-wonder",
"wise-dancers-hang",
"wise-donkeys-marry",

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: take into account member expressions when determining legacy reactive dependencies

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: make `ComponentType` generic optional

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: silence false positive state warning

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure nested blocks are inert during outro transitions

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: improve ssr template literal generation

@ -1,5 +1,29 @@
# svelte
## 5.0.0-next.31
### Patch Changes
- fix: infer `svg` namespace correctly ([#10027](https://github.com/sveltejs/svelte/pull/10027))
- fix: keep intermediate number value representations ([`d171a39b0`](https://github.com/sveltejs/svelte/commit/d171a39b0ad97e2a05de1f38bc76a3d345e2b3d5))
- feat: allow modifiying derived props ([#10080](https://github.com/sveltejs/svelte/pull/10080))
- fix: improve signal consumer tracking behavior ([#10121](https://github.com/sveltejs/svelte/pull/10121))
- fix: support async/await in destructuring assignments ([#9962](https://github.com/sveltejs/svelte/pull/9962))
- fix: take into account member expressions when determining legacy reactive dependencies ([#10128](https://github.com/sveltejs/svelte/pull/10128))
- fix: make `ComponentType` generic optional ([`14dbc1be1`](https://github.com/sveltejs/svelte/commit/14dbc1be1720ff69e6f3c407e43c9c0765b0c140))
- fix: silence false positive state warning ([`dda4ad510`](https://github.com/sveltejs/svelte/commit/dda4ad510f1907a114a16227c3412eb00bd21738))
- fix: ensure nested blocks are inert during outro transitions ([#10126](https://github.com/sveltejs/svelte/pull/10126))
- fix: improve ssr template literal generation ([#10127](https://github.com/sveltejs/svelte/pull/10127))
## 5.0.0-next.30
### Patch Changes

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

@ -428,7 +428,7 @@ export function analyze_component(root, options) {
for (const scope of [module.scope, instance.scope]) {
outer: for (const [name, binding] of scope.declarations) {
if (binding.kind === 'normal' && binding.reassigned) {
for (const { path } of binding.references) {
inner: for (const { path } of binding.references) {
if (path[0].type !== 'Fragment') continue;
for (let i = 1; i < path.length; i += 1) {
const type = path[i].type;
@ -437,7 +437,7 @@ export function analyze_component(root, options) {
type === 'FunctionExpression' ||
type === 'ArrowFunctionExpression'
) {
continue;
continue inner;
}
}
@ -500,7 +500,15 @@ const legacy_scope_tweaker = {
node.body.type === 'ExpressionStatement' &&
node.body.expression.type === 'AssignmentExpression'
) {
for (const id of extract_identifiers(node.body.expression.left)) {
let ids = extract_identifiers(node.body.expression.left);
if (node.body.expression.left.type === 'MemberExpression') {
const id = object(node.body.expression.left);
if (id !== null) {
ids = [id];
}
}
for (const id of ids) {
const binding = state.scope.get(id.name);
if (binding?.kind === 'legacy_reactive') {
// TODO does this include `let double; $: double = x * 2`?
@ -511,10 +519,17 @@ const legacy_scope_tweaker = {
},
AssignmentExpression(node, { state, next }) {
if (state.reactive_statement && node.operator === '=') {
if (node.left.type === 'MemberExpression') {
const id = object(node.left);
if (id !== null) {
state.reactive_statement.assignments.add(id);
}
} else {
for (const id of extract_identifiers(node.left)) {
state.reactive_statement.assignments.add(id);
}
}
}
next();
},

@ -854,6 +854,13 @@ function validate_assignment(node, argument, state) {
let left = /** @type {import('estree').Expression | import('estree').Super} */ (argument);
if (left.type === 'Identifier') {
const binding = state.scope.get(left.name);
if (binding?.kind === 'derived') {
error(node, 'invalid-derived-assignment');
}
}
/** @type {import('estree').Expression | import('estree').PrivateIdentifier | null} */
let property = null;
@ -862,13 +869,6 @@ function validate_assignment(node, argument, state) {
left = left.object;
}
if (left.type === 'Identifier') {
const binding = state.scope.get(left.name);
if (binding?.kind === 'derived') {
error(node, 'invalid-derived-assignment');
}
}
if (left.type === 'ThisExpression' && property?.type === 'PrivateIdentifier') {
if (state.private_derived_state.includes(property.name)) {
error(node, 'invalid-derived-assignment');

@ -115,6 +115,103 @@ export function serialize_get_binding(node, state) {
return node;
}
/**
* @param {import('estree').Expression | import('estree').Pattern} expression
* @returns {boolean}
*/
function is_expression_async(expression) {
switch (expression.type) {
case 'AwaitExpression': {
return true;
}
case 'ArrayPattern': {
return expression.elements.some((element) => element && is_expression_async(element));
}
case 'ArrayExpression': {
return expression.elements.some((element) => {
if (!element) {
return false;
} else if (element.type === 'SpreadElement') {
return is_expression_async(element.argument);
} else {
return is_expression_async(element);
}
});
}
case 'AssignmentPattern':
case 'AssignmentExpression':
case 'BinaryExpression':
case 'LogicalExpression': {
return is_expression_async(expression.left) || is_expression_async(expression.right);
}
case 'CallExpression':
case 'NewExpression': {
return (
(expression.callee.type !== 'Super' && is_expression_async(expression.callee)) ||
expression.arguments.some((element) => {
if (element.type === 'SpreadElement') {
return is_expression_async(element.argument);
} else {
return is_expression_async(element);
}
})
);
}
case 'ChainExpression': {
return is_expression_async(expression.expression);
}
case 'ConditionalExpression': {
return (
is_expression_async(expression.test) ||
is_expression_async(expression.alternate) ||
is_expression_async(expression.consequent)
);
}
case 'ImportExpression': {
return is_expression_async(expression.source);
}
case 'MemberExpression': {
return (
(expression.object.type !== 'Super' && is_expression_async(expression.object)) ||
(expression.property.type !== 'PrivateIdentifier' &&
is_expression_async(expression.property))
);
}
case 'ObjectPattern':
case 'ObjectExpression': {
return expression.properties.some((property) => {
if (property.type === 'SpreadElement') {
return is_expression_async(property.argument);
} else if (property.type === 'Property') {
return (
(property.key.type !== 'PrivateIdentifier' && is_expression_async(property.key)) ||
is_expression_async(property.value)
);
}
});
}
case 'RestElement': {
return is_expression_async(expression.argument);
}
case 'SequenceExpression':
case 'TemplateLiteral': {
return expression.expressions.some((subexpression) => is_expression_async(subexpression));
}
case 'TaggedTemplateExpression': {
return is_expression_async(expression.tag) || is_expression_async(expression.quasi);
}
case 'UnaryExpression':
case 'UpdateExpression': {
return is_expression_async(expression.argument);
}
case 'YieldExpression': {
return expression.argument ? is_expression_async(expression.argument) : false;
}
default:
return false;
}
}
/**
* @template {import('./types').ClientTransformState} State
* @param {import('estree').AssignmentExpression} node
@ -153,17 +250,28 @@ export function serialize_set_binding(node, context, fallback) {
return fallback();
}
return b.call(
b.thunk(
const rhs_expression = /** @type {import('estree').Expression} */ (visit(node.right));
const iife_is_async =
is_expression_async(rhs_expression) ||
assignments.some((assignment) => is_expression_async(assignment));
const iife = b.arrow(
[],
b.block([
b.const(tmp_id, /** @type {import('estree').Expression} */ (visit(node.right))),
b.const(tmp_id, rhs_expression),
b.stmt(b.sequence(assignments)),
// return because it could be used in a nested expression where the value is needed.
// example: { foo: ({ bar } = { bar: 1 })}
b.return(b.id(tmp_id))
])
)
);
if (iife_is_async) {
return b.await(b.call(b.async(iife)));
} else {
return b.call(iife);
}
}
if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') {

@ -705,7 +705,7 @@ function serialize_attribute_value(
/** @type {import('estree').Expression} */ (context.visit(node.expression))
)
);
if (i === attribute_value.length) {
if (i === attribute_value.length || attribute_value[i]?.type !== 'Text') {
quasis.push(b.quasi('', true));
}
}

@ -211,23 +211,67 @@ export function infer_namespace(namespace, parent, nodes, path) {
parent_node.type === 'SvelteFragment' ||
parent_node.type === 'SnippetBlock')
) {
// Heuristic: Keep current namespace, unless we find a regular element,
// in which case we always want html, or we only find svg nodes,
// in which case we assume svg.
let only_svg = true;
const new_namespace = check_nodes_for_namespace(nodes, 'keep');
if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') {
namespace = new_namespace;
}
}
return namespace;
}
/**
* Heuristic: Keep current namespace, unless we find a regular element,
* in which case we always want html, or we only find svg nodes,
* in which case we assume svg.
* @param {import('#compiler').SvelteNode[]} nodes
* @param {import('#compiler').Namespace | 'keep' | 'maybe_html'} namespace
*/
function check_nodes_for_namespace(nodes, namespace) {
for (const node of nodes) {
if (node.type === 'RegularElement') {
if (!node.metadata.svg) {
namespace = 'html';
only_svg = false;
break;
} else if (namespace === 'keep') {
namespace = 'svg';
}
} else if (
(node.type === 'Text' && node.data.trim() !== '') ||
node.type === 'HtmlTag' ||
node.type === 'RenderTag'
) {
namespace = 'maybe_html';
} else if (node.type === 'EachBlock') {
namespace = check_nodes_for_namespace(node.body.nodes, namespace);
if (namespace === 'html') break;
if (node.fallback) {
namespace = check_nodes_for_namespace(node.fallback.nodes, namespace);
if (namespace === 'html') break;
}
} else if (node.type !== 'Text' || node.data.trim() !== '') {
only_svg = false;
} else if (node.type === 'IfBlock') {
namespace = check_nodes_for_namespace(node.consequent.nodes, namespace);
if (namespace === 'html') break;
if (node.alternate) {
namespace = check_nodes_for_namespace(node.alternate.nodes, namespace);
if (namespace === 'html') break;
}
} else if (node.type === 'AwaitBlock') {
if (node.pending) {
namespace = check_nodes_for_namespace(node.pending.nodes, namespace);
if (namespace === 'html') break;
}
if (only_svg) {
namespace = 'svg';
if (node.then) {
namespace = check_nodes_for_namespace(node.then.nodes, namespace);
if (namespace === 'html') break;
}
if (node.catch) {
namespace = check_nodes_for_namespace(node.catch.nodes, namespace);
if (namespace === 'html') break;
}
} else if (node.type === 'KeyBlock') {
namespace = check_nodes_for_namespace(node.fragment.nodes, namespace);
if (namespace === 'html') break;
}
}

@ -44,6 +44,23 @@ export function assignment(operator, left, right) {
return { type: 'AssignmentExpression', operator, left, right };
}
/**
* @template T
* @param {T & import('estree').BaseFunction} func
* @returns {T & import('estree').BaseFunction}
*/
export function async(func) {
return { ...func, async: true };
}
/**
* @param {import('estree').Expression} argument
* @returns {import('estree').AwaitExpression}
*/
export function await_builder(argument) {
return { type: 'AwaitExpression', argument };
}
/**
* @param {import('estree').BinaryOperator} operator
* @param {import('estree').Expression} left
@ -573,6 +590,7 @@ export function throw_error(str) {
}
export {
await_builder as await,
new_builder as new,
let_builder as let,
const_builder as const,

@ -926,32 +926,50 @@ export function selected(dom) {
}
/**
* @param {Element} dom
* @param {HTMLInputElement} dom
* @param {() => unknown} get_value
* @param {(value: unknown) => void} update
* @returns {void}
*/
export function bind_value(dom, get_value, update) {
dom.addEventListener('input', () => {
// @ts-ignore
/** @type {any} */
let value = dom.value;
// @ts-ignore
const type = dom.type;
if (type === 'number' || type === 'range') {
value = value === '' ? null : +value;
if (is_numberlike_input(dom)) {
value = to_number(value);
}
update(value);
});
render_effect(() => {
const value = get_value();
const coerced_value = value == null ? null : value + '';
// @ts-ignore
dom.value = coerced_value;
// @ts-ignore
dom.__value = value;
if (is_numberlike_input(dom) && value === to_number(dom.value)) {
// handles 0 vs 00 case (see https://github.com/sveltejs/svelte/issues/9959)
return;
}
dom.value = stringify(value);
});
}
/**
* @param {HTMLInputElement} dom
*/
function is_numberlike_input(dom) {
const type = dom.type;
return type === 'number' || type === 'range';
}
/**
* @param {string} value
*/
function to_number(value) {
return value === '' ? null : +value;
}
/**
* @param {HTMLSelectElement} dom
* @param {() => unknown} get_value

@ -10,6 +10,7 @@ import {
} from '../../constants.js';
import { readonly } from './proxy/readonly.js';
import { READONLY_SYMBOL, STATE_SYMBOL, proxy, unstate } from './proxy/proxy.js';
import { EACH_BLOCK, IF_BLOCK } from './block.js';
export const SOURCE = 1;
export const DERIVED = 1 << 1;
@ -1019,6 +1020,28 @@ export function mark_subtree_inert(signal, inert) {
if (!inert && (flags & IS_EFFECT) !== 0 && (flags & CLEAN) === 0) {
schedule_effect(/** @type {import('./types.js').EffectSignal} */ (signal), false);
}
// Nested if block effects
const block = signal.b;
if (block !== null) {
const type = block.t;
if (type === IF_BLOCK) {
const consequent_effect = block.ce;
if (consequent_effect !== null) {
mark_subtree_inert(consequent_effect, inert);
}
const alternate_effect = block.ae;
if (alternate_effect !== null) {
mark_subtree_inert(alternate_effect, inert);
}
} else if (type === EACH_BLOCK) {
const items = block.v;
for (let { e: each_item_effect } of items) {
if (each_item_effect !== null) {
mark_subtree_inert(each_item_effect, inert);
}
}
}
}
}
const references = signal.r;
if (references !== null) {

@ -177,7 +177,7 @@ export type ComponentProps<Comp extends SvelteComponent> = Comp extends SvelteCo
* <svelte:component this={componentOfCertainSubType} needsThisProp="hello" />
* ```
*/
export type ComponentType<Comp extends SvelteComponent> = (new (
export type ComponentType<Comp extends SvelteComponent = SvelteComponent> = (new (
options: ComponentConstructorOptions<
Comp extends SvelteComponent<infer Props> ? Props : Record<string, any>
>

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

@ -1,5 +1,5 @@
<script>
const a = $state(0);
const b = $derived({ a });
b.a += 1;
let b = $derived(a);
b = 1;
</script>

@ -1,5 +1,5 @@
<script>
const a = $state(0);
const b = $derived({ a });
b.a++;
let b = $derived(a);
b++;
</script>

@ -0,0 +1,37 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
html: `<button>false</button><button>false</button><button>false</button><button>false</button>`,
test({ assert, target }) {
const [button1, button2, button3, button4] = target.querySelectorAll('button');
button1.click();
flushSync();
assert.htmlEqual(
target.innerHTML,
`<button>true</button><button>false</button><button>false</button><button>false</button>`
);
button2.click();
flushSync();
assert.htmlEqual(
target.innerHTML,
`<button>true</button><button>true</button><button>false</button><button>false</button>`
);
button3.click();
flushSync();
assert.htmlEqual(
target.innerHTML,
`<button>true</button><button>true</button><button>true</button><button>false</button>`
);
button4.click();
flushSync();
assert.htmlEqual(
target.innerHTML,
`<button>true</button><button>true</button><button>true</button><button>true</button>`
);
}
});

@ -0,0 +1,15 @@
<script>
let array;
$: {
// test that this doesn't rerun on array change
array = []
array[0] = [false, false];
array[1] = [false, false];
}
</script>
{#each array as row, i}
{#each row as item, j}
<button on:click={() => array[i][j] = !array[i][j]}>{item}</button>
{/each}
{/each}

@ -0,0 +1,6 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
html: `<div class="123"></div><img src="12 hello, world 13">`
});

@ -0,0 +1,9 @@
<script>
let a = 1;
let b = 2;
let c = 3;
</script>
<div class="{a}{b}{c}" />
<img src="{a}{b} hello, world {a}{c}" />

@ -0,0 +1,15 @@
import { test } from '../../test';
export default test({
html: `<button>0 / 0</button><button>0 / 0</button>`,
async test({ assert, target }) {
const [btn1, btn2] = target.querySelectorAll('button');
await btn1?.click();
assert.htmlEqual(target.innerHTML, `<button>1 / 2</button><button>1 / 2</button>`);
await btn2?.click();
assert.htmlEqual(target.innerHTML, `<button>2 / 4</button><button>2 / 4</button>`);
}
});

@ -0,0 +1,7 @@
<script>
let count = $state(0);
let double = $derived({ get value() { return count * 2}, set value(c) { count = c / 2 } });
</script>
<button on:click={() => count++}>{count} / {double.value}</button>
<button on:click={() => double.value += 2}>{count} / {double.value}</button>

@ -0,0 +1,75 @@
import { test } from '../../test';
export default test({
html: `
<button>Update me!</button>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
`,
async test({ assert, target, window }) {
const btn = target.querySelector('button');
const clickEvent = new window.Event('click', { bubbles: true });
await btn?.dispatchEvent(clickEvent);
for (let i = 1; i <= 42; i += 1) {
await Promise.resolve();
}
assert.htmlEqual(
target.innerHTML,
`
<button>Update me!</button>
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>
<p>6</p>
<p>7</p>
<p>8</p>
<p>9</p>
<p>10</p>
<p>11</p>
<p>12</p>
<p>13</p>
<p>14</p>
<p>15</p>
<p>16</p>
<p>17</p>
<p>18</p>
<p>19</p>
<p>20</p>
<p>21</p>
<p>22</p>
<p>23</p>
<p>24</p>
<p>25</p>
<p>26</p>
`
);
}
});

@ -0,0 +1,93 @@
<script>
let a = $state(0);
let b = $state(0);
let c = $state(0);
let d = $state(0);
let e = $state(0);
let f = $state(0);
let g = $state(0);
let h = $state(0);
let i = $state(0);
let j = $state(0);
let k = $state(0);
let l = $state(0);
let m = $state(0);
let n = $state(0);
let o = $state(0);
let p = $state(0);
let q = $state(0);
let r = $state(0);
let s = $state(0);
let t = $state(0);
let u = $state(0);
let v = $state(0);
let w = $state(0);
let x = $state(0);
let y = $state(0);
let z = $state(0);
const get_vwx = () => {
return Promise.resolve({ v: 22, rest: [23, 24] });
}
const get_y = () => {
return Promise.resolve([24, 25]);
}
const some = {
fn: () => {}
}
const update = async () => {
[a, b] = [1, await Promise.resolve(2)];
({ c = await Promise.resolve(3), d } = { d: 4 });
[e] = [await Promise.resolve(2) + await Promise.resolve(3)];
({ f = false || await Promise.resolve(6) } = {});
let func = Promise.resolve(() => 7);
[g = (await func)()] = [];
let mult = (a, b) => (a * b);
({ h } = { h: mult(2, await Promise.resolve(4))});
[i] = [new Date(await Promise.resolve(9)).getTime()];
[j = "19" ? 10 : await Promise.resolve(11)] = [];
let obj = ({ [await Promise.resolve("prop")]: k } = { prop: 11 });
[l = obj[await Promise.resolve("prop")] + 1] = [];
[m] = [`${1}${await Promise.resolve("3")}`];
[n] = [-(await Promise.resolve(-14))];
[o] = [(some.fn(), await Promise.resolve(15))];
({ anotherprop: p = await Promise.resolve(16) } = obj);
let val1, val2;
({ val1 = (async function (x) { return await x; })(Promise.resolve(18)), r = await val1 }
= ({ val2 = (async (x) => await x)(Promise.resolve(17)), q = await val2 } = []));
({ u = 21 } = ({ t = await Promise.resolve(20) } = ([s] = [await Promise.resolve(19)])));
({ v, rest: [w] } = await get_vwx());
[x, y, ...{ z = 26 }] = await get_y();
}
</script>
<button on:click={update}>Update me!</button>
<p>{a}</p>
<p>{b}</p>
<p>{c}</p>
<p>{d}</p>
<p>{e}</p>
<p>{f}</p>
<p>{g}</p>
<p>{h}</p>
<p>{i}</p>
<p>{j}</p>
<p>{k}</p>
<p>{l}</p>
<p>{m}</p>
<p>{n}</p>
<p>{o}</p>
<p>{p}</p>
<p>{q}</p>
<p>{r}</p>
<p>{s}</p>
<p>{t}</p>
<p>{u}</p>
<p>{v}</p>
<p>{w}</p>
<p>{x}</p>
<p>{y}</p>
<p>{z}</p>

@ -0,0 +1,16 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
html: `<button>hide</button><div>hello</div>`,
async test({ assert, target }) {
const [btn1, btn2] = target.querySelectorAll('button');
flushSync(() => {
btn1.click();
});
assert.htmlEqual(target.innerHTML, `<button>hide</button><div style="opacity: 0;">hello</div>`);
}
});

@ -0,0 +1,17 @@
<script>
import { fade } from "svelte/transition";
let state = $state("hello");
</script>
<button onclick={() => state = ''}>hide</button>
{#if state}
<div in:fade={{ duration: 2000 }} out:fade={{ duration: 2000 }}>
{#if true}
{state}
{/if}
</div>
{/if}

@ -0,0 +1,13 @@
<text x="0" y=14>outside</text>
{#if true}
<text x="0" y="26">true</text>
{:else}
<text x="0" y="26">false</text>
{/if}
{#each Array(3).fill(0) as item, idx}
<text x={idx * 10} y={42}>{idx}</text>
{/each}
<!-- comment should not set infer html namespace -->

@ -0,0 +1,26 @@
import { test, ok } from '../../test';
export default test({
html: `
<svg>
<text x="0" y="14">outside</text>
<text x="0" y="26">true</text>
<text x="0" y="42">0</text>
<text x="10" y="42">1</text>
<text x="20" y="42">2</text>
</svg>
`,
test({ assert, target }) {
const svg = target.querySelector('svg');
ok(svg);
assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
const text_elements = target.querySelectorAll('text');
assert.equal(text_elements.length, 5);
for (const { namespaceURI } of text_elements)
assert.equal(namespaceURI, 'http://www.w3.org/2000/svg');
}
});

@ -0,0 +1,7 @@
<script>
import Wrapper from "./Wrapper.svelte";
</script>
<svg>
<Wrapper />
</svg>

@ -2,9 +2,11 @@
let a = $state(1);
let b = 2;
let c = 3;
let d = 4;
</script>
<button onclick={() => a += 1}>a += 1</button>
<button onclick={() => b += 1}>b += 1</button>
<button onclick={() => c += 1}>c += 1</button>
<button onclick={() => d += 1}>d += 1</button>
<p>{a} + {b} + {c} = {a + b + c}</p>

@ -178,7 +178,7 @@ declare module 'svelte' {
* <svelte:component this={componentOfCertainSubType} needsThisProp="hello" />
* ```
*/
export type ComponentType<Comp extends SvelteComponent> = (new (
export type ComponentType<Comp extends SvelteComponent = SvelteComponent> = (new (
options: ComponentConstructorOptions<
Comp extends SvelteComponent<infer Props> ? Props : Record<string, any>
>

@ -111,14 +111,15 @@
${styles}
{
const styles = document.querySelectorAll('style[id^=svelte-]');
let i = styles.length;
while (i--) styles[i].parentNode.removeChild(styles[i]);
if (window.unmount) {
if (window.__unmount_previous) {
try {
window.unmount();
window.__unmount_previous();
} catch (err) {
console.error(err);
}
@ -126,11 +127,14 @@
document.body.innerHTML = '';
window._svelteTransitionManager = null;
}
const { mount, App } = ${$bundle.client?.code};
const __repl_exports = ${$bundle.client?.code};
{
const { mount, App } = __repl_exports;
const [, destroy] = mount(App, { target: document.body });
window.unmount = destroy;
window.__unmount_previous = destroy;
}
//# sourceURL=playground:output
`);
error = null;

Loading…
Cancel
Save