ripple
Version:
Ripple is an elegant TypeScript UI framework
1,703 lines (1,521 loc) • 40.3 kB
JavaScript
/** @import { 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 {
destroy_block,
destroy_non_branch_children,
effect,
pause_block,
pre_effect,
} from './blocks.js';
import {
ASYNC_DERIVED_READ_THROWN,
BLOCK_HAS_RUN,
BRANCH_BLOCK,
DERIVED,
COMPUTED_PROPERTY,
CONTAINS_TEARDOWN,
CONTAINS_UPDATE,
DESTROYED,
EFFECT_BLOCK,
PAUSED,
PRE_EFFECT_BLOCK,
ROOT_BLOCK,
TRACKED,
UNINITIALIZED,
REF_PROP,
TRACKED_OBJECT,
DEFAULT_NAMESPACE,
TRACKED_UPDATED,
SUSPENSE_PENDING,
SUSPENSE_REJECTED,
TRY_BLOCK,
DIRECT_CHILD_BLOCK,
} from './constants.js';
import {
begin_boundary_request,
complete_boundary_request,
get_boundary_with_catch,
get_pending_boundary,
register_boundary_deferred,
register_boundary_paused_block,
replace_boundary_request,
} from './try.js';
import { is_ripple_object } from './utils.js';
import {
define_property,
get_descriptor,
get_own_property_symbols,
is_array,
object_keys,
} from '@tsrx/core/runtime/language-helpers';
import { get_async_track_result } from '../../../utils/async.js';
import { get_track_async_script_id } from '../../../utils/track-async-serialization.js';
import * as devalue from 'devalue';
import { hydrating, track_hash_reference } from './hydration.js';
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;
/** @type {Map<Tracked | Derived, any>} */
var old_values = new Map();
// 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;
/** @type {Block[]} */
let queued_root_blocks = [];
/** @type {(() => void)[]} */
let queued_microtasks = [];
/** @type {number} */
let flush_count = 0;
/** @type {(() => void)[]} */
var queued_post_block_flush = [];
/** @type {null | Dependency} */
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 {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;
fn.call(null);
} 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
*/
function update_tracked_value_clock(tracked, value) {
tracked.__v = value;
tracked.c = increment_clock();
}
/**
* @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();
computed.d = active_dependency;
return value;
} catch (error) {
computed.d = 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;
}
}
/**
* @param {unknown} error
* @param {Block} block
* @returns {BlockWithTryBoundaryAndCatch}
*/
export function handle_error(error, block) {
var boundary_with_catch = get_boundary_with_catch(block);
if (boundary_with_catch !== null) {
boundary_with_catch.s.c(error);
return boundary_with_catch;
}
throw error;
}
/**
* @param {Block} block
*/
export function run_block(block) {
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;
destroy_non_branch_children(block);
run_teardown(block);
tracking = (block.f & (ROOT_BLOCK | BRANCH_BLOCK)) === 0;
active_dependency = null;
var res = block.fn(block.s);
if (typeof res === 'function') {
block.t = res;
/** @type {Block | null} */
let current = block;
while (current !== null && (current.f & CONTAINS_TEARDOWN) === 0) {
current.f ^= CONTAINS_TEARDOWN;
current = current.p;
}
}
block.d = active_dependency;
} catch (error) {
var is_component_direct = false;
var is_try_fn_block = false;
block.d = active_dependency;
// When a derived read throws ASYNC_DERIVED_READ_THROWN, it means the
// derived is still SUSPENSE_PENDING. The dependency was already registered,
// so we swallow the throw and let the parent continue processing. When
// the derived settles, the block will be dirty and rerun automatically.
if (error !== ASYNC_DERIVED_READ_THROWN) {
handle_error(error, block);
} else if (
// pending async tracked was read outside allowed blocks
(is_component_direct = active_component?.b === block) ||
(is_try_fn_block =
block.p !== null && (block.p.f & TRY_BLOCK) !== 0 && (block.f & DIRECT_CHILD_BLOCK) !== 0)
) {
throw new Error(
`Reads on pending tracked values directly inside ${is_component_direct ? 'component' : 'try/pending/catch'} body are prohibited. Use trackPending() test or peek() for safe access or create another derived instead.`,
);
} else {
// pending async tracked was read and threw ASYNC_DERIVED_READ_THROWN
var boundary = get_pending_boundary(block);
if (boundary !== null) {
pause_block(block);
register_boundary_paused_block(boundary, block);
// Register deferred boundary completions for async tracked deps.
// This handles the case where a child boundary reads a tracked value
// whose resolution is managed by a different (parent) boundary.
var dep = block.d;
while (dep !== null) {
var dep_tracked = /** @type {Tracked} */ (dep.t);
if (
(dep_tracked.__v === SUSPENSE_PENDING || dep_tracked.__v === SUSPENSE_REJECTED) &&
(dep_tracked.f & TRACKED) !== 0
) {
var deferred_req = begin_boundary_request(boundary);
var entry = /** @type {DeferredTrackedEntry} */ ({ b: boundary, r: deferred_req });
if (dep_tracked.d === null) {
dep_tracked.d = [entry];
} else {
dep_tracked.d.push(entry);
}
}
dep = dep.n;
}
}
}
} finally {
active_block = previous_block;
active_reaction = previous_reaction;
tracking = previous_tracking;
active_dependency = previous_dependency;
active_component = previous_component;
}
}
var empty_get_set = { get: undefined, set: undefined };
/**
* Complete all deferred boundary requests registered on a tracked value.
* @param {Tracked} t
* @param {boolean} [show_resolved=true]
*/
function complete_deferred_boundaries(t, show_resolved = true) {
if (t.d !== null) {
for (var i = 0; i < t.d.length; i++) {
var entry = t.d[i];
complete_boundary_request(entry.b, entry.r, show_resolved);
}
t.d = null;
}
}
class TrackedValue {
/**
* @param {any} v
* @param {Block} block
* @param {{ get?: Function; set?: Function }} a
* @param {string} [hash]
*/
constructor(v, block, a, hash) {
/** @type {{ get?: Function; set?: Function }} */
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 {string | undefined} */
this.h = hash;
/** @type {any} */
this.__v = v;
}
/** @returns {any} */
get [0]() {
return get_tracked(this);
}
/** @param {any} v */
set [0](v) {
set(this, v);
}
/** @returns {Tracked} */
get [1]() {
return /** @type {Tracked} */ (this);
}
/** @returns {any} */
get value() {
return get_tracked(this);
}
/** @param {any} v */
set value(v) {
set(this, v);
}
/** @returns {2} */
get length() {
return 2;
}
/** @returns {Iterator<any | Tracked>} */
*[Symbol.iterator]() {
yield get_tracked(this);
yield this;
}
}
class DerivedValue {
/**
* @param {Function} fn
* @param {Block} block
* @param {{ get?: Function; set?: Function }} a
* @param {string} [hash]
*/
constructor(fn, block, a, hash) {
/** @type {{ get?: Function; set?: Function }} */
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 {any} */
this.__v = UNINITIALIZED;
}
/** @returns {any} */
get [0]() {
return get_derived(this);
}
/** @param {any} v */
set [0](v) {
set(this, v);
}
/** @returns {Derived} */
get [1]() {
return /** @type {Derived} */ (this);
}
/** @returns {any} */
get value() {
return get_derived(this);
}
/** @param {any} v */
set value(v) {
set(this, v);
}
/** @returns {2} */
get length() {
return 2;
}
/** @returns {Iterator<any | Derived>} */
*[Symbol.iterator]() {
yield get_derived(this);
yield this;
}
}
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} [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 (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} [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)
);
if (hydrating && hash !== undefined) {
track_hash_reference.set(hash, d);
}
return d;
}
/**
* @param {any} v
* @param {Block} b
* @param {string} [hash]
* @param {(value: any) => any} [get]
* @param {(next: any, prev: any) => any} [set]
* @returns {Tracked | Derived}
*/
export function track(v, b, hash, get, set) {
if (is_ripple_object(v)) {
return v;
}
if (b === null) {
throw new TypeError('track() requires a valid component context');
}
if (typeof v === 'function') {
return derived(v, b, hash, get, set);
}
return tracked(v, b, hash, get, set);
}
/**
* @param {any} fn
* @param {Block} b
* @param {string} hash - Unique hash for SSR serialization/hydration
* @returns {Tracked | void}
*/
export function track_async(fn, b, hash) {
if (is_ripple_object(fn)) {
return fn;
}
var target_block = b || active_block;
if (target_block === null) {
throw new TypeError('trackAsync() requires a valid component context');
}
if (typeof fn !== 'function') {
throw new TypeError(
'trackAsync() only accepts function arguments that return a promise or an object with a promise property',
);
}
// During hydration, attempt to read serialized data from SSR
var had_hydration_data = false;
var hydration_value;
/** @type {string[] | undefined} */
var hydration_deps;
if (hydrating) {
var script_id = get_track_async_script_id(hash);
var script_el = document.getElementById(script_id);
if (script_el) {
var envelope = JSON.parse(/** @type {string} */ (script_el.textContent));
script_el.remove();
if (envelope.ok) {
had_hydration_data = true;
hydration_value = devalue.parse(envelope.payload);
hydration_deps = envelope.deps;
} else {
// trigger the catch block
throw new Error(envelope.error?.message ?? 'Unknown server error');
}
}
}
var t = tracked(had_hydration_data ? hydration_value : SUSPENSE_PENDING, target_block, hash);
// Capture the call-site block for boundary lookups. target_block is the
// component's block (passed by compiler), but the actual try/pending/catch
// boundary is an ancestor of active_block (the block executing trackAsync).
var call_site_block = /** @type {Block} */ (active_block);
var version = 0;
/** @type {AbortController | null} */
var abort_controller = null;
var request_id = 0;
/** @type {Block | null} */
var boundary = null;
// TODO: decide if instead of insisting on pending, we create our own boundary
// we currently require a pending block upstream but we could also
// create a try/pending/catch boundary at mount and hydration like
// we do on the server so that there is always a boundary present.
// It can handle global pending when none were provided.
// Not sure about the catch boundary because if none were provided,
// the whole app for any error will be unmounted with the catch block rendered
// Find boundary from the call-site block.
boundary = get_pending_boundary(active_block);
if (boundary === null) {
throw new Error('Missing parent `try { ... } pending { ... }` statement');
}
// If we hydrated with resolved data, the SSR already completed this request.
// Otherwise mark a pending request on the boundary for the client-side run.
if (!had_hydration_data) {
request_id = begin_boundary_request(boundary);
}
pre_effect(() => {
if (had_hydration_data) {
// First run after hydration: skip fn() entirely (the SSR already
// produced the resolved value) and instead register the direct
// dependencies from the serialized deps list so future dep changes
// trigger a re-run via the normal async path.
had_hydration_data = false;
if (hydration_deps !== undefined) {
for (var i = 0; i < hydration_deps.length; i++) {
var dep_ref = track_hash_reference.get(hydration_deps[i]);
if (dep_ref !== undefined) {
get(dep_ref);
}
}
}
return;
}
var current_version = ++version;
// Abort previous in-flight request
if (abort_controller !== null && abort_controller.signal.aborted === false) {
abort_controller.abort(TRACKED_UPDATED);
}
abort_controller = null;
// Manage boundary request: replace if in-flight, or begin new if previous completed
if (request_id > 0 && boundary !== null) {
request_id = replace_boundary_request(boundary, request_id);
} else if (boundary !== null) {
request_id = begin_boundary_request(boundary);
}
// Set to pending before calling fn() in case it's sync.
if (t.__v !== SUSPENSE_PENDING) {
update_tracked_value_clock(t, SUSPENSE_PENDING);
schedule_update(t.b);
}
// Temporarily allow mutations so set() doesn't throw inside the pre-effect
var previous_is_mutating_allowed = is_mutating_allowed;
is_mutating_allowed = true;
var result;
try {
result = fn();
} catch (e) {
is_mutating_allowed = previous_is_mutating_allowed;
if (e === ASYNC_DERIVED_READ_THROWN) {
// A dependency is still pending or rejected (e.g. chained trackAsync).
// Check if any dependency is rejected — if so, propagate rejection.
var dep = active_dependency;
while (dep !== null) {
if (dep.t.__v === SUSPENSE_REJECTED) {
update_tracked_value_clock(t, SUSPENSE_REJECTED);
schedule_update(t.b);
complete_deferred_boundaries(t, false);
if (request_id > 0 && boundary !== null) {
complete_boundary_request(boundary, request_id, false);
request_id = 0;
}
return;
}
dep = dep.n;
}
// Dependencies are pending, not rejected — register deferred
// rejection so that if the boundary goes to catch mode, this
// tracked value is also set to REJECTED.
if (request_id > 0 && boundary !== null) {
register_boundary_deferred(boundary, request_id, () => {
update_tracked_value_clock(t, SUSPENSE_REJECTED);
});
}
return;
}
throw e;
}
is_mutating_allowed = previous_is_mutating_allowed;
// Check if the result is async
var previous_tracking = tracking;
tracking = false;
var async_result = get_async_track_result(result);
tracking = previous_tracking;
if (async_result === null) {
// Sync result
update_tracked_value_clock(t, result);
schedule_update(t.b);
if (request_id > 0 && boundary !== null) {
complete_boundary_request(boundary, request_id);
request_id = 0;
}
return;
}
// Capture per-invocation so async closures (rejection handler, teardown)
// have a stable reference. The shared abort_controller is only read
// synchronously at the top of the pre_effect to abort the previous request.
var current_abort_controller = async_result.abort_controller;
abort_controller = current_abort_controller;
async_result.promise.then(
(resolved) => {
if (current_version !== version) {
// stale
return;
}
update_tracked_value_clock(t, resolved);
schedule_update(t.b);
complete_deferred_boundaries(t);
if (request_id > 0 && boundary !== null) {
complete_boundary_request(boundary, request_id);
request_id = 0;
}
},
(error) => {
if (current_version !== version) return; // stale
var is_internal_abort =
error === TRACKED_UPDATED || current_abort_controller?.signal?.reason === TRACKED_UPDATED;
if (is_internal_abort) {
// Internal abort (superseded by a new request) — don't set rejected
if (request_id > 0 && boundary !== null) {
complete_boundary_request(boundary, request_id, false);
request_id = 0;
}
complete_deferred_boundaries(t, false);
return;
}
update_tracked_value_clock(t, SUSPENSE_REJECTED);
schedule_update(t.b);
complete_deferred_boundaries(t, false);
// Route error to catch boundary
var boundary_with_catch = get_boundary_with_catch(call_site_block);
if (boundary_with_catch !== null) {
boundary_with_catch.s.c(error);
}
if (request_id > 0 && boundary !== null) {
var should_show_resolved =
boundary_with_catch === boundary || boundary === null ? false : true;
complete_boundary_request(boundary, request_id, should_show_resolved);
request_id = 0;
}
},
);
return () => {
// Teardown: abort in-flight request when block is destroyed
if (current_abort_controller !== null && current_abort_controller.signal.aborted === false) {
current_abort_controller.abort(TRACKED_UPDATED);
}
};
});
return t;
}
/**
* @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.t = tracked;
existing.n = null;
return existing;
}
return {
c: tracked.c,
t: tracked,
n: null,
};
}
/**
* @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;
if ((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} root_block
*/
function flush_updates(root_block) {
/** @type {Block | null} */
var current = root_block;
var containing_update = null;
var pre_effects = [];
var other_blocks = [];
var effects = [];
var containing_update_head = null;
while (current !== null) {
var flags = current.f;
if ((flags & CONTAINS_UPDATE) !== 0) {
current.f ^= CONTAINS_UPDATE;
containing_update_head = { v: containing_update, n: containing_update_head };
containing_update = current;
}
if ((flags & PAUSED) === 0 && containing_update !== null) {
if ((flags & PRE_EFFECT_BLOCK) !== 0) {
pre_effects.push(current);
} else if ((flags & EFFECT_BLOCK) !== 0) {
effects.push(current);
} else {
other_blocks.push(current);
}
/** @type {Block | null} */
var child = current.first;
if (child !== null) {
current = child;
continue;
}
}
/** @type {Block | null} */
var parent = current.p;
current = current.next;
while (current === null && parent !== null) {
if (parent === containing_update) {
var head = /** @type {{ v: Block | null, n: any }} */ (containing_update_head);
containing_update = head.v;
containing_update_head = head.n;
}
current = parent.next;
parent = parent.p;
}
}
var arr_length = 0;
// Phase 1: pre-effects (e.g. update tracked values before render blocks read them)
arr_length = pre_effects.length;
for (var i = 0; i < arr_length; i++) {
var block = pre_effects[i];
try {
if ((block.f & (PAUSED | DESTROYED)) === 0 && is_block_dirty(block)) {
run_block(block);
}
} catch (error) {
handle_error(error, block);
}
}
// Phase 2: all other blocks except effects
arr_length = other_blocks.length;
for (var i = 0; i < arr_length; i++) {
var block = other_blocks[i];
try {
if ((block.f & (PAUSED | DESTROYED)) === 0 && is_block_dirty(block)) {
run_block(block);
}
} catch (error) {
handle_error(error, block);
}
}
// Phase 3: effects
arr_length = effects.length;
for (var i = 0; i < arr_length; i++) {
var block = effects[i];
try {
if ((block.f & (PAUSED | DESTROYED)) === 0 && is_block_dirty(block)) {
run_block(block);
}
} catch (error) {
handle_error(error, block);
}
}
}
/**
* @param {Block[]} root_blocks
*/
function flush_queued_root_blocks(root_blocks) {
for (let i = 0; i < root_blocks.length; i++) {
flush_updates(root_blocks[i]);
}
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]();
}
}
}
/**
* @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) {
throw new Error(
'Maximum update depth exceeded. This typically indicates that an effect reads and writes the same piece of state.',
);
}
var previous_queued_root_blocks = queued_root_blocks;
queued_root_blocks = [];
flush_queued_root_blocks(previous_queued_root_blocks);
if (!is_micro_task_queued) {
flush_count = 0;
}
old_values.clear();
}
/**
* @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 (scheduler_mode === FLUSH_MICROTASK) {
queue_microtask();
}
let current = block;
while (current !== null) {
var flags = current.f;
if ((flags & CONTAINS_UPDATE) !== 0) return;
current.f ^= CONTAINS_UPDATE;
if ((flags & ROOT_BLOCK) !== 0) {
break;
}
current = /** @type {Block} */ (current.p);
}
queued_root_blocks.push(current);
}
/**
* @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(tracked);
}
if (value === SUSPENSE_PENDING || value === SUSPENSE_REJECTED) {
throw ASYNC_DERIVED_READ_THROWN;
}
if (teardown && old_values.has(tracked)) {
value = old_values.get(tracked);
}
var get = tracked.a.get;
if (get !== undefined) {
value = trigger_track_get(get, value);
}
return value;
}
/**
* Exposed version of `set` to avoid internal bugs
* since block is required on the internal `set`
* @param {Derived | Tracked} tracked
* @param {any} value
*/
export function public_set(tracked, value) {
set(tracked, value);
}
/**
* @param {Derived | Tracked} tracked
* @param {any} value
*/
export function set(tracked, value) {
if (!is_mutating_allowed) {
throw new Error(
'Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation',
);
}
var old_value = tracked.__v;
if (value !== old_value) {
var tracked_block = tracked.b;
if ((tracked_block.f & CONTAINS_TEARDOWN) !== 0) {
if (teardown) {
old_values.set(tracked, value);
} else {
old_values.set(tracked, old_value);
}
}
let set = tracked.a.set;
if (set !== undefined) {
value = untrack(() => set(value, old_value));
}
tracked.__v = value;
tracked.c = increment_clock();
schedule_update(tracked_block);
}
}
/**
* @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_queued_root_blocks = queued_root_blocks;
try {
/** @type {Block[]} */
var root_blocks = [];
scheduler_mode = FLUSH_SYNC;
queued_root_blocks = root_blocks;
is_micro_task_queued = false;
flush_queued_root_blocks(previous_queued_root_blocks);
var result = fn?.();
if (queued_root_blocks.length > 0 || root_blocks.length > 0) {
flush_sync();
}
flush_count = 0;
return /** @type {T} */ (result);
} finally {
scheduler_mode = previous_scheduler_mode;
queued_root_blocks = previous_queued_root_blocks;
}
}
/**
* @param {() => Object} fn
* @returns {Object}
*/
export function spread_props(fn) {
return proxy_props(fn);
}
/**
* @param {() => Object} fn
* @returns {Object}
*/
export function proxy_props(fn) {
const memo = derived(fn, /** @type {Block} */ (active_block));
return new Proxy(
{},
{
get(_, property) {
/** @type {Record<string | symbol, any> | Record<string | symbol, any>[]} */
var obj = get_derived(memo);
// Handle array of objects/spreads (for multiple props)
if (is_array(obj)) {
// Search in reverse order (right-to-left) since later props override earlier ones
/** @type {Record<string | symbol, any>} */
var item;
for (var i = obj.length - 1; i >= 0; i--) {
item = obj[i];
if (property in item) {
return item[property];
}
}
return undefined;
}
// Single object case
return obj[property];
},
has(_, property) {
if (property === TRACKED_OBJECT) {
return true;
}
/** @type {Record<string | symbol, any> | Record<string | symbol, any>[]} */
var obj = get_derived(memo);
// Handle array of objects/spreads
if (is_array(obj)) {
for (var i = obj.length - 1; i >= 0; i--) {
if (property in obj[i]) {
return true;
}
}
return false;
}
return property in obj;
},
getOwnPropertyDescriptor(_, key) {
/** @type {Record<string | symbol, any> | Record<string | symbol, any>[]} */
var obj = get_derived(memo);
// Handle array of objects/spreads
if (is_array(obj)) {
/** @type {Record<string | symbol, any>} */
var item;
for (var i = obj.length - 1; i >= 0; i--) {
item = obj[i];
if (key in item) {
return get_descriptor(item, key);
}
}
return undefined;
}
if (key in obj) {
return get_descriptor(obj, key);
}
},
ownKeys() {
/** @type {Record<string | symbol, any> | Record<string | symbol, any>[]} */
var obj = get_derived(memo);
/** @type {Record<string | symbol, 1>} */
var done = {};
/** @type {(string | symbol)[]} */
var keys = [];
// Handle array of objects/spreads
if (is_array(obj)) {
// Collect all keys from all objects, order doesn't matter
/** @type {Record<string | symbol, any>} */
var item;
for (var i = 0; i < obj.length; i++) {
item = obj[i];
for (const key of Reflect.ownKeys(item)) {
if (done[key]) {
continue;
}
done[key] = 1;
keys.push(key);
}
}
return keys;
}
return Reflect.ownKeys(obj);
},
},
);
}
/**
* @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 = 'Cannot access outside of a component context') {
if (active_scope === null) {
throw new Error(err);
}
return /** @type {Block} */ (active_scope);
}
export function create_component_ctx() {
return {
b: active_block,
c: null,
e: null,
m: false,
p: active_component,
};
}
/**
* @returns {void}
*/
export function push_component() {
var component = create_component_ctx();
active_component = component;
}
/**
* @returns {void}
*/
export function pop_component() {
var component = /** @type {Component} */ (active_component);
component.m = true;
var effects = component.e;
if (effects !== null) {
var length = effects.length;
for (var i = 0; i < length; i++) {
var { b: block, fn, r: reaction } = effects[i];
var previous_block = active_block;
var previous_reaction = active_reaction;
try {
active_block = block;
active_reaction = reaction;
effect(fn);
} finally {
active_block = previous_block;
active_reaction = previous_reaction;
}
}
}
active_component = component.p;
}
/**
* @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;
}
}
/**
* @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);
}
/**
* @template T
* @param {T | undefined} value
* @param {T} fallback
* @returns {T}
*/
export function fallback(value, fallback) {
return value === undefined ? fallback : value;
}
/**
* @param {Record<string | symbol, unknown>} obj
* @param {string[]} exclude_keys
* @returns {Record<string | symbol, unknown>}
*/
export function exclude_from_object(obj, exclude_keys) {
var keys = object_keys(obj);
/** @type {Record<string | symbol, unknown>} */
var new_obj = {};
for (const key of keys) {
if (!exclude_keys.includes(key)) {
new_obj[key] = obj[key];
}
}
for (const symbol of get_own_property_symbols(obj)) {
var ref_fn = obj[symbol];
if (symbol.description === REF_PROP) {
new_obj[symbol] = ref_fn;
}
}
return new_obj;
}