init
This commit is contained in:
+258
@@ -0,0 +1,258 @@
|
||||
/** @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a `[get, set]` pair of functions for working with context in a type-safe way.
|
||||
*
|
||||
* `get` will throw an error if no parent component called `set`.
|
||||
*
|
||||
* @template T
|
||||
* @returns {[() => T, (context: T) => T]}
|
||||
* @since 5.40.0
|
||||
*/
|
||||
export function createContext() {
|
||||
const key = {};
|
||||
|
||||
return [
|
||||
() => {
|
||||
if (!hasContext(key)) {
|
||||
e.missing_context();
|
||||
}
|
||||
|
||||
return getContext(key);
|
||||
},
|
||||
(context) => setContext(key, context)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the context that belongs to the closest parent component with the specified `key`.
|
||||
* Must be called during component initialisation.
|
||||
*
|
||||
* [`createContext`](https://svelte.dev/docs/svelte/svelte#createContext) is a type-safe alternative.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* [`createContext`](https://svelte.dev/docs/svelte/svelte#createContext) is a type-safe alternative.
|
||||
*
|
||||
* @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 &&
|
||||
// pop() runs synchronously, so this indicates we're setting context after an await
|
||||
!(/** @type {ComponentContext} */ (component_context).i);
|
||||
|
||||
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,
|
||||
i: false,
|
||||
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;
|
||||
}
|
||||
|
||||
context.i = true;
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user