Skip to content

Commit a382304

Browse files
committed
elements
1 parent 446ffc2 commit a382304

File tree

3 files changed

+84
-76
lines changed

3 files changed

+84
-76
lines changed

packages/svelte/src/internal/client/dom/blocks/branches.js

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
pause_effect,
99
resume_effect
1010
} from '../../reactivity/effects.js';
11+
import { set_should_intro, should_intro } from '../../render.js';
1112
import { hydrate_node, hydrating } from '../hydration.js';
1213
import { create_text, should_defer_append } from '../operations.js';
1314

@@ -34,11 +35,18 @@ export class BranchManager {
3435
#legacy = !is_runes();
3536

3637
/**
37-
*
38+
* Whether to pause (i.e. outro) on change, or destroy immediately.
39+
* This is necessary for `<svelte:element>`
40+
*/
41+
#transition = true;
42+
43+
/**
3844
* @param {TemplateNode} anchor
45+
* @param {boolean} transition
3946
*/
40-
constructor(anchor) {
47+
constructor(anchor, transition = true) {
4148
this.anchor = anchor;
49+
this.#transition = transition;
4250
}
4351

4452
#commit = () => {
@@ -67,6 +75,7 @@ export class BranchManager {
6775

6876
// ...and append the fragment
6977
this.anchor.before(offscreen.fragment);
78+
onscreen = offscreen.effect;
7079
}
7180
}
7281

@@ -89,27 +98,29 @@ export class BranchManager {
8998
for (const [k, effect] of this.#onscreen) {
9099
if (k === key) continue;
91100

92-
pause_effect(
93-
effect,
94-
() => {
95-
const keys = Array.from(this.#batches.values());
101+
const on_destroy = () => {
102+
const keys = Array.from(this.#batches.values());
96103

97-
if (keys.includes(k)) {
98-
// keep the effect offscreen, as another batch will need it
99-
var fragment = document.createDocumentFragment();
100-
move_effect(effect, fragment);
104+
if (keys.includes(k)) {
105+
// keep the effect offscreen, as another batch will need it
106+
var fragment = document.createDocumentFragment();
107+
move_effect(effect, fragment);
101108

102-
fragment.append(create_text()); // TODO can we avoid this?
109+
fragment.append(create_text()); // TODO can we avoid this?
103110

104-
this.#offscreen.set(k, { effect, fragment });
105-
} else {
106-
destroy_effect(effect);
107-
}
111+
this.#offscreen.set(k, { effect, fragment });
112+
} else {
113+
destroy_effect(effect);
114+
}
108115

109-
this.#onscreen.delete(k);
110-
},
111-
false
112-
);
116+
this.#onscreen.delete(k);
117+
};
118+
119+
if (this.#transition || !onscreen) {
120+
pause_effect(effect, on_destroy, false);
121+
} else {
122+
on_destroy();
123+
}
113124
}
114125
};
115126

@@ -120,28 +131,35 @@ export class BranchManager {
120131
*/
121132
ensure(key, fn) {
122133
var batch = /** @type {Batch} */ (current_batch);
134+
var defer = should_defer_append();
123135

124136
// key blocks in Svelte <5 had stupid semantics
125-
if (this.#legacy && typeof key === 'object') {
137+
if (this.#legacy && key !== null && typeof key === 'object') {
126138
key = {};
127139
}
128140

129141
if (fn && !this.#onscreen.has(key) && !this.#offscreen.has(key)) {
130-
var fragment = document.createDocumentFragment();
131-
var target = create_text();
132-
133-
fragment.append(target);
134-
135-
this.#offscreen.set(key, {
136-
effect: branch(() => fn(target)),
137-
fragment
138-
});
142+
if (defer) {
143+
var fragment = document.createDocumentFragment();
144+
var target = create_text();
145+
146+
fragment.append(target);
147+
148+
this.#offscreen.set(key, {
149+
effect: branch(() => fn(target)),
150+
fragment
151+
});
152+
} else {
153+
this.#onscreen.set(
154+
key,
155+
branch(() => fn(this.anchor))
156+
);
157+
}
139158
}
140159

141160
this.#batches.set(batch, key);
142161

143-
// TODO in the no-defer case, we could skip the offscreen step
144-
if (should_defer_append()) {
162+
if (defer) {
145163
for (const [k, effect] of this.#onscreen) {
146164
if (k === key) {
147165
batch.skipped_effects.delete(effect);

packages/svelte/src/internal/client/dom/blocks/svelte-element.js

Lines changed: 34 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,7 @@ import {
88
set_hydrating
99
} from '../hydration.js';
1010
import { create_text, get_first_child } from '../operations.js';
11-
import {
12-
block,
13-
branch,
14-
destroy_effect,
15-
pause_effect,
16-
resume_effect
17-
} from '../../reactivity/effects.js';
11+
import { block, teardown } from '../../reactivity/effects.js';
1812
import { set_should_intro } from '../../render.js';
1913
import { current_each_item, set_current_each_item } from './each.js';
2014
import { active_effect } from '../../runtime.js';
@@ -23,6 +17,7 @@ import { DEV } from 'esm-env';
2317
import { EFFECT_TRANSPARENT, ELEMENT_NODE } from '#client/constants';
2418
import { assign_nodes } from '../template.js';
2519
import { is_raw_text_element } from '../../../../utils.js';
20+
import { BranchManager } from './branches.js';
2621

2722
/**
2823
* @param {Comment | Element} node
@@ -42,12 +37,6 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
4237

4338
var filename = DEV && location && component_context?.function[FILENAME];
4439

45-
/** @type {string | null} */
46-
var tag;
47-
48-
/** @type {string | null} */
49-
var current_tag;
50-
5140
/** @type {null | Element} */
5241
var element = null;
5342

@@ -58,46 +47,31 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
5847

5948
var anchor = /** @type {TemplateNode} */ (hydrating ? hydrate_node : node);
6049

61-
/** @type {Effect | null} */
62-
var effect;
63-
6450
/**
6551
* The keyed `{#each ...}` item block, if any, that this element is inside.
6652
* We track this so we can set it when changing the element, allowing any
6753
* `animate:` directive to bind itself to the correct block
6854
*/
6955
var each_item_block = current_each_item;
7056

57+
var branches = new BranchManager(anchor, false);
58+
7159
block(() => {
7260
const next_tag = get_tag() || null;
7361
var ns = get_namespace ? get_namespace() : is_svg || next_tag === 'svg' ? NAMESPACE_SVG : null;
7462

75-
// Assumption: Noone changes the namespace but not the tag (what would that even mean?)
76-
if (next_tag === tag) return;
77-
78-
// See explanation of `each_item_block` above
79-
var previous_each_item = current_each_item;
80-
set_current_each_item(each_item_block);
81-
82-
if (effect) {
83-
if (next_tag === null) {
84-
// start outro
85-
pause_effect(effect, () => {
86-
effect = null;
87-
current_tag = null;
88-
});
89-
} else if (next_tag === current_tag) {
90-
// same tag as is currently rendered — abort outro
91-
resume_effect(effect);
92-
} else {
93-
// tag is changing — destroy immediately, render contents without intro transitions
94-
destroy_effect(effect);
95-
set_should_intro(false);
96-
}
63+
if (next_tag === null) {
64+
branches.ensure(null, null);
65+
set_should_intro(true);
66+
return;
9767
}
9868

99-
if (next_tag && next_tag !== current_tag) {
100-
effect = branch(() => {
69+
branches.ensure(next_tag, (anchor) => {
70+
// See explanation of `each_item_block` above
71+
var previous_each_item = current_each_item;
72+
set_current_each_item(each_item_block);
73+
74+
if (next_tag) {
10175
element = hydrating
10276
? /** @type {Element} */ (element)
10377
: ns
@@ -149,16 +123,31 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
149123
/** @type {Effect} */ (active_effect).nodes_end = element;
150124

151125
anchor.before(element);
152-
});
153-
}
126+
}
127+
128+
set_current_each_item(previous_each_item);
129+
130+
if (hydrating) {
131+
set_hydrate_node(anchor);
132+
}
133+
});
154134

155-
tag = next_tag;
156-
if (tag) current_tag = tag;
135+
// revert to the default state after the effect has been created
157136
set_should_intro(true);
158137

159-
set_current_each_item(previous_each_item);
138+
return () => {
139+
if (next_tag) {
140+
// if we're in this callback because we're re-running the effect,
141+
// disable intros (unless no element is currently displayed)
142+
set_should_intro(false);
143+
}
144+
};
160145
}, EFFECT_TRANSPARENT);
161146

147+
teardown(() => {
148+
set_should_intro(true);
149+
});
150+
162151
if (was_hydrating) {
163152
set_hydrating(true);
164153
set_hydrate_node(anchor);

packages/svelte/src/internal/client/render.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ export function hydrate(component, options) {
137137
if (error !== HYDRATION_ERROR) {
138138
// eslint-disable-next-line no-console
139139
console.warn('Failed to hydrate: ', error);
140+
console.error(error);
140141
}
141142

142143
if (options.recover === false) {

0 commit comments

Comments
 (0)