mirror of https://github.com/sveltejs/svelte
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
5.6 KiB
224 lines
5.6 KiB
/** @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;
|
|
}
|