@tinymce/tinymce-svelte
Version:
TinyMCE Svelte Component
1,952 lines (1,601 loc) • 131 kB
JavaScript
// generated during release, do not modify
const PUBLIC_VERSION = '5';
if (typeof window !== 'undefined') {
// @ts-expect-error
((window.__svelte ??= {}).v ??= new Set()).add(PUBLIC_VERSION);
}
const PROPS_IS_IMMUTABLE = 1;
const PROPS_IS_UPDATED = 1 << 2;
const PROPS_IS_BINDABLE = 1 << 3;
const PROPS_IS_LAZY_INITIAL = 1 << 4;
const TEMPLATE_FRAGMENT = 1;
const TEMPLATE_USE_IMPORT_NODE = 1 << 1;
const UNINITIALIZED = Symbol();
// Dev-time component properties
const FILENAME = Symbol('filename');
const NAMESPACE_HTML = 'http://www.w3.org/1999/xhtml';
const node_env = globalThis.process?.env?.NODE_ENV;
var DEV = node_env && !node_env.toLowerCase().startsWith('prod');
// Store the references to globals in case someone tries to monkey patch these, causing the below
// to de-opt (this occurs often when using popular extensions).
var is_array = Array.isArray;
var index_of = Array.prototype.indexOf;
var includes = Array.prototype.includes;
var define_property = Object.defineProperty;
var get_descriptor = Object.getOwnPropertyDescriptor;
var get_descriptors = Object.getOwnPropertyDescriptors;
var object_prototype = Object.prototype;
var array_prototype = Array.prototype;
var get_prototype_of = Object.getPrototypeOf;
const noop = () => {};
/** @param {Array<() => void>} arr */
function run_all(arr) {
for (var i = 0; i < arr.length; i++) {
arr[i]();
}
}
/**
* TODO replace with Promise.withResolvers once supported widely enough
* @template [T=void]
*/
function deferred() {
/** @type {(value: T) => void} */
var resolve;
/** @type {(reason: any) => void} */
var reject;
/** @type {Promise<T>} */
var promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
// @ts-expect-error
return { promise, resolve, reject };
}
// General flags
const DERIVED = 1 << 1;
const EFFECT = 1 << 2;
const RENDER_EFFECT = 1 << 3;
/**
* An effect that does not destroy its child effects when it reruns.
* Runs as part of render effects, i.e. not eagerly as part of tree traversal or effect flushing.
*/
const MANAGED_EFFECT = 1 << 24;
/**
* An effect that does not destroy its child effects when it reruns (like MANAGED_EFFECT).
* Runs eagerly as part of tree traversal or effect flushing.
*/
const BLOCK_EFFECT = 1 << 4;
const BRANCH_EFFECT = 1 << 5;
const ROOT_EFFECT = 1 << 6;
const BOUNDARY_EFFECT = 1 << 7;
/**
* Indicates that a reaction is connected to an effect root — either it is an effect,
* or it is a derived that is depended on by at least one effect. If a derived has
* no dependents, we can disconnect it from the graph, allowing it to either be
* GC'd or reconnected later if an effect comes to depend on it again
*/
const CONNECTED = 1 << 9;
const CLEAN = 1 << 10;
const DIRTY = 1 << 11;
const MAYBE_DIRTY = 1 << 12;
const INERT = 1 << 13;
const DESTROYED = 1 << 14;
/** Set once a reaction has run for the first time */
const REACTION_RAN = 1 << 15;
// Flags exclusive to effects
/**
* 'Transparent' effects do not create a transition boundary.
* This is on a block effect 99% of the time but may also be on a branch effect if its parent block effect was pruned
*/
const EFFECT_TRANSPARENT = 1 << 16;
const EAGER_EFFECT = 1 << 17;
const HEAD_EFFECT = 1 << 18;
const EFFECT_PRESERVED = 1 << 19;
const USER_EFFECT = 1 << 20;
// Flags exclusive to deriveds
/**
* Tells that we marked this derived and its reactions as visited during the "mark as (maybe) dirty"-phase.
* Will be lifted during execution of the derived and during checking its dirty state (both are necessary
* because a derived might be checked but not executed).
*/
const WAS_MARKED = 1 << 16;
// Flags used for async
const REACTION_IS_UPDATING = 1 << 21;
const ASYNC = 1 << 22;
const ERROR_VALUE = 1 << 23;
const STATE_SYMBOL = Symbol('$state');
const LEGACY_PROPS = Symbol('legacy props');
const LOADING_ATTR_SYMBOL = Symbol('');
const PROXY_PATH_SYMBOL = Symbol('proxy path');
/** allow users to ignore aborted signal errors if `reason.name === 'StaleReactionError` */
const STALE_REACTION = new (class StaleReactionError extends Error {
name = 'StaleReactionError';
message = 'The reaction that called `getAbortSignal()` was re-run or destroyed';
})();
/* This file is generated by scripts/process-messages/index.js. Do not edit! */
/**
* Cannot create a `$derived(...)` with an `await` expression outside of an effect tree
* @returns {never}
*/
function async_derived_orphan() {
if (DEV) {
const error = new Error(`async_derived_orphan\nCannot create a \`$derived(...)\` with an \`await\` expression outside of an effect tree\nhttps://svelte.dev/e/async_derived_orphan`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/async_derived_orphan`);
}
}
/**
* A derived value cannot reference itself recursively
* @returns {never}
*/
function derived_references_self() {
if (DEV) {
const error = new Error(`derived_references_self\nA derived value cannot reference itself recursively\nhttps://svelte.dev/e/derived_references_self`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/derived_references_self`);
}
}
/**
* `%rune%` cannot be used inside an effect cleanup function
* @param {string} rune
* @returns {never}
*/
function effect_in_teardown(rune) {
if (DEV) {
const error = new Error(`effect_in_teardown\n\`${rune}\` cannot be used inside an effect cleanup function\nhttps://svelte.dev/e/effect_in_teardown`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/effect_in_teardown`);
}
}
/**
* Effect cannot be created inside a `$derived` value that was not itself created inside an effect
* @returns {never}
*/
function effect_in_unowned_derived() {
if (DEV) {
const error = new Error(`effect_in_unowned_derived\nEffect cannot be created inside a \`$derived\` value that was not itself created inside an effect\nhttps://svelte.dev/e/effect_in_unowned_derived`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/effect_in_unowned_derived`);
}
}
/**
* `%rune%` can only be used inside an effect (e.g. during component initialisation)
* @param {string} rune
* @returns {never}
*/
function effect_orphan(rune) {
if (DEV) {
const error = new Error(`effect_orphan\n\`${rune}\` can only be used inside an effect (e.g. during component initialisation)\nhttps://svelte.dev/e/effect_orphan`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/effect_orphan`);
}
}
/**
* Maximum update depth exceeded. This typically indicates that an effect reads and writes the same piece of state
* @returns {never}
*/
function effect_update_depth_exceeded() {
if (DEV) {
const error = new Error(`effect_update_depth_exceeded\nMaximum update depth exceeded. This typically indicates that an effect reads and writes the same piece of state\nhttps://svelte.dev/e/effect_update_depth_exceeded`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/effect_update_depth_exceeded`);
}
}
/**
* Cannot do `bind:%key%={undefined}` when `%key%` has a fallback value
* @param {string} key
* @returns {never}
*/
function props_invalid_value(key) {
if (DEV) {
const error = new Error(`props_invalid_value\nCannot do \`bind:${key}={undefined}\` when \`${key}\` has a fallback value\nhttps://svelte.dev/e/props_invalid_value`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/props_invalid_value`);
}
}
/**
* Rest element properties of `$props()` such as `%property%` are readonly
* @param {string} property
* @returns {never}
*/
function props_rest_readonly(property) {
if (DEV) {
const error = new Error(`props_rest_readonly\nRest element properties of \`$props()\` such as \`${property}\` are readonly\nhttps://svelte.dev/e/props_rest_readonly`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/props_rest_readonly`);
}
}
/**
* The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files
* @param {string} rune
* @returns {never}
*/
function rune_outside_svelte(rune) {
if (DEV) {
const error = new Error(`rune_outside_svelte\nThe \`${rune}\` rune is only available inside \`.svelte\` and \`.svelte.js/ts\` files\nhttps://svelte.dev/e/rune_outside_svelte`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/rune_outside_svelte`);
}
}
/**
* Property descriptors defined on `$state` objects must contain `value` and always be `enumerable`, `configurable` and `writable`.
* @returns {never}
*/
function state_descriptors_fixed() {
if (DEV) {
const error = new Error(`state_descriptors_fixed\nProperty descriptors defined on \`$state\` objects must contain \`value\` and always be \`enumerable\`, \`configurable\` and \`writable\`.\nhttps://svelte.dev/e/state_descriptors_fixed`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/state_descriptors_fixed`);
}
}
/**
* Cannot set prototype of `$state` object
* @returns {never}
*/
function state_prototype_fixed() {
if (DEV) {
const error = new Error(`state_prototype_fixed\nCannot set prototype of \`$state\` object\nhttps://svelte.dev/e/state_prototype_fixed`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/state_prototype_fixed`);
}
}
/**
* Updating state inside `$derived(...)`, `$inspect(...)` or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
* @returns {never}
*/
function state_unsafe_mutation() {
if (DEV) {
const error = new Error(`state_unsafe_mutation\nUpdating state inside \`$derived(...)\`, \`$inspect(...)\` or a template expression is forbidden. If the value should not be reactive, declare it without \`$state\`\nhttps://svelte.dev/e/state_unsafe_mutation`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/state_unsafe_mutation`);
}
}
/* This file is generated by scripts/process-messages/index.js. Do not edit! */
var bold$1 = 'font-weight: bold';
var normal$1 = 'font-weight: normal';
/**
* An async derived, `%name%` (%location%) was not read immediately after it resolved. This often indicates an unnecessary waterfall, which can slow down your app
* @param {string} name
* @param {string} location
*/
function await_waterfall(name, location) {
if (DEV) {
console.warn(`%c[svelte] await_waterfall\n%cAn async derived, \`${name}\` (${location}) was not read immediately after it resolved. This often indicates an unnecessary waterfall, which can slow down your app\nhttps://svelte.dev/e/await_waterfall`, bold$1, normal$1);
} else {
console.warn(`https://svelte.dev/e/await_waterfall`);
}
}
/** @import { Equals } from '#client' */
/** @type {Equals} */
function equals(value) {
return value === this.v;
}
/**
* @param {unknown} a
* @param {unknown} b
* @returns {boolean}
*/
function safe_not_equal(a, b) {
return a != a
? b == b
: a !== b || (a !== null && typeof a === 'object') || typeof a === 'function';
}
/** @type {Equals} */
function safe_equals(value) {
return !safe_not_equal(value, this.v);
}
/** True if experimental.async=true */
let async_mode_flag = false;
/** True if we're not certain that we only have Svelte 5 code in the compilation */
let legacy_mode_flag = false;
/** True if $inspect.trace is used */
let tracing_mode_flag = false;
/* This file is generated by scripts/process-messages/index.js. Do not edit! */
var bold = 'font-weight: bold';
var normal = 'font-weight: normal';
/**
* The following properties cannot be cloned with `$state.snapshot` — the return value contains the originals:
*
* %properties%
* @param {string | undefined | null} [properties]
*/
function state_snapshot_uncloneable(properties) {
if (DEV) {
console.warn(
`%c[svelte] state_snapshot_uncloneable\n%c${properties
? `The following properties cannot be cloned with \`$state.snapshot\` — the return value contains the originals:
${properties}`
: 'Value cannot be cloned with `$state.snapshot` — the original value was returned'}\nhttps://svelte.dev/e/state_snapshot_uncloneable`,
bold,
normal
);
} else {
console.warn(`https://svelte.dev/e/state_snapshot_uncloneable`);
}
}
/** @import { Snapshot } from './types' */
/**
* In dev, we keep track of which properties could not be cloned. In prod
* we don't bother, but we keep a dummy array around so that the
* signature stays the same
* @type {string[]}
*/
const empty = [];
/**
* @template T
* @param {T} value
* @param {boolean} [skip_warning]
* @param {boolean} [no_tojson]
* @returns {Snapshot<T>}
*/
function snapshot(value, skip_warning = false, no_tojson = false) {
if (DEV && !skip_warning) {
/** @type {string[]} */
const paths = [];
const copy = clone(value, new Map(), '', paths, null, no_tojson);
if (paths.length === 1 && paths[0] === '') {
// value could not be cloned
state_snapshot_uncloneable();
} else if (paths.length > 0) {
// some properties could not be cloned
const slice = paths.length > 10 ? paths.slice(0, 7) : paths.slice(0, 10);
const excess = paths.length - slice.length;
let uncloned = slice.map((path) => `- <value>${path}`).join('\n');
if (excess > 0) uncloned += `\n- ...and ${excess} more`;
state_snapshot_uncloneable(uncloned);
}
return copy;
}
return clone(value, new Map(), '', empty, null, no_tojson);
}
/**
* @template T
* @param {T} value
* @param {Map<T, Snapshot<T>>} cloned
* @param {string} path
* @param {string[]} paths
* @param {null | T} [original] The original value, if `value` was produced from a `toJSON` call
* @param {boolean} [no_tojson]
* @returns {Snapshot<T>}
*/
function clone(value, cloned, path, paths, original = null, no_tojson = false) {
if (typeof value === 'object' && value !== null) {
var unwrapped = cloned.get(value);
if (unwrapped !== undefined) return unwrapped;
if (value instanceof Map) return /** @type {Snapshot<T>} */ (new Map(value));
if (value instanceof Set) return /** @type {Snapshot<T>} */ (new Set(value));
if (is_array(value)) {
var copy = /** @type {Snapshot<any>} */ (Array(value.length));
cloned.set(value, copy);
if (original !== null) {
cloned.set(original, copy);
}
for (var i = 0; i < value.length; i += 1) {
var element = value[i];
if (i in value) {
copy[i] = clone(element, cloned, DEV ? `${path}[${i}]` : path, paths, null, no_tojson);
}
}
return copy;
}
if (get_prototype_of(value) === object_prototype) {
/** @type {Snapshot<any>} */
copy = {};
cloned.set(value, copy);
if (original !== null) {
cloned.set(original, copy);
}
for (var key of Object.keys(value)) {
copy[key] = clone(
// @ts-expect-error
value[key],
cloned,
DEV ? `${path}.${key}` : path,
paths,
null,
no_tojson
);
}
return copy;
}
if (value instanceof Date) {
return /** @type {Snapshot<T>} */ (structuredClone(value));
}
if (typeof (/** @type {T & { toJSON?: any } } */ (value).toJSON) === 'function' && !no_tojson) {
return clone(
/** @type {T & { toJSON(): any } } */ (value).toJSON(),
cloned,
DEV ? `${path}.toJSON()` : path,
paths,
// Associate the instance with the toJSON clone
value
);
}
}
if (value instanceof EventTarget) {
// can't be cloned
return /** @type {Snapshot<T>} */ (value);
}
try {
return /** @type {Snapshot<T>} */ (structuredClone(value));
} catch (e) {
if (DEV) {
paths.push(path);
}
return /** @type {Snapshot<T>} */ (value);
}
}
/** @import { Derived, Reaction, Value } from '#client' */
/**
* @param {Value} source
* @param {string} label
*/
function tag(source, label) {
source.label = label;
tag_proxy(source.v, label);
return source;
}
/**
* @param {unknown} value
* @param {string} label
*/
function tag_proxy(value, label) {
// @ts-expect-error
value?.[PROXY_PATH_SYMBOL]?.(label);
return value;
}
/**
* @param {string} label
* @returns {Error & { stack: string } | null}
*/
function get_error(label) {
const error = new Error();
const stack = get_stack();
if (stack.length === 0) {
return null;
}
stack.unshift('\n');
define_property(error, 'stack', {
value: stack.join('\n')
});
define_property(error, 'name', {
value: label
});
return /** @type {Error & { stack: string }} */ (error);
}
/**
* @returns {string[]}
*/
function get_stack() {
// @ts-ignore - doesn't exist everywhere
const limit = Error.stackTraceLimit;
// @ts-ignore - doesn't exist everywhere
Error.stackTraceLimit = Infinity;
const stack = new Error().stack;
// @ts-ignore - doesn't exist everywhere
Error.stackTraceLimit = limit;
if (!stack) return [];
const lines = stack.split('\n');
const new_lines = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const posixified = line.replaceAll('\\', '/');
if (line.trim() === 'Error') {
continue;
}
if (line.includes('validate_each_keys')) {
return [];
}
if (posixified.includes('svelte/src/internal') || posixified.includes('node_modules/.vite')) {
continue;
}
new_lines.push(line);
}
return new_lines;
}
/** @import { ComponentContext, DevStackEntry, Effect } from '#client' */
/** @type {ComponentContext | null} */
let component_context = null;
/** @param {ComponentContext | null} context */
function set_component_context(context) {
component_context = context;
}
/** @type {DevStackEntry | null} */
let dev_stack = null;
/** @param {DevStackEntry | null} stack */
function set_dev_stack(stack) {
dev_stack = stack;
}
/**
* The current component function. Different from current component context:
* ```html
* <!-- App.svelte -->
* <Foo>
* <Bar /> <!-- context == Foo.svelte, function == App.svelte -->
* </Foo>
* ```
* @type {ComponentContext['function']}
*/
let dev_current_component_function = null;
/** @param {ComponentContext['function']} fn */
function set_dev_current_component_function(fn) {
dev_current_component_function = fn;
}
/**
* @param {Record<string, unknown>} props
* @param {any} runes
* @param {Function} [fn]
* @returns {void}
*/
function push(props, runes = false, fn) {
component_context = {
p: component_context,
i: false,
c: null,
e: null,
s: props,
x: null,
l: null
};
if (DEV) {
// component function
component_context.function = fn;
dev_current_component_function = fn;
}
}
/**
* @template {Record<string, any>} T
* @param {T} [component]
* @returns {T}
*/
function pop(component) {
var context = /** @type {ComponentContext} */ (component_context);
var effects = context.e;
if (effects !== null) {
context.e = null;
for (var fn of effects) {
create_user_effect(fn);
}
}
if (component !== undefined) {
context.x = component;
}
context.i = true;
component_context = context.p;
if (DEV) {
dev_current_component_function = component_context?.function ?? null;
}
return component ?? /** @type {T} */ ({});
}
/** @returns {boolean} */
function is_runes() {
return !legacy_mode_flag ;
}
/** @type {Array<() => void>} */
let micro_tasks = [];
function run_micro_tasks() {
var tasks = micro_tasks;
micro_tasks = [];
run_all(tasks);
}
/**
* @param {() => void} fn
*/
function queue_micro_task(fn) {
if (micro_tasks.length === 0 && !is_flushing_sync) {
var tasks = micro_tasks;
queueMicrotask(() => {
// If this is false, a flushSync happened in the meantime. Do _not_ run new scheduled microtasks in that case
// as the ordering of microtasks would be broken at that point - consider this case:
// - queue_micro_task schedules microtask A to flush task X
// - synchronously after, flushSync runs, processing task X
// - synchronously after, some other microtask B is scheduled, but not through queue_micro_task but for example a Promise.resolve() in user code
// - synchronously after, queue_micro_task schedules microtask C to flush task Y
// - one tick later, microtask A now resolves, flushing task Y before microtask B, which is incorrect
// This if check prevents that race condition (that realistically will only happen in tests)
if (tasks === micro_tasks) run_micro_tasks();
});
}
micro_tasks.push(fn);
}
/** @import { Derived, Effect } from '#client' */
const adjustments = new WeakMap();
/**
* @param {unknown} error
*/
function handle_error(error) {
var effect = active_effect;
// for unowned deriveds, don't throw until we read the value
if (effect === null) {
/** @type {Derived} */ (active_reaction).f |= ERROR_VALUE;
return error;
}
if (DEV && error instanceof Error && !adjustments.has(error)) {
adjustments.set(error, get_adjustments(error, effect));
}
// if the error occurred while creating this subtree, we let it
// bubble up until it hits a boundary that can handle it, unless
// it's an $effect in which case it doesn't run immediately
if ((effect.f & REACTION_RAN) === 0 && (effect.f & EFFECT) === 0) {
if (DEV && !effect.parent && error instanceof Error) {
apply_adjustments(error);
}
throw error;
}
// otherwise we bubble up the effect tree ourselves
invoke_error_boundary(error, effect);
}
/**
* @param {unknown} error
* @param {Effect | null} effect
*/
function invoke_error_boundary(error, effect) {
while (effect !== null) {
if ((effect.f & BOUNDARY_EFFECT) !== 0) {
if ((effect.f & REACTION_RAN) === 0) {
// we are still creating the boundary effect
throw error;
}
try {
/** @type {Boundary} */ (effect.b).error(error);
return;
} catch (e) {
error = e;
}
}
effect = effect.parent;
}
if (DEV && error instanceof Error) {
apply_adjustments(error);
}
throw error;
}
/**
* Add useful information to the error message/stack in development
* @param {Error} error
* @param {Effect} effect
*/
function get_adjustments(error, effect) {
const message_descriptor = get_descriptor(error, 'message');
// if the message was already changed and it's not configurable we can't change it
// or it will throw a different error swallowing the original error
if (message_descriptor && !message_descriptor.configurable) return;
var indent = '\t';
var component_stack = `\n${indent}in ${effect.fn?.name || '<unknown>'}`;
var context = effect.ctx;
while (context !== null) {
component_stack += `\n${indent}in ${context.function?.[FILENAME].split('/').pop()}`;
context = context.p;
}
return {
message: error.message + `\n${component_stack}\n`,
stack: error.stack
?.split('\n')
.filter((line) => !line.includes('svelte/src/internal'))
.join('\n')
};
}
/**
* @param {Error} error
*/
function apply_adjustments(error) {
const adjusted = adjustments.get(error);
if (adjusted) {
define_property(error, 'message', {
value: adjusted.message
});
define_property(error, 'stack', {
value: adjusted.stack
});
}
}
/** @import { Derived, Signal } from '#client' */
const STATUS_MASK = ~(DIRTY | MAYBE_DIRTY | CLEAN);
/**
* @param {Signal} signal
* @param {number} status
*/
function set_signal_status(signal, status) {
signal.f = (signal.f & STATUS_MASK) | status;
}
/**
* Set a derived's status to CLEAN or MAYBE_DIRTY based on its connection state.
* @param {Derived} derived
*/
function update_derived_status(derived) {
// Only mark as MAYBE_DIRTY if disconnected and has dependencies.
if ((derived.f & CONNECTED) !== 0 || derived.deps === null) {
set_signal_status(derived, CLEAN);
} else {
set_signal_status(derived, MAYBE_DIRTY);
}
}
/** @import { Derived, Effect, Value } from '#client' */
/**
* @param {Value[] | null} deps
*/
function clear_marked(deps) {
if (deps === null) return;
for (const dep of deps) {
if ((dep.f & DERIVED) === 0 || (dep.f & WAS_MARKED) === 0) {
continue;
}
dep.f ^= WAS_MARKED;
clear_marked(/** @type {Derived} */ (dep).deps);
}
}
/**
* @param {Effect} effect
* @param {Set<Effect>} dirty_effects
* @param {Set<Effect>} maybe_dirty_effects
*/
function defer_effect(effect, dirty_effects, maybe_dirty_effects) {
if ((effect.f & DIRTY) !== 0) {
dirty_effects.add(effect);
} else if ((effect.f & MAYBE_DIRTY) !== 0) {
maybe_dirty_effects.add(effect);
}
// Since we're not executing these effects now, we need to clear any WAS_MARKED flags
// so that other batches can correctly reach these effects during their own traversal
clear_marked(effect.deps);
// mark as clean so they get scheduled if they depend on pending async state
set_signal_status(effect, CLEAN);
}
/** @import { StoreReferencesContainer } from '#client' */
/**
* We set this to `true` when updating a store so that we correctly
* schedule effects if the update takes place inside a `$:` effect
*/
let legacy_is_updating_store = false;
/**
* Whether or not the prop currently being read is a store binding, as in
* `<Child bind:x={$y} />`. If it is, we treat the prop as mutable even in
* runes mode, and skip `binding_property_non_reactive` validation
*/
let is_store_binding = false;
/**
* Returns a tuple that indicates whether `fn()` reads a prop that is a store binding.
* Used to prevent `binding_property_non_reactive` validation false positives and
* ensure that these props are treated as mutable even in runes mode
* @template T
* @param {() => T} fn
* @returns {[T, boolean]}
*/
function capture_store_binding(fn) {
var previous_is_store_binding = is_store_binding;
try {
is_store_binding = false;
return [fn(), is_store_binding];
} finally {
is_store_binding = previous_is_store_binding;
}
}
/** @import { Fork } from 'svelte' */
/** @type {Set<Batch>} */
const batches = new Set();
/** @type {Batch | null} */
let current_batch = null;
/**
* When time travelling (i.e. working in one batch, while other batches
* still have ongoing work), we ignore the real values of affected
* signals in favour of their values within the batch
* @type {Map<Value, any> | null}
*/
let batch_values = null;
/** @type {Effect | null} */
let last_scheduled_effect = null;
let is_flushing_sync = false;
let is_processing = false;
/**
* During traversal, this is an array. Newly created effects are (if not immediately
* executed) pushed to this array, rather than going through the scheduling
* rigamarole that would cause another turn of the flush loop.
* @type {Effect[] | null}
*/
let collected_effects = null;
/**
* An array of effects that are marked during traversal as a result of a `set`
* (not `internal_set`) call. These will be added to the next batch and
* trigger another `batch.process()`
* @type {Effect[] | null}
* @deprecated when we get rid of legacy mode and stores, we can get rid of this
*/
let legacy_updates = null;
var flush_count = 0;
var source_stacks = DEV ? new Set() : null;
let uid = 1;
class Batch {
// for debugging. TODO remove once async is stable
id = uid++;
/**
* The current values of any sources that are updated in this batch
* They keys of this map are identical to `this.#previous`
* @type {Map<Source, any>}
*/
current = new Map();
/**
* The values of any sources that are updated in this batch _before_ those updates took place.
* They keys of this map are identical to `this.#current`
* @type {Map<Source, any>}
*/
previous = new Map();
/**
* When the batch is committed (and the DOM is updated), we need to remove old branches
* and append new ones by calling the functions added inside (if/each/key/etc) blocks
* @type {Set<(batch: Batch) => void>}
*/
#commit_callbacks = new Set();
/**
* If a fork is discarded, we need to destroy any effects that are no longer needed
* @type {Set<(batch: Batch) => void>}
*/
#discard_callbacks = new Set();
/**
* The number of async effects that are currently in flight
*/
#pending = 0;
/**
* The number of async effects that are currently in flight, _not_ inside a pending boundary
*/
#blocking_pending = 0;
/**
* A deferred that resolves when the batch is committed, used with `settled()`
* TODO replace with Promise.withResolvers once supported widely enough
* @type {{ promise: Promise<void>, resolve: (value?: any) => void, reject: (reason: unknown) => void } | null}
*/
#deferred = null;
/**
* The root effects that need to be flushed
* @type {Effect[]}
*/
#roots = [];
/**
* Deferred effects (which run after async work has completed) that are DIRTY
* @type {Set<Effect>}
*/
#dirty_effects = new Set();
/**
* Deferred effects that are MAYBE_DIRTY
* @type {Set<Effect>}
*/
#maybe_dirty_effects = new Set();
/**
* A map of branches that still exist, but will be destroyed when this batch
* is committed — we skip over these during `process`.
* The value contains child effects that were dirty/maybe_dirty before being reset,
* so they can be rescheduled if the branch survives.
* @type {Map<Effect, { d: Effect[], m: Effect[] }>}
*/
#skipped_branches = new Map();
is_fork = false;
#decrement_queued = false;
#is_deferred() {
return this.is_fork || this.#blocking_pending > 0;
}
/**
* Add an effect to the #skipped_branches map and reset its children
* @param {Effect} effect
*/
skip_effect(effect) {
if (!this.#skipped_branches.has(effect)) {
this.#skipped_branches.set(effect, { d: [], m: [] });
}
}
/**
* Remove an effect from the #skipped_branches map and reschedule
* any tracked dirty/maybe_dirty child effects
* @param {Effect} effect
*/
unskip_effect(effect) {
var tracked = this.#skipped_branches.get(effect);
if (tracked) {
this.#skipped_branches.delete(effect);
for (var e of tracked.d) {
set_signal_status(e, DIRTY);
this.schedule(e);
}
for (e of tracked.m) {
set_signal_status(e, MAYBE_DIRTY);
this.schedule(e);
}
}
}
#process() {
if (flush_count++ > 1000) {
infinite_loop_guard();
}
const roots = this.#roots;
this.#roots = [];
this.apply();
/** @type {Effect[]} */
var effects = (collected_effects = []);
/** @type {Effect[]} */
var render_effects = [];
/**
* @type {Effect[]}
* @deprecated when we get rid of legacy mode and stores, we can get rid of this
*/
var updates = (legacy_updates = []);
for (const root of roots) {
this.#traverse(root, effects, render_effects);
}
// any writes should take effect in a subsequent batch
current_batch = null;
if (updates.length > 0) {
var batch = Batch.ensure();
for (const e of updates) {
batch.schedule(e);
}
}
collected_effects = null;
legacy_updates = null;
if (this.#is_deferred()) {
this.#defer_effects(render_effects);
this.#defer_effects(effects);
for (const [e, t] of this.#skipped_branches) {
reset_branch(e, t);
}
} else {
// clear effects. Those that are still needed will be rescheduled through unskipping the skipped branches.
this.#dirty_effects.clear();
this.#maybe_dirty_effects.clear();
// append/remove branches
for (const fn of this.#commit_callbacks) fn(this);
this.#commit_callbacks.clear();
flush_queued_effects(render_effects);
flush_queued_effects(effects);
if (this.#pending === 0) {
this.#commit();
}
this.#deferred?.resolve();
}
var next_batch = /** @type {Batch | null} */ (/** @type {unknown} */ (current_batch));
if (next_batch !== null) {
batches.add(next_batch);
if (DEV) {
for (const source of this.current.keys()) {
/** @type {Set<Source>} */ (source_stacks).add(source);
}
}
next_batch.#process();
}
}
/**
* Traverse the effect tree, executing effects or stashing
* them for later execution as appropriate
* @param {Effect} root
* @param {Effect[]} effects
* @param {Effect[]} render_effects
*/
#traverse(root, effects, render_effects) {
root.f ^= CLEAN;
var effect = root.first;
while (effect !== null) {
var flags = effect.f;
var is_branch = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) !== 0;
var is_skippable_branch = is_branch && (flags & CLEAN) !== 0;
var skip = is_skippable_branch || (flags & INERT) !== 0 || this.#skipped_branches.has(effect);
if (!skip && effect.fn !== null) {
if (is_branch) {
effect.f ^= CLEAN;
} else if ((flags & EFFECT) !== 0) {
effects.push(effect);
} else if (is_dirty(effect)) {
if ((flags & BLOCK_EFFECT) !== 0) this.#maybe_dirty_effects.add(effect);
update_effect(effect);
}
var child = effect.first;
if (child !== null) {
effect = child;
continue;
}
}
while (effect !== null) {
var next = effect.next;
if (next !== null) {
effect = next;
break;
}
effect = effect.parent;
}
}
}
/**
* @param {Effect[]} effects
*/
#defer_effects(effects) {
for (var i = 0; i < effects.length; i += 1) {
defer_effect(effects[i], this.#dirty_effects, this.#maybe_dirty_effects);
}
}
/**
* Associate a change to a given source with the current
* batch, noting its previous and current values
* @param {Source} source
* @param {any} value
*/
capture(source, value) {
if (value !== UNINITIALIZED && !this.previous.has(source)) {
this.previous.set(source, value);
}
// Don't save errors in `batch_values`, or they won't be thrown in `runtime.js#get`
if ((source.f & ERROR_VALUE) === 0) {
this.current.set(source, source.v);
batch_values?.set(source, source.v);
}
}
activate() {
current_batch = this;
}
deactivate() {
current_batch = null;
batch_values = null;
}
flush() {
var source_stacks = DEV ? new Set() : null;
try {
is_processing = true;
current_batch = this;
// we only reschedule previously-deferred effects if we expect
// to be able to run them after processing the batch
if (!this.#is_deferred()) {
for (const e of this.#dirty_effects) {
this.#maybe_dirty_effects.delete(e);
set_signal_status(e, DIRTY);
this.schedule(e);
}
for (const e of this.#maybe_dirty_effects) {
set_signal_status(e, MAYBE_DIRTY);
this.schedule(e);
}
}
this.#process();
} finally {
flush_count = 0;
last_scheduled_effect = null;
collected_effects = null;
legacy_updates = null;
is_processing = false;
current_batch = null;
batch_values = null;
old_values.clear();
if (DEV) {
for (const source of /** @type {Set<Source>} */ (source_stacks)) {
source.updated = null;
}
}
}
}
discard() {
for (const fn of this.#discard_callbacks) fn(this);
this.#discard_callbacks.clear();
}
#commit() {
// If there are other pending batches, they now need to be 'rebased' —
// in other words, we re-run block/async effects with the newly
// committed state, unless the batch in question has a more
// recent value for a given source
if (batches.size > 1) {
this.previous.clear();
var previous_batch = current_batch;
var previous_batch_values = batch_values;
var is_earlier = true;
for (const batch of batches) {
if (batch === this) {
is_earlier = false;
continue;
}
/** @type {Source[]} */
const sources = [];
for (const [source, value] of this.current) {
if (batch.current.has(source)) {
if (is_earlier && value !== batch.current.get(source)) {
// bring the value up to date
batch.current.set(source, value);
} else {
// same value or later batch has more recent value,
// no need to re-run these effects
continue;
}
}
sources.push(source);
}
if (sources.length === 0) {
continue;
}
// Re-run async/block effects that depend on distinct values changed in both batches
const others = [...batch.current.keys()].filter((s) => !this.current.has(s));
if (others.length > 0) {
batch.activate();
/** @type {Set<Value>} */
const marked = new Set();
/** @type {Map<Reaction, boolean>} */
const checked = new Map();
for (const source of sources) {
mark_effects(source, others, marked, checked);
}
if (batch.#roots.length > 0) {
batch.apply();
for (const root of batch.#roots) {
batch.#traverse(root, [], []);
}
// TODO do we need to do anything with the dummy effect arrays?
}
batch.deactivate();
}
}
current_batch = previous_batch;
batch_values = previous_batch_values;
}
this.#skipped_branches.clear();
batches.delete(this);
}
/**
*
* @param {boolean} blocking
*/
increment(blocking) {
this.#pending += 1;
if (blocking) this.#blocking_pending += 1;
}
/**
* @param {boolean} blocking
* @param {boolean} skip - whether to skip updates (because this is triggered by a stale reaction)
*/
decrement(blocking, skip) {
this.#pending -= 1;
if (blocking) this.#blocking_pending -= 1;
if (this.#decrement_queued || skip) return;
this.#decrement_queued = true;
queue_micro_task(() => {
this.#decrement_queued = false;
this.flush();
});
}
/** @param {(batch: Batch) => void} fn */
oncommit(fn) {
this.#commit_callbacks.add(fn);
}
/** @param {(batch: Batch) => void} fn */
ondiscard(fn) {
this.#discard_callbacks.add(fn);
}
settled() {
return (this.#deferred ??= deferred()).promise;
}
static ensure() {
if (current_batch === null) {
const batch = (current_batch = new Batch());
if (!is_processing) {
batches.add(current_batch);
{
queue_micro_task(() => {
if (current_batch !== batch) {
// a flushSync happened in the meantime
return;
}
batch.flush();
});
}
}
}
return current_batch;
}
apply() {
return;
}
/**
*
* @param {Effect} effect
*/
schedule(effect) {
last_scheduled_effect = effect;
// defer render effects inside a pending boundary
// TODO the `REACTION_RAN` check is only necessary because of legacy `$:` effects AFAICT — we can remove later
if (
effect.b?.is_pending &&
(effect.f & (EFFECT | RENDER_EFFECT | MANAGED_EFFECT)) !== 0 &&
(effect.f & REACTION_RAN) === 0
) {
effect.b.defer_effect(effect);
return;
}
var e = effect;
while (e.parent !== null) {
e = e.parent;
var flags = e.f;
// if the effect is being scheduled because a parent (each/await/etc) block
// updated an internal source, or because a branch is being unskipped,
// bail out or we'll cause a second flush
if (collected_effects !== null && e === active_effect) {
// in sync mode, render effects run during traversal. in an extreme edge case
// — namely that we're setting a value inside a derived read during traversal —
// they can be made dirty after they have already been visited, in which
// case we shouldn't bail out. we also shouldn't bail out if we're
// updating a store inside a `$:`, since this might invalidate
// effects that were already visited
if (
(active_reaction === null || (active_reaction.f & DERIVED) === 0) &&
!legacy_is_updating_store
) {
return;
}
}
if ((flags & (ROOT_EFFECT | BRANCH_EFFECT)) !== 0) {
if ((flags & CLEAN) === 0) {
// branch is already dirty, bail
return;
}
e.f ^= CLEAN;
}
}
this.#roots.push(e);
}
}
function infinite_loop_guard() {
if (DEV) {
var updates = new Map();
for (const source of /** @type {Batch} */ (current_batch).current.keys()) {
for (const [stack, update] of source.updated ?? []) {
var entry = updates.get(stack);
if (!entry) {
entry = { error: update.error, count: 0 };
updates.set(stack, entry);
}
entry.count += update.count;
}
}
for (const update of updates.values()) {
if (update.error) {
// eslint-disable-next-line no-console
console.error(update.error);
}
}
}
try {
effect_update_depth_exceeded();
} catch (error) {
if (DEV) {
// stack contains no useful information, replace it
define_property(error, 'stack', { value: '' });
}
// Best effort: invoke the boundary nearest the most recent
// effect and hope that it's relevant to the infinite loop
invoke_error_boundary(error, last_scheduled_effect);
}
}
/** @type {Set<Effect> | null} */
let eager_block_effects = null;
/**
* @param {Array<Effect>} effects
* @returns {void}
*/
function flush_queued_effects(effects) {
var length = effects.length;
if (length === 0) return;
var i = 0;
while (i < length) {
var effect = effects[i++];
if ((effect.f & (DESTROYED | INERT)) === 0 && is_dirty(effect)) {
eager_block_effects = new Set();
update_effect(effect);
// Effects with no dependencies or teardown do not get added to the effect tree.
// Deferred effects (e.g. `$effect(...)`) _are_ added to the tree because we
// don't know if we need to keep them until they are executed. Doing the check
// here (rather than in `update_effect`) allows us to skip the work for
// immediate effects.
if (
effect.deps === null &&
effect.first === null &&
effect.nodes === null &&
effect.teardown === null &&
effect.ac === null
) {
// remove this effect from the graph
unlink_effect(effect);
}
// If update_effect() has a flushSync() in it, we may have flushed another flush_queued_effects(),
// which already handled this logic and did set eager_block_effects to null.
if (eager_block_effects?.size > 0) {
old_values.clear();
for (const e of eager_block_effects) {
// Skip eager effects that have already been unmounted
if ((e.f & (DESTROYED | INERT)) !== 0) continue;
// Run effects in order from ancestor to descendant, else we could run into nullpointers
/** @type {Effect[]} */
const ordered_effects = [e];
let ancestor = e.parent;
while (ancestor !== null) {
if (eager_block_effects.has(ancestor)) {
eager_block_effects.delete(ancestor);
ordered_effects.push(ancestor);
}
ancestor = ancestor.parent;
}
for (let j = ordered_effects.length - 1; j >= 0; j--) {
const e = ordered_effects[j];
// Skip eager effects that have already been unmounted
if ((e.f & (DESTROYED | INERT)) !== 0) continue;
update_effect(e);
}
}
eager_block_effects.clear();
}
}
}
eager_block_effects = null;
}
/**
* This is similar to `mark_reactions`, but it only marks async/block effects
* depending on `value` and at least one of the other `sources`, so that
* these effects can re-run after another batch has been committed
* @param {Value} value
* @param {Source[]} sources
* @param {Set<Value>} marked
* @param {Map<Reaction, boolean>} checked
*/
function mark_effects(value, sources, marked, checked) {
if (marked.has(value)) return;
marked.add(value);
if (value.reactions !== null) {
for (const reaction of value.reactions) {
const flags = reaction.f;
if ((flags & DERIVED) !== 0) {
mark_effects(/** @type {Derived} */ (reaction), sources, marked, checked);
} else if (
(flags & (ASYNC | BLOCK_EFFECT)) !== 0 &&
(flags & DIRTY) === 0 &&
depends_on(reaction, sources, checked)
) {
set_signal_status(reaction, DIRTY);
schedule_effect(/** @type {Effect} */ (reaction));
}
}
}
}
/**
* @param {Reaction} reaction
* @param {Source[]} sources
* @param {Map<Reaction, boolean>} checked
*/
function depends_on(reaction, sources, checked) {
const depends = checked.get(reaction);
if (depends !== undefined) return depends;
if (reaction.deps !== null) {
for (const dep of reaction.deps) {
if (includes.call(sources, dep)) {
return true;
}
if ((dep.f & DERIVED) !== 0 && depends_on(/** @type {Derived} */ (dep), sources, checked)) {
checked.set(/** @type {Derived} */ (dep), true);
return true;
}
}
}
checked.set(reaction, false);
return false;
}
/**
* @param {Effect} effect
* @returns {void}
*/
function schedule_effect(effect) {
/** @type {Batch} */ (current_batch).schedule(effect);
}
/**
* Mark all the effects inside a skipped branch CLEAN, so that
* they can be correctly rescheduled later. Tracks dirty and maybe_dirty
* effects so they can be rescheduled if the branch survives.
* @param {Effect} effect
* @param {{ d: Effect[], m: Effect[] }} tracked
*/
function reset_branch(effect, tracked) {
// clean branch = nothing dirty inside, no need to traverse further
if ((effect.f & BRANCH_EFFECT) !== 0 && (effect.f & CLEAN) !== 0) {
return;
}
if ((effect.f & DIRTY) !== 0) {
tracked.d.push(effect);
} else if ((effect.f & MAYBE_DIRTY) !== 0) {
tracked.m.push(effect);
}
set_signal_status(effect, CLEAN);
var e = effect.first;
while (e !== null) {
reset_branch(e, tracked);
e = e.next;
}
}
/** @import { Blocker, Effect, Value } from '#client' */
/**
* @param {Blocker[]} blockers
* @param {Array<() => any>} sync
* @param {Array<() => Promise<any>>} async
* @param {(values: Value[]) => any} fn
*/
function flatten(blockers, sync, async, fn) {
const d = derived ;
// Filter out already-settled blockers - no need to wait for them
var pending = blockers.filter((b) => !b.settled);
if (async.length === 0 && pending.length === 0) {
fn(sync.map(d));
return;
}
var parent = /** @type {Effect} */ (active_effect);
var restore = capture();
var blocker_promise =
pending.length === 1
? pending[0].promise
: pending.length > 1
? Promise.all(pending.map((b) => b.promise))
: null;
/** @param {Value[]} values */
function finish(values) {
restore();
try {
fn(values);
} catch (error) {
if ((parent.f & DESTROYED) === 0) {
invoke_error_boundary(error, parent);
}
}
unset_context();
}
// Fast path: blockers but no async expressions
if (async.length === 0) {
/** @type {Promise<any>} */ (blocker_promise).then(() => finish(sync.map(d)));
return;
}
var decrement_pending = increment_pending();
// Full path: has async expressions
function run() {
Promise.all(async.map((expression) => async_derived(expression)))
.then((result) => finish([...sync.map(d), ...result]))
.catch((error) => invoke_error_boundary(error, parent))
.finally(() => decrement_pending());
}
if (blocker_promise) {
blocker_promise.then(() => {
restore();
run();
unset_context();
});
} else {
run();
}
}
/**
* Captures the current effect context so that we can restore it after
* some asynchronous work has happened (so that e.g. `await a + b`
* causes `b` to be registered as a dependency).
*/
function capture() {
var previous_effect = /** @type {Effect} */ (active_effect);
var previous_reaction = active_reaction;
var previous_component_context = component_context;
var previous_batch = /** @type {Batch} */ (current_batch);
if (DEV) {
var previous_dev_stack = dev_stack;
}
return function restore(activate_batch = true) {
set_active_effect(previous_effect);
set_active_reaction(previous_reaction);
set_component_context(previous_component_context);
if (activate_batch && (previous_effect.f & DESTROYED) === 0) {
// TODO we only need optional chaining here because `{#await ...}` blocks
// are anomalous. Once we retire them we can get rid of it
previous_batch?.activate();
previous_batch?.apply();
}
if (DEV) {
set_dev_stack(previous_dev_stack);
}
};
}
function unset_context(deactivate_batch = true) {
set_active_effect(null);
set_active_reaction(null);
set_component_context(null);
if (deactivate_batch) current_batch?.deactivate();
if (DEV) {
set_dev_stack(null);
}
}
/**
* @returns {(skip?: boolean) => void}
*/
function increment_pending() {
var boundary = /** @type {Boundary} */ (/** @type {Effect} */ (active_effect).b);
var batch = /** @type {Batch} */ (current_batch);
var blocking = boundary.is_rendered();
boundary.update_pending_count(1, batch);
batch.increment(blocking);
return (skip = false) => {
boundary.update_pending_count(-1, batch);
batch.decrement(blocking, skip);
};
}
/** @import { Derived, Effect, Source } from '#client' */
const recent_async_deriveds = new Set();
/**
* @template V
* @param {() => V} fn
* @returns {Derived<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
function derived(fn) {
var flags = DERIVED | DIRTY;
var parent_derived =
active_reaction !== null && (active_reaction.f & DERIVED) !== 0
? /** @type {Derived} */ (active_reaction)
: null;
if (active_effect !== null) {
// Since deriveds are evaluated lazily, any effects created inside them are
// created too late to ensure that the parent effect is added to the tree
active_effect.f |= EFFECT_PRESERVED;
}
/** @type {Derived<V>} */
const signal = {
ctx: component_context,
deps: null,
effects: null,
equals,
f: flags,
fn,
reactions: null,
rv: 0,
v: /** @type {V} */ (UNINITIALIZED),
wv: 0,
parent: parent_derived ?? active_effect,
ac: null
};
if (DEV && tracing_mode_flag) {
signal.created = get_error('created at');
}
return signal;
}
/**
* @template V
* @param {() => V | Promise<V>} fn
* @param {string} [label]
* @param {string} [location] If provided, print a warning if the value is not read immediately after update
* @returns {Promise<Source<V>>}
*/
/*#__NO_SIDE_EFFECTS__*/
function async_derived(fn, label, location) {
let parent = /** @type {Effect | null} */ (active_effect);
if (parent === null) {
async_derived_orphan();
}
var promise = /** @type {Promise<V>} */ (/** @type {unknown} */ (undefined));
var signal = source(/** @type {V} */ (UNINITIALIZED));
if (DEV) signal.label = label;
// only suspend in async deriveds created on initialisation
var should_suspend = !active_reaction;
/** @type {Map<Batch, ReturnType<typeof deferred<V>>>} */
var deferreds = new Map();
async_eff