diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index e594793996..19550d9df9 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -57,15 +57,15 @@ export function set_active_boundary(boundary) { export class Boundary { suspended = false; + /** @type {Boundary | null} */ + parent; + /** @type {TemplateNode} */ #anchor; /** @type {BoundaryProps} */ #props; - /** @type {Boundary | null} */ - #parent; - /** @type {Effect} */ #effect; @@ -102,12 +102,14 @@ export class Boundary { constructor(node, props, children) { this.#anchor = node; this.#props = props; - this.#parent = active_boundary; + this.parent = active_boundary; active_boundary = this; this.#effect = block(() => { var boundary_effect = /** @type {Effect} */ (active_effect); + boundary_effect.b = this; + var hydrate_open = hydrate_node; const reset = () => { @@ -138,39 +140,6 @@ export class Boundary { // @ts-ignore We re-use the effect's fn property to avoid allocation of an additional field boundary_effect.fn = (/** @type {unknown} */ input, /** @type {any} */ payload) => { - if (input === ASYNC_INCREMENT) { - // post-init, show the pending snippet after a timeout - if (!this.suspended && (boundary_effect.f & EFFECT_RAN) !== 0) { - var start = raf.now(); - var end = start + (this.#props.showPendingAfter ?? 500); - - loop((now) => { - if (this.#pending_count === 0) return false; - if (now < end) return true; - - this.#show_pending_snippet(false); - }); - } - - this.suspended = true; - this.#pending_count++; - - return; - } - - if (input === ASYNC_DECREMENT) { - if (--this.#pending_count === 0 && !this.#keep_pending_snippet) { - this.commit(); - - if (this.#main_effect !== null) { - // TODO do we also need to `resume_effect` here? - schedule_effect(this.#main_effect); - } - } - - return; - } - var error = input; var onerror = this.#props.onerror; let failed = this.#props.failed; @@ -269,6 +238,8 @@ export class Boundary { reset_is_throwing_error(); }, flags); + this.ran = true; + // @ts-expect-error this.#effect.fn.boundary = this; @@ -276,7 +247,11 @@ export class Boundary { this.#anchor = hydrate_node; } - active_boundary = this.#parent; + active_boundary = this.parent; + } + + has_pending_snippet() { + return !!this.#props.pending; } /** @@ -336,7 +311,7 @@ export class Boundary { return true; }); } - } else if (this.#parent) { + } else if (this.parent) { throw new Error('TODO show pending snippet on parent'); } else { throw new Error('no pending snippet to show'); @@ -394,10 +369,36 @@ export class Boundary { } } } -} -const ASYNC_INCREMENT = Symbol(); -const ASYNC_DECREMENT = Symbol(); + increment() { + // post-init, show the pending snippet after a timeout + if (!this.suspended && this.ran) { + var start = raf.now(); + var end = start + (this.#props.showPendingAfter ?? 500); + + loop((now) => { + if (this.#pending_count === 0) return false; + if (now < end) return true; + + this.#show_pending_snippet(false); + }); + } + + this.suspended = true; + this.#pending_count++; + } + + decrement() { + if (--this.#pending_count === 0 && !this.#keep_pending_snippet) { + this.commit(); + + if (this.#main_effect !== null) { + // TODO do we also need to `resume_effect` here? + schedule_effect(this.#main_effect); + } + } + } +} var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED | BOUNDARY_EFFECT; @@ -458,19 +459,12 @@ export function capture(track = true) { }; } -/** - * @param {Effect} boundary - */ -export function is_pending_boundary(boundary) { - // @ts-ignore - return boundary.fn.is_pending(); -} - export function suspend() { - var boundary = active_effect; + let boundary = /** @type {Effect} */ (active_effect).b; while (boundary !== null) { - if ((boundary.f & BOUNDARY_EFFECT) !== 0 && is_pending_boundary(boundary)) { + // TODO pretty sure this is wrong + if (boundary.has_pending_snippet()) { break; } @@ -481,12 +475,10 @@ export function suspend() { e.await_outside_boundary(); } - // @ts-ignore - boundary?.fn(ASYNC_INCREMENT); + boundary.increment(); return function unsuspend() { - // @ts-ignore - boundary?.fn?.(ASYNC_DECREMENT); + boundary.decrement(); }; } diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 0691b86180..c54f39a774 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -43,7 +43,7 @@ import { DEV } from 'esm-env'; import { define_property } from '../../shared/utils.js'; import { get_next_sibling } from '../dom/operations.js'; import { async_derived, derived } from './deriveds.js'; -import { capture, suspend } from '../dom/blocks/boundary.js'; +import { active_boundary, capture, suspend } from '../dom/blocks/boundary.js'; import { component_context, dev_current_component_function } from '../context.js'; /** @@ -112,6 +112,7 @@ function create_effect(type, fn, sync, push = true) { last: null, next: null, parent: is_root ? null : parent_effect, + b: parent_effect && parent_effect.b, prev: null, teardown: null, transitions: null, diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 5ef0097649..6c665bbbe1 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 { ComponentContext, Dom, Equals, TemplateNode, TransitionManager } from '#client'; +import type { Boundary } from '../dom/blocks/boundary'; export interface Signal { /** Flags bitmask */ @@ -67,6 +68,8 @@ export interface Effect extends Reaction { last: null | Effect; /** Parent effect */ parent: Effect | null; + /** THe boundary this effect belongs to */ + b: Boundary | null; /** Dev only */ component_function?: any; }