UNPKG

svelte

Version:

Cybernetically enhanced web apps

79 lines (70 loc) 2.91 kB
/** @import { ComponentContext, Effect } from '#client' */ import { DESTROYING, STATE_SYMBOL } from '#client/constants'; import { component_context } from '../../../context.js'; import { effect, render_effect } from '../../../reactivity/effects.js'; import { active_effect, untrack } from '../../../runtime.js'; /** * @param {any} bound_value * @param {Element} element_or_component * @returns {boolean} */ function is_bound_this(bound_value, element_or_component) { return ( bound_value === element_or_component || bound_value?.[STATE_SYMBOL] === element_or_component ); } /** * @param {any} element_or_component * @param {(value: unknown, ...parts: unknown[]) => void} update * @param {(...parts: unknown[]) => unknown} get_value * @param {() => unknown[]} [get_parts] Set if the this binding is used inside an each block, * returns all the parts of the each block context that are used in the expression * @returns {void} */ export function bind_this(element_or_component = {}, update, get_value, get_parts) { var component_effect = /** @type {ComponentContext} */ (component_context).r; var parent = /** @type {Effect} */ (active_effect); effect(() => { /** @type {unknown[]} */ var old_parts; /** @type {unknown[]} */ var parts; render_effect(() => { old_parts = parts; // We only track changes to the parts, not the value itself to avoid unnecessary reruns. parts = get_parts?.() || []; untrack(() => { if (element_or_component !== get_value(...parts)) { update(element_or_component, ...parts); // If this is an effect rerun (cause: each block context changes), then nullify the binding at // the previous position if it isn't already taken over by a different effect. if (old_parts && is_bound_this(get_value(...old_parts), element_or_component)) { update(null, ...old_parts); } } }); }); return () => { // When the bind:this effect is destroyed, we go up the effect parent chain until we find the last parent effect that is destroyed, // or the effect containing the component bind:this is in (whichever comes first). That way we can time the nulling of the binding // as close to user/developer expectation as possible. // TODO Svelte 6: Decide if we want to keep this logic or just always null the binding in the component effect's teardown // (which would be simpler, but less intuitive in some cases, and breaks the `ondestroy-before-cleanup` test) let p = parent; while (p !== component_effect && p.parent !== null && p.parent.f & DESTROYING) { p = p.parent; } const teardown = () => { if (parts && is_bound_this(get_value(...parts), element_or_component)) { update(null, ...parts); } }; const original_teardown = p.teardown; p.teardown = () => { teardown(); original_teardown?.(); }; }; }); return element_or_component; }