mirror of https://github.com/sveltejs/svelte
commit
26d9ae0f13
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
fix: improve element class attribute behaviour
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": patch
|
||||
---
|
||||
|
||||
breaking: apply fallback value every time in runes mode
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,140 +1,76 @@
|
||||
import { UNINITIALIZED, KEY_BLOCK } from '../../constants.js';
|
||||
import { UNINITIALIZED } from '../../constants.js';
|
||||
import { hydrate_block_anchor } from '../hydration.js';
|
||||
import { remove } from '../reconciler.js';
|
||||
import { current_block, execute_effect } from '../../runtime.js';
|
||||
import { destroy_effect, render_effect } from '../../reactivity/effects.js';
|
||||
import { trigger_transitions } from '../elements/transitions.js';
|
||||
import { pause_effect, render_effect } from '../../reactivity/effects.js';
|
||||
import { safe_not_equal } from '../../reactivity/equality.js';
|
||||
|
||||
/** @returns {import('../../types.js').KeyBlock} */
|
||||
function create_key_block() {
|
||||
return {
|
||||
// dom
|
||||
d: null,
|
||||
// effect
|
||||
e: null,
|
||||
// parent
|
||||
p: /** @type {import('../../types.js').Block} */ (current_block),
|
||||
// transition
|
||||
r: null,
|
||||
// type
|
||||
t: KEY_BLOCK
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @template V
|
||||
* @param {Comment} anchor_node
|
||||
* @param {() => V} key
|
||||
* @param {Comment} anchor
|
||||
* @param {() => V} get_key
|
||||
* @param {(anchor: Node) => void} render_fn
|
||||
* @returns {void}
|
||||
*/
|
||||
export function key_block(anchor_node, key, render_fn) {
|
||||
const block = create_key_block();
|
||||
export function key_block(anchor, get_key, render_fn) {
|
||||
const block = {};
|
||||
|
||||
/** @type {null | import('../../types.js').Render} */
|
||||
let current_render = null;
|
||||
hydrate_block_anchor(anchor_node);
|
||||
hydrate_block_anchor(anchor);
|
||||
|
||||
/** @type {V | typeof UNINITIALIZED} */
|
||||
let key_value = UNINITIALIZED;
|
||||
let mounted = false;
|
||||
block.r =
|
||||
/**
|
||||
* @param {import('../../types.js').Transition} transition
|
||||
* @returns {void}
|
||||
*/
|
||||
(transition) => {
|
||||
const render = /** @type {import('../../types.js').Render} */ (current_render);
|
||||
const transitions = render.s;
|
||||
transitions.add(transition);
|
||||
transition.f(() => {
|
||||
transitions.delete(transition);
|
||||
if (transitions.size === 0) {
|
||||
// If the current render has changed since, then we can remove the old render
|
||||
// effect as it's stale.
|
||||
if (current_render !== render && render.e !== null) {
|
||||
if (render.d !== null) {
|
||||
remove(render.d);
|
||||
render.d = null;
|
||||
}
|
||||
destroy_effect(render.e);
|
||||
render.e = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
const create_render_effect = () => {
|
||||
/** @type {import('../../types.js').Render} */
|
||||
const render = {
|
||||
d: null,
|
||||
e: null,
|
||||
s: new Set(),
|
||||
p: current_render
|
||||
};
|
||||
const effect = render_effect(
|
||||
() => {
|
||||
render_fn(anchor_node);
|
||||
render.d = block.d;
|
||||
block.d = null;
|
||||
},
|
||||
block,
|
||||
true,
|
||||
true
|
||||
);
|
||||
render.e = effect;
|
||||
current_render = render;
|
||||
};
|
||||
const render = () => {
|
||||
const render = current_render;
|
||||
if (render === null) {
|
||||
create_render_effect();
|
||||
return;
|
||||
}
|
||||
const transitions = render.s;
|
||||
if (transitions.size === 0) {
|
||||
if (render.d !== null) {
|
||||
remove(render.d);
|
||||
render.d = null;
|
||||
}
|
||||
if (render.e) {
|
||||
execute_effect(render.e);
|
||||
} else {
|
||||
create_render_effect();
|
||||
}
|
||||
} else {
|
||||
trigger_transitions(transitions, 'out');
|
||||
create_render_effect();
|
||||
}
|
||||
};
|
||||
let key = UNINITIALIZED;
|
||||
|
||||
/** @type {import('#client').Effect} */
|
||||
let effect;
|
||||
|
||||
/**
|
||||
* Every time `key` changes, we create a new effect. Old effects are
|
||||
* removed from this set when they have fully transitioned out
|
||||
* @type {Set<import('#client').Effect>}
|
||||
*/
|
||||
let effects = new Set();
|
||||
|
||||
const key_effect = render_effect(
|
||||
() => {
|
||||
const prev_key_value = key_value;
|
||||
key_value = key();
|
||||
if (mounted && safe_not_equal(prev_key_value, key_value)) {
|
||||
render();
|
||||
if (safe_not_equal(key, (key = get_key()))) {
|
||||
if (effect) {
|
||||
var e = effect;
|
||||
pause_effect(e, () => {
|
||||
effects.delete(e);
|
||||
});
|
||||
}
|
||||
|
||||
effect = render_effect(
|
||||
() => {
|
||||
render_fn(anchor);
|
||||
|
||||
// @ts-expect-error TODO this should be unnecessary
|
||||
const dom = block.d;
|
||||
|
||||
return () => {
|
||||
if (dom !== null) {
|
||||
remove(dom);
|
||||
}
|
||||
};
|
||||
},
|
||||
block,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
// @ts-expect-error TODO tidy up
|
||||
effect.d = block.d;
|
||||
|
||||
effects.add(effect);
|
||||
}
|
||||
},
|
||||
block,
|
||||
false
|
||||
);
|
||||
// To ensure topological ordering of the key effect to the render effect,
|
||||
// we trigger the effect after.
|
||||
render();
|
||||
mounted = true;
|
||||
|
||||
key_effect.ondestroy = () => {
|
||||
let render = current_render;
|
||||
while (render !== null) {
|
||||
const dom = render.d;
|
||||
if (dom !== null) {
|
||||
remove(dom);
|
||||
}
|
||||
const effect = render.e;
|
||||
if (effect !== null) {
|
||||
destroy_effect(effect);
|
||||
}
|
||||
render = render.p;
|
||||
for (const e of effects) {
|
||||
// @ts-expect-error TODO tidy up. ondestroy should be totally unnecessary
|
||||
if (e.d) remove(e.d);
|
||||
}
|
||||
};
|
||||
block.e = key_effect;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,8 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
error: {
|
||||
code: 'invalid-rune-args-length',
|
||||
message: '$bindable can only be called with 0 or 1 arguments'
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,3 @@
|
||||
<script>
|
||||
const { foo = $bindable(1, 2) } = $props();
|
||||
</script>
|
||||
@ -0,0 +1,8 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
error: {
|
||||
code: 'invalid-bindable-location',
|
||||
message: '$bindable() can only be used inside a $props() declaration'
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,3 @@
|
||||
<script>
|
||||
const { a = $bindable() } = $state();
|
||||
</script>
|
||||
@ -0,0 +1 @@
|
||||
const { a = $bindable() } = $state();
|
||||
@ -1 +1 @@
|
||||
<!--ssr:0--><div class="foo"></div><!--ssr:0-->
|
||||
<!--ssr:0--><div id="foo"></div><!--ssr:0-->
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script>
|
||||
export let className;
|
||||
export let id;
|
||||
</script>
|
||||
|
||||
<div class={className}></div>
|
||||
<div id={id}></div>
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
skip: true, // failing test for https://github.com/sveltejs/svelte/issues/10787
|
||||
html: `<button>3</button>`,
|
||||
async test({ assert, target }) {
|
||||
target.querySelector('button')?.click();
|
||||
await tick();
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `<button>1</button>`);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,16 @@
|
||||
<script>
|
||||
let x = 1;
|
||||
let y = true;
|
||||
$: array = y ? [1, 2] : [1];
|
||||
$: count = array.length === 2 && x ? 1 : 0;
|
||||
$: sum = count + array.length;
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click={() => {
|
||||
// order is important here: x must be updated before y
|
||||
// in order to test that $: still runs in the correct order
|
||||
x = 2;
|
||||
y = false;
|
||||
}}>{sum}</button
|
||||
>
|
||||
@ -0,0 +1,29 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
// Tests that default values only fire lazily when the prop is undefined, and every time
|
||||
export default test({
|
||||
props: {
|
||||
p0: 0,
|
||||
p1: 0,
|
||||
p2: 0,
|
||||
p3: 0
|
||||
},
|
||||
html: `<p>props: 0 0 0 0 1 1 1 1</p><p>log: nested.fallback_value,fallback_fn`,
|
||||
async test({ assert, target, component }) {
|
||||
component.p0 = undefined;
|
||||
component.p1 = undefined;
|
||||
component.p2 = undefined;
|
||||
component.p3 = undefined;
|
||||
// Nuance: these are already undefined in the props, but we're setting them to undefined again,
|
||||
// which calls the fallback value again, even if it will result in the same value. There's no way
|
||||
// to prevent this, and in practise it won't matter - and you shouldn't use accessors in runes mode anyway.
|
||||
component.p4 = undefined;
|
||||
component.p5 = undefined;
|
||||
component.p6 = undefined;
|
||||
component.p7 = undefined;
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`<p>props: 1 1 1 1 1 1 1 1</p><p>log: nested.fallback_value,fallback_fn,nested.fallback_value,fallback_fn,nested.fallback_value,fallback_fn`
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,29 @@
|
||||
<script>
|
||||
let log = $state([]);
|
||||
|
||||
const fallback_value = 1;
|
||||
const nested = {
|
||||
get fallback_value() {
|
||||
log.push('nested.fallback_value');
|
||||
return fallback_value;
|
||||
}
|
||||
}
|
||||
const fallback_fn = () => {
|
||||
log.push('fallback_fn');
|
||||
return fallback_value;
|
||||
}
|
||||
|
||||
const {
|
||||
p0 = 1,
|
||||
p1 = fallback_value,
|
||||
p2 = nested.fallback_value,
|
||||
p3 = fallback_fn(),
|
||||
p4 = 1,
|
||||
p5 = fallback_value,
|
||||
p6 = nested.fallback_value,
|
||||
p7 = fallback_fn()
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<p>props: {p0} {p1} {p2} {p3} {p4} {p5} {p6} {p7}</p>
|
||||
<p>log: {log}</p>
|
||||
@ -1,23 +1,24 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
// Tests that default values only fire lazily when the prop is undefined, and only initially
|
||||
// Tests that default values only fire lazily when the prop is undefined, and every time
|
||||
export default test({
|
||||
props: {
|
||||
p0: 0,
|
||||
p1: 0,
|
||||
p2: 0,
|
||||
p3: 0
|
||||
},
|
||||
html: `<p>props: 0 0 0 0 1 1 1 1</p><p>log: nested.fallback_value,fallback_fn`,
|
||||
async test({ assert, target, component }) {
|
||||
component.p0 = undefined;
|
||||
component.p1 = undefined;
|
||||
component.p2 = undefined;
|
||||
component.p3 = undefined;
|
||||
component.p4 = undefined;
|
||||
component.p5 = undefined;
|
||||
component.p6 = undefined;
|
||||
component.p7 = undefined;
|
||||
assert.htmlEqual(target.innerHTML, `<p>props: </p><p>log: nested.fallback_value,fallback_fn`);
|
||||
html: `
|
||||
<p>props: 0 0 0 0 1 1 1 1</p>
|
||||
<p>log: nested.fallback_value,fallback_fn</p>
|
||||
<button>Set all to undefined</button>
|
||||
`,
|
||||
async test({ assert, target }) {
|
||||
const btn = target.querySelector('button');
|
||||
btn?.click();
|
||||
await tick();
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<p>props: 1 1 1 1 1 1 1 1</p>
|
||||
<p>log: nested.fallback_value,fallback_fn,nested.fallback_value,fallback_fn</p>
|
||||
<button>Set all to undefined</button>
|
||||
`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,31 +1,25 @@
|
||||
<script>
|
||||
let log = $state([]);
|
||||
import Sub from "./sub.svelte";
|
||||
|
||||
const fallback_value = 1;
|
||||
const nested = {
|
||||
get fallback_value() {
|
||||
log.push('nested.fallback_value');
|
||||
log = log;
|
||||
return fallback_value;
|
||||
}
|
||||
}
|
||||
const fallback_fn = () => {
|
||||
log.push('fallback_fn');
|
||||
log = log;
|
||||
return fallback_value;
|
||||
}
|
||||
|
||||
const {
|
||||
p0 = 1,
|
||||
p1 = fallback_value,
|
||||
p2 = nested.fallback_value,
|
||||
p3 = fallback_fn(),
|
||||
p4 = 1,
|
||||
p5 = fallback_value,
|
||||
p6 = nested.fallback_value,
|
||||
p7 = fallback_fn()
|
||||
} = $props();
|
||||
let p0 = $state(0);
|
||||
let p1 = $state(0);
|
||||
let p2 = $state(0);
|
||||
let p3 = $state(0);
|
||||
let p4 = $state();
|
||||
let p5 = $state();
|
||||
let p6 = $state();
|
||||
let p7 = $state();
|
||||
</script>
|
||||
|
||||
<p>props: {p0} {p1} {p2} {p3} {p4} {p5} {p6} {p7}</p>
|
||||
<p>log: {log}</p>
|
||||
<Sub {p0} {p1} {p2} {p3} {p4} {p5} {p6} {p7} />
|
||||
|
||||
<button onclick={() => {
|
||||
p0 = undefined;
|
||||
p1 = undefined;
|
||||
p2 = undefined;
|
||||
p3 = undefined;
|
||||
p4 = undefined;
|
||||
p5 = undefined;
|
||||
p6 = undefined;
|
||||
p7 = undefined;
|
||||
}}>Set all to undefined</button>
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
<script>
|
||||
let log = $state([]);
|
||||
|
||||
const fallback_value = 1;
|
||||
const nested = {
|
||||
get fallback_value() {
|
||||
log.push('nested.fallback_value');
|
||||
return fallback_value;
|
||||
}
|
||||
}
|
||||
const fallback_fn = () => {
|
||||
log.push('fallback_fn');
|
||||
return fallback_value;
|
||||
}
|
||||
|
||||
const {
|
||||
p0 = 1,
|
||||
p1 = fallback_value,
|
||||
p2 = nested.fallback_value,
|
||||
p3 = fallback_fn(),
|
||||
p4 = 1,
|
||||
p5 = fallback_value,
|
||||
p6 = nested.fallback_value,
|
||||
p7 = fallback_fn()
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<p>props: {p0} {p1} {p2} {p3} {p4} {p5} {p6} {p7}</p>
|
||||
<p>log: {log}</p>
|
||||
Loading…
Reference in new issue