|
|
@ -37,361 +37,400 @@ import { from_async_derived, set_from_async_derived } from '../../reactivity/der
|
|
|
|
import { raf } from '../../timing.js';
|
|
|
|
import { raf } from '../../timing.js';
|
|
|
|
import { loop } from '../../loop.js';
|
|
|
|
import { loop } from '../../loop.js';
|
|
|
|
|
|
|
|
|
|
|
|
const ASYNC_INCREMENT = Symbol();
|
|
|
|
/** @type {Boundary | null} */
|
|
|
|
const ASYNC_DECREMENT = Symbol();
|
|
|
|
export let active_boundary = null;
|
|
|
|
const ADD_CALLBACK = Symbol();
|
|
|
|
|
|
|
|
const ADD_RENDER_EFFECT = Symbol();
|
|
|
|
|
|
|
|
const ADD_EFFECT = Symbol();
|
|
|
|
|
|
|
|
const COMMIT = Symbol();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/** @param {Boundary | null} boundary */
|
|
|
|
* @param {Effect} boundary
|
|
|
|
export function set_active_boundary(boundary) {
|
|
|
|
* @param {() => Effect | null} fn
|
|
|
|
active_boundary = boundary;
|
|
|
|
* @returns {Effect | null}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
function with_boundary(boundary, fn) {
|
|
|
|
|
|
|
|
var previous_effect = active_effect;
|
|
|
|
|
|
|
|
var previous_reaction = active_reaction;
|
|
|
|
|
|
|
|
var previous_ctx = component_context;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
set_active_effect(boundary);
|
|
|
|
|
|
|
|
set_active_reaction(boundary);
|
|
|
|
|
|
|
|
set_component_context(boundary.ctx);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
return fn();
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
set_active_effect(previous_effect);
|
|
|
|
|
|
|
|
set_active_reaction(previous_reaction);
|
|
|
|
|
|
|
|
set_component_context(previous_ctx);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Boundary {
|
|
|
|
|
|
|
|
/** @type {Boundary | null} */
|
|
|
|
|
|
|
|
#parent;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Effect} */
|
|
|
|
|
|
|
|
#effect;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Set<() => void>} */
|
|
|
|
|
|
|
|
#callbacks = new Set();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @param {TemplateNode} node
|
|
|
|
|
|
|
|
* @param {{
|
|
|
|
|
|
|
|
* onerror?: (error: unknown, reset: () => void) => void;
|
|
|
|
|
|
|
|
* failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void;
|
|
|
|
|
|
|
|
* pending?: (anchor: Node) => void;
|
|
|
|
|
|
|
|
* showPendingAfter?: number;
|
|
|
|
|
|
|
|
* showPendingFor?: number;
|
|
|
|
|
|
|
|
* }} props
|
|
|
|
|
|
|
|
* @param {((anchor: Node) => void)} children
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
constructor(node, props, children) {
|
|
|
|
|
|
|
|
var anchor = node;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.#parent = active_boundary;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
active_boundary = this;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var parent_boundary = find_boundary(active_effect);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.#effect = block(() => {
|
|
|
|
|
|
|
|
/** @type {Effect | null} */
|
|
|
|
|
|
|
|
var main_effect = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Effect | null} */
|
|
|
|
|
|
|
|
var pending_effect = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Effect | null} */
|
|
|
|
|
|
|
|
var failed_effect = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {DocumentFragment | null} */
|
|
|
|
|
|
|
|
var offscreen_fragment = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var async_count = 0;
|
|
|
|
|
|
|
|
var boundary_effect = /** @type {Effect} */ (active_effect);
|
|
|
|
|
|
|
|
var hydrate_open = hydrate_node;
|
|
|
|
|
|
|
|
var is_creating_fallback = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Effect[]} */
|
|
|
|
|
|
|
|
var render_effects = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Effect[]} */
|
|
|
|
|
|
|
|
var effects = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var keep_pending_snippet = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @param {() => void} snippet_fn
|
|
|
|
|
|
|
|
* @returns {Effect | null}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
const render_snippet = (snippet_fn) => {
|
|
|
|
|
|
|
|
return this.#run(() => {
|
|
|
|
|
|
|
|
is_creating_fallback = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
return branch(snippet_fn);
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
handle_error(error, boundary_effect, null, boundary_effect.ctx);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
reset_is_throwing_error();
|
|
|
|
|
|
|
|
is_creating_fallback = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED | BOUNDARY_EFFECT;
|
|
|
|
const reset = () => {
|
|
|
|
|
|
|
|
async_count = 0;
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @param {TemplateNode} node
|
|
|
|
|
|
|
|
* @param {{
|
|
|
|
|
|
|
|
* onerror?: (error: unknown, reset: () => void) => void;
|
|
|
|
|
|
|
|
* failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void;
|
|
|
|
|
|
|
|
* pending?: (anchor: Node) => void;
|
|
|
|
|
|
|
|
* showPendingAfter?: number;
|
|
|
|
|
|
|
|
* showPendingFor?: number;
|
|
|
|
|
|
|
|
* }} props
|
|
|
|
|
|
|
|
* @param {((anchor: Node) => void)} children
|
|
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
export function boundary(node, props, children) {
|
|
|
|
|
|
|
|
var anchor = node;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var parent_boundary = find_boundary(active_effect);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
block(() => {
|
|
|
|
|
|
|
|
/** @type {Effect | null} */
|
|
|
|
|
|
|
|
var main_effect = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Effect | null} */
|
|
|
|
|
|
|
|
var pending_effect = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Effect | null} */
|
|
|
|
|
|
|
|
var failed_effect = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {DocumentFragment | null} */
|
|
|
|
|
|
|
|
var offscreen_fragment = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var async_count = 0;
|
|
|
|
|
|
|
|
var boundary = /** @type {Effect} */ (active_effect);
|
|
|
|
|
|
|
|
var hydrate_open = hydrate_node;
|
|
|
|
|
|
|
|
var is_creating_fallback = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Set<() => void>} */
|
|
|
|
if ((boundary_effect.f & BOUNDARY_SUSPENDED) !== 0) {
|
|
|
|
var callbacks = new Set();
|
|
|
|
boundary_effect.f ^= BOUNDARY_SUSPENDED;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Effect[]} */
|
|
|
|
if (failed_effect !== null) {
|
|
|
|
var render_effects = [];
|
|
|
|
pause_effect(failed_effect, () => {
|
|
|
|
|
|
|
|
failed_effect = null;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Effect[]} */
|
|
|
|
main_effect = this.#run(() => {
|
|
|
|
var effects = [];
|
|
|
|
is_creating_fallback = false;
|
|
|
|
|
|
|
|
|
|
|
|
var keep_pending_snippet = false;
|
|
|
|
try {
|
|
|
|
|
|
|
|
return branch(() => children(anchor));
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
reset_is_throwing_error();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
if (async_count > 0) {
|
|
|
|
* @param {() => void} snippet_fn
|
|
|
|
boundary_effect.f |= BOUNDARY_SUSPENDED;
|
|
|
|
* @returns {Effect | null}
|
|
|
|
show_pending_snippet(true);
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
function render_snippet(snippet_fn) {
|
|
|
|
};
|
|
|
|
return with_boundary(boundary, () => {
|
|
|
|
|
|
|
|
is_creating_fallback = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
const unsuspend = () => {
|
|
|
|
return branch(snippet_fn);
|
|
|
|
if (keep_pending_snippet || async_count > 0) {
|
|
|
|
} catch (error) {
|
|
|
|
return;
|
|
|
|
handle_error(error, boundary, null, boundary.ctx);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
reset_is_throwing_error();
|
|
|
|
|
|
|
|
is_creating_fallback = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function reset() {
|
|
|
|
if ((boundary_effect.f & BOUNDARY_SUSPENDED) !== 0) {
|
|
|
|
async_count = 0;
|
|
|
|
boundary_effect.f ^= BOUNDARY_SUSPENDED;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ((boundary.f & BOUNDARY_SUSPENDED) !== 0) {
|
|
|
|
for (const e of render_effects) {
|
|
|
|
boundary.f ^= BOUNDARY_SUSPENDED;
|
|
|
|
try {
|
|
|
|
}
|
|
|
|
if (check_dirtiness(e)) {
|
|
|
|
|
|
|
|
update_effect(e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
handle_error(error, e, null, e.ctx);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (failed_effect !== null) {
|
|
|
|
for (const fn of this.#callbacks) fn();
|
|
|
|
pause_effect(failed_effect, () => {
|
|
|
|
this.#callbacks.clear();
|
|
|
|
failed_effect = null;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
main_effect = with_boundary(boundary, () => {
|
|
|
|
if (pending_effect) {
|
|
|
|
is_creating_fallback = false;
|
|
|
|
pause_effect(pending_effect, () => {
|
|
|
|
|
|
|
|
pending_effect = null;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (offscreen_fragment) {
|
|
|
|
return branch(() => children(anchor));
|
|
|
|
anchor.before(offscreen_fragment);
|
|
|
|
} finally {
|
|
|
|
offscreen_fragment = null;
|
|
|
|
reset_is_throwing_error();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (async_count > 0) {
|
|
|
|
for (const e of effects) {
|
|
|
|
boundary.f |= BOUNDARY_SUSPENDED;
|
|
|
|
try {
|
|
|
|
show_pending_snippet(true);
|
|
|
|
if (check_dirtiness(e)) {
|
|
|
|
}
|
|
|
|
update_effect(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
handle_error(error, e, null, e.ctx);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
function unsuspend() {
|
|
|
|
/**
|
|
|
|
if (keep_pending_snippet || async_count > 0) {
|
|
|
|
* @param {boolean} initial
|
|
|
|
return;
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
function show_pending_snippet(initial) {
|
|
|
|
|
|
|
|
const pending = props.pending;
|
|
|
|
|
|
|
|
|
|
|
|
if ((boundary.f & BOUNDARY_SUSPENDED) !== 0) {
|
|
|
|
if (pending !== undefined) {
|
|
|
|
boundary.f ^= BOUNDARY_SUSPENDED;
|
|
|
|
// TODO can this be false?
|
|
|
|
}
|
|
|
|
if (main_effect !== null) {
|
|
|
|
|
|
|
|
offscreen_fragment = document.createDocumentFragment();
|
|
|
|
|
|
|
|
move_effect(main_effect, offscreen_fragment);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (const e of render_effects) {
|
|
|
|
if (pending_effect === null) {
|
|
|
|
try {
|
|
|
|
pending_effect = branch(() => pending(anchor));
|
|
|
|
if (check_dirtiness(e)) {
|
|
|
|
|
|
|
|
update_effect(e);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
handle_error(error, e, null, e.ctx);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const fn of callbacks) fn();
|
|
|
|
// TODO do we want to differentiate between initial render and updates here?
|
|
|
|
callbacks.clear();
|
|
|
|
if (!initial) {
|
|
|
|
|
|
|
|
keep_pending_snippet = true;
|
|
|
|
|
|
|
|
|
|
|
|
if (pending_effect) {
|
|
|
|
var end = raf.now() + (props.showPendingFor ?? 300);
|
|
|
|
pause_effect(pending_effect, () => {
|
|
|
|
|
|
|
|
pending_effect = null;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (offscreen_fragment) {
|
|
|
|
loop((now) => {
|
|
|
|
anchor.before(offscreen_fragment);
|
|
|
|
if (now >= end) {
|
|
|
|
offscreen_fragment = null;
|
|
|
|
keep_pending_snippet = false;
|
|
|
|
}
|
|
|
|
unsuspend();
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (const e of effects) {
|
|
|
|
return true;
|
|
|
|
try {
|
|
|
|
});
|
|
|
|
if (check_dirtiness(e)) {
|
|
|
|
|
|
|
|
update_effect(e);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
} else if (parent_boundary) {
|
|
|
|
handle_error(error, e, null, e.ctx);
|
|
|
|
throw new Error('TODO show pending snippet on parent');
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
throw new Error('no pending snippet to show');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
// @ts-ignore We re-use the effect's fn property to avoid allocation of an additional field
|
|
|
|
* @param {boolean} initial
|
|
|
|
boundary_effect.fn = (/** @type {unknown} */ input, /** @type {any} */ payload) => {
|
|
|
|
*/
|
|
|
|
if (input === ASYNC_INCREMENT) {
|
|
|
|
function show_pending_snippet(initial) {
|
|
|
|
// post-init, show the pending snippet after a timeout
|
|
|
|
const pending = props.pending;
|
|
|
|
if (
|
|
|
|
|
|
|
|
(boundary_effect.f & BOUNDARY_SUSPENDED) === 0 &&
|
|
|
|
|
|
|
|
(boundary_effect.f & EFFECT_RAN) !== 0
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
var start = raf.now();
|
|
|
|
|
|
|
|
var end = start + (props.showPendingAfter ?? 500);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loop((now) => {
|
|
|
|
|
|
|
|
if (async_count === 0) return false;
|
|
|
|
|
|
|
|
if (now < end) return true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
show_pending_snippet(false);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (pending !== undefined) {
|
|
|
|
boundary_effect.f |= BOUNDARY_SUSPENDED;
|
|
|
|
// TODO can this be false?
|
|
|
|
async_count++;
|
|
|
|
if (main_effect !== null) {
|
|
|
|
|
|
|
|
offscreen_fragment = document.createDocumentFragment();
|
|
|
|
|
|
|
|
move_effect(main_effect, offscreen_fragment);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (pending_effect === null) {
|
|
|
|
return;
|
|
|
|
pending_effect = branch(() => pending(anchor));
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO do we want to differentiate between initial render and updates here?
|
|
|
|
if (input === ASYNC_DECREMENT) {
|
|
|
|
if (!initial) {
|
|
|
|
if (--async_count === 0 && !keep_pending_snippet) {
|
|
|
|
keep_pending_snippet = true;
|
|
|
|
unsuspend();
|
|
|
|
|
|
|
|
|
|
|
|
var end = raf.now() + (props.showPendingFor ?? 300);
|
|
|
|
if (main_effect !== null) {
|
|
|
|
|
|
|
|
// TODO do we also need to `resume_effect` here?
|
|
|
|
loop((now) => {
|
|
|
|
schedule_effect(main_effect);
|
|
|
|
if (now >= end) {
|
|
|
|
|
|
|
|
keep_pending_snippet = false;
|
|
|
|
|
|
|
|
unsuspend();
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
return;
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (parent_boundary) {
|
|
|
|
|
|
|
|
throw new Error('TODO show pending snippet on parent');
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
throw new Error('no pending snippet to show');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// @ts-ignore We re-use the effect's fn property to avoid allocation of an additional field
|
|
|
|
if (input === ADD_RENDER_EFFECT) {
|
|
|
|
boundary.fn = (/** @type {unknown} */ input, /** @type {any} */ payload) => {
|
|
|
|
render_effects.push(payload);
|
|
|
|
if (input === ASYNC_INCREMENT) {
|
|
|
|
return;
|
|
|
|
// post-init, show the pending snippet after a timeout
|
|
|
|
}
|
|
|
|
if ((boundary.f & BOUNDARY_SUSPENDED) === 0 && (boundary.f & EFFECT_RAN) !== 0) {
|
|
|
|
|
|
|
|
var start = raf.now();
|
|
|
|
|
|
|
|
var end = start + (props.showPendingAfter ?? 500);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loop((now) => {
|
|
|
|
if (input === ADD_EFFECT) {
|
|
|
|
if (async_count === 0) return false;
|
|
|
|
effects.push(payload);
|
|
|
|
if (now < end) return true;
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
show_pending_snippet(false);
|
|
|
|
if (input === COMMIT) {
|
|
|
|
});
|
|
|
|
unsuspend();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
boundary.f |= BOUNDARY_SUSPENDED;
|
|
|
|
var error = input;
|
|
|
|
async_count++;
|
|
|
|
var onerror = props.onerror;
|
|
|
|
|
|
|
|
let failed = props.failed;
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
// If we have nothing to capture the error, or if we hit an error while
|
|
|
|
}
|
|
|
|
// rendering the fallback, re-throw for another boundary to handle
|
|
|
|
|
|
|
|
if (is_creating_fallback || (!onerror && !failed)) {
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (input === ASYNC_DECREMENT) {
|
|
|
|
onerror?.(error, reset);
|
|
|
|
if (--async_count === 0 && !keep_pending_snippet) {
|
|
|
|
|
|
|
|
unsuspend();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (main_effect !== null) {
|
|
|
|
if (main_effect) {
|
|
|
|
// TODO do we also need to `resume_effect` here?
|
|
|
|
destroy_effect(main_effect);
|
|
|
|
schedule_effect(main_effect);
|
|
|
|
main_effect = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
if (pending_effect) {
|
|
|
|
}
|
|
|
|
destroy_effect(pending_effect);
|
|
|
|
|
|
|
|
pending_effect = null;
|
|
|
|
if (input === ADD_CALLBACK) {
|
|
|
|
}
|
|
|
|
callbacks.add(payload);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (input === ADD_RENDER_EFFECT) {
|
|
|
|
if (failed_effect) {
|
|
|
|
render_effects.push(payload);
|
|
|
|
destroy_effect(failed_effect);
|
|
|
|
return;
|
|
|
|
failed_effect = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (input === ADD_EFFECT) {
|
|
|
|
if (hydrating) {
|
|
|
|
effects.push(payload);
|
|
|
|
set_hydrate_node(hydrate_open);
|
|
|
|
return;
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
set_hydrate_node(remove_nodes());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (input === COMMIT) {
|
|
|
|
if (failed) {
|
|
|
|
unsuspend();
|
|
|
|
queue_boundary_micro_task(() => {
|
|
|
|
return;
|
|
|
|
failed_effect = render_snippet(() => {
|
|
|
|
}
|
|
|
|
failed(
|
|
|
|
|
|
|
|
anchor,
|
|
|
|
|
|
|
|
() => error,
|
|
|
|
|
|
|
|
() => reset
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var error = input;
|
|
|
|
// @ts-ignore
|
|
|
|
var onerror = props.onerror;
|
|
|
|
boundary_effect.fn.is_pending = () => props.pending;
|
|
|
|
let failed = props.failed;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If we have nothing to capture the error, or if we hit an error while
|
|
|
|
if (hydrating) {
|
|
|
|
// rendering the fallback, re-throw for another boundary to handle
|
|
|
|
hydrate_next();
|
|
|
|
if (is_creating_fallback || (!onerror && !failed)) {
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onerror?.(error, reset);
|
|
|
|
const pending = props.pending;
|
|
|
|
|
|
|
|
|
|
|
|
if (main_effect) {
|
|
|
|
if (hydrating && pending) {
|
|
|
|
destroy_effect(main_effect);
|
|
|
|
pending_effect = branch(() => pending(anchor));
|
|
|
|
main_effect = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (pending_effect) {
|
|
|
|
// ...now what? we need to start rendering `boundary_fn` offscreen,
|
|
|
|
destroy_effect(pending_effect);
|
|
|
|
// and either insert the resulting fragment (if nothing suspends)
|
|
|
|
pending_effect = null;
|
|
|
|
// or keep the pending effect alive until it unsuspends.
|
|
|
|
}
|
|
|
|
// not exactly sure how to do that.
|
|
|
|
|
|
|
|
|
|
|
|
if (failed_effect) {
|
|
|
|
// future work: when we have some form of async SSR, we will
|
|
|
|
destroy_effect(failed_effect);
|
|
|
|
// need to use hydration boundary comments to report whether
|
|
|
|
failed_effect = null;
|
|
|
|
// the pending or main block was rendered for a given
|
|
|
|
}
|
|
|
|
// boundary, and hydrate accordingly
|
|
|
|
|
|
|
|
queueMicrotask(() => {
|
|
|
|
|
|
|
|
destroy_effect(/** @type {Effect} */ (pending_effect));
|
|
|
|
|
|
|
|
|
|
|
|
if (hydrating) {
|
|
|
|
main_effect = this.#run(() => {
|
|
|
|
set_hydrate_node(hydrate_open);
|
|
|
|
return branch(() => children(anchor));
|
|
|
|
next();
|
|
|
|
|
|
|
|
set_hydrate_node(remove_nodes());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (failed) {
|
|
|
|
|
|
|
|
queue_boundary_micro_task(() => {
|
|
|
|
|
|
|
|
failed_effect = render_snippet(() => {
|
|
|
|
|
|
|
|
failed(
|
|
|
|
|
|
|
|
anchor,
|
|
|
|
|
|
|
|
() => error,
|
|
|
|
|
|
|
|
() => reset
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
main_effect = branch(() => children(anchor));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (async_count > 0) {
|
|
|
|
|
|
|
|
boundary_effect.f |= BOUNDARY_SUSPENDED;
|
|
|
|
|
|
|
|
show_pending_snippet(true);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
reset_is_throwing_error();
|
|
|
|
boundary.fn.is_pending = () => props.pending;
|
|
|
|
}, flags);
|
|
|
|
|
|
|
|
|
|
|
|
if (hydrating) {
|
|
|
|
if (hydrating) {
|
|
|
|
hydrate_next();
|
|
|
|
anchor = hydrate_node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const pending = props.pending;
|
|
|
|
active_boundary = this.#parent;
|
|
|
|
|
|
|
|
}
|
|
|
|
if (hydrating && pending) {
|
|
|
|
|
|
|
|
pending_effect = branch(() => pending(anchor));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ...now what? we need to start rendering `boundary_fn` offscreen,
|
|
|
|
|
|
|
|
// and either insert the resulting fragment (if nothing suspends)
|
|
|
|
|
|
|
|
// or keep the pending effect alive until it unsuspends.
|
|
|
|
|
|
|
|
// not exactly sure how to do that.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// future work: when we have some form of async SSR, we will
|
|
|
|
/**
|
|
|
|
// need to use hydration boundary comments to report whether
|
|
|
|
* @param {() => Effect | null} fn
|
|
|
|
// the pending or main block was rendered for a given
|
|
|
|
*/
|
|
|
|
// boundary, and hydrate accordingly
|
|
|
|
#run(fn) {
|
|
|
|
queueMicrotask(() => {
|
|
|
|
var previous_boundary = active_boundary;
|
|
|
|
destroy_effect(/** @type {Effect} */ (pending_effect));
|
|
|
|
var previous_effect = active_effect;
|
|
|
|
|
|
|
|
var previous_reaction = active_reaction;
|
|
|
|
|
|
|
|
var previous_ctx = component_context;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
active_boundary = this;
|
|
|
|
|
|
|
|
set_active_effect(this.#effect);
|
|
|
|
|
|
|
|
set_active_reaction(this.#effect);
|
|
|
|
|
|
|
|
set_component_context(this.#effect.ctx);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
return fn();
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
active_boundary = previous_boundary;
|
|
|
|
|
|
|
|
set_active_effect(previous_effect);
|
|
|
|
|
|
|
|
set_active_reaction(previous_reaction);
|
|
|
|
|
|
|
|
set_component_context(previous_ctx);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
main_effect = with_boundary(boundary, () => {
|
|
|
|
/** @param {() => void} fn */
|
|
|
|
return branch(() => children(anchor));
|
|
|
|
add_callback(fn) {
|
|
|
|
});
|
|
|
|
this.#callbacks.add(fn);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
}
|
|
|
|
main_effect = branch(() => children(anchor));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (async_count > 0) {
|
|
|
|
const ASYNC_INCREMENT = Symbol();
|
|
|
|
boundary.f |= BOUNDARY_SUSPENDED;
|
|
|
|
const ASYNC_DECREMENT = Symbol();
|
|
|
|
show_pending_snippet(true);
|
|
|
|
const ADD_RENDER_EFFECT = Symbol();
|
|
|
|
}
|
|
|
|
const ADD_EFFECT = Symbol();
|
|
|
|
}
|
|
|
|
const COMMIT = Symbol();
|
|
|
|
|
|
|
|
|
|
|
|
reset_is_throwing_error();
|
|
|
|
var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED | BOUNDARY_EFFECT;
|
|
|
|
}, flags);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (hydrating) {
|
|
|
|
/**
|
|
|
|
anchor = hydrate_node;
|
|
|
|
* @param {TemplateNode} node
|
|
|
|
}
|
|
|
|
* @param {{
|
|
|
|
|
|
|
|
* onerror?: (error: unknown, reset: () => void) => void;
|
|
|
|
|
|
|
|
* failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void;
|
|
|
|
|
|
|
|
* pending?: (anchor: Node) => void;
|
|
|
|
|
|
|
|
* showPendingAfter?: number;
|
|
|
|
|
|
|
|
* showPendingFor?: number;
|
|
|
|
|
|
|
|
* }} props
|
|
|
|
|
|
|
|
* @param {((anchor: Node) => void)} children
|
|
|
|
|
|
|
|
* @returns {void}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
export function boundary(node, props, children) {
|
|
|
|
|
|
|
|
new Boundary(node, props, children);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
@ -500,19 +539,6 @@ export function find_boundary(effect) {
|
|
|
|
return effect;
|
|
|
|
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);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @param {Effect} boundary
|
|
|
|
* @param {Effect} boundary
|
|
|
|
* @param {Effect} effect
|
|
|
|
* @param {Effect} effect
|
|
|
|