Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/petite-mammals-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

chore: move DOM-related effect properties to `effect.nodes`
8 changes: 4 additions & 4 deletions packages/svelte/src/internal/client/dev/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
64 changes: 23 additions & 41 deletions packages/svelte/src/internal/client/dom/blocks/each.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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();
}
}

Expand All @@ -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();
}
});
}
Expand All @@ -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;

Expand All @@ -598,35 +585,28 @@ 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,
prev,
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;
}

/**
Expand All @@ -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));
Expand Down
6 changes: 3 additions & 3 deletions packages/svelte/src/internal/client/dom/blocks/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 8 additions & 11 deletions packages/svelte/src/internal/client/dom/blocks/svelte-element.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -10,14 +10,14 @@ 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';
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
Expand Down Expand Up @@ -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);

Expand All @@ -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)
Expand Down Expand Up @@ -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);
}
Expand Down
22 changes: 15 additions & 7 deletions packages/svelte/src/internal/client/dom/elements/transitions.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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.
Expand All @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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
Expand Down
22 changes: 12 additions & 10 deletions packages/svelte/src/internal/client/dom/template.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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 };
}
}

Expand Down Expand Up @@ -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');
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down
7 changes: 1 addition & 6 deletions packages/svelte/src/internal/client/reactivity/async.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<Promise<void>>} blockers
Expand Down Expand Up @@ -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);
}

/**
Expand Down
Loading
Loading