@tinymce/tinymce-svelte 4.0.1 → 4.0.2-feature.20260514214723764.sha51bac15

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.
package/dist/index.mjs CHANGED
@@ -97,6 +97,8 @@ const INERT = 1 << 13;
97
97
  const DESTROYED = 1 << 14;
98
98
  /** Set once a reaction has run for the first time */
99
99
  const REACTION_RAN = 1 << 15;
100
+ /** Effect is in the process of getting destroyed. Can be observed in child teardown functions */
101
+ const DESTROYING = 1 << 25;
100
102
 
101
103
  // Flags exclusive to effects
102
104
  /**
@@ -113,7 +115,8 @@ const USER_EFFECT = 1 << 20;
113
115
  /**
114
116
  * Tells that we marked this derived and its reactions as visited during the "mark as (maybe) dirty"-phase.
115
117
  * Will be lifted during execution of the derived and during checking its dirty state (both are necessary
116
- * because a derived might be checked but not executed).
118
+ * because a derived might be checked but not executed). This is a pure performance optimization flag and
119
+ * should not be used for any other purpose!
117
120
  */
118
121
  const WAS_MARKED = 1 << 16;
119
122
 
@@ -127,6 +130,10 @@ const STATE_SYMBOL = Symbol('$state');
127
130
  const LEGACY_PROPS = Symbol('legacy props');
128
131
  const LOADING_ATTR_SYMBOL = Symbol('');
129
132
  const PROXY_PATH_SYMBOL = Symbol('proxy path');
133
+ const ATTRIBUTES_CACHE = Symbol('attributes');
134
+ const CLASS_CACHE = Symbol('class');
135
+ /** An anchor might change, via this symbol on the original anchor we can tell HMR about the updated anchor */
136
+ const HMR_ANCHOR = Symbol('hmr anchor');
130
137
 
131
138
  /** allow users to ignore aborted signal errors if `reason.name === 'StaleReactionError` */
132
139
  const STALE_REACTION = new (class StaleReactionError extends Error {
@@ -136,6 +143,25 @@ const STALE_REACTION = new (class StaleReactionError extends Error {
136
143
 
137
144
  /* This file is generated by scripts/process-messages/index.js. Do not edit! */
138
145
 
146
+ /**
147
+ * An invariant violation occurred, meaning Svelte's internal assumptions were flawed. This is a bug in Svelte, not your app — please open an issue at https://github.com/sveltejs/svelte, citing the following message: "%message%"
148
+ * @param {string} message
149
+ * @returns {never}
150
+ */
151
+ function invariant_violation(message) {
152
+ if (DEV) {
153
+ const error = new Error(`invariant_violation\nAn invariant violation occurred, meaning Svelte's internal assumptions were flawed. This is a bug in Svelte, not your app — please open an issue at https://github.com/sveltejs/svelte, citing the following message: "${message}"\nhttps://svelte.dev/e/invariant_violation`);
154
+
155
+ error.name = 'Svelte error';
156
+
157
+ throw error;
158
+ } else {
159
+ throw new Error(`https://svelte.dev/e/invariant_violation`);
160
+ }
161
+ }
162
+
163
+ /* This file is generated by scripts/process-messages/index.js. Do not edit! */
164
+
139
165
  /**
140
166
  * Cannot create a `$derived(...)` with an `await` expression outside of an effect tree
141
167
  * @returns {never}
@@ -338,6 +364,18 @@ function state_unsafe_mutation() {
338
364
  var bold$1 = 'font-weight: bold';
339
365
  var normal$1 = 'font-weight: normal';
340
366
 
367
+ /**
368
+ * Detected reactivity loss when reading `%name%`. This happens when state is read in an async function after an earlier `await`
369
+ * @param {string} name
370
+ */
371
+ function await_reactivity_loss(name) {
372
+ if (DEV) {
373
+ console.warn(`%c[svelte] await_reactivity_loss\n%cDetected reactivity loss when reading \`${name}\`. This happens when state is read in an async function after an earlier \`await\`\nhttps://svelte.dev/e/await_reactivity_loss`, bold$1, normal$1);
374
+ } else {
375
+ console.warn(`https://svelte.dev/e/await_reactivity_loss`);
376
+ }
377
+ }
378
+
341
379
  /**
342
380
  * An async derived, `%name%` (%location%) was not read immediately after it resolved. This often indicates an unnecessary waterfall, which can slow down your app
343
381
  * @param {string} name
@@ -351,6 +389,17 @@ function await_waterfall(name, location) {
351
389
  }
352
390
  }
353
391
 
392
+ /**
393
+ * Reading a derived belonging to a now-destroyed effect may result in stale values
394
+ */
395
+ function derived_inert() {
396
+ if (DEV) {
397
+ console.warn(`%c[svelte] derived_inert\n%cReading a derived belonging to a now-destroyed effect may result in stale values\nhttps://svelte.dev/e/derived_inert`, bold$1, normal$1);
398
+ } else {
399
+ console.warn(`https://svelte.dev/e/derived_inert`);
400
+ }
401
+ }
402
+
354
403
  /** @import { Equals } from '#client' */
355
404
 
356
405
  /** @type {Equals} */
@@ -630,6 +679,18 @@ function get_stack() {
630
679
  return new_lines;
631
680
  }
632
681
 
682
+ /**
683
+ * @param {boolean} condition
684
+ * @param {string} message
685
+ */
686
+ function invariant(condition, message) {
687
+ if (!DEV) {
688
+ throw new Error('invariant(...) was not guarded by if (DEV)');
689
+ }
690
+
691
+ if (!condition) invariant_violation(message);
692
+ }
693
+
633
694
  /** @import { ComponentContext, DevStackEntry, Effect } from '#client' */
634
695
 
635
696
  /** @type {ComponentContext | null} */
@@ -679,6 +740,7 @@ function push(props, runes = false, fn) {
679
740
  e: null,
680
741
  s: props,
681
742
  x: null,
743
+ r: /** @type {Effect} */ (active_effect),
682
744
  l: null
683
745
  };
684
746
 
@@ -969,12 +1031,21 @@ function capture_store_binding(fn) {
969
1031
 
970
1032
  /** @import { Fork } from 'svelte' */
971
1033
 
972
- /** @type {Set<Batch>} */
973
- const batches = new Set();
1034
+ /** @type {Batch | null} */
1035
+ let first_batch = null;
1036
+
1037
+ /** @type {Batch | null} */
1038
+ let last_batch = null;
974
1039
 
975
1040
  /** @type {Batch | null} */
976
1041
  let current_batch = null;
977
1042
 
1043
+ /**
1044
+ * This is needed to avoid overwriting inputs
1045
+ * @type {Batch | null}
1046
+ */
1047
+ let previous_batch = null;
1048
+
978
1049
  /**
979
1050
  * When time travelling (i.e. working in one batch, while other batches
980
1051
  * still have ongoing work), we ignore the real values of affected
@@ -1007,28 +1078,51 @@ let collected_effects = null;
1007
1078
  let legacy_updates = null;
1008
1079
 
1009
1080
  var flush_count = 0;
1010
- var source_stacks = DEV ? new Set() : null;
1081
+
1082
+ /** @type {Set<Value>} */
1083
+ var source_stacks = new Set();
1011
1084
 
1012
1085
  let uid = 1;
1013
1086
 
1014
1087
  class Batch {
1015
- // for debugging. TODO remove once async is stable
1016
1088
  id = uid++;
1017
1089
 
1090
+ /** True as soon as `#process` was called */
1091
+ #started = false;
1092
+
1093
+ linked = true;
1094
+
1095
+ /** @type {Batch | null} */
1096
+ #prev = null;
1097
+
1098
+ /** @type {Batch | null} */
1099
+ #next = null;
1100
+
1101
+ /** @type {Map<Effect, ReturnType<typeof deferred<any>>>} */
1102
+ async_deriveds = new Map();
1103
+
1018
1104
  /**
1019
- * The current values of any sources that are updated in this batch
1105
+ * The current values of any signals that are updated in this batch.
1106
+ * Tuple format: [value, is_derived] (note: is_derived is false for deriveds, too, if they were overridden via assignment)
1020
1107
  * They keys of this map are identical to `this.#previous`
1021
- * @type {Map<Source, any>}
1108
+ * @type {Map<Value, [any, boolean]>}
1022
1109
  */
1023
1110
  current = new Map();
1024
1111
 
1025
1112
  /**
1026
- * The values of any sources that are updated in this batch _before_ those updates took place.
1113
+ * The values of any signals (sources and deriveds) that are updated in this batch _before_ those updates took place.
1027
1114
  * They keys of this map are identical to `this.#current`
1028
- * @type {Map<Source, any>}
1115
+ * @type {Map<Value, any>}
1029
1116
  */
1030
1117
  previous = new Map();
1031
1118
 
1119
+ /**
1120
+ * Async effects which this batch doesn't take into account anymore when calculating blockers,
1121
+ * as it has a value for it already.
1122
+ * @type {Set<Effect>}
1123
+ */
1124
+ unblocked = new Set();
1125
+
1032
1126
  /**
1033
1127
  * When the batch is committed (and the DOM is updated), we need to remove old branches
1034
1128
  * and append new ones by calling the functions added inside (if/each/key/etc) blocks
@@ -1042,15 +1136,22 @@ class Batch {
1042
1136
  */
1043
1137
  #discard_callbacks = new Set();
1044
1138
 
1139
+ /**
1140
+ * Callbacks that should run only when a fork is committed.
1141
+ * @type {Set<(batch: Batch) => void>}
1142
+ */
1143
+ #fork_commit_callbacks = new Set();
1144
+
1045
1145
  /**
1046
1146
  * The number of async effects that are currently in flight
1047
1147
  */
1048
1148
  #pending = 0;
1049
1149
 
1050
1150
  /**
1051
- * The number of async effects that are currently in flight, _not_ inside a pending boundary
1151
+ * Async effects that are currently in flight, _not_ inside a pending boundary
1152
+ * @type {Map<Effect, number>}
1052
1153
  */
1053
- #blocking_pending = 0;
1154
+ #blocking_pending = new Map();
1054
1155
 
1055
1156
  /**
1056
1157
  * A deferred that resolves when the batch is committed, used with `settled()`
@@ -1065,6 +1166,12 @@ class Batch {
1065
1166
  */
1066
1167
  #roots = [];
1067
1168
 
1169
+ /**
1170
+ * Effects created while this batch was active.
1171
+ * @type {Effect[]}
1172
+ */
1173
+ #new_effects = [];
1174
+
1068
1175
  /**
1069
1176
  * Deferred effects (which run after async work has completed) that are DIRTY
1070
1177
  * @type {Set<Effect>}
@@ -1086,12 +1193,38 @@ class Batch {
1086
1193
  */
1087
1194
  #skipped_branches = new Map();
1088
1195
 
1196
+ /**
1197
+ * Inverse of #skipped_branches which we need to tell prior batches to unskip them when committing
1198
+ * @type {Set<Effect>}
1199
+ */
1200
+ #unskipped_branches = new Set();
1201
+
1089
1202
  is_fork = false;
1090
1203
 
1091
1204
  #decrement_queued = false;
1092
1205
 
1093
1206
  #is_deferred() {
1094
- return this.is_fork || this.#blocking_pending > 0;
1207
+ if (this.is_fork) return true;
1208
+
1209
+ for (const effect of this.#blocking_pending.keys()) {
1210
+ var e = effect;
1211
+ var skipped = false;
1212
+
1213
+ while (e.parent !== null) {
1214
+ if (this.#skipped_branches.has(e)) {
1215
+ skipped = true;
1216
+ break;
1217
+ }
1218
+
1219
+ e = e.parent;
1220
+ }
1221
+
1222
+ if (!skipped) {
1223
+ return true;
1224
+ }
1225
+ }
1226
+
1227
+ return false;
1095
1228
  }
1096
1229
 
1097
1230
  /**
@@ -1102,35 +1235,64 @@ class Batch {
1102
1235
  if (!this.#skipped_branches.has(effect)) {
1103
1236
  this.#skipped_branches.set(effect, { d: [], m: [] });
1104
1237
  }
1238
+ this.#unskipped_branches.delete(effect);
1105
1239
  }
1106
1240
 
1107
1241
  /**
1108
1242
  * Remove an effect from the #skipped_branches map and reschedule
1109
1243
  * any tracked dirty/maybe_dirty child effects
1110
1244
  * @param {Effect} effect
1245
+ * @param {(e: Effect) => void} callback
1111
1246
  */
1112
- unskip_effect(effect) {
1247
+ unskip_effect(effect, callback = (e) => this.schedule(e)) {
1113
1248
  var tracked = this.#skipped_branches.get(effect);
1114
1249
  if (tracked) {
1115
1250
  this.#skipped_branches.delete(effect);
1116
1251
 
1117
1252
  for (var e of tracked.d) {
1118
1253
  set_signal_status(e, DIRTY);
1119
- this.schedule(e);
1254
+ callback(e);
1120
1255
  }
1121
1256
 
1122
1257
  for (e of tracked.m) {
1123
1258
  set_signal_status(e, MAYBE_DIRTY);
1124
- this.schedule(e);
1259
+ callback(e);
1125
1260
  }
1126
1261
  }
1262
+ this.#unskipped_branches.add(effect);
1127
1263
  }
1128
1264
 
1129
1265
  #process() {
1266
+ this.#started = true;
1267
+
1130
1268
  if (flush_count++ > 1000) {
1269
+ this.#unlink();
1131
1270
  infinite_loop_guard();
1132
1271
  }
1133
1272
 
1273
+ if (DEV) {
1274
+ // track all the values that were updated during this flush,
1275
+ // so that they can be reset afterwards
1276
+ for (const value of this.current.keys()) {
1277
+ source_stacks.add(value);
1278
+ }
1279
+ }
1280
+
1281
+ // we only reschedule previously-deferred effects if we expect
1282
+ // to be able to run them after processing the batch
1283
+ if (!this.#is_deferred()) {
1284
+ for (const e of this.#dirty_effects) {
1285
+ this.#maybe_dirty_effects.delete(e);
1286
+ set_signal_status(e, DIRTY);
1287
+ this.schedule(e);
1288
+ }
1289
+
1290
+ for (const e of this.#maybe_dirty_effects) {
1291
+ set_signal_status(e, MAYBE_DIRTY);
1292
+ this.schedule(e);
1293
+ }
1294
+ }
1295
+
1134
1296
  const roots = this.#roots;
1135
1297
  this.#roots = [];
1136
1298
 
@@ -1149,7 +1311,12 @@ class Batch {
1149
1311
  var updates = (legacy_updates = []);
1150
1312
 
1151
1313
  for (const root of roots) {
1152
- this.#traverse(root, effects, render_effects);
1314
+ try {
1315
+ this.#traverse(root, effects, render_effects);
1316
+ } catch (e) {
1317
+ reset_all(root);
1318
+ throw e;
1319
+ }
1153
1320
  }
1154
1321
 
1155
1322
  // any writes should take effect in a subsequent batch
@@ -1165,6 +1332,7 @@ class Batch {
1165
1332
  collected_effects = null;
1166
1333
  legacy_updates = null;
1167
1334
 
1335
+ // if the batch has outstanding pending work, stash effects and bail
1168
1336
  if (this.#is_deferred()) {
1169
1337
  this.#defer_effects(render_effects);
1170
1338
  this.#defer_effects(effects);
@@ -1172,35 +1340,56 @@ class Batch {
1172
1340
  for (const [e, t] of this.#skipped_branches) {
1173
1341
  reset_branch(e, t);
1174
1342
  }
1175
- } else {
1176
- // clear effects. Those that are still needed will be rescheduled through unskipping the skipped branches.
1177
- this.#dirty_effects.clear();
1178
- this.#maybe_dirty_effects.clear();
1179
-
1180
- // append/remove branches
1181
- for (const fn of this.#commit_callbacks) fn(this);
1182
- this.#commit_callbacks.clear();
1183
- flush_queued_effects(render_effects);
1184
- flush_queued_effects(effects);
1185
-
1186
- if (this.#pending === 0) {
1187
- this.#commit();
1343
+
1344
+ if (updates.length > 0) {
1345
+ /** @type {Batch} */ (/** @type {unknown} */ (current_batch)).#process();
1188
1346
  }
1189
1347
 
1190
- this.#deferred?.resolve();
1348
+ return;
1349
+ }
1350
+
1351
+ const earlier_batch = this.#find_earlier_batch();
1352
+
1353
+ if (earlier_batch) {
1354
+ earlier_batch.#merge(this);
1355
+ return;
1191
1356
  }
1192
1357
 
1358
+ // clear effects. Those that are still needed will be rescheduled through unskipping the skipped branches.
1359
+ this.#dirty_effects.clear();
1360
+ this.#maybe_dirty_effects.clear();
1361
+
1362
+ // append/remove branches
1363
+ for (const fn of this.#commit_callbacks) fn(this);
1364
+ this.#commit_callbacks.clear();
1365
+
1366
+ previous_batch = this;
1367
+ flush_queued_effects(render_effects);
1368
+ flush_queued_effects(effects);
1369
+ previous_batch = null;
1370
+
1371
+ this.#deferred?.resolve();
1372
+
1193
1373
  var next_batch = /** @type {Batch | null} */ (/** @type {unknown} */ (current_batch));
1194
1374
 
1195
- if (next_batch !== null) {
1196
- batches.add(next_batch);
1375
+ if (this.linked && this.#pending === 0) {
1376
+ this.#unlink();
1377
+ }
1197
1378
 
1198
- if (DEV) {
1199
- for (const source of this.current.keys()) {
1200
- /** @type {Set<Source>} */ (source_stacks).add(source);
1201
- }
1379
+ // Edge case: During traversal new branches might create effects that run immediately and set state,
1380
+ // causing an effect and therefore a root to be scheduled again. We need to traverse the current batch
1381
+ // once more in that case - most of the time this will just clean up dirty branches.
1382
+ if (this.#roots.length > 0) {
1383
+ if (next_batch === null) {
1384
+ next_batch = this;
1385
+ this.#link();
1202
1386
  }
1203
1387
 
1388
+ const batch = next_batch;
1389
+ batch.#roots.push(...this.#roots.filter((r) => !batch.#roots.includes(r)));
1390
+ }
1391
+
1392
+ if (next_batch !== null) {
1204
1393
  next_batch.#process();
1205
1394
  }
1206
1395
  }
@@ -1255,6 +1444,82 @@ class Batch {
1255
1444
  }
1256
1445
  }
1257
1446
 
1447
+ #find_earlier_batch() {
1448
+ var batch = this.#prev;
1449
+
1450
+ while (batch !== null) {
1451
+ if (!batch.is_fork) {
1452
+ // if the batches are connected, break
1453
+ for (const [value, [, is_derived]] of this.current) {
1454
+ if (batch.current.has(value) && !is_derived) {
1455
+ return batch;
1456
+ }
1457
+ }
1458
+ }
1459
+
1460
+ batch = batch.#prev;
1461
+ }
1462
+
1463
+ return null;
1464
+ }
1465
+
1466
+ /**
1467
+ * @param {Batch} batch
1468
+ */
1469
+ #merge(batch) {
1470
+ for (const [source, value] of batch.current) {
1471
+ if (!this.previous.has(source) && batch.previous.has(source)) {
1472
+ this.previous.set(source, batch.previous.get(source));
1473
+ }
1474
+
1475
+ this.current.set(source, value);
1476
+ }
1477
+
1478
+ for (const [effect, deferred] of batch.async_deriveds) {
1479
+ const d = this.async_deriveds.get(effect);
1480
+ if (d) deferred.promise.then(d.resolve);
1481
+ }
1482
+
1483
+ /**
1484
+ * mark all effects that depend on `batch.current`, except the
1485
+ * async effects that we just resolved (TODO unless they depend
1486
+ * on values in this batch that are NOT in the later batch?).
1487
+ * Through this we also will populate the correct #skipped_branches,
1488
+ * oncommit callbacks etc, so we don't need to merge them separately.
1489
+ * @param {Value} value
1490
+ */
1491
+ const mark = (value) => {
1492
+ var reactions = value.reactions;
1493
+ if (reactions === null) return;
1494
+
1495
+ for (const reaction of reactions) {
1496
+ var flags = reaction.f;
1497
+
1498
+ if ((flags & DERIVED) !== 0) {
1499
+ mark(/** @type {Derived} */ (reaction));
1500
+ } else {
1501
+ var effect = /** @type {Effect} */ (reaction);
1502
+
1503
+ if (flags & (ASYNC | BLOCK_EFFECT) && !this.async_deriveds.has(effect)) {
1504
+ this.#maybe_dirty_effects.delete(effect);
1505
+ set_signal_status(effect, DIRTY);
1506
+ this.schedule(effect);
1507
+ }
1508
+ }
1509
+ }
1510
+ };
1511
+
1512
+ for (const source of this.current.keys()) {
1513
+ mark(source);
1514
+ }
1515
+
1516
+ this.oncommit(() => batch.discard());
1517
+ batch.#unlink();
1518
+
1519
+ current_batch = this;
1520
+ this.#process();
1521
+ }
1522
+
1258
1523
  /**
1259
1524
  * @param {Effect[]} effects
1260
1525
  */
@@ -1267,18 +1532,23 @@ class Batch {
1267
1532
  /**
1268
1533
  * Associate a change to a given source with the current
1269
1534
  * batch, noting its previous and current values
1270
- * @param {Source} source
1535
+ * @param {Value} source
1271
1536
  * @param {any} value
1537
+ * @param {boolean} [is_derived]
1272
1538
  */
1273
- capture(source, value) {
1274
- if (value !== UNINITIALIZED && !this.previous.has(source)) {
1275
- this.previous.set(source, value);
1539
+ capture(source, value, is_derived = false) {
1540
+ if (source.v !== UNINITIALIZED && !this.previous.has(source)) {
1541
+ this.previous.set(source, source.v);
1276
1542
  }
1277
1543
 
1278
1544
  // Don't save errors in `batch_values`, or they won't be thrown in `runtime.js#get`
1279
1545
  if ((source.f & ERROR_VALUE) === 0) {
1280
- this.current.set(source, source.v);
1281
- batch_values?.set(source, source.v);
1546
+ this.current.set(source, [value, is_derived]);
1547
+ batch_values?.set(source, value);
1548
+ }
1549
+
1550
+ if (!this.is_fork) {
1551
+ source.v = value;
1282
1552
  }
1283
1553
  }
1284
1554
 
@@ -1292,27 +1562,14 @@ class Batch {
1292
1562
  }
1293
1563
 
1294
1564
  flush() {
1295
- var source_stacks = DEV ? new Set() : null;
1296
-
1297
1565
  try {
1566
+ if (DEV) {
1567
+ source_stacks.clear();
1568
+ }
1569
+
1298
1570
  is_processing = true;
1299
1571
  current_batch = this;
1300
1572
 
1301
- // we only reschedule previously-deferred effects if we expect
1302
- // to be able to run them after processing the batch
1303
- if (!this.#is_deferred()) {
1304
- for (const e of this.#dirty_effects) {
1305
- this.#maybe_dirty_effects.delete(e);
1306
- set_signal_status(e, DIRTY);
1307
- this.schedule(e);
1308
- }
1309
-
1310
- for (const e of this.#maybe_dirty_effects) {
1311
- set_signal_status(e, MAYBE_DIRTY);
1312
- this.schedule(e);
1313
- }
1314
- }
1315
-
1316
1573
  this.#process();
1317
1574
  } finally {
1318
1575
  flush_count = 0;
@@ -1327,7 +1584,7 @@ class Batch {
1327
1584
  old_values.clear();
1328
1585
 
1329
1586
  if (DEV) {
1330
- for (const source of /** @type {Set<Source>} */ (source_stacks)) {
1587
+ for (const source of source_stacks) {
1331
1588
  source.updated = null;
1332
1589
  }
1333
1590
  }
@@ -1337,109 +1594,195 @@ class Batch {
1337
1594
  discard() {
1338
1595
  for (const fn of this.#discard_callbacks) fn(this);
1339
1596
  this.#discard_callbacks.clear();
1597
+ this.#fork_commit_callbacks.clear();
1598
+
1599
+ this.#unlink();
1600
+ }
1601
+
1602
+ /**
1603
+ * @param {Effect} effect
1604
+ */
1605
+ register_created_effect(effect) {
1606
+ this.#new_effects.push(effect);
1340
1607
  }
1341
1608
 
1342
1609
  #commit() {
1610
+ this.#unlink();
1611
+
1343
1612
  // If there are other pending batches, they now need to be 'rebased' —
1344
1613
  // in other words, we re-run block/async effects with the newly
1345
1614
  // committed state, unless the batch in question has a more
1346
1615
  // recent value for a given source
1347
- if (batches.size > 1) {
1348
- this.previous.clear();
1616
+ for (let batch = first_batch; batch !== null; batch = batch.#next) {
1617
+ var is_earlier = batch.id < this.id;
1349
1618
 
1350
- var previous_batch = current_batch;
1351
- var previous_batch_values = batch_values;
1352
- var is_earlier = true;
1619
+ /** @type {Source[]} */
1620
+ var sources = [];
1353
1621
 
1354
- for (const batch of batches) {
1355
- if (batch === this) {
1356
- is_earlier = false;
1357
- continue;
1358
- }
1622
+ for (const [source, [value, is_derived]] of this.current) {
1623
+ if (batch.current.has(source)) {
1624
+ var batch_value = /** @type {[any, boolean]} */ (batch.current.get(source))[0]; // faster than destructuring
1359
1625
 
1360
- /** @type {Source[]} */
1361
- const sources = [];
1362
-
1363
- for (const [source, value] of this.current) {
1364
- if (batch.current.has(source)) {
1365
- if (is_earlier && value !== batch.current.get(source)) {
1366
- // bring the value up to date
1367
- batch.current.set(source, value);
1368
- } else {
1369
- // same value or later batch has more recent value,
1370
- // no need to re-run these effects
1371
- continue;
1372
- }
1626
+ if (is_earlier && value !== batch_value) {
1627
+ // bring the value up to date
1628
+ batch.current.set(source, [value, is_derived]);
1629
+ } else {
1630
+ // same value or later batch has more recent value,
1631
+ // no need to re-run these effects
1632
+ continue;
1373
1633
  }
1634
+ }
1374
1635
 
1375
- sources.push(source);
1636
+ sources.push(source);
1637
+ }
1638
+
1639
+ if (is_earlier) {
1640
+ // TODO do we need to restart these in some cases, instead of
1641
+ // immediately resolving them? Likely not because of how this.apply() works.
1642
+ for (const [effect, deferred] of this.async_deriveds) {
1643
+ const d = batch.async_deriveds.get(effect);
1644
+ if (d) deferred.promise.then(d.resolve);
1376
1645
  }
1646
+ }
1377
1647
 
1378
- if (sources.length === 0) {
1379
- continue;
1648
+ if (!batch.#started) continue;
1649
+
1650
+ // Re-run async/block effects that depend on distinct values changed in both batches
1651
+ var others = [...batch.current.keys()].filter((s) => !this.current.has(s));
1652
+
1653
+ if (others.length === 0) {
1654
+ if (is_earlier) {
1655
+ // this batch is now obsolete and can be discarded
1656
+ batch.discard();
1657
+ }
1658
+ } else if (sources.length > 0) {
1659
+ if (DEV) {
1660
+ invariant(batch.#roots.length === 0, 'Batch has scheduled roots');
1380
1661
  }
1381
1662
 
1382
- // Re-run async/block effects that depend on distinct values changed in both batches
1383
- const others = [...batch.current.keys()].filter((s) => !this.current.has(s));
1384
- if (others.length > 0) {
1385
- batch.activate();
1386
-
1387
- /** @type {Set<Value>} */
1388
- const marked = new Set();
1389
- /** @type {Map<Reaction, boolean>} */
1390
- const checked = new Map();
1391
- for (const source of sources) {
1392
- mark_effects(source, others, marked, checked);
1663
+ // A batch was unskipped in a later batch -> tell prior batches to unskip it, too
1664
+ if (is_earlier) {
1665
+ for (const unskipped of this.#unskipped_branches) {
1666
+ batch.unskip_effect(unskipped, (e) => {
1667
+ if ((e.f & (BLOCK_EFFECT | ASYNC)) !== 0) {
1668
+ batch.schedule(e);
1669
+ } else {
1670
+ batch.#defer_effects([e]);
1671
+ }
1672
+ });
1393
1673
  }
1674
+ }
1394
1675
 
1395
- if (batch.#roots.length > 0) {
1396
- batch.apply();
1676
+ batch.activate();
1397
1677
 
1398
- for (const root of batch.#roots) {
1399
- batch.#traverse(root, [], []);
1678
+ /** @type {Set<Value>} */
1679
+ var marked = new Set();
1680
+
1681
+ /** @type {Map<Reaction, boolean>} */
1682
+ var checked = new Map();
1683
+
1684
+ for (var source of sources) {
1685
+ mark_effects(source, others, marked, checked);
1686
+ }
1687
+
1688
+ checked = new Map();
1689
+ var current_unequal = [...batch.current.keys()].filter((c) =>
1690
+ this.current.has(c)
1691
+ ? /** @type {[any, boolean]} */ (this.current.get(c))[0] !== c.v
1692
+ : true
1693
+ );
1694
+
1695
+ if (current_unequal.length > 0) {
1696
+ for (const effect of this.#new_effects) {
1697
+ if (
1698
+ (effect.f & (DESTROYED | INERT | EAGER_EFFECT)) === 0 &&
1699
+ depends_on(effect, current_unequal, checked)
1700
+ ) {
1701
+ if ((effect.f & (ASYNC | BLOCK_EFFECT)) !== 0) {
1702
+ set_signal_status(effect, DIRTY);
1703
+ batch.schedule(effect);
1704
+ } else {
1705
+ batch.#dirty_effects.add(effect);
1706
+ }
1400
1707
  }
1708
+ }
1709
+ }
1401
1710
 
1402
- // TODO do we need to do anything with the dummy effect arrays?
1711
+ // Only apply and traverse when we know we triggered async work with marking the effects
1712
+ if (batch.#roots.length > 0) {
1713
+ batch.apply();
1714
+
1715
+ for (var root of batch.#roots) {
1716
+ batch.#traverse(root, [], []);
1403
1717
  }
1404
1718
 
1405
- batch.deactivate();
1719
+ batch.#roots = [];
1406
1720
  }
1407
- }
1408
1721
 
1409
- current_batch = previous_batch;
1410
- batch_values = previous_batch_values;
1722
+ batch.deactivate();
1723
+ }
1411
1724
  }
1412
-
1413
- this.#skipped_branches.clear();
1414
- batches.delete(this);
1415
1725
  }
1416
1726
 
1417
1727
  /**
1418
- *
1419
1728
  * @param {boolean} blocking
1729
+ * @param {Effect} effect
1420
1730
  */
1421
- increment(blocking) {
1731
+ increment(blocking, effect) {
1422
1732
  this.#pending += 1;
1423
- if (blocking) this.#blocking_pending += 1;
1733
+
1734
+ if (blocking) {
1735
+ let blocking_pending_count = this.#blocking_pending.get(effect) ?? 0;
1736
+ this.#blocking_pending.set(effect, blocking_pending_count + 1);
1737
+ }
1424
1738
  }
1425
1739
 
1426
1740
  /**
1427
1741
  * @param {boolean} blocking
1428
- * @param {boolean} skip - whether to skip updates (because this is triggered by a stale reaction)
1742
+ * @param {Effect} effect
1429
1743
  */
1430
- decrement(blocking, skip) {
1744
+ decrement(blocking, effect) {
1431
1745
  this.#pending -= 1;
1432
- if (blocking) this.#blocking_pending -= 1;
1433
1746
 
1434
- if (this.#decrement_queued || skip) return;
1747
+ if (blocking) {
1748
+ let blocking_pending_count = this.#blocking_pending.get(effect) ?? 0;
1749
+
1750
+ if (blocking_pending_count === 1) {
1751
+ this.#blocking_pending.delete(effect);
1752
+ } else {
1753
+ this.#blocking_pending.set(effect, blocking_pending_count - 1);
1754
+ }
1755
+ }
1756
+
1757
+ if (this.#decrement_queued) return;
1435
1758
  this.#decrement_queued = true;
1436
1759
 
1437
1760
  queue_micro_task(() => {
1438
1761
  this.#decrement_queued = false;
1439
- this.flush();
1762
+
1763
+ if (this.linked) {
1764
+ this.flush();
1765
+ }
1440
1766
  });
1441
1767
  }
1442
1768
 
1769
+ /**
1770
+ * @param {Set<Effect>} dirty_effects
1771
+ * @param {Set<Effect>} maybe_dirty_effects
1772
+ */
1773
+ transfer_effects(dirty_effects, maybe_dirty_effects) {
1774
+ for (const e of dirty_effects) {
1775
+ this.#dirty_effects.add(e);
1776
+ }
1777
+
1778
+ for (const e of maybe_dirty_effects) {
1779
+ this.#maybe_dirty_effects.add(e);
1780
+ }
1781
+
1782
+ dirty_effects.clear();
1783
+ maybe_dirty_effects.clear();
1784
+ }
1785
+
1443
1786
  /** @param {(batch: Batch) => void} fn */
1444
1787
  oncommit(fn) {
1445
1788
  this.#commit_callbacks.add(fn);
@@ -1450,6 +1793,16 @@ class Batch {
1450
1793
  this.#discard_callbacks.add(fn);
1451
1794
  }
1452
1795
 
1796
+ /** @param {(batch: Batch) => void} fn */
1797
+ on_fork_commit(fn) {
1798
+ this.#fork_commit_callbacks.add(fn);
1799
+ }
1800
+
1801
+ run_fork_commit_callbacks() {
1802
+ for (const fn of this.#fork_commit_callbacks) fn(this);
1803
+ this.#fork_commit_callbacks.clear();
1804
+ }
1805
+
1453
1806
  settled() {
1454
1807
  return (this.#deferred ??= deferred()).promise;
1455
1808
  }
@@ -1457,20 +1810,14 @@ class Batch {
1457
1810
  static ensure() {
1458
1811
  if (current_batch === null) {
1459
1812
  const batch = (current_batch = new Batch());
1813
+ batch.#link();
1460
1814
 
1461
- if (!is_processing) {
1462
- batches.add(current_batch);
1463
-
1464
- {
1465
- queue_micro_task(() => {
1466
- if (current_batch !== batch) {
1467
- // a flushSync happened in the meantime
1468
- return;
1469
- }
1470
-
1815
+ if (!is_processing && !is_flushing_sync) {
1816
+ queue_micro_task(() => {
1817
+ if (!batch.#started) {
1471
1818
  batch.flush();
1472
- });
1473
- }
1819
+ }
1820
+ });
1474
1821
  }
1475
1822
  }
1476
1823
 
@@ -1478,7 +1825,10 @@ class Batch {
1478
1825
  }
1479
1826
 
1480
1827
  apply() {
1481
- return;
1828
+ {
1829
+ batch_values = null;
1830
+ return;
1831
+ }
1482
1832
  }
1483
1833
 
1484
1834
  /**
@@ -1536,6 +1886,36 @@ class Batch {
1536
1886
 
1537
1887
  this.#roots.push(e);
1538
1888
  }
1889
+
1890
+ #link() {
1891
+ if (last_batch === null) {
1892
+ first_batch = last_batch = this;
1893
+ } else {
1894
+ last_batch.#next = this;
1895
+ this.#prev = last_batch;
1896
+ }
1897
+
1898
+ last_batch = this;
1899
+ }
1900
+
1901
+ #unlink() {
1902
+ var prev = this.#prev;
1903
+ var next = this.#next;
1904
+
1905
+ if (prev === null) {
1906
+ first_batch = next;
1907
+ } else {
1908
+ prev.#next = next;
1909
+ }
1910
+
1911
+ if (next === null) {
1912
+ last_batch = prev;
1913
+ } else {
1914
+ next.#prev = prev;
1915
+ }
1916
+
1917
+ this.linked = false;
1918
+ }
1539
1919
  }
1540
1920
 
1541
1921
  function infinite_loop_guard() {
@@ -1745,6 +2125,20 @@ function reset_branch(effect, tracked) {
1745
2125
  }
1746
2126
  }
1747
2127
 
2128
+ /**
2129
+ * Mark an entire effect tree clean following an error
2130
+ * @param {Effect} effect
2131
+ */
2132
+ function reset_all(effect) {
2133
+ set_signal_status(effect, CLEAN);
2134
+
2135
+ var e = effect.first;
2136
+ while (e !== null) {
2137
+ reset_all(e);
2138
+ e = e.next;
2139
+ }
2140
+ }
2141
+
1748
2142
  /** @import { Blocker, Effect, Value } from '#client' */
1749
2143
 
1750
2144
  /**
@@ -1776,33 +2170,38 @@ function flatten(blockers, sync, async, fn) {
1776
2170
 
1777
2171
  /** @param {Value[]} values */
1778
2172
  function finish(values) {
2173
+ if ((parent.f & DESTROYED) !== 0) {
2174
+ return;
2175
+ }
2176
+
1779
2177
  restore();
1780
2178
 
1781
2179
  try {
1782
2180
  fn(values);
1783
2181
  } catch (error) {
1784
- if ((parent.f & DESTROYED) === 0) {
1785
- invoke_error_boundary(error, parent);
1786
- }
2182
+ invoke_error_boundary(error, parent);
1787
2183
  }
1788
2184
 
1789
2185
  unset_context();
1790
2186
  }
1791
2187
 
2188
+ var decrement_pending = increment_pending();
2189
+
1792
2190
  // Fast path: blockers but no async expressions
1793
2191
  if (async.length === 0) {
1794
- /** @type {Promise<any>} */ (blocker_promise).then(() => finish(sync.map(d)));
2192
+ /** @type {Promise<any>} */ (blocker_promise)
2193
+ .then(() => finish(sync.map(d)))
2194
+ .finally(decrement_pending);
2195
+
1795
2196
  return;
1796
2197
  }
1797
2198
 
1798
- var decrement_pending = increment_pending();
1799
-
1800
2199
  // Full path: has async expressions
1801
2200
  function run() {
1802
2201
  Promise.all(async.map((expression) => async_derived(expression)))
1803
2202
  .then((result) => finish([...sync.map(d), ...result]))
1804
2203
  .catch((error) => invoke_error_boundary(error, parent))
1805
- .finally(() => decrement_pending());
2204
+ .finally(decrement_pending);
1806
2205
  }
1807
2206
 
1808
2207
  if (blocker_promise) {
@@ -1844,6 +2243,7 @@ function capture() {
1844
2243
  }
1845
2244
 
1846
2245
  if (DEV) {
2246
+ set_reactivity_loss_tracker(null);
1847
2247
  set_dev_stack(previous_dev_stack);
1848
2248
  }
1849
2249
  };
@@ -1856,6 +2256,7 @@ function unset_context(deactivate_batch = true) {
1856
2256
  if (deactivate_batch) current_batch?.deactivate();
1857
2257
 
1858
2258
  if (DEV) {
2259
+ set_reactivity_loss_tracker(null);
1859
2260
  set_dev_stack(null);
1860
2261
  }
1861
2262
  }
@@ -1864,20 +2265,33 @@ function unset_context(deactivate_batch = true) {
1864
2265
  * @returns {(skip?: boolean) => void}
1865
2266
  */
1866
2267
  function increment_pending() {
1867
- var boundary = /** @type {Boundary} */ (/** @type {Effect} */ (active_effect).b);
2268
+ var effect = /** @type {Effect} */ (active_effect);
2269
+ var boundary = /** @type {Boundary} */ (effect.b);
1868
2270
  var batch = /** @type {Batch} */ (current_batch);
1869
2271
  var blocking = boundary.is_rendered();
1870
2272
 
1871
2273
  boundary.update_pending_count(1, batch);
1872
- batch.increment(blocking);
2274
+ batch.increment(blocking, effect);
1873
2275
 
1874
- return (skip = false) => {
2276
+ return () => {
1875
2277
  boundary.update_pending_count(-1, batch);
1876
- batch.decrement(blocking, skip);
2278
+ batch.decrement(blocking, effect);
1877
2279
  };
1878
2280
  }
1879
2281
 
1880
- /** @import { Derived, Effect, Source } from '#client' */
2282
+ /** @import { Derived, Effect, Reaction, Source, Value } from '#client' */
2283
+
2284
+ /**
2285
+ * This allows us to track 'reactivity loss' that occurs when signals
2286
+ * are read after a non-context-restoring `await`. Dev-only
2287
+ * @type {{ effect: Effect, effect_deps: Set<Value>, warned: boolean } | null}
2288
+ */
2289
+ let reactivity_loss_tracker = null;
2290
+
2291
+ /** @param {{ effect: Effect, effect_deps: Set<Value>, warned: boolean } | null} v */
2292
+ function set_reactivity_loss_tracker(v) {
2293
+ reactivity_loss_tracker = v;
2294
+ }
1881
2295
 
1882
2296
  const recent_async_deriveds = new Set();
1883
2297
 
@@ -1889,10 +2303,6 @@ const recent_async_deriveds = new Set();
1889
2303
  /*#__NO_SIDE_EFFECTS__*/
1890
2304
  function derived(fn) {
1891
2305
  var flags = DERIVED | DIRTY;
1892
- var parent_derived =
1893
- active_reaction !== null && (active_reaction.f & DERIVED) !== 0
1894
- ? /** @type {Derived} */ (active_reaction)
1895
- : null;
1896
2306
 
1897
2307
  if (active_effect !== null) {
1898
2308
  // Since deriveds are evaluated lazily, any effects created inside them are
@@ -1912,7 +2322,7 @@ function derived(fn) {
1912
2322
  rv: 0,
1913
2323
  v: /** @type {V} */ (UNINITIALIZED),
1914
2324
  wv: 0,
1915
- parent: parent_derived ?? active_effect,
2325
+ parent: active_effect,
1916
2326
  ac: null
1917
2327
  };
1918
2328
 
@@ -1923,6 +2333,8 @@ function derived(fn) {
1923
2333
  return signal;
1924
2334
  }
1925
2335
 
2336
+ const OBSOLETE = Symbol('obsolete');
2337
+
1926
2338
  /**
1927
2339
  * @template V
1928
2340
  * @param {() => V | Promise<V>} fn
@@ -1941,18 +2353,21 @@ function async_derived(fn, label, location) {
1941
2353
  var promise = /** @type {Promise<V>} */ (/** @type {unknown} */ (undefined));
1942
2354
  var signal = source(/** @type {V} */ (UNINITIALIZED));
1943
2355
 
1944
- if (DEV) signal.label = label;
2356
+ if (DEV) signal.label = label ?? fn.toString();
1945
2357
 
1946
2358
  // only suspend in async deriveds created on initialisation
1947
2359
  var should_suspend = !active_reaction;
1948
2360
 
1949
- /** @type {Map<Batch, ReturnType<typeof deferred<V>>>} */
1950
- var deferreds = new Map();
2361
+ /** @type {Set<ReturnType<typeof deferred<V>>>} */
2362
+ var deferreds = new Set();
1951
2363
 
1952
2364
  async_effect(() => {
1953
-
1954
2365
  var effect = /** @type {Effect} */ (active_effect);
1955
2366
 
2367
+ if (DEV) {
2368
+ reactivity_loss_tracker = { effect, effect_deps: new Set(), warned: false };
2369
+ }
2370
+
1956
2371
  /** @type {ReturnType<typeof deferred<V>>} */
1957
2372
  var d = deferred();
1958
2373
  promise = d.promise;
@@ -1961,12 +2376,40 @@ function async_derived(fn, label, location) {
1961
2376
  // If this code is changed at some point, make sure to still access the then property
1962
2377
  // of fn() to read any signals it might access, so that we track them as dependencies.
1963
2378
  // We call `unset_context` to undo any `save` calls that happen inside `fn()`
1964
- Promise.resolve(fn()).then(d.resolve, d.reject).finally(unset_context);
2379
+ Promise.resolve(fn())
2380
+ .then(d.resolve, (e) => {
2381
+ // if the promise was rejected by the user, via `getAbortSignal`, then
2382
+ // wait for a subsequent resolution instead of flushing the batch
2383
+ if (e !== STALE_REACTION) d.reject(e);
2384
+ })
2385
+ .finally(unset_context);
1965
2386
  } catch (error) {
1966
2387
  d.reject(error);
1967
2388
  unset_context();
1968
2389
  }
1969
2390
 
2391
+ if (DEV) {
2392
+ if (reactivity_loss_tracker) {
2393
+ // Reused deps from previous run (indices 0 to skipped_deps-1)
2394
+ // We deliberately only track direct dependencies of the async expression to encourage
2395
+ // dependencies being directly visible at the point of the expression
2396
+ if (effect.deps !== null) {
2397
+ for (let i = 0; i < skipped_deps; i += 1) {
2398
+ reactivity_loss_tracker.effect_deps.add(effect.deps[i]);
2399
+ }
2400
+ }
2401
+
2402
+ // New deps discovered this run
2403
+ if (new_deps !== null) {
2404
+ for (let i = 0; i < new_deps.length; i += 1) {
2405
+ reactivity_loss_tracker.effect_deps.add(new_deps[i]);
2406
+ }
2407
+ }
2408
+ }
2409
+
2410
+ reactivity_loss_tracker = null;
2411
+ }
2412
+
1970
2413
  var batch = /** @type {Batch} */ (current_batch);
1971
2414
 
1972
2415
  if (should_suspend) {
@@ -1978,18 +2421,17 @@ function async_derived(fn, label, location) {
1978
2421
  }
1979
2422
 
1980
2423
  if (/** @type {Boundary} */ (parent.b).is_rendered()) {
1981
- deferreds.get(batch)?.reject(STALE_REACTION);
1982
- deferreds.delete(batch); // delete to ensure correct order in Map iteration below
2424
+ batch.async_deriveds.get(effect)?.reject(OBSOLETE);
1983
2425
  } else {
1984
2426
  // While the boundary is still showing pending, a new run supersedes all older in-flight runs
1985
2427
  // for this async expression. Cancel eagerly so resolution cannot commit stale values.
1986
2428
  for (const d of deferreds.values()) {
1987
- d.reject(STALE_REACTION);
2429
+ d.reject(OBSOLETE);
1988
2430
  }
1989
- deferreds.clear();
1990
2431
  }
1991
2432
 
1992
- deferreds.set(batch, d);
2433
+ deferreds.add(d);
2434
+ batch.async_deriveds.set(effect, d);
1993
2435
  }
1994
2436
 
1995
2437
  /**
@@ -1997,17 +2439,14 @@ function async_derived(fn, label, location) {
1997
2439
  * @param {unknown} error
1998
2440
  */
1999
2441
  const handler = (value, error = undefined) => {
2000
-
2001
- if (decrement_pending) {
2002
- // don't trigger an update if we're only here because
2003
- // the promise was superseded before it could resolve
2004
- var skip = error === STALE_REACTION;
2005
- decrement_pending(skip);
2442
+ if (DEV) {
2443
+ reactivity_loss_tracker = null;
2006
2444
  }
2007
2445
 
2008
- if (error === STALE_REACTION || (effect.f & DESTROYED) !== 0) {
2009
- return;
2010
- }
2446
+ decrement_pending?.();
2447
+ deferreds.delete(d);
2448
+
2449
+ if (error === OBSOLETE) return;
2011
2450
 
2012
2451
  batch.activate();
2013
2452
 
@@ -2023,18 +2462,11 @@ function async_derived(fn, label, location) {
2023
2462
 
2024
2463
  internal_set(signal, value);
2025
2464
 
2026
- // All prior async derived runs are now stale
2027
- for (const [b, d] of deferreds) {
2028
- deferreds.delete(b);
2029
- if (b === batch) break;
2030
- d.reject(STALE_REACTION);
2031
- }
2032
-
2033
2465
  if (DEV && location !== undefined) {
2034
2466
  recent_async_deriveds.add(signal);
2035
2467
 
2036
2468
  setTimeout(() => {
2037
- if (recent_async_deriveds.has(signal)) {
2469
+ if (recent_async_deriveds.has(signal) && (effect.f & DESTROYED) === 0) {
2038
2470
  await_waterfall(/** @type {string} */ (signal.label), location);
2039
2471
  recent_async_deriveds.delete(signal);
2040
2472
  }
@@ -2049,8 +2481,8 @@ function async_derived(fn, label, location) {
2049
2481
  });
2050
2482
 
2051
2483
  teardown(() => {
2052
- for (const d of deferreds.values()) {
2053
- d.reject(STALE_REACTION);
2484
+ for (const d of deferreds) {
2485
+ d.reject(OBSOLETE);
2054
2486
  }
2055
2487
  });
2056
2488
 
@@ -2115,23 +2547,6 @@ function destroy_derived_effects(derived) {
2115
2547
  */
2116
2548
  let stack = [];
2117
2549
 
2118
- /**
2119
- * @param {Derived} derived
2120
- * @returns {Effect | null}
2121
- */
2122
- function get_derived_parent_effect(derived) {
2123
- var parent = derived.parent;
2124
- while (parent !== null) {
2125
- if ((parent.f & DERIVED) === 0) {
2126
- // The original parent effect might've been destroyed but the derived
2127
- // is used elsewhere now - do not return the destroyed effect in that case
2128
- return (parent.f & DESTROYED) === 0 ? /** @type {Effect} */ (parent) : null;
2129
- }
2130
- parent = parent.parent;
2131
- }
2132
- return null;
2133
- }
2134
-
2135
2550
  /**
2136
2551
  * @template T
2137
2552
  * @param {Derived} derived
@@ -2140,8 +2555,15 @@ function get_derived_parent_effect(derived) {
2140
2555
  function execute_derived(derived) {
2141
2556
  var value;
2142
2557
  var prev_active_effect = active_effect;
2558
+ var parent = derived.parent;
2559
+
2560
+ if (!is_destroying_effect && parent !== null && (parent.f & (DESTROYED | INERT)) !== 0) {
2561
+ derived_inert();
2143
2562
 
2144
- set_active_effect(get_derived_parent_effect(derived));
2563
+ return derived.v;
2564
+ }
2565
+
2566
+ set_active_effect(parent);
2145
2567
 
2146
2568
  if (DEV) {
2147
2569
  let prev_eager_effects = eager_effects;
@@ -2189,7 +2611,18 @@ function update_derived(derived) {
2189
2611
  // otherwise, the next time we get here after a 'real world' state
2190
2612
  // change, `derived.equals` may incorrectly return `true`
2191
2613
  if (!current_batch?.is_fork || derived.deps === null) {
2192
- derived.v = value;
2614
+ if (current_batch !== null) {
2615
+ // We also write to previous_batch because if it exists, it is a sign that we're
2616
+ // currently in the process of flushing effects. These updates to deriveds may belong
2617
+ // to the previous batch, not the new one (which can already exist if an earlier
2618
+ // effect wrote to a source). This can cause bugs when running batch.#commit() later,
2619
+ // but not adding it to current_batch can, too, so we add it to both.
2620
+ // See https://github.com/sveltejs/svelte/pull/18117 for more details.
2621
+ current_batch.capture(derived, value, true);
2622
+ previous_batch?.capture(derived, value, true);
2623
+ } else {
2624
+ derived.v = value;
2625
+ }
2193
2626
 
2194
2627
  // deriveds without dependencies should never be recomputed
2195
2628
  if (derived.deps === null) {
@@ -2260,7 +2693,7 @@ function unfreeze_derived_effects(derived) {
2260
2693
 
2261
2694
  /** @import { Derived, Effect, Source, Value } from '#client' */
2262
2695
 
2263
- /** @type {Set<any>} */
2696
+ /** @type {Set<Effect>} */
2264
2697
  let eager_effects = new Set();
2265
2698
 
2266
2699
  /** @type {Map<Source, any>} */
@@ -2359,18 +2792,10 @@ function set(source, value, should_proxy = false) {
2359
2792
  */
2360
2793
  function internal_set(source, value, updated_during_traversal = null) {
2361
2794
  if (!source.equals(value)) {
2362
- var old_value = source.v;
2363
-
2364
- if (is_destroying_effect) {
2365
- old_values.set(source, value);
2366
- } else {
2367
- old_values.set(source, old_value);
2368
- }
2369
-
2370
- source.v = value;
2795
+ old_values.set(source, is_destroying_effect ? value : source.v);
2371
2796
 
2372
2797
  var batch = Batch.ensure();
2373
- batch.capture(source, old_value);
2798
+ batch.capture(source, value);
2374
2799
 
2375
2800
  if (DEV) {
2376
2801
  if (active_effect !== null) {
@@ -2410,7 +2835,11 @@ function internal_set(source, value, updated_during_traversal = null) {
2410
2835
  execute_derived(derived);
2411
2836
  }
2412
2837
 
2413
- update_derived_status(derived);
2838
+ // During time traveling we don't want to reset the status so that
2839
+ // traversal of the graph in the other batches still happens
2840
+ if (batch_values === null) {
2841
+ update_derived_status(derived);
2842
+ }
2414
2843
  }
2415
2844
 
2416
2845
  source.wv = increment_write_version();
@@ -2453,7 +2882,18 @@ function flush_eager_effects() {
2453
2882
  set_signal_status(effect, MAYBE_DIRTY);
2454
2883
  }
2455
2884
 
2456
- if (is_dirty(effect)) {
2885
+ let dirty;
2886
+
2887
+ try {
2888
+ dirty = is_dirty(effect);
2889
+ } catch {
2890
+ // Dirty-checking can evaluate derived dependencies and throw in cases where
2891
+ // parent effects are about to destroy this eager effect. Run the effect so
2892
+ // its own error handling can deal with transient failures.
2893
+ dirty = true;
2894
+ }
2895
+
2896
+ if (dirty) {
2457
2897
  update_effect(effect);
2458
2898
  }
2459
2899
  }
@@ -2484,12 +2924,6 @@ function mark_reactions(signal, status, updated_during_traversal) {
2484
2924
  var reaction = reactions[i];
2485
2925
  var flags = reaction.f;
2486
2926
 
2487
- // Inspect effects need to run immediately, so that the stack trace makes sense
2488
- if (DEV && (flags & EAGER_EFFECT) !== 0) {
2489
- eager_effects.add(reaction);
2490
- continue;
2491
- }
2492
-
2493
2927
  var not_dirty = (flags & DIRTY) === 0;
2494
2928
 
2495
2929
  // don't set a DIRTY reaction to MAYBE_DIRTY
@@ -2497,14 +2931,22 @@ function mark_reactions(signal, status, updated_during_traversal) {
2497
2931
  set_signal_status(reaction, status);
2498
2932
  }
2499
2933
 
2500
- if ((flags & DERIVED) !== 0) {
2934
+ if ((flags & EAGER_EFFECT) !== 0) {
2935
+ // Eager effects need to run immediately:
2936
+ // - for $inspect so that the stack trace makes sense
2937
+ // - for $state.eager because they might be without an effect parent
2938
+ eager_effects.add(/** @type {Effect} */ (reaction));
2939
+ } else if ((flags & DERIVED) !== 0) {
2501
2940
  var derived = /** @type {Derived} */ (reaction);
2502
2941
 
2503
2942
  batch_values?.delete(derived);
2504
2943
 
2505
2944
  if ((flags & WAS_MARKED) === 0) {
2506
- // Only connected deriveds can be reliably unmarked right away
2507
- if (flags & CONNECTED) {
2945
+ // Only connected deriveds being executed outside the update cycle can be reliably unmarked right away
2946
+ if (
2947
+ flags & CONNECTED &&
2948
+ (active_effect === null || (active_effect.f & REACTION_IS_UPDATING) === 0)
2949
+ ) {
2508
2950
  reaction.f |= WAS_MARKED;
2509
2951
  }
2510
2952
 
@@ -3066,6 +3508,8 @@ function create_effect(type, fn) {
3066
3508
  effect.component_function = dev_current_component_function;
3067
3509
  }
3068
3510
 
3511
+ current_batch?.register_created_effect(effect);
3512
+
3069
3513
  /** @type {Effect | null} */
3070
3514
  var e = effect;
3071
3515
 
@@ -3314,9 +3758,9 @@ function destroy_effect(effect, remove_dom = true) {
3314
3758
  removed = true;
3315
3759
  }
3316
3760
 
3761
+ set_signal_status(effect, DESTROYING);
3317
3762
  destroy_effect_children(effect, remove_dom && !removed);
3318
3763
  remove_reactions(effect, 0);
3319
- set_signal_status(effect, DESTROYED);
3320
3764
 
3321
3765
  var transitions = effect.nodes && effect.nodes.t;
3322
3766
 
@@ -3328,6 +3772,9 @@ function destroy_effect(effect, remove_dom = true) {
3328
3772
 
3329
3773
  execute_effect_teardown(effect);
3330
3774
 
3775
+ effect.f ^= DESTROYING;
3776
+ effect.f |= DESTROYED;
3777
+
3331
3778
  var parent = effect.parent;
3332
3779
 
3333
3780
  // If the parent doesn't have any children, then skip this work altogether
@@ -3349,6 +3796,7 @@ function destroy_effect(effect, remove_dom = true) {
3349
3796
  effect.fn =
3350
3797
  effect.nodes =
3351
3798
  effect.ac =
3799
+ effect.b =
3352
3800
  null;
3353
3801
  }
3354
3802
 
@@ -3441,16 +3889,22 @@ function pause_children(effect, transitions, local) {
3441
3889
 
3442
3890
  while (child !== null) {
3443
3891
  var sibling = child.next;
3444
- var transparent =
3445
- (child.f & EFFECT_TRANSPARENT) !== 0 ||
3446
- // If this is a branch effect without a block effect parent,
3447
- // it means the parent block effect was pruned. In that case,
3448
- // transparency information was transferred to the branch effect.
3449
- ((child.f & BRANCH_EFFECT) !== 0 && (effect.f & BLOCK_EFFECT) !== 0);
3450
- // TODO we don't need to call pause_children recursively with a linked list in place
3451
- // it's slightly more involved though as we have to account for `transparent` changing
3452
- // through the tree.
3453
- pause_children(child, transitions, transparent ? local : false);
3892
+
3893
+ // If this child is a root effect, then it will become an independent root when its parent
3894
+ // is destroyed, it should therefore not become inert nor partake in transitions.
3895
+ if ((child.f & ROOT_EFFECT) === 0) {
3896
+ var transparent =
3897
+ (child.f & EFFECT_TRANSPARENT) !== 0 ||
3898
+ // If this is a branch effect without a block effect parent,
3899
+ // it means the parent block effect was pruned. In that case,
3900
+ // transparency information was transferred to the branch effect.
3901
+ ((child.f & BRANCH_EFFECT) !== 0 && (effect.f & BLOCK_EFFECT) !== 0);
3902
+ // TODO we don't need to call pause_children recursively with a linked list in place
3903
+ // it's slightly more involved though as we have to account for `transparent` changing
3904
+ // through the tree.
3905
+ pause_children(child, transitions, transparent ? local : false);
3906
+ }
3907
+
3454
3908
  child = sibling;
3455
3909
  }
3456
3910
  }
@@ -3873,7 +4327,14 @@ function remove_reaction(signal, dependency) {
3873
4327
  derived.f &= ~WAS_MARKED;
3874
4328
  }
3875
4329
 
3876
- update_derived_status(derived);
4330
+ // In a fork it's possible that a derived is executed and gets reactions, then commits, but is
4331
+ // never re-executed. This is possible when the derived is only executed once in the context
4332
+ // of a new branch which happens before fork.commit() runs. In this case, the derived still has
4333
+ // UNINITIALIZED as its value, and then when it's loosing its reactions we need to ensure it stays
4334
+ // DIRTY so it is reexecuted once someone wants its value again.
4335
+ if (derived.v !== UNINITIALIZED) {
4336
+ update_derived_status(derived);
4337
+ }
3877
4338
 
3878
4339
  // freeze any effects inside this derived
3879
4340
  freeze_derived_effects(derived);
@@ -4011,19 +4472,21 @@ function get(signal) {
4011
4472
  }
4012
4473
 
4013
4474
  if (DEV) {
4014
- // TODO reinstate this, but make it actually work
4015
- // if (current_async_effect) {
4016
- // var tracking = (current_async_effect.f & REACTION_IS_UPDATING) !== 0;
4017
- // var was_read = current_async_effect.deps?.includes(signal);
4475
+ if (
4476
+ !untracking &&
4477
+ reactivity_loss_tracker &&
4478
+ !reactivity_loss_tracker.warned &&
4479
+ (reactivity_loss_tracker.effect.f & REACTION_IS_UPDATING) === 0 &&
4480
+ !reactivity_loss_tracker.effect_deps.has(signal)
4481
+ ) {
4482
+ reactivity_loss_tracker.warned = true;
4018
4483
 
4019
- // if (!tracking && !untracking && !was_read) {
4020
- // w.await_reactivity_loss(/** @type {string} */ (signal.label));
4484
+ await_reactivity_loss(/** @type {string} */ (signal.label));
4021
4485
 
4022
- // var trace = get_error('traced at');
4023
- // // eslint-disable-next-line no-console
4024
- // if (trace) console.warn(trace);
4025
- // }
4026
- // }
4486
+ var trace = get_error('traced at');
4487
+ // eslint-disable-next-line no-console
4488
+ if (trace) console.warn(trace);
4489
+ }
4027
4490
 
4028
4491
  recent_async_deriveds.delete(signal);
4029
4492
  }
@@ -4332,6 +4795,12 @@ class BranchManager {
4332
4795
  this.#onscreen.set(key, offscreen.effect);
4333
4796
  this.#offscreen.delete(key);
4334
4797
 
4798
+ if (DEV) {
4799
+ // Tell hmr.js about the anchor it should use for updates,
4800
+ // since the initial one will be removed
4801
+ /** @type {any} */ (offscreen.fragment.lastChild)[HMR_ANCHOR] = this.anchor;
4802
+ }
4803
+
4335
4804
  // remove the anchor...
4336
4805
  /** @type {TemplateNode} */ (offscreen.fragment.lastChild).remove();
4337
4806
 
@@ -4604,8 +5073,7 @@ function to_class(value, hash, directives) {
4604
5073
  * @returns {Record<string, boolean> | undefined}
4605
5074
  */
4606
5075
  function set_class(dom, is_html, value, hash, prev_classes, next_classes) {
4607
- // @ts-expect-error need to add __className to patched prototype
4608
- var prev = dom.__className;
5076
+ var prev = /** @type {any} */ (dom)[CLASS_CACHE];
4609
5077
 
4610
5078
  if (
4611
5079
  prev !== value ||
@@ -4627,8 +5095,7 @@ function set_class(dom, is_html, value, hash, prev_classes, next_classes) {
4627
5095
  }
4628
5096
  }
4629
5097
 
4630
- // @ts-expect-error need to add __className to patched prototype
4631
- dom.__className = value;
5098
+ /** @type {any} */ (dom)[CLASS_CACHE] = value;
4632
5099
  } else if (next_classes && prev_classes !== next_classes) {
4633
5100
  for (var key in next_classes) {
4634
5101
  var is_present = !!next_classes[key];
@@ -4679,8 +5146,7 @@ function set_attribute(element, attribute, value, skip_warning) {
4679
5146
  */
4680
5147
  function get_attributes(element) {
4681
5148
  return /** @type {Record<string | symbol, unknown>} **/ (
4682
- // @ts-expect-error
4683
- element.__attributes ??= {
5149
+ /** @type {any} */ (element)[ATTRIBUTES_CACHE] ??= {
4684
5150
  [IS_CUSTOM_ELEMENT]: element.nodeName.includes('-'),
4685
5151
  [IS_HTML]: element.namespaceURI === NAMESPACE_HTML
4686
5152
  }
@@ -4701,13 +5167,19 @@ function get_setters(element) {
4701
5167
  var proto = element; // In the case of custom elements there might be setters on the instance
4702
5168
  var element_proto = Element.prototype;
4703
5169
 
4704
- // Stop at Element, from there on there's only unnecessary setters we're not interested in
4705
- // Do not use contructor.name here as that's unreliable in some browser environments
5170
+ // Stop at Element, from there on there's only unnecessary (and dangerous, like innerHTML) setters we're not interested in
5171
+ // Do not use constructor.name here as that's unreliable in some browser environments
4706
5172
  while (element_proto !== proto) {
4707
5173
  descriptors = get_descriptors(proto);
4708
5174
 
4709
5175
  for (var key in descriptors) {
4710
- if (descriptors[key].set) {
5176
+ if (
5177
+ descriptors[key].set &&
5178
+ // better safe than sorry, we don't want spread attributes to mess with HTML content
5179
+ key !== 'innerHTML' &&
5180
+ key !== 'textContent' &&
5181
+ key !== 'innerText'
5182
+ ) {
4711
5183
  setters.push(key);
4712
5184
  }
4713
5185
  }
@@ -4718,6 +5190,8 @@ function get_setters(element) {
4718
5190
  return setters;
4719
5191
  }
4720
5192
 
5193
+ /** @import { ComponentContext, Effect } from '#client' */
5194
+
4721
5195
  /**
4722
5196
  * @param {any} bound_value
4723
5197
  * @param {Element} element_or_component
@@ -4738,6 +5212,9 @@ function is_bound_this(bound_value, element_or_component) {
4738
5212
  * @returns {void}
4739
5213
  */
4740
5214
  function bind_this(element_or_component = {}, update, get_value, get_parts) {
5215
+ var component_effect = /** @type {ComponentContext} */ (component_context).r;
5216
+ var parent = /** @type {Effect} */ (active_effect);
5217
+
4741
5218
  effect(() => {
4742
5219
  /** @type {unknown[]} */
4743
5220
  var old_parts;
@@ -4751,7 +5228,7 @@ function bind_this(element_or_component = {}, update, get_value, get_parts) {
4751
5228
  parts = get_parts?.() || [];
4752
5229
 
4753
5230
  untrack(() => {
4754
- if (element_or_component !== get_value(...parts)) {
5231
+ if (!is_bound_this(get_value(...parts), element_or_component)) {
4755
5232
  update(element_or_component, ...parts);
4756
5233
  // If this is an effect rerun (cause: each block context changes), then nullify the binding at
4757
5234
  // the previous position if it isn't already taken over by a different effect.
@@ -4763,19 +5240,32 @@ function bind_this(element_or_component = {}, update, get_value, get_parts) {
4763
5240
  });
4764
5241
 
4765
5242
  return () => {
4766
- // We cannot use effects in the teardown phase, we we use a microtask instead.
4767
- queue_micro_task(() => {
5243
+ // When the bind:this effect is destroyed, we go up the effect parent chain until we find the last parent effect that is destroyed,
5244
+ // or the effect containing the component bind:this is in (whichever comes first). That way we can time the nulling of the binding
5245
+ // as close to user/developer expectation as possible.
5246
+ // TODO Svelte 6: Decide if we want to keep this logic or just always null the binding in the component effect's teardown
5247
+ // (which would be simpler, but less intuitive in some cases, and breaks the `ondestroy-before-cleanup` test)
5248
+ let p = parent;
5249
+ while (p !== component_effect && p.parent !== null && p.parent.f & DESTROYING) {
5250
+ p = p.parent;
5251
+ }
5252
+ const teardown = () => {
4768
5253
  if (parts && is_bound_this(get_value(...parts), element_or_component)) {
4769
5254
  update(null, ...parts);
4770
5255
  }
4771
- });
5256
+ };
5257
+ const original_teardown = p.teardown;
5258
+ p.teardown = () => {
5259
+ teardown();
5260
+ original_teardown?.();
5261
+ };
4772
5262
  };
4773
5263
  });
4774
5264
 
4775
5265
  return element_or_component;
4776
5266
  }
4777
5267
 
4778
- /** @import { Effect, Source } from './types.js' */
5268
+ /** @import { Derived, Effect, Source } from './types.js' */
4779
5269
 
4780
5270
  /**
4781
5271
  * The proxy handler for rest props (i.e. `const { x, ...rest } = $props()`).
@@ -4839,13 +5329,20 @@ function rest_props(props, exclude, name) {
4839
5329
  * @returns {(() => V | ((arg: V) => V) | ((arg: V, mutation: boolean) => V))}
4840
5330
  */
4841
5331
  function prop(props, key, flags, fallback) {
5332
+ var runes = !legacy_mode_flag ;
4842
5333
  var bindable = (flags & PROPS_IS_BINDABLE) !== 0;
4843
5334
  var lazy = (flags & PROPS_IS_LAZY_INITIAL) !== 0;
4844
5335
 
4845
5336
  var fallback_value = /** @type {V} */ (fallback);
4846
5337
  var fallback_dirty = true;
5338
+ var fallback_signal = /** @type {Derived<V> | undefined} */ (undefined);
4847
5339
 
4848
5340
  var get_fallback = () => {
5341
+ if (lazy && runes) {
5342
+ fallback_signal ??= derived(/** @type {() => V} */ (fallback));
5343
+ return get(fallback_signal);
5344
+ }
5345
+
4849
5346
  if (fallback_dirty) {
4850
5347
  fallback_dirty = false;
4851
5348
 
@@ -4966,9 +5463,7 @@ function prop(props, key, flags, fallback) {
4966
5463
 
4967
5464
  // special case — avoid recalculating the derived if we're in a
4968
5465
  // teardown function and the prop was overridden locally, or the
4969
- // component was already destroyed (this latter part is necessary
4970
- // because `bind:this` can read props after the component has
4971
- // been destroyed. TODO simplify `bind:this`
5466
+ // component was already destroyed (people could access props in a timeout)
4972
5467
  if ((is_destroying_effect && overridden) || (parent_effect.f & DESTROYED) !== 0) {
4973
5468
  return d.v;
4974
5469
  }