chore: remove blocks (#10880)

* attach DOM to effects

* null out effect.dom

* remove some block.d references

* another

* drive-by fix

* better comment

* unused arg

* another

* another

* another

* more

* finish renaming stuff

* more

* remove item.d

* remove block.d

* remove effect.block

* remove current_block

* delete delete delete

* rename

* remove some stuff we dont need

* simplify
pull/10882/head
Rich Harris 1 year ago committed by GitHub
parent c47c5713e2
commit b6c7956b26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -9,8 +9,7 @@ import {
set_current_reaction set_current_reaction
} from '../../runtime.js'; } from '../../runtime.js';
import { destroy_effect, pause_effect, render_effect } from '../../reactivity/effects.js'; import { destroy_effect, pause_effect, render_effect } from '../../reactivity/effects.js';
import { DESTROYED, INERT } from '../../constants.js'; import { INERT } from '../../constants.js';
import { create_block } from './utils.js';
/** /**
* @template V * @template V
@ -22,8 +21,6 @@ import { create_block } from './utils.js';
* @returns {void} * @returns {void}
*/ */
export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) { export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
const block = create_block();
const component_context = current_component_context; const component_context = current_component_context;
hydrate_block_anchor(anchor); hydrate_block_anchor(anchor);
@ -48,7 +45,7 @@ export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
set_current_effect(branch); set_current_effect(branch);
set_current_reaction(branch); // TODO do we need both? set_current_reaction(branch); // TODO do we need both?
set_current_component_context(component_context); set_current_component_context(component_context);
var effect = render_effect(() => fn(anchor, value), {}, true); var effect = render_effect(() => fn(anchor, value), true);
set_current_component_context(null); set_current_component_context(null);
set_current_reaction(null); set_current_reaction(null);
set_current_effect(null); set_current_effect(null);
@ -60,18 +57,6 @@ export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
return effect; return effect;
} }
/** @param {import('#client').Effect} effect */
function pause(effect) {
if ((effect.f & DESTROYED) !== 0) return;
const block = effect.block;
pause_effect(effect, () => {
// TODO make this unnecessary
const dom = block?.d;
if (dom) remove(dom);
});
}
const branch = render_effect(() => { const branch = render_effect(() => {
if (input === (input = get_input())) return; if (input === (input = get_input())) return;
@ -80,20 +65,20 @@ export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
if (pending_fn) { if (pending_fn) {
if (pending_effect && (pending_effect.f & INERT) === 0) { if (pending_effect && (pending_effect.f & INERT) === 0) {
if (pending_effect.block?.d) remove(pending_effect.block.d); if (pending_effect.dom) remove(pending_effect.dom);
destroy_effect(pending_effect); destroy_effect(pending_effect);
} }
pending_effect = render_effect(() => pending_fn(anchor), {}, true); pending_effect = render_effect(() => pending_fn(anchor), true);
} }
if (then_effect) pause(then_effect); if (then_effect) pause_effect(then_effect);
if (catch_effect) pause(catch_effect); if (catch_effect) pause_effect(catch_effect);
promise.then( promise.then(
(value) => { (value) => {
if (promise !== input) return; if (promise !== input) return;
if (pending_effect) pause(pending_effect); if (pending_effect) pause_effect(pending_effect);
if (then_fn) { if (then_fn) {
then_effect = create_effect(then_fn, value); then_effect = create_effect(then_fn, value);
@ -101,7 +86,7 @@ export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
}, },
(error) => { (error) => {
if (promise !== input) return; if (promise !== input) return;
if (pending_effect) pause(pending_effect); if (pending_effect) pause_effect(pending_effect);
if (catch_fn) { if (catch_fn) {
catch_effect = create_effect(catch_fn, error); catch_effect = create_effect(catch_fn, error);
@ -109,24 +94,24 @@ export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
} }
); );
} else { } else {
if (pending_effect) pause(pending_effect); if (pending_effect) pause_effect(pending_effect);
if (catch_effect) pause(catch_effect); if (catch_effect) pause_effect(catch_effect);
if (then_fn) { if (then_fn) {
if (then_effect) { if (then_effect) {
if (then_effect.block?.d) remove(then_effect.block.d); if (then_effect.dom) remove(then_effect.dom);
destroy_effect(then_effect); destroy_effect(then_effect);
} }
then_effect = render_effect(() => then_fn(anchor, input), {}, true); then_effect = render_effect(() => then_fn(anchor, input), true);
} }
} }
}, block); });
branch.ondestroy = () => { branch.ondestroy = () => {
// TODO this sucks, tidy it up // TODO this sucks, tidy it up
if (pending_effect?.block?.d) remove(pending_effect.block.d); if (pending_effect?.dom) remove(pending_effect.dom);
if (then_effect?.block?.d) remove(then_effect.block.d); if (then_effect?.dom) remove(then_effect.dom);
if (catch_effect?.block?.d) remove(catch_effect.block.d); if (catch_effect?.dom) remove(catch_effect.dom);
}; };
} }

@ -19,6 +19,7 @@ import { untrack } from '../../runtime.js';
import { import {
destroy_effect, destroy_effect,
pause_effect, pause_effect,
pause_effects,
render_effect, render_effect,
resume_effect, resume_effect,
user_effect user_effect
@ -26,21 +27,20 @@ import {
import { source, mutable_source, set } from '../../reactivity/sources.js'; import { source, mutable_source, set } from '../../reactivity/sources.js';
import { is_array, is_frozen, map_get, map_set } from '../../utils.js'; import { is_array, is_frozen, map_get, map_set } from '../../utils.js';
import { STATE_SYMBOL } from '../../constants.js'; import { STATE_SYMBOL } from '../../constants.js';
import { create_block } from './utils.js';
var NEW_BLOCK = -1; var NEW_ITEM = -1;
var LIS_BLOCK = -2; var LIS_ITEM = -2;
/** /**
* The row of a keyed each block that is currently updating. We track this * The row of a keyed each block that is currently updating. We track this
* so that `animate:` directives have something to attach themselves to * so that `animate:` directives have something to attach themselves to
* @type {import('#client').EachItem | null} * @type {import('#client').EachItem | null}
*/ */
export let current_each_item_block = null; export let current_each_item = null;
/** @param {import('#client').EachItem | null} block */ /** @param {import('#client').EachItem | null} item */
export function set_current_each_item_block(block) { export function set_current_each_item(item) {
current_each_item_block = block; current_each_item = item;
} }
/** /**
@ -55,8 +55,6 @@ export function set_current_each_item_block(block) {
* @returns {void} * @returns {void}
*/ */
function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, reconcile_fn) { function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, reconcile_fn) {
var block = create_block();
/** @type {import('#client').EachState} */ /** @type {import('#client').EachState} */
var state = { flags, items: [] }; var state = { flags, items: [] };
@ -71,132 +69,122 @@ function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, re
/** @type {import('#client').Effect | null} */ /** @type {import('#client').Effect | null} */
var fallback = null; var fallback = null;
var effect = render_effect( var effect = render_effect(() => {
() => { var collection = get_collection();
var collection = get_collection();
var array = is_array(collection) var array = is_array(collection)
? collection ? collection
: collection == null : collection == null
? [] ? []
: Array.from(collection); : Array.from(collection);
var keys = get_key === null ? array : array.map(get_key); var keys = get_key === null ? array : array.map(get_key);
var length = array.length; var length = array.length;
// If we are working with an array that isn't proxied or frozen, then remove strict equality and ensure the items // If we are working with an array that isn't proxied or frozen, then remove strict equality and ensure the items
// are treated as reactive, so they get wrapped in a signal. // are treated as reactive, so they get wrapped in a signal.
var flags = state.flags; var flags = state.flags;
if ((flags & EACH_IS_STRICT_EQUALS) !== 0 && !is_frozen(array) && !(STATE_SYMBOL in array)) { if ((flags & EACH_IS_STRICT_EQUALS) !== 0 && !is_frozen(array) && !(STATE_SYMBOL in array)) {
flags ^= EACH_IS_STRICT_EQUALS; flags ^= EACH_IS_STRICT_EQUALS;
// Additionally if we're in an keyed each block, we'll need ensure the items are all wrapped in signals. // Additionally if we're in an keyed each block, we'll need ensure the items are all wrapped in signals.
if ((flags & EACH_KEYED) !== 0 && (flags & EACH_ITEM_REACTIVE) === 0) { if ((flags & EACH_KEYED) !== 0 && (flags & EACH_ITEM_REACTIVE) === 0) {
flags ^= EACH_ITEM_REACTIVE; flags ^= EACH_ITEM_REACTIVE;
}
} }
}
/** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */ /** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false; let mismatch = false;
if (hydrating) { if (hydrating) {
var is_else = var is_else =
/** @type {Comment} */ (current_hydration_fragment?.[0])?.data === 'ssr:each_else'; /** @type {Comment} */ (current_hydration_fragment?.[0])?.data === 'ssr:each_else';
if (is_else !== (length === 0)) {
// hydration mismatch — remove the server-rendered DOM and start over
remove(current_hydration_fragment);
set_current_hydration_fragment(null);
mismatch = true;
} else if (is_else) {
// Remove the each_else comment node or else it will confuse the subsequent hydration algorithm
/** @type {import('#client').TemplateNode[]} */ (current_hydration_fragment).shift();
}
}
if (is_else !== (length === 0)) { // this is separate to the previous block because `hydrating` might change
// hydration mismatch — remove the server-rendered DOM and start over if (hydrating) {
remove(current_hydration_fragment); var b_items = [];
set_current_hydration_fragment(null);
// Hydrate block
var hydration_list = /** @type {import('#client').TemplateNode[]} */ (
current_hydration_fragment
);
var hydrating_node = hydration_list[0];
for (var i = 0; i < length; i++) {
var fragment = get_hydration_fragment(hydrating_node);
set_current_hydration_fragment(fragment);
if (!fragment) {
// If fragment is null, then that means that the server rendered less items than what
// the client code specifies -> break out and continue with client-side node creation
mismatch = true; mismatch = true;
} else if (is_else) { break;
// Remove the each_else comment node or else it will confuse the subsequent hydration algorithm
/** @type {import('#client').TemplateNode[]} */ (current_hydration_fragment).shift();
} }
}
// this is separate to the previous block because `hydrating` might change b_items[i] = create_item(array[i], keys?.[i], i, render_fn, flags);
if (hydrating) {
var b_blocks = [];
// Hydrate block // TODO helperise this
var hydration_list = /** @type {import('#client').TemplateNode[]} */ ( hydrating_node = /** @type {import('#client').TemplateNode} */ (
current_hydration_fragment /** @type {Node} */ (
/** @type {Node} */ (fragment[fragment.length - 1] || hydrating_node).nextSibling
).nextSibling
); );
var hydrating_node = hydration_list[0]; }
for (var i = 0; i < length; i++) {
var fragment = get_hydration_fragment(hydrating_node);
set_current_hydration_fragment(fragment);
if (!fragment) {
// If fragment is null, then that means that the server rendered less items than what
// the client code specifies -> break out and continue with client-side node creation
mismatch = true;
break;
}
b_blocks[i] = create_item(array[i], keys?.[i], i, render_fn, flags);
// TODO helperise this
hydrating_node = /** @type {import('#client').TemplateNode} */ (
/** @type {Node} */ (
/** @type {Node} */ (fragment[fragment.length - 1] || hydrating_node).nextSibling
).nextSibling
);
}
remove_excess_hydration_nodes(hydration_list, hydrating_node); remove_excess_hydration_nodes(hydration_list, hydrating_node);
state.items = b_blocks; state.items = b_items;
} }
if (!hydrating) { if (!hydrating) {
// TODO add 'empty controlled block' optimisation here // TODO add 'empty controlled block' optimisation here
reconcile_fn(array, state, anchor, render_fn, flags, keys); reconcile_fn(array, state, anchor, render_fn, flags, keys);
} }
if (fallback_fn !== null) { if (fallback_fn !== null) {
if (length === 0) { if (length === 0) {
if (fallback) { if (fallback) {
resume_effect(fallback); resume_effect(fallback);
} else { } else {
fallback = render_effect( fallback = render_effect(() => {
() => { var dom = fallback_fn(anchor);
fallback_fn(anchor);
var dom = block.d; // TODO would be nice if this was just returned from the managed effect function... return () => {
if (dom !== undefined) {
return () => { remove(dom);
if (dom !== null) { }
remove(dom); };
dom = null; }, true);
}
};
},
block,
true
);
}
} else if (fallback !== null) {
pause_effect(fallback, () => {
fallback = null;
});
} }
} else if (fallback !== null) {
pause_effect(fallback, () => {
fallback = null;
});
} }
}
if (mismatch) { if (mismatch) {
// Set a fragment so that Svelte continues to operate in hydration mode // Set a fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]); set_current_hydration_fragment([]);
} }
}, });
block,
false
);
effect.ondestroy = () => { effect.ondestroy = () => {
for (var item of state.items) { for (var item of state.items) {
if (item.d !== null) { if (item.e.dom !== null) {
remove(item.e.dom);
destroy_effect(item.e); destroy_effect(item.e);
remove(item.d);
} }
} }
@ -274,26 +262,14 @@ function reconcile_indexed_array(array, state, anchor, render_fn, flags) {
state.items = b_items; state.items = b_items;
} else if (a > b) { } else if (a > b) {
// remove items // remove items
var remaining = a - b; var effects = [];
for (i = b; i < a; i += 1) {
var clear = () => { effects.push(a_items[i].e);
for (var i = b; i < a; i += 1) { }
var block = a_items[i];
if (block.d) remove(block.d);
}
pause_effects(effects, () => {
state.items.length = b; state.items.length = b;
}; });
var check = () => {
if (--remaining === 0) {
clear();
}
};
for (; i < a; i += 1) {
pause_effect(a_items[i].e, check);
}
} }
} }
@ -311,42 +287,42 @@ function reconcile_indexed_array(array, state, anchor, render_fn, flags) {
* @returns {void} * @returns {void}
*/ */
function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) { function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
var a_blocks = state.items; var a_items = state.items;
var a = a_blocks.length; var a = a_items.length;
var b = array.length; var b = array.length;
/** @type {Array<import('#client').EachItem>} */ /** @type {Array<import('#client').EachItem>} */
var b_blocks = Array(b); var b_items = Array(b);
var is_animated = (flags & EACH_IS_ANIMATED) !== 0; var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0; var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0;
var start = 0; var start = 0;
var block; var item;
/** @type {Array<import('#client').EachItem>} */ /** @type {import('#client').Effect[]} */
var to_destroy = []; var to_destroy = [];
// Step 1 — trim common suffix // Step 1 — trim common suffix
while (a > 0 && b > 0 && a_blocks[a - 1].k === keys[b - 1]) { while (a > 0 && b > 0 && a_items[a - 1].k === keys[b - 1]) {
block = b_blocks[--b] = a_blocks[--a]; item = b_items[--b] = a_items[--a];
anchor = get_first_child(block); anchor = get_first_child(item);
resume_effect(block.e); resume_effect(item.e);
if (should_update) { if (should_update) {
update_item(block, array[b], b, flags); update_item(item, array[b], b, flags);
} }
} }
// Step 2 — trim common prefix // Step 2 — trim common prefix
while (start < a && start < b && a_blocks[start].k === keys[start]) { while (start < a && start < b && a_items[start].k === keys[start]) {
block = b_blocks[start] = a_blocks[start]; item = b_items[start] = a_items[start];
resume_effect(block.e); resume_effect(item.e);
if (should_update) { if (should_update) {
update_item(block, array[start], start, flags); update_item(item, array[start], start, flags);
} }
start += 1; start += 1;
@ -356,14 +332,14 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
if (start === a) { if (start === a) {
// add only // add only
while (start < b) { while (start < b) {
block = create_item(array[start], keys[start], start, render_fn, flags); item = create_item(array[start], keys[start], start, render_fn, flags);
b_blocks[start++] = block; b_items[start++] = item;
insert_item(block, anchor); insert_item(item, anchor);
} }
} else if (start === b) { } else if (start === b) {
// remove only // remove only
while (start < a) { while (start < a) {
to_destroy.push(a_blocks[start++]); to_destroy.push(a_items[start++].e);
} }
} else { } else {
// reconcile // reconcile
@ -372,78 +348,78 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
var indexes = new Map(); var indexes = new Map();
var i; var i;
var index; var index;
var last_block; var last_item;
var last_sibling; var last_sibling;
// store the indexes of each block in the new world // store the indexes of each item in the new world
for (i = start; i < b; i += 1) { for (i = start; i < b; i += 1) {
sources[i - start] = NEW_BLOCK; sources[i - start] = NEW_ITEM;
map_set(indexes, keys[i], i); map_set(indexes, keys[i], i);
} }
/** @type {Array<import('#client').EachItem>} */ /** @type {Array<import('#client').EachItem>} */
var to_animate = []; var to_animate = [];
if (is_animated) { if (is_animated) {
// for all blocks that were in both the old and the new list, // for all items that were in both the old and the new list,
// measure them and store them in `to_animate` so we can // measure them and store them in `to_animate` so we can
// apply animations once the DOM has been updated // apply animations once the DOM has been updated
for (i = 0; i < a_blocks.length; i += 1) { for (i = 0; i < a_items.length; i += 1) {
block = a_blocks[i]; item = a_items[i];
if (indexes.has(block.k)) { if (indexes.has(item.k)) {
block.a?.measure(); item.a?.measure();
to_animate.push(block); to_animate.push(item);
} }
} }
} }
// populate the `sources` array for each old block with // populate the `sources` array for each old item with
// its new index, so that we can calculate moves // its new index, so that we can calculate moves
for (i = start; i < a; i += 1) { for (i = start; i < a; i += 1) {
block = a_blocks[i]; item = a_items[i];
index = map_get(indexes, block.k); index = map_get(indexes, item.k);
resume_effect(block.e); resume_effect(item.e);
if (index === undefined) { if (index === undefined) {
to_destroy.push(block); to_destroy.push(item.e);
} else { } else {
moved = true; moved = true;
sources[index - start] = i; sources[index - start] = i;
b_blocks[index] = block; b_items[index] = item;
if (is_animated) { if (is_animated) {
to_animate.push(block); to_animate.push(item);
} }
} }
} }
// if we need to move blocks (as opposed to just adding/removing), // if we need to move items (as opposed to just adding/removing),
// figure out how to do so efficiently (I would be lying if I said // figure out how to do so efficiently (I would be lying if I said
// I fully understand this part) // I fully understand this part)
if (moved) { if (moved) {
mark_lis(sources); mark_lis(sources);
} }
// working from the back, insert new or moved blocks // working from the back, insert new or moved items
while (b-- > start) { while (b-- > start) {
index = sources[b - start]; index = sources[b - start];
var insert = index === NEW_BLOCK; var insert = index === NEW_ITEM;
if (insert) { if (insert) {
block = create_item(array[b], keys[b], b, render_fn, flags); item = create_item(array[b], keys[b], b, render_fn, flags);
} else { } else {
block = b_blocks[b]; item = b_items[b];
if (should_update) { if (should_update) {
update_item(block, array[b], b, flags); update_item(item, array[b], b, flags);
} }
} }
if (insert || (moved && index !== LIS_BLOCK)) { if (insert || (moved && index !== LIS_ITEM)) {
last_sibling = last_block === undefined ? anchor : get_first_child(last_block); last_sibling = last_item === undefined ? anchor : get_first_child(last_item);
anchor = insert_item(block, last_sibling); anchor = insert_item(item, last_sibling);
} }
last_block = b_blocks[b] = block; last_item = b_items[b] = item;
} }
if (to_animate.length > 0) { if (to_animate.length > 0) {
@ -453,36 +429,17 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
// - https://svelte.dev/repl/6e891305e9644a7ca7065fa95c79d2d2?version=4.2.9 // - https://svelte.dev/repl/6e891305e9644a7ca7065fa95c79d2d2?version=4.2.9
user_effect(() => { user_effect(() => {
untrack(() => { untrack(() => {
for (block of to_animate) { for (item of to_animate) {
block.a?.apply(); item.a?.apply();
} }
}); });
}); });
} }
} }
var remaining = to_destroy.length; pause_effects(to_destroy, () => {
if (remaining > 0) { state.items = b_items;
var clear = () => { });
for (block of to_destroy) {
if (block.d) remove(block.d);
}
state.items = b_blocks;
};
var check = () => {
if (--remaining === 0) {
clear();
}
};
for (block of to_destroy) {
pause_effect(block.e, check);
}
} else {
state.items = b_blocks;
}
} }
/** /**
@ -524,7 +481,7 @@ function mark_lis(a) {
var hi; var hi;
// Skip -1 values at the start of the input array `a`. // Skip -1 values at the start of the input array `a`.
for (; a[i] === NEW_BLOCK; ++i) { for (; a[i] === NEW_ITEM; ++i) {
/**/ /**/
} }
@ -533,7 +490,7 @@ function mark_lis(a) {
for (; i < length; ++i) { for (; i < length; ++i) {
k = a[i]; k = a[i];
if (k !== NEW_BLOCK) { if (k !== NEW_ITEM) {
// Ignore -1 values. // Ignore -1 values.
j = index[index_length]; j = index[index_length];
@ -567,27 +524,27 @@ function mark_lis(a) {
j = index[index_length]; j = index[index_length];
while (index_length-- >= 0) { while (index_length-- >= 0) {
a[j] = LIS_BLOCK; a[j] = LIS_ITEM;
j = parent[j]; j = parent[j];
} }
} }
/** /**
* @param {import('#client').EachItem} block * @param {import('#client').EachItem} item
* @param {Text | Element | Comment} sibling * @param {Text | Element | Comment} anchor
* @returns {Text | Element | Comment} * @returns {Text | Element | Comment}
*/ */
function insert_item(block, sibling) { function insert_item(item, anchor) {
var current = /** @type {import('#client').TemplateNode} */ (block.d); var current = /** @type {import('#client').Dom} */ (item.e.dom);
return insert(current, sibling); return insert(current, anchor);
} }
/** /**
* @param {import('#client').EachItem} block * @param {import('#client').EachItem} item
* @returns {Text | Element | Comment} * @returns {Text | Element | Comment}
*/ */
function get_first_child(block) { function get_first_child(item) {
var current = block.d; var current = item.e.dom;
if (is_array(current)) { if (is_array(current)) {
return /** @type {Text | Element | Comment} */ (current[0]); return /** @type {Text | Element | Comment} */ (current[0]);
@ -597,48 +554,40 @@ function get_first_child(block) {
} }
/** /**
* @param {import('#client').EachItem} block * @param {import('#client').EachItem} item
* @param {any} item * @param {any} value
* @param {number} index * @param {number} index
* @param {number} type * @param {number} type
* @returns {void} * @returns {void}
*/ */
function update_item(block, item, index, type) { function update_item(item, value, index, type) {
if ((type & EACH_ITEM_REACTIVE) !== 0) { if ((type & EACH_ITEM_REACTIVE) !== 0) {
set(block.v, item); set(item.v, value);
} }
if ((type & EACH_INDEX_REACTIVE) !== 0) { if ((type & EACH_INDEX_REACTIVE) !== 0) {
set(/** @type {import('#client').Value<number>} */ (block.i), index); set(/** @type {import('#client').Value<number>} */ (item.i), index);
} else { } else {
block.i = index; item.i = index;
} }
} }
/** /**
* @template V * @template V
* @param {V} item * @param {V} value
* @param {unknown} key * @param {unknown} key
* @param {number} index * @param {number} index
* @param {(anchor: null, item: V, index: number | import('#client').Value<number>) => void} render_fn * @param {(anchor: null, item: V, index: number | import('#client').Value<number>) => void} render_fn
* @param {number} flags * @param {number} flags
* @returns {import('#client').EachItem} * @returns {import('#client').EachItem}
*/ */
function create_item(item, key, index, render_fn, flags) { function create_item(value, key, index, render_fn, flags) {
var each_item_not_reactive = (flags & EACH_ITEM_REACTIVE) === 0; var each_item_not_reactive = (flags & EACH_ITEM_REACTIVE) === 0;
var item_value = each_item_not_reactive
? item
: (flags & EACH_IS_STRICT_EQUALS) !== 0
? source(item)
: mutable_source(item);
/** @type {import('#client').EachItem} */ /** @type {import('#client').EachItem} */
var block = { var item = {
a: null, a: null,
// dom // dom
d: null,
// effect
// @ts-expect-error // @ts-expect-error
e: null, e: null,
// index // index
@ -646,24 +595,30 @@ function create_item(item, key, index, render_fn, flags) {
// key // key
k: key, k: key,
// item // item
v: item_value v: each_item_not_reactive
? value
: (flags & EACH_IS_STRICT_EQUALS) !== 0
? source(value)
: mutable_source(value)
}; };
var previous_each_item_block = current_each_item_block; var previous_each_item = current_each_item;
try { try {
current_each_item_block = block; current_each_item = item;
item.e = render_effect(() => {
var dom = render_fn(null, item.v, item.i);
block.e = render_effect( return () => {
() => { if (dom !== undefined) {
render_fn(null, block.v, block.i); remove(dom);
}, }
block, };
true }, true);
);
return block; return item;
} finally { } finally {
current_each_item_block = previous_each_item_block; current_each_item = previous_each_item;
} }
} }

@ -12,7 +12,6 @@ import {
render_effect, render_effect,
resume_effect resume_effect
} from '../../reactivity/effects.js'; } from '../../reactivity/effects.js';
import { create_block } from './utils.js';
/** /**
* @param {Comment} anchor * @param {Comment} anchor
@ -23,16 +22,8 @@ import { create_block } from './utils.js';
* @returns {void} * @returns {void}
*/ */
export function if_block(anchor, get_condition, consequent_fn, alternate_fn, elseif = false) { export function if_block(anchor, get_condition, consequent_fn, alternate_fn, elseif = false) {
const block = create_block();
hydrate_block_anchor(anchor); hydrate_block_anchor(anchor);
/** @type {undefined | import('#client').Dom} */
let consequent_dom;
/** @type {undefined | import('#client').Dom} */
let alternate_dom;
/** @type {import('#client').Effect | null} */ /** @type {import('#client').Effect | null} */
let consequent_effect = null; let consequent_effect = null;
@ -71,56 +62,24 @@ export function if_block(anchor, get_condition, consequent_fn, alternate_fn, els
if (consequent_effect) { if (consequent_effect) {
resume_effect(consequent_effect); resume_effect(consequent_effect);
} else { } else {
consequent_effect = render_effect( consequent_effect = render_effect(() => consequent_fn(anchor), true);
() => {
consequent_dom = consequent_fn(anchor);
return () => {
// TODO make this unnecessary by linking the dom to the effect,
// and removing automatically on teardown
if (consequent_dom !== undefined) {
remove(consequent_dom);
consequent_dom = undefined;
}
};
},
block,
true
);
} }
if (alternate_effect) { if (alternate_effect) {
pause_effect(alternate_effect, () => { pause_effect(alternate_effect, () => {
alternate_effect = null; alternate_effect = null;
if (alternate_dom) remove(alternate_dom);
}); });
} }
} else { } else {
if (alternate_effect) { if (alternate_effect) {
resume_effect(alternate_effect); resume_effect(alternate_effect);
} else if (alternate_fn) { } else if (alternate_fn) {
alternate_effect = render_effect( alternate_effect = render_effect(() => alternate_fn(anchor), true);
() => {
alternate_dom = alternate_fn(anchor);
return () => {
// TODO make this unnecessary by linking the dom to the effect,
// and removing automatically on teardown
if (alternate_dom !== undefined) {
remove(alternate_dom);
alternate_dom = undefined;
}
};
},
block,
true
);
} }
if (consequent_effect) { if (consequent_effect) {
pause_effect(consequent_effect, () => { pause_effect(consequent_effect, () => {
consequent_effect = null; consequent_effect = null;
if (consequent_dom) remove(consequent_dom);
}); });
} }
} }
@ -129,23 +88,14 @@ export function if_block(anchor, get_condition, consequent_fn, alternate_fn, els
// Set fragment so that Svelte continues to operate in hydration mode // Set fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]); set_current_hydration_fragment([]);
} }
}, block); });
if (elseif) { if (elseif) {
if_effect.f |= IS_ELSEIF; if_effect.f |= IS_ELSEIF;
} }
if_effect.ondestroy = () => { if_effect.ondestroy = () => {
// TODO make this unnecessary by linking the dom to the effect, // TODO why is this not automatic? this should be children of `if_effect`
// and removing automatically on teardown
if (consequent_dom !== undefined) {
remove(consequent_dom);
}
if (alternate_dom !== undefined) {
remove(alternate_dom);
}
if (consequent_effect) { if (consequent_effect) {
destroy_effect(consequent_effect); destroy_effect(consequent_effect);
} }

@ -3,18 +3,15 @@ import { hydrate_block_anchor } from '../hydration.js';
import { remove } from '../reconciler.js'; import { remove } from '../reconciler.js';
import { pause_effect, render_effect } from '../../reactivity/effects.js'; import { pause_effect, render_effect } from '../../reactivity/effects.js';
import { safe_not_equal } from '../../reactivity/equality.js'; import { safe_not_equal } from '../../reactivity/equality.js';
import { create_block } from './utils.js';
/** /**
* @template V * @template V
* @param {Comment} anchor * @param {Comment} anchor
* @param {() => V} get_key * @param {() => V} get_key
* @param {(anchor: Node) => void} render_fn * @param {(anchor: Node) => import('#client').Dom | void} render_fn
* @returns {void} * @returns {void}
*/ */
export function key_block(anchor, get_key, render_fn) { export function key_block(anchor, get_key, render_fn) {
const block = create_block();
hydrate_block_anchor(anchor); hydrate_block_anchor(anchor);
/** @type {V | typeof UNINITIALIZED} */ /** @type {V | typeof UNINITIALIZED} */
@ -30,47 +27,36 @@ export function key_block(anchor, get_key, render_fn) {
*/ */
let effects = new Set(); let effects = new Set();
const key_effect = render_effect( const key_effect = render_effect(() => {
() => { if (safe_not_equal(key, (key = get_key()))) {
if (safe_not_equal(key, (key = get_key()))) { if (effect) {
if (effect) { var e = effect;
var e = effect; pause_effect(e, () => {
pause_effect(e, () => { effects.delete(e);
effects.delete(e); });
});
}
effect = render_effect(
() => {
render_fn(anchor);
const dom = block.d;
return () => {
if (dom !== null) {
remove(dom);
}
};
},
block,
true,
true
);
// @ts-expect-error TODO tidy up
effect.d = block.d;
effects.add(effect);
} }
},
block, effect = render_effect(
false () => {
); const dom = render_fn(anchor);
return () => {
if (dom !== undefined) {
remove(dom);
}
};
},
true,
true
);
effects.add(effect);
}
});
key_effect.ondestroy = () => { key_effect.ondestroy = () => {
for (const e of effects) { for (const e of effects) {
// @ts-expect-error TODO tidy up. ondestroy should be totally unnecessary if (e.dom) remove(e.dom);
if (e.d) remove(e.d);
} }
}; };
} }

@ -1,28 +1,31 @@
import { render_effect } from '../../reactivity/effects.js'; import { render_effect } from '../../reactivity/effects.js';
import { remove } from '../reconciler.js'; import { remove } from '../reconciler.js';
import { untrack } from '../../runtime.js'; import { untrack } from '../../runtime.js';
import { create_block } from './utils.js';
/** /**
* @param {() => Function | null | undefined} get_snippet * @template {(node: import('#client').TemplateNode, ...args: any[]) => import('#client').Dom} SnippetFn
* @param {Node} node * @param {() => SnippetFn | null | undefined} get_snippet
* @param {import('#client').TemplateNode} node
* @param {(() => any)[]} args * @param {(() => any)[]} args
* @returns {void} * @returns {void}
*/ */
export function snippet(get_snippet, node, ...args) { export function snippet(get_snippet, node, ...args) {
const block = create_block(); /** @type {SnippetFn | null | undefined} */
var snippet_fn;
render_effect(() => { render_effect(() => {
// Only rerender when the snippet function itself changes, if (snippet_fn === (snippet_fn = get_snippet())) return;
// not when an eagerly-read prop inside the snippet function changes
const snippet = get_snippet(); if (snippet_fn) {
if (snippet) { // Untrack so we only rerender when the snippet function itself changes,
untrack(() => snippet(node, ...args)); // not when an eagerly-read prop inside the snippet function changes
var dom = untrack(() => /** @type {SnippetFn} */ (snippet_fn)(node, ...args));
} }
return () => { return () => {
if (block.d !== null) { if (dom !== undefined) {
remove(block.d); remove(dom);
} }
}; };
}, block); });
} }

@ -1,7 +1,7 @@
import { hydrate_block_anchor } from '../hydration.js'; import { hydrate_block_anchor } from '../hydration.js';
import { pause_effect, render_effect } from '../../reactivity/effects.js'; import { pause_effect, render_effect } from '../../reactivity/effects.js';
import { remove } from '../reconciler.js'; import { remove } from '../reconciler.js';
import { create_block } from './utils.js'; import { current_effect } from '../../runtime.js';
// TODO this is very similar to `key`, can we deduplicate? // TODO this is very similar to `key`, can we deduplicate?
@ -10,12 +10,10 @@ import { create_block } from './utils.js';
* @template {(props: P) => void} C * @template {(props: P) => void} C
* @param {Comment} anchor * @param {Comment} anchor
* @param {() => C} get_component * @param {() => C} get_component
* @param {(component: C) => void} render_fn * @param {(component: C) => import('#client').Dom | void} render_fn
* @returns {void} * @returns {void}
*/ */
export function component(anchor, get_component, render_fn) { export function component(anchor, get_component, render_fn) {
const block = create_block();
hydrate_block_anchor(anchor); hydrate_block_anchor(anchor);
/** @type {C} */ /** @type {C} */
@ -31,49 +29,36 @@ export function component(anchor, get_component, render_fn) {
*/ */
let effects = new Set(); let effects = new Set();
const component_effect = render_effect( const component_effect = render_effect(() => {
() => { if (component === (component = get_component())) return;
if (component === (component = get_component())) return;
if (effect) {
var e = effect;
pause_effect(e, () => {
effects.delete(e);
});
}
if (component) { if (effect) {
effect = render_effect( var e = effect;
() => { pause_effect(e, () => {
render_fn(component); effects.delete(e);
});
}
const dom = block.d; if (component) {
effect = render_effect(() => {
render_fn(component);
return () => { // `render_fn` doesn't return anything, and we can't reference `effect`
if (dom !== null) { // yet, so we reference it indirectly as `current_effect`
remove(dom); const dom = /** @type {import('#client').Effect} */ (current_effect).dom;
}
};
},
block,
true,
true
);
// @ts-expect-error TODO tidy up return () => {
effect.d = block.d; if (dom !== null) remove(dom);
};
}, true);
effects.add(effect); effects.add(effect);
} }
}, });
block,
false
);
component_effect.ondestroy = () => { component_effect.ondestroy = () => {
for (const e of effects) { for (const e of effects) {
// @ts-expect-error TODO tidy up. ondestroy should be totally unnecessary if (e.dom) remove(e.dom);
if (e.d) remove(e.d);
} }
}; };
} }

@ -10,18 +10,18 @@ import {
import { remove } from '../reconciler.js'; import { remove } from '../reconciler.js';
import { is_array } from '../../utils.js'; import { is_array } from '../../utils.js';
import { set_should_intro } from '../../render.js'; import { set_should_intro } from '../../render.js';
import { current_each_item_block, set_current_each_item_block } from './each.js'; import { current_each_item, set_current_each_item } from './each.js';
import { create_block } from './utils.js'; import { current_effect } from '../../runtime.js';
import { current_block } from '../../runtime.js';
/** /**
* @param {import('#client').Block} block * @param {import('#client').Effect} effect
* @param {Element} from * @param {Element} from
* @param {Element} to * @param {Element} to
* @returns {void} * @returns {void}
*/ */
function swap_block_dom(block, from, to) { function swap_block_dom(effect, from, to) {
const dom = block.d; const dom = effect.dom;
if (is_array(dom)) { if (is_array(dom)) {
for (let i = 0; i < dom.length; i++) { for (let i = 0; i < dom.length; i++) {
if (dom[i] === from) { if (dom[i] === from) {
@ -30,7 +30,7 @@ function swap_block_dom(block, from, to) {
} }
} }
} else if (dom === from) { } else if (dom === from) {
block.d = to; effect.dom = to;
} }
} }
@ -42,8 +42,7 @@ function swap_block_dom(block, from, to) {
* @returns {void} * @returns {void}
*/ */
export function element(anchor, get_tag, is_svg, render_fn) { export function element(anchor, get_tag, is_svg, render_fn) {
const parent_block = /** @type {import('#client').Block} */ (current_block); const parent_effect = /** @type {import('#client').Effect} */ (current_effect);
const block = create_block();
hydrate_block_anchor(anchor); hydrate_block_anchor(anchor);
@ -64,15 +63,15 @@ export function element(anchor, get_tag, is_svg, render_fn) {
* We track this so we can set it when changing the element, allowing any * We track this so we can set it when changing the element, allowing any
* `animate:` directive to bind itself to the correct block * `animate:` directive to bind itself to the correct block
*/ */
let each_item_block = current_each_item_block; let each_item_block = current_each_item;
const wrapper = render_effect(() => { const wrapper = render_effect(() => {
const next_tag = get_tag() || null; const next_tag = get_tag() || null;
if (next_tag === tag) return; if (next_tag === tag) return;
// See explanation of `each_item_block` above // See explanation of `each_item_block` above
var previous_each_item_block = current_each_item_block; var previous_each_item = current_each_item;
set_current_each_item_block(each_item_block); set_current_each_item(each_item_block);
// We try our best infering the namespace in case it's not possible to determine statically, // We try our best infering the namespace in case it's not possible to determine statically,
// but on the first render on the client (without hydration) the parent will be undefined, // but on the first render on the client (without hydration) the parent will be undefined,
@ -103,51 +102,46 @@ export function element(anchor, get_tag, is_svg, render_fn) {
} }
if (next_tag && next_tag !== current_tag) { if (next_tag && next_tag !== current_tag) {
effect = render_effect( effect = render_effect(() => {
() => { const prev_element = element;
const prev_element = element; element = hydrating
element = hydrating ? /** @type {Element} */ (current_hydration_fragment[0])
? /** @type {Element} */ (current_hydration_fragment[0]) : ns
: ns ? document.createElementNS(ns, next_tag)
? document.createElementNS(ns, next_tag) : document.createElement(next_tag);
: document.createElement(next_tag);
if (render_fn) {
if (render_fn) { let anchor;
let anchor; if (hydrating) {
if (hydrating) { // Use the existing ssr comment as the anchor so that the inner open and close
// Use the existing ssr comment as the anchor so that the inner open and close // methods can pick up the existing nodes correctly
// methods can pick up the existing nodes correctly anchor = /** @type {Comment} */ (element.firstChild);
anchor = /** @type {Comment} */ (element.firstChild); } else {
} else { anchor = empty();
anchor = empty(); element.appendChild(anchor);
element.appendChild(anchor);
}
render_fn(element, anchor);
} }
render_fn(element, anchor);
}
anchor.before(element); anchor.before(element);
if (prev_element) { if (prev_element) {
swap_block_dom(parent_block, prev_element, element); swap_block_dom(parent_effect, prev_element, element);
prev_element.remove(); prev_element.remove();
} }
}, }, true);
block,
true
);
} }
tag = next_tag; tag = next_tag;
if (tag) current_tag = tag; if (tag) current_tag = tag;
set_should_intro(true); set_should_intro(true);
set_current_each_item_block(previous_each_item_block); set_current_each_item(previous_each_item);
}, block); });
wrapper.ondestroy = () => { wrapper.ondestroy = () => {
if (element !== null) { if (element !== null) {
remove(element); remove(element);
block.d = null;
element = null; element = null;
} }

@ -7,15 +7,12 @@ import {
import { empty } from '../operations.js'; import { empty } from '../operations.js';
import { render_effect } from '../../reactivity/effects.js'; import { render_effect } from '../../reactivity/effects.js';
import { remove } from '../reconciler.js'; import { remove } from '../reconciler.js';
import { create_block } from './utils.js';
/** /**
* @param {(anchor: Node | null) => void} render_fn * @param {(anchor: Node | null) => import('#client').Dom | void} render_fn
* @returns {void} * @returns {void}
*/ */
export function head(render_fn) { export function head(render_fn) {
const block = create_block();
// The head function may be called after the first hydration pass and ssr comment nodes may still be present, // The head function may be called after the first hydration pass and ssr comment nodes may still be present,
// therefore we need to skip that when we detect that we're not in hydration mode. // therefore we need to skip that when we detect that we're not in hydration mode.
let hydration_fragment = null; let hydration_fragment = null;
@ -29,28 +26,27 @@ export function head(render_fn) {
} }
try { try {
const head_effect = render_effect( /** @type {import('#client').Dom | null} */
() => { var dom = null;
const current = block.d;
if (current !== null) { const head_effect = render_effect(() => {
remove(current); if (dom !== null) {
block.d = null; remove(dom);
} head_effect.dom = dom = null;
let anchor = null; }
if (!hydrating) {
anchor = empty(); let anchor = null;
document.head.appendChild(anchor); if (!hydrating) {
} anchor = empty();
render_fn(anchor); document.head.appendChild(anchor);
}, }
block,
false dom = render_fn(anchor) ?? null;
); });
head_effect.ondestroy = () => { head_effect.ondestroy = () => {
const current = block.d; if (dom !== null) {
if (current !== null) { remove(dom);
remove(current);
} }
}; };
} finally { } finally {

@ -1,7 +0,0 @@
/** @returns {import('#client').Block} */
export function create_block() {
return {
// dom
d: null
};
}

@ -1,6 +1,5 @@
import { hydrating } from '../hydration.js'; import { hydrating } from '../hydration.js';
import { render_effect } from '../../reactivity/effects.js'; import { render_effect } from '../../reactivity/effects.js';
import { current_block } from '../../runtime.js';
/** /**
* @param {HTMLElement} dom * @param {HTMLElement} dom
@ -17,7 +16,6 @@ export function autofocus(dom, value) {
dom.focus(); dom.focus();
} }
}, },
current_block,
true, true,
false false
); );

@ -5,7 +5,7 @@ import { raf } from '../../timing.js';
import { loop } from '../../loop.js'; import { loop } from '../../loop.js';
import { should_intro } from '../../render.js'; import { should_intro } from '../../render.js';
import { is_function } from '../../utils.js'; import { is_function } from '../../utils.js';
import { current_each_item_block } from '../blocks/each.js'; import { current_each_item } from '../blocks/each.js';
import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js'; import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js';
import { EFFECT_RAN } from '../../constants.js'; import { EFFECT_RAN } from '../../constants.js';
@ -77,7 +77,7 @@ const linear = (t) => t;
* @param {(() => P) | null} get_params * @param {(() => P) | null} get_params
*/ */
export function animation(element, get_fn, get_params) { export function animation(element, get_fn, get_params) {
var block = /** @type {import('#client').EachItem} */ (current_each_item_block); var item = /** @type {import('#client').EachItem} */ (current_each_item);
/** @type {DOMRect} */ /** @type {DOMRect} */
var from; var from;
@ -88,7 +88,7 @@ export function animation(element, get_fn, get_params) {
/** @type {import('#client').Animation | undefined} */ /** @type {import('#client').Animation | undefined} */
var animation; var animation;
block.a ??= { item.a ??= {
element, element,
measure() { measure() {
from = this.element.getBoundingClientRect(); from = this.element.getBoundingClientRect();
@ -118,7 +118,7 @@ export function animation(element, get_fn, get_params) {
// when an animation manager already exists, if the tag changes. in that case, we need to // when an animation manager already exists, if the tag changes. in that case, we need to
// swap out the element rather than creating a new manager, in case it happened at the same // swap out the element rather than creating a new manager, in case it happened at the same
// moment as a reconciliation // moment as a reconciliation
block.a.element = element; item.a.element = element;
} }
/** /**

@ -5,7 +5,7 @@ import {
create_fragment_with_script_from_html, create_fragment_with_script_from_html,
insert insert
} from './reconciler.js'; } from './reconciler.js';
import { current_block } from '../runtime.js'; import { current_effect } from '../runtime.js';
import { is_array } from '../utils.js'; import { is_array } from '../utils.js';
/** /**
@ -192,7 +192,7 @@ function close_template(dom, is_fragment, anchor) {
insert(current, anchor); insert(current, anchor);
} }
/** @type {import('#client').Block} */ (current_block).d = current; /** @type {import('#client').Effect} */ (current_effect).dom = current;
return current; return current;
} }

@ -1,7 +1,6 @@
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { import {
check_dirtiness, check_dirtiness,
current_block,
current_component_context, current_component_context,
current_effect, current_effect,
current_reaction, current_reaction,
@ -25,20 +24,20 @@ import {
} from '../constants.js'; } from '../constants.js';
import { set } from './sources.js'; import { set } from './sources.js';
import { noop } from '../../common.js'; import { noop } from '../../common.js';
import { remove } from '../dom/reconciler.js';
/** /**
* @param {import('./types.js').EffectType} type * @param {import('./types.js').EffectType} type
* @param {(() => void | (() => void))} fn * @param {(() => void | (() => void))} fn
* @param {boolean} sync * @param {boolean} sync
* @param {null | import('#client').Block} block
* @param {boolean} init * @param {boolean} init
* @returns {import('#client').Effect} * @returns {import('#client').Effect}
*/ */
function create_effect(type, fn, sync, block = current_block, init = true) { function create_effect(type, fn, sync, init = true) {
/** @type {import('#client').Effect} */ /** @type {import('#client').Effect} */
const signal = { const signal = {
parent: current_effect, parent: current_effect,
block, dom: null,
deps: null, deps: null,
f: type | DIRTY, f: type | DIRTY,
l: 0, l: 0,
@ -99,7 +98,7 @@ export function user_effect(fn) {
current_component_context !== null && current_component_context !== null &&
!current_component_context.m; !current_component_context.m;
const effect = create_effect(EFFECT, fn, false, current_block, !defer); const effect = create_effect(EFFECT, fn, false, !defer);
if (defer) { if (defer) {
const context = /** @type {import('#client').ComponentContext} */ (current_component_context); const context = /** @type {import('#client').ComponentContext} */ (current_component_context);
@ -115,7 +114,7 @@ export function user_effect(fn) {
* @returns {() => void} * @returns {() => void}
*/ */
export function user_root_effect(fn) { export function user_root_effect(fn) {
const effect = render_effect(fn, current_block, true); const effect = render_effect(fn, true);
return () => { return () => {
destroy_effect(effect); destroy_effect(effect);
}; };
@ -216,17 +215,15 @@ export function invalidate_effect(fn) {
/** /**
* @param {(() => void)} fn * @param {(() => void)} fn
* @param {any} block * @param {boolean} managed
* @param {any} managed * @param {boolean} sync
* @param {any} sync
* @returns {import('#client').Effect} * @returns {import('#client').Effect}
*/ */
export function render_effect(fn, block = current_block, managed = false, sync = true) { export function render_effect(fn, managed = false, sync = true) {
let flags = RENDER_EFFECT; let flags = RENDER_EFFECT;
if (managed) { if (managed) flags |= MANAGED;
flags |= MANAGED;
} return create_effect(flags, /** @type {any} */ (fn), sync);
return create_effect(flags, /** @type {any} */ (fn), sync, block);
} }
/** /**
@ -245,6 +242,11 @@ export function destroy_effect(effect) {
} }
effect.teardown?.(); effect.teardown?.();
if (effect.dom !== null) {
remove(effect.dom);
}
effect.ondestroy?.(); effect.ondestroy?.();
// @ts-expect-error // @ts-expect-error
@ -253,7 +255,7 @@ export function destroy_effect(effect) {
effect.teardown = effect.teardown =
effect.ondestroy = effect.ondestroy =
effect.ctx = effect.ctx =
effect.block = effect.dom =
effect.deps = effect.deps =
null; null;
} }
@ -269,26 +271,51 @@ export function destroy_effect(effect) {
*/ */
export function pause_effect(effect, callback = noop) { export function pause_effect(effect, callback = noop) {
/** @type {import('#client').TransitionManager[]} */ /** @type {import('#client').TransitionManager[]} */
const transitions = []; var transitions = [];
pause_children(effect, transitions, true); pause_children(effect, transitions, true);
let remaining = transitions.length; out(transitions, () => {
destroy_effect(effect);
callback();
});
}
if (remaining > 0) { /**
const check = () => { * Pause multiple effects simultaneously, and coordinate their
if (!--remaining) { * subsequent destruction. Used in each blocks
destroy_effect(effect); * @param {import('#client').Effect[]} effects
callback(); * @param {() => void} callback
} */
}; export function pause_effects(effects, callback = noop) {
/** @type {import('#client').TransitionManager[]} */
var transitions = [];
for (var effect of effects) {
pause_children(effect, transitions, true);
}
out(transitions, () => {
for (var effect of effects) {
destroy_effect(effect);
}
callback();
});
}
for (const transition of transitions) { /**
* @param {import('#client').TransitionManager[]} transitions
* @param {() => void} fn
*/
function out(transitions, fn) {
var remaining = transitions.length;
if (remaining > 0) {
var check = () => --remaining || fn();
for (var transition of transitions) {
transition.out(check); transition.out(check);
} }
} else { } else {
destroy_effect(effect); fn();
callback();
} }
} }

@ -1,4 +1,4 @@
import type { Block, ComponentContext, Equals, TransitionManager } from '#client'; import type { Block, ComponentContext, Dom, Equals, TransitionManager } from '#client';
import type { EFFECT, PRE_EFFECT, RENDER_EFFECT } from '../constants'; import type { EFFECT, PRE_EFFECT, RENDER_EFFECT } from '../constants';
export type EffectType = typeof EFFECT | typeof PRE_EFFECT | typeof RENDER_EFFECT; export type EffectType = typeof EFFECT | typeof PRE_EFFECT | typeof RENDER_EFFECT;
@ -37,8 +37,7 @@ export interface Derived<V = unknown> extends Value<V>, Reaction {
export interface Effect extends Reaction { export interface Effect extends Reaction {
parent: Effect | null; parent: Effect | null;
/** The block associated with this effect */ dom: Dom | null;
block: null | Block;
/** The associated component context */ /** The associated component context */
ctx: null | ComponentContext; ctx: null | ComponentContext;
/** Stuff to do when the effect is destroyed */ /** Stuff to do when the effect is destroyed */

@ -211,40 +211,30 @@ function _mount(Component, options) {
should_intro = options.intro ?? false; should_intro = options.intro ?? false;
/** @type {import('#client').Block} */
const block = {
// dom
d: null
};
/** @type {Exports} */ /** @type {Exports} */
// @ts-expect-error will be defined because the render effect runs synchronously // @ts-expect-error will be defined because the render effect runs synchronously
let component = undefined; let component = undefined;
const effect = render_effect( const effect = render_effect(() => {
() => { if (options.context) {
if (options.context) { push({});
push({}); /** @type {import('../client/types.js').ComponentContext} */ (current_component_context).c =
/** @type {import('../client/types.js').ComponentContext} */ (current_component_context).c = options.context;
options.context; }
} if (!options.props) {
if (!options.props) { options.props = /** @type {Props} */ ({});
options.props = /** @type {Props} */ ({}); }
} if (options.events) {
if (options.events) { // We can't spread the object or else we'd lose the state proxy stuff, if it is one
// We can't spread the object or else we'd lose the state proxy stuff, if it is one /** @type {any} */ (options.props).$$events = options.events;
/** @type {any} */ (options.props).$$events = options.events; }
} component =
component = // @ts-expect-error the public typings are not what the actual function looks like
// @ts-expect-error the public typings are not what the actual function looks like Component(options.anchor, options.props) || {};
Component(options.anchor, options.props) || {}; if (options.context) {
if (options.context) { pop();
pop(); }
} }, true);
},
block,
true
);
const bound_event_listener = handle_event_propagation.bind(null, container); const bound_event_listener = handle_event_propagation.bind(null, container);
const bound_document_event_listener = handle_event_propagation.bind(null, document); const bound_document_event_listener = handle_event_propagation.bind(null, document);
@ -291,7 +281,7 @@ function _mount(Component, options) {
container.removeEventListener(event_name, bound_event_listener); container.removeEventListener(event_name, bound_event_listener);
} }
root_event_handles.delete(event_handle); root_event_handles.delete(event_handle);
const dom = block.d; const dom = effect.dom;
if (dom !== null) { if (dom !== null) {
remove(dom); remove(dom);
} }

@ -107,11 +107,7 @@ export let inspect_fn = null;
/** @type {Array<import('./types.js').ValueDebug>} */ /** @type {Array<import('./types.js').ValueDebug>} */
let inspect_captured_signals = []; let inspect_captured_signals = [];
// Handle rendering tree blocks and anchors
/** @type {null | import('./types.js').Block} */
export let current_block = null;
// Handling runtime component context // Handling runtime component context
/** @type {import('./types.js').ComponentContext | null} */ /** @type {import('./types.js').ComponentContext | null} */
export let current_component_context = null; export let current_component_context = null;
@ -387,13 +383,11 @@ export function execute_effect(signal) {
const previous_effect = current_effect; const previous_effect = current_effect;
const previous_component_context = current_component_context; const previous_component_context = current_component_context;
const previous_block = current_block;
const component_context = signal.ctx; const component_context = signal.ctx;
current_effect = signal; current_effect = signal;
current_component_context = component_context; current_component_context = component_context;
current_block = signal.block;
try { try {
destroy_children(signal); destroy_children(signal);
@ -403,7 +397,6 @@ export function execute_effect(signal) {
} finally { } finally {
current_effect = previous_effect; current_effect = previous_effect;
current_component_context = previous_component_context; current_component_context = previous_component_context;
current_block = previous_block;
} }
if ((signal.f & PRE_EFFECT) !== 0 && current_queued_pre_and_render_effects.length > 0) { if ((signal.f & PRE_EFFECT) !== 0 && current_queued_pre_and_render_effects.length > 0) {
@ -514,7 +507,6 @@ export function schedule_effect(signal, sync) {
if (!should_append) { if (!should_append) {
const target_level = signal.l; const target_level = signal.l;
const target_block = signal.block;
const is_pre_effect = (flags & PRE_EFFECT) !== 0; const is_pre_effect = (flags & PRE_EFFECT) !== 0;
let target_signal; let target_signal;
let target_signal_level; let target_signal_level;
@ -530,7 +522,7 @@ export function schedule_effect(signal, sync) {
is_target_pre_effect = (target_signal.f & PRE_EFFECT) !== 0; is_target_pre_effect = (target_signal.f & PRE_EFFECT) !== 0;
if ( if (
target_signal_level < target_level || target_signal_level < target_level ||
target_signal.block !== target_block || target_signal !== signal ||
(is_target_pre_effect && !is_pre_effect) (is_target_pre_effect && !is_pre_effect)
) { ) {
i++; i++;

@ -51,10 +51,7 @@ export type TemplateNode = Text | Element | Comment;
export type Dom = TemplateNode | TemplateNode[]; export type Dom = TemplateNode | TemplateNode[];
export interface Block { export interface Block {}
/** dom */
d: null | Dom;
}
export type EachState = { export type EachState = {
/** flags */ /** flags */
@ -66,8 +63,6 @@ export type EachState = {
export type EachItem = { export type EachItem = {
/** animation manager */ /** animation manager */
a: AnimationManager | null; a: AnimationManager | null;
/** dom */
d: null | Dom;
/** effect */ /** effect */
e: Effect; e: Effect;
/** item */ /** item */

@ -26,7 +26,6 @@ function run_test(runes: boolean, fn: (runes: boolean) => () => void) {
() => { () => {
execute = fn(runes); execute = fn(runes);
}, },
null,
true, true,
true true
); );

Loading…
Cancel
Save