UNPKG

ripple

Version:

Ripple is an elegant TypeScript UI framework

1,919 lines • 50 kB
/** @import { AppendIntoAnchor, Block, Component, Dependency, BlockWithTryBoundaryAndCatch, DeferredTrackedEntry } from '#client' */
/** @import { NAMESPACE_URI } from './constants.js' */
/** @typedef {TrackedValue} Tracked */
/** @typedef {DerivedValue} Derived */

import { DEV } from 'esm-env';
import { block, destroy_block, destroy_non_branch_children } from './blocks.js';
import {
	ASYNC_DERIVED_READ_THROWN,
	BLOCK_HAS_RUN,
	BRANCH_BLOCK,
	DERIVED,
	COMPUTED_PROPERTY,
	CONTAINS_TEARDOWN,
	DESTROYED,
	EFFECT_BLOCK,
	FOR_BLOCK,
	PAUSED,
	PRE_EFFECT_BLOCK,
	ROOT_BLOCK,
	TRACKED,
	UNINITIALIZED,
	REF_PROP,
	DEFAULT_NAMESPACE,
	SUSPENSE_PENDING,
	SUSPENSE_REJECTED,
	SCHEDULED,
	SELECTOR,
	IF_BLOCK,
	COMPOSITE_BLOCK,
	ITEM_BLOCK,
	RELEASED,
	RENDER_ENTRY,
	CREATES_DERIVEDS,
	NAMESPACE_BLOCK,
	SVG_BLOCK,
} from './constants.js';
import { is_ripple_object } from './utils.js';
import { scope_orphan, set_in_derived, track_orphan, update_depth_exceeded } from './errors.js';
import { render_value } from './expression.js';
import { throw_invalid_component_type } from './component.js';

import {
	define_property,
	get_descriptor,
	is_array,
	object_keys,
} from '@tsrx/core/runtime/language-helpers';
import { hydrating, track_hash_reference } from './hydration.js';
import { HYDRATION } from 'ripple/internal/client/hydration-enabled';
import { create_ref_prop as create_core_ref_prop } from '@tsrx/core/runtime/ref';

const FLUSH_MICROTASK = 0;
const FLUSH_SYNC = 1;

/** @type {null | Block} */
export let active_block = null;
/** @type {null | Block | Derived} */
export let active_reaction = null;
/** @type {null | Block} */
export let active_scope = null;
/** @type {null | Component} */
export let active_component = null;
/** @type {keyof typeof NAMESPACE_URI} */
export let active_namespace = DEFAULT_NAMESPACE;
/** @type {boolean} */
export let is_mutating_allowed = true;

/**
 * Tracked values written during the current flush that hold their previous
 * value in `o`, for teardowns that read them; released when the flush ends.
 * @type {(Tracked | Derived)[]}
 */
var old_value_holders = [];
/** Nesting depth of running flushes; old values are released at depth zero. */
var flush_depth = 0;

/** @returns {void} */
function release_old_values() {
	var holders = old_value_holders;
	var length = holders.length;
	if (length === 0) return;
	for (var i = 0; i < length; i++) {
		holders[i].o = UNINITIALIZED;
	}
	holders.length = 0;
}

// Used for controlling the flush of blocks
/** @type {number} */
let scheduler_mode = FLUSH_MICROTASK;
// Used for handling scheduling
/** @type {boolean} */
let is_micro_task_queued = false;
/** @type {number} */
let clock = 0;
/**
 * Blocks scheduled for the next flush. `sorted` tracks whether they were
 * pushed in creation order, so the common case avoids a sort.
 * @typedef {{ blocks: Block[]; sorted: boolean; last: number }} Queue
 */
/** @type {Queue} */
let queue = create_queue();

/** @returns {Queue} */
function create_queue() {
	return { blocks: [], sorted: true, last: 0 };
}
/** @type {(() => void)[]} */
let queued_microtasks = [];
/** @type {number} */
let flush_count = 0;
/** @type {(() => void)[]} */
var queued_post_block_flush = [];
/** @type {null | Dependency} */
export let active_dependency = null;

export let tracking = false;
export let teardown = false;

/**
 * @returns {number}
 */
function increment_clock() {
	return ++clock;
}

/**
 * @param {Block | null} block
 */
export function set_active_block(block) {
	active_block = block;
}

/**
 * @param {Block | Derived | null} reaction
 */
export function set_active_reaction(reaction) {
	active_reaction = reaction;
}

/**
 * @param {Component | null} component
 */
export function set_active_component(component) {
	active_component = component;
}

/**
 * @param {boolean} value
 */
export function set_tracking(value) {
	tracking = value;
}

/**
 * @param {boolean} value
 */
export function set_mutating_allowed(value) {
	is_mutating_allowed = value;
}

/**
 * Handles a block whose run read a pending async value (see
 * `track-async.js`). Installed by the first `trackAsync()`, so an application
 * without one carries none of the boundary request machinery.
 * @type {((block: Block) => void) | null}
 */
let pending_read = null;

/**
 * @param {(block: Block) => void} fn
 */
export function set_pending_read_handler(fn) {
	pending_read = fn;
}

/**
 * @param {Block} block
 */
export function run_teardown(block) {
	var fn = block.t;
	if (fn !== null) {
		var previous_block = active_block;
		var previous_reaction = active_reaction;
		var previous_tracking = tracking;
		var previous_teardown = teardown;

		try {
			active_block = null;
			active_reaction = null;
			tracking = false;
			teardown = true;
			// Runtime-internal teardowns keyed on block state get it as their
			// argument, so they need no per-block closure; user effects have
			// null state.
			fn.call(null, block.s);
		} finally {
			active_block = previous_block;
			active_reaction = previous_reaction;
			tracking = previous_tracking;
			teardown = previous_teardown;
		}
	}
}

/**
 * @param {Block} block
 * @param {() => any} fn
 */
export function with_block(block, fn) {
	var prev_block = active_block;
	var previous_component = active_component;
	active_block = block;
	active_component = block.co;
	try {
		return fn();
	} finally {
		active_component = previous_component;
		active_block = prev_block;
	}
}

/**
 * @param {Derived} computed
 */
function update_derived(computed) {
	var value = computed.__v;

	if (value === UNINITIALIZED || is_tracking_dirty(computed.d)) {
		value = run_derived(computed);

		if (value !== computed.__v) {
			computed.__v = value;
			computed.c = increment_clock();
		}
	}
}

/**
 * @param {Tracked} tracked
 * @param {any} value
 */
export function update_tracked_value_clock(tracked, value) {
	tracked.__v = value;
	tracked.c = increment_clock();
	mark_subscribers(tracked);
}

/**
 * @param {Derived} computed
 */
function destroy_computed_children(computed) {
	var blocks = computed.blocks;

	if (blocks !== null) {
		computed.blocks = null;
		for (var i = 0; i < blocks.length; i++) {
			destroy_block(blocks[i]);
		}
	}
}

/**
 * @param {Derived} computed
 */
function run_derived(computed) {
	var previous_block = active_block;
	var previous_reaction = active_reaction;
	var previous_tracking = tracking;
	var previous_dependency = active_dependency;
	var previous_component = active_component;
	var previous_is_mutating_allowed = is_mutating_allowed;

	try {
		active_block = computed.b;
		active_reaction = computed;
		tracking = true;
		active_dependency = null;
		active_component = computed.co;
		is_mutating_allowed = false;

		destroy_computed_children(computed);

		var value = computed.fn();

		finish_dependencies(computed, active_dependency);

		return value;
	} catch (error) {
		finish_dependencies(computed, active_dependency);
		if (error === ASYNC_DERIVED_READ_THROWN) {
			// Check if any dependency is rejected — if so, propagate rejection
			var dep = active_dependency;
			while (dep !== null) {
				if (dep.t.__v === SUSPENSE_REJECTED) {
					return SUSPENSE_REJECTED;
				}
				dep = dep.n;
			}
			return SUSPENSE_PENDING;
		}
		throw error;
	} finally {
		active_block = previous_block;
		active_reaction = previous_reaction;
		tracking = previous_tracking;
		active_dependency = previous_dependency;
		active_component = previous_component;
		is_mutating_allowed = previous_is_mutating_allowed;
	}
}

/**
 * Routes an error thrown by a block into the nearest boundary with a catch
 * branch (see `try.js`). Installed by the first `try_block`, so a bundle whose
 * mount and components create no boundary carries no catch machinery.
 * @type {((error: unknown, block: Block) => BlockWithTryBoundaryAndCatch | null) | null}
 */
let catch_router = null;

/**
 * @param {(error: unknown, block: Block) => BlockWithTryBoundaryAndCatch | null} fn
 */
export function set_catch_router(fn) {
	catch_router = fn;
}

/**
 * @param {unknown} error
 * @param {Block} block
 * @returns {BlockWithTryBoundaryAndCatch}
 */
export function handle_error(error, block) {
	var boundary_with_catch = catch_router === null ? null : catch_router(error, block);
	if (boundary_with_catch === null) {
		throw error;
	}
	return boundary_with_catch;
}

/**
 * The error path of {@link run_block}, kept out of that hot function so a
 * healthy mount never compiles it: routes real errors to the nearest catch
 * boundary, and a read of a pending async value to the handler `trackAsync`
 * installs (the read is registered as a dependency, so the block re-runs once
 * the value settles).
 * @param {unknown} error
 * @param {Block} block
 */
function handle_run_error(error, block) {
	finish_dependencies(block, active_dependency);
	if (error !== ASYNC_DERIVED_READ_THROWN) {
		handle_error(error, block);
	} else if (pending_read !== null) {
		pending_read(block);
	} else {
		throw error;
	}
}

/**
 * Clears what a block's previous run left behind before it runs again. A
 * list's children are all item branches, so there is nothing to sweep; an if
 * block's children are its branch, which it replaces itself only when the
 * condition changes. Returns the deriveds the previous run created, to be
 * released once the new run has destroyed whatever read them (an if block
 * swaps its branch inside its own run).
 * @param {Block} block
 * @returns {Derived[] | null}
 */
function prepare_rerun(block) {
	// A list, an if, a composite and a list item re-run only their own logic;
	// their child blocks (items, the branch's blocks, a dynamic element's
	// children, an item body's nested blocks) stay.
	if ((block.f & (FOR_BLOCK | IF_BLOCK | ITEM_BLOCK | COMPOSITE_BLOCK)) === 0) {
		destroy_non_branch_children(block);
	}
	run_teardown(block);
	return (block.f & CREATES_DERIVEDS) !== 0 ? take_created_deriveds(block) : null;
}

/**
 * @param {Block} block
 * @param {(state?: any) => void} teardown
 */
function register_teardown(block, teardown) {
	block.t = teardown;
	/** @type {Block | null} */
	var current = block;

	while (current !== null && (current.f & CONTAINS_TEARDOWN) === 0) {
		current.f ^= CONTAINS_TEARDOWN;
		current = current.p;
	}
}

/**
 * Runs the body of a branch block that was just created, in place of a first
 * `run_block`: a branch renders untracked, so the only bookkeeping its first
 * run needs is the globals the body reads and the dependencies a nested
 * `item` records on it (a list item re-runs through `run_block`, see
 * `run_item`). Errors are handled as `run_block` handles them.
 * @param {Block} block
 * @param {(anchor: any, value: any, index?: any, key?: any) => void} fn
 * @param {any} anchor
 * @param {any} value a list item's value, or its tracked
 * @param {any} [index] a list item's tracked index (see `create_item`)
 * @param {any} [key] a keyed list item's key
 */
export function run_branch(block, fn, anchor, value, index, key) {
	var previous_block = active_block;
	var previous_reaction = active_reaction;
	var previous_tracking = tracking;
	var previous_dependency = active_dependency;
	var previous_component = active_component;

	try {
		active_block = block;
		active_reaction = block;
		active_component = block.co;
		tracking = false;
		active_dependency = null;
		fn(anchor, value, index, key);
		if (active_dependency !== null) {
			block.d = active_dependency;
		}
	} catch (error) {
		handle_run_error(error, block);
	} finally {
		active_block = previous_block;
		active_reaction = previous_reaction;
		tracking = previous_tracking;
		active_dependency = previous_dependency;
		active_component = previous_component;
	}
}

/**
 * Runs `fn(block.s, arg)` as a rerun of `block` driven from outside it: an if
 * whose condition the enclosing render function evaluates renders its branch
 * this way. The function runs under the block, in the block's namespace, and
 * the deriveds the block's previous run created are released afterwards
 * unless still read, as `run_block` does for a rerun. An error propagates to
 * the caller's block.
 * @template S, A
 * @param {Block} block
 * @param {(state: S, arg: A) => void} fn
 * @param {A} arg
 */
export function run_in_block(block, fn, arg) {
	var previous_block = active_block;
	var previous_reaction = active_reaction;
	var previous_component = active_component;
	var previous_namespace = active_namespace;
	var previous_deriveds = (block.f & CREATES_DERIVEDS) !== 0 ? take_created_deriveds(block) : null;

	try {
		active_block = block;
		active_reaction = block;
		active_component = block.co;
		var ns = block.f & NAMESPACE_BLOCK;
		active_namespace = ns === 0 ? DEFAULT_NAMESPACE : ns === SVG_BLOCK ? 'svg' : 'mathml';
		fn(/** @type {S} */ (block.s), arg);
	} finally {
		active_block = previous_block;
		active_reaction = previous_reaction;
		active_component = previous_component;
		active_namespace = previous_namespace;
		if (previous_deriveds !== null) {
			keep_deriveds(block, release_deriveds(previous_deriveds));
		}
	}
}

/**
 * @param {Block} block
 * @param {boolean} [first_run] true when the block has no children, teardown,
 * or dependencies yet, so that cleanup can be skipped
 */
export function run_block(block, first_run = false) {
	var previous_block = active_block;
	var previous_reaction = active_reaction;
	var previous_tracking = tracking;
	var previous_dependency = active_dependency;
	var previous_component = active_component;
	var previous_namespace = active_namespace;
	/** @type {Derived[] | null} */
	var previous_deriveds = null;

	try {
		active_block = block;
		active_reaction = block;
		active_component = block.co;
		// The namespace the block was created in (see `create_block`): a rerun
		// from a flush is outside the `with_ns()` call that established it.
		var ns = block.f & NAMESPACE_BLOCK;
		active_namespace = ns === 0 ? DEFAULT_NAMESPACE : ns === SVG_BLOCK ? 'svg' : 'mathml';

		if (!first_run) {
			previous_deriveds = prepare_rerun(block);
		}

		tracking = (block.f & (ROOT_BLOCK | BRANCH_BLOCK)) === 0;
		active_dependency = null;
		var res = block.fn(block.s);

		if (typeof res === 'function') {
			register_teardown(block, res);
		}

		if (block.d === null) {
			// First run (or no dependencies so far): nothing stale to unlink.
			if (active_dependency !== null) {
				block.d = active_dependency;
			}
		} else {
			finish_dependencies(block, active_dependency);
		}
	} catch (error) {
		handle_run_error(error, block);
	} finally {
		active_block = previous_block;
		active_reaction = previous_reaction;
		tracking = previous_tracking;
		active_dependency = previous_dependency;
		active_component = previous_component;
		active_namespace = previous_namespace;

		// The previous run's deriveds, whether this run finished or threw, and
		// after this run's dependencies replaced the old ones (so a derived the
		// old run read is not kept alive by that stale link). Those still read
		// stay recorded under the block for its next rerun or destruction: a
		// rerun that keeps the same branch must not lose them.
		if (previous_deriveds !== null) {
			keep_deriveds(block, release_deriveds(previous_deriveds));
		}
	}
}

var empty_get_set = { get: undefined, set: undefined };

class TrackedValue {
	/**
	 * @param {any} v
	 * @param {Block} block
	 * @param {{ get?: Function; set?: Function | true }} a
	 * @param {string} [hash]
	 */
	constructor(v, block, a, hash) {
		/** @type {{ get?: Function; set?: Function | true }} */
		this.a = a;
		/** @type {Block} */
		this.b = block;
		/** @type {number} */
		this.c = 0;
		/** @type {DeferredTrackedEntry[] | null} */
		this.d = null;
		/** @type {number} */
		this.f = TRACKED;
		/** @type {any} hydration hash, or the key of a selector match flag */
		this.h = hash;
		/** @type {Dependency | null} */
		this.sb = null;
		/** @type {any} */
		this.__v = v;
		/** @type {any} previous value while a flush with teardowns is running */
		this.o = UNINITIALIZED;
	}
	/** @returns {any} */
	get value() {
		return get_tracked(this);
	}
	/** @param {any} v */
	set value(v) {
		set(this, v);
	}
}

class DerivedValue {
	/**
	 * @param {Function} fn
	 * @param {Block} block
	 * @param {{ get?: Function; set?: Function | true }} a
	 * @param {string} [hash]
	 */
	constructor(fn, block, a, hash) {
		/** @type {{ get?: Function; set?: Function | true }} */
		this.a = a;
		/** @type {Block} */
		this.b = block;
		/** @type {Block[] | null} */
		this.blocks = null;
		/** @type {number} */
		this.c = 0;
		/** @type {Component | null} */
		this.co = active_component;
		/** @type {Dependency | null} */
		this.d = null;
		/** @type {number} */
		this.f = DERIVED;
		/** @type {Function} */
		this.fn = fn;
		/** @type {string | undefined} */
		this.h = hash;
		/** @type {Dependency | null} */
		this.sb = null;
		/** @type {any} */
		this.__v = UNINITIALIZED;
		/** @type {any} previous value while a flush with teardowns is running */
		this.o = UNINITIALIZED;
	}
	/** @returns {any} */
	get value() {
		return get_derived(this);
	}
	/** @param {any} v */
	set value(v) {
		set(this, v);
	}
}

if (DEV) {
	define_property(TrackedValue.prototype, 'DO_NOT_ACCESS_THIS_OBJECT_DIRECTLY', { value: true });
	define_property(DerivedValue.prototype, 'DO_NOT_ACCESS_THIS_OBJECT_DIRECTLY', { value: true });
}

/**
 *
 * @param {any} v
 * @param {Block} block
 * @param {string} [hash]
 * @param {(value: any) => any} [get]
 * @param {((next: any, prev: any) => any) | true} [set]
 * @returns {Tracked}
 */
export function tracked(v, block, hash, get, set) {
	var t = /** @type {Tracked} */ (
		new TrackedValue(v, block || active_block, get || set ? { get, set } : empty_get_set, hash)
	);
	if (HYDRATION && hydrating && hash !== undefined) {
		track_hash_reference.set(hash, t);
	}
	return t;
}

/**
 * @param {any} fn
 * @param {Block} block
 * @param {string} [hash]
 * @param {(value: any) => any} [get]
 * @param {((next: any, prev: any) => any) | true} [set]
 * @returns {Derived}
 */
export function derived(fn, block, hash, get, set) {
	var d = /** @type {Derived} */ (
		new DerivedValue(fn, block || active_block, get || set ? { get, set } : empty_get_set, hash)
	);
	// The run that creates a derived owns its release: when that block reruns
	// or is destroyed, the derived is unsubscribed from its sources unless a
	// reader that is still alive holds it (see `release_deriveds`).
	var creator = active_block;
	if (creator !== null) {
		var created = created_deriveds.get(creator);
		if (created === undefined) {
			created_deriveds.set(creator, [d]);
			creator.f |= CREATES_DERIVEDS;
		} else {
			created.push(d);
		}
	}
	if (HYDRATION && hydrating && hash !== undefined) {
		track_hash_reference.set(hash, d);
	}
	return d;
}

/**
 * The deriveds each block's latest run created, keyed by the block. A side
 * table rather than a block field: most blocks never create a derived, and
 * block creation is the hot path, so blocks keep their shape. A block with an
 * entry carries `CREATES_DERIVEDS` in its flags, so a rerun or destroy of any
 * other block never looks here.
 * @type {Map<Block, Derived[]>}
 */
var created_deriveds = new Map();

/**
 * Removes and returns the deriveds `block`'s latest run created, or null.
 * @param {Block} block
 * @returns {Derived[] | null}
 */
export function take_created_deriveds(block) {
	var deriveds = created_deriveds.get(block);
	block.f &= ~CREATES_DERIVEDS;
	if (deriveds === undefined) {
		return null;
	}
	created_deriveds.delete(block);
	return deriveds;
}

/**
 * Releases the deriveds a block run created, once that run's readers are
 * gone: the block reran (its previous run's readers were destroyed by the
 * rerun) or was destroyed. A derived that a live reader still holds (the
 * branch is still showing, or it was handed to a longer-lived scope) is
 * kept. Walked newest first, so a derived read by a later one of the same
 * run sees that reader unlinked before it is examined; the survivors are
 * packed into the tail of the same array as they are met, which leaves them
 * in creation order for the next pass, and slid to the front at the end.
 * @param {Derived[]} deriveds in creation order; reused for the result
 * @returns {Derived[] | null} the deriveds kept, in creation order, if any
 */
export function release_deriveds(deriveds) {
	var length = deriveds.length;
	var kept = length;
	for (var i = length - 1; i >= 0; i--) {
		var derived = deriveds[i];
		var alive = false;
		for (var sub = derived.sb; sub !== null; sub = sub.sn) {
			if ((sub.r.f & DESTROYED) === 0) {
				alive = true;
				break;
			}
		}
		if (alive) {
			// `kept` never drops below `i`, so this only overwrites visited slots.
			deriveds[--kept] = derived;
		} else {
			finish_dependencies(derived, null);
			destroy_computed_children(derived);
		}
	}
	if (kept === length) {
		return null;
	}
	if (kept !== 0) {
		deriveds.copyWithin(0, kept);
		deriveds.length = length - kept;
	}
	return deriveds;
}

/**
 * Records deriveds under `block` again after a rerun kept them, ahead of
 * whatever the new run created: the kept ones are older, and the release
 * order (newest first) relies on the list being in creation order.
 * @param {Block} block
 * @param {Derived[] | null} kept
 */
function keep_deriveds(block, kept) {
	if (kept === null) {
		return;
	}
	var created = created_deriveds.get(block);
	if (created !== undefined) {
		for (var i = 0; i < created.length; i++) {
			kept.push(created[i]);
		}
	}
	created_deriveds.set(block, kept);
	block.f |= CREATES_DERIVEDS;
}

/**
 * A read-only view of a tracked or derived value, for a receiver that should
 * read but not write it: a derived over a tracked (or a writable derived),
 * equivalent to `track(() => value.value)`; a read-only derived as it is; a
 * plain value as it is, like `get`. The view is owned by the block that
 * creates it, not by the value's block, so a view made in a component is
 * released with that component even when the value outlives it.
 * @param {any} value
 * @param {Block} [block]
 * @returns {any}
 */
export function track_read_only(value, block) {
	if (!is_ripple_object(value)) {
		return value;
	}
	var owner = block || /** @type {Block} */ (active_block);
	if ((value.f & DERIVED) !== 0) {
		var d = /** @type {Derived} */ (value);
		return d.a.set === undefined ? d : derived(() => get_derived(d), owner);
	}
	var t = /** @type {Tracked} */ (value);
	return derived(() => get_tracked(t), owner);
}

/**
 * @param {any} v
 * @param {Block} b
 * @param {string} [hash]
 * @param {(value: any) => any} [get]
 * @param {((next: any, prev: any) => any) | true} [set]
 * @returns {Tracked | Derived}
 */
export function track(v, b, hash, get, set) {
	// is_ripple_object(), inline: track() is on every component's cold path
	// (kept identical by tests/utils/inline-drift.test.js).
	if (typeof v === 'object' && v !== null && typeof v.f === 'number') {
		return v;
	}
	if (b === null) {
		track_orphan();
	}

	if (typeof v === 'function') {
		return derived(v, b, hash, get, set);
	}
	return tracked(v, b, hash, get, set);
}

/**
 * @param {(Derived | Tracked) | (() => any)} t
 * @returns {boolean}
 */
export function is_tracked_pending(t) {
	try {
		if (typeof t === 'function') {
			t();
		} else {
			get(t);
		}
		return false;
	} catch (error) {
		if (error === ASYNC_DERIVED_READ_THROWN) {
			return true;
		}
		throw error;
	}
}

/**
 * @param {Tracked | Derived} tracked
 * @return {any}
 */
export function peek_tracked(tracked) {
	if (!is_ripple_object(tracked)) {
		return tracked;
	}

	return tracked.__v;
}

/**
 * @param {Tracked | Derived} tracked
 * @returns {Dependency}
 */
function create_dependency(tracked) {
	var reaction = /** @type {Derived | Block} **/ (active_reaction);
	var existing = reaction.d;

	// Recycle tracking entries
	if (existing !== null) {
		reaction.d = existing.n;
		existing.c = tracked.c;
		existing.n = null;
		if (existing.t !== tracked) {
			unlink_subscriber(existing);
			existing.t = tracked;
			link_subscriber(existing, tracked);
		}
		return existing;
	}

	// link_subscriber, inline: the first read of every block run lands here.
	var head = tracked.sb;
	/** @type {Dependency} */
	var dependency = {
		c: tracked.c,
		t: tracked,
		n: null,
		r: reaction,
		sp: null,
		sn: head,
	};
	if (head !== null) {
		head.sp = dependency;
	}
	tracked.sb = dependency;
	return dependency;
}

/**
 * @param {Dependency} dependency
 * @param {Tracked | Derived} tracked
 */
function link_subscriber(dependency, tracked) {
	var head = tracked.sb;
	dependency.sn = head;
	if (head !== null) {
		head.sp = dependency;
	}
	tracked.sb = dependency;
}

/**
 * @param {Dependency} dependency
 */
export function unlink_subscriber(dependency) {
	var prev = dependency.sp;
	var next = dependency.sn;
	if (prev !== null) {
		prev.sn = next;
	} else {
		var tracked = dependency.t;
		// Already unlinked (a destroyed block's dependency can be pruned by a
		// write during its own teardown, then unlinked again by
		// remove_dependencies); it must not be mistaken for the list head.
		if (tracked.sb !== dependency) {
			return;
		}
		tracked.sb = next;
		if (next === null && (tracked.f & SELECTOR) !== 0) {
			release_selector_key(/** @type {Tracked} */ (tracked));
		}
	}
	if (next !== null) {
		next.sp = prev;
	}
	dependency.sp = dependency.sn = null;
}

/**
 * `n` counts released keys still in the map.
 * @typedef {{ get: undefined; set: undefined; m: Map<any, Tracked>; n: number }} SelectorAccessors
 */

/** Released selector keys kept for reuse before the map is swept. */
var SELECTOR_SWEEP_THRESHOLD = 1024;

/**
 * The last subscriber of a selector key left. The entry stays in the map so
 * a key that comes back (a list re-rendering the same items) reuses it
 * instead of allocating and inserting again; once enough released keys pile
 * up they are swept out, so the map stays bounded.
 * @param {Tracked} tracked
 */
function release_selector_key(tracked) {
	var accessors = /** @type {SelectorAccessors} */ (tracked.a);
	tracked.f |= RELEASED;
	if (++accessors.n > SELECTOR_SWEEP_THRESHOLD) {
		var m = accessors.m;
		for (var [key, t] of m) {
			if ((t.f & RELEASED) !== 0) {
				m.delete(key);
			}
		}
		accessors.n = 0;
	}
}

/**
 * A key found in the map again after its subscribers had all left.
 * @param {Tracked} tracked
 */
export function revive_selector_key(tracked) {
	tracked.f ^= RELEASED;
	/** @type {SelectorAccessors} */ (tracked.a).n--;
}

/**
 * A tracked match flag for one selector key. The selector's shared accessor
 * object carries the map and the key rides in the hash slot, so the entry can
 * be released from `unlink_subscriber` without an object per key.
 * @param {boolean} value
 * @param {Block} block
 * @param {SelectorAccessors} accessors
 * @param {any} key
 * @returns {Tracked}
 */
export function selector_tracked(value, block, accessors, key) {
	var t = /** @type {Tracked} */ (new TrackedValue(value, block, accessors, key));
	t.f |= SELECTOR;
	return t;
}

/**
 * Install the dependency chain produced by a run, unsubscribing from any
 * previous dependencies that were not read again.
 * @param {Block | Derived} reaction
 * @param {Dependency | null} dependencies
 */
function finish_dependencies(reaction, dependencies) {
	var stale = reaction.d;
	while (stale !== null) {
		unlink_subscriber(stale);
		stale = stale.n;
	}
	reaction.d = dependencies;
}

/**
 * Unsubscribe a block from every tracked value it read. Dependencies on
 * values owned by an already-destroyed block are dropped without unlinking,
 * since the owner's subscriber list dies with it.
 * @param {Block} block
 */
export function remove_dependencies(block) {
	var dependency = block.d;
	while (dependency !== null) {
		var owner = dependency.t.b;
		if (owner === null || (owner.f & DESTROYED) === 0) {
			unlink_subscriber(dependency);
		}
		dependency = dependency.n;
	}
	block.d = null;
}

/**
 * Schedule every reaction subscribed to `tracked`. Deriveds are lazy, so their
 * own subscribers are marked instead; subscribers that were destroyed without
 * unlinking are pruned here.
 * @param {Tracked | Derived} tracked
 */
function mark_subscribers(tracked) {
	var dependency = tracked.sb;
	while (dependency !== null) {
		var next = dependency.sn;
		var reaction = dependency.r;
		var flags = reaction.f;
		if ((flags & DERIVED) !== 0) {
			var derived = /** @type {Derived} */ (reaction);
			// Marking the derived's own subscribers first prunes its destroyed
			// readers, so a derived whose owner is gone and that nothing reads any
			// more is dropped on this write; one still read elsewhere keeps
			// forwarding notifications.
			mark_subscribers(derived);
			var derived_owner = derived.b;
			if (derived_owner !== null && (derived_owner.f & DESTROYED) !== 0 && derived.sb === null) {
				unlink_subscriber(dependency);
			}
		} else if ((flags & DESTROYED) !== 0) {
			unlink_subscriber(dependency);
		} else {
			schedule_update(/** @type {Block} */ (reaction));
		}
		dependency = next;
	}
}

/**
 * @param {Dependency | null} tracking
 */
function is_tracking_dirty(tracking) {
	if (tracking === null) {
		return false;
	}
	while (tracking !== null) {
		var tracked = tracking.t;

		if ((tracked.f & DERIVED) !== 0) {
			try {
				update_derived(/** @type {Derived} **/ (tracked));
			} catch (e) {
				if (e === ASYNC_DERIVED_READ_THROWN) {
					// The derived depends on a pending async value — treat as dirty
					return true;
				}
				throw e;
			}
		}

		if (tracked.c > tracking.c) {
			return true;
		}
		tracking = tracking.n;
	}

	return false;
}

/**
 * @param {Block} block
 */
export function is_block_dirty(block) {
	var flags = block.f;

	// A branch never re-runs, except a list item that carries its render block.
	if ((flags & ITEM_BLOCK) === 0 && (flags & (ROOT_BLOCK | BRANCH_BLOCK)) !== 0) {
		return false;
	}
	if ((flags & BLOCK_HAS_RUN) === 0) {
		block.f ^= BLOCK_HAS_RUN;
		return true;
	}

	return is_tracking_dirty(block.d);
}

/**
 * @template V
 * @param {Function} fn
 * @param {V} v
 */
function trigger_track_get(fn, v) {
	var previous_is_mutating_allowed = is_mutating_allowed;
	try {
		is_mutating_allowed = false;
		return untrack(() => fn(v));
	} finally {
		is_mutating_allowed = previous_is_mutating_allowed;
	}
}

/**
 * @param {Block} a
 * @param {Block} b
 * @returns {number}
 */
function by_block_id(a, b) {
	return a.i - b.i;
}

/**
 * Run every scheduled block, in creation order (so a parent runs before its
 * descendants), in three phases: pre-effects, render blocks, then effects.
 * @param {Queue} pending
 */
function flush_queue(pending) {
	var blocks = pending.blocks;
	var length = blocks.length;

	if (length > 0) {
		if (!pending.sorted) {
			blocks.sort(by_block_id);
		}

		var effect_kinds = 0;
		var has_render_blocks = false;

		for (var i = 0; i < length; i++) {
			var block = blocks[i];
			var flags = block.f;
			block.f = flags & ~SCHEDULED;
			var kind = flags & (PRE_EFFECT_BLOCK | EFFECT_BLOCK);
			if (kind !== 0) {
				effect_kinds |= kind;
			} else {
				has_render_blocks = true;
			}
		}

		// New schedules land in the queue that replaced `pending`, so the array
		// can be run in place. A queue mixing kinds needs the three-phase
		// ordering; one of a single kind (the common flush of render blocks,
		// the first flush of a mounted app's effects) runs straight through.
		if (
			effect_kinds === (PRE_EFFECT_BLOCK | EFFECT_BLOCK) ||
			(effect_kinds !== 0 && has_render_blocks)
		) {
			run_phases(blocks, length);
		} else {
			run_phase(blocks);
		}

		blocks.length = 0;
		pending.sorted = true;
		pending.last = 0;
	}

	if (queued_post_block_flush.length > 0) {
		var callbacks = queued_post_block_flush;
		queued_post_block_flush = [];
		for (var j = 0; j < callbacks.length; j++) {
			callbacks[j]();
		}
	}
}

/**
 * Runs a queue that contains effects in three phases: pre-effects, render
 * blocks, then effects. Kept out of {@link flush_queue} so a render-only flush
 * never compiles it.
 * @param {Block[]} blocks
 * @param {number} length
 */
function run_phases(blocks, length) {
	/** @type {Block[]} */
	var pre_effects = [];
	/** @type {Block[]} */
	var other_blocks = [];
	/** @type {Block[]} */
	var effects = [];

	for (var i = 0; i < length; i++) {
		var block = blocks[i];
		var flags = block.f;

		// A paused block is re-checked when it resumes; a destroyed one is gone.
		if ((flags & (PAUSED | DESTROYED)) !== 0) {
			continue;
		}
		if ((flags & PRE_EFFECT_BLOCK) !== 0) {
			pre_effects.push(block);
		} else if ((flags & EFFECT_BLOCK) !== 0) {
			effects.push(block);
		} else {
			other_blocks.push(block);
		}
	}

	run_phase(pre_effects);
	run_phase(other_blocks);
	run_phase(effects);
}

/**
 * @param {Block[]} blocks
 */
function run_phase(blocks) {
	for (var i = 0; i < blocks.length; i++) {
		var block = blocks[i];
		var flags = block.f;

		try {
			if ((flags & (PAUSED | DESTROYED)) === 0 && is_block_dirty(block)) {
				// A block that has never run has no children or teardown to clear.
				run_block(block, (flags & BLOCK_HAS_RUN) === 0);
			}
		} catch (error) {
			handle_error(error, block);
		}
	}
}

/**
 * @returns {Promise<void>}
 */
export async function tick() {
	return new Promise((f) => requestAnimationFrame(() => f()));
}

/**
 * @returns {void}
 */
function flush_microtasks() {
	is_micro_task_queued = false;

	if (queued_microtasks.length > 0) {
		var microtasks = queued_microtasks;
		queued_microtasks = [];
		for (var i = 0; i < microtasks.length; i++) {
			microtasks[i]();
		}
	}

	flush_count++;
	if (flush_count > 1001) {
		update_depth_exceeded();
	}
	var pending = queue;
	queue = create_queue();
	flush_depth++;
	try {
		flush_queue(pending);
	} finally {
		flush_depth--;
	}

	if (!is_micro_task_queued) {
		flush_count = 0;
	}
	if (flush_depth === 0) {
		release_old_values();
	}
}

/**
 * @param { (() => void) } [fn]
 */
export function queue_microtask(fn) {
	if (!is_micro_task_queued) {
		is_micro_task_queued = true;
		queueMicrotask(flush_microtasks);
	}
	if (fn !== undefined) {
		queued_microtasks.push(fn);
	}
}

/**
 * Queue a callback to run after all root blocks are flushed.
 * Used to defer boundary completions so chained async deriveds evaluated during
 * the flush can start new requests before the boundary transitions out of pending.
 * @param {() => void} fn
 */
export function queue_post_block_flush_callback(fn) {
	queued_post_block_flush.push(fn);
}

/**
 * @param {Block} block
 */
export function schedule_update(block) {
	if ((block.f & SCHEDULED) !== 0) {
		return;
	}
	block.f |= SCHEDULED;

	if (scheduler_mode === FLUSH_MICROTASK) {
		queue_microtask();
	}

	var id = block.i;
	if (id < queue.last) {
		queue.sorted = false;
	}
	queue.last = id;
	queue.blocks.push(block);
}

/**
 * @param {Tracked | Derived} tracked
 */
function register_dependency(tracked) {
	var dependency = active_dependency;

	if (dependency === null) {
		dependency = create_dependency(tracked);
		active_dependency = dependency;
	} else {
		var current = dependency;

		while (current !== null) {
			if (current.t === tracked) {
				current.c = tracked.c;
				return;
			}
			var next = current.n;
			if (next === null) {
				break;
			}
			current = next;
		}

		dependency = create_dependency(tracked);
		current.n = dependency;
	}
}

/**
 * @param {Derived} computed
 */
export function get_derived(computed) {
	update_derived(computed);
	if (tracking) {
		register_dependency(computed);
	}
	var value = computed.__v;
	var get = computed.a.get;
	if (get !== undefined) {
		value = trigger_track_get(get, value);
		computed.__v = value;
	}

	if (value === SUSPENSE_PENDING || value === SUSPENSE_REJECTED) {
		throw ASYNC_DERIVED_READ_THROWN;
	}

	return value;
}

/**
 * @param {Derived | Tracked} tracked
 */
export function get(tracked) {
	// reflect back the value if it's not boxed
	if (!is_ripple_object(tracked)) {
		return tracked;
	}

	return (tracked.f & DERIVED) !== 0
		? get_derived(/** @type {Derived} */ (tracked))
		: get_tracked(/** @type {Tracked} */ (tracked));
}

/**
 * @param {Tracked} tracked
 */
export function get_tracked(tracked) {
	var value = tracked.__v;
	if (tracking) {
		// register_dependency, inline for the first read of a run.
		if (active_dependency === null) {
			active_dependency = create_dependency(tracked);
		} else {
			register_dependency(tracked);
		}
	}

	if (value === SUSPENSE_PENDING || value === SUSPENSE_REJECTED) {
		throw ASYNC_DERIVED_READ_THROWN;
	}

	if (teardown && tracked.o !== UNINITIALIZED) {
		value = tracked.o;
	}
	var get = tracked.a.get;
	if (get !== undefined) {
		value = trigger_track_get(get, value);
	}
	return value;
}

/**
 * @param {Derived | Tracked} tracked
 * @param {any} value
 */
export function set(tracked, value) {
	if (!is_mutating_allowed) {
		set_in_derived();
	}

	var old_value = tracked.__v;

	if (value !== old_value) {
		var tracked_block = tracked.b;

		if ((tracked_block.f & CONTAINS_TEARDOWN) !== 0) {
			if (tracked.o === UNINITIALIZED) {
				old_value_holders.push(tracked);
			}
			tracked.o = teardown ? value : old_value;
		}

		let set = tracked.a.set;
		if (typeof set === 'function') {
			value = untrack(() => set(value, old_value));
		} else if (DEV && set === undefined && (tracked.f & DERIVED) !== 0) {
			warn_readonly_derived_write(/** @type {Derived} */ (tracked));
		}

		tracked.__v = value;
		tracked.c = increment_clock();
		mark_subscribers(tracked);
	}
}

/** @type {WeakSet<object>} */
var warned_derived = new WeakSet();

/**
 * A derived created without a setter is read-only by contract (`Derived<V>`);
 * a write still lands as a temporary value until the next recompute, so in
 * development it is reported once per derived.
 * @param {Derived} derived
 */
function warn_readonly_derived_write(derived) {
	if (warned_derived.has(derived)) return;
	warned_derived.add(derived);
	console.warn(
		'Writing to a read-only derived. Create it with a setter, `track(fn, undefined, true)` or `track(fn, get, set)`, when it is meant to be written.',
	);
}

/**
 * @template T
 * @param {() => T} fn
 * @returns {T}
 */
export function untrack(fn) {
	var previous_tracking = tracking;
	var previous_dependency = active_dependency;
	tracking = false;
	active_dependency = null;
	try {
		return fn();
	} finally {
		tracking = previous_tracking;
		active_dependency = previous_dependency;
	}
}

/**
 * @template T
 * @param {() => T} [fn]
 * @returns {T}
 */
export function flush_sync(fn) {
	var previous_scheduler_mode = scheduler_mode;
	var previous_queue = queue;

	flush_depth++;
	try {
		scheduler_mode = FLUSH_SYNC;
		queue = create_queue();
		is_micro_task_queued = false;

		// Drains previous_queue, which then stays empty for the restore below.
		flush_queue(previous_queue);

		var result = fn?.();

		// Each pass runs the blocks scheduled by the previous one.
		while (queue.blocks.length > 0) {
			var pending = queue;
			queue = create_queue();
			flush_queue(pending);
		}

		flush_count = 0;

		// Old values only matter to teardowns run by the flush in progress; a
		// sync flush nested in another flush (an effect calling `flushSync`)
		// leaves them for the outer flush's remaining teardowns.
		if (flush_depth === 1) {
			release_old_values();
		}

		return /** @type {T} */ (result);
	} finally {
		flush_depth--;
		scheduler_mode = previous_scheduler_mode;
		queue = previous_queue;
	}
}

/**
 * @template T
 * @param {() => T} fn
 * @returns {() => T}
 */
export function computed_property(fn) {
	define_property(fn, COMPUTED_PROPERTY, {
		value: true,
		enumerable: false,
	});
	return fn;
}

/**
 * @param {any} obj
 * @param {string | number | symbol} property
 * @param {boolean} chain_obj
 * @param {boolean} chain_prop
 * @param {...any} args
 * @returns {any}
 */
export function call_property(obj, property, chain_obj, chain_prop, ...args) {
	// don't swallow errors if either the object or property is nullish,
	// respect optional chaining as provided
	if (!chain_obj && !chain_prop) {
		return obj[property].call(obj, ...args);
	} else if (chain_obj && chain_prop) {
		return obj?.[property]?.call(obj, ...args);
	} else if (chain_obj) {
		return obj?.[property].call(obj, ...args);
	} else if (chain_prop) {
		return obj[property]?.call(obj, ...args);
	}
}

/**
 * @param {any} obj
 * @param {string | number | symbol} property
 * @param {boolean} [chain=false]
 * @returns {any}
 */
export function get_property(obj, property, chain = false) {
	if (chain && obj == null) {
		return undefined;
	}
	var tracked = obj[property];
	if (tracked == null) {
		return tracked;
	}
	return get(tracked);
}

/**
 * @param {any} obj
 * @param {string | number | symbol} property
 * @param {any} value
 * @returns {void}
 */
export function set_property(obj, property, value) {
	var tracked = obj[property];
	set(tracked, value);
}

/**
 * @param {Tracked} tracked
 * @param {number} [d]
 * @returns {number}
 */
export function update(tracked, d = 1) {
	var value = get(tracked);
	var result = d === 1 ? value++ : value--;
	set(tracked, value);
	return result;
}

/**
 * @param {Tracked} tracked
 * @returns {void}
 */
export function increment(tracked) {
	set(tracked, tracked.__v + 1);
}

/**
 * @param {Tracked} tracked
 * @returns {void}
 */
export function decrement(tracked) {
	set(tracked, tracked.__v - 1);
}

/**
 * @param {Tracked} tracked
 * @param {number} [d]
 * @returns {number}
 */
export function update_pre(tracked, d = 1) {
	var value = get(tracked);
	var new_value = d === 1 ? ++value : --value;
	set(tracked, new_value);
	return new_value;
}

/**
 * @param {any} obj
 * @param {string | number | symbol} property
 * @param {number} [d=1]
 * @returns {number}
 */
export function update_property(obj, property, d = 1) {
	var tracked = obj[property];
	var value = get(tracked);
	var new_value = d === 1 ? value++ : value--;
	set(tracked, value);
	return new_value;
}

/**
 * @param {any} obj
 * @param {string | number | symbol} property
 * @param {number} [d=1]
 * @returns {number}
 */
export function update_pre_property(obj, property, d = 1) {
	var tracked = obj[property];
	var value = get(tracked);
	var new_value = d === 1 ? ++value : --value;
	set(tracked, new_value);
	return new_value;
}

/**
 * @template T
 * @param {Block} block
 * @param {() => T} fn
 * @returns {T}
 */
export function with_scope(block, fn) {
	var previous_scope = active_scope;
	try {
		active_scope = block;
		return fn();
	} finally {
		active_scope = previous_scope;
	}
}

/**
 * @returns {Block | null}
 */
export function scope() {
	return active_scope || active_block;
}

/**
 * @param {string} [err]
 * @returns {Block | never}
 */
export function safe_scope(err) {
	if (active_scope === null) {
		scope_orphan(err);
	}

	return /** @type {Block} */ (active_scope);
}

export function create_component_ctx() {
	return {
		b: active_block,
		c: active_component === null ? null : active_component.c,
		e: null,
		m: false,
		p: active_component,
	};
}

/**
 * @returns {void}
 */
export function push_component() {
	active_component = create_component_ctx();
}

/**
 * @returns {void}
 */
export function pop_component() {
	var component = /** @type {Component} */ (active_component);
	component.m = true;
	if (component.e !== null) {
		create_deferred_effects(component.e);
	}
	active_component = component.p;
}

/**
 * Renders a component: `fn(props)` runs under a fresh component context (the
 * owner of the component's context values and deferred effects), and the
 * element it returns renders before `anchor`.
 * @param {Function} fn
 * @param {Node | AppendIntoAnchor} anchor
 * @param {Record<string, any>} props
 * @param {Block | null} [block=active_block]
 * @returns {void}
 */
export function render_component(fn, anchor, props, block = active_block) {
	// An optional component (an undefined prop, an import that resolved to
	// nothing) renders nothing; any other non-function is a programming error.
	if (fn == null) {
		return;
	}
	if (typeof fn !== 'function') {
		throw_invalid_component_type(fn);
	}

	// push_component, inline: one component per call is the common case.
	var parent = active_component;
	/** @type {Component} */
	var component = (active_component = {
		b: active_block,
		c: parent === null ? null : parent.c,
		e: null,
		m: false,
		p: parent,
	});

	// A module-level component carries its render function under a symbol
	// (see the compiler): calling it directly skips the element the component
	// would return only to be rendered here.
	var render = /** @type {any} */ (fn)[RENDER_ENTRY];
	if (render !== undefined) {
		render(anchor, block, props);
	} else {
		render_value(fn(props), /** @type {ChildNode} */ (anchor), block);
	}

	// pop_component, inline.
	component.m = true;
	if (component.e !== null) {
		create_deferred_effects(component.e);
	}
	active_component = component.p;
}

/**
 * A reaction that stands in for a block whose creation is being decided (see
 * `probe_if`). Flagged destroyed so a write during the probe prunes
 * its subscription instead of scheduling it.
 * @type {any}
 */
var probe_reaction = { f: DESTROYED, d: null, blocks: null };

/**
 * Evaluates an if condition once, tracked, against a scratch reaction that is
 * never scheduled. When the evaluation recorded no dependency the condition
 * can never re-run (a block with no dependencies is never scheduled), so the
 * selected branch is rendered straight away, untracked, and true is returned:
 * the if needs no block. Otherwise the recorded links are unlinked again and
 * false is returned: the caller creates the block, whose own first run
 * evaluates the condition and subscribes it. The links are never handed to
 * the block, because a write during the block's first run (a child's setup,
 * a nested if) would prune links that still point at this destroyed reaction
 * before the block could take them over. A throwing condition (a pending
 * async read) unlinks what it recorded and rethrows.
 * @param {(x: any) => any} fn
 * @param {any} x
 * @param {Node | import('#client').AppendIntoAnchor} node
 * @returns {boolean} whether the branch was rendered without a block
 */
export function probe_if(fn, x, node) {
	var previous_reaction = active_reaction;
	var previous_tracking = tracking;
	var previous_dependency = active_dependency;
	active_reaction = probe_reaction;
	tracking = true;
	active_dependency = null;
	/** @type {Dependency | null} */
	var recorded;
	/** @type {any} */
	var branch;
	try {
		branch = fn(x);
		recorded = active_dependency;
	} catch (error) {
		unlink_dependencies(active_dependency);
		active_reaction = previous_reaction;
		tracking = previous_tracking;
		active_dependency = previous_dependency;
		throw error;
	}
	active_reaction = previous_reaction;
	active_dependency = previous_dependency;
	if (recorded !== null) {
		unlink_dependencies(recorded);
		tracking = previous_tracking;
		return false;
	}
	// The branch renders untracked, like a branch block would; `tracking` is
	// restored by the enclosing `run_block` if the branch throws.
	if (branch !== undefined) {
		tracking = false;
		branch(node, x);
	}
	tracking = previous_tracking;
	return true;
}

/**
 * @param {Dependency | null} dependency the head of a recorded chain
 */
function unlink_dependencies(dependency) {
	while (dependency !== null) {
		unlink_subscriber(dependency);
		dependency = dependency.n;
	}
}

/**
 * Calls `fn(arg)` with tracking off, so reads inside it subscribe nothing.
 * `tracking` is restored by the enclosing `run_block` if `fn` throws.
 * @param {(arg: any, context: any) => void} fn
 * @param {any} arg
 * @param {any} [context]
 */
export function run_untracked(fn, arg, context) {
	var previous_tracking = tracking;
	tracking = false;
	fn(arg, context);
	tracking = previous_tracking;
}

/**
 * Creates the effects a component registered while rendering, each under the
 * block and reaction that were active at its `effect()` call.
 * @param {any[]} effects flat triples: fn, block, reaction (see `user_effect`)
 */
function create_deferred_effects(effects) {
	// Creating an effect block only links and schedules it, so nothing here
	// can throw between saving and restoring the active block.
	var previous_block = active_block;
	var previous_reaction = active_reaction;
	var length = effects.length;
	for (var i = 0; i < length; i += 3) {
		active_block = /** @type {Block} */ (effects[i + 1]);
		active_reaction = /** @type {Block | Derived | null} */ (effects[i + 2]);
		// effect(), inline: one function less on every component's cold path
		// (kept identical by tests/utils/inline-drift.test.js).
		block(EFFECT_BLOCK, /** @type {Function} */ (effects[i]));
	}
	active_block = previous_block;
	active_reaction = previous_reaction;
}

/**
 * @template T
 * @param {() => T} fn
 * @param {keyof typeof NAMESPACE_URI} namespace
 * @returns {T}
 */
export function with_ns(namespace, fn) {
	var previous_namespace = active_namespace;
	active_namespace = namespace;
	try {
		return fn();
	} finally {
		active_namespace = previous_namespace;
	}
}

/**
 * Sets the active namespace and returns the previous one, for setup code that
 * switches namespace part-way through a render function (the children of a
 * `foreignObject`), where a `with_ns()` thunk would hide its declarations.
 * `run_block` restores the namespace after the render function either way.
 * @param {keyof typeof NAMESPACE_URI} namespace
 * @returns {keyof typeof NAMESPACE_URI}
 */
export function set_ns(namespace) {
	var previous_namespace = active_namespace;
	active_namespace = namespace;
	return previous_namespace;
}

/**
 * @returns {symbol}
 */
export function ref_prop() {
	return Symbol(REF_PROP);
}

/**
 * @param {() => any} get_ref_value
 * @param {(value: any) => void} [set_ref_value]
 * @returns {(node: any) => void | (() => void)}
 */
export function create_ref_prop(get_ref_value, set_ref_value) {
	return create_core_ref_prop(() => untrack(get_ref_value), set_ref_value);
}