UNPKG

svelte

Version:

Cybernetically enhanced web apps

217 lines (196 loc) • 5.76 kB
/** @import { ComponentContext } from '#client' */ import { DEV } from 'esm-env'; import { add_owner } from './dev/ownership.js'; import { lifecycle_outside_component } from '../shared/errors.js'; import { source } from './reactivity/sources.js'; import { active_effect, active_reaction, set_active_effect, set_active_reaction, untrack } from './runtime.js'; import { effect, teardown } from './reactivity/effects.js'; import { legacy_mode_flag } from '../flags/index.js'; /** @type {ComponentContext | null} */ export let component_context = null; /** @param {ComponentContext | null} context */ export function set_component_context(context) { component_context = context; } /** * The current component function. Different from current component context: * ```html * <!-- App.svelte --> * <Foo> * <Bar /> <!-- context == Foo.svelte, function == App.svelte --> * </Foo> * ``` * @type {ComponentContext['function']} */ export let dev_current_component_function = null; /** @param {ComponentContext['function']} fn */ export function set_dev_current_component_function(fn) { dev_current_component_function = fn; } /** * Retrieves the context that belongs to the closest parent component with the specified `key`. * Must be called during component initialisation. * * @template T * @param {any} key * @returns {T} */ export function getContext(key) { const context_map = get_or_init_context_map('getContext'); const result = /** @type {T} */ (context_map.get(key)); return result; } /** * Associates an arbitrary `context` object with the current component and the specified `key` * and returns that object. The context is then available to children of the component * (including slotted content) with `getContext`. * * Like lifecycle functions, this must be called during component initialisation. * * @template T * @param {any} key * @param {T} context * @returns {T} */ export function setContext(key, context) { const context_map = get_or_init_context_map('setContext'); if (DEV) { // When state is put into context, we treat as if it's global from now on. // We do for performance reasons (it's for example very expensive to call // getContext on a big object many times when part of a list component) // and danger of false positives. untrack(() => add_owner(context, null, true)); } context_map.set(key, context); return context; } /** * Checks whether a given `key` has been set in the context of a parent component. * Must be called during component initialisation. * * @param {any} key * @returns {boolean} */ export function hasContext(key) { const context_map = get_or_init_context_map('hasContext'); return context_map.has(key); } /** * Retrieves the whole context map that belongs to the closest parent component. * Must be called during component initialisation. Useful, for example, if you * programmatically create a component and want to pass the existing context to it. * * @template {Map<any, any>} [T=Map<any, any>] * @returns {T} */ export function getAllContexts() { const context_map = get_or_init_context_map('getAllContexts'); return /** @type {T} */ (context_map); } /** * @param {Record<string, unknown>} props * @param {any} runes * @param {Function} [fn] * @returns {void} */ export function push(props, runes = false, fn) { var ctx = (component_context = { p: component_context, c: null, d: false, e: null, m: false, s: props, x: null, l: null }); if (legacy_mode_flag && !runes) { component_context.l = { s: null, u: null, r1: [], r2: source(false) }; } teardown(() => { /** @type {ComponentContext} */ (ctx).d = true; }); if (DEV) { // component function component_context.function = fn; dev_current_component_function = fn; } } /** * @template {Record<string, any>} T * @param {T} [component] * @returns {T} */ export function pop(component) { const context_stack_item = component_context; if (context_stack_item !== null) { if (component !== undefined) { context_stack_item.x = component; } const component_effects = context_stack_item.e; if (component_effects !== null) { var previous_effect = active_effect; var previous_reaction = active_reaction; context_stack_item.e = null; try { for (var i = 0; i < component_effects.length; i++) { var component_effect = component_effects[i]; set_active_effect(component_effect.effect); set_active_reaction(component_effect.reaction); effect(component_effect.fn); } } finally { set_active_effect(previous_effect); set_active_reaction(previous_reaction); } } component_context = context_stack_item.p; if (DEV) { dev_current_component_function = context_stack_item.p?.function ?? null; } context_stack_item.m = true; } // Micro-optimization: Don't set .a above to the empty object // so it can be garbage-collected when the return here is unused return component || /** @type {T} */ ({}); } /** @returns {boolean} */ export function is_runes() { return !legacy_mode_flag || (component_context !== null && component_context.l === null); } /** * @param {string} name * @returns {Map<unknown, unknown>} */ function get_or_init_context_map(name) { if (component_context === null) { lifecycle_outside_component(name); } return (component_context.c ??= new Map(get_parent_context(component_context) || undefined)); } /** * @param {ComponentContext} component_context * @returns {Map<unknown, unknown> | null} */ function get_parent_context(component_context) { let parent = component_context.p; while (parent !== null) { const context_map = parent.c; if (context_map !== null) { return context_map; } parent = parent.p; } return null; }