mirror of https://github.com/sveltejs/svelte
chore: move context code into new module (#15132)
parent
b2c8224a73
commit
5e9b29c351
@ -0,0 +1,214 @@
|
||||
/** @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
|
||||
} from './runtime.js';
|
||||
import { effect } 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));
|
||||
|
||||
if (DEV) {
|
||||
const fn = /** @type {ComponentContext} */ (component_context).function;
|
||||
if (fn) {
|
||||
add_owner(result, fn, true);
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
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');
|
||||
|
||||
if (DEV) {
|
||||
const fn = component_context?.function;
|
||||
if (fn) {
|
||||
for (const value of context_map.values()) {
|
||||
add_owner(value, fn, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
m: false,
|
||||
s: props,
|
||||
x: null,
|
||||
l: null
|
||||
};
|
||||
|
||||
if (legacy_mode_flag && !runes) {
|
||||
component_context.l = {
|
||||
s: null,
|
||||
u: null,
|
||||
r1: [],
|
||||
r2: source(false)
|
||||
};
|
||||
}
|
||||
|
||||
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} */ ({});
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
Loading…
Reference in new issue