diff --git a/.changeset/petite-mammals-talk.md b/.changeset/petite-mammals-talk.md new file mode 100644 index 000000000000..97edc1953a33 --- /dev/null +++ b/.changeset/petite-mammals-talk.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: move DOM-related effect properties to `effect.nodes` diff --git a/packages/svelte/src/internal/client/dev/debug.js b/packages/svelte/src/internal/client/dev/debug.js index e1f8a688bb94..22c4de1179af 100644 --- a/packages/svelte/src/internal/client/dev/debug.js +++ b/packages/svelte/src/internal/client/dev/debug.js @@ -110,13 +110,13 @@ export function log_effect_tree(effect, depth = 0) { console.groupEnd(); } - if (effect.nodes_start && effect.nodes_end) { + if (effect.nodes) { // eslint-disable-next-line no-console - console.log(effect.nodes_start); + console.log(effect.nodes.start); - if (effect.nodes_start !== effect.nodes_end) { + if (effect.nodes.start !== effect.nodes.end) { // eslint-disable-next-line no-console - console.log(effect.nodes_end); + console.log(effect.nodes.end); } } diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 501577053db8..2bfdc369fb93 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -1,4 +1,4 @@ -/** @import { EachItem, EachState, Effect, MaybeSource, Source, TemplateNode, TransitionManager, Value } from '#client' */ +/** @import { EachItem, EachState, Effect, EffectNodes, MaybeSource, Source, TemplateNode, TransitionManager, Value } from '#client' */ /** @import { Batch } from '../../reactivity/batch.js'; */ import { EACH_INDEX_REACTIVE, @@ -43,18 +43,6 @@ import { DEV } from 'esm-env'; import { derived_safe_equal } from '../../reactivity/deriveds.js'; import { current_batch } from '../../reactivity/batch.js'; -/** - * 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 {EachItem | null} - */ -export let current_each_item = null; - -/** @param {EachItem | null} item */ -export function set_current_each_item(item) { - current_each_item = item; -} - /** * @param {any} _ * @param {number} i @@ -397,7 +385,7 @@ function reconcile(state, array, anchor, flags, get_key) { // offscreen == coming in now, no animation in that case, // else this would happen https://github.com/sveltejs/svelte/issues/17181 if (item.o) { - item.a?.measure(); + item.e.nodes?.a?.measure(); (to_animate ??= new Set()).add(item); } } @@ -432,7 +420,7 @@ function reconcile(state, array, anchor, flags, get_key) { if ((item.e.f & INERT) !== 0) { resume_effect(item.e); if (is_animated) { - item.a?.unfix(); + item.e.nodes?.a?.unfix(); (to_animate ??= new Set()).delete(item); } } @@ -529,11 +517,11 @@ function reconcile(state, array, anchor, flags, get_key) { if (is_animated) { for (i = 0; i < destroy_length; i += 1) { - to_destroy[i].a?.measure(); + to_destroy[i].e.nodes?.a?.measure(); } for (i = 0; i < destroy_length; i += 1) { - to_destroy[i].a?.fix(); + to_destroy[i].e.nodes?.a?.fix(); } } @@ -557,7 +545,7 @@ function reconcile(state, array, anchor, flags, get_key) { queue_micro_task(() => { if (to_animate === undefined) return; for (item of to_animate) { - item.a?.apply(); + item.e.nodes?.a?.apply(); } }); } @@ -576,7 +564,6 @@ function reconcile(state, array, anchor, flags, get_key) { * @returns {EachItem} */ function create_item(anchor, prev, value, key, index, render_fn, flags, get_collection) { - var previous_each_item = current_each_item; var reactive = (flags & EACH_ITEM_REACTIVE) !== 0; var mutable = (flags & EACH_ITEM_IMMUTABLE) === 0; @@ -598,7 +585,6 @@ function create_item(anchor, prev, value, key, index, render_fn, flags, get_coll i, v, k: key, - a: null, // @ts-expect-error e: null, o: false, @@ -606,27 +592,21 @@ function create_item(anchor, prev, value, key, index, render_fn, flags, get_coll next: null }; - current_each_item = item; - - try { - if (anchor === null) { - var fragment = document.createDocumentFragment(); - fragment.append((anchor = create_text())); - } - - item.e = branch(() => render_fn(/** @type {Node} */ (anchor), v, i, get_collection)); + if (anchor === null) { + var fragment = document.createDocumentFragment(); + fragment.append((anchor = create_text())); + } - if (prev !== null) { - // we only need to set `prev.next = item`, because - // `item.prev = prev` was set on initialization. - // the effects themselves are already linked - prev.next = item; - } + item.e = branch(() => render_fn(/** @type {Node} */ (anchor), v, i, get_collection)); - return item; - } finally { - current_each_item = previous_each_item; + if (prev !== null) { + // we only need to set `prev.next = item`, because + // `item.prev = prev` was set on initialization. + // the effects themselves are already linked + prev.next = item; } + + return item; } /** @@ -635,10 +615,12 @@ function create_item(anchor, prev, value, key, index, render_fn, flags, get_coll * @param {Text | Element | Comment} anchor */ function move(item, next, anchor) { - var end = item.next ? /** @type {TemplateNode} */ (item.next.e.nodes_start) : anchor; + if (!item.e.nodes) return; + + var end = item.next ? /** @type {EffectNodes} */ (item.next.e.nodes).start : anchor; - var dest = next ? /** @type {TemplateNode} */ (next.e.nodes_start) : anchor; - var node = /** @type {TemplateNode} */ (item.e.nodes_start); + var dest = next ? /** @type {EffectNodes} */ (next.e.nodes).start : anchor; + var node = /** @type {TemplateNode} */ (item.e.nodes.start); while (node !== null && node !== end) { var next_node = /** @type {TemplateNode} */ (get_next_sibling(node)); diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js index d7190abc6668..f7c11f42ca90 100644 --- a/packages/svelte/src/internal/client/dom/blocks/html.js +++ b/packages/svelte/src/internal/client/dom/blocks/html.js @@ -54,9 +54,9 @@ export function html(node, get_value, svg = false, mathml = false, skip_warning return; } - if (effect.nodes_start !== null) { - remove_effect_dom(effect.nodes_start, /** @type {TemplateNode} */ (effect.nodes_end)); - effect.nodes_start = effect.nodes_end = null; + if (effect.nodes !== null) { + remove_effect_dom(effect.nodes.start, /** @type {TemplateNode} */ (effect.nodes.end)); + effect.nodes = null; } if (value === '') return; diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js index 6533ff8921a2..35d2aaa1d9ed 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -1,4 +1,4 @@ -/** @import { Effect, TemplateNode } from '#client' */ +/** @import { Effect, EffectNodes, TemplateNode } from '#client' */ import { FILENAME, NAMESPACE_SVG } from '../../../../constants.js'; import { hydrate_next, @@ -10,7 +10,6 @@ import { import { create_text, get_first_child } from '../operations.js'; import { block, teardown } from '../../reactivity/effects.js'; import { set_should_intro } from '../../render.js'; -import { current_each_item, set_current_each_item } from './each.js'; import { active_effect } from '../../runtime.js'; import { component_context, dev_stack } from '../../context.js'; import { DEV } from 'esm-env'; @@ -18,6 +17,7 @@ import { EFFECT_TRANSPARENT, ELEMENT_NODE } from '#client/constants'; import { assign_nodes } from '../template.js'; import { is_raw_text_element } from '../../../../utils.js'; import { BranchManager } from './branches.js'; +import { set_animation_effect_override } from '../elements/transitions.js'; /** * @param {Comment | Element} node @@ -48,11 +48,10 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio var anchor = /** @type {TemplateNode} */ (hydrating ? hydrate_node : node); /** - * 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 */ - var each_item_block = current_each_item; + var parent_effect = /** @type {Effect} */ (active_effect); var branches = new BranchManager(anchor, false); @@ -67,10 +66,6 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio } branches.ensure(next_tag, (anchor) => { - // See explanation of `each_item_block` above - var previous_each_item = current_each_item; - set_current_each_item(each_item_block); - if (next_tag) { element = hydrating ? /** @type {Element} */ (element) @@ -112,21 +107,23 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio } } + set_animation_effect_override(parent_effect); + // `child_anchor` is undefined if this is a void element, but we still // need to call `render_fn` in order to run actions etc. If the element // contains children, it's a user error (which is warned on elsewhere) // and the DOM will be silently discarded render_fn(element, child_anchor); + + set_animation_effect_override(null); } // we do this after calling `render_fn` so that child effects don't override `nodes.end` - /** @type {Effect} */ (active_effect).nodes_end = element; + /** @type {Effect & { nodes: EffectNodes }} */ (active_effect).nodes.end = element; anchor.before(element); } - set_current_each_item(previous_each_item); - if (hydrating) { set_hydrate_node(anchor); } diff --git a/packages/svelte/src/internal/client/dom/elements/transitions.js b/packages/svelte/src/internal/client/dom/elements/transitions.js index d1d034d402d3..e9f0953df713 100644 --- a/packages/svelte/src/internal/client/dom/elements/transitions.js +++ b/packages/svelte/src/internal/client/dom/elements/transitions.js @@ -1,10 +1,9 @@ -/** @import { AnimateFn, Animation, AnimationConfig, EachItem, Effect, TransitionFn, TransitionManager } from '#client' */ +/** @import { AnimateFn, Animation, AnimationConfig, EachItem, Effect, EffectNodes, TransitionFn, TransitionManager } from '#client' */ import { noop, is_function } from '../../../shared/utils.js'; import { effect } from '../../reactivity/effects.js'; import { active_effect, untrack } from '../../runtime.js'; import { loop } from '../../loop.js'; import { should_intro } from '../../render.js'; -import { current_each_item } from '../blocks/each.js'; import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js'; import { BLOCK_EFFECT, EFFECT_RAN, EFFECT_TRANSPARENT } from '#client/constants'; import { queue_micro_task } from '../task.js'; @@ -66,6 +65,14 @@ function css_to_keyframe(css) { /** @param {number} t */ const linear = (t) => t; +/** @type {Effect | null} */ +let animation_effect_override = null; + +/** @param {Effect | null} v */ +export function set_animation_effect_override(v) { + animation_effect_override = v; +} + /** * 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. @@ -75,7 +82,8 @@ const linear = (t) => t; * @param {(() => P) | null} get_params */ export function animation(element, get_fn, get_params) { - var item = /** @type {EachItem} */ (current_each_item); + var effect = animation_effect_override ?? /** @type {Effect} */ (active_effect); + var nodes = /** @type {EffectNodes} */ (effect.nodes); /** @type {DOMRect} */ var from; @@ -89,7 +97,7 @@ export function animation(element, get_fn, get_params) { /** @type {null | { position: string, width: string, height: string, transform: string }} */ var original_styles = null; - item.a ??= { + nodes.a ??= { element, measure() { from = this.element.getBoundingClientRect(); @@ -161,7 +169,7 @@ export function animation(element, get_fn, get_params) { // 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 - item.a.element = element; + nodes.a.element = element; } /** @@ -265,9 +273,9 @@ export function transition(flags, element, get_fn, get_params) { } }; - var e = /** @type {Effect} */ (active_effect); + var e = /** @type {Effect & { nodes: EffectNodes }} */ (active_effect); - (e.transitions ??= []).push(transition); + (e.nodes.t ??= []).push(transition); // if this is a local transition, we only want to run it if the parent (branch) effect's // parent (block) effect is where the state change happened. we can determine that by diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 2ad0538b43ef..7382da7fb5bc 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -1,4 +1,4 @@ -/** @import { Effect, TemplateNode } from '#client' */ +/** @import { Effect, EffectNodes, TemplateNode } from '#client' */ /** @import { TemplateStructure } from './types' */ import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from './hydration.js'; import { @@ -28,9 +28,8 @@ import { COMMENT_NODE, DOCUMENT_FRAGMENT_NODE, EFFECT_RAN, TEXT_NODE } from '#cl */ export function assign_nodes(start, end) { var effect = /** @type {Effect} */ (active_effect); - if (effect.nodes_start === null) { - effect.nodes_start = start; - effect.nodes_end = end; + if (effect.nodes === null) { + effect.nodes = { start, end, a: null, t: null }; } } @@ -270,7 +269,8 @@ function run_scripts(node) { /** @type {HTMLElement} */ (node).tagName === 'SCRIPT' ? [/** @type {HTMLScriptElement} */ (node)] : node.querySelectorAll('script'); - const effect = /** @type {Effect} */ (active_effect); + + const effect = /** @type {Effect & { nodes: EffectNodes }} */ (active_effect); for (const script of scripts) { const clone = document.createElement('script'); @@ -282,10 +282,10 @@ function run_scripts(node) { // The script has changed - if it's at the edges, the effect now points at dead nodes if (is_fragment ? node.firstChild === script : node === script) { - effect.nodes_start = clone; + effect.nodes.start = clone; } if (is_fragment ? node.lastChild === script : node === script) { - effect.nodes_end = clone; + effect.nodes.end = clone; } script.replaceWith(clone); @@ -344,13 +344,15 @@ export function comment() { */ export function append(anchor, dom) { if (hydrating) { - var effect = /** @type {Effect} */ (active_effect); + var effect = /** @type {Effect & { nodes: EffectNodes }} */ (active_effect); + // When hydrating and outer component and an inner component is async, i.e. blocked on a promise, // then by the time the inner resolves we have already advanced to the end of the hydrated nodes // of the parent component. Check for defined for that reason to avoid rewinding the parent's end marker. - if ((effect.f & EFFECT_RAN) === 0 || effect.nodes_end === null) { - effect.nodes_end = hydrate_node; + if ((effect.f & EFFECT_RAN) === 0 || effect.nodes.end === null) { + effect.nodes.end = hydrate_node; } + hydrate_next(); return; } diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 50c36ed021c1..e48aff3d7f1a 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -26,7 +26,6 @@ import { } from './deriveds.js'; import { aborted } from './effects.js'; import { hydrate_next, hydrating, set_hydrate_node, skip_nodes } from '../dom/hydration.js'; -import { current_each_item, set_current_each_item } from '../dom/blocks/each.js'; /** * @param {Array>} blockers @@ -90,11 +89,7 @@ export function flatten(blockers, sync, async, fn) { * @param {(values: Value[]) => any} fn */ export function run_after_blockers(blockers, fn) { - var each_item = current_each_item; // TODO should this be part of capture? - flatten(blockers, [], [], (v) => { - set_current_each_item(each_item); - fn(v); - }); + flatten(blockers, [], [], fn); } /** diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index fc3e18562e9c..24dee5141960 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -708,7 +708,7 @@ function flush_queued_effects(effects) { // don't know if we need to keep them until they are executed. Doing the check // here (rather than in `update_effect`) allows us to skip the work for // immediate effects. - if (effect.deps === null && effect.first === null && effect.nodes_start === null) { + if (effect.deps === null && effect.first === null && effect.nodes === null) { // if there's no teardown or abort controller we completely unlink // the effect from the graph if (effect.teardown === null && effect.ac === null) { diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 4359378e01c8..4e8565ab8fef 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -101,8 +101,7 @@ function create_effect(type, fn, sync) { var effect = { ctx: component_context, deps: null, - nodes_start: null, - nodes_end: null, + nodes: null, f: type | DIRTY | CONNECTED, first: null, fn, @@ -112,7 +111,6 @@ function create_effect(type, fn, sync) { b: parent && parent.b, prev: null, teardown: null, - transitions: null, wv: 0, ac: null }; @@ -143,7 +141,7 @@ function create_effect(type, fn, sync) { sync && e.deps === null && e.teardown === null && - e.nodes_start === null && + e.nodes === null && e.first === e.last && // either `null`, or a singular child (e.f & EFFECT_PRESERVED) === 0 ) { @@ -497,10 +495,10 @@ export function destroy_effect(effect, remove_dom = true) { if ( (remove_dom || (effect.f & HEAD_EFFECT) !== 0) && - effect.nodes_start !== null && - effect.nodes_end !== null + effect.nodes !== null && + effect.nodes.end !== null ) { - remove_effect_dom(effect.nodes_start, /** @type {TemplateNode} */ (effect.nodes_end)); + remove_effect_dom(effect.nodes.start, /** @type {TemplateNode} */ (effect.nodes.end)); removed = true; } @@ -508,7 +506,7 @@ export function destroy_effect(effect, remove_dom = true) { remove_reactions(effect, 0); set_signal_status(effect, DESTROYED); - var transitions = effect.transitions; + var transitions = effect.nodes && effect.nodes.t; if (transitions !== null) { for (const transition of transitions) { @@ -537,8 +535,7 @@ export function destroy_effect(effect, remove_dom = true) { effect.ctx = effect.deps = effect.fn = - effect.nodes_start = - effect.nodes_end = + effect.nodes = effect.ac = null; } @@ -624,8 +621,10 @@ export 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) { + var t = effect.nodes && effect.nodes.t; + + if (t !== null) { + for (const transition of t) { if (transition.is_global || local) { transitions.push(transition); } @@ -688,8 +687,10 @@ function resume_children(effect, local) { child = sibling; } - if (effect.transitions !== null) { - for (const transition of effect.transitions) { + var t = effect.nodes && effect.nodes.t; + + if (t !== null) { + for (const transition of t) { if (transition.is_global || local) { transition.in(); } @@ -706,8 +707,11 @@ export function aborted(effect = /** @type {Effect} */ (active_effect)) { * @param {DocumentFragment} fragment */ export function move_effect(effect, fragment) { - var node = effect.nodes_start; - var end = effect.nodes_end; + if (!effect.nodes) return; + + /** @type {TemplateNode | null} */ + var node = effect.nodes.start; + var end = effect.nodes.end; while (node !== null) { /** @type {TemplateNode | null} */ diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 81f7197b806d..908576922f28 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -1,4 +1,5 @@ import type { + AnimationManager, ComponentContext, DevStackEntry, Equals, @@ -60,6 +61,15 @@ export interface Derived extends Value, Reaction { parent: Effect | Derived | null; } +export interface EffectNodes { + start: TemplateNode; + end: TemplateNode | null; + /** $.animation */ + a: AnimationManager | null; + /** $.transition */ + t: TransitionManager[] | null; +} + export interface Effect extends Reaction { /** * Branch effects store their start/end nodes so that they can be @@ -67,14 +77,11 @@ export interface Effect extends Reaction { * block is reconciled. In the case of a single text/element node, * `start` and `end` will be the same. */ - nodes_start: null | TemplateNode; - nodes_end: null | TemplateNode; + nodes: null | EffectNodes; /** The effect function */ fn: null | (() => void | (() => void)); /** The teardown function returned from the effect function */ teardown: null | (() => void); - /** Transition managers created with `$.transition` */ - transitions: null | TransitionManager[]; /** Next sibling child effect created inside the parent signal */ prev: null | Effect; /** Next sibling child effect created inside the parent signal */ diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 416627a1572a..92b83dbd5ac3 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -1,4 +1,4 @@ -/** @import { ComponentContext, Effect, TemplateNode } from '#client' */ +/** @import { ComponentContext, Effect, EffectNodes, TemplateNode } from '#client' */ /** @import { Component, ComponentType, SvelteComponent, MountOptions } from '../../index.js' */ import { DEV } from 'esm-env'; import { @@ -228,7 +228,7 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro should_intro = true; if (hydrating) { - /** @type {Effect} */ (active_effect).nodes_end = hydrate_node; + /** @type {Effect & { nodes: EffectNodes }} */ (active_effect).nodes.end = hydrate_node; if ( hydrate_node === null || diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index 5c682ed14067..895bfba571de 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -84,8 +84,6 @@ export type EachState = { }; export type EachItem = { - /** animation manager */ - a: AnimationManager | null; /** effect */ e: Effect; /** item */