diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index df9082ad0d..6820ac224d 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -33,6 +33,7 @@ import * as e from '../../../shared/errors.js'; const ASYNC_INCREMENT = Symbol(); const ASYNC_DECREMENT = Symbol(); +const ADD_CALLBACK = Symbol(); /** * @param {Effect} boundary @@ -88,6 +89,9 @@ export function boundary(node, props, children) { var hydrate_open = hydrate_node; var is_creating_fallback = false; + /** @type {Function[]} */ + var callbacks = []; + /** * @param {() => void} snippet_fn * @returns {Effect | null} @@ -108,48 +112,6 @@ export function boundary(node, props, children) { }); } - function suspend() { - if (offscreen_fragment || !main_effect) { - return; - } - - var effect = main_effect; - - pause_effect( - effect, - () => { - offscreen_fragment = document.createDocumentFragment(); - move_effect(effect, offscreen_fragment); - }, - false - ); - - const pending = props.pending; - - if (pending) { - pending_effect = render_snippet(() => pending(anchor)); - } - } - - function unsuspend() { - if (!offscreen_fragment) { - return; - } - - if (pending_effect !== null) { - pause_effect(pending_effect, () => { - pending_effect = null; - }); - } - - anchor.before(/** @type {DocumentFragment} */ (offscreen_fragment)); - offscreen_fragment = null; - - if (main_effect !== null) { - resume_effect(main_effect); - } - } - function reset() { if (failed_effect !== null) { pause_effect(failed_effect, () => { @@ -169,7 +131,7 @@ export function boundary(node, props, children) { } // @ts-ignore We re-use the effect's fn property to avoid allocation of an additional field - boundary.fn = (/** @type {unknown} */ input) => { + boundary.fn = (/** @type {unknown} */ input, /** @type {Function} */ payload) => { if (input === ASYNC_INCREMENT) { async_count++; @@ -182,6 +144,12 @@ export function boundary(node, props, children) { if (--async_count === 0) { boundary.f ^= BOUNDARY_SUSPENDED; + for (const callback of callbacks) { + callback(); + } + + callbacks.length = 0; + if (pending_effect) { pause_effect(pending_effect, () => { pending_effect = null; @@ -202,6 +170,11 @@ export function boundary(node, props, children) { return; } + if (input === ADD_CALLBACK) { + callbacks.push(payload); + return; + } + var error = input; var onerror = props.onerror; let failed = props.failed; @@ -377,3 +350,27 @@ function exit() { set_active_reaction(null); set_component_context(null); } + +/** + * @param {Effect | null} effect + */ +export function find_boundary(effect) { + while (effect !== null && (effect.f & BOUNDARY_EFFECT) === 0) { + effect = effect.parent; + } + + return effect; +} + +/** + * @param {Effect | null} boundary + * @param {Function} fn + */ +export function add_boundary_callback(boundary, fn) { + if (boundary === null) { + throw new Error('TODO'); + } + + // @ts-ignore + boundary.fn(ADD_CALLBACK, fn); +} diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index 36790c05c1..86b504fb61 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -10,6 +10,8 @@ import { } from '../hydration.js'; import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js'; import { HYDRATION_START_ELSE, UNINITIALIZED } from '../../../../constants.js'; +import { active_effect, suspended } from '../../runtime.js'; +import { add_boundary_callback, find_boundary } from './boundary.js'; /** * @param {TemplateNode} node @@ -42,6 +44,46 @@ export function if_block(node, fn, elseif = false) { update_branch(flag, fn); }; + /** @type {DocumentFragment | null} */ + var offscreen_fragment = null; + + /** @type {Effect | null} */ + var pending_effect = null; + + var boundary = find_boundary(active_effect); + + function commit() { + if (offscreen_fragment !== null) { + anchor.before(offscreen_fragment); + offscreen_fragment = null; + } + + if (condition) { + consequent_effect = pending_effect; + } else { + alternate_effect = pending_effect; + } + + var current_effect = condition ? consequent_effect : alternate_effect; + var previous_effect = condition ? alternate_effect : consequent_effect; + + if (current_effect !== null) { + resume_effect(current_effect); + } + + if (previous_effect !== null) { + pause_effect(previous_effect, () => { + if (condition) { + alternate_effect = null; + } else { + consequent_effect = null; + } + }); + } + + pending_effect = null; + } + const update_branch = ( /** @type {boolean | null} */ new_condition, /** @type {null | ((anchor: Node) => void)} */ fn @@ -65,30 +107,19 @@ export function if_block(node, fn, elseif = false) { } } - if (condition) { - if (consequent_effect) { - resume_effect(consequent_effect); - } else if (fn) { - consequent_effect = branch(() => fn(anchor)); - } + var target = anchor; - if (alternate_effect) { - pause_effect(alternate_effect, () => { - alternate_effect = null; - }); - } - } else { - if (alternate_effect) { - resume_effect(alternate_effect); - } else if (fn) { - alternate_effect = branch(() => fn(anchor)); - } + if (suspended) { + offscreen_fragment = document.createDocumentFragment(); + offscreen_fragment.append((target = document.createComment(''))); + } - if (consequent_effect) { - pause_effect(consequent_effect, () => { - consequent_effect = null; - }); - } + pending_effect = fn && branch(() => fn(target)); + + if (suspended) { + add_boundary_callback(boundary, commit); + } else { + commit(); } if (mismatch) { diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 0f130e0b51..29e2b74a1f 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -567,20 +567,15 @@ export function unlink_effect(effect) { * A paused effect does not update, and the DOM subtree becomes inert. * @param {Effect} effect * @param {() => void} [callback] - * @param {boolean} [destroy] */ -export function pause_effect(effect, callback, destroy = true) { +export function pause_effect(effect, callback) { /** @type {TransitionManager[]} */ var transitions = []; pause_children(effect, transitions, true); run_out_transitions(transitions, () => { - if (destroy) { - destroy_effect(effect); - } else { - execute_effect_teardown(effect); - } + destroy_effect(effect); if (callback) callback(); }); } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index e19567d733..bcc6f7a8a6 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -27,7 +27,6 @@ import { DISCONNECTED, BOUNDARY_EFFECT, REACTION_IS_UPDATING, - IS_ASYNC, TEMPLATE_EFFECT, BOUNDARY_SUSPENDED } from './constants.js'; @@ -44,7 +43,6 @@ import { lifecycle_outside_component } from '../shared/errors.js'; import { FILENAME } from '../../constants.js'; import { legacy_mode_flag, tracing_mode_flag } from '../flags/index.js'; import { tracing_expressions, get_stack } from './dev/tracing.js'; -import { is_pending_boundary } from './dom/blocks/boundary.js'; const FLUSH_MICROTASK = 0; const FLUSH_SYNC = 1; @@ -89,6 +87,8 @@ export let active_reaction = null; export let untracking = false; +export let suspended = false; + /** @param {null | Reaction} reaction */ export function set_active_reaction(reaction) { active_reaction = reaction; @@ -826,7 +826,7 @@ export function schedule_effect(signal) { function process_effects(effect, collected_effects) { var current_effect = effect.first; var effects = []; - var suspended = false; + suspended = false; main_loop: while (current_effect !== null) { var flags = current_effect.f;