svelte 5.46.0 → 5.46.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  /** @import { Fork } from 'svelte' */
2
2
  /** @import { Derived, Effect, Reaction, Source, Value } from '#client' */
3
+ /** @import { Boundary } from '../dom/blocks/boundary' */
3
4
  import {
4
5
  BLOCK_EFFECT,
5
6
  BRANCH_EFFECT,
@@ -17,7 +18,6 @@ import {
17
18
  EAGER_EFFECT,
18
19
  HEAD_EFFECT,
19
20
  ERROR_VALUE,
20
- WAS_MARKED,
21
21
  MANAGED_EFFECT
22
22
  } from '#client/constants';
23
23
  import { async_mode_flag } from '../../flags/index.js';
@@ -25,10 +25,10 @@ import { deferred, define_property } from '../../shared/utils.js';
25
25
  import {
26
26
  active_effect,
27
27
  get,
28
+ increment_write_version,
28
29
  is_dirty,
29
30
  is_updating_effect,
30
31
  set_is_updating_effect,
31
- set_signal_status,
32
32
  update_effect
33
33
  } from '../runtime.js';
34
34
  import * as e from '../errors.js';
@@ -37,15 +37,9 @@ import { DEV } from 'esm-env';
37
37
  import { invoke_error_boundary } from '../error-handling.js';
38
38
  import { flush_eager_effects, old_values, set_eager_effects, source, update } from './sources.js';
39
39
  import { eager_effect, unlink_effect } from './effects.js';
40
-
41
- /**
42
- * @typedef {{
43
- * parent: EffectTarget | null;
44
- * effect: Effect | null;
45
- * effects: Effect[];
46
- * render_effects: Effect[];
47
- * }} EffectTarget
48
- */
40
+ import { defer_effect } from './utils.js';
41
+ import { UNINITIALIZED } from '../../../constants.js';
42
+ import { set_signal_status } from './status.js';
49
43
 
50
44
  /** @type {Set<Batch>} */
51
45
  const batches = new Set();
@@ -161,16 +155,14 @@ export class Batch {
161
155
 
162
156
  this.apply();
163
157
 
164
- /** @type {EffectTarget} */
165
- var target = {
166
- parent: null,
167
- effect: null,
168
- effects: [],
169
- render_effects: []
170
- };
158
+ /** @type {Effect[]} */
159
+ var effects = [];
160
+
161
+ /** @type {Effect[]} */
162
+ var render_effects = [];
171
163
 
172
164
  for (const root of root_effects) {
173
- this.#traverse_effect_tree(root, target);
165
+ this.#traverse_effect_tree(root, effects, render_effects);
174
166
  // Note: #traverse_effect_tree runs block effects eagerly, which can schedule effects,
175
167
  // which means queued_root_effects now may be filled again.
176
168
 
@@ -183,16 +175,16 @@ export class Batch {
183
175
  }
184
176
 
185
177
  if (this.is_deferred()) {
186
- this.#defer_effects(target.effects);
187
- this.#defer_effects(target.render_effects);
178
+ this.#defer_effects(render_effects);
179
+ this.#defer_effects(effects);
188
180
  } else {
189
181
  // If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with
190
182
  // newly updated sources, which could lead to infinite loops when effects run over and over again.
191
183
  previous_batch = this;
192
184
  current_batch = null;
193
185
 
194
- flush_queued_effects(target.render_effects);
195
- flush_queued_effects(target.effects);
186
+ flush_queued_effects(render_effects);
187
+ flush_queued_effects(effects);
196
188
 
197
189
  previous_batch = null;
198
190
 
@@ -206,13 +198,17 @@ export class Batch {
206
198
  * Traverse the effect tree, executing effects or stashing
207
199
  * them for later execution as appropriate
208
200
  * @param {Effect} root
209
- * @param {EffectTarget} target
201
+ * @param {Effect[]} effects
202
+ * @param {Effect[]} render_effects
210
203
  */
211
- #traverse_effect_tree(root, target) {
204
+ #traverse_effect_tree(root, effects, render_effects) {
212
205
  root.f ^= CLEAN;
213
206
 
214
207
  var effect = root.first;
215
208
 
209
+ /** @type {Effect | null} */
210
+ var pending_boundary = null;
211
+
216
212
  while (effect !== null) {
217
213
  var flags = effect.f;
218
214
  var is_branch = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) !== 0;
@@ -220,24 +216,32 @@ export class Batch {
220
216
 
221
217
  var skip = is_skippable_branch || (flags & INERT) !== 0 || this.skipped_effects.has(effect);
222
218
 
223
- if ((effect.f & BOUNDARY_EFFECT) !== 0 && effect.b?.is_pending()) {
224
- target = {
225
- parent: target,
226
- effect,
227
- effects: [],
228
- render_effects: []
229
- };
219
+ // Inside a `<svelte:boundary>` with a pending snippet,
220
+ // all effects are deferred until the boundary resolves
221
+ // (except block/async effects, which run immediately)
222
+ if (
223
+ async_mode_flag &&
224
+ pending_boundary === null &&
225
+ (flags & BOUNDARY_EFFECT) !== 0 &&
226
+ effect.b?.is_pending
227
+ ) {
228
+ pending_boundary = effect;
230
229
  }
231
230
 
232
231
  if (!skip && effect.fn !== null) {
233
232
  if (is_branch) {
234
233
  effect.f ^= CLEAN;
234
+ } else if (
235
+ pending_boundary !== null &&
236
+ (flags & (EFFECT | RENDER_EFFECT | MANAGED_EFFECT)) !== 0
237
+ ) {
238
+ /** @type {Boundary} */ (pending_boundary.b).defer_effect(effect);
235
239
  } else if ((flags & EFFECT) !== 0) {
236
- target.effects.push(effect);
240
+ effects.push(effect);
237
241
  } else if (async_mode_flag && (flags & (RENDER_EFFECT | MANAGED_EFFECT)) !== 0) {
238
- target.render_effects.push(effect);
242
+ render_effects.push(effect);
239
243
  } else if (is_dirty(effect)) {
240
- if ((effect.f & BLOCK_EFFECT) !== 0) this.#dirty_effects.add(effect);
244
+ if ((flags & BLOCK_EFFECT) !== 0) this.#dirty_effects.add(effect);
241
245
  update_effect(effect);
242
246
  }
243
247
 
@@ -253,14 +257,8 @@ export class Batch {
253
257
  effect = effect.next;
254
258
 
255
259
  while (effect === null && parent !== null) {
256
- if (parent === target.effect) {
257
- // TODO rather than traversing into pending boundaries and deferring the effects,
258
- // could we just attach the effects _to_ the pending boundary and schedule them
259
- // once the boundary is ready?
260
- this.#defer_effects(target.effects);
261
- this.#defer_effects(target.render_effects);
262
-
263
- target = /** @type {EffectTarget} */ (target.parent);
260
+ if (parent === pending_boundary) {
261
+ pending_boundary = null;
264
262
  }
265
263
 
266
264
  effect = parent.next;
@@ -273,36 +271,8 @@ export class Batch {
273
271
  * @param {Effect[]} effects
274
272
  */
275
273
  #defer_effects(effects) {
276
- for (const e of effects) {
277
- if ((e.f & DIRTY) !== 0) {
278
- this.#dirty_effects.add(e);
279
- } else if ((e.f & MAYBE_DIRTY) !== 0) {
280
- this.#maybe_dirty_effects.add(e);
281
- }
282
-
283
- // Since we're not executing these effects now, we need to clear any WAS_MARKED flags
284
- // so that other batches can correctly reach these effects during their own traversal
285
- this.#clear_marked(e.deps);
286
-
287
- // mark as clean so they get scheduled if they depend on pending async state
288
- set_signal_status(e, CLEAN);
289
- }
290
- }
291
-
292
- /**
293
- * @param {Value[] | null} deps
294
- */
295
- #clear_marked(deps) {
296
- if (deps === null) return;
297
-
298
- for (const dep of deps) {
299
- if ((dep.f & DERIVED) === 0 || (dep.f & WAS_MARKED) === 0) {
300
- continue;
301
- }
302
-
303
- dep.f ^= WAS_MARKED;
304
-
305
- this.#clear_marked(/** @type {Derived} */ (dep).deps);
274
+ for (var i = 0; i < effects.length; i += 1) {
275
+ defer_effect(effects[i], this.#dirty_effects, this.#maybe_dirty_effects);
306
276
  }
307
277
  }
308
278
 
@@ -313,7 +283,7 @@ export class Batch {
313
283
  * @param {any} value
314
284
  */
315
285
  capture(source, value) {
316
- if (!this.previous.has(source)) {
286
+ if (value !== UNINITIALIZED && !this.previous.has(source)) {
317
287
  this.previous.set(source, value);
318
288
  }
319
289
 
@@ -383,14 +353,6 @@ export class Batch {
383
353
  var previous_batch_values = batch_values;
384
354
  var is_earlier = true;
385
355
 
386
- /** @type {EffectTarget} */
387
- var dummy_target = {
388
- parent: null,
389
- effect: null,
390
- effects: [],
391
- render_effects: []
392
- };
393
-
394
356
  for (const batch of batches) {
395
357
  if (batch === this) {
396
358
  is_earlier = false;
@@ -439,10 +401,10 @@ export class Batch {
439
401
  batch.apply();
440
402
 
441
403
  for (const root of queued_root_effects) {
442
- batch.#traverse_effect_tree(root, dummy_target);
404
+ batch.#traverse_effect_tree(root, [], []);
443
405
  }
444
406
 
445
- // TODO do we need to do anything with `target`? defer block effects?
407
+ // TODO do we need to do anything with the dummy effect arrays?
446
408
 
447
409
  batch.deactivate();
448
410
  }
@@ -959,13 +921,18 @@ export function fork(fn) {
959
921
 
960
922
  flushSync(fn);
961
923
 
962
- batch_values = null;
963
-
964
924
  // revert state changes
965
925
  for (var [source, value] of batch.previous) {
966
926
  source.v = value;
967
927
  }
968
928
 
929
+ // make writable deriveds dirty, so they recalculate correctly
930
+ for (source of batch.current.keys()) {
931
+ if ((source.f & DERIVED) !== 0) {
932
+ set_signal_status(source, DIRTY);
933
+ }
934
+ }
935
+
969
936
  return {
970
937
  commit: async () => {
971
938
  if (committed) {
@@ -981,9 +948,10 @@ export function fork(fn) {
981
948
 
982
949
  batch.is_fork = false;
983
950
 
984
- // apply changes
951
+ // apply changes and update write versions so deriveds see the change
985
952
  for (var [source, value] of batch.current) {
986
953
  source.v = value;
954
+ source.wv = increment_write_version();
987
955
  }
988
956
 
989
957
  // trigger any `$state.eager(...)` expressions with the new state.
@@ -3,21 +3,18 @@
3
3
  import { DEV } from 'esm-env';
4
4
  import {
5
5
  ERROR_VALUE,
6
- CLEAN,
7
6
  DERIVED,
8
7
  DIRTY,
9
8
  EFFECT_PRESERVED,
10
- MAYBE_DIRTY,
11
9
  STALE_REACTION,
12
10
  ASYNC,
13
11
  WAS_MARKED,
14
- CONNECTED,
15
- DESTROYED
12
+ DESTROYED,
13
+ CLEAN
16
14
  } from '#client/constants';
17
15
  import {
18
16
  active_reaction,
19
17
  active_effect,
20
- set_signal_status,
21
18
  update_reaction,
22
19
  increment_write_version,
23
20
  set_active_effect,
@@ -37,6 +34,7 @@ import { UNINITIALIZED } from '../../../constants.js';
37
34
  import { batch_values, current_batch } from './batch.js';
38
35
  import { unset_context } from './async.js';
39
36
  import { deferred } from '../../shared/utils.js';
37
+ import { set_signal_status, update_derived_status } from './status.js';
40
38
 
41
39
  /** @type {Effect | null} */
42
40
  export let current_async_effect = null;
@@ -93,11 +91,12 @@ export function derived(fn) {
93
91
  /**
94
92
  * @template V
95
93
  * @param {() => V | Promise<V>} fn
94
+ * @param {string} [label]
96
95
  * @param {string} [location] If provided, print a warning if the value is not read immediately after update
97
96
  * @returns {Promise<Source<V>>}
98
97
  */
99
98
  /*#__NO_SIDE_EFFECTS__*/
100
- export function async_derived(fn, location) {
99
+ export function async_derived(fn, label, location) {
101
100
  let parent = /** @type {Effect | null} */ (active_effect);
102
101
 
103
102
  if (parent === null) {
@@ -109,6 +108,8 @@ export function async_derived(fn, location) {
109
108
  var promise = /** @type {Promise<V>} */ (/** @type {unknown} */ (undefined));
110
109
  var signal = source(/** @type {V} */ (UNINITIALIZED));
111
110
 
111
+ if (DEV) signal.label = label;
112
+
112
113
  // only suspend in async deriveds created on initialisation
113
114
  var should_suspend = !active_reaction;
114
115
 
@@ -147,7 +148,7 @@ export function async_derived(fn, location) {
147
148
  var batch = /** @type {Batch} */ (current_batch);
148
149
 
149
150
  if (should_suspend) {
150
- var blocking = !boundary.is_pending();
151
+ var blocking = boundary.is_rendered();
151
152
 
152
153
  boundary.update_pending_count(1);
153
154
  batch.increment(blocking);
@@ -356,15 +357,21 @@ export function update_derived(derived) {
356
357
  var value = execute_derived(derived);
357
358
 
358
359
  if (!derived.equals(value)) {
360
+ derived.wv = increment_write_version();
361
+
359
362
  // in a fork, we don't update the underlying value, just `batch_values`.
360
363
  // the underlying value will be updated when the fork is committed.
361
364
  // otherwise, the next time we get here after a 'real world' state
362
365
  // change, `derived.equals` may incorrectly return `true`
363
- if (!current_batch?.is_fork) {
366
+ if (!current_batch?.is_fork || derived.deps === null) {
364
367
  derived.v = value;
365
- }
366
368
 
367
- derived.wv = increment_write_version();
369
+ // deriveds without dependencies should never be recomputed
370
+ if (derived.deps === null) {
371
+ set_signal_status(derived, CLEAN);
372
+ return;
373
+ }
374
+ }
368
375
  }
369
376
 
370
377
  // don't mark derived clean if we're reading it inside a
@@ -382,7 +389,6 @@ export function update_derived(derived) {
382
389
  batch_values.set(derived, value);
383
390
  }
384
391
  } else {
385
- var status = (derived.f & CONNECTED) === 0 ? MAYBE_DIRTY : CLEAN;
386
- set_signal_status(derived, status);
392
+ update_derived_status(derived);
387
393
  }
388
394
  }
@@ -9,7 +9,6 @@ import {
9
9
  remove_reactions,
10
10
  set_active_reaction,
11
11
  set_is_destroying_effect,
12
- set_signal_status,
13
12
  untrack,
14
13
  untracking
15
14
  } from '../runtime.js';
@@ -44,6 +43,7 @@ import { component_context, dev_current_component_function, dev_stack } from '..
44
43
  import { Batch, current_batch, schedule_effect } from './batch.js';
45
44
  import { flatten } from './async.js';
46
45
  import { without_reactive_context } from '../dom/elements/bindings/shared.js';
46
+ import { set_signal_status } from './status.js';
47
47
 
48
48
  /**
49
49
  * @param {'$effect' | '$effect.pre' | '$inspect'} rune
@@ -328,7 +328,7 @@ export function legacy_pre_effect_reset() {
328
328
 
329
329
  // If the effect is CLEAN, then make it MAYBE_DIRTY. This ensures we traverse through
330
330
  // the effects dependencies and correctly ensure each dependency is up-to-date.
331
- if ((effect.f & CLEAN) !== 0) {
331
+ if ((effect.f & CLEAN) !== 0 && effect.deps !== null) {
332
332
  set_signal_status(effect, MAYBE_DIRTY);
333
333
  }
334
334
 
@@ -6,7 +6,6 @@ import {
6
6
  untracked_writes,
7
7
  get,
8
8
  set_untracked_writes,
9
- set_signal_status,
10
9
  untrack,
11
10
  increment_write_version,
12
11
  update_effect,
@@ -40,6 +39,7 @@ import { component_context, is_runes } from '../context.js';
40
39
  import { Batch, batch_values, eager_block_effects, schedule_effect } from './batch.js';
41
40
  import { proxy } from '../proxy.js';
42
41
  import { execute_derived } from './deriveds.js';
42
+ import { set_signal_status, update_derived_status } from './status.js';
43
43
 
44
44
  /** @type {Set<any>} */
45
45
  export let eager_effects = new Set();
@@ -218,12 +218,14 @@ export function internal_set(source, value) {
218
218
  }
219
219
 
220
220
  if ((source.f & DERIVED) !== 0) {
221
+ const derived = /** @type {Derived} */ (source);
222
+
221
223
  // if we are assigning to a dirty derived we set it to clean/maybe dirty but we also eagerly execute it to track the dependencies
222
224
  if ((source.f & DIRTY) !== 0) {
223
- execute_derived(/** @type {Derived} */ (source));
225
+ execute_derived(derived);
224
226
  }
225
227
 
226
- set_signal_status(source, (source.f & CONNECTED) !== 0 ? CLEAN : MAYBE_DIRTY);
228
+ update_derived_status(derived);
227
229
  }
228
230
 
229
231
  source.wv = increment_write_version();
@@ -0,0 +1,25 @@
1
+ /** @import { Derived, Signal } from '#client' */
2
+ import { CLEAN, CONNECTED, DIRTY, MAYBE_DIRTY } from '#client/constants';
3
+
4
+ const STATUS_MASK = ~(DIRTY | MAYBE_DIRTY | CLEAN);
5
+
6
+ /**
7
+ * @param {Signal} signal
8
+ * @param {number} status
9
+ */
10
+ export function set_signal_status(signal, status) {
11
+ signal.f = (signal.f & STATUS_MASK) | status;
12
+ }
13
+
14
+ /**
15
+ * Set a derived's status to CLEAN or MAYBE_DIRTY based on its connection state.
16
+ * @param {Derived} derived
17
+ */
18
+ export function update_derived_status(derived) {
19
+ // Only mark as MAYBE_DIRTY if disconnected and has dependencies.
20
+ if ((derived.f & CONNECTED) !== 0 || derived.deps === null) {
21
+ set_signal_status(derived, CLEAN);
22
+ } else {
23
+ set_signal_status(derived, MAYBE_DIRTY);
24
+ }
25
+ }
@@ -0,0 +1,40 @@
1
+ /** @import { Derived, Effect, Value } from '#client' */
2
+ import { CLEAN, DERIVED, DIRTY, MAYBE_DIRTY, WAS_MARKED } from '#client/constants';
3
+ import { set_signal_status } from './status.js';
4
+
5
+ /**
6
+ * @param {Value[] | null} deps
7
+ */
8
+ function clear_marked(deps) {
9
+ if (deps === null) return;
10
+
11
+ for (const dep of deps) {
12
+ if ((dep.f & DERIVED) === 0 || (dep.f & WAS_MARKED) === 0) {
13
+ continue;
14
+ }
15
+
16
+ dep.f ^= WAS_MARKED;
17
+
18
+ clear_marked(/** @type {Derived} */ (dep).deps);
19
+ }
20
+ }
21
+
22
+ /**
23
+ * @param {Effect} effect
24
+ * @param {Set<Effect>} dirty_effects
25
+ * @param {Set<Effect>} maybe_dirty_effects
26
+ */
27
+ export function defer_effect(effect, dirty_effects, maybe_dirty_effects) {
28
+ if ((effect.f & DIRTY) !== 0) {
29
+ dirty_effects.add(effect);
30
+ } else if ((effect.f & MAYBE_DIRTY) !== 0) {
31
+ maybe_dirty_effects.add(effect);
32
+ }
33
+
34
+ // Since we're not executing these effects now, we need to clear any WAS_MARKED flags
35
+ // so that other batches can correctly reach these effects during their own traversal
36
+ clear_marked(effect.deps);
37
+
38
+ // mark as clean so they get scheduled if they depend on pending async state
39
+ set_signal_status(effect, CLEAN);
40
+ }
@@ -1,4 +1,4 @@
1
- /** @import { Derived, Effect, Reaction, Signal, Source, Value } from '#client' */
1
+ /** @import { Derived, Effect, Reaction, Source, Value } from '#client' */
2
2
  import { DEV } from 'esm-env';
3
3
  import { get_descriptors, get_prototype_of, index_of } from '../shared/utils.js';
4
4
  import {
@@ -28,7 +28,6 @@ import { old_values } from './reactivity/sources.js';
28
28
  import {
29
29
  destroy_derived_effects,
30
30
  execute_derived,
31
- current_async_effect,
32
31
  recent_async_deriveds,
33
32
  update_derived
34
33
  } from './reactivity/deriveds.js';
@@ -44,7 +43,6 @@ import {
44
43
  set_dev_current_component_function,
45
44
  set_dev_stack
46
45
  } from './context.js';
47
- import * as w from './warnings.js';
48
46
  import {
49
47
  Batch,
50
48
  batch_values,
@@ -56,6 +54,7 @@ import { handle_error } from './error-handling.js';
56
54
  import { UNINITIALIZED } from '../../constants.js';
57
55
  import { captured_signals } from './legacy.js';
58
56
  import { without_reactive_context } from './dom/elements/bindings/shared.js';
57
+ import { set_signal_status, update_derived_status } from './reactivity/status.js';
59
58
 
60
59
  export let is_updating_effect = false;
61
60
 
@@ -167,21 +166,18 @@ export function is_dirty(reaction) {
167
166
  }
168
167
 
169
168
  if ((flags & MAYBE_DIRTY) !== 0) {
170
- var dependencies = reaction.deps;
169
+ var dependencies = /** @type {Value[]} */ (reaction.deps);
170
+ var length = dependencies.length;
171
171
 
172
- if (dependencies !== null) {
173
- var length = dependencies.length;
172
+ for (var i = 0; i < length; i++) {
173
+ var dependency = dependencies[i];
174
174
 
175
- for (var i = 0; i < length; i++) {
176
- var dependency = dependencies[i];
177
-
178
- if (is_dirty(/** @type {Derived} */ (dependency))) {
179
- update_derived(/** @type {Derived} */ (dependency));
180
- }
175
+ if (is_dirty(/** @type {Derived} */ (dependency))) {
176
+ update_derived(/** @type {Derived} */ (dependency));
177
+ }
181
178
 
182
- if (dependency.wv > reaction.wv) {
183
- return true;
184
- }
179
+ if (dependency.wv > reaction.wv) {
180
+ return true;
185
181
  }
186
182
  }
187
183
 
@@ -374,16 +370,20 @@ function remove_reaction(signal, dependency) {
374
370
  // allows us to skip the expensive work of disconnecting and immediately reconnecting it
375
371
  (new_deps === null || !new_deps.includes(dependency))
376
372
  ) {
377
- set_signal_status(dependency, MAYBE_DIRTY);
373
+ var derived = /** @type {Derived} */ (dependency);
374
+
378
375
  // If we are working with a derived that is owned by an effect, then mark it as being
379
376
  // disconnected and remove the mark flag, as it cannot be reliably removed otherwise
380
- if ((dependency.f & CONNECTED) !== 0) {
381
- dependency.f ^= CONNECTED;
382
- dependency.f &= ~WAS_MARKED;
377
+ if ((derived.f & CONNECTED) !== 0) {
378
+ derived.f ^= CONNECTED;
379
+ derived.f &= ~WAS_MARKED;
383
380
  }
381
+
382
+ update_derived_status(derived);
383
+
384
384
  // Disconnect any reactions owned by this reaction
385
- destroy_derived_effects(/** @type {Derived} **/ (dependency));
386
- remove_reactions(/** @type {Derived} **/ (dependency), 0);
385
+ destroy_derived_effects(derived);
386
+ remove_reactions(derived, 0);
387
387
  }
388
388
  }
389
389
 
@@ -596,14 +596,14 @@ export function get(signal) {
596
596
  }
597
597
  }
598
598
 
599
- if (is_destroying_effect) {
600
- if (old_values.has(signal)) {
601
- return old_values.get(signal);
602
- }
599
+ if (is_destroying_effect && old_values.has(signal)) {
600
+ return old_values.get(signal);
601
+ }
603
602
 
604
- if (is_derived) {
605
- var derived = /** @type {Derived} */ (signal);
603
+ if (is_derived) {
604
+ var derived = /** @type {Derived} */ (signal);
606
605
 
606
+ if (is_destroying_effect) {
607
607
  var value = derived.v;
608
608
 
609
609
  // if the derived is dirty and has reactions, or depends on the values that just changed, re-execute
@@ -619,17 +619,28 @@ export function get(signal) {
619
619
 
620
620
  return value;
621
621
  }
622
- } else if (
623
- is_derived &&
624
- (!batch_values?.has(signal) || (current_batch?.is_fork && !effect_tracking()))
625
- ) {
626
- derived = /** @type {Derived} */ (signal);
622
+
623
+ // connect disconnected deriveds if we are reading them inside an effect,
624
+ // or inside another derived that is already connected
625
+ var should_connect =
626
+ (derived.f & CONNECTED) === 0 &&
627
+ !untracking &&
628
+ active_reaction !== null &&
629
+ (is_updating_effect || (active_reaction.f & CONNECTED) !== 0);
630
+
631
+ var is_new = derived.deps === null;
627
632
 
628
633
  if (is_dirty(derived)) {
634
+ if (should_connect) {
635
+ // set the flag before `update_derived`, so that the derived
636
+ // is added as a reaction to its dependencies
637
+ derived.f |= CONNECTED;
638
+ }
639
+
629
640
  update_derived(derived);
630
641
  }
631
642
 
632
- if (is_updating_effect && effect_tracking() && (derived.f & CONNECTED) === 0) {
643
+ if (should_connect && !is_new) {
633
644
  reconnect(derived);
634
645
  }
635
646
  }
@@ -653,7 +664,7 @@ export function get(signal) {
653
664
  function reconnect(derived) {
654
665
  if (derived.deps === null) return;
655
666
 
656
- derived.f ^= CONNECTED;
667
+ derived.f |= CONNECTED;
657
668
 
658
669
  for (const dep of derived.deps) {
659
670
  (dep.reactions ??= []).push(derived);
@@ -718,17 +729,6 @@ export function untrack(fn) {
718
729
  }
719
730
  }
720
731
 
721
- const STATUS_MASK = ~(DIRTY | MAYBE_DIRTY | CLEAN);
722
-
723
- /**
724
- * @param {Signal} signal
725
- * @param {number} status
726
- * @returns {void}
727
- */
728
- export function set_signal_status(signal, status) {
729
- signal.f = (signal.f & STATUS_MASK) | status;
730
- }
731
-
732
732
  /**
733
733
  * @param {Record<string | symbol, unknown>} obj
734
734
  * @param {Array<string | symbol>} keys
@@ -3,7 +3,7 @@ import { DIRTY, LEGACY_PROPS, MAYBE_DIRTY } from '../internal/client/constants.j
3
3
  import { user_pre_effect } from '../internal/client/reactivity/effects.js';
4
4
  import { mutable_source, set } from '../internal/client/reactivity/sources.js';
5
5
  import { hydrate, mount, unmount } from '../internal/client/render.js';
6
- import { active_effect, get, set_signal_status } from '../internal/client/runtime.js';
6
+ import { active_effect, get } from '../internal/client/runtime.js';
7
7
  import { flushSync } from '../internal/client/reactivity/batch.js';
8
8
  import { define_property, is_array } from '../internal/shared/utils.js';
9
9
  import * as e from '../internal/client/errors.js';
@@ -12,6 +12,7 @@ import { DEV } from 'esm-env';
12
12
  import { FILENAME } from '../constants.js';
13
13
  import { component_context, dev_current_component_function } from '../internal/client/context.js';
14
14
  import { async_mode_flag } from '../internal/flags/index.js';
15
+ import { set_signal_status } from '../internal/client/reactivity/status.js';
15
16
 
16
17
  /**
17
18
  * Takes the same options as a Svelte 4 component and the component function and returns a Svelte 4 compatible component.
package/src/version.js CHANGED
@@ -4,5 +4,5 @@
4
4
  * The current version, as set in package.json.
5
5
  * @type {string}
6
6
  */
7
- export const VERSION = '5.46.0';
7
+ export const VERSION = '5.46.3';
8
8
  export const PUBLIC_VERSION = '5';