UNPKG

svelte

Version:

Cybernetically enhanced web apps

224 lines (194 loc) • 5.69 kB
/** @import { ComponentContext, DevStackEntry, Effect } from '#client' */ import { DEV } from 'esm-env'; import * as e from './errors.js'; import { active_effect, active_reaction } from './runtime.js'; import { create_user_effect } from './reactivity/effects.js'; import { async_mode_flag, legacy_mode_flag } from '../flags/index.js'; import { FILENAME } from '../../constants.js'; import { BRANCH_EFFECT, EFFECT_RAN } from './constants.js'; /** @type {ComponentContext | null} */ export let component_context = null; /** @param {ComponentContext | null} context */ export function set_component_context(context) { component_context = context; } /** @type {DevStackEntry | null} */ export let dev_stack = null; /** @param {DevStackEntry | null} stack */ export function set_dev_stack(stack) { dev_stack = stack; } /** * Execute a callback with a new dev stack entry * @param {() => any} callback - Function to execute * @param {DevStackEntry['type']} type - Type of block/component * @param {any} component - Component function * @param {number} line - Line number * @param {number} column - Column number * @param {Record<string, any>} [additional] - Any additional properties to add to the dev stack entry * @returns {any} */ export function add_svelte_meta(callback, type, component, line, column, additional) { const parent = dev_stack; dev_stack = { type, file: component[FILENAME], line, column, parent, ...additional }; try { return callback(); } finally { dev_stack = parent; } } /** * 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 (async_mode_flag) { var flags = /** @type {Effect} */ (active_effect).f; var valid = !active_reaction && (flags & BRANCH_EFFECT) !== 0 && (flags & EFFECT_RAN) === 0; if (!valid) { e.set_context_after_init(); } } 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) { component_context = { p: component_context, c: null, e: null, s: props, x: null, l: legacy_mode_flag && !runes ? { s: null, u: null, $: [] } : null }; 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) { 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; } component_context = context.p; if (DEV) { dev_current_component_function = component_context?.function ?? null; } 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) { e.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; }