sibujs 3.4.0 → 4.0.0

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.
Files changed (98) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +118 -15
  3. package/dist/browser.cjs +410 -101
  4. package/dist/browser.d.cts +50 -5
  5. package/dist/browser.d.ts +50 -5
  6. package/dist/browser.js +8 -6
  7. package/dist/build.cjs +777 -385
  8. package/dist/build.js +15 -15
  9. package/dist/cdn.global.js +11 -7
  10. package/dist/{chunk-JCO7AHNU.js → chunk-3EKJK5FZ.js} +73 -34
  11. package/dist/chunk-4MFZMLUU.js +147 -0
  12. package/dist/chunk-52XFPGSN.js +123 -0
  13. package/dist/chunk-5INI7D2L.js +194 -0
  14. package/dist/{chunk-OAVXAFAY.js → chunk-6LG2T7LO.js} +5 -5
  15. package/dist/{chunk-OOUQUCNN.js → chunk-ACBT6UZS.js} +57 -22
  16. package/dist/{chunk-F7HW2NI5.js → chunk-AW6HPJ3L.js} +1 -1
  17. package/dist/chunk-DHDZ7IVN.js +109 -0
  18. package/dist/{chunk-JNGGM2XE.js → chunk-DQVUBVNX.js} +16 -25
  19. package/dist/{chunk-AXFWGY32.js → chunk-DR7H6UGM.js} +232 -67
  20. package/dist/{chunk-WSGLJYFI.js → chunk-E62UADKL.js} +9 -3
  21. package/dist/{chunk-3Z2NGWQI.js → chunk-ES2GERY2.js} +1 -1
  22. package/dist/{chunk-S3V6ZXPI.js → chunk-EWEE7NBF.js} +115 -41
  23. package/dist/{chunk-3DVGCW7O.js → chunk-F64ZPCFE.js} +8 -8
  24. package/dist/{chunk-APFRCIRI.js → chunk-GCSGGA3Q.js} +295 -198
  25. package/dist/{chunk-EX77FXTT.js → chunk-HYCCIYNS.js} +228 -51
  26. package/dist/{chunk-ELXMRSVN.js → chunk-NF3LYCQH.js} +259 -124
  27. package/dist/chunk-O3QQ3XOL.js +61 -0
  28. package/dist/{chunk-BUKYCWHX.js → chunk-PBQEW5VW.js} +14 -10
  29. package/dist/{chunk-H5FI5WDP.js → chunk-RRIDJQ5C.js} +3 -3
  30. package/dist/{chunk-S3NFJO6L.js → chunk-TIRZCERI.js} +36 -5
  31. package/dist/{chunk-ODXVQ3CS.js → chunk-VFHIVEPF.js} +309 -188
  32. package/dist/chunk-VPP2FONR.js +142 -0
  33. package/dist/{chunk-KZBEVFXZ.js → chunk-W55YHPEP.js} +5 -3
  34. package/dist/{chunk-FKQG7FTY.js → chunk-WN6WDFCB.js} +4 -4
  35. package/dist/{chunk-CEBWDD63.js → chunk-WOOMUJFT.js} +228 -86
  36. package/dist/{chunk-ITWCTZYH.js → chunk-Z2CE42DB.js} +19 -25
  37. package/dist/{contracts-ey_Qh8ef.d.cts → contracts-Hg6ITAEF.d.cts} +6 -6
  38. package/dist/{contracts-ey_Qh8ef.d.ts → contracts-Hg6ITAEF.d.ts} +6 -6
  39. package/dist/{customElement-CPfIrbvg.d.ts → customElement-CKLoO63w.d.cts} +73 -11
  40. package/dist/{customElement-CPfIrbvg.d.cts → customElement-CKLoO63w.d.ts} +73 -11
  41. package/dist/data.cjs +483 -205
  42. package/dist/data.d.cts +139 -7
  43. package/dist/data.d.ts +139 -7
  44. package/dist/data.js +9 -9
  45. package/dist/devtools.cjs +222 -87
  46. package/dist/devtools.js +8 -8
  47. package/dist/dispose-4FZ6OFXR.js +19 -0
  48. package/dist/ecosystem.cjs +419 -175
  49. package/dist/ecosystem.js +11 -11
  50. package/dist/extras.cjs +1981 -765
  51. package/dist/extras.d.cts +7 -7
  52. package/dist/extras.d.ts +7 -7
  53. package/dist/extras.js +29 -26
  54. package/dist/index.cjs +784 -385
  55. package/dist/index.d.cts +299 -28
  56. package/dist/index.d.ts +299 -28
  57. package/dist/index.js +33 -18
  58. package/dist/motion.cjs +180 -50
  59. package/dist/motion.d.cts +26 -0
  60. package/dist/motion.d.ts +26 -0
  61. package/dist/motion.js +5 -5
  62. package/dist/patterns.cjs +437 -170
  63. package/dist/patterns.d.cts +60 -13
  64. package/dist/patterns.d.ts +60 -13
  65. package/dist/patterns.js +7 -7
  66. package/dist/performance.cjs +333 -148
  67. package/dist/performance.d.cts +55 -7
  68. package/dist/performance.d.ts +55 -7
  69. package/dist/performance.js +9 -9
  70. package/dist/plugins.cjs +1374 -576
  71. package/dist/plugins.d.cts +227 -9
  72. package/dist/plugins.d.ts +227 -9
  73. package/dist/plugins.js +613 -321
  74. package/dist/{ssr-D62yFwuw.d.cts → ssr-BiPRdZ6n.d.cts} +44 -15
  75. package/dist/{ssr-D62yFwuw.d.ts → ssr-BiPRdZ6n.d.ts} +44 -15
  76. package/dist/{ssr-2PPULEK2.js → ssr-JFP5535P.js} +5 -4
  77. package/dist/ssr.cjs +1104 -443
  78. package/dist/ssr.d.cts +230 -20
  79. package/dist/ssr.d.ts +230 -20
  80. package/dist/ssr.js +15 -12
  81. package/dist/{startup-0Qv6aosO.d.cts → startup-BMpaiMhP.d.cts} +1 -1
  82. package/dist/{startup-0Qv6aosO.d.ts → startup-BMpaiMhP.d.ts} +1 -1
  83. package/dist/testing.cjs +205 -63
  84. package/dist/testing.d.cts +1 -1
  85. package/dist/testing.d.ts +1 -1
  86. package/dist/testing.js +8 -10
  87. package/dist/ui.cjs +640 -199
  88. package/dist/ui.d.cts +2 -2
  89. package/dist/ui.d.ts +2 -2
  90. package/dist/ui.js +10 -10
  91. package/dist/widgets.cjs +207 -82
  92. package/dist/widgets.js +9 -9
  93. package/package.json +12 -7
  94. package/dist/chunk-5VGSK6D2.js +0 -80
  95. package/dist/chunk-COY6PUD2.js +0 -34
  96. package/dist/chunk-K35BAR4P.js +0 -96
  97. package/dist/chunk-L3GAGWCC.js +0 -111
  98. package/dist/chunk-RT3IYFN2.js +0 -38
@@ -135,45 +135,40 @@ import {
135
135
  use,
136
136
  var_,
137
137
  video
138
- } from "./chunk-3Z2NGWQI.js";
138
+ } from "./chunk-ES2GERY2.js";
139
+ import {
140
+ trustHTML
141
+ } from "./chunk-HYCCIYNS.js";
139
142
  import {
140
143
  watch
141
- } from "./chunk-H5FI5WDP.js";
144
+ } from "./chunk-RRIDJQ5C.js";
142
145
  import {
143
146
  __resetIdCounter,
144
147
  createId
145
148
  } from "./chunk-CCSJMTRN.js";
146
- import {
147
- trustHTML
148
- } from "./chunk-EX77FXTT.js";
149
149
  import {
150
150
  context
151
- } from "./chunk-RT3IYFN2.js";
151
+ } from "./chunk-O3QQ3XOL.js";
152
152
  import {
153
153
  SVG_NS,
154
154
  bindChildNode,
155
155
  tagFactory
156
- } from "./chunk-JNGGM2XE.js";
156
+ } from "./chunk-DQVUBVNX.js";
157
157
  import {
158
158
  bindAttribute,
159
- bindDynamic
160
- } from "./chunk-K35BAR4P.js";
159
+ bindDynamic,
160
+ setSafeAttribute
161
+ } from "./chunk-4MFZMLUU.js";
161
162
  import {
162
163
  derived
163
- } from "./chunk-ITWCTZYH.js";
164
+ } from "./chunk-Z2CE42DB.js";
164
165
  import {
165
166
  globalSingleton
166
167
  } from "./chunk-7ZHH77QA.js";
167
- import {
168
- isEventHandlerAttr,
169
- isUrlAttribute,
170
- sanitizeSrcset,
171
- sanitizeUrl
172
- } from "./chunk-L3GAGWCC.js";
173
168
  import {
174
169
  effect,
175
170
  on
176
- } from "./chunk-BUKYCWHX.js";
171
+ } from "./chunk-PBQEW5VW.js";
177
172
  import {
178
173
  disableSSR,
179
174
  enableSSR,
@@ -182,18 +177,22 @@ import {
182
177
  isSSR,
183
178
  runInSSRContext,
184
179
  withSSR
185
- } from "./chunk-S3NFJO6L.js";
180
+ } from "./chunk-TIRZCERI.js";
186
181
  import {
182
+ MAX_DRAIN_TEARDOWNS,
187
183
  checkLeaks,
188
184
  dispose,
189
- registerDisposer
190
- } from "./chunk-5VGSK6D2.js";
185
+ registerDisposer,
186
+ replaceChildrenSafely,
187
+ reportDrainRunaway,
188
+ unregisterDisposer
189
+ } from "./chunk-DHDZ7IVN.js";
191
190
  import {
192
191
  batch,
193
192
  enqueueBatchedSignal,
194
193
  isBatching,
195
194
  signal
196
- } from "./chunk-KZBEVFXZ.js";
195
+ } from "./chunk-W55YHPEP.js";
197
196
  import {
198
197
  notifySubscribers,
199
198
  recordDependency,
@@ -203,13 +202,17 @@ import {
203
202
  suspendTracking,
204
203
  track,
205
204
  untracked
206
- } from "./chunk-OOUQUCNN.js";
205
+ } from "./chunk-ACBT6UZS.js";
207
206
  import {
208
207
  __export,
209
208
  devAssert,
210
209
  devWarn,
211
- isDev
212
- } from "./chunk-COY6PUD2.js";
210
+ getRuntimeErrorHandler,
211
+ isDev,
212
+ normalizeError,
213
+ reportError,
214
+ setRuntimeErrorHandler
215
+ } from "./chunk-VPP2FONR.js";
213
216
 
214
217
  // index.ts
215
218
  var index_exports = {};
@@ -220,6 +223,7 @@ __export(index_exports, {
220
223
  Fragment: () => Fragment,
221
224
  KeepAlive: () => KeepAlive,
222
225
  Loading: () => Loading,
226
+ MAX_DRAIN_TEARDOWNS: () => MAX_DRAIN_TEARDOWNS,
223
227
  Portal: () => Portal,
224
228
  SVG_NS: () => SVG_NS,
225
229
  Suspense: () => Suspense,
@@ -297,6 +301,7 @@ __export(index_exports, {
297
301
  g: () => g,
298
302
  getAction: () => getAction,
299
303
  getRequestScopedCache: () => getRequestScopedCache,
304
+ getRuntimeErrorHandler: () => getRuntimeErrorHandler,
300
305
  getSSRStore: () => getSSRStore,
301
306
  getSlot: () => getSlot,
302
307
  h1: () => h1,
@@ -370,6 +375,9 @@ __export(index_exports, {
370
375
  registerComponent: () => registerComponent,
371
376
  registerDisposer: () => registerDisposer,
372
377
  registerIsland: () => registerIsland,
378
+ replaceChildrenSafely: () => replaceChildrenSafely,
379
+ reportDrainRunaway: () => reportDrainRunaway,
380
+ reportError: () => reportError,
373
381
  resolveComponent: () => resolveComponent,
374
382
  retrack: () => retrack,
375
383
  rp: () => rp,
@@ -383,6 +391,7 @@ __export(index_exports, {
383
391
  select: () => select,
384
392
  setGlobalErrorHandler: () => setGlobalErrorHandler,
385
393
  setMaxDrainIterations: () => setMaxDrainIterations,
394
+ setRuntimeErrorHandler: () => setRuntimeErrorHandler,
386
395
  show: () => show,
387
396
  signal: () => signal,
388
397
  slot: () => slot,
@@ -422,6 +431,7 @@ __export(index_exports, {
422
431
  u: () => u,
423
432
  ul: () => ul,
424
433
  unregisterComponent: () => unregisterComponent,
434
+ unregisterDisposer: () => unregisterDisposer,
425
435
  unregisterIsland: () => unregisterIsland,
426
436
  untracked: () => untracked,
427
437
  use: () => use,
@@ -436,18 +446,14 @@ __export(index_exports, {
436
446
  // src/core/rendering/lazy.ts
437
447
  var PENDING_ERROR = "__sibuPendingError";
438
448
  function dispatchPropagate(node, error) {
439
- const fire = () => {
440
- try {
441
- if (!node.parentNode) return false;
442
- node.dispatchEvent(new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error } }));
443
- return true;
444
- } catch {
445
- return false;
446
- }
449
+ const report = () => {
450
+ if (!node.parentNode) return false;
451
+ reportError(error, { phase: "render", name: "lazy", node });
452
+ return true;
447
453
  };
448
- if (node.parentNode && fire()) return;
454
+ if (report()) return;
449
455
  queueMicrotask(() => {
450
- if (fire()) return;
456
+ if (report()) return;
451
457
  node[PENDING_ERROR] = error;
452
458
  });
453
459
  }
@@ -472,12 +478,14 @@ function lazy(importFn) {
472
478
  if (disposed) return;
473
479
  cached = mod.default;
474
480
  const rendered = cached();
475
- container.replaceChildren(rendered);
481
+ replaceChildrenSafely(container, rendered);
476
482
  }).catch((err) => {
477
483
  if (disposed) return;
478
484
  const errorObj = err instanceof Error ? err : new Error(String(err));
479
- devWarn(`[SibuJS] lazy() failed to load component: ${errorObj.message}`);
480
- container.replaceChildren(div({ class: "sibu-lazy-error" }, `Failed to load component: ${errorObj.message}`));
485
+ replaceChildrenSafely(
486
+ container,
487
+ div({ class: "sibu-lazy-error" }, `Failed to load component: ${errorObj.message}`)
488
+ );
481
489
  dispatchPropagate(container, errorObj);
482
490
  });
483
491
  container.appendChild(span("sibu-lazy-loading", "Loading..."));
@@ -509,7 +517,7 @@ function Suspense({ nodes, fallback }) {
509
517
  childEl = el;
510
518
  if (el.classList.contains("sibu-lazy")) {
511
519
  if (!el.querySelector(".sibu-lazy-loading")) {
512
- container.replaceChildren(el);
520
+ replaceChildrenSafely(container, el);
513
521
  return;
514
522
  }
515
523
  observer = new MutationObserver(() => {
@@ -518,16 +526,15 @@ function Suspense({ nodes, fallback }) {
518
526
  if (!loading) {
519
527
  observer?.disconnect();
520
528
  observer = null;
521
- container.replaceChildren(el);
529
+ replaceChildrenSafely(container, el);
522
530
  }
523
531
  });
524
532
  observer.observe(el, { childList: true, subtree: true });
525
533
  } else {
526
- container.replaceChildren(el);
534
+ replaceChildrenSafely(container, el);
527
535
  }
528
536
  } catch (err) {
529
537
  const errorObj = err instanceof Error ? err : new Error(String(err));
530
- devWarn(`[SibuJS] Suspense nodes() threw: ${errorObj.message}`);
531
538
  dispatchPropagate(container, errorObj);
532
539
  }
533
540
  });
@@ -539,7 +546,8 @@ function safeCall(cb, hookName) {
539
546
  try {
540
547
  return cb();
541
548
  } catch (err) {
542
- devWarn(`${hookName}: callback threw: ${err instanceof Error ? err.message : String(err)}`);
549
+ const phase = hookName.toLowerCase().includes("unmount") ? "cleanup" : "effect";
550
+ reportError(err, { phase, name: hookName });
543
551
  return void 0;
544
552
  }
545
553
  }
@@ -970,14 +978,14 @@ function parseStack(stack) {
970
978
  }
971
979
  return frames;
972
980
  }
973
- function normalizeError(err) {
981
+ function normalizeError2(err) {
974
982
  if (err instanceof Error) {
975
983
  const code2 = err.code ?? err.name ?? "ERROR";
976
984
  const message = err.message || "Unknown error";
977
985
  const stack = err.stack ?? "";
978
986
  const frames = parseStack(stack);
979
987
  const rawCause = err.cause;
980
- const cause = rawCause != null ? normalizeError(rawCause) : null;
988
+ const cause = rawCause != null ? normalizeError2(rawCause) : null;
981
989
  return { code: code2, message, stack, frames, cause };
982
990
  }
983
991
  return {
@@ -1073,7 +1081,7 @@ function renderMetadata(meta2) {
1073
1081
  function ErrorDisplay(props) {
1074
1082
  injectStyles();
1075
1083
  const severity = props.severity ?? "error";
1076
- const normalized = normalizeError(props.error);
1084
+ const normalized = normalizeError2(props.error);
1077
1085
  const showDetails = props.alwaysShowDetails ?? isDev();
1078
1086
  const headline = props.title ?? normalized.message;
1079
1087
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
@@ -1323,6 +1331,7 @@ var errorBoundaryStyles = `
1323
1331
  background: #3a3a4e;
1324
1332
  }
1325
1333
  `;
1334
+ var RESET_KEY_THREW = /* @__PURE__ */ Symbol("sibujs.ErrorBoundary.resetKeyThrew");
1326
1335
  var stylesInjected = false;
1327
1336
  function injectStyles2() {
1328
1337
  if (!stylesInjected && typeof document !== "undefined") {
@@ -1331,29 +1340,6 @@ function injectStyles2() {
1331
1340
  stylesInjected = true;
1332
1341
  }
1333
1342
  }
1334
- var FALLBACK_CACHE_MAX = 50;
1335
- var fallbackCache = /* @__PURE__ */ new WeakMap();
1336
- function getMemoizedFallback(fallbackFn, error, retry) {
1337
- let cache2 = fallbackCache.get(fallbackFn);
1338
- if (!cache2) {
1339
- cache2 = /* @__PURE__ */ new Map();
1340
- fallbackCache.set(fallbackFn, cache2);
1341
- }
1342
- const key = error.message;
1343
- let factory = cache2.get(key);
1344
- if (factory) {
1345
- cache2.delete(key);
1346
- cache2.set(key, factory);
1347
- } else {
1348
- factory = () => fallbackFn(error, retry);
1349
- cache2.set(key, factory);
1350
- if (cache2.size > FALLBACK_CACHE_MAX) {
1351
- const oldestKey = cache2.keys().next().value;
1352
- if (oldestKey !== void 0) cache2.delete(oldestKey);
1353
- }
1354
- }
1355
- return factory();
1356
- }
1357
1343
  function ErrorBoundary(optionsOrChildren, maybeChildren) {
1358
1344
  const children = typeof optionsOrChildren === "function" ? optionsOrChildren : maybeChildren;
1359
1345
  const options = typeof optionsOrChildren === "function" ? {} : optionsOrChildren;
@@ -1361,43 +1347,61 @@ function ErrorBoundary(optionsOrChildren, maybeChildren) {
1361
1347
  injectStyles2();
1362
1348
  const [error, setError] = signal(null);
1363
1349
  const retry = () => {
1364
- if (fallback) {
1365
- const cur = error();
1366
- const inner = fallbackCache.get(fallback);
1367
- if (cur && inner) inner.delete(cur.message);
1368
- }
1350
+ stopResetKeysWatcher();
1369
1351
  setError(null);
1370
1352
  };
1371
1353
  let resetKeysTeardown = null;
1372
- if (resetKeys && resetKeys.length > 0) {
1373
- let initialized = false;
1354
+ const stopResetKeysWatcher = () => {
1355
+ if (!resetKeysTeardown) return;
1356
+ const teardown = resetKeysTeardown;
1357
+ resetKeysTeardown = null;
1358
+ teardown();
1359
+ };
1360
+ const readResetKeyValues = (keys) => {
1361
+ const values = new Array(keys.length);
1362
+ for (let i2 = 0; i2 < keys.length; i2++) {
1363
+ try {
1364
+ values[i2] = keys[i2]();
1365
+ } catch (err) {
1366
+ reportError(err, { phase: "effect", name: "ErrorBoundary.resetKeys" });
1367
+ values[i2] = RESET_KEY_THREW;
1368
+ }
1369
+ }
1370
+ return values;
1371
+ };
1372
+ const startResetKeysWatcher = () => {
1373
+ const keys = resetKeys;
1374
+ if (!keys || keys.length === 0) return;
1375
+ stopResetKeysWatcher();
1376
+ let baseline = null;
1374
1377
  resetKeysTeardown = effect(() => {
1375
- for (const k of resetKeys) {
1376
- try {
1377
- k();
1378
- } catch (err) {
1379
- if (typeof console !== "undefined") {
1380
- console.warn("[SibuJS ErrorBoundary] resetKeys getter threw:", err);
1378
+ const current = readResetKeyValues(keys);
1379
+ if (baseline === null) {
1380
+ baseline = current;
1381
+ return;
1382
+ }
1383
+ let changed = current.length !== baseline.length;
1384
+ if (!changed) {
1385
+ for (let i2 = 0; i2 < current.length; i2++) {
1386
+ if (!Object.is(current[i2], baseline[i2])) {
1387
+ changed = true;
1388
+ break;
1381
1389
  }
1382
1390
  }
1383
1391
  }
1384
- if (!initialized) {
1385
- initialized = true;
1386
- return;
1387
- }
1388
- if (error() !== null) retry();
1392
+ baseline = current;
1393
+ if (changed) retry();
1389
1394
  });
1390
- }
1395
+ };
1391
1396
  const handleError = (e) => {
1392
- const errorObj = e instanceof Error ? e : new Error(String(e));
1397
+ const errorObj = normalizeError(e);
1393
1398
  setError(errorObj);
1399
+ startResetKeysWatcher();
1394
1400
  if (onError) {
1395
1401
  try {
1396
1402
  onError(errorObj);
1397
1403
  } catch (cbErr) {
1398
- if (typeof console !== "undefined") {
1399
- console.error("[SibuJS ErrorBoundary] onError callback threw:", cbErr);
1400
- }
1404
+ reportError(cbErr, { phase: "render", name: "ErrorBoundary onError callback" });
1401
1405
  }
1402
1406
  }
1403
1407
  return errorObj;
@@ -1408,18 +1412,14 @@ function ErrorBoundary(optionsOrChildren, maybeChildren) {
1408
1412
  const tryRenderFallback = (err) => {
1409
1413
  const fn = fallback || defaultFallback;
1410
1414
  try {
1411
- return getMemoizedFallback(fn, err, retry);
1415
+ return fn(err, retry);
1412
1416
  } catch (fallbackError) {
1413
- const propagateError = fallbackError instanceof Error ? fallbackError : new Error(String(fallbackError));
1414
1417
  queueMicrotask(() => {
1415
- if (container.parentNode) {
1416
- container.dispatchEvent(
1417
- new CustomEvent("sibu:error-propagate", {
1418
- bubbles: true,
1419
- detail: { error: propagateError }
1420
- })
1421
- );
1422
- }
1418
+ reportError(fallbackError, {
1419
+ phase: "render",
1420
+ name: "ErrorBoundary fallback",
1421
+ node: container.parentNode ? container : void 0
1422
+ });
1423
1423
  });
1424
1424
  return document.createComment("error-boundary-failed");
1425
1425
  }
@@ -1436,11 +1436,20 @@ function ErrorBoundary(optionsOrChildren, maybeChildren) {
1436
1436
  if (result && typeof result.then === "function") {
1437
1437
  const asyncContainer = div({ class: "sibu-error-async" });
1438
1438
  asyncContainer.appendChild(span({ class: "sibu-lazy-loading", nodes: "Loading..." }));
1439
+ let asyncDisposed = false;
1440
+ registerDisposer(asyncContainer, () => {
1441
+ asyncDisposed = true;
1442
+ });
1439
1443
  result.then((el) => {
1440
- asyncContainer.replaceChildren(el);
1444
+ if (asyncDisposed) {
1445
+ dispose(el);
1446
+ return;
1447
+ }
1448
+ replaceChildrenSafely(asyncContainer, el);
1441
1449
  }).catch((e) => {
1450
+ if (asyncDisposed) return;
1442
1451
  const err = handleError(e);
1443
- asyncContainer.replaceChildren(tryRenderFallback(err));
1452
+ replaceChildrenSafely(asyncContainer, tryRenderFallback(err));
1444
1453
  });
1445
1454
  return asyncContainer;
1446
1455
  }
@@ -1453,12 +1462,12 @@ function ErrorBoundary(optionsOrChildren, maybeChildren) {
1453
1462
  });
1454
1463
  const propagateListener = (e) => {
1455
1464
  if (error()) return;
1456
- e.stopPropagation();
1457
1465
  const customEvent = e;
1458
1466
  const propagatedError = customEvent.detail?.error;
1459
- if (propagatedError) {
1460
- handleError(propagatedError);
1461
- }
1467
+ if (!propagatedError) return;
1468
+ e.preventDefault();
1469
+ e.stopPropagation();
1470
+ handleError(propagatedError);
1462
1471
  };
1463
1472
  container.addEventListener("sibu:error-propagate", propagateListener);
1464
1473
  onMount(() => {
@@ -1486,7 +1495,7 @@ function ErrorBoundary(optionsOrChildren, maybeChildren) {
1486
1495
  return void 0;
1487
1496
  }, container);
1488
1497
  registerDisposer(container, () => {
1489
- if (resetKeysTeardown) resetKeysTeardown();
1498
+ stopResetKeysWatcher();
1490
1499
  container.removeEventListener("sibu:error-propagate", propagateListener);
1491
1500
  });
1492
1501
  return container;
@@ -1836,10 +1845,7 @@ function DynamicComponent(is) {
1836
1845
  } else {
1837
1846
  el = resolveComponent(target);
1838
1847
  }
1839
- for (const child of Array.from(container.childNodes)) {
1840
- dispose(child);
1841
- }
1842
- container.replaceChildren(el);
1848
+ replaceChildrenSafely(container, el);
1843
1849
  }
1844
1850
  const untrack = track(render);
1845
1851
  registerDisposer(container, untrack);
@@ -1900,6 +1906,10 @@ function each(getArray, render, options) {
1900
1906
  let oldLen = 0;
1901
1907
  let nodeMap = /* @__PURE__ */ new Map();
1902
1908
  let workMap = /* @__PURE__ */ new Map();
1909
+ const pendingRows = [];
1910
+ const pendingItems = [];
1911
+ const pendingIndices = [];
1912
+ let pendingCount = 0;
1903
1913
  let newNodes = [];
1904
1914
  let newKeysBuf = [];
1905
1915
  let isStableBuf = new Uint8Array(0);
@@ -1909,8 +1919,10 @@ function each(getArray, render, options) {
1909
1919
  const keyIndexMap = /* @__PURE__ */ new Map();
1910
1920
  let initialized = false;
1911
1921
  let sentinelInserted = false;
1922
+ let rangeDisposed = false;
1912
1923
  const keyFn = options.key;
1913
1924
  const update = () => {
1925
+ if (rangeDisposed) return;
1914
1926
  const arr = getArray();
1915
1927
  const newLen = arr.length;
1916
1928
  const parent = anchor.parentNode;
@@ -1939,49 +1951,44 @@ function each(getArray, render, options) {
1939
1951
  }
1940
1952
  keyIndexMap.set(newKeys[i2], i2);
1941
1953
  }
1954
+ pendingCount = 0;
1942
1955
  for (let i2 = 0; i2 < newLen; i2++) {
1943
1956
  const key = newKeys[i2];
1944
1957
  const existing = nodeMap.get(key);
1945
- let node;
1958
+ let row;
1946
1959
  if (existing !== void 0) {
1947
- node = existing;
1960
+ row = existing;
1961
+ pendingRows[pendingCount] = row;
1962
+ pendingItems[pendingCount] = arr[i2];
1963
+ pendingIndices[pendingCount] = i2;
1964
+ pendingCount++;
1948
1965
  } else {
1949
- const itemKey = key;
1950
- const itemGetter = () => untracked(() => getArray()[keyIndexMap.get(itemKey)]);
1951
- const indexGetter = () => keyIndexMap.get(itemKey);
1966
+ const [itemGetter, setItem] = signal(arr[i2]);
1967
+ const [indexGetter, setIndex] = signal(i2);
1968
+ let node;
1952
1969
  try {
1953
1970
  node = resolveNodeChild(render(itemGetter, indexGetter));
1954
1971
  } catch (err) {
1955
- if (_isDev) {
1956
- devWarn(
1957
- `each: render threw for item at index ${i2} (key="${newKeys[i2]}"): ${err instanceof Error ? err.message : String(err)}`
1958
- );
1959
- }
1972
+ const key2 = newKeys[i2];
1960
1973
  node = document.createComment(`each:error:${i2}`);
1961
- const errorObj = err instanceof Error ? err : new Error(String(err));
1962
1974
  queueMicrotask(() => {
1963
- try {
1964
- const target = anchor.parentNode;
1965
- if (target?.dispatchEvent) {
1966
- target.dispatchEvent(
1967
- new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error: errorObj } })
1968
- );
1969
- } else if (_isDev) {
1970
- devWarn(`each: error not surfaced \u2014 anchor detached: ${errorObj.message}`);
1971
- }
1972
- } catch {
1973
- }
1975
+ reportError(err, {
1976
+ phase: "render",
1977
+ name: `each row (key="${String(key2)}")`,
1978
+ node: anchor.parentNode ? anchor : void 0
1979
+ });
1974
1980
  });
1975
1981
  }
1982
+ row = { node, item: itemGetter, setItem, index: indexGetter, setIndex };
1976
1983
  }
1977
- workMap.set(key, node);
1978
- newNodes[i2] = node;
1984
+ workMap.set(key, row);
1985
+ newNodes[i2] = row.node;
1979
1986
  }
1980
- for (const [key, node] of nodeMap) {
1987
+ for (const [key, row] of nodeMap) {
1981
1988
  if (!workMap.has(key)) {
1982
- dispose(node);
1983
- if (node.parentNode) {
1984
- parent.removeChild(node);
1989
+ dispose(row.node);
1990
+ if (row.node.parentNode) {
1991
+ parent.removeChild(row.node);
1985
1992
  }
1986
1993
  }
1987
1994
  }
@@ -1990,6 +1997,8 @@ function each(getArray, render, options) {
1990
1997
  const tmp2 = nodeMap;
1991
1998
  nodeMap = workMap;
1992
1999
  workMap = tmp2;
2000
+ initialized = true;
2001
+ pendingCount = 0;
1993
2002
  return;
1994
2003
  }
1995
2004
  oldKeyIndex.clear();
@@ -2039,12 +2048,47 @@ function each(getArray, render, options) {
2039
2048
  nodeMap = workMap;
2040
2049
  workMap = tmp;
2041
2050
  initialized = true;
2051
+ flushRowCells();
2042
2052
  };
2053
+ function flushRowCells() {
2054
+ if (pendingCount === 0) return;
2055
+ const count = pendingCount;
2056
+ pendingCount = 0;
2057
+ batch(() => {
2058
+ for (let i2 = 0; i2 < count; i2++) {
2059
+ const row = pendingRows[i2];
2060
+ row.setItem(pendingItems[i2]);
2061
+ row.setIndex(pendingIndices[i2]);
2062
+ pendingRows[i2] = void 0;
2063
+ pendingItems[i2] = void 0;
2064
+ }
2065
+ });
2066
+ }
2043
2067
  const untrack = track(update);
2044
- registerDisposer(anchor, untrack);
2068
+ const disposeRange = () => {
2069
+ if (rangeDisposed) return;
2070
+ rangeDisposed = true;
2071
+ untrack();
2072
+ for (const row of nodeMap.values()) {
2073
+ dispose(row.node);
2074
+ row.node.parentNode?.removeChild(row.node);
2075
+ }
2076
+ if (end.parentNode) end.parentNode.removeChild(end);
2077
+ sentinelInserted = false;
2078
+ nodeMap.clear();
2079
+ workMap.clear();
2080
+ keyIndexMap.clear();
2081
+ oldKeyIndex.clear();
2082
+ oldLen = 0;
2083
+ pendingRows.length = 0;
2084
+ pendingItems.length = 0;
2085
+ pendingIndices.length = 0;
2086
+ pendingCount = 0;
2087
+ };
2088
+ registerDisposer(anchor, disposeRange);
2045
2089
  if (!initialized) {
2046
2090
  queueMicrotask(() => {
2047
- if (!initialized && anchor.parentNode) {
2091
+ if (!initialized && !rangeDisposed && anchor.parentNode) {
2048
2092
  update();
2049
2093
  }
2050
2094
  });
@@ -2343,20 +2387,11 @@ function executeElement(tmpl, values) {
2343
2387
  break;
2344
2388
  case 1: {
2345
2389
  const name = attr.name;
2346
- const lname = name.toLowerCase();
2347
- if (isEventHandlerAttr(name)) break;
2348
2390
  const val = values[attr.idx];
2349
2391
  if (typeof val === "function") {
2350
2392
  registerDisposer(el, bindAttribute(el, name, val));
2351
- } else if (val != null) {
2352
- const str = String(val);
2353
- if (lname === "srcset") {
2354
- el.setAttribute(name, sanitizeSrcset(str));
2355
- } else if (isUrlAttribute(lname)) {
2356
- el.setAttribute(name, sanitizeUrl(str));
2357
- } else {
2358
- el.setAttribute(name, str);
2359
- }
2393
+ } else {
2394
+ setSafeAttribute(el, name, val, { syncValueProperty: false, label: "html" });
2360
2395
  }
2361
2396
  break;
2362
2397
  }
@@ -2366,14 +2401,7 @@ function executeElement(tmpl, values) {
2366
2401
  const ev = values[attr.exprs[j]];
2367
2402
  val += (ev == null ? "" : String(ev)) + attr.statics[j + 1];
2368
2403
  }
2369
- const lname2 = attr.name.toLowerCase();
2370
- if (lname2 === "srcset") {
2371
- el.setAttribute(attr.name, sanitizeSrcset(val));
2372
- } else if (isUrlAttribute(lname2)) {
2373
- el.setAttribute(attr.name, sanitizeUrl(val));
2374
- } else {
2375
- el.setAttribute(attr.name, val);
2376
- }
2404
+ setSafeAttribute(el, attr.name, val, { syncValueProperty: false, label: "html" });
2377
2405
  break;
2378
2406
  }
2379
2407
  case 3: {
@@ -2599,20 +2627,12 @@ function Portal(nodes, target) {
2599
2627
  portalContent = nodes();
2600
2628
  container.appendChild(portalContent);
2601
2629
  } catch (err) {
2602
- if (typeof console !== "undefined") {
2603
- console.error("[Portal] Render error:", err);
2604
- }
2605
- const errorObj = err instanceof Error ? err : new Error(String(err));
2606
2630
  queueMicrotask(() => {
2607
- try {
2608
- const target2 = anchor.parentNode;
2609
- if (target2?.dispatchEvent) {
2610
- target2.dispatchEvent(
2611
- new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error: errorObj } })
2612
- );
2613
- }
2614
- } catch {
2615
- }
2631
+ reportError(err, {
2632
+ phase: "render",
2633
+ name: "Portal",
2634
+ node: anchor.parentNode ? anchor : void 0
2635
+ });
2616
2636
  });
2617
2637
  }
2618
2638
  });
@@ -2709,9 +2729,10 @@ function array(initial = []) {
2709
2729
  function reactiveArray(initial = []) {
2710
2730
  let data2 = [...initial];
2711
2731
  let snapshot = null;
2712
- const signal2 = {};
2732
+ const signal2 = { __v: 0 };
2713
2733
  function notify() {
2714
2734
  snapshot = null;
2735
+ signal2.__v++;
2715
2736
  if (!enqueueBatchedSignal(signal2)) {
2716
2737
  notifySubscribers(signal2);
2717
2738
  }
@@ -2831,17 +2852,34 @@ function asyncDerived(factory, initial) {
2831
2852
  const [error, setError] = signal(null);
2832
2853
  const [tick, setTick] = signal(0);
2833
2854
  let runId = 0;
2834
- effect(() => {
2855
+ let disposed = false;
2856
+ let controller = null;
2857
+ const abortInFlight = () => {
2858
+ if (controller === null) return;
2859
+ const c = controller;
2860
+ controller = null;
2861
+ try {
2862
+ c.abort();
2863
+ } catch {
2864
+ }
2865
+ };
2866
+ const stopEffect = effect(() => {
2835
2867
  tick();
2868
+ if (disposed) return;
2869
+ abortInFlight();
2836
2870
  const currentRun = ++runId;
2871
+ const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
2872
+ controller = ac;
2837
2873
  batch(() => {
2838
2874
  setLoading(true);
2839
2875
  setError(null);
2840
2876
  });
2877
+ const stale = () => disposed || currentRun !== runId;
2841
2878
  let promise;
2842
2879
  try {
2843
- promise = factory();
2880
+ promise = factory({ signal: ac ? ac.signal : void 0 });
2844
2881
  } catch (err) {
2882
+ if (stale()) return;
2845
2883
  batch(() => {
2846
2884
  setError(err);
2847
2885
  setLoading(false);
@@ -2850,14 +2888,16 @@ function asyncDerived(factory, initial) {
2850
2888
  }
2851
2889
  promise.then(
2852
2890
  (result) => {
2853
- if (currentRun !== runId) return;
2891
+ if (stale()) return;
2892
+ controller = null;
2854
2893
  batch(() => {
2855
2894
  setValue(result);
2856
2895
  setLoading(false);
2857
2896
  });
2858
2897
  },
2859
2898
  (err) => {
2860
- if (currentRun !== runId) return;
2899
+ if (stale()) return;
2900
+ controller = null;
2861
2901
  batch(() => {
2862
2902
  setError(err);
2863
2903
  setLoading(false);
@@ -2869,7 +2909,16 @@ function asyncDerived(factory, initial) {
2869
2909
  value,
2870
2910
  loading,
2871
2911
  error,
2872
- refresh: () => setTick((n) => n + 1)
2912
+ refresh: () => {
2913
+ if (disposed) return;
2914
+ setTick((n) => n + 1);
2915
+ },
2916
+ dispose: () => {
2917
+ if (disposed) return;
2918
+ disposed = true;
2919
+ stopEffect();
2920
+ abortInFlight();
2921
+ }
2873
2922
  };
2874
2923
  }
2875
2924
 
@@ -3102,6 +3151,28 @@ function strictEffect(fn) {
3102
3151
  }
3103
3152
 
3104
3153
  // src/platform/enhance.ts
3154
+ var ENHANCED_ATTR = "data-sibu-enhanced";
3155
+ var enhancementOwner = /* @__PURE__ */ new WeakMap();
3156
+ function drainTeardowns(teardowns, label2) {
3157
+ let executed = 0;
3158
+ while (teardowns.length > 0) {
3159
+ const batch2 = teardowns.splice(0);
3160
+ for (let i2 = 0; i2 < batch2.length; i2++) {
3161
+ if (executed >= MAX_DRAIN_TEARDOWNS) {
3162
+ const remaining = batch2.length - i2 + teardowns.length;
3163
+ teardowns.length = 0;
3164
+ reportDrainRunaway(label2, executed, remaining);
3165
+ return;
3166
+ }
3167
+ executed++;
3168
+ try {
3169
+ batch2[i2]();
3170
+ } catch (err) {
3171
+ if (typeof console !== "undefined") console.error(`[SibuJS ${label2}] teardown error:`, err);
3172
+ }
3173
+ }
3174
+ }
3175
+ }
3105
3176
  function resolveTarget(root, target) {
3106
3177
  if (target == null) return root;
3107
3178
  if (typeof target !== "string") return target;
@@ -3144,7 +3215,7 @@ function enhance(target, setup) {
3144
3215
  return () => {
3145
3216
  };
3146
3217
  }
3147
- if (root.getAttribute("data-sibu-enhanced") === "true") {
3218
+ if (root.getAttribute(ENHANCED_ATTR) === "true") {
3148
3219
  if (isDev() && typeof console !== "undefined") {
3149
3220
  console.warn("[SibuJS enhance] element is already enhanced; ignoring re-enhance.", root);
3150
3221
  }
@@ -3152,6 +3223,7 @@ function enhance(target, setup) {
3152
3223
  };
3153
3224
  }
3154
3225
  const teardowns = [];
3226
+ const owner = /* @__PURE__ */ Symbol("sibujs.enhance");
3155
3227
  let disposed = false;
3156
3228
  const bind = (target_, fn) => {
3157
3229
  const el = resolveTarget(root, target_);
@@ -3198,11 +3270,7 @@ function enhance(target, setup) {
3198
3270
  effect(() => {
3199
3271
  const v = value();
3200
3272
  const next = v == null ? null : String(v);
3201
- if (next === null) {
3202
- if (el.hasAttribute(name)) el.removeAttribute(name);
3203
- } else if (el.getAttribute(name) !== next) {
3204
- el.setAttribute(name, next);
3205
- }
3273
+ setSafeAttribute(el, name, next, { syncValueProperty: false, label: "enhance attr()" });
3206
3274
  })
3207
3275
  );
3208
3276
  });
@@ -3248,29 +3316,50 @@ function enhance(target, setup) {
3248
3316
  teardowns.push(fn);
3249
3317
  }
3250
3318
  };
3251
- const extra = setup(ctx);
3319
+ let extra;
3320
+ try {
3321
+ extra = setup(ctx);
3322
+ } catch (err) {
3323
+ drainTeardowns(teardowns, "enhance");
3324
+ throw err;
3325
+ }
3252
3326
  if (typeof extra === "function") teardowns.push(extra);
3253
3327
  const dispose2 = () => {
3254
3328
  if (disposed) return;
3255
3329
  disposed = true;
3256
- for (const fn of teardowns.splice(0)) {
3257
- try {
3258
- fn();
3259
- } catch (err) {
3260
- if (typeof console !== "undefined") console.error("[SibuJS enhance] teardown error:", err);
3261
- }
3330
+ if (enhancementOwner.get(root) === owner) {
3331
+ enhancementOwner.delete(root);
3332
+ root.removeAttribute(ENHANCED_ATTR);
3262
3333
  }
3334
+ unregisterDisposer(root, dispose2);
3335
+ drainTeardowns(teardowns, "enhance");
3263
3336
  };
3264
3337
  registerDisposer(root, dispose2);
3265
- root.setAttribute("data-sibu-enhanced", "true");
3338
+ enhancementOwner.set(root, owner);
3339
+ root.setAttribute(ENHANCED_ATTR, "true");
3266
3340
  return dispose2;
3267
3341
  }
3268
3342
  function enhanceAll(selector, setup) {
3269
3343
  if (typeof document === "undefined") return () => {
3270
3344
  };
3271
- const disposers = Array.from(document.querySelectorAll(selector)).map((el) => enhance(el, setup));
3345
+ const disposers = [];
3346
+ try {
3347
+ for (const el of Array.from(document.querySelectorAll(selector))) {
3348
+ disposers.push(enhance(el, setup));
3349
+ }
3350
+ } catch (err) {
3351
+ for (let i2 = disposers.length - 1; i2 >= 0; i2--) {
3352
+ try {
3353
+ disposers[i2]();
3354
+ } catch (rollbackErr) {
3355
+ if (typeof console !== "undefined") console.error("[SibuJS enhanceAll] rollback error:", rollbackErr);
3356
+ }
3357
+ }
3358
+ disposers.length = 0;
3359
+ throw err;
3360
+ }
3272
3361
  return () => {
3273
- for (const d of disposers) d();
3362
+ for (const d of disposers.splice(0)) d();
3274
3363
  };
3275
3364
  }
3276
3365
 
@@ -3304,6 +3393,7 @@ function mountIslands(root = typeof document !== "undefined" ? document : null,
3304
3393
  };
3305
3394
  const cancels = [];
3306
3395
  const disposers = [];
3396
+ let torndown = false;
3307
3397
  for (const el of Array.from(root.querySelectorAll("[data-sibu-island]"))) {
3308
3398
  if (el.getAttribute("data-sibu-enhanced") === "true") continue;
3309
3399
  const name = el.getAttribute("data-sibu-island") ?? "";
@@ -3317,17 +3407,23 @@ function mountIslands(root = typeof document !== "undefined" ? document : null,
3317
3407
  }
3318
3408
  let activated = false;
3319
3409
  const activate = () => {
3320
- if (activated) return;
3410
+ if (activated || torndown) return;
3321
3411
  activated = true;
3322
3412
  const reg = registry.get(name);
3323
3413
  if (!reg) return;
3324
3414
  resolveSetup(reg).then((setup) => {
3415
+ if (torndown) return;
3325
3416
  if (!setup) {
3326
3417
  if (typeof console !== "undefined") console.warn(`[SibuJS islands] "${name}" loader produced no setup.`);
3327
3418
  return;
3328
3419
  }
3329
3420
  try {
3330
- disposers.push(enhance(el, setup));
3421
+ const disposeIsland = enhance(el, setup);
3422
+ if (torndown) {
3423
+ disposeIsland();
3424
+ return;
3425
+ }
3426
+ disposers.push(disposeIsland);
3331
3427
  el.setAttribute("data-sibu-hydrated", "true");
3332
3428
  } catch (err) {
3333
3429
  if (typeof console !== "undefined") console.error(`[SibuJS islands] "${name}" failed to mount:`, err);
@@ -3340,6 +3436,7 @@ function mountIslands(root = typeof document !== "undefined" ? document : null,
3340
3436
  cancels.push(schedule(strategy, el, activate, options));
3341
3437
  }
3342
3438
  return () => {
3439
+ torndown = true;
3343
3440
  for (const c of cancels.splice(0)) c();
3344
3441
  for (const d of disposers.splice(0)) d();
3345
3442
  };