Socket
Socket
Sign inDemoInstall

svelte

Package Overview
Dependencies
18
Maintainers
3
Versions
617
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 5.0.0-next.81 to 5.0.0-next.82

src/internal/client/dom/blocks/utils.js

2

package.json

@@ -5,3 +5,3 @@ {

"license": "MIT",
"version": "5.0.0-next.81",
"version": "5.0.0-next.82",
"type": "module",

@@ -8,0 +8,0 @@ "types": "./types/index.d.ts",

@@ -185,2 +185,3 @@ /** @typedef {{ start?: number, end?: number }} NodeLike */

`$props() can only be used at the top level of components as a variable declaration initializer`,
'invalid-bindable-location': () => `$bindable() can only be used inside a $props() declaration`,
/** @param {string} rune */

@@ -187,0 +188,0 @@ 'invalid-state-location': (rune) =>

@@ -124,3 +124,6 @@ // @ts-expect-error acorn type definitions are borked in the release we use

insert +
parser.template.slice(parser.index);
// If this is a type annotation for a function parameter, Acorn-TS will treat subsequent
// parameters as part of a sequence expression instead, and will then error on optional
// parameters (`?:`). Therefore replace that sequence with something that will not error.
parser.template.slice(parser.index).replace(/\?\s*:/g, ':');
let expression = parse_expression_at(template, parser.ts, a);

@@ -127,0 +130,0 @@

@@ -439,3 +439,3 @@ import is_reference from 'is-reference';

} else {
instance.scope.declare(b.id('$$props'), 'prop', 'synthetic');
instance.scope.declare(b.id('$$props'), 'bindable_prop', 'synthetic');
instance.scope.declare(b.id('$$restProps'), 'rest_prop', 'synthetic');

@@ -470,3 +470,6 @@

for (const [name, binding] of instance.scope.declarations) {
if (binding.kind === 'prop' && binding.node.name !== '$$props') {
if (
(binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
binding.node.name !== '$$props'
) {
const references = binding.references.filter(

@@ -758,8 +761,9 @@ (r) => r.node !== binding.node && r.path.at(-1)?.type !== 'ExportSpecifier'

if (
binding.kind === 'state' ||
binding.kind === 'frozen_state' ||
(binding.kind === 'normal' &&
(binding.declaration_kind === 'let' || binding.declaration_kind === 'var'))
binding !== null &&
(binding.kind === 'state' ||
binding.kind === 'frozen_state' ||
(binding.kind === 'normal' &&
(binding.declaration_kind === 'let' || binding.declaration_kind === 'var')))
) {
binding.kind = 'prop';
binding.kind = 'bindable_prop';
if (specifier.exported.name !== specifier.local.name) {

@@ -802,3 +806,3 @@ binding.prop_alias = specifier.exported.name;

const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(id.name));
binding.kind = 'prop';
binding.kind = 'bindable_prop';
}

@@ -892,7 +896,20 @@ }

: /** @type {string} */ (/** @type {import('estree').Literal} */ (property.key).value);
const initial = property.value.type === 'AssignmentPattern' ? property.value.right : null;
let initial = property.value.type === 'AssignmentPattern' ? property.value.right : null;
const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(name));
binding.prop_alias = alias;
binding.initial = initial; // rewire initial from $props() to the actual initial value
// rewire initial from $props() to the actual initial value, stripping $bindable() if necessary
if (
initial?.type === 'CallExpression' &&
initial.callee.type === 'Identifier' &&
initial.callee.name === '$bindable'
) {
binding.initial = /** @type {import('estree').Expression | null} */ (
initial.arguments[0] ?? null
);
binding.kind = 'bindable_prop';
} else {
binding.initial = initial;
}
}

@@ -1080,2 +1097,3 @@ }

if (
context.state.analysis.runes &&
node !== binding.node &&

@@ -1082,0 +1100,0 @@ // If we have $state that can be proxied or frozen and isn't re-assigned, then that means

@@ -302,2 +302,4 @@ import {

const binding = context.state.scope.get(left.name);
if (

@@ -307,3 +309,2 @@ assignee.type === 'Identifier' &&

) {
const binding = context.state.scope.get(left.name);
// reassignment

@@ -315,2 +316,3 @@ if (

binding.kind !== 'prop' &&
binding.kind !== 'bindable_prop' &&
binding.kind !== 'each' &&

@@ -334,4 +336,2 @@ binding.kind !== 'store_sub' &&

const binding = context.state.scope.get(left.name);
if (node.name === 'group') {

@@ -787,3 +787,21 @@ if (!binding) {

if (rune === '$state' || rune === '$derived' || rune === '$derived.by') {
if (rune === '$bindable') {
if (parent.type === 'AssignmentPattern' && path.at(-3)?.type === 'ObjectPattern') {
const declarator = path.at(-4);
if (
declarator?.type === 'VariableDeclarator' &&
get_rune(declarator.init, scope) === '$props'
) {
return;
}
}
error(node, 'invalid-bindable-location');
}
if (
rune === '$state' ||
rune === '$state.frozen' ||
rune === '$derived' ||
rune === '$derived.by'
) {
if (parent.type === 'VariableDeclarator') return;

@@ -881,2 +899,4 @@ if (parent.type === 'PropertyDefinition' && !parent.static && !parent.computed) return;

error(node, 'invalid-props-location');
} else if (rune === '$bindable') {
error(node, 'invalid-bindable-location');
}

@@ -1031,2 +1051,5 @@ },

CallExpression(node, { state, path }) {
if (get_rune(node, state.scope) === '$bindable' && node.arguments.length > 1) {
error(node, 'invalid-rune-args-length', '$bindable', [0, 1]);
}
validate_call_expression(node, state.scope, path);

@@ -1072,3 +1095,3 @@ },

if (args.length > 0) {
error(node, 'invalid-rune-args-length', '$props', [0]);
error(node, 'invalid-rune-args-length', rune, [0]);
}

@@ -1075,0 +1098,0 @@

@@ -242,3 +242,3 @@ import { walk } from 'zimmerframe';

const properties = analysis.exports.map(({ name, alias }) => {
const component_returned_object = analysis.exports.map(({ name, alias }) => {
const expression = serialize_get_binding(b.id(name), instance_state);

@@ -253,12 +253,41 @@

const properties = [...analysis.instance.scope.declarations].filter(
([name, binding]) =>
(binding.kind === 'prop' || binding.kind === 'bindable_prop') && !name.startsWith('$$')
);
if (analysis.runes && options.dev) {
/** @type {import('estree').Literal[]} */
const bindable = [];
for (const [name, binding] of properties) {
if (binding.kind === 'bindable_prop') {
bindable.push(b.literal(binding.prop_alias ?? name));
}
}
instance.body.unshift(
b.stmt(b.call('$.validate_prop_bindings', b.id('$$props'), b.array(bindable)))
);
}
if (analysis.accessors) {
for (const [name, binding] of analysis.instance.scope.declarations) {
if (binding.kind !== 'prop' || name.startsWith('$$')) continue;
for (const [name, binding] of properties) {
const key = binding.prop_alias ?? name;
properties.push(
b.get(key, [b.return(b.call(b.id(name)))]),
b.set(key, [b.stmt(b.call(b.id(name), b.id('$$value'))), b.stmt(b.call('$.flushSync'))])
);
const getter = b.get(key, [b.return(b.call(b.id(name)))]);
const setter = b.set(key, [
b.stmt(b.call(b.id(name), b.id('$$value'))),
b.stmt(b.call('$.flushSync'))
]);
if (analysis.runes && binding.initial) {
// turn `set foo($$value)` into `set foo($$value = expression)`
setter.value.params[0] = {
type: 'AssignmentPattern',
left: b.id('$$value'),
right: /** @type {import('estree').Expression} */ (binding.initial)
};
}
component_returned_object.push(getter, setter);
}

@@ -268,3 +297,3 @@ }

if (options.legacy.componentApi) {
properties.push(
component_returned_object.push(
b.init('$set', b.id('$.update_legacy_props')),

@@ -285,3 +314,3 @@ b.init(

} else if (options.dev) {
properties.push(
component_returned_object.push(
b.init(

@@ -354,4 +383,4 @@ '$set',

component_block.body.push(
properties.length > 0
? b.return(b.call('$.pop', b.object(properties)))
component_returned_object.length > 0
? b.return(b.call('$.pop', b.object(component_returned_object)))
: b.stmt(b.call('$.pop'))

@@ -364,3 +393,3 @@ );

for (const [name, binding] of analysis.instance.scope.declarations) {
if (binding.kind === 'prop') named_props.push(binding.prop_alias ?? name);
if (binding.kind === 'bindable_prop') named_props.push(binding.prop_alias ?? name);
}

@@ -472,5 +501,3 @@

for (const [name, binding] of analysis.instance.scope.declarations) {
if (binding.kind !== 'prop' || name.startsWith('$$')) continue;
for (const [name, binding] of properties) {
const key = binding.prop_alias ?? name;

@@ -477,0 +504,0 @@ const prop_def = typeof ce === 'boolean' ? {} : ce.props?.[key] || {};

@@ -81,3 +81,3 @@ import * as b from '../../../utils/builders.js';

if (binding.kind === 'prop') {
if (binding.kind === 'prop' || binding.kind === 'bindable_prop') {
if (binding.node.name === '$$props') {

@@ -381,2 +381,3 @@ // Special case for $$props which only exists in the old world

binding.kind !== 'prop' &&
binding.kind !== 'bindable_prop' &&
binding.kind !== 'each' &&

@@ -394,3 +395,3 @@ binding.kind !== 'legacy_reactive' &&

if (left === node.left) {
if (binding.kind === 'prop') {
if (binding.kind === 'prop' || binding.kind === 'bindable_prop') {
return b.call(left, value);

@@ -473,3 +474,3 @@ } else if (is_store) {

} else if (!state.analysis.runes) {
if (binding.kind === 'prop') {
if (binding.kind === 'bindable_prop') {
return b.call(

@@ -578,3 +579,3 @@ left,

// If we are referencing a simple $$props value, then we need to reference the object property instead
binding.kind === 'prop' &&
(binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
!binding.reassigned &&

@@ -581,0 +582,0 @@ binding.initial === null &&

@@ -55,2 +55,3 @@ import is_reference from 'is-reference';

binding?.kind === 'prop' ||
binding?.kind === 'bindable_prop' ||
is_store

@@ -68,3 +69,3 @@ ) {

} else {
if (binding.kind === 'prop') fn += '_prop';
if (binding.kind === 'prop' || binding.kind === 'bindable_prop') fn += '_prop';
args.push(b.id(name));

@@ -71,0 +72,0 @@ }

@@ -43,3 +43,3 @@ import { is_hoistable_function } from '../../utils.js';

const has_state = bindings.some((binding) => binding.kind === 'state');
const has_props = bindings.some((binding) => binding.kind === 'prop');
const has_props = bindings.some((binding) => binding.kind === 'bindable_prop');

@@ -84,3 +84,3 @@ if (!has_state && !has_props) {

path.node,
binding.kind === 'prop'
binding.kind === 'bindable_prop'
? get_prop_source(binding, state, binding.prop_alias ?? name, value)

@@ -173,3 +173,3 @@ : value

// from a runes-component, where mutations don't trigger an update on the prop as a whole.
if (name === '$$props' || name === '$$restProps' || binding.kind === 'prop') {
if (name === '$$props' || name === '$$restProps' || binding.kind === 'bindable_prop') {
serialized = b.call('$.deep_read_state', serialized);

@@ -176,0 +176,0 @@ }

@@ -210,13 +210,9 @@ import { get_rune } from '../../../scope.js';

let id = property.value;
let initial = undefined;
if (property.value.type === 'AssignmentPattern') {
id = property.value.left;
initial = /** @type {import('estree').Expression} */ (visit(property.value.right));
}
let id =
property.value.type === 'AssignmentPattern' ? property.value.left : property.value;
assert.equal(id.type, 'Identifier');
const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(id.name));
const initial =
binding.initial &&
/** @type {import('estree').Expression} */ (visit(binding.initial));

@@ -228,12 +224,13 @@ if (binding.reassigned || state.analysis.accessors || initial) {

// RestElement
declarations.push(
b.declarator(
property.argument,
b.call(
'$.rest_props',
b.id('$$props'),
b.array(seen.map((name) => b.literal(name)))
)
)
);
/** @type {import('estree').Expression[]} */
const args = [b.id('$$props'), b.array(seen.map((name) => b.literal(name)))];
if (state.options.dev) {
// include rest name, so we can provide informative error messages
args.push(
b.literal(/** @type {import('estree').Identifier} */ (property.argument).name)
);
}
declarations.push(b.declarator(property.argument, b.call('$.rest_props', ...args)));
}

@@ -240,0 +237,0 @@ }

@@ -185,4 +185,2 @@ /**

files: {
event: 'change',
type: 'set',
valid_elements: ['input'],

@@ -189,0 +187,0 @@ omit_in_ssr: true

@@ -35,2 +35,3 @@ import { AttributeAliases, DOMBooleanAttributes } from '../../constants.js';

'$props',
'$bindable',
'$derived',

@@ -37,0 +38,0 @@ '$derived.by',

@@ -244,3 +244,4 @@ import type {

* - `normal`: A variable that is not in any way special
* - `prop`: A normal prop (possibly mutated)
* - `prop`: A normal prop (possibly reassigned or mutated)
* - `bindable_prop`: A prop one can `bind:` to (possibly reassigned or mutated)
* - `rest_prop`: A rest prop

@@ -257,2 +258,3 @@ * - `state`: A state variable

| 'prop'
| 'bindable_prop'
| 'rest_prop'

@@ -285,3 +287,3 @@ | 'state'

legacy_dependencies: Binding[];
/** Legacy props: the `class` in `{ export klass as class}` */
/** Legacy props: the `class` in `{ export klass as class}`. $props(): The `class` in { class: klass } = $props() */
prop_alias: string | null;

@@ -288,0 +290,0 @@ /**

@@ -21,2 +21,11 @@ import { regex_is_valid_identifier } from '../phases/patterns.js';

/**
* @param {import('estree').Pattern} left
* @param {import('estree').Expression} right
* @returns {import('estree').AssignmentPattern}
*/
export function assignment_pattern(left, right) {
return { type: 'AssignmentPattern', left, right };
}
/**
* @param {Array<import('estree').Pattern>} params

@@ -214,3 +223,3 @@ * @param {import('estree').BlockStatement | import('estree').Expression} body

* @param {import('estree').Statement[]} body
* @returns {import('estree').Property}
* @returns {import('estree').Property & { value: import('estree').FunctionExpression}}}
*/

@@ -310,7 +319,8 @@ export function get(name, body) {

/**
* @template {import('estree').Expression} Value
* @param {'init' | 'get' | 'set'} kind
* @param {import('estree').Expression} key
* @param {import('estree').Expression} value
* @param {Value} value
* @param {boolean} computed
* @returns {import('estree').Property}
* @returns {import('estree').Property & { value: Value }}
*/

@@ -361,3 +371,3 @@ export function prop(kind, key, value, computed = false) {

* @param {import('estree').Statement[]} body
* @returns {import('estree').Property}
* @returns {import('estree').Property & { value: import('estree').FunctionExpression}}
*/

@@ -364,0 +374,0 @@ export function set(name, body) {

@@ -15,2 +15,6 @@ export const EACH_ITEM_REACTIVE = 1;

export const TRANSITION_IN = 1;
export const TRANSITION_OUT = 1 << 1;
export const TRANSITION_GLOBAL = 1 << 2;
/** List of Element events that will be delegated */

@@ -17,0 +21,0 @@ export const DelegatedEvents = [

@@ -12,15 +12,6 @@ export const DERIVED = 1 << 1;

export const DESTROYED = 1 << 12;
export const IS_ELSEIF = 1 << 13;
export const EFFECT_RAN = 1 << 14;
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');
import { is_promise } from '../../../common.js';
import { hydrate_block_anchor } from '../hydration.js';
import { remove } from '../reconciler.js';
import { current_block, execute_effect, flushSync } from '../../runtime.js';
import { destroy_effect, render_effect } from '../../reactivity/effects.js';
import { trigger_transitions } from '../elements/transitions.js';
import { AWAIT_BLOCK, UNINITIALIZED } from '../../constants.js';
import {
current_component_context,
flushSync,
set_current_component_context,
set_current_effect,
set_current_reaction
} from '../../runtime.js';
import { destroy_effect, pause_effect, render_effect } from '../../reactivity/effects.js';
import { DESTROYED, INERT } from '../../constants.js';
import { create_block } from './utils.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 {Comment} anchor
* @param {(() => Promise<V>)} get_input
* @param {null | ((anchor: Node) => void)} pending_fn

@@ -36,160 +24,110 @@ * @param {null | ((anchor: Node, value: V) => void)} then_fn

*/
export function await_block(anchor_node, input, pending_fn, then_fn, catch_fn) {
const block = create_await_block();
export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
const block = create_block();
/** @type {null | import('../../types.js').Render} */
let current_render = null;
hydrate_block_anchor(anchor_node);
const component_context = current_component_context;
/** @type {{}} */
let latest_token;
hydrate_block_anchor(anchor);
/** @type {typeof UNINITIALIZED | V} */
let resolved_value = UNINITIALIZED;
/** @type {any} */
let input;
/** @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_effect(render.e);
render.e = null;
}
/** @type {import('#client').Effect | null} */
let pending_effect;
/** @type {import('#client').Effect | null} */
let then_effect;
/** @type {import('#client').Effect | null} */
let catch_effect;
/**
* @param {(anchor: Comment, value: any) => void} fn
* @param {any} value
*/
function create_effect(fn, value) {
set_current_effect(branch);
set_current_reaction(branch); // TODO do we need both?
set_current_component_context(component_context);
var effect = render_effect(() => fn(anchor, value), {}, true);
set_current_component_context(null);
set_current_reaction(null);
set_current_effect(null);
// without this, the DOM does not update until two ticks after the promise,
// resolves which is unexpected behaviour (and somewhat irksome to test)
flushSync();
return effect;
}
/** @param {import('#client').Effect} effect */
function pause(effect) {
if ((effect.f & DESTROYED) !== 0) return;
const block = effect.block;
pause_effect(effect, () => {
// TODO make this unnecessary
const dom = block?.d;
if (dom) remove(dom);
});
}
const branch = render_effect(() => {
if (input === (input = get_input())) return;
if (is_promise(input)) {
const promise = /** @type {Promise<any>} */ (input);
if (pending_fn) {
if (pending_effect && (pending_effect.f & INERT) === 0) {
if (pending_effect.block?.d) remove(pending_effect.block.d);
destroy_effect(pending_effect);
}
});
};
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);
pending_effect = render_effect(() => pending_fn(anchor), {}, true);
}
if (then_effect) pause(then_effect);
if (catch_effect) pause(catch_effect);
promise.then(
(value) => {
if (promise !== input) return;
if (pending_effect) pause(pending_effect);
if (then_fn) {
then_effect = create_effect(then_fn, value);
}
} else if (catch_fn !== null) {
// pending = false
block.n = false;
catch_fn(anchor_node, error);
},
(error) => {
if (promise !== input) return;
if (pending_effect) pause(pending_effect);
if (catch_fn) {
catch_effect = create_effect(catch_fn, 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_effect) pause(pending_effect);
if (catch_effect) pause(catch_effect);
if (then_fn) {
if (then_effect) {
if (then_effect.block?.d) remove(then_effect.block.d);
destroy_effect(then_effect);
}
if (!pending) {
pending = true;
render();
}
} else {
error = UNINITIALIZED;
resolved_value = promise;
pending = false;
render();
then_effect = render_effect(() => then_fn(anchor, input), {}, true);
}
},
block,
false
);
await_effect.ondestroy = () => {
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_effect(effect);
}
render = render.p;
}
}, block);
branch.ondestroy = () => {
// TODO this sucks, tidy it up
if (pending_effect?.block?.d) remove(pending_effect.block.d);
if (then_effect?.block?.d) remove(then_effect.block.d);
if (catch_effect?.block?.d) remove(catch_effect.block.d);
};
block.e = await_effect;
}

@@ -5,3 +5,3 @@ import { namespace_svg } from '../../../../constants.js';

import { render_effect } from '../../reactivity/effects.js';
import { insert, remove } from '../reconciler.js';
import { remove } from '../reconciler.js';

@@ -37,3 +37,3 @@ /**

insert(tag, null, anchor);
anchor.before(tag);
component_anchor = empty();

@@ -40,0 +40,0 @@ tag.appendChild(component_anchor);

@@ -9,3 +9,2 @@ import {

} from '../../../../constants.js';
import { noop } from '../../../common.js';
import {

@@ -18,71 +17,30 @@ current_hydration_fragment,

} from '../hydration.js';
import { clear_text_content, empty } from '../operations.js';
import { empty } from '../operations.js';
import { insert, remove } from '../reconciler.js';
import { current_block, execute_effect } from '../../runtime.js';
import { destroy_effect, render_effect } from '../../reactivity/effects.js';
import { untrack } from '../../runtime.js';
import {
destroy_effect,
pause_effect,
render_effect,
resume_effect,
user_effect
} from '../../reactivity/effects.js';
import { source, mutable_source, set } from '../../reactivity/sources.js';
import { trigger_transitions } from '../elements/transitions.js';
import { is_array, is_frozen, map_get, map_set } from '../../utils.js';
import { EACH_BLOCK, EACH_ITEM_BLOCK, STATE_SYMBOL } from '../../constants.js';
import { STATE_SYMBOL } from '../../constants.js';
import { create_block } from './utils.js';
const NEW_BLOCK = -1;
const MOVED_BLOCK = 99999999;
const LIS_BLOCK = -2;
var NEW_BLOCK = -1;
var LIS_BLOCK = -2;
/**
* @param {number} flags
* @param {Element | Comment} anchor
* @returns {import('../../types.js').EachBlock}
* The row of a keyed each block that is currently updating. We track this
* so that `animate:` directives have something to attach themselves to
* @type {import('#client').EachItem | null}
*/
export function create_each_block(flags, anchor) {
return {
// anchor
a: anchor,
// dom
d: null,
// flags
f: flags,
// items
v: [],
// effect
e: null,
p: /** @type {import('../../types.js').Block} */ (current_block),
// transition
r: null,
// transitions
s: [],
// type
t: EACH_BLOCK
};
}
export let current_each_item_block = null;
/**
* @param {any | import('../../types.js').Value<any>} item
* @param {number | import('../../types.js').Value<number>} index
* @param {null | unknown} key
* @returns {import('../../types.js').EachItemBlock}
*/
export function create_each_item_block(item, index, key) {
return {
// animate transition
a: null,
// dom
d: null,
// effect
e: null,
// index
i: index,
// key
k: key,
// item
v: item,
// parent
p: /** @type {import('../../types.js').EachBlock} */ (current_block),
// transition
r: null,
// transitions
s: null,
// type
t: EACH_ITEM_BLOCK
};
/** @param {import('#client').EachItem | null} block */
export function set_current_each_item_block(block) {
current_each_item_block = block;
}

@@ -92,6 +50,6 @@

* @template V
* @param {Element | Comment} anchor_node
* @param {() => V[]} collection
* @param {Element | Comment} anchor The next sibling node, or the parent node if this is a 'controlled' block
* @param {() => V[]} get_collection
* @param {number} flags
* @param {null | ((item: V) => string)} key_fn
* @param {null | ((item: V) => string)} get_key
* @param {(anchor: null, item: V, index: import('#client').MaybeSource<number>) => void} render_fn

@@ -102,154 +60,134 @@ * @param {null | ((anchor: Node) => void)} fallback_fn

*/
function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_fn) {
const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
const block = create_each_block(flags, anchor_node);
function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, reconcile_fn) {
var block = create_block();
/** @type {null | import('../../types.js').Render} */
let current_fallback = null;
hydrate_block_anchor(anchor_node, is_controlled);
/** @type {import('#client').EachState} */
var state = { flags, items: [] };
/** @type {V[]} */
let array;
var is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
hydrate_block_anchor(is_controlled ? /** @type {Node} */ (anchor.firstChild) : anchor);
/** @type {Array<string> | null} */
let keys = null;
if (is_controlled) {
var parent_node = /** @type {Element} */ (anchor);
parent_node.append((anchor = empty()));
}
/** @type {null | import('../../types.js').Effect} */
let render = null;
/** @type {import('#client').Effect | null} */
var fallback = null;
/**
* Whether or not there was a "rendered fallback but want to render items" (or vice versa) hydration mismatch.
* Needs to be a `let` or else it isn't treeshaken out
*/
let mismatch = false;
var effect = render_effect(
() => {
var collection = get_collection();
block.r =
/** @param {import('../../types.js').Transition} transition */
(transition) => {
const fallback = /** @type {import('../../types.js').Render} */ (current_fallback);
const transitions = fallback.s;
transitions.add(transition);
transition.f(() => {
transitions.delete(transition);
if (transitions.size === 0) {
if (fallback.e !== null) {
if (fallback.d !== null) {
remove(fallback.d);
fallback.d = null;
}
destroy_effect(fallback.e);
fallback.e = null;
}
}
});
};
var array = is_array(collection)
? collection
: collection == null
? []
: Array.from(collection);
const create_fallback_effect = () => {
/** @type {import('../../types.js').Render} */
const fallback = {
d: null,
e: null,
s: new Set(),
p: current_fallback
};
// Managed effect
const effect = render_effect(
() => {
const dom = block.d;
if (dom !== null) {
remove(dom);
block.d = null;
}
let anchor = block.a;
const is_controlled = (block.f & EACH_IS_CONTROLLED) !== 0;
if (is_controlled) {
// If the each block is controlled, then the anchor node will be the surrounding
// element in which the each block is rendered, which requires certain handling
// depending on whether we're in hydration mode or not
if (!hydrating) {
// Create a new anchor on the fly because there's none due to the optimization
anchor = empty();
block.a.appendChild(anchor);
} else {
// In case of hydration the anchor will be the first child of the surrounding element
anchor = /** @type {Comment} */ (anchor.firstChild);
}
}
/** @type {(anchor: Node) => void} */ (fallback_fn)(anchor);
fallback.d = block.d;
block.d = null;
},
block,
true
);
fallback.e = effect;
current_fallback = fallback;
};
var keys = get_key === null ? array : array.map(get_key);
/** @param {import('../../types.js').EachBlock} block */
const render_each = (block) => {
const flags = block.f;
const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
const anchor_node = block.a;
reconcile_fn(array, block, anchor_node, is_controlled, render_fn, flags, true, keys);
};
var length = array.length;
const each = render_effect(
() => {
/** @type {V[]} */
const maybe_array = collection();
array = is_array(maybe_array)
? maybe_array
: maybe_array == null
? []
: Array.from(maybe_array);
// If we are working with an array that isn't proxied or frozen, then remove strict equality and ensure the items
// are treated as reactive, so they get wrapped in a signal.
var flags = state.flags;
if ((flags & EACH_IS_STRICT_EQUALS) !== 0 && !is_frozen(array) && !(STATE_SYMBOL in array)) {
flags ^= EACH_IS_STRICT_EQUALS;
if (key_fn !== null) {
keys = array.map(key_fn);
} else if ((flags & EACH_KEYED) === 0) {
array.map(noop);
// Additionally if we're in an keyed each block, we'll need ensure the items are all wrapped in signals.
if ((flags & EACH_KEYED) !== 0 && (flags & EACH_ITEM_REACTIVE) === 0) {
flags ^= EACH_ITEM_REACTIVE;
}
}
const length = array.length;
/** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false;
if (hydrating) {
const is_each_else_comment =
var is_else =
/** @type {Comment} */ (current_hydration_fragment?.[0])?.data === 'ssr:each_else';
// Check for hydration mismatch which can happen if the server renders the each fallback
// but the client has items, or vice versa. If so, remove everything inside the anchor and start fresh.
if ((is_each_else_comment && length) || (!is_each_else_comment && !length)) {
if (is_else !== (length === 0)) {
// hydration mismatch — remove the server-rendered DOM and start over
remove(current_hydration_fragment);
set_current_hydration_fragment(null);
mismatch = true;
} else if (is_each_else_comment) {
} else if (is_else) {
// Remove the each_else comment node or else it will confuse the subsequent hydration algorithm
/** @type {import('../../types.js').TemplateNode[]} */ (
current_hydration_fragment
).shift();
/** @type {import('#client').TemplateNode[]} */ (current_hydration_fragment).shift();
}
}
// this is separate to the previous block because `hydrating` might change
if (hydrating) {
var b_blocks = [];
// Hydrate block
var hydration_list = /** @type {import('#client').TemplateNode[]} */ (
current_hydration_fragment
);
var hydrating_node = hydration_list[0];
for (var i = 0; i < length; i++) {
var fragment = get_hydration_fragment(hydrating_node);
set_current_hydration_fragment(fragment);
if (!fragment) {
// If fragment is null, then that means that the server rendered less items than what
// the client code specifies -> break out and continue with client-side node creation
mismatch = true;
break;
}
b_blocks[i] = create_item(array[i], keys?.[i], i, render_fn, flags);
// TODO helperise this
hydrating_node = /** @type {import('#client').TemplateNode} */ (
/** @type {Node} */ (
/** @type {Node} */ (fragment[fragment.length - 1] || hydrating_node).nextSibling
).nextSibling
);
}
remove_excess_hydration_nodes(hydration_list, hydrating_node);
state.items = b_blocks;
}
if (!hydrating) {
// TODO add 'empty controlled block' optimisation here
reconcile_fn(array, state, anchor, render_fn, flags, keys);
}
if (fallback_fn !== null) {
if (length === 0) {
if (block.v.length !== 0 || render === null) {
render_each(block);
create_fallback_effect();
return;
}
} else if (block.v.length === 0 && current_fallback !== null) {
const fallback = current_fallback;
const transitions = fallback.s;
if (transitions.size === 0) {
if (fallback.d !== null) {
remove(fallback.d);
fallback.d = null;
}
if (fallback) {
resume_effect(fallback);
} else {
trigger_transitions(transitions, 'out');
fallback = render_effect(
() => {
fallback_fn(anchor);
var dom = block.d; // TODO would be nice if this was just returned from the managed effect function...
return () => {
if (dom !== null) {
remove(dom);
dom = null;
}
};
},
block,
true
);
}
} else if (fallback !== null) {
pause_effect(fallback, () => {
fallback = null;
});
}
}
if (render !== null) {
execute_effect(render);
if (mismatch) {
// Set a fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}

@@ -261,31 +199,12 @@ },

render = render_effect(render_each, block, true);
if (mismatch) {
// Set a fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
each.ondestroy = () => {
const flags = block.f;
const anchor_node = block.a;
const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
let fallback = current_fallback;
while (fallback !== null) {
const dom = fallback.d;
if (dom !== null) {
remove(dom);
effect.ondestroy = () => {
for (var item of state.items) {
if (item.d !== null) {
destroy_effect(item.e);
remove(item.d);
}
const effect = fallback.e;
if (effect !== null) {
destroy_effect(effect);
}
fallback = fallback.p;
}
// Clear the array
reconcile_fn([], block, anchor_node, is_controlled, render_fn, flags, false, keys);
destroy_effect(/** @type {import('#client').Effect} */ (render));
if (fallback) destroy_effect(fallback);
};
block.e = each;
}

@@ -295,6 +214,6 @@

* @template V
* @param {Element | Comment} anchor_node
* @param {() => V[]} collection
* @param {Element | Comment} anchor
* @param {() => V[]} get_collection
* @param {number} flags
* @param {null | ((item: V) => string)} key_fn
* @param {null | ((item: V) => string)} get_key
* @param {(anchor: null, item: V, index: import('#client').MaybeSource<number>) => void} render_fn

@@ -304,4 +223,4 @@ * @param {null | ((anchor: Node) => void)} fallback_fn

*/
export function each_keyed(anchor_node, collection, flags, key_fn, render_fn, fallback_fn) {
each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_tracked_array);
export function each_keyed(anchor, get_collection, flags, get_key, render_fn, fallback_fn) {
each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, reconcile_tracked_array);
}

@@ -311,4 +230,4 @@

* @template V
* @param {Element | Comment} anchor_node
* @param {() => V[]} collection
* @param {Element | Comment} anchor
* @param {() => V[]} get_collection
* @param {number} flags

@@ -319,4 +238,4 @@ * @param {(anchor: null, item: V, index: import('#client').MaybeSource<number>) => void} render_fn

*/
export function each_indexed(anchor_node, collection, flags, render_fn, fallback_fn) {
each(anchor_node, collection, flags, null, render_fn, fallback_fn, reconcile_indexed_array);
export function each_indexed(anchor, get_collection, flags, render_fn, fallback_fn) {
each(anchor, get_collection, flags, null, render_fn, fallback_fn, reconcile_indexed_array);
}

@@ -327,114 +246,63 @@

* @param {Array<V>} array
* @param {import('../../types.js').EachBlock} each_block
* @param {Element | Comment | Text} dom
* @param {boolean} is_controlled
* @param {(anchor: null, item: V, index: number | import('../../types.js').Source<number>) => void} render_fn
* @param {import('#client').EachState} state
* @param {Element | Comment | Text} anchor
* @param {(anchor: null, item: V, index: number | import('#client').Source<number>) => void} render_fn
* @param {number} flags
* @param {boolean} apply_transitions
* @returns {void}
*/
function reconcile_indexed_array(
array,
each_block,
dom,
is_controlled,
render_fn,
flags,
apply_transitions
) {
// If we are working with an array that isn't proxied or frozen, then remove strict equality and ensure the items
// are treated as reactive, so they get wrapped in a signal.
if ((flags & EACH_IS_STRICT_EQUALS) !== 0 && !is_frozen(array) && !(STATE_SYMBOL in array)) {
flags ^= EACH_IS_STRICT_EQUALS;
}
var a_blocks = each_block.v;
var active_transitions = each_block.s;
function reconcile_indexed_array(array, state, anchor, render_fn, flags) {
var a_items = state.items;
/** @type {number | void} */
var a = a_blocks.length;
/** @type {number} */
var a = a_items.length;
var b = array.length;
var length = Math.max(a, b);
var index = 0;
var min = Math.min(a, b);
/** @type {Array<import('../../types.js').EachItemBlock>} */
var b_blocks;
var block;
/** @type {typeof a_items} */
var b_items = Array(b);
if (active_transitions.length !== 0) {
destroy_active_transition_blocks(active_transitions);
var item;
var value;
// update items
for (var i = 0; i < min; i += 1) {
value = array[i];
item = a_items[i];
b_items[i] = item;
update_item(item, value, i, flags);
resume_effect(item.e);
}
if (b === 0) {
b_blocks = [];
// Remove old blocks
if (is_controlled && a !== 0) {
clear_text_content(dom);
if (b > a) {
// add items
for (; i < b; i += 1) {
value = array[i];
item = create_item(value, null, i, render_fn, flags);
b_items[i] = item;
insert_item(item, anchor);
}
while (index < length) {
block = a_blocks[index++];
destroy_each_item_block(block, active_transitions, apply_transitions, is_controlled);
}
} else {
var item;
/** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false;
b_blocks = Array(b);
if (hydrating) {
// Hydrate block
var hydration_list = /** @type {import('../../types.js').TemplateNode[]} */ (
current_hydration_fragment
);
var hydrating_node = hydration_list[0];
for (; index < length; index++) {
var fragment = get_hydration_fragment(hydrating_node);
set_current_hydration_fragment(fragment);
if (!fragment) {
// If fragment is null, then that means that the server rendered less items than what
// the client code specifies -> break out and continue with client-side node creation
mismatch = true;
break;
}
item = array[index];
block = each_item_block(item, null, index, render_fn, flags);
b_blocks[index] = block;
state.items = b_items;
} else if (a > b) {
// remove items
var remaining = a - b;
hydrating_node = /** @type {import('../../types.js').TemplateNode} */ (
/** @type {Node} */ (/** @type {Node} */ (fragment[fragment.length - 1]).nextSibling)
.nextSibling
);
var clear = () => {
for (var i = b; i < a; i += 1) {
var block = a_items[i];
if (block.d) remove(block.d);
}
remove_excess_hydration_nodes(hydration_list, hydrating_node);
}
state.items.length = b;
};
for (; index < length; index++) {
if (index >= a) {
// Add block
item = array[index];
block = each_item_block(item, null, index, render_fn, flags);
b_blocks[index] = block;
insert_each_item_block(block, dom, is_controlled, null);
} else if (index >= b) {
// Remove block
block = a_blocks[index];
destroy_each_item_block(block, active_transitions, apply_transitions);
} else {
// Update block
item = array[index];
block = a_blocks[index];
b_blocks[index] = block;
update_each_item_block(block, item, index, flags);
var check = () => {
if (--remaining === 0) {
clear();
}
}
};
if (mismatch) {
// Server rendered less nodes than the client -> set empty array so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
for (; i < a; i += 1) {
pause_effect(a_items[i].e, check);
}
}
each_block.v = b_blocks;
}

@@ -448,241 +316,182 @@

* @param {Array<V>} array
* @param {import('../../types.js').EachBlock} each_block
* @param {Element | Comment | Text} dom
* @param {boolean} is_controlled
* @param {(anchor: null, item: V, index: number | import('../../types.js').Source<number>) => void} render_fn
* @param {import('#client').EachState} state
* @param {Element | Comment | Text} anchor
* @param {(anchor: null, item: V, index: number | import('#client').Source<number>) => void} render_fn
* @param {number} flags
* @param {boolean} apply_transitions
* @param {Array<string> | null} keys
* @param {any[]} keys
* @returns {void}
*/
function reconcile_tracked_array(
array,
each_block,
dom,
is_controlled,
render_fn,
flags,
apply_transitions,
keys
) {
// If we are working with an array that isn't proxied or frozen, then remove strict equality and ensure the items
// are treated as reactive, so they get wrapped in a signal.
if ((flags & EACH_IS_STRICT_EQUALS) !== 0 && !is_frozen(array) && !(STATE_SYMBOL in array)) {
flags ^= EACH_IS_STRICT_EQUALS;
// Additionally as we're in an keyed each block, we'll need ensure the itens are all wrapped in signals.
if ((flags & EACH_ITEM_REACTIVE) === 0) {
flags ^= EACH_ITEM_REACTIVE;
}
}
var a_blocks = each_block.v;
const is_computed_key = keys !== null;
var active_transitions = each_block.s;
function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
var a_blocks = state.items;
/** @type {number | void} */
var a = a_blocks.length;
/** @type {number} */
var b = array.length;
/** @type {Array<import('../../types.js').EachItemBlock>} */
var b_blocks;
/** @type {Array<import('#client').EachItem>} */
var b_blocks = Array(b);
var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0;
var start = 0;
var block;
if (active_transitions.length !== 0) {
destroy_active_transition_blocks(active_transitions);
/** @type {Array<import('#client').EachItem>} */
var to_destroy = [];
// Step 1 — trim common suffix
while (a > 0 && b > 0 && a_blocks[a - 1].k === keys[b - 1]) {
block = b_blocks[--b] = a_blocks[--a];
anchor = get_first_child(block);
resume_effect(block.e);
if (should_update) {
update_item(block, array[b], b, flags);
}
}
if (b === 0) {
b_blocks = [];
// Remove old blocks
if (is_controlled && a !== 0) {
clear_text_content(dom);
// Step 2 — trim common prefix
while (start < a && start < b && a_blocks[start].k === keys[start]) {
block = b_blocks[start] = a_blocks[start];
resume_effect(block.e);
if (should_update) {
update_item(block, array[start], start, flags);
}
while (a > 0) {
block = a_blocks[--a];
destroy_each_item_block(block, active_transitions, apply_transitions, is_controlled);
start += 1;
}
// Step 3 — update
if (start === a) {
// add only
while (start < b) {
block = create_item(array[start], keys[start], start, render_fn, flags);
b_blocks[start++] = block;
insert_item(block, anchor);
}
} else if (start === b) {
// remove only
while (start < a) {
to_destroy.push(a_blocks[start++]);
}
} else {
var a_end = a - 1;
var b_end = b - 1;
var key;
var item;
var idx;
/** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false;
b_blocks = Array(b);
if (hydrating) {
// Hydrate block
var fragment;
var hydration_list = /** @type {import('../../types.js').TemplateNode[]} */ (
current_hydration_fragment
);
var hydrating_node = hydration_list[0];
while (b > 0) {
fragment = get_hydration_fragment(hydrating_node);
set_current_hydration_fragment(fragment);
if (!fragment) {
// If fragment is null, then that means that the server rendered less items than what
// the client code specifies -> break out and continue with client-side node creation
mismatch = true;
break;
// reconcile
var moved = false;
var sources = new Int32Array(b - start);
var indexes = new Map();
var i;
var index;
var last_block;
var last_sibling;
// store the indexes of each block in the new world
for (i = start; i < b; i += 1) {
sources[i - start] = NEW_BLOCK;
map_set(indexes, keys[i], i);
}
/** @type {Array<import('#client').EachItem>} */
var to_animate = [];
if (is_animated) {
// for all blocks that were in both the old and the new list,
// measure them and store them in `to_animate` so we can
// apply animations once the DOM has been updated
for (i = 0; i < a_blocks.length; i += 1) {
block = a_blocks[i];
if (indexes.has(block.k)) {
block.a?.measure();
to_animate.push(block);
}
}
}
idx = b_end - --b;
item = array[idx];
key = is_computed_key ? keys[idx] : item;
block = each_item_block(item, key, idx, render_fn, flags);
b_blocks[idx] = block;
// populate the `sources` array for each old block with
// its new index, so that we can calculate moves
for (i = start; i < a; i += 1) {
block = a_blocks[i];
index = map_get(indexes, block.k);
// Get the <!--ssr:..--> tag of the next item in the list
// The fragment array can be empty if each block has no content
hydrating_node = /** @type {import('../../types.js').TemplateNode} */ (
/** @type {Node} */ ((fragment[fragment.length - 1] || hydrating_node).nextSibling)
.nextSibling
);
resume_effect(block.e);
if (index === undefined) {
to_destroy.push(block);
} else {
moved = true;
sources[index - start] = i;
b_blocks[index] = block;
if (is_animated) {
to_animate.push(block);
}
}
}
remove_excess_hydration_nodes(hydration_list, hydrating_node);
// if we need to move blocks (as opposed to just adding/removing),
// figure out how to do so efficiently (I would be lying if I said
// I fully understand this part)
if (moved) {
mark_lis(sources);
}
if (a === 0) {
// Create new blocks
while (b > 0) {
idx = b_end - --b;
item = array[idx];
key = is_computed_key ? keys[idx] : item;
block = each_item_block(item, key, idx, render_fn, flags);
b_blocks[idx] = block;
insert_each_item_block(block, dom, is_controlled, null);
}
} else {
var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
var should_update_block =
(flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0 || is_animated;
var start = 0;
// working from the back, insert new or moved blocks
while (b-- > start) {
index = sources[b - start];
var insert = index === NEW_BLOCK;
/** @type {null | Text | Element | Comment} */
var sibling = null;
item = array[b_end];
key = is_computed_key ? keys[b_end] : item;
// Step 1
outer: while (true) {
// From the end
while (a_blocks[a_end].k === key) {
block = a_blocks[a_end--];
item = array[b_end];
if (should_update_block) {
update_each_item_block(block, item, b_end, flags);
}
sibling = get_first_child(block);
b_blocks[b_end] = block;
if (start > --b_end || start > a_end) {
break outer;
}
key = is_computed_key ? keys[b_end] : item;
}
item = array[start];
key = is_computed_key ? keys[start] : item;
// At the start
while (start <= a_end && start <= b_end && a_blocks[start].k === key) {
item = array[start];
block = a_blocks[start];
if (should_update_block) {
update_each_item_block(block, item, start, flags);
}
b_blocks[start] = block;
++start;
key = is_computed_key ? keys[start] : array[start];
}
break;
}
// Step 2
if (start > a_end) {
while (b_end >= start) {
item = array[b_end];
key = is_computed_key ? keys[b_end] : item;
block = each_item_block(item, key, b_end, render_fn, flags);
b_blocks[b_end--] = block;
sibling = insert_each_item_block(block, dom, is_controlled, sibling);
}
} else if (start > b_end) {
b = start;
do {
if ((block = a_blocks[b++]) !== null) {
destroy_each_item_block(block, active_transitions, apply_transitions);
}
} while (b <= a_end);
if (insert) {
block = create_item(array[b], keys[b], b, render_fn, flags);
} else {
// Step 3
var pos = 0;
var b_length = b_end - start + 1;
var sources = new Int32Array(b_length);
var item_index = new Map();
for (b = 0; b < b_length; ++b) {
a = b + start;
sources[b] = NEW_BLOCK;
item = array[a];
key = is_computed_key ? keys[a] : item;
map_set(item_index, key, a);
block = b_blocks[b];
if (should_update) {
update_item(block, array[b], b, flags);
}
// If keys are animated, we need to do updates before actual moves
if (is_animated) {
for (b = start; b <= a_end; ++b) {
a = map_get(item_index, /** @type {V} */ (a_blocks[b].k));
if (a !== undefined) {
item = array[a];
block = a_blocks[b];
update_each_item_block(block, item, a, flags);
}
}
}
for (b = start; b <= a_end; ++b) {
a = map_get(item_index, /** @type {V} */ (a_blocks[b].k));
block = a_blocks[b];
if (a !== undefined) {
pos = pos < a ? a : MOVED_BLOCK;
sources[a - start] = b;
b_blocks[a] = block;
} else if (block !== null) {
destroy_each_item_block(block, active_transitions, apply_transitions);
}
}
// Step 4
if (pos === MOVED_BLOCK) {
mark_lis(sources);
}
var last_block;
var last_sibling;
var should_create;
while (b_length-- > 0) {
b_end = b_length + start;
a = sources[b_length];
should_create = a === -1;
item = array[b_end];
if (should_create) {
key = is_computed_key ? keys[b_end] : item;
block = each_item_block(item, key, b_end, render_fn, flags);
} else {
block = b_blocks[b_end];
if (!is_animated && should_update_block) {
update_each_item_block(block, item, b_end, flags);
}
}
if (should_create || (pos === MOVED_BLOCK && a !== LIS_BLOCK)) {
last_sibling = last_block === undefined ? sibling : get_first_child(last_block);
sibling = insert_each_item_block(block, dom, is_controlled, last_sibling);
}
b_blocks[b_end] = block;
last_block = block;
}
}
if (insert || (moved && index !== LIS_BLOCK)) {
last_sibling = last_block === undefined ? anchor : get_first_child(last_block);
anchor = insert_item(block, last_sibling);
}
last_block = b_blocks[b] = block;
}
if (mismatch) {
// Server rendered less nodes than the client -> set empty array so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
if (to_animate.length > 0) {
// TODO we need to briefly take any outroing elements out of the flow, so that
// we can figure out the eventual destination of the animating elements
// - https://github.com/sveltejs/svelte/pull/10798#issuecomment-2013681778
// - https://svelte.dev/repl/6e891305e9644a7ca7065fa95c79d2d2?version=4.2.9
user_effect(() => {
untrack(() => {
for (block of to_animate) {
block.a?.apply();
}
});
});
}
}
each_block.v = b_blocks;
var remaining = to_destroy.length;
if (remaining > 0) {
var clear = () => {
for (block of to_destroy) {
if (block.d) remove(block.d);
}
state.items = b_blocks;
};
var check = () => {
if (--remaining === 0) {
clear();
}
};
for (block of to_destroy) {
pause_effect(block.e, check);
}
} else {
state.items = b_blocks;
}
}

@@ -693,4 +502,4 @@

* In that case, we need to remove the remaining server-rendered nodes.
* @param {import('../../types.js').TemplateNode[]} hydration_list
* @param {import('../../types.js').TemplateNode | null} next_node
* @param {import('#client').TemplateNode[]} hydration_list
* @param {import('#client').TemplateNode | null} next_node
*/

@@ -779,24 +588,13 @@ function remove_excess_hydration_nodes(hydration_list, next_node) {

/**
* @param {import('../../types.js').Block} block
* @param {Element | Comment | Text} dom
* @param {boolean} is_controlled
* @param {null | Text | Element | Comment} sibling
* @param {import('#client').EachItem} block
* @param {Text | Element | Comment} sibling
* @returns {Text | Element | Comment}
*/
function insert_each_item_block(block, dom, is_controlled, sibling) {
var current = /** @type {import('../../types.js').TemplateNode} */ (block.d);
if (sibling === null) {
if (is_controlled) {
return insert(current, /** @type {Element} */ (dom), null);
} else {
return insert(current, /** @type {Element} */ (dom.parentNode), dom);
}
}
return insert(current, null, sibling);
function insert_item(block, sibling) {
var current = /** @type {import('#client').TemplateNode} */ (block.d);
return insert(current, sibling);
}
/**
* @param {import('../../types.js').Block} block
* @param {import('#client').EachItem} block
* @returns {Text | Element | Comment}

@@ -815,47 +613,3 @@ */

/**
* @param {Array<import('../../types.js').EachItemBlock>} active_transitions
* @returns {void}
*/
function destroy_active_transition_blocks(active_transitions) {
var length = active_transitions.length;
if (length > 0) {
var i = 0;
var block;
var transition;
for (; i < length; i++) {
block = active_transitions[i];
transition = block.r;
if (transition !== null) {
block.r = null;
destroy_each_item_block(block, null, false);
}
}
active_transitions.length = 0;
}
}
/**
* @param {import('../../types.js').Block} block
* @returns {Text | Element | Comment}
*/
export function get_first_element(block) {
const current = block.d;
if (is_array(current)) {
for (let i = 0; i < current.length; i++) {
const node = current[i];
if (node.nodeType !== 8) {
return node;
}
}
}
return /** @type {Text | Element | Comment} */ (current);
}
/**
* @param {import('../../types.js').EachItemBlock} block
* @param {import('#client').EachItem} block
* @param {any} item

@@ -866,16 +620,9 @@ * @param {number} index

*/
function update_each_item_block(block, item, index, type) {
const block_v = block.v;
function update_item(block, item, index, type) {
if ((type & EACH_ITEM_REACTIVE) !== 0) {
set(block_v, item);
set(block.v, item);
}
const transitions = block.s;
const index_is_reactive = (type & EACH_INDEX_REACTIVE) !== 0;
// Handle each item animations
const each_animation = block.a;
if (transitions !== null && (type & EACH_KEYED) !== 0 && each_animation !== null) {
each_animation(block, transitions);
}
if (index_is_reactive) {
set(/** @type {import('../../types.js').Value<number>} */ (block.i), index);
if ((type & EACH_INDEX_REACTIVE) !== 0) {
set(/** @type {import('#client').Value<number>} */ (block.i), index);
} else {

@@ -887,41 +634,2 @@ block.i = index;

/**
* @param {import('../../types.js').EachItemBlock} block
* @param {null | Array<import('../../types.js').Block>} transition_block
* @param {boolean} apply_transitions
* @param {any} controlled
* @returns {void}
*/
export function destroy_each_item_block(
block,
transition_block,
apply_transitions,
controlled = false
) {
const transitions = block.s;
if (apply_transitions && transitions !== null) {
// We might have pending key transitions, if so remove them first
for (let other of transitions) {
if (other.r === 'key') {
transitions.delete(other);
}
}
if (transitions.size === 0) {
block.s = null;
} else {
trigger_transitions(transitions, 'out');
if (transition_block !== null) {
transition_block.push(block);
}
return;
}
}
const dom = block.d;
if (!controlled && dom !== null) {
remove(dom);
}
destroy_effect(/** @type {import('#client').Effect} */ (block.e));
}
/**
* @template V

@@ -931,10 +639,10 @@ * @param {V} item

* @param {number} index
* @param {(anchor: null, item: V, index: number | import('../../types.js').Value<number>) => void} render_fn
* @param {(anchor: null, item: V, index: number | import('#client').Value<number>) => void} render_fn
* @param {number} flags
* @returns {import('../../types.js').EachItemBlock}
* @returns {import('#client').EachItem}
*/
function each_item_block(item, key, index, render_fn, flags) {
const each_item_not_reactive = (flags & EACH_ITEM_REACTIVE) === 0;
function create_item(item, key, index, render_fn, flags) {
var each_item_not_reactive = (flags & EACH_ITEM_REACTIVE) === 0;
const item_value = each_item_not_reactive
var item_value = each_item_not_reactive
? item

@@ -945,16 +653,35 @@ : (flags & EACH_IS_STRICT_EQUALS) !== 0

const index_value = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index);
const block = create_each_item_block(item_value, index_value, key);
/** @type {import('#client').EachItem} */
var block = {
a: null,
// dom
d: null,
// effect
// @ts-expect-error
e: null,
// index
i: (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index),
// key
k: key,
// item
v: item_value
};
const effect = render_effect(
/** @param {import('../../types.js').EachItemBlock} block */
(block) => {
render_fn(null, block.v, block.i);
},
block,
true
);
var previous_each_item_block = current_each_item_block;
block.e = effect;
return block;
try {
current_each_item_block = block;
block.e = render_effect(
() => {
render_fn(null, block.v, block.i);
},
block,
true
);
return block;
} finally {
current_each_item_block = previous_each_item_block;
}
}

@@ -11,3 +11,3 @@ import { render_effect } from '../../reactivity/effects.js';

export function html(dom, get_value, svg) {
/** @type {import('#client').TemplateNode | import('#client').TemplateNode[]} */
/** @type {import('#client').Dom} */
let html_dom;

@@ -14,0 +14,0 @@

@@ -1,2 +0,2 @@

import { IF_BLOCK } from '../../constants.js';
import { IS_ELSEIF } from '../../constants.js';
import {

@@ -9,194 +9,150 @@ current_hydration_fragment,

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 {
destroy_effect,
pause_effect,
render_effect,
resume_effect
} from '../../reactivity/effects.js';
import { create_block } from './utils.js';
/** @returns {import('#client').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('#client').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
* @param {Comment} anchor
* @param {() => boolean} get_condition
* @param {(anchor: Node) => import('#client').Dom} consequent_fn
* @param {null | ((anchor: Node) => import('#client').Dom)} alternate_fn
* @param {boolean} [elseif] True if this is an `{:else if ...}` block rather than an `{#if ...}`, as that affects which transitions are considered 'local'
* @returns {void}
*/
export function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
const block = create_if_block();
export function if_block(anchor, get_condition, consequent_fn, alternate_fn, elseif = false) {
const block = create_block();
hydrate_block_anchor(anchor_node);
hydrate_block_anchor(anchor);
/** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false;
/** @type {undefined | import('#client').Dom} */
let consequent_dom;
/** @type {null | import('#client').TemplateNode | Array<import('#client').TemplateNode>} */
let consequent_dom = null;
/** @type {undefined | import('#client').Dom} */
let alternate_dom;
/** @type {null | import('#client').TemplateNode | Array<import('#client').TemplateNode>} */
let alternate_dom = null;
/** @type {import('#client').Effect | null} */
let consequent_effect = null;
let has_mounted = false;
/** @type {import('#client').Effect | null} */
let alternate_effect = null;
/**
* @type {import('#client').Effect | null}
*/
let current_branch_effect = null;
/** @type {boolean | null} */
let condition = null;
/** @type {import('#client').Effect} */
let consequent_effect;
/** @type {import('#client').Effect} */
let alternate_effect;
const if_effect = render_effect(() => {
const result = !!condition_fn();
if (condition === (condition = !!get_condition())) return;
if (block.v !== result || !has_mounted) {
block.v = result;
/** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false;
if (has_mounted) {
const consequent_transitions = block.c;
const alternate_transitions = block.a;
if (hydrating) {
const comment_text = /** @type {Comment} */ (current_hydration_fragment?.[0])?.data;
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();
}
if (
!comment_text ||
(comment_text === 'ssr:if:true' && !condition) ||
(comment_text === 'ssr:if:false' && condition)
) {
// 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;
}
// create these here so they have the correct parent/child relationship
consequent_effect ??= render_effect(
(/** @type {any} */ _, /** @type {import('#client').Effect | null} */ consequent_effect) => {
const result = block.v;
if (condition) {
if (consequent_effect) {
resume_effect(consequent_effect);
} else {
consequent_effect = render_effect(
() => {
consequent_dom = consequent_fn(anchor);
if (!result && consequent_dom !== null) {
remove(consequent_dom);
consequent_dom = null;
}
return () => {
// TODO make this unnecessary by linking the dom to the effect,
// and removing automatically on teardown
if (consequent_dom !== undefined) {
remove(consequent_dom);
consequent_dom = undefined;
}
};
},
block,
true
);
}
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;
}
if (alternate_effect) {
pause_effect(alternate_effect, () => {
alternate_effect = null;
if (alternate_dom) remove(alternate_dom);
});
}
} else {
if (alternate_effect) {
resume_effect(alternate_effect);
} else if (alternate_fn) {
alternate_effect = render_effect(
() => {
alternate_dom = alternate_fn(anchor);
block.d = null;
},
block,
true
);
block.ce = consequent_effect;
return () => {
// TODO make this unnecessary by linking the dom to the effect,
// and removing automatically on teardown
if (alternate_dom !== undefined) {
remove(alternate_dom);
alternate_dom = undefined;
}
};
},
block,
true
);
}
alternate_effect ??= render_effect(
(/** @type {any} */ _, /** @type {import('#client').Effect | null} */ alternate_effect) => {
const result = block.v;
if (consequent_effect) {
pause_effect(consequent_effect, () => {
consequent_effect = null;
if (consequent_dom) remove(consequent_dom);
});
}
}
if (result && alternate_dom !== null) {
remove(alternate_dom);
alternate_dom = null;
}
if (mismatch) {
// Set fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
}, block);
if (!result && current_branch_effect !== alternate_effect) {
if (alternate_fn !== null) {
alternate_fn(anchor_node);
}
if (elseif) {
if_effect.f |= IS_ELSEIF;
}
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;
}, block);
if_effect.ondestroy = () => {
if (consequent_dom !== null) {
// TODO make this unnecessary by linking the dom to the effect,
// and removing automatically on teardown
if (consequent_dom !== undefined) {
remove(consequent_dom);
}
if (alternate_dom !== null) {
if (alternate_dom !== undefined) {
remove(alternate_dom);
}
destroy_effect(consequent_effect);
destroy_effect(alternate_effect);
if (consequent_effect) {
destroy_effect(consequent_effect);
}
if (alternate_effect) {
destroy_effect(alternate_effect);
}
};
block.e = if_effect;
}

@@ -1,116 +0,64 @@

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';
import { create_block } from './utils.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 = create_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);
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);
}

@@ -121,21 +69,9 @@ },

);
// 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;
}

@@ -1,5 +0,5 @@

import { SNIPPET_BLOCK } from '../../constants.js';
import { render_effect } from '../../reactivity/effects.js';
import { remove } from '../reconciler.js';
import { current_block, untrack } from '../../runtime.js';
import { untrack } from '../../runtime.js';
import { create_block } from './utils.js';

@@ -13,15 +13,3 @@ /**

export function snippet(get_snippet, node, ...args) {
/** @type {import('#client').SnippetBlock} */
const block = {
// dom
d: null,
// parent
p: /** @type {import('#client').Block} */ (current_block),
// effect
e: null,
// transition
r: null,
// type
t: SNIPPET_BLOCK
};
const block = create_block();

@@ -28,0 +16,0 @@ render_effect(() => {

@@ -1,125 +0,67 @@

import { DYNAMIC_COMPONENT_BLOCK } from '../../constants.js';
import { hydrate_block_anchor } from '../hydration.js';
import { destroy_effect, render_effect } from '../../reactivity/effects.js';
import { pause_effect, render_effect } from '../../reactivity/effects.js';
import { remove } from '../reconciler.js';
import { current_block, execute_effect } from '../../runtime.js';
import { trigger_transitions } from '../elements/transitions.js';
import { create_block } from './utils.js';
// TODO this is very similar to `key`, can we deduplicate?
/**
* @template P
* @param {Comment} anchor_node
* @param {() => (props: P) => void} component_fn
* @param {(component: (props: P) => void) => void} render_fn
* @template {(props: P) => void} C
* @param {Comment} anchor
* @param {() => C} get_component
* @param {(component: C) => void} render_fn
* @returns {void}
*/
export function component(anchor_node, component_fn, render_fn) {
/** @type {import('#client').DynamicComponentBlock} */
const block = {
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('#client').Block} */ (current_block),
// transition
r: null,
// type
t: DYNAMIC_COMPONENT_BLOCK
};
export function component(anchor, get_component, render_fn) {
const block = create_block();
/** @type {null | import('#client').Render} */
let current_render = null;
hydrate_block_anchor(anchor_node);
hydrate_block_anchor(anchor);
/** @type {null | ((props: P) => void)} */
let component = null;
/** @type {C} */
let component;
block.r =
/**
* @param {import('#client').Transition} transition
* @returns {void}
*/
(transition) => {
const render = /** @type {import('#client').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;
}
}
});
};
/** @type {import('#client').Effect} */
let effect;
const create_render_effect = () => {
/** @type {import('#client').Render} */
const render = {
d: null,
e: null,
s: new Set(),
p: current_render
};
/**
* Every time `component` 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();
// Managed effect
render.e = render_effect(
() => {
const current = block.d;
if (current !== null) {
remove(current);
block.d = null;
}
if (component) {
render_fn(component);
}
render.d = block.d;
block.d = null;
},
block,
true
);
const component_effect = render_effect(
() => {
if (component === (component = get_component())) return;
current_render = render;
};
if (effect) {
var e = effect;
pause_effect(e, () => {
effects.delete(e);
});
}
const render = () => {
const render = current_render;
if (component) {
effect = render_effect(
() => {
render_fn(component);
if (render === null) {
create_render_effect();
return;
}
const dom = block.d;
const transitions = render.s;
return () => {
if (dom !== null) {
remove(dom);
}
};
},
block,
true,
true
);
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');
}
};
// @ts-expect-error TODO tidy up
effect.d = block.d;
const component_effect = render_effect(
() => {
const next_component = component_fn();
if (component !== next_component) {
component = next_component;
render();
effects.add(effect);
}

@@ -132,17 +74,7 @@ },

component_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 = component_effect;
}
import { namespace_svg } from '../../../../constants.js';
import { DYNAMIC_ELEMENT_BLOCK } from '../../constants.js';
import { current_hydration_fragment, hydrate_block_anchor, hydrating } from '../hydration.js';
import { empty } from '../operations.js';
import { destroy_effect, render_effect } from '../../reactivity/effects.js';
import { insert, remove } from '../reconciler.js';
import { current_block, execute_effect } from '../../runtime.js';
import {
destroy_effect,
pause_effect,
render_effect,
resume_effect
} from '../../reactivity/effects.js';
import { remove } from '../reconciler.js';
import { is_array } from '../../utils.js';
import { set_should_intro } from '../../render.js';
import { current_each_item_block, set_current_each_item_block } from './each.js';
import { create_block } from './utils.js';
import { current_block } from '../../runtime.js';

@@ -31,4 +38,4 @@ /**

/**
* @param {Comment} anchor_node
* @param {() => string} tag_fn
* @param {Comment} anchor
* @param {() => string} get_tag
* @param {boolean | null} is_svg `null` == not statically known

@@ -38,95 +45,106 @@ * @param {undefined | ((element: Element, anchor: Node) => void)} render_fn

*/
export function element(anchor_node, tag_fn, is_svg, render_fn) {
/** @type {import('#client').DynamicElementBlock} */
const block = {
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('#client').Block} */ (current_block),
// transition
r: null,
// type
t: DYNAMIC_ELEMENT_BLOCK
};
export function element(anchor, get_tag, is_svg, render_fn) {
const parent_block = /** @type {import('#client').Block} */ (current_block);
const block = create_block();
hydrate_block_anchor(anchor_node);
let has_mounted = false;
hydrate_block_anchor(anchor);
/** @type {string} */
/** @type {string | null} */
let tag;
/** @type {string | null} */
let current_tag;
/** @type {null | Element} */
let element = null;
const element_effect = render_effect(
() => {
tag = tag_fn();
if (has_mounted) {
execute_effect(render_effect_signal);
}
has_mounted = true;
},
block,
false
);
/** @type {import('#client').Effect | null} */
let effect;
// Managed effect
const render_effect_signal = render_effect(
() => {
// We try our best infering the namespace in case it's not possible to determine statically,
// but on the first render on the client (without hydration) the parent will be undefined,
// since the anchor is not attached to its parent / the dom yet.
const ns =
is_svg || tag === 'svg'
? namespace_svg
: is_svg === false || anchor_node.parentElement?.tagName === 'foreignObject'
? null
: anchor_node.parentElement?.namespaceURI ?? null;
/**
* The keyed `{#each ...}` item block, if any, that this element is inside.
* We track this so we can set it when changing the element, allowing any
* `animate:` directive to bind itself to the correct block
*/
let each_item_block = current_each_item_block;
const next_element = tag
? hydrating
? /** @type {Element} */ (current_hydration_fragment[0])
: ns
? document.createElementNS(ns, tag)
: document.createElement(tag)
: null;
const wrapper = render_effect(() => {
const next_tag = get_tag() || null;
if (next_tag === tag) return;
const prev_element = element;
if (prev_element !== null) {
block.d = null;
}
// See explanation of `each_item_block` above
var previous_each_item_block = current_each_item_block;
set_current_each_item_block(each_item_block);
element = next_element;
if (element !== null && render_fn !== undefined) {
let anchor;
if (hydrating) {
// Use the existing ssr comment as the anchor so that the inner open and close
// methods can pick up the existing nodes correctly
anchor = /** @type {Comment} */ (element.firstChild);
} else {
anchor = empty();
element.appendChild(anchor);
}
render_fn(element, anchor);
}
// We try our best infering the namespace in case it's not possible to determine statically,
// but on the first render on the client (without hydration) the parent will be undefined,
// since the anchor is not attached to its parent / the dom yet.
const ns =
is_svg || next_tag === 'svg'
? namespace_svg
: is_svg === false || anchor.parentElement?.tagName === 'foreignObject'
? null
: anchor.parentElement?.namespaceURI ?? null;
const has_prev_element = prev_element !== null;
if (has_prev_element) {
remove(prev_element);
if (effect) {
if (next_tag === null) {
// start outro
pause_effect(effect, () => {
effect = null;
current_tag = null;
element?.remove(); // TODO this should be unnecessary
});
} else if (next_tag === current_tag) {
// same tag as is currently rendered — abort outro
resume_effect(effect);
} else {
// tag is changing — destroy immediately, render contents without intro transitions
destroy_effect(effect);
set_should_intro(false);
}
if (element !== null) {
insert(element, null, anchor_node);
if (has_prev_element) {
const parent_block = block.p;
swap_block_dom(parent_block, prev_element, element);
}
}
},
block,
true
);
}
element_effect.ondestroy = () => {
if (next_tag && next_tag !== current_tag) {
effect = render_effect(
() => {
const prev_element = element;
element = hydrating
? /** @type {Element} */ (current_hydration_fragment[0])
: ns
? document.createElementNS(ns, next_tag)
: document.createElement(next_tag);
if (render_fn) {
let anchor;
if (hydrating) {
// Use the existing ssr comment as the anchor so that the inner open and close
// methods can pick up the existing nodes correctly
anchor = /** @type {Comment} */ (element.firstChild);
} else {
anchor = empty();
element.appendChild(anchor);
}
render_fn(element, anchor);
}
anchor.before(element);
if (prev_element) {
swap_block_dom(parent_block, prev_element, element);
prev_element.remove();
}
},
block,
true
);
}
tag = next_tag;
if (tag) current_tag = tag;
set_should_intro(true);
set_current_each_item_block(previous_each_item_block);
}, block);
wrapper.ondestroy = () => {
if (element !== null) {

@@ -137,6 +155,7 @@ remove(element);

}
destroy_effect(render_effect_signal);
if (effect) {
destroy_effect(effect);
}
};
block.e = element_effect;
}

@@ -1,2 +0,1 @@

import { HEAD_BLOCK } from '../../constants.js';
import {

@@ -11,3 +10,3 @@ current_hydration_fragment,

import { remove } from '../reconciler.js';
import { current_block } from '../../runtime.js';
import { create_block } from './utils.js';

@@ -19,15 +18,3 @@ /**

export function head(render_fn) {
/** @type {import('#client').HeadBlock} */
const block = {
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('#client').Block} */ (current_block),
// transition
r: null,
// type
t: HEAD_BLOCK
};
const block = create_block();

@@ -71,4 +58,2 @@ // The head function may be called after the first hydration pass and ssr comment nodes may still be present,

};
block.e = head_effect;
} finally {

@@ -75,0 +60,0 @@ if (is_hydrating) {

import { DEV } from 'esm-env';
import { render_effect } from '../../../reactivity/effects.js';
import { render_effect, user_effect } from '../../../reactivity/effects.js';
import { stringify } from '../../../render.js';
import { listen_to_event_and_reset_event } from './shared.js';

@@ -12,3 +13,3 @@ /**

export function bind_value(input, get_value, update) {
input.addEventListener('input', () => {
listen_to_event_and_reset_event(input, 'input', () => {
if (DEV && input.type === 'checkbox') {

@@ -76,12 +77,18 @@ throw new Error(

input.addEventListener('change', () => {
// @ts-ignore
var value = input.__value;
listen_to_event_and_reset_event(
input,
'change',
() => {
// @ts-ignore
var value = input.__value;
if (is_checkbox) {
value = get_binding_group_value(binding_group, value, input.checked);
}
if (is_checkbox) {
value = get_binding_group_value(binding_group, value, input.checked);
}
update(value);
});
update(value);
},
// TODO better default value handling
() => update(is_checkbox ? [] : null)
);

@@ -101,2 +108,7 @@ render_effect(() => {

user_effect(() => {
// necessary to maintain binding group order in all insertion scenarios. TODO optimise
binding_group.sort((a, b) => (a.compareDocumentPosition(b) === 4 ? -1 : 1));
});
render_effect(() => {

@@ -120,3 +132,3 @@ return () => {

export function bind_checked(input, get_value, update) {
input.addEventListener('change', () => {
listen_to_event_and_reset_event(input, 'change', () => {
var value = input.checked;

@@ -175,1 +187,15 @@ update(value);

}
/**
* @param {HTMLInputElement} input
* @param {() => FileList | null} get_value
* @param {(value: FileList | null) => void} update
*/
export function bind_files(input, get_value, update) {
listen_to_event_and_reset_event(input, 'change', () => {
update(input.files);
});
render_effect(() => {
input.files = get_value();
});
}
import { effect } from '../../../reactivity/effects.js';
import { listen_to_event_and_reset_event } from './shared.js';
import { untrack } from '../../../runtime.js';

@@ -79,3 +80,3 @@

select.addEventListener('change', () => {
listen_to_event_and_reset_event(select, 'change', () => {
/** @type {unknown} */

@@ -82,0 +83,0 @@ var value;

@@ -28,1 +28,49 @@ import { render_effect } from '../../../reactivity/effects.js';

}
let listening_to_form_reset = false;
/**
* Listen to the given event, and then instantiate a global form reset listener if not already done,
* to notify all bindings when the form is reset
* @param {HTMLElement} element
* @param {string} event
* @param {() => void} handler
* @param {() => void} [on_reset]
*/
export function listen_to_event_and_reset_event(element, event, handler, on_reset = handler) {
element.addEventListener(event, handler);
// @ts-expect-error
const prev = element.__on_r;
if (prev) {
// special case for checkbox that can have multiple binds (group & checked)
// @ts-expect-error
element.__on_r = () => {
prev();
on_reset();
};
} else {
// @ts-expect-error
element.__on_r = on_reset;
}
if (!listening_to_form_reset) {
listening_to_form_reset = true;
document.addEventListener(
'reset',
(evt) => {
// Needs to happen one tick later or else the dom properties of the form
// elements have not updated to their reset values yet
Promise.resolve().then(() => {
if (!evt.defaultPrevented) {
for (const e of /**@type {HTMLFormElement} */ (evt.target).elements) {
// @ts-expect-error
e.__on_r?.();
}
}
});
},
// In the capture phase to guarantee we get noticed of it (no possiblity of stopPropagation)
{ capture: true }
);
}
}

@@ -1,85 +0,13 @@

import { EACH_IS_ANIMATED, EACH_IS_CONTROLLED } from '../../../../constants.js';
import { run_all } from '../../../common.js';
import {
AWAIT_BLOCK,
DYNAMIC_COMPONENT_BLOCK,
EACH_BLOCK,
EACH_ITEM_BLOCK,
IF_BLOCK,
KEY_BLOCK,
ROOT_BLOCK
} from '../../constants.js';
import { destroy_each_item_block, get_first_element } from '../blocks/each.js';
import { schedule_raf_task } from '../task.js';
import { append_child, empty } from '../operations.js';
import {
destroy_effect,
effect,
managed_effect,
managed_pre_effect
} from '../../reactivity/effects.js';
import {
current_block,
current_effect,
execute_effect,
mark_subtree_inert,
untrack
} from '../../runtime.js';
import { noop } from '../../../common.js';
import { user_effect } from '../../reactivity/effects.js';
import { current_effect, untrack } from '../../runtime.js';
import { raf } from '../../timing.js';
import { loop } from '../../loop.js';
import { should_intro } from '../../render.js';
import { is_function } from '../../utils.js';
import { current_each_item_block } from '../blocks/each.js';
import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js';
import { EFFECT_RAN } from '../../constants.js';
const active_tick_animations = new Set();
const DELAY_NEXT_TICK = Number.MIN_SAFE_INTEGER;
/** @type {undefined | number} */
let active_tick_ref = undefined;
/**
* @template P
* @param {HTMLElement} dom
* @param {() => import('#client').TransitionFn<P | undefined>} get_transition_fn
* @param {(() => P) | null} props
* @param {any} global
* @returns {void}
*/
export function transition(dom, get_transition_fn, props, global = false) {
bind_transition(dom, get_transition_fn, props, 'both', global);
}
/**
* @template P
* @param {HTMLElement} dom
* @param {() => import('#client').TransitionFn<P | undefined>} get_transition_fn
* @param {(() => P) | null} props
* @returns {void}
*/
export function animate(dom, get_transition_fn, props) {
bind_transition(dom, get_transition_fn, props, 'key', false);
}
/**
* @template P
* @param {HTMLElement} dom
* @param {() => import('#client').TransitionFn<P | undefined>} get_transition_fn
* @param {(() => P) | null} props
* @param {any} global
* @returns {void}
*/
function in_fn(dom, get_transition_fn, props, global = false) {
bind_transition(dom, get_transition_fn, props, 'in', global);
}
export { in_fn as in };
/**
* @template P
* @param {HTMLElement} dom
* @param {() => import('#client').TransitionFn<P | undefined>} get_transition_fn
* @param {(() => P) | null} props
* @param {any} global
* @returns {void}
*/
export function out(dom, get_transition_fn, props, global = false) {
bind_transition(dom, get_transition_fn, props, 'out', global);
}
/**
* @template T

@@ -98,3 +26,3 @@ * @param {string} type

/**
* @param {HTMLElement} dom
* @param {Element} dom
* @param {'introstart' | 'introend' | 'outrostart' | 'outroend'} type

@@ -141,683 +69,279 @@ * @returns {void}

class TickAnimation {
/** @type {null | (() => void)} */
onfinish;
/** @param {number} t */
const linear = (t) => t;
/** @type {(t: number, u: number) => string} */
#tick_fn;
/**
* Called inside keyed `{#each ...}` blocks (as `$.animation(...)`). This creates an animation manager
* and attaches it to the block, so that moves can be animated following reconciliation.
* @template P
* @param {Element} element
* @param {() => import('#client').AnimateFn<P | undefined>} get_fn
* @param {(() => P) | null} get_params
*/
export function animation(element, get_fn, get_params) {
var block = /** @type {import('#client').EachItem} */ (current_each_item_block);
/** @type {number} */
#duration;
/** @type {DOMRect} */
var from;
/** @type {number} */
#current;
/** @type {DOMRect} */
var to;
/** @type {number} */
#delay;
/** @type {import('#client').Animation | undefined} */
var animation;
/** @type {number} */
#previous;
block.a ??= {
element,
measure() {
from = this.element.getBoundingClientRect();
},
apply() {
animation?.abort();
/** @type {boolean} */
paused;
to = this.element.getBoundingClientRect();
/** @type {boolean} */
#reversed;
const options = get_fn()(this.element, { from, to }, get_params?.());
/** @type {number} */
#delay_current;
/** @type {boolean} */
#delayed_reverse;
/**
* @param {(t: number, u: number) => string} tick_fn
* @param {number} duration
* @param {number} delay
* @param {boolean} out
*/
constructor(tick_fn, duration, delay, out) {
this.#duration = duration;
this.#delay = delay;
this.paused = false;
this.#tick_fn = tick_fn;
this.#reversed = out;
this.#delay_current = delay;
this.#current = out ? duration : 0;
this.#previous = 0;
this.#delayed_reverse = false;
this.onfinish = null;
if (this.#delay) {
if (!out) {
this.#tick_fn(0, 1);
if (
from.left !== to.left ||
from.right !== to.right ||
from.top !== to.top ||
from.bottom !== to.bottom
) {
animation = animate(this.element, options, undefined, 1, () => {
animation?.abort();
animation = undefined;
});
}
}
}
};
pause() {
this.paused = true;
}
play() {
this.paused = false;
if (!active_tick_animations.has(this)) {
this.#previous = raf.now();
if (active_tick_ref === undefined) {
active_tick_ref = raf.tick(handle_raf);
}
active_tick_animations.add(this);
}
}
#reverse() {
this.#reversed = !this.#reversed;
if (this.paused) {
if (this.#current === 0) {
this.#current = this.#duration;
}
this.play();
}
}
reverse() {
if (this.#delay === 0) {
this.#reverse();
} else {
this.#delay_current = this.#delay;
this.#delayed_reverse = true;
}
}
cancel() {
active_tick_animations.delete(this);
const current = this.#current / this.#duration;
if (current > 0 && current < 1) {
const t = this.#reversed ? 1 : 0;
this.#tick_fn(t, 1 - t);
}
}
finish() {
active_tick_animations.delete(this);
if (this.onfinish) {
this.onfinish();
}
}
/** @param {number} time */
_update(time) {
let diff = time - this.#previous;
this.#previous = time;
if (this.#delay_current !== 0) {
const is_delayed = this.#delay_current === DELAY_NEXT_TICK;
let cancel = !this.#delayed_reverse;
this.#delay_current -= diff;
if (this.#delay_current < 0 || is_delayed || (this.#delay_current === 0 && this.#reversed)) {
const delay_diff = is_delayed ? 0 : -this.#delay_current;
this.#delay_current = 0;
if (this.#delayed_reverse) {
this.#delayed_reverse = false;
this.#reverse();
} else if (delay_diff !== 0 || this.#reversed) {
diff = delay_diff;
}
cancel = false;
} else if (this.#delay_current === 0) {
this.#delay_current = DELAY_NEXT_TICK;
}
if (cancel) {
return;
}
}
this.#current += this.#reversed ? -diff : diff;
let t = this.#current / this.#duration;
if (t < 0) {
t = 0;
} else if (t > 1) {
t = 1;
}
if ((this.#reversed && t <= 0) || (!this.#reversed && t >= 1)) {
t = this.#reversed ? 0 : 1;
if (this.#delay_current === 0) {
active_tick_animations.delete(this);
if (this.onfinish) {
this.paused = true;
this.onfinish();
}
}
}
this.#tick_fn(t, 1 - t);
}
// in the case of a `<svelte:element>`, it's possible for `$.animation(...)` to be called
// when an animation manager already exists, if the tag changes. in that case, we need to
// swap out the element rather than creating a new manager, in case it happened at the same
// moment as a reconciliation
block.a.element = element;
}
/** @param {number} time */
function handle_raf(time) {
for (const animation of active_tick_animations) {
if (!animation.paused) {
animation._update(time);
}
}
if (active_tick_animations.size !== 0) {
active_tick_ref = raf.tick(handle_raf);
} else {
active_tick_ref = undefined;
}
}
/**
* @param {{(t: number): number;(t: number): number;(arg0: number): any;}} easing_fn
* @param {((t: number, u: number) => string)} css_fn
* @param {number} duration
* @param {string} direction
* @param {boolean} reverse
* Called inside block effects as `$.transition(...)`. This creates a transition manager and
* attaches it to the current effect — later, inside `pause_effect` and `resume_effect`, we
* use this to create `intro` and `outro` transitions.
* @template P
* @param {number} flags
* @param {HTMLElement} element
* @param {() => import('#client').TransitionFn<P | undefined>} get_fn
* @param {(() => P) | null} get_params
* @returns {void}
*/
function create_keyframes(easing_fn, css_fn, duration, direction, reverse) {
/** @type {Keyframe[]} */
const keyframes = [];
// We need at least two frames
const frame_time = 16.666;
const max_duration = Math.max(duration, frame_time);
// Have a keyframe every fame for 60 FPS
for (let i = 0; i <= max_duration; i += frame_time) {
let time;
if (i + frame_time > max_duration) {
time = 1;
} else if (i === 0) {
time = 0;
} else {
time = i / max_duration;
}
let t = easing_fn(time);
if (reverse) {
t = 1 - t;
}
keyframes.push(css_to_keyframe(css_fn(t, 1 - t)));
}
if (direction === 'out' || reverse) {
keyframes.reverse();
}
return keyframes;
}
export function transition(flags, element, get_fn, get_params) {
var is_intro = (flags & TRANSITION_IN) !== 0;
var is_outro = (flags & TRANSITION_OUT) !== 0;
var is_global = (flags & TRANSITION_GLOBAL) !== 0;
/** @param {number} t */
const linear = (t) => t;
/** @type {'in' | 'out' | 'both'} */
var direction = is_intro && is_outro ? 'both' : is_intro ? 'in' : 'out';
/**
* @param {HTMLElement} dom
* @param {() => import('../../types.js').TransitionPayload} init
* @param {'in' | 'out' | 'both' | 'key'} direction
* @param {import('../../types.js').Effect} effect
* @returns {import('../../types.js').Transition}
*/
function create_transition(dom, init, direction, effect) {
let curr_direction = 'in';
/** @type {import('#client').AnimationConfig | ((opts: { direction: 'in' | 'out' }) => import('#client').AnimationConfig) | undefined} */
var current_options;
/** @type {Array<() => void>} */
let subs = [];
var inert = element.inert;
/** @type {null | Animation | TickAnimation} */
let animation = null;
let cancelled = false;
/** @type {import('#client').Animation | undefined} */
var intro;
const create_animation = () => {
let payload = /** @type {import('../../types.js').TransitionPayload} */ (transition.p);
if (typeof payload === 'function') {
// @ts-ignore
payload = payload({ direction: curr_direction });
}
if (payload == null) {
return;
}
const duration = payload.duration ?? 300;
const delay = payload.delay ?? 0;
const css_fn = payload.css;
const tick_fn = payload.tick;
const easing_fn = payload.easing || linear;
/** @type {import('#client').Animation | undefined} */
var outro;
if (typeof tick_fn === 'function') {
animation = new TickAnimation(tick_fn, duration, delay, direction === 'out');
} else {
const keyframes =
typeof css_fn === 'function'
? create_keyframes(easing_fn, css_fn, duration, direction, false)
: [];
animation = dom.animate(keyframes, {
duration,
endDelay: delay,
delay,
fill: 'both'
});
}
animation.pause();
/** @type {(() => void) | undefined} */
var reset;
animation.onfinish = () => {
const is_outro = curr_direction === 'out';
/** @type {Animation | TickAnimation} */ (animation).cancel();
if (is_outro) {
run_all(subs);
subs = [];
}
dispatch_event(dom, is_outro ? 'outroend' : 'introend');
};
};
function get_options() {
// If a transition is still ongoing, we use the existing options rather than generating
// new ones. This ensures that reversible transitions reverse smoothly, rather than
// jumping to a new spot because (for example) a different `duration` was used
return (current_options ??= get_fn()(element, get_params?.(), { direction }));
}
/** @type {import('../../types.js').Transition} */
const transition = {
e: effect,
i: init,
// payload
p: null,
/** @type {import('#client').TransitionManager} */
var transition = {
is_global,
in() {
element.inert = inert;
// finished
/** @param {() => void} fn */
f(fn) {
subs.push(fn);
},
in() {
const needs_reverse = curr_direction !== 'in';
curr_direction = 'in';
if (animation === null || cancelled) {
cancelled = false;
create_animation();
}
if (animation === null) {
transition.x();
if (is_intro) {
dispatch_event(element, 'introstart');
intro = animate(element, get_options(), outro, 1, () => {
dispatch_event(element, 'introend');
intro = current_options = undefined;
});
} else {
dispatch_event(dom, 'introstart');
if (needs_reverse) {
/** @type {Animation | TickAnimation} */ (animation).reverse();
}
/** @type {Animation | TickAnimation} */ (animation).play();
outro?.abort();
reset?.();
}
},
// out
o() {
// @ts-ignore
const has_keyed_transition = dom.__animate;
// If we're outroing an element that has an animation, then we need to fix
// its position to ensure it behaves nicely without causing layout shift.
if (has_keyed_transition) {
const style = getComputedStyle(dom);
const position = style.position;
out(fn) {
if (is_outro) {
element.inert = true;
if (position !== 'absolute' && position !== 'fixed') {
const { width, height } = style;
const a = dom.getBoundingClientRect();
dom.style.position = 'absolute';
dispatch_event(element, 'outrostart');
outro = animate(element, get_options(), intro, 0, () => {
dispatch_event(element, 'outroend');
outro = current_options = undefined;
fn?.();
});
dom.style.width = width;
dom.style.height = height;
const b = dom.getBoundingClientRect();
if (a.left !== b.left || a.top !== b.top) {
const translate = `translate(${a.left - b.left}px, ${a.top - b.top}px)`;
const existing_transform = style.transform;
if (existing_transform === 'none') {
dom.style.transform = translate;
} else {
// Previously, in the Svelte 4, we'd just apply the transform the the DOM element. However,
// because we're now using Web Animations, we can't do that as it won't work properly if the
// animation is also making use of the same transformations. So instead, we apply an
// instantaneous animation and pause it on the first frame, just applying the same behavior.
// We also need to take into consideration matrix transforms and how they might combine with
// an existing behavior that is already in progress (such as scale).
// > Follow the white rabbit.
const transform = existing_transform.startsWith('matrix(1,')
? translate
: `matrix(1,0,0,1,0,0)`;
const frame = {
transform
};
const animation = dom.animate([frame, frame], { duration: 1 });
animation.pause();
}
}
}
}
const needs_reverse = direction === 'both' && curr_direction !== 'out';
curr_direction = 'out';
if (animation === null || cancelled) {
cancelled = false;
create_animation();
}
if (animation === null) {
transition.x();
// TODO arguably the outro should never null itself out until _all_ outros for this effect have completed...
// in that case we wouldn't need to store `reset` separately
reset = outro.reset;
} else {
dispatch_event(dom, 'outrostart');
if (needs_reverse) {
const payload = transition.p;
const current_animation = /** @type {Animation} */ (animation);
// If we are working with CSS animations, then before we call reverse, we also need to ensure
// that we reverse the easing logic. To do this we need to re-create the keyframes so they're
// in reverse with easing properly reversed too.
if (
payload !== null &&
payload.css !== undefined &&
current_animation.playState === 'idle'
) {
const duration = payload.duration ?? 300;
const css_fn = payload.css;
const easing_fn = payload.easing || linear;
const keyframes = create_keyframes(easing_fn, css_fn, duration, direction, true);
const effect = current_animation.effect;
if (effect !== null) {
// @ts-ignore
effect.setKeyframes(keyframes);
}
}
/** @type {Animation | TickAnimation} */ (animation).reverse();
} else {
/** @type {Animation | TickAnimation} */ (animation).play();
}
fn?.();
}
},
// cancel
c() {
if (animation !== null) {
/** @type {Animation | TickAnimation} */ (animation).cancel();
}
cancelled = true;
},
// cleanup
x() {
run_all(subs);
subs = [];
},
r: direction,
d: dom
stop: () => {
intro?.abort();
outro?.abort();
}
};
return transition;
}
/**
* @param {import('../../types.js').Block} block
* @returns {boolean}
*/
function is_transition_block(block) {
const type = block.t;
return (
type === IF_BLOCK ||
type === EACH_ITEM_BLOCK ||
type === KEY_BLOCK ||
type === AWAIT_BLOCK ||
type === DYNAMIC_COMPONENT_BLOCK ||
(type === EACH_BLOCK && block.v.length === 0)
);
var effect = /** @type {import('#client').Effect} */ (current_effect);
(effect.transitions ??= []).push(transition);
// if this is a local transition, we only want to run it if the parent (block) effect's
// parent (branch) effect is where the state change happened. we can determine that by
// looking at whether the branch effect is currently initializing
if (is_intro && should_intro) {
var parent = /** @type {import('#client').Effect} */ (effect.parent);
if (is_global || (parent.f & EFFECT_RAN) !== 0) {
user_effect(() => {
untrack(() => transition.in());
});
}
}
}
/**
* @template P
* @param {HTMLElement} dom
* @param {() => import('../../types.js').TransitionFn<P | undefined> | import('../../types.js').AnimateFn<P | undefined>} get_transition_fn
* @param {(() => P) | null} props_fn
* @param {'in' | 'out' | 'both' | 'key'} direction
* @param {boolean} global
* @returns {void}
* Animates an element, according to the provided configuration
* @param {Element} element
* @param {import('#client').AnimationConfig | ((opts: { direction: 'in' | 'out' }) => import('#client').AnimationConfig)} options
* @param {import('#client').Animation | undefined} counterpart The corresponding intro/outro to this outro/intro
* @param {number} t2 The target `t` value — `1` for intro, `0` for outro
* @param {(() => void) | undefined} callback
* @returns {import('#client').Animation}
*/
function bind_transition(dom, get_transition_fn, props_fn, direction, global) {
const transition_effect = /** @type {import('../../types.js').Effect} */ (current_effect);
const block = current_block;
const is_keyed_transition = direction === 'key';
function animate(element, options, counterpart, t2, callback) {
if (is_function(options)) {
// In the case of a deferred transition (such as `crossfade`), `option` will be
// a function rather than an `AnimationConfig`. We need to call this function
// once DOM has been updated...
/** @type {import('#client').Animation} */
var a;
let can_show_intro_on_mount = true;
let can_apply_lazy_transitions = false;
user_effect(() => {
var o = untrack(() => options({ direction: t2 === 1 ? 'in' : 'out' }));
a = animate(element, o, counterpart, t2, callback);
});
if (is_keyed_transition) {
// @ts-ignore
dom.__animate = true;
// ...but we want to do so without using `async`/`await` everywhere, so
// we return a facade that allows everything to remain synchronous
return {
abort: () => a.abort(),
deactivate: () => a.deactivate(),
reset: () => a.reset(),
t: (now) => a.t(now)
};
}
/** @type {import('../../types.js').Block | null} */
let transition_block = block;
main: while (transition_block !== null) {
if (is_transition_block(transition_block)) {
if (transition_block.t === EACH_ITEM_BLOCK) {
// Lazily apply the each block transition
transition_block.r = each_item_transition;
transition_block.a = each_item_animate;
transition_block = transition_block.p;
} else if (transition_block.t === AWAIT_BLOCK && transition_block.n /* pending */) {
can_show_intro_on_mount = true;
} else if (transition_block.t === IF_BLOCK) {
transition_block.r = if_block_transition;
if (can_show_intro_on_mount) {
/** @type {import('../../types.js').Block | null} */
let if_block = transition_block;
while (if_block.t === IF_BLOCK) {
// If we have an if block parent that is currently falsy then
// we can show the intro on mount as long as that block is mounted
if (if_block.e !== null && !if_block.v) {
can_show_intro_on_mount = true;
break main;
}
if_block = if_block.p;
}
}
}
if (!can_apply_lazy_transitions && can_show_intro_on_mount) {
can_show_intro_on_mount = transition_block.e !== null;
}
if (can_show_intro_on_mount || !global) {
can_apply_lazy_transitions = true;
}
} else if (transition_block.t === ROOT_BLOCK && !can_apply_lazy_transitions) {
can_show_intro_on_mount = transition_block.e !== null || transition_block.i;
}
transition_block = transition_block.p;
counterpart?.deactivate();
if (!options?.duration) {
callback?.();
return {
abort: noop,
deactivate: noop,
reset: noop,
t: () => t2
};
}
/** @type {import('../../types.js').Transition} */
let transition;
var { delay = 0, duration, css, tick, easing = linear } = options;
effect(() => {
let already_mounted = false;
if (transition !== undefined) {
already_mounted = true;
// Destroy any existing transitions first
transition.x();
}
const transition_fn = get_transition_fn();
/** @param {DOMRect} [from] */
const init = (from) =>
untrack(() => {
const props = props_fn === null ? {} : props_fn();
return is_keyed_transition
? /** @type {import('../../types.js').AnimateFn<any>} */ (transition_fn)(
dom,
{ from: /** @type {DOMRect} */ (from), to: dom.getBoundingClientRect() },
props,
{}
)
: /** @type {import('../../types.js').TransitionFn<any>} */ (transition_fn)(dom, props, {
direction
});
});
var start = raf.now() + delay;
var t1 = counterpart?.t(start) ?? 1 - t2;
var delta = t2 - t1;
transition = create_transition(dom, init, direction, transition_effect);
const is_intro = direction === 'in';
const show_intro = can_show_intro_on_mount && (is_intro || direction === 'both');
duration *= Math.abs(delta);
var end = start + duration;
if (show_intro && !already_mounted) {
transition.p = transition.i();
}
/** @type {Animation} */
var animation;
const effect = managed_pre_effect(() => {
destroy_effect(effect);
dom.inert = false;
/** @type {import('#client').Task} */
var task;
if (show_intro && !already_mounted) {
transition.in();
}
if (css) {
// WAAPI
var keyframes = [];
var n = duration / (1000 / 60);
/** @type {import('../../types.js').Block | null} */
let transition_block = block;
while (!is_intro && transition_block !== null) {
const parent = transition_block.p;
if (is_transition_block(transition_block)) {
if (transition_block.r !== null) {
transition_block.r(transition);
}
if (
parent === null ||
(!global && (transition_block.t !== IF_BLOCK || parent.t !== IF_BLOCK || parent.v))
) {
break;
}
}
transition_block = parent;
}
});
});
for (var i = 0; i <= n; i += 1) {
var t = t1 + delta * easing(i / n);
var styles = css(t, 1 - t);
keyframes.push(css_to_keyframe(styles));
}
if (direction === 'key') {
effect(() => {
return () => {
transition.x();
};
animation = element.animate(keyframes, {
delay,
duration,
easing: 'linear',
fill: 'forwards'
});
}
}
/**
* @param {Set<import('../../types.js').Transition>} transitions
* @param {'in' | 'out' | 'key'} target_direction
* @param {DOMRect} [from]
* @returns {void}
*/
export function trigger_transitions(transitions, target_direction, from) {
/** @type {Array<() => void>} */
const outros = [];
for (const transition of transitions) {
const direction = transition.r;
const effect = transition.e;
if (target_direction === 'in') {
if (direction === 'in' || direction === 'both') {
transition.in();
} else {
transition.c();
}
transition.d.inert = false;
mark_subtree_inert(effect, false);
} else if (target_direction === 'key') {
if (direction === 'key') {
if (!transition.p) {
transition.p = transition.i(/** @type {DOMRect} */ (from));
}
transition.in();
}
} else {
if (direction === 'out' || direction === 'both') {
if (!transition.p) {
transition.p = transition.i();
}
outros.push(transition.o);
}
transition.d.inert = true;
mark_subtree_inert(effect, true);
animation.finished
.then(() => {
callback?.();
})
.catch(noop);
} else {
// Timer
if (t1 === 0) {
tick?.(0, 1); // TODO put in nested effect, to avoid interleaved reads/writes?
}
}
if (outros.length > 0) {
// Defer the outros to a microtask
const e = managed_pre_effect(() => {
destroy_effect(e);
const e2 = managed_effect(() => {
destroy_effect(e2);
run_all(outros);
});
});
}
}
/**
* @this {import('../../types.js').IfBlock}
* @param {import('../../types.js').Transition} transition
* @returns {void}
*/
function if_block_transition(transition) {
const block = this;
// block.value === true
if (block.v) {
const consequent_transitions = (block.c ??= new Set());
consequent_transitions.add(transition);
transition.f(() => {
const c = /** @type {Set<import('../../types.js').Transition>} */ (consequent_transitions);
c.delete(transition);
// If the block has changed to falsy and has transitions
if (!block.v && c.size === 0) {
const consequent_effect = block.ce;
execute_effect(/** @type {import('../../types.js').Effect} */ (consequent_effect));
task = loop((now) => {
if (now >= end) {
tick?.(t2, 1 - t2);
callback?.();
return false;
}
});
} else {
const alternate_transitions = (block.a ??= new Set());
alternate_transitions.add(transition);
transition.f(() => {
const a = /** @type {Set<import('../../types.js').Transition>} */ (alternate_transitions);
a.delete(transition);
// If the block has changed to truthy and has transitions
if (block.v && a.size === 0) {
const alternate_effect = block.ae;
execute_effect(/** @type {import('../../types.js').Effect} */ (alternate_effect));
if (now >= start) {
var p = t1 + delta * easing((now - start) / duration);
tick?.(p, 1 - p);
}
return true;
});
}
}
/**
* @this {import('../../types.js').EachItemBlock}
* @param {import('../../types.js').Transition} transition
* @returns {void}
*/
function each_item_transition(transition) {
const block = this;
const each_block = block.p;
const is_controlled = (each_block.f & EACH_IS_CONTROLLED) !== 0;
// Disable optimization
if (is_controlled) {
const anchor = empty();
each_block.f ^= EACH_IS_CONTROLLED;
append_child(/** @type {Element} */ (each_block.a), anchor);
each_block.a = anchor;
}
if (transition.r === 'key' && (each_block.f & EACH_IS_ANIMATED) === 0) {
each_block.f |= EACH_IS_ANIMATED;
}
const transitions = (block.s ??= new Set());
transition.f(() => {
transitions.delete(transition);
if (transition.r !== 'key') {
for (let other of transitions) {
const type = other.r;
if (type === 'key' || type === 'in') {
transitions.delete(other);
}
return {
abort: () => {
animation?.cancel();
task?.abort();
},
deactivate: () => {
callback = undefined;
},
reset: () => {
if (t2 === 0) {
tick?.(1, 0);
}
if (transitions.size === 0) {
block.s = null;
destroy_each_item_block(block, null, true);
}
},
t: (now) => {
var t = t1 + delta * easing((now - start) / duration);
return Math.min(1, Math.max(0, t));
}
});
transitions.add(transition);
};
}
/**
*
* @param {import('../../types.js').EachItemBlock} block
* @param {Set<import('../../types.js').Transition>} transitions
*/
function each_item_animate(block, transitions) {
const from_dom = /** @type {Element} */ (get_first_element(block));
const from = from_dom.getBoundingClientRect();
// Cancel any existing key transitions
for (const transition of transitions) {
const type = transition.r;
if (type === 'key') {
transition.c();
}
}
schedule_raf_task(() => {
trigger_transitions(transitions, 'key', from);
});
}

@@ -76,31 +76,24 @@ // Handle hydration

/**
* @param {Text | Comment | Element} anchor_node
* @param {boolean} [is_controlled]
* @param {Node} node
* @returns {void}
*/
export function hydrate_block_anchor(anchor_node, is_controlled) {
if (hydrating) {
/** @type {Node} */
let target_node = anchor_node;
export function hydrate_block_anchor(node) {
if (!hydrating) return;
if (is_controlled) {
target_node = /** @type {Node} */ (target_node.firstChild);
}
if (target_node.nodeType === 8) {
// @ts-ignore
let fragment = target_node.$$fragment;
if (fragment === undefined) {
fragment = get_hydration_fragment(target_node);
} else {
schedule_task(() => {
// @ts-expect-error clean up memory
target_node.$$fragment = undefined;
});
}
set_current_hydration_fragment(fragment);
if (node.nodeType === 8) {
// @ts-ignore
let fragment = node.$$fragment;
if (fragment === undefined) {
fragment = get_hydration_fragment(node);
} else {
const first_child = /** @type {Element | null} */ (target_node.firstChild);
set_current_hydration_fragment(first_child === null ? [] : [first_child]);
schedule_task(() => {
// @ts-expect-error clean up memory
node.$$fragment = undefined;
});
}
set_current_hydration_fragment(fragment);
} else {
const first_child = /** @type {Element | null} */ (node.firstChild);
set_current_hydration_fragment(first_child === null ? [] : [first_child]);
}
}

@@ -32,27 +32,18 @@ import { append_child } from './operations.js';

/**
* @param {Array<import('../types.js').TemplateNode> | import('../types.js').TemplateNode} current
* @param {null | Element} parent_element
* @param {null | Text | Element | Comment} sibling
* @param {import('#client').Dom} current
* @param {Text | Element | Comment} sibling
* @returns {Text | Element | Comment}
*/
export function insert(current, parent_element, sibling) {
export function insert(current, sibling) {
if (!current) return sibling;
if (is_array(current)) {
var i = 0;
var node;
for (; i < current.length; i++) {
node = current[i];
if (sibling === null) {
append_child(/** @type {Element} */ (parent_element), /** @type {Node} */ (node));
} else {
sibling.before(/** @type {Node} */ (node));
}
for (var i = 0; i < current.length; i++) {
sibling.before(/** @type {Node} */ (current[i]));
}
return current[0];
} else if (current !== null) {
if (sibling === null) {
append_child(/** @type {Element} */ (parent_element), /** @type {Node} */ (current));
} else {
sibling.before(/** @type {Node} */ (current));
}
}
sibling.before(/** @type {Node} */ (current));
return /** @type {Text | Element | Comment} */ (current);

@@ -62,15 +53,8 @@ }

/**
* @param {Array<import('../types.js').TemplateNode> | import('../types.js').TemplateNode} current
* @returns {Element | Comment | Text}
* @param {import('#client').Dom} current
*/
export function remove(current) {
var first_node = current;
if (is_array(current)) {
var i = 0;
var node;
for (; i < current.length; i++) {
node = current[i];
if (i === 0) {
first_node = node;
}
for (var i = 0; i < current.length; i++) {
var node = current[i];
if (node.isConnected) {

@@ -83,3 +67,2 @@ node.remove();

}
return /** @type {Element | Comment | Text} */ (first_node);
}

@@ -86,0 +69,0 @@

@@ -93,3 +93,3 @@ import { current_hydration_fragment, hydrate_block_anchor, hydrating } from './hydration.js';

if (anchor !== null) {
hydrate_block_anchor(anchor, false);
hydrate_block_anchor(anchor);
}

@@ -182,9 +182,7 @@ // In ssr+hydration optimization mode, we might remove the template_element,

* @param {null | Text | Comment | Element} anchor
* @returns {void}
* @returns {import('#client').Dom}
*/
function close_template(dom, is_fragment, anchor) {
const block = /** @type {import('#client').Block} */ (current_block);
/** @type {import('#client').TemplateNode | Array<import('#client').TemplateNode>} */
const current = is_fragment
/** @type {import('#client').Dom} */
var current = is_fragment
? is_array(dom)

@@ -194,6 +192,10 @@ ? dom

: dom;
if (!hydrating && anchor !== null) {
insert(current, null, anchor);
insert(current, anchor);
}
block.d = current;
/** @type {import('#client').Block} */ (current_block).d = current;
return current;
}

@@ -204,6 +206,5 @@

* @param {Element | Text} dom
* @returns {void}
*/
export function close(anchor, dom) {
close_template(dom, false, anchor);
return close_template(dom, false, anchor);
}

@@ -214,6 +215,5 @@

* @param {Element | Text} dom
* @returns {void}
*/
export function close_frag(anchor, dom) {
close_template(dom, true, anchor);
return close_template(dom, true, anchor);
}
import { raf } from './timing.js';
const tasks = new Set();
// TODO move this into timing.js where it probably belongs

@@ -10,9 +10,12 @@ /**

function run_tasks(now) {
tasks.forEach((task) => {
raf.tasks.forEach((task) => {
if (!task.c(now)) {
tasks.delete(task);
raf.tasks.delete(task);
task.f();
}
});
if (tasks.size !== 0) raf.tick(run_tasks);
if (raf.tasks.size !== 0) {
raf.tick(run_tasks);
}
}

@@ -29,11 +32,15 @@

let task;
if (tasks.size === 0) raf.tick(run_tasks);
if (raf.tasks.size === 0) {
raf.tick(run_tasks);
}
return {
promise: new Promise((fulfill) => {
tasks.add((task = { c: callback, f: fulfill }));
raf.tasks.add((task = { c: callback, f: fulfill }));
}),
abort() {
tasks.delete(task);
raf.tasks.delete(task);
}
};
}
import { DEV } from 'esm-env';
import {
check_dirtiness,
current_block,

@@ -8,2 +9,3 @@ current_component_context,

destroy_children,
execute_effect,
get,

@@ -15,8 +17,18 @@ remove_reactions,

} from '../runtime.js';
import { DIRTY, MANAGED, RENDER_EFFECT, EFFECT, PRE_EFFECT, DESTROYED } from '../constants.js';
import {
DIRTY,
MANAGED,
RENDER_EFFECT,
EFFECT,
PRE_EFFECT,
DESTROYED,
INERT,
IS_ELSEIF
} from '../constants.js';
import { set } from './sources.js';
import { noop } from '../../common.js';
/**
* @param {import('./types.js').EffectType} type
* @param {(() => void | (() => void)) | ((b: import('#client').Block) => void | (() => void))} fn
* @param {(() => void | (() => void))} fn
* @param {boolean} sync

@@ -30,2 +42,3 @@ * @param {null | import('#client').Block} block

const signal = {
parent: current_effect,
block,

@@ -40,3 +53,4 @@ deps: null,

ctx: current_component_context,
ondestroy: null
ondestroy: null,
transitions: null
};

@@ -208,4 +222,3 @@

/**
* @template {import('#client').Block} B
* @param {(block: B) => void | (() => void)} fn
* @param {(() => void)} fn
* @param {any} block

@@ -225,20 +238,126 @@ * @param {any} managed

/**
* @param {import('#client').Effect} signal
* @param {import('#client').Effect} effect
* @returns {void}
*/
export function destroy_effect(signal) {
destroy_children(signal);
remove_reactions(signal, 0);
set_signal_status(signal, DESTROYED);
export function destroy_effect(effect) {
destroy_children(effect);
remove_reactions(effect, 0);
set_signal_status(effect, DESTROYED);
signal.teardown?.();
signal.ondestroy?.();
signal.fn =
signal.effects =
signal.teardown =
signal.ondestroy =
signal.ctx =
signal.block =
signal.deps =
if (effect.transitions) {
for (const transition of effect.transitions) {
transition.stop();
}
}
effect.teardown?.();
effect.ondestroy?.();
// @ts-expect-error
effect.fn =
effect.effects =
effect.teardown =
effect.ondestroy =
effect.ctx =
effect.block =
effect.deps =
null;
}
/**
* When a block effect is removed, we don't immediately destroy it or yank it
* out of the DOM, because it might have transitions. Instead, we 'pause' it.
* It stays around (in memory, and in the DOM) until outro transitions have
* completed, and if the state change is reversed then we _resume_ it.
* A paused effect does not update, and the DOM subtree becomes inert.
* @param {import('#client').Effect} effect
* @param {() => void} callback
*/
export function pause_effect(effect, callback = noop) {
/** @type {import('#client').TransitionManager[]} */
const transitions = [];
pause_children(effect, transitions, true);
let remaining = transitions.length;
if (remaining > 0) {
const check = () => {
if (!--remaining) {
destroy_effect(effect);
callback();
}
};
for (const transition of transitions) {
transition.out(check);
}
} else {
destroy_effect(effect);
callback();
}
}
/**
* @param {import('#client').Effect} effect
* @param {import('#client').TransitionManager[]} transitions
* @param {boolean} local
*/
function pause_children(effect, transitions, local) {
if ((effect.f & INERT) !== 0) return;
effect.f ^= INERT;
if (effect.transitions !== null) {
for (const transition of effect.transitions) {
if (transition.is_global || local) {
transitions.push(transition);
}
}
}
if (effect.effects !== null) {
for (const child of effect.effects) {
var transparent = (child.f & IS_ELSEIF) !== 0 || (child.f & MANAGED) !== 0;
pause_children(child, transitions, transparent ? local : false);
}
}
}
/**
* The opposite of `pause_effect`. We call this if (for example)
* `x` becomes falsy then truthy: `{#if x}...{/if}`
* @param {import('#client').Effect} effect
*/
export function resume_effect(effect) {
resume_children(effect, true);
}
/**
* @param {import('#client').Effect} effect
* @param {boolean} local
*/
function resume_children(effect, local) {
if ((effect.f & INERT) === 0) return;
effect.f ^= INERT;
// If a dependency of this effect changed while it was paused,
// apply the change now
if (check_dirtiness(effect)) {
execute_effect(effect);
}
if (effect.effects !== null) {
for (const child of effect.effects) {
var transparent = (child.f & IS_ELSEIF) !== 0 || (child.f & MANAGED) !== 0;
resume_children(child, transparent ? local : false);
}
}
if (effect.transitions !== null) {
for (const transition of effect.transitions) {
if (transition.is_global || local) {
transition.in();
}
}
}
}

@@ -11,3 +11,3 @@ import { DEV } from 'esm-env';

import { derived } from './deriveds.js';
import { get, inspect_fn, is_signals_recorded } from '../runtime.js';
import { get, inspect_fn, is_signals_recorded, untrack } from '../runtime.js';
import { safe_equals, safe_not_equal } from './equality.js';

@@ -40,3 +40,3 @@

* Is passed the full `$$props` object and excludes the named props.
* @type {ProxyHandler<{ props: Record<string | symbol, unknown>, exclude: Array<string | symbol> }>}}
* @type {ProxyHandler<{ props: Record<string | symbol, unknown>, exclude: Array<string | symbol>, name?: string }>}}
*/

@@ -48,2 +48,11 @@ const rest_props_handler = {

},
set(target, key) {
if (DEV) {
throw new Error(
`Rest element properties of $props() such as ${target.name}.${String(key)} are readonly`
);
}
return false;
},
getOwnPropertyDescriptor(target, key) {

@@ -70,7 +79,8 @@ if (target.exclude.includes(key)) return;

* @param {Record<string, unknown>} props
* @param {string[]} rest
* @param {string[]} exclude
* @param {string} [name]
* @returns {Record<string, unknown>}
*/
export function rest_props(props, rest) {
return new Proxy({ props, exclude: rest }, rest_props_handler);
export function rest_props(props, exclude, name) {
return new Proxy(DEV ? { props, exclude, name } : { props, exclude }, rest_props_handler);
}

@@ -140,12 +150,26 @@

* @param {number} flags
* @param {V | (() => V)} [initial]
* @param {V | (() => V)} [fallback]
* @returns {(() => V | ((arg: V) => V) | ((arg: V, mutation: boolean) => V))}
*/
export function prop(props, key, flags, initial) {
export function prop(props, key, flags, fallback) {
var immutable = (flags & PROPS_IS_IMMUTABLE) !== 0;
var runes = (flags & PROPS_IS_RUNES) !== 0;
var lazy = (flags & PROPS_IS_LAZY_INITIAL) !== 0;
var prop_value = /** @type {V} */ (props[key]);
var setter = get_descriptor(props, key)?.set;
if (prop_value === undefined && initial !== undefined) {
var fallback_value = /** @type {V} */ (fallback);
var fallback_dirty = true;
var get_fallback = () => {
if (lazy && fallback_dirty) {
fallback_dirty = false;
fallback_value = untrack(/** @type {() => V} */ (fallback));
}
return fallback_value;
};
if (prop_value === undefined && fallback !== undefined) {
if (setter && runes) {

@@ -161,15 +185,18 @@ // TODO consolidate all these random runtime errors

// @ts-expect-error would need a cumbersome method overload to type this
if ((flags & PROPS_IS_LAZY_INITIAL) !== 0) initial = initial();
prop_value = /** @type {V} */ (initial);
prop_value = get_fallback();
if (setter) setter(prop_value);
}
var getter = () => {
var value = /** @type {V} */ (props[key]);
if (value !== undefined) initial = undefined;
return value === undefined ? /** @type {V} */ (initial) : value;
};
var getter = runes
? () => {
var value = /** @type {V} */ (props[key]);
if (value === undefined) return get_fallback();
fallback_dirty = true;
return value;
}
: () => {
var value = /** @type {V} */ (props[key]);
if (value !== undefined) fallback_value = /** @type {V} */ (undefined);
return value === undefined ? fallback_value : value;
};

@@ -176,0 +203,0 @@ // easy mode — prop is never written to

@@ -1,2 +0,2 @@

import type { Block, ComponentContext, Equals } from '#client';
import type { Block, ComponentContext, Equals, TransitionManager } from '#client';
import type { EFFECT, PRE_EFFECT, RENDER_EFFECT } from '../constants';

@@ -24,3 +24,3 @@

/** The reaction function */
fn: null | Function;
fn: Function;
/** Signals that this signal reads from */

@@ -40,2 +40,3 @@ deps: null | Value[];

export interface Effect extends Reaction {
parent: Effect | null;
/** The block associated with this effect */

@@ -48,3 +49,3 @@ block: null | Block;

/** The effect function */
fn: null | (() => void | (() => void)) | ((b: Block, s: Signal) => void | (() => void));
fn: () => void | (() => void);
/** The teardown function returned from the effect function */

@@ -54,2 +55,4 @@ teardown: null | (() => void);

l: number;
/** Transition managers created with `$.transition` */
transitions: null | TransitionManager[];
}

@@ -56,0 +59,0 @@

@@ -15,3 +15,2 @@ import { DEV } from 'esm-env';

import { array_from } from './utils.js';
import { ROOT_BLOCK } from './constants.js';
import { handle_event_propagation } from './dom/elements/events.js';

@@ -26,2 +25,14 @@

/**
* This is normally true — block effects should run their intro transitions —
* but is false during hydration and mounting (unless `options.intro` is `true`)
* and when creating the children of a `<svelte:element>` that just changed tag
*/
export let should_intro = true;
/** @param {boolean} value */
export function set_should_intro(value) {
should_intro = value;
}
/**
* @param {Element} dom

@@ -56,3 +67,3 @@ * @param {() => string} value

/**
* @param {Comment} anchor_node
* @param {Comment} anchor
* @param {void | ((anchor: Comment, slot_props: Record<string, unknown>) => void)} slot_fn

@@ -62,10 +73,10 @@ * @param {Record<string, unknown>} slot_props

*/
export function slot(anchor_node, slot_fn, slot_props, fallback_fn) {
hydrate_block_anchor(anchor_node);
export function slot(anchor, slot_fn, slot_props, fallback_fn) {
hydrate_block_anchor(anchor);
if (slot_fn === undefined) {
if (fallback_fn !== null) {
fallback_fn(anchor_node);
fallback_fn(anchor);
}
} else {
slot_fn(anchor_node, slot_props);
slot_fn(anchor, slot_props);
}

@@ -205,16 +216,8 @@ }

/** @type {import('#client').RootBlock} */
should_intro = options.intro ?? false;
/** @type {import('#client').Block} */
const block = {
// dom
d: null,
// effect
e: null,
// intro
i: options.intro || false,
// parent
p: null,
// transition
r: null,
// type
t: ROOT_BLOCK
d: null
};

@@ -250,6 +253,8 @@

);
block.e = effect;
const bound_event_listener = handle_event_propagation.bind(null, container);
const bound_document_event_listener = handle_event_propagation.bind(null, document);
should_intro = true;
/** @param {Array<string>} events */

@@ -299,3 +304,3 @@ const event_handle = (events) => {

}
destroy_effect(/** @type {import('./types.js').Effect} */ (block.e));
destroy_effect(effect);
});

@@ -302,0 +307,0 @@

@@ -24,3 +24,4 @@ import { DEV } from 'esm-env';

MANAGED,
STATE_SYMBOL
STATE_SYMBOL,
EFFECT_RAN
} from './constants.js';

@@ -58,5 +59,15 @@ import { flush_tasks } from './dom/task.js';

/** @param {null | import('./types.js').Reaction} reaction */
export function set_current_reaction(reaction) {
current_reaction = reaction;
}
/** @type {null | import('./types.js').Effect} */
export let current_effect = null;
/** @param {null | import('./types.js').Effect} effect */
export function set_current_effect(effect) {
current_effect = effect;
}
/** @type {null | import('./types.js').Value[]} */

@@ -109,2 +120,7 @@ export let current_dependencies = null;

/** @param {import('./types.js').ComponentContext | null} context */
export function set_current_component_context(context) {
current_component_context = context;
}
/** @returns {boolean} */

@@ -153,3 +169,3 @@ export function is_runes() {

*/
function check_dirtiness(reaction) {
export function check_dirtiness(reaction) {
var flags = reaction.f;

@@ -207,3 +223,2 @@

const flags = signal.f;
const is_render_effect = (flags & RENDER_EFFECT) !== 0;

@@ -225,15 +240,3 @@ const previous_dependencies = current_dependencies;

try {
let res;
if (is_render_effect) {
res = /** @type {(block: import('#client').Block, signal: import('#client').Signal) => V} */ (
fn
)(
/** @type {import('#client').Block} */ (
/** @type {import('#client').Effect} */ (signal).block
),
/** @type {import('#client').Signal} */ (signal)
);
} else {
res = /** @type {() => V} */ (fn)();
}
let res = fn();
let dependencies = /** @type {import('./types.js').Value<unknown>[]} **/ (signal.deps);

@@ -557,2 +560,4 @@ if (current_dependencies !== null) {

}
signal.f |= EFFECT_RAN;
}

@@ -1263,10 +1268,24 @@

if (DEV) {
/** @param {string} rune */
/**
* @param {string} rune
*/
function throw_rune_error(rune) {
if (!(rune in globalThis)) {
// @ts-ignore
globalThis[rune] = () => {
// TODO if people start adjusting the "this can contain runes" config through v-p-s more, adjust this message
throw new Error(`${rune} is only available inside .svelte and .svelte.js/ts files`);
};
// TODO if people start adjusting the "this can contain runes" config through v-p-s more, adjust this message
/** @type {any} */
let value; // let's hope noone modifies this global, but belts and braces
Object.defineProperty(globalThis, rune, {
configurable: true,
get: () => {
if (value !== undefined) {
return value;
}
throw new Error(
`The ${rune} rune is only available inside .svelte and .svelte.js/ts files`
);
},
set: (v) => {
value = v;
}
});
}

@@ -1280,2 +1299,3 @@ }

throw_rune_error('$props');
throw_rune_error('$bindable');
}

@@ -1282,0 +1302,0 @@

@@ -12,3 +12,4 @@ import { noop } from '../common.js';

tick: /** @param {any} _ */ (_) => request_animation_frame(_),
now: () => now()
now: () => now(),
tasks: new Set()
};

@@ -1,14 +0,2 @@

import {
ROOT_BLOCK,
EACH_BLOCK,
EACH_ITEM_BLOCK,
IF_BLOCK,
AWAIT_BLOCK,
KEY_BLOCK,
HEAD_BLOCK,
DYNAMIC_COMPONENT_BLOCK,
DYNAMIC_ELEMENT_BLOCK,
SNIPPET_BLOCK,
STATE_SYMBOL
} from './constants.js';
import { STATE_SYMBOL } from './constants.js';
import type { Effect, Source, Value } from './reactivity/types.js';

@@ -62,171 +50,25 @@

export type BlockType =
| typeof ROOT_BLOCK
| typeof EACH_BLOCK
| typeof EACH_ITEM_BLOCK
| typeof IF_BLOCK
| typeof AWAIT_BLOCK
| typeof KEY_BLOCK
| typeof SNIPPET_BLOCK
| typeof HEAD_BLOCK
| typeof DYNAMIC_COMPONENT_BLOCK
| typeof DYNAMIC_ELEMENT_BLOCK;
export type TemplateNode = Text | Element | Comment;
export type Transition = {
/** effect */
e: Effect;
/** payload */
p: null | TransitionPayload;
/** init */
i: (from?: DOMRect) => TransitionPayload;
/** finished */
f: (fn: () => void) => void;
in: () => void;
/** out */
o: () => void;
/** cancel */
c: () => void;
/** cleanup */
x: () => void;
/** direction */
r: 'in' | 'out' | 'both' | 'key';
/** dom */
d: HTMLElement;
};
export type Dom = TemplateNode | TemplateNode[];
export type RootBlock = {
export interface Block {
/** dom */
d: null | TemplateNode | Array<TemplateNode>;
/** effect */
e: null | Effect;
/** intro */
i: boolean;
/** parent */
p: null;
/** transition */
r: null | ((transition: Transition) => void);
/** type */
t: typeof ROOT_BLOCK;
};
d: null | Dom;
}
export type IfBlock = {
/** value */
v: boolean;
/** dom */
d: null | TemplateNode | Array<TemplateNode>;
/** effect */
e: null | Effect;
/** parent */
p: Block;
/** transition */
r: null | ((transition: Transition) => void);
/** consequent transitions */
c: null | Set<Transition>;
/** alternate transitions */
a: null | Set<Transition>;
/** effect */
ce: null | Effect;
/** effect */
ae: null | Effect;
/** type */
t: typeof IF_BLOCK;
};
export type KeyBlock = {
/** dom */
d: null | TemplateNode | Array<TemplateNode>;
/** effect */
e: null | Effect;
/** parent */
p: Block;
/** transition */
r: null | ((transition: Transition) => void);
/** type */
t: typeof KEY_BLOCK;
};
export type HeadBlock = {
/** dom */
d: null | TemplateNode | Array<TemplateNode>;
/** effect */
e: null | Effect;
/** parent */
p: Block;
/** transition */
r: null | ((transition: Transition) => void);
/** type */
t: typeof HEAD_BLOCK;
};
export type DynamicElementBlock = {
/** dom */
d: null | TemplateNode | Array<TemplateNode>;
/** effect */
e: null | Effect;
/** parent */
p: Block;
/** transition */
r: null | ((transition: Transition) => void);
/** type */
t: typeof DYNAMIC_ELEMENT_BLOCK;
};
export type DynamicComponentBlock = {
/** dom */
d: null | TemplateNode | Array<TemplateNode>;
/** effect */
e: null | Effect;
/** parent */
p: Block;
/** transition */
r: null | ((transition: Transition) => void);
/** type */
t: typeof DYNAMIC_COMPONENT_BLOCK;
};
export type AwaitBlock = {
/** dom */
d: null | TemplateNode | Array<TemplateNode>;
/** effect */
e: null | Effect;
/** parent */
p: Block;
/** pending */
n: boolean;
/** transition */
r: null | ((transition: Transition) => void);
/** type */
t: typeof AWAIT_BLOCK;
};
export type EachBlock = {
/** anchor */
a: Element | Comment;
export type EachState = {
/** flags */
f: number;
/** dom */
d: null | TemplateNode | Array<TemplateNode>;
flags: number;
/** items */
v: EachItemBlock[];
/** effewct */
e: null | Effect;
/** parent */
p: Block;
/** transition */
r: null | ((transition: Transition) => void);
/** transitions */
s: Array<EachItemBlock>;
/** type */
t: typeof EACH_BLOCK;
items: EachItem[];
};
export type EachItemBlock = {
/** transition */
a: null | ((block: EachItemBlock, transitions: Set<Transition>) => void);
export type EachItem = {
/** animation manager */
a: AnimationManager | null;
/** dom */
d: null | TemplateNode | Array<TemplateNode>;
d: null | Dom;
/** effect */
e: null | Effect;
e: Effect;
/** item */

@@ -238,37 +80,35 @@ v: any | Source<any>;

k: unknown;
/** parent */
p: EachBlock;
/** transition */
r: null | ((transition: Transition) => void);
/** transitions */
s: null | Set<Transition>;
/** type */
t: typeof EACH_ITEM_BLOCK;
};
export type SnippetBlock = {
/** dom */
d: null | TemplateNode | Array<TemplateNode>;
/** parent */
p: Block;
/** effect */
e: null | Effect;
/** transition */
r: null;
/** type */
t: typeof SNIPPET_BLOCK;
};
export interface TransitionManager {
/** Whether the `global` modifier was used (i.e. `transition:fade|global`) */
is_global: boolean;
/** Called inside `resume_effect` */
in: () => void;
/** Called inside `pause_effect` */
out: (callback?: () => void) => void;
/** Called inside `destroy_effect` */
stop: () => void;
}
export type Block =
| RootBlock
| IfBlock
| AwaitBlock
| DynamicElementBlock
| DynamicComponentBlock
| HeadBlock
| KeyBlock
| EachBlock
| EachItemBlock
| SnippetBlock;
export interface AnimationManager {
/** An element with an `animate:` directive */
element: Element;
/** Called during keyed each block reconciliation, before updates */
measure: () => void;
/** Called during keyed each block reconciliation, after updates — this triggers the animation */
apply: () => void;
}
export interface Animation {
/** Abort the animation */
abort: () => void;
/** Allow the animation to continue running, but remove any callback. This prevents the removal of an outroing block if the corresponding intro has a `delay` */
deactivate: () => void;
/** Resets an animation to its starting state, if it uses `tick`. Exposed as a separate method so that an aborted `out:` can still reset even if the `outro` had already completed */
reset: () => void;
/** Get the `t` value (between `0` and `1`) of the animation, so that its counterpart can start from the right place */
t: (now: number) => number;
}
export type TransitionFn<P> = (

@@ -278,3 +118,3 @@ element: Element,

options: { direction?: 'in' | 'out' | 'both' }
) => TransitionPayload;
) => AnimationConfig | ((options: { direction?: 'in' | 'out' }) => AnimationConfig);

@@ -284,7 +124,6 @@ export type AnimateFn<P> = (

rects: { from: DOMRect; to: DOMRect },
props: P,
options: {}
) => TransitionPayload;
props: P
) => AnimationConfig;
export type TransitionPayload = {
export type AnimationConfig = {
delay?: number;

@@ -309,16 +148,9 @@ duration?: number;

export type Render = {
/** dom */
d: null | TemplateNode | Array<TemplateNode>;
/** effect */
e: null | Effect;
/** transitions */
s: Set<Transition>;
/** prev */
p: Render | null;
};
export type Raf = {
/** Alias for `requestAnimationFrame`, exposed in such a way that we can override in tests */
tick: (callback: (time: DOMHighResTimeStamp) => void) => any;
/** Alias for `performance.now()`, exposed in such a way that we can override in tests */
now: () => number;
/** A set of tasks that will run to completion, unless aborted */
tasks: Set<TaskEntry>;
};

@@ -325,0 +157,0 @@

import { untrack } from './runtime.js';
import { is_array } from './utils.js';
import { get_descriptor, is_array } from './utils.js';

@@ -140,1 +140,20 @@ /** regex of all html void element names */

}
/**
* @param {Record<string, any>} $$props
* @param {string[]} bindable
*/
export function validate_prop_bindings($$props, bindable) {
for (const key in $$props) {
if (!bindable.includes(key)) {
var setter = get_descriptor($$props, key)?.set;
if (setter) {
throw new Error(
`Cannot use bind:${key} on this component because the property was not declared as bindable. ` +
`To mark a property as bindable, use the $bindable() rune like this: \`let { ${key} = $bindable() } = $props()\``
);
}
}
}
}

@@ -591,4 +591,4 @@ import * as $ from '../client/runtime.js';

/**
* If the prop has a fallback and is bound in the parent component,
* propagate the fallback value upwards.
* Legacy mode: If the prop has a fallback and is bound in the
* parent component, propagate the fallback value upwards.
* @param {Record<string, unknown>} props_parent

@@ -595,0 +595,0 @@ * @param {Record<string, unknown>} props_now

@@ -175,3 +175,3 @@ declare module '*.svelte' {

* ```ts
* let { optionalProp = 42, requiredProp }: { optionalProp?: number; requiredProps: string } = $props();
* let { optionalProp = 42, requiredProp, bindableProp = $bindable() }: { optionalProp?: number; requiredProps: string; bindableProp: boolean } = $props();
* ```

@@ -184,2 +184,13 @@ *

/**
* Declares a prop as bindable, meaning the parent component can use `bind:propName={value}` to bind to it.
*
* ```ts
* let { propName = $bindable() }: { propName: boolean } = $props();
* ```
*
* https://svelte-5-preview.vercel.app/docs/runes#$bindable
*/
declare function $bindable<T>(t?: T): T;
/**
* Inspects one or more values whenever they, or the properties they contain, change. Example:

@@ -186,0 +197,0 @@ *

@@ -9,3 +9,3 @@ // generated during release, do not modify

*/
export const VERSION = '5.0.0-next.81';
export const VERSION = '5.0.0-next.82';
export const PUBLIC_VERSION = '5';

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc