|
|
@ -1,6 +1,5 @@
|
|
|
|
/** @import { Effect, TemplateNode, } from '#client' */
|
|
|
|
/** @import { Effect, TemplateNode, } from '#client' */
|
|
|
|
|
|
|
|
import { BOUNDARY_EFFECT, EFFECT_PRESERVED, EFFECT_TRANSPARENT } from '#client/constants';
|
|
|
|
import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '#client/constants';
|
|
|
|
|
|
|
|
import { component_context, set_component_context } from '../../context.js';
|
|
|
|
import { component_context, set_component_context } from '../../context.js';
|
|
|
|
import { invoke_error_boundary } from '../../error-handling.js';
|
|
|
|
import { invoke_error_boundary } from '../../error-handling.js';
|
|
|
|
import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js';
|
|
|
|
import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js';
|
|
|
@ -21,116 +20,170 @@ import {
|
|
|
|
import { queue_micro_task } from '../task.js';
|
|
|
|
import { queue_micro_task } from '../task.js';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @param {Effect} boundary
|
|
|
|
* @typedef {{
|
|
|
|
* @param {() => void} fn
|
|
|
|
* onerror?: (error: unknown, reset: () => void) => void;
|
|
|
|
|
|
|
|
* failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void;
|
|
|
|
|
|
|
|
* }} BoundaryProps
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function with_boundary(boundary, fn) {
|
|
|
|
|
|
|
|
var previous_effect = active_effect;
|
|
|
|
var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED | BOUNDARY_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 {
|
|
|
|
|
|
|
|
fn();
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
set_active_effect(previous_effect);
|
|
|
|
|
|
|
|
set_active_reaction(previous_reaction);
|
|
|
|
|
|
|
|
set_component_context(previous_ctx);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @param {TemplateNode} node
|
|
|
|
* @param {TemplateNode} node
|
|
|
|
* @param {{
|
|
|
|
* @param {BoundaryProps} props
|
|
|
|
* onerror?: (error: unknown, reset: () => void) => void,
|
|
|
|
* @param {((anchor: Node) => void)} children
|
|
|
|
* failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void
|
|
|
|
|
|
|
|
* }} props
|
|
|
|
|
|
|
|
* @param {((anchor: Node) => void)} boundary_fn
|
|
|
|
|
|
|
|
* @returns {void}
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function boundary(node, props, boundary_fn) {
|
|
|
|
export function boundary(node, props, children) {
|
|
|
|
var anchor = node;
|
|
|
|
new Boundary(node, props, children);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class Boundary {
|
|
|
|
|
|
|
|
/** @type {TemplateNode} */
|
|
|
|
|
|
|
|
#anchor;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {TemplateNode} */
|
|
|
|
|
|
|
|
#hydrate_open;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {BoundaryProps} */
|
|
|
|
|
|
|
|
#props;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {((anchor: Node) => void)} */
|
|
|
|
|
|
|
|
#children;
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Effect} */
|
|
|
|
/** @type {Effect} */
|
|
|
|
var boundary_effect;
|
|
|
|
#effect;
|
|
|
|
|
|
|
|
|
|
|
|
block(() => {
|
|
|
|
|
|
|
|
var boundary = /** @type {Effect} */ (active_effect);
|
|
|
|
|
|
|
|
var hydrate_open = hydrate_node;
|
|
|
|
|
|
|
|
var is_creating_fallback = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// We re-use the effect's fn property to avoid allocation of an additional field
|
|
|
|
|
|
|
|
boundary.fn = (/** @type {unknown}} */ error) => {
|
|
|
|
|
|
|
|
var onerror = props.onerror;
|
|
|
|
|
|
|
|
let failed = props.failed;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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 ((!onerror && !failed) || is_creating_fallback) {
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var reset = () => {
|
|
|
|
/** @type {Effect | null} */
|
|
|
|
pause_effect(boundary_effect);
|
|
|
|
#main_effect = null;
|
|
|
|
|
|
|
|
|
|
|
|
with_boundary(boundary, () => {
|
|
|
|
/** @type {Effect | null} */
|
|
|
|
is_creating_fallback = false;
|
|
|
|
#failed_effect = null;
|
|
|
|
boundary_effect = branch(() => boundary_fn(anchor));
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var previous_reaction = active_reaction;
|
|
|
|
#is_creating_fallback = false;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
/**
|
|
|
|
set_active_reaction(null);
|
|
|
|
* @param {TemplateNode} node
|
|
|
|
onerror?.(error, reset);
|
|
|
|
* @param {BoundaryProps} props
|
|
|
|
} finally {
|
|
|
|
* @param {((anchor: Node) => void)} children
|
|
|
|
set_active_reaction(previous_reaction);
|
|
|
|
*/
|
|
|
|
|
|
|
|
constructor(node, props, children) {
|
|
|
|
|
|
|
|
this.#anchor = node;
|
|
|
|
|
|
|
|
this.#props = props;
|
|
|
|
|
|
|
|
this.#children = children;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.#hydrate_open = hydrate_node;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.#effect = block(() => {
|
|
|
|
|
|
|
|
/** @type {Effect} */ (active_effect).b = this;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (hydrating) {
|
|
|
|
|
|
|
|
hydrate_next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (boundary_effect) {
|
|
|
|
try {
|
|
|
|
destroy_effect(boundary_effect);
|
|
|
|
this.#main_effect = branch(() => children(this.#anchor));
|
|
|
|
} else if (hydrating) {
|
|
|
|
} catch (error) {
|
|
|
|
set_hydrate_node(hydrate_open);
|
|
|
|
this.error(error);
|
|
|
|
next();
|
|
|
|
|
|
|
|
set_hydrate_node(remove_nodes());
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}, flags);
|
|
|
|
|
|
|
|
|
|
|
|
if (failed) {
|
|
|
|
if (hydrating) {
|
|
|
|
// Render the `failed` snippet in a microtask
|
|
|
|
this.#anchor = hydrate_node;
|
|
|
|
queue_micro_task(() => {
|
|
|
|
}
|
|
|
|
with_boundary(boundary, () => {
|
|
|
|
}
|
|
|
|
is_creating_fallback = true;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
try {
|
|
|
|
* @param {() => Effect | null} fn
|
|
|
|
boundary_effect = branch(() => {
|
|
|
|
*/
|
|
|
|
failed(
|
|
|
|
#run(fn) {
|
|
|
|
anchor,
|
|
|
|
var previous_effect = active_effect;
|
|
|
|
() => error,
|
|
|
|
var previous_reaction = active_reaction;
|
|
|
|
() => reset
|
|
|
|
var previous_ctx = component_context;
|
|
|
|
);
|
|
|
|
|
|
|
|
});
|
|
|
|
set_active_effect(this.#effect);
|
|
|
|
} catch (error) {
|
|
|
|
set_active_reaction(this.#effect);
|
|
|
|
invoke_error_boundary(error, /** @type {Effect} */ (boundary.parent));
|
|
|
|
set_component_context(this.#effect.ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
is_creating_fallback = false;
|
|
|
|
return fn();
|
|
|
|
});
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
set_active_effect(previous_effect);
|
|
|
|
|
|
|
|
set_active_reaction(previous_reaction);
|
|
|
|
|
|
|
|
set_component_context(previous_ctx);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @param {unknown} error */
|
|
|
|
|
|
|
|
error(error) {
|
|
|
|
|
|
|
|
var onerror = this.#props.onerror;
|
|
|
|
|
|
|
|
let failed = this.#props.failed;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const reset = () => {
|
|
|
|
|
|
|
|
if (this.#failed_effect !== null) {
|
|
|
|
|
|
|
|
pause_effect(this.#failed_effect, () => {
|
|
|
|
|
|
|
|
this.#failed_effect = null;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.#main_effect = this.#run(() => {
|
|
|
|
|
|
|
|
this.#is_creating_fallback = false;
|
|
|
|
|
|
|
|
return branch(() => this.#children(this.#anchor));
|
|
|
|
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (hydrating) {
|
|
|
|
// If we have nothing to capture the error, or if we hit an error while
|
|
|
|
hydrate_next();
|
|
|
|
// rendering the fallback, re-throw for another boundary to handle
|
|
|
|
|
|
|
|
if (this.#is_creating_fallback || (!onerror && !failed)) {
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var previous_reaction = active_reaction;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
set_active_reaction(null);
|
|
|
|
|
|
|
|
onerror?.(error, reset);
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
set_active_reaction(previous_reaction);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
boundary_effect = branch(() => boundary_fn(anchor));
|
|
|
|
if (this.#main_effect) {
|
|
|
|
}, EFFECT_TRANSPARENT | BOUNDARY_EFFECT);
|
|
|
|
destroy_effect(this.#main_effect);
|
|
|
|
|
|
|
|
this.#main_effect = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (hydrating) {
|
|
|
|
if (this.#failed_effect) {
|
|
|
|
anchor = hydrate_node;
|
|
|
|
destroy_effect(this.#failed_effect);
|
|
|
|
|
|
|
|
this.#failed_effect = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (hydrating) {
|
|
|
|
|
|
|
|
set_hydrate_node(this.#hydrate_open);
|
|
|
|
|
|
|
|
next();
|
|
|
|
|
|
|
|
set_hydrate_node(remove_nodes());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (failed) {
|
|
|
|
|
|
|
|
queue_micro_task(() => {
|
|
|
|
|
|
|
|
this.#failed_effect = this.#run(() => {
|
|
|
|
|
|
|
|
this.#is_creating_fallback = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
return branch(() => {
|
|
|
|
|
|
|
|
failed(
|
|
|
|
|
|
|
|
this.#anchor,
|
|
|
|
|
|
|
|
() => error,
|
|
|
|
|
|
|
|
() => reset
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
invoke_error_boundary(error, /** @type {Effect} */ (this.#effect.parent));
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
this.#is_creating_fallback = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|