mirror of https://github.com/sveltejs/svelte
commit
b7f3b88761
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: correctly scope CSS selectors with descendant combinators
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: implement support for `:is(...)` and `:where(...)`
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
chore: improve code generation for `bind:this` in SSR mode
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
chore: treeshake unused store subscriptions in SSR mode
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: improve bind:this support for each blocks
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: improve effect over-fire on store subscription init
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: use init properties when exporting non-state values in prod
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: implement nested CSS support
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: improve each block item equality for immutable mode
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: handle member expressions in directives
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: improve handling of unowned derived signals
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: better handling of empty text node hydration
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: improve $inspect handling of derived objects
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: remove memory leak
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: ensure update methods of actions and reactive statements work with fine-grained `$state`
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: don't execute scripts inside `@html` when instantiated on the client
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: make `bind_this` implementation more robust
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: only escape characters in SSR template
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
breaking: remove `createRoot`, adjust `mount`/`hydrate` APIs, introduce `unmount`
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: call beforeUpdate/afterUpdate callbacks when props are mutated
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: allow state/derived/props to be explicitly exported from components
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: replace proxy-based readonly validation with stack-trace-based ownership tracking
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: permit whitespace within template scripts
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: wire up `events` in `mount` correctly and fix its types
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: inherit ownerlessness when creating child proxies
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: allow boolean `contenteditable` attribute
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
breaking: encapsulate/remove selectors inside `:is(...)` and `:where(...)`
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: improve props spreading logic
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: correct context applied to batch_inspect
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: visit expression node in directives
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: better handling of derived signals that have no dependencies
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: improve state store mutation compiler output
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: improve import event handler support
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
export const SOURCE = 1;
|
||||||
|
export const DERIVED = 1 << 1;
|
||||||
|
export const EFFECT = 1 << 2;
|
||||||
|
export const PRE_EFFECT = 1 << 3;
|
||||||
|
export const RENDER_EFFECT = 1 << 4;
|
||||||
|
export const MANAGED = 1 << 6;
|
||||||
|
export const UNOWNED = 1 << 7;
|
||||||
|
export const CLEAN = 1 << 8;
|
||||||
|
export const DIRTY = 1 << 9;
|
||||||
|
export const MAYBE_DIRTY = 1 << 10;
|
||||||
|
export const INERT = 1 << 11;
|
||||||
|
export const DESTROYED = 1 << 12;
|
||||||
|
|
||||||
|
export const ROOT_BLOCK = 0;
|
||||||
|
export const IF_BLOCK = 1;
|
||||||
|
export const EACH_BLOCK = 2;
|
||||||
|
export const EACH_ITEM_BLOCK = 3;
|
||||||
|
export const AWAIT_BLOCK = 4;
|
||||||
|
export const KEY_BLOCK = 5;
|
||||||
|
export const HEAD_BLOCK = 6;
|
||||||
|
export const DYNAMIC_COMPONENT_BLOCK = 7;
|
||||||
|
export const DYNAMIC_ELEMENT_BLOCK = 8;
|
||||||
|
export const SNIPPET_BLOCK = 9;
|
||||||
|
|
||||||
|
export const UNINITIALIZED = Symbol();
|
||||||
|
export const STATE_SYMBOL = Symbol('$state');
|
||||||
@ -0,0 +1,154 @@
|
|||||||
|
/** @typedef {{ file: string, line: number, column: number }} Location */
|
||||||
|
|
||||||
|
import { STATE_SYMBOL } from '../constants.js';
|
||||||
|
import { untrack } from '../runtime.js';
|
||||||
|
|
||||||
|
/** @type {Record<string, Array<{ start: Location, end: Location, component: Function }>>} */
|
||||||
|
const boundaries = {};
|
||||||
|
|
||||||
|
const chrome_pattern = /at (?:.+ \()?(.+):(\d+):(\d+)\)?$/;
|
||||||
|
const firefox_pattern = /@(.+):(\d+):(\d+)$/;
|
||||||
|
|
||||||
|
export function get_stack() {
|
||||||
|
const stack = new Error().stack;
|
||||||
|
if (!stack) return null;
|
||||||
|
|
||||||
|
const entries = [];
|
||||||
|
|
||||||
|
for (const line of stack.split('\n')) {
|
||||||
|
let match = chrome_pattern.exec(line) ?? firefox_pattern.exec(line);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
entries.push({
|
||||||
|
file: match[1],
|
||||||
|
line: +match[2],
|
||||||
|
column: +match[3]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines which `.svelte` component is responsible for a given state change
|
||||||
|
* @returns {Function | null}
|
||||||
|
*/
|
||||||
|
export function get_component() {
|
||||||
|
const stack = get_stack();
|
||||||
|
if (!stack) return null;
|
||||||
|
|
||||||
|
for (const entry of stack) {
|
||||||
|
const modules = boundaries[entry.file];
|
||||||
|
if (!modules) continue;
|
||||||
|
|
||||||
|
for (const module of modules) {
|
||||||
|
if (module.start.line < entry.line && module.end.line > entry.line) {
|
||||||
|
return module.component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Together with `mark_module_end`, this function establishes the boundaries of a `.svelte` file,
|
||||||
|
* such that subsequent calls to `get_component` can tell us which component is responsible
|
||||||
|
* for a given state change
|
||||||
|
* @param {Function} component
|
||||||
|
*/
|
||||||
|
export function mark_module_start(component) {
|
||||||
|
const start = get_stack()?.[2];
|
||||||
|
|
||||||
|
if (start) {
|
||||||
|
(boundaries[start.file] ??= []).push({
|
||||||
|
start,
|
||||||
|
// @ts-expect-error
|
||||||
|
end: null,
|
||||||
|
component
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mark_module_end() {
|
||||||
|
const end = get_stack()?.[2];
|
||||||
|
|
||||||
|
if (end) {
|
||||||
|
// @ts-expect-error
|
||||||
|
boundaries[end.file].at(-1).end = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {any} object
|
||||||
|
* @param {any} owner
|
||||||
|
*/
|
||||||
|
export function add_owner(object, owner) {
|
||||||
|
untrack(() => {
|
||||||
|
add_owner_to_object(object, owner);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} object
|
||||||
|
* @param {Function} owner
|
||||||
|
*/
|
||||||
|
function add_owner_to_object(object, owner) {
|
||||||
|
if (object?.[STATE_SYMBOL]?.o && !object[STATE_SYMBOL].o.has(owner)) {
|
||||||
|
object[STATE_SYMBOL].o.add(owner);
|
||||||
|
|
||||||
|
for (const key in object) {
|
||||||
|
add_owner_to_object(object[key], owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} object
|
||||||
|
*/
|
||||||
|
export function strip_owner(object) {
|
||||||
|
untrack(() => {
|
||||||
|
strip_owner_from_object(object);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} object
|
||||||
|
*/
|
||||||
|
function strip_owner_from_object(object) {
|
||||||
|
if (object?.[STATE_SYMBOL]?.o) {
|
||||||
|
object[STATE_SYMBOL].o = null;
|
||||||
|
|
||||||
|
for (const key in object) {
|
||||||
|
strip_owner(object[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Set<Function>} owners
|
||||||
|
*/
|
||||||
|
export function check_ownership(owners) {
|
||||||
|
const component = get_component();
|
||||||
|
|
||||||
|
if (component && !owners.has(component)) {
|
||||||
|
let original = [...owners][0];
|
||||||
|
|
||||||
|
let message =
|
||||||
|
// @ts-expect-error
|
||||||
|
original.filename !== component.filename
|
||||||
|
? // @ts-expect-error
|
||||||
|
`${component.filename} mutated a value owned by ${original.filename}. This is strongly discouraged`
|
||||||
|
: 'Mutating a value outside the component that created it is strongly discouraged';
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(
|
||||||
|
`${message}. Consider passing values to child components with \`bind:\`, or use a callback instead.`
|
||||||
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.trace();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,200 @@
|
|||||||
|
import { is_promise } from '../../../common.js';
|
||||||
|
import { hydrate_block_anchor } from '../../hydration.js';
|
||||||
|
import { remove } from '../../reconciler.js';
|
||||||
|
import {
|
||||||
|
current_block,
|
||||||
|
destroy_signal,
|
||||||
|
execute_effect,
|
||||||
|
flushSync,
|
||||||
|
push_destroy_fn
|
||||||
|
} from '../../runtime.js';
|
||||||
|
import { render_effect } from '../../reactivity/computations.js';
|
||||||
|
import { trigger_transitions } from '../../transitions.js';
|
||||||
|
import { AWAIT_BLOCK, UNINITIALIZED } from '../../constants.js';
|
||||||
|
|
||||||
|
/** @returns {import('../../types.js').AwaitBlock} */
|
||||||
|
export function create_await_block() {
|
||||||
|
return {
|
||||||
|
// dom
|
||||||
|
d: null,
|
||||||
|
// effect
|
||||||
|
e: null,
|
||||||
|
// parent
|
||||||
|
p: /** @type {import('../../types.js').Block} */ (current_block),
|
||||||
|
// pending
|
||||||
|
n: true,
|
||||||
|
// transition
|
||||||
|
r: null,
|
||||||
|
// type
|
||||||
|
t: AWAIT_BLOCK
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template V
|
||||||
|
* @param {Comment} anchor_node
|
||||||
|
* @param {(() => Promise<V>)} input
|
||||||
|
* @param {null | ((anchor: Node) => void)} pending_fn
|
||||||
|
* @param {null | ((anchor: Node, value: V) => void)} then_fn
|
||||||
|
* @param {null | ((anchor: Node, error: unknown) => void)} catch_fn
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
export function await_block(anchor_node, input, pending_fn, then_fn, catch_fn) {
|
||||||
|
const block = create_await_block();
|
||||||
|
|
||||||
|
/** @type {null | import('../../types.js').Render} */
|
||||||
|
let current_render = null;
|
||||||
|
hydrate_block_anchor(anchor_node);
|
||||||
|
|
||||||
|
/** @type {{}} */
|
||||||
|
let latest_token;
|
||||||
|
|
||||||
|
/** @type {typeof UNINITIALIZED | V} */
|
||||||
|
let resolved_value = UNINITIALIZED;
|
||||||
|
|
||||||
|
/** @type {unknown} */
|
||||||
|
let error = UNINITIALIZED;
|
||||||
|
let pending = 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_signal(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(
|
||||||
|
() => {
|
||||||
|
if (error === UNINITIALIZED) {
|
||||||
|
if (resolved_value === UNINITIALIZED) {
|
||||||
|
// pending = true
|
||||||
|
block.n = true;
|
||||||
|
if (pending_fn !== null) {
|
||||||
|
pending_fn(anchor_node);
|
||||||
|
}
|
||||||
|
} else if (then_fn !== null) {
|
||||||
|
// pending = false
|
||||||
|
block.n = false;
|
||||||
|
then_fn(anchor_node, resolved_value);
|
||||||
|
}
|
||||||
|
} else if (catch_fn !== null) {
|
||||||
|
// pending = false
|
||||||
|
block.n = false;
|
||||||
|
catch_fn(anchor_node, error);
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
create_render_effect();
|
||||||
|
trigger_transitions(transitions, 'out');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const await_effect = render_effect(
|
||||||
|
() => {
|
||||||
|
const token = {};
|
||||||
|
latest_token = token;
|
||||||
|
const promise = input();
|
||||||
|
if (is_promise(promise)) {
|
||||||
|
promise.then(
|
||||||
|
/** @param {V} v */
|
||||||
|
(v) => {
|
||||||
|
if (latest_token === token) {
|
||||||
|
// Ensure UI is in sync before resolving value.
|
||||||
|
flushSync();
|
||||||
|
resolved_value = v;
|
||||||
|
pending = false;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** @param {unknown} _error */
|
||||||
|
(_error) => {
|
||||||
|
error = _error;
|
||||||
|
pending = false;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (resolved_value !== UNINITIALIZED || error !== UNINITIALIZED) {
|
||||||
|
error = UNINITIALIZED;
|
||||||
|
resolved_value = UNINITIALIZED;
|
||||||
|
}
|
||||||
|
if (!pending) {
|
||||||
|
pending = true;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error = UNINITIALIZED;
|
||||||
|
resolved_value = promise;
|
||||||
|
pending = false;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
block,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
push_destroy_fn(await_effect, () => {
|
||||||
|
let render = current_render;
|
||||||
|
latest_token = {};
|
||||||
|
while (render !== null) {
|
||||||
|
const dom = render.d;
|
||||||
|
if (dom !== null) {
|
||||||
|
remove(dom);
|
||||||
|
}
|
||||||
|
const effect = render.e;
|
||||||
|
if (effect !== null) {
|
||||||
|
destroy_signal(effect);
|
||||||
|
}
|
||||||
|
render = render.p;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
block.e = await_effect;
|
||||||
|
}
|
||||||
@ -0,0 +1,181 @@
|
|||||||
|
import { IF_BLOCK } from '../../constants.js';
|
||||||
|
import {
|
||||||
|
current_hydration_fragment,
|
||||||
|
hydrate_block_anchor,
|
||||||
|
hydrating,
|
||||||
|
set_current_hydration_fragment
|
||||||
|
} from '../../hydration.js';
|
||||||
|
import { remove } from '../../reconciler.js';
|
||||||
|
import { current_block, destroy_signal, execute_effect, push_destroy_fn } from '../../runtime.js';
|
||||||
|
import { render_effect } from '../../reactivity/computations.js';
|
||||||
|
import { trigger_transitions } from '../../transitions.js';
|
||||||
|
|
||||||
|
/** @returns {import('../../types.js').IfBlock} */
|
||||||
|
function create_if_block() {
|
||||||
|
return {
|
||||||
|
// alternate transitions
|
||||||
|
a: null,
|
||||||
|
// alternate effect
|
||||||
|
ae: null,
|
||||||
|
// consequent transitions
|
||||||
|
c: null,
|
||||||
|
// consequent effect
|
||||||
|
ce: null,
|
||||||
|
// dom
|
||||||
|
d: null,
|
||||||
|
// effect
|
||||||
|
e: null,
|
||||||
|
// parent
|
||||||
|
p: /** @type {import('../../types.js').Block} */ (current_block),
|
||||||
|
// transition
|
||||||
|
r: null,
|
||||||
|
// type
|
||||||
|
t: IF_BLOCK,
|
||||||
|
// value
|
||||||
|
v: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Comment} anchor_node
|
||||||
|
* @param {() => boolean} condition_fn
|
||||||
|
* @param {(anchor: Node) => void} consequent_fn
|
||||||
|
* @param {null | ((anchor: Node) => void)} alternate_fn
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
export function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
|
||||||
|
const block = create_if_block();
|
||||||
|
hydrate_block_anchor(anchor_node);
|
||||||
|
/** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
|
||||||
|
let mismatch = false;
|
||||||
|
|
||||||
|
/** @type {null | import('../../types.js').TemplateNode | Array<import('../../types.js').TemplateNode>} */
|
||||||
|
let consequent_dom = null;
|
||||||
|
/** @type {null | import('../../types.js').TemplateNode | Array<import('../../types.js').TemplateNode>} */
|
||||||
|
let alternate_dom = null;
|
||||||
|
let has_mounted = false;
|
||||||
|
/**
|
||||||
|
* @type {import('../../types.js').EffectSignal | null}
|
||||||
|
*/
|
||||||
|
let current_branch_effect = null;
|
||||||
|
|
||||||
|
const if_effect = render_effect(
|
||||||
|
() => {
|
||||||
|
const result = !!condition_fn();
|
||||||
|
if (block.v !== result || !has_mounted) {
|
||||||
|
block.v = result;
|
||||||
|
if (has_mounted) {
|
||||||
|
const consequent_transitions = block.c;
|
||||||
|
const alternate_transitions = block.a;
|
||||||
|
if (result) {
|
||||||
|
if (alternate_transitions === null || alternate_transitions.size === 0) {
|
||||||
|
execute_effect(alternate_effect);
|
||||||
|
} else {
|
||||||
|
trigger_transitions(alternate_transitions, 'out');
|
||||||
|
}
|
||||||
|
if (consequent_transitions === null || consequent_transitions.size === 0) {
|
||||||
|
execute_effect(consequent_effect);
|
||||||
|
} else {
|
||||||
|
trigger_transitions(consequent_transitions, 'in');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (consequent_transitions === null || consequent_transitions.size === 0) {
|
||||||
|
execute_effect(consequent_effect);
|
||||||
|
} else {
|
||||||
|
trigger_transitions(consequent_transitions, 'out');
|
||||||
|
}
|
||||||
|
if (alternate_transitions === null || alternate_transitions.size === 0) {
|
||||||
|
execute_effect(alternate_effect);
|
||||||
|
} else {
|
||||||
|
trigger_transitions(alternate_transitions, 'in');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (hydrating) {
|
||||||
|
const comment_text = /** @type {Comment} */ (current_hydration_fragment?.[0])?.data;
|
||||||
|
if (
|
||||||
|
!comment_text ||
|
||||||
|
(comment_text === 'ssr:if:true' && !result) ||
|
||||||
|
(comment_text === 'ssr:if:false' && result)
|
||||||
|
) {
|
||||||
|
// Hydration mismatch: remove everything inside the anchor and start fresh.
|
||||||
|
// This could happen using when `{#if browser} .. {/if}` in SvelteKit.
|
||||||
|
remove(current_hydration_fragment);
|
||||||
|
set_current_hydration_fragment(null);
|
||||||
|
mismatch = true;
|
||||||
|
} else {
|
||||||
|
// Remove the ssr:if comment node or else it will confuse the subsequent hydration algorithm
|
||||||
|
current_hydration_fragment.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
has_mounted = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
block,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
// Managed effect
|
||||||
|
const consequent_effect = render_effect(
|
||||||
|
(
|
||||||
|
/** @type {any} */ _,
|
||||||
|
/** @type {import('../../types.js').EffectSignal | null} */ consequent_effect
|
||||||
|
) => {
|
||||||
|
const result = block.v;
|
||||||
|
if (!result && consequent_dom !== null) {
|
||||||
|
remove(consequent_dom);
|
||||||
|
consequent_dom = null;
|
||||||
|
}
|
||||||
|
if (result && current_branch_effect !== consequent_effect) {
|
||||||
|
consequent_fn(anchor_node);
|
||||||
|
if (mismatch && current_branch_effect === null) {
|
||||||
|
// Set fragment so that Svelte continues to operate in hydration mode
|
||||||
|
set_current_hydration_fragment([]);
|
||||||
|
}
|
||||||
|
current_branch_effect = consequent_effect;
|
||||||
|
consequent_dom = block.d;
|
||||||
|
}
|
||||||
|
block.d = null;
|
||||||
|
},
|
||||||
|
block,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
block.ce = consequent_effect;
|
||||||
|
// Managed effect
|
||||||
|
const alternate_effect = render_effect(
|
||||||
|
(
|
||||||
|
/** @type {any} */ _,
|
||||||
|
/** @type {import('../../types.js').EffectSignal | null} */ alternate_effect
|
||||||
|
) => {
|
||||||
|
const result = block.v;
|
||||||
|
if (result && alternate_dom !== null) {
|
||||||
|
remove(alternate_dom);
|
||||||
|
alternate_dom = null;
|
||||||
|
}
|
||||||
|
if (!result && current_branch_effect !== alternate_effect) {
|
||||||
|
if (alternate_fn !== null) {
|
||||||
|
alternate_fn(anchor_node);
|
||||||
|
}
|
||||||
|
if (mismatch && current_branch_effect === null) {
|
||||||
|
// Set fragment so that Svelte continues to operate in hydration mode
|
||||||
|
set_current_hydration_fragment([]);
|
||||||
|
}
|
||||||
|
current_branch_effect = alternate_effect;
|
||||||
|
alternate_dom = block.d;
|
||||||
|
}
|
||||||
|
block.d = null;
|
||||||
|
},
|
||||||
|
block,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
block.ae = alternate_effect;
|
||||||
|
push_destroy_fn(if_effect, () => {
|
||||||
|
if (consequent_dom !== null) {
|
||||||
|
remove(consequent_dom);
|
||||||
|
}
|
||||||
|
if (alternate_dom !== null) {
|
||||||
|
remove(alternate_dom);
|
||||||
|
}
|
||||||
|
destroy_signal(consequent_effect);
|
||||||
|
destroy_signal(alternate_effect);
|
||||||
|
});
|
||||||
|
block.e = if_effect;
|
||||||
|
}
|
||||||
@ -0,0 +1,140 @@
|
|||||||
|
import { UNINITIALIZED, KEY_BLOCK } from '../../constants.js';
|
||||||
|
import { hydrate_block_anchor } from '../../hydration.js';
|
||||||
|
import { remove } from '../../reconciler.js';
|
||||||
|
import { current_block, destroy_signal, execute_effect, push_destroy_fn } from '../../runtime.js';
|
||||||
|
import { render_effect } from '../../reactivity/computations.js';
|
||||||
|
import { trigger_transitions } from '../../transitions.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 {(anchor: Node) => void} render_fn
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
export function key_block(anchor_node, key, render_fn) {
|
||||||
|
const block = create_key_block();
|
||||||
|
|
||||||
|
/** @type {null | import('../../types.js').Render} */
|
||||||
|
let current_render = null;
|
||||||
|
hydrate_block_anchor(anchor_node);
|
||||||
|
|
||||||
|
/** @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_signal(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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
block,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
// To ensure topological ordering of the key effect to the render effect,
|
||||||
|
// we trigger the effect after.
|
||||||
|
render();
|
||||||
|
mounted = true;
|
||||||
|
push_destroy_fn(key_effect, () => {
|
||||||
|
let render = current_render;
|
||||||
|
while (render !== null) {
|
||||||
|
const dom = render.d;
|
||||||
|
if (dom !== null) {
|
||||||
|
remove(dom);
|
||||||
|
}
|
||||||
|
const effect = render.e;
|
||||||
|
if (effect !== null) {
|
||||||
|
destroy_signal(effect);
|
||||||
|
}
|
||||||
|
render = render.p;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
block.e = key_effect;
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
import { run_all } from '../../common.js';
|
||||||
|
|
||||||
|
let is_task_queued = false;
|
||||||
|
let is_raf_queued = false;
|
||||||
|
|
||||||
|
/** @type {Array<() => void>} */
|
||||||
|
let current_queued_tasks = [];
|
||||||
|
/** @type {Array<() => void>} */
|
||||||
|
let current_raf_tasks = [];
|
||||||
|
|
||||||
|
function process_task() {
|
||||||
|
is_task_queued = false;
|
||||||
|
const tasks = current_queued_tasks.slice();
|
||||||
|
current_queued_tasks = [];
|
||||||
|
run_all(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
function process_raf_task() {
|
||||||
|
is_raf_queued = false;
|
||||||
|
const tasks = current_raf_tasks.slice();
|
||||||
|
current_raf_tasks = [];
|
||||||
|
run_all(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {() => void} fn
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
export function schedule_task(fn) {
|
||||||
|
if (!is_task_queued) {
|
||||||
|
is_task_queued = true;
|
||||||
|
setTimeout(process_task, 0);
|
||||||
|
}
|
||||||
|
current_queued_tasks.push(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {() => void} fn
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
export function schedule_raf_task(fn) {
|
||||||
|
if (!is_raf_queued) {
|
||||||
|
is_raf_queued = true;
|
||||||
|
requestAnimationFrame(process_raf_task);
|
||||||
|
}
|
||||||
|
current_raf_tasks.push(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronously run any queued tasks.
|
||||||
|
*/
|
||||||
|
export function flush_tasks() {
|
||||||
|
if (is_task_queued) {
|
||||||
|
process_task();
|
||||||
|
}
|
||||||
|
if (is_raf_queued) {
|
||||||
|
process_raf_task();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,258 @@
|
|||||||
|
import { DEV } from 'esm-env';
|
||||||
|
import {
|
||||||
|
current_block,
|
||||||
|
current_component_context,
|
||||||
|
current_consumer,
|
||||||
|
current_effect,
|
||||||
|
destroy_signal,
|
||||||
|
flush_local_render_effects,
|
||||||
|
schedule_effect
|
||||||
|
} from '../runtime.js';
|
||||||
|
import { default_equals, safe_equal } from './equality.js';
|
||||||
|
import {
|
||||||
|
DIRTY,
|
||||||
|
MANAGED,
|
||||||
|
RENDER_EFFECT,
|
||||||
|
EFFECT,
|
||||||
|
PRE_EFFECT,
|
||||||
|
DERIVED,
|
||||||
|
UNOWNED,
|
||||||
|
CLEAN,
|
||||||
|
UNINITIALIZED
|
||||||
|
} from '../constants.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template V
|
||||||
|
* @param {import('../types.js').SignalFlags} flags
|
||||||
|
* @param {V} value
|
||||||
|
* @param {import('../types.js').Block | null} block
|
||||||
|
*/
|
||||||
|
function create_computation_signal(flags, value, block) {
|
||||||
|
/** @type {import('../types.js').ComputationSignal<V>} */
|
||||||
|
const signal = {
|
||||||
|
b: block,
|
||||||
|
c: null,
|
||||||
|
d: null,
|
||||||
|
e: null,
|
||||||
|
f: flags,
|
||||||
|
l: 0,
|
||||||
|
i: null,
|
||||||
|
r: null,
|
||||||
|
v: value,
|
||||||
|
w: 0,
|
||||||
|
x: null,
|
||||||
|
y: null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (DEV) {
|
||||||
|
// @ts-expect-error
|
||||||
|
signal.inspect = new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
return signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../types.js').ComputationSignal} target_signal
|
||||||
|
* @param {import('../types.js').ComputationSignal} ref_signal
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
export function push_reference(target_signal, ref_signal) {
|
||||||
|
const references = target_signal.r;
|
||||||
|
if (references === null) {
|
||||||
|
target_signal.r = [ref_signal];
|
||||||
|
} else {
|
||||||
|
references.push(ref_signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../types.js').EffectType} type
|
||||||
|
* @param {(() => void | (() => void)) | ((b: import('../types.js').Block) => void | (() => void))} fn
|
||||||
|
* @param {boolean} sync
|
||||||
|
* @param {null | import('../types.js').Block} block
|
||||||
|
* @param {boolean} schedule
|
||||||
|
* @returns {import('../types.js').EffectSignal}
|
||||||
|
*/
|
||||||
|
function internal_create_effect(type, fn, sync, block, schedule) {
|
||||||
|
const signal = create_computation_signal(type | DIRTY, null, block);
|
||||||
|
signal.i = fn;
|
||||||
|
signal.x = current_component_context;
|
||||||
|
if (current_effect !== null) {
|
||||||
|
signal.l = current_effect.l + 1;
|
||||||
|
if ((type & MANAGED) === 0) {
|
||||||
|
push_reference(current_effect, signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (schedule) {
|
||||||
|
schedule_effect(signal, sync);
|
||||||
|
}
|
||||||
|
return signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function effect_active() {
|
||||||
|
return current_effect ? (current_effect.f & MANAGED) === 0 : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal representation of `$effect(...)`
|
||||||
|
* @param {() => void | (() => void)} fn
|
||||||
|
* @returns {import('../types.js').EffectSignal}
|
||||||
|
*/
|
||||||
|
export function user_effect(fn) {
|
||||||
|
if (current_effect === null) {
|
||||||
|
throw new Error(
|
||||||
|
'ERR_SVELTE_ORPHAN_EFFECT' +
|
||||||
|
(DEV ? ': The Svelte $effect rune can only be used during component initialisation.' : '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const apply_component_effect_heuristics =
|
||||||
|
current_effect.f & RENDER_EFFECT &&
|
||||||
|
current_component_context !== null &&
|
||||||
|
!current_component_context.m;
|
||||||
|
|
||||||
|
const effect = internal_create_effect(
|
||||||
|
EFFECT,
|
||||||
|
fn,
|
||||||
|
false,
|
||||||
|
current_block,
|
||||||
|
!apply_component_effect_heuristics
|
||||||
|
);
|
||||||
|
|
||||||
|
if (apply_component_effect_heuristics) {
|
||||||
|
const context = /** @type {import('../types.js').ComponentContext} */ (
|
||||||
|
current_component_context
|
||||||
|
);
|
||||||
|
(context.e ??= []).push(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
return effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal representation of `$effect.root(...)`
|
||||||
|
* @param {() => void | (() => void)} fn
|
||||||
|
* @returns {() => void}
|
||||||
|
*/
|
||||||
|
export function user_root_effect(fn) {
|
||||||
|
const effect = render_effect(fn, current_block, true);
|
||||||
|
return () => {
|
||||||
|
destroy_signal(effect);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {() => void | (() => void)} fn
|
||||||
|
* @returns {import('../types.js').EffectSignal}
|
||||||
|
*/
|
||||||
|
export function effect(fn) {
|
||||||
|
return internal_create_effect(EFFECT, fn, false, current_block, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {() => void | (() => void)} fn
|
||||||
|
* @returns {import('../types.js').EffectSignal}
|
||||||
|
*/
|
||||||
|
export function managed_effect(fn) {
|
||||||
|
return internal_create_effect(EFFECT | MANAGED, fn, false, current_block, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {() => void | (() => void)} fn
|
||||||
|
* @param {boolean} sync
|
||||||
|
* @returns {import('../types.js').EffectSignal}
|
||||||
|
*/
|
||||||
|
export function managed_pre_effect(fn, sync) {
|
||||||
|
return internal_create_effect(PRE_EFFECT | MANAGED, fn, sync, current_block, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal representation of `$effect.pre(...)`
|
||||||
|
* @param {() => void | (() => void)} fn
|
||||||
|
* @returns {import('../types.js').EffectSignal}
|
||||||
|
*/
|
||||||
|
export function pre_effect(fn) {
|
||||||
|
if (current_effect === null) {
|
||||||
|
throw new Error(
|
||||||
|
'ERR_SVELTE_ORPHAN_EFFECT' +
|
||||||
|
(DEV
|
||||||
|
? ': The Svelte $effect.pre rune can only be used during component initialisation.'
|
||||||
|
: '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const sync = current_effect !== null && (current_effect.f & RENDER_EFFECT) !== 0;
|
||||||
|
return internal_create_effect(
|
||||||
|
PRE_EFFECT,
|
||||||
|
() => {
|
||||||
|
const val = fn();
|
||||||
|
flush_local_render_effects();
|
||||||
|
return val;
|
||||||
|
},
|
||||||
|
sync,
|
||||||
|
current_block,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This effect is used to ensure binding are kept in sync. We use a pre effect to ensure we run before the
|
||||||
|
* bindings which are in later effects. However, we don't use a pre_effect directly as we don't want to flush anything.
|
||||||
|
*
|
||||||
|
* @param {() => void | (() => void)} fn
|
||||||
|
* @returns {import('../types.js').EffectSignal}
|
||||||
|
*/
|
||||||
|
export function invalidate_effect(fn) {
|
||||||
|
return internal_create_effect(PRE_EFFECT, fn, true, current_block, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template {import('../types.js').Block} B
|
||||||
|
* @param {(block: B) => void | (() => void)} fn
|
||||||
|
* @param {any} block
|
||||||
|
* @param {any} managed
|
||||||
|
* @param {any} sync
|
||||||
|
* @returns {import('../types.js').EffectSignal}
|
||||||
|
*/
|
||||||
|
export function render_effect(fn, block = current_block, managed = false, sync = true) {
|
||||||
|
let flags = RENDER_EFFECT;
|
||||||
|
if (managed) {
|
||||||
|
flags |= MANAGED;
|
||||||
|
}
|
||||||
|
return internal_create_effect(flags, /** @type {any} */ (fn), sync, block, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template V
|
||||||
|
* @param {() => V} fn
|
||||||
|
* @returns {import('../types.js').ComputationSignal<V>}
|
||||||
|
*/
|
||||||
|
/*#__NO_SIDE_EFFECTS__*/
|
||||||
|
export function derived(fn) {
|
||||||
|
const is_unowned = current_effect === null;
|
||||||
|
const flags = is_unowned ? DERIVED | UNOWNED : DERIVED;
|
||||||
|
const signal = /** @type {import('../types.js').ComputationSignal<V>} */ (
|
||||||
|
create_computation_signal(flags | CLEAN, UNINITIALIZED, current_block)
|
||||||
|
);
|
||||||
|
signal.i = fn;
|
||||||
|
signal.e = default_equals;
|
||||||
|
if (current_consumer !== null) {
|
||||||
|
push_reference(current_consumer, signal);
|
||||||
|
}
|
||||||
|
return signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template V
|
||||||
|
* @param {() => V} fn
|
||||||
|
* @returns {import('../types.js').ComputationSignal<V>}
|
||||||
|
*/
|
||||||
|
/*#__NO_SIDE_EFFECTS__*/
|
||||||
|
export function derived_safe_equal(fn) {
|
||||||
|
const signal = derived(fn);
|
||||||
|
signal.e = safe_equal;
|
||||||
|
return signal;
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* @param {unknown} a
|
||||||
|
* @param {unknown} b
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function default_equals(a, b) {
|
||||||
|
return a === b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {unknown} a
|
||||||
|
* @param {unknown} b
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function safe_not_equal(a, b) {
|
||||||
|
// eslint-disable-next-line eqeqeq
|
||||||
|
return a != a
|
||||||
|
? // eslint-disable-next-line eqeqeq
|
||||||
|
b == b
|
||||||
|
: a !== b || (a !== null && typeof a === 'object') || typeof a === 'function';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {unknown} a
|
||||||
|
* @param {unknown} b
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function safe_equal(a, b) {
|
||||||
|
return !safe_not_equal(a, b);
|
||||||
|
}
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
import { DEV } from 'esm-env';
|
||||||
|
import { current_component_context } from '../runtime.js';
|
||||||
|
import { default_equals, safe_equal } from './equality.js';
|
||||||
|
import { CLEAN, SOURCE } from '../constants.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template V
|
||||||
|
* @param {V} initial_value
|
||||||
|
* @returns {import('../types.js').SourceSignal<V>}
|
||||||
|
*/
|
||||||
|
/*#__NO_SIDE_EFFECTS__*/
|
||||||
|
export function source(initial_value) {
|
||||||
|
return create_source_signal(SOURCE | CLEAN, initial_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template V
|
||||||
|
* @param {V} initial_value
|
||||||
|
* @returns {import('../types.js').SourceSignal<V>}
|
||||||
|
*/
|
||||||
|
/*#__NO_SIDE_EFFECTS__*/
|
||||||
|
export function mutable_source(initial_value) {
|
||||||
|
const s = source(initial_value);
|
||||||
|
s.e = safe_equal;
|
||||||
|
|
||||||
|
// bind the signal to the component context, in case we need to
|
||||||
|
// track updates to trigger beforeUpdate/afterUpdate callbacks
|
||||||
|
if (current_component_context) {
|
||||||
|
(current_component_context.d ??= []).push(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template V
|
||||||
|
* @param {import('../types.js').SignalFlags} flags
|
||||||
|
* @param {V} value
|
||||||
|
* @returns {import('../types.js').SourceSignal<V> | import('../types.js').SourceSignal<V> & import('../types.js').SourceSignalDebug}
|
||||||
|
*/
|
||||||
|
function create_source_signal(flags, value) {
|
||||||
|
if (DEV) {
|
||||||
|
return {
|
||||||
|
// consumers
|
||||||
|
c: null,
|
||||||
|
// equals
|
||||||
|
e: default_equals,
|
||||||
|
// flags
|
||||||
|
f: flags,
|
||||||
|
// value
|
||||||
|
v: value,
|
||||||
|
// write version
|
||||||
|
w: 0,
|
||||||
|
// this is for DEV only
|
||||||
|
inspect: new Set()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
// consumers
|
||||||
|
c: null,
|
||||||
|
// equals
|
||||||
|
e: default_equals,
|
||||||
|
// flags
|
||||||
|
f: flags,
|
||||||
|
// value
|
||||||
|
v: value,
|
||||||
|
// write version
|
||||||
|
w: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,153 @@
|
|||||||
|
import { subscribe_to_store } from '../../../store/utils.js';
|
||||||
|
import { noop } from '../../common.js';
|
||||||
|
import { UNINITIALIZED } from '../constants.js';
|
||||||
|
import { get, set, set_ignore_mutation_validation, untrack } from '../runtime.js';
|
||||||
|
import { user_effect } from './computations.js';
|
||||||
|
import { mutable_source } from './sources.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current value of a store. If the store isn't subscribed to yet, it will create a proxy
|
||||||
|
* signal that will be updated when the store is. The store references container is needed to
|
||||||
|
* track reassignments to stores and to track the correct component context.
|
||||||
|
* @template V
|
||||||
|
* @param {import('../types.js').Store<V> | null | undefined} store
|
||||||
|
* @param {string} store_name
|
||||||
|
* @param {import('../types.js').StoreReferencesContainer} stores
|
||||||
|
* @returns {V}
|
||||||
|
*/
|
||||||
|
export function store_get(store, store_name, stores) {
|
||||||
|
/** @type {import('../types.js').StoreReferencesContainer[''] | undefined} */
|
||||||
|
let entry = stores[store_name];
|
||||||
|
const is_new = entry === undefined;
|
||||||
|
|
||||||
|
if (is_new) {
|
||||||
|
entry = {
|
||||||
|
store: null,
|
||||||
|
last_value: null,
|
||||||
|
value: mutable_source(UNINITIALIZED),
|
||||||
|
unsubscribe: noop
|
||||||
|
};
|
||||||
|
// TODO: can we remove this code? it was refactored out when we split up source/comptued signals
|
||||||
|
// push_destroy_fn(entry.value, () => {
|
||||||
|
// /** @type {import('../types.js').StoreReferencesContainer['']} */ (entry).last_value =
|
||||||
|
// /** @type {import('../types.js').StoreReferencesContainer['']} */ (entry).value.value;
|
||||||
|
// });
|
||||||
|
stores[store_name] = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_new || entry.store !== store) {
|
||||||
|
entry.unsubscribe();
|
||||||
|
entry.store = store ?? null;
|
||||||
|
entry.unsubscribe = connect_store_to_signal(store, entry.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = get(entry.value);
|
||||||
|
// This could happen if the store was cleaned up because the component was destroyed and there's a leak on the user side.
|
||||||
|
// In that case we don't want to fail with a cryptic Symbol error, but rather return the last value we got.
|
||||||
|
return value === UNINITIALIZED ? entry.last_value : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template V
|
||||||
|
* @param {import('../types.js').Store<V> | null | undefined} store
|
||||||
|
* @param {import('../types.js').SourceSignal<V>} source
|
||||||
|
*/
|
||||||
|
function connect_store_to_signal(store, source) {
|
||||||
|
if (store == null) {
|
||||||
|
set(source, undefined);
|
||||||
|
return noop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {V} v */
|
||||||
|
const run = (v) => {
|
||||||
|
set_ignore_mutation_validation(true);
|
||||||
|
set(source, v);
|
||||||
|
set_ignore_mutation_validation(false);
|
||||||
|
};
|
||||||
|
return subscribe_to_store(store, run);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the new value of a store and returns that value.
|
||||||
|
* @template V
|
||||||
|
* @param {import('../types.js').Store<V>} store
|
||||||
|
* @param {V} value
|
||||||
|
* @returns {V}
|
||||||
|
*/
|
||||||
|
export function store_set(store, value) {
|
||||||
|
store.set(value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribes from all auto-subscribed stores on destroy
|
||||||
|
* @param {import('../types.js').StoreReferencesContainer} stores
|
||||||
|
*/
|
||||||
|
export function unsubscribe_on_destroy(stores) {
|
||||||
|
on_destroy(() => {
|
||||||
|
let store_name;
|
||||||
|
for (store_name in stores) {
|
||||||
|
const ref = stores[store_name];
|
||||||
|
ref.unsubscribe();
|
||||||
|
// TODO: can we remove this code? it was refactored out when we split up source/comptued signals
|
||||||
|
// destroy_signal(ref.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a store with a new value.
|
||||||
|
* @param {import('../types.js').Store<V>} store the store to update
|
||||||
|
* @param {any} expression the expression that mutates the store
|
||||||
|
* @param {V} new_value the new store value
|
||||||
|
* @template V
|
||||||
|
*/
|
||||||
|
export function mutate_store(store, expression, new_value) {
|
||||||
|
store.set(new_value);
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template V
|
||||||
|
* @param {unknown} val
|
||||||
|
* @returns {val is import('../types.js').Store<V>}
|
||||||
|
*/
|
||||||
|
export function is_store(val) {
|
||||||
|
return (
|
||||||
|
typeof val === 'object' &&
|
||||||
|
val !== null &&
|
||||||
|
typeof (/** @type {import('../types.js').Store<V>} */ (val).subscribe) === 'function'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../types.js').Store<number>} store
|
||||||
|
* @param {number} store_value
|
||||||
|
* @param {1 | -1} [d]
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function update_store(store, store_value, d = 1) {
|
||||||
|
store.set(store_value + d);
|
||||||
|
return store_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../types.js').Store<number>} store
|
||||||
|
* @param {number} store_value
|
||||||
|
* @param {1 | -1} [d]
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function update_pre_store(store, store_value, d = 1) {
|
||||||
|
const value = store_value + d;
|
||||||
|
store.set(value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules a callback to run immediately before the component is unmounted.
|
||||||
|
* @param {() => any} fn
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function on_destroy(fn) {
|
||||||
|
user_effect(() => () => untrack(fn));
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,4 +0,0 @@
|
|||||||
<script>
|
|
||||||
let count = $state(0);
|
|
||||||
export const double = $derived(count * 2);
|
|
||||||
</script>
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
import { test } from '../../test';
|
|
||||||
|
|
||||||
export default test({
|
|
||||||
error: {
|
|
||||||
code: 'invalid-state-export',
|
|
||||||
message:
|
|
||||||
"Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
|
|
||||||
position: [59, 99]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
<script>
|
|
||||||
export const object = $state({
|
|
||||||
ok: true
|
|
||||||
});
|
|
||||||
|
|
||||||
export const primitive = $state('nope');
|
|
||||||
|
|
||||||
export function update_object() {
|
|
||||||
object.ok = !object.ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function update_primitive() {
|
|
||||||
primitive = 'yep';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import { test } from '../../test';
|
|
||||||
|
|
||||||
export default test({
|
|
||||||
error: {
|
|
||||||
code: 'invalid-prop-export',
|
|
||||||
message:
|
|
||||||
'Cannot export properties. To expose the current value of a property, export a function returning its value'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
<script>
|
|
||||||
let { foo } = $props();
|
|
||||||
export { foo };
|
|
||||||
</script>
|
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
/* (unused) x y z {
|
||||||
|
color: red;
|
||||||
|
}*/
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
<x>
|
||||||
|
<z></z>
|
||||||
|
</x>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
x y z {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,12 +1,5 @@
|
|||||||
import { test } from '../../test';
|
import { test } from '../../test';
|
||||||
|
|
||||||
export default test({
|
export default test({
|
||||||
warnings: [
|
warnings: []
|
||||||
{
|
|
||||||
code: 'css-unused-selector',
|
|
||||||
message: 'Unused CSS selector "a:global(.foo) > div"',
|
|
||||||
start: { character: 91, column: 1, line: 8 },
|
|
||||||
end: { character: 111, column: 21, line: 8 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
div > div.svelte-xyz {
|
a > b > div.svelte-xyz {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
<div class="svelte-xyz">
|
<div class="svelte-xyz">
|
||||||
<div class="svelte-xyz"></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
@ -1,3 +0,0 @@
|
|||||||
a > b > div.svelte-xyz {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
<div class="svelte-xyz">
|
|
||||||
<div class="svelte-xyz"></div>
|
|
||||||
</div>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue