sibujs 1.5.0 → 2.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 (207) hide show
  1. package/dist/browser.cjs +238 -69
  2. package/dist/browser.d.cts +5 -0
  3. package/dist/browser.d.ts +5 -0
  4. package/dist/browser.js +6 -6
  5. package/dist/build.cjs +916 -292
  6. package/dist/build.js +15 -13
  7. package/dist/cdn.global.js +17 -16
  8. package/dist/chunk-2RA7SHDA.js +65 -0
  9. package/dist/chunk-2UPRY23K.js +80 -0
  10. package/dist/chunk-3JHCYHWN.js +125 -0
  11. package/dist/{chunk-VAPYJN4X.js → chunk-3LR7GLWQ.js} +93 -23
  12. package/dist/{chunk-RJ46C3CS.js → chunk-3NSGB5JN.js} +71 -20
  13. package/dist/{chunk-XUEEGU5O.js → chunk-52YJLLRO.js} +16 -4
  14. package/dist/{chunk-XHK6BDAJ.js → chunk-54EDRCEF.js} +25 -8
  15. package/dist/chunk-7JDB7I65.js +1327 -0
  16. package/dist/{chunk-WZSPOOER.js → chunk-CC65Y57T.js} +8 -5
  17. package/dist/{chunk-23VV7YD3.js → chunk-DFPFITST.js} +25 -30
  18. package/dist/{chunk-BGN5ZMP4.js → chunk-GTBNNBJ6.js} +14 -2
  19. package/dist/chunk-HB24TBAF.js +121 -0
  20. package/dist/{chunk-CZUGLNJS.js → chunk-ITX6OO3F.js} +3 -3
  21. package/dist/{chunk-BGTHZHJ5.js → chunk-JA6667UN.js} +188 -44
  22. package/dist/{chunk-7GRNSCFT.js → chunk-JXMMDLBY.js} +306 -183
  23. package/dist/{chunk-3X2YG6YM.js → chunk-JYD2PWXH.js} +59 -28
  24. package/dist/{chunk-SFKNRVCU.js → chunk-KLRMB5ZS.js} +135 -79
  25. package/dist/{chunk-5X6PP2UK.js → chunk-LMLD24FC.js} +2 -2
  26. package/dist/{chunk-M4NLBH4I.js → chunk-LYTCUZ7H.js} +3 -2
  27. package/dist/{chunk-BMPL52BF.js → chunk-MIUAXB7K.js} +118 -66
  28. package/dist/{chunk-JCDUJN2F.js → chunk-ND2664SF.js} +486 -153
  29. package/dist/{chunk-VQDZK23A.js → chunk-O2MNQFLP.js} +181 -66
  30. package/dist/{chunk-NHUC2QWH.js → chunk-R73P76YZ.js} +1 -1
  31. package/dist/{chunk-2BYQDGN3.js → chunk-SAHNHTFC.js} +234 -63
  32. package/dist/chunk-UCS6AMJ7.js +79 -0
  33. package/dist/{chunk-K4G4ZQNR.js → chunk-VLPPXTYG.js} +84 -38
  34. package/dist/{chunk-OUZZEE4S.js → chunk-WOMYAHHI.js} +17 -11
  35. package/dist/{customElement-BL3Uo8dL.d.cts → customElement-CPfIrbvg.d.cts} +14 -10
  36. package/dist/{customElement-BL3Uo8dL.d.ts → customElement-CPfIrbvg.d.ts} +14 -10
  37. package/dist/data.cjs +410 -99
  38. package/dist/data.d.cts +20 -2
  39. package/dist/data.d.ts +20 -2
  40. package/dist/data.js +11 -9
  41. package/dist/devtools.cjs +513 -223
  42. package/dist/devtools.d.cts +1 -1
  43. package/dist/devtools.d.ts +1 -1
  44. package/dist/devtools.js +12 -6
  45. package/dist/ecosystem.cjs +475 -144
  46. package/dist/ecosystem.d.cts +9 -7
  47. package/dist/ecosystem.d.ts +9 -7
  48. package/dist/ecosystem.js +12 -11
  49. package/dist/extras.cjs +3355 -1541
  50. package/dist/extras.d.cts +9 -9
  51. package/dist/extras.d.ts +9 -9
  52. package/dist/extras.js +58 -45
  53. package/dist/index.cjs +920 -292
  54. package/dist/index.d.cts +71 -8
  55. package/dist/index.d.ts +71 -8
  56. package/dist/index.js +28 -16
  57. package/dist/{introspect-BumjnBKr.d.cts → introspect-BWNjNw64.d.cts} +22 -2
  58. package/dist/{introspect-CZrlcaYy.d.ts → introspect-cY2pg9pW.d.ts} +22 -2
  59. package/dist/motion.cjs +77 -34
  60. package/dist/motion.js +4 -4
  61. package/dist/patterns.cjs +335 -69
  62. package/dist/patterns.d.cts +11 -12
  63. package/dist/patterns.d.ts +11 -12
  64. package/dist/patterns.js +7 -7
  65. package/dist/performance.cjs +279 -108
  66. package/dist/performance.d.cts +23 -16
  67. package/dist/performance.d.ts +23 -16
  68. package/dist/performance.js +13 -8
  69. package/dist/plugin-D30wlGW5.d.cts +71 -0
  70. package/dist/plugin-D30wlGW5.d.ts +71 -0
  71. package/dist/plugins.cjs +635 -260
  72. package/dist/plugins.d.cts +10 -3
  73. package/dist/plugins.d.ts +10 -3
  74. package/dist/plugins.js +106 -38
  75. package/dist/{ssr-Do_SiVoL.d.cts → ssr-CrVNy6Pa.d.cts} +9 -15
  76. package/dist/{ssr-Do_SiVoL.d.ts → ssr-CrVNy6Pa.d.ts} +9 -15
  77. package/dist/{ssr-4PBXAOO3.js → ssr-FXD2PPMC.js} +4 -3
  78. package/dist/ssr.cjs +642 -222
  79. package/dist/ssr.d.cts +26 -6
  80. package/dist/ssr.d.ts +26 -6
  81. package/dist/ssr.js +12 -11
  82. package/dist/{tagFactory-DaJ0YWX6.d.cts → tagFactory-S17H2qxu.d.cts} +9 -1
  83. package/dist/{tagFactory-DaJ0YWX6.d.ts → tagFactory-S17H2qxu.d.ts} +9 -1
  84. package/dist/testing.cjs +252 -63
  85. package/dist/testing.d.cts +17 -4
  86. package/dist/testing.d.ts +17 -4
  87. package/dist/testing.js +100 -44
  88. package/dist/ui.cjs +463 -137
  89. package/dist/ui.d.cts +1 -1
  90. package/dist/ui.d.ts +1 -1
  91. package/dist/ui.js +20 -17
  92. package/dist/widgets.cjs +977 -94
  93. package/dist/widgets.d.cts +104 -2
  94. package/dist/widgets.d.ts +104 -2
  95. package/dist/widgets.js +9 -7
  96. package/package.json +8 -2
  97. package/dist/chunk-32DY64NT.js +0 -282
  98. package/dist/chunk-3AIRKM3B.js +0 -1263
  99. package/dist/chunk-3ARAQO7B.js +0 -398
  100. package/dist/chunk-3CRQALYP.js +0 -877
  101. package/dist/chunk-4EI4AG32.js +0 -482
  102. package/dist/chunk-4MYMUBRS.js +0 -21
  103. package/dist/chunk-5ZYQ6KDD.js +0 -154
  104. package/dist/chunk-6BMPXPUW.js +0 -26
  105. package/dist/chunk-6HLLIF3K.js +0 -398
  106. package/dist/chunk-6LSNVCS2.js +0 -937
  107. package/dist/chunk-6SA3QQES.js +0 -61
  108. package/dist/chunk-77L6NL3X.js +0 -1097
  109. package/dist/chunk-7BF6TK55.js +0 -1097
  110. package/dist/chunk-7TQKR4PP.js +0 -294
  111. package/dist/chunk-7V26P53V.js +0 -712
  112. package/dist/chunk-AZ3ISID5.js +0 -298
  113. package/dist/chunk-B7SWRFUT.js +0 -332
  114. package/dist/chunk-BTU3TJDS.js +0 -365
  115. package/dist/chunk-BW3WT46K.js +0 -937
  116. package/dist/chunk-C6KFWOFV.js +0 -616
  117. package/dist/chunk-CHF5OHIA.js +0 -61
  118. package/dist/chunk-CHJ27IGK.js +0 -26
  119. package/dist/chunk-CMBFNA7L.js +0 -27
  120. package/dist/chunk-DAHRH4ON.js +0 -331
  121. package/dist/chunk-DKOHBI74.js +0 -924
  122. package/dist/chunk-DTCOOBMX.js +0 -725
  123. package/dist/chunk-EBGIRKQY.js +0 -616
  124. package/dist/chunk-EUZND3CB.js +0 -27
  125. package/dist/chunk-EVCZO745.js +0 -365
  126. package/dist/chunk-EWFVA3TJ.js +0 -282
  127. package/dist/chunk-F3FA4F32.js +0 -292
  128. package/dist/chunk-FGOEVHY3.js +0 -60
  129. package/dist/chunk-G3BOQPVO.js +0 -365
  130. package/dist/chunk-GCOK2LC3.js +0 -282
  131. package/dist/chunk-GJPXRJ45.js +0 -37
  132. package/dist/chunk-HGMJFBC7.js +0 -654
  133. package/dist/chunk-JAKHTMQU.js +0 -1000
  134. package/dist/chunk-JCI5M6U6.js +0 -956
  135. package/dist/chunk-K5ZUMYVS.js +0 -89
  136. package/dist/chunk-KQPDEVVS.js +0 -398
  137. package/dist/chunk-L6JRBDNS.js +0 -60
  138. package/dist/chunk-LA6KQEDU.js +0 -712
  139. package/dist/chunk-MB6QFH3I.js +0 -2776
  140. package/dist/chunk-MDVXJWFN.js +0 -304
  141. package/dist/chunk-MEZVEBPN.js +0 -2008
  142. package/dist/chunk-MK4ERFYL.js +0 -2249
  143. package/dist/chunk-MLKGABMK.js +0 -9
  144. package/dist/chunk-MQ5GOYPH.js +0 -2249
  145. package/dist/chunk-MYRV7VDM.js +0 -742
  146. package/dist/chunk-N6IZB6KJ.js +0 -567
  147. package/dist/chunk-NEKUBFPT.js +0 -60
  148. package/dist/chunk-NMRUZALC.js +0 -1097
  149. package/dist/chunk-NYVAC6P5.js +0 -37
  150. package/dist/chunk-NZIIMDWI.js +0 -84
  151. package/dist/chunk-OF7UZIVB.js +0 -725
  152. package/dist/chunk-P3XWXJZU.js +0 -282
  153. package/dist/chunk-P6W3STU4.js +0 -2249
  154. package/dist/chunk-PBHF5WKN.js +0 -616
  155. package/dist/chunk-PDZQY43A.js +0 -616
  156. package/dist/chunk-PTQJDMRT.js +0 -146
  157. package/dist/chunk-PZEGYCF5.js +0 -61
  158. package/dist/chunk-QBMDLBU2.js +0 -975
  159. package/dist/chunk-QWZG56ET.js +0 -2744
  160. package/dist/chunk-RQGQSLQK.js +0 -725
  161. package/dist/chunk-SDLZDHKP.js +0 -107
  162. package/dist/chunk-TDGZL5CU.js +0 -365
  163. package/dist/chunk-TNQWPPE6.js +0 -37
  164. package/dist/chunk-TSOKIX5Z.js +0 -654
  165. package/dist/chunk-UHNL42EF.js +0 -2730
  166. package/dist/chunk-UNXCEF6S.js +0 -21
  167. package/dist/chunk-V2XTI523.js +0 -347
  168. package/dist/chunk-VAU366PN.js +0 -2241
  169. package/dist/chunk-VMVDTCXB.js +0 -712
  170. package/dist/chunk-VQNQZCWJ.js +0 -61
  171. package/dist/chunk-VRW3FULF.js +0 -725
  172. package/dist/chunk-WADYRCO2.js +0 -304
  173. package/dist/chunk-WILQZRO4.js +0 -282
  174. package/dist/chunk-WR5D4EGH.js +0 -26
  175. package/dist/chunk-WUHJISPP.js +0 -298
  176. package/dist/chunk-XYU6TZOW.js +0 -182
  177. package/dist/chunk-Y6GP4QGG.js +0 -276
  178. package/dist/chunk-YECR7UIA.js +0 -347
  179. package/dist/chunk-YUTWTI4B.js +0 -654
  180. package/dist/chunk-Z65KYU7I.js +0 -26
  181. package/dist/chunk-Z6POF5YC.js +0 -975
  182. package/dist/chunk-ZBJP6WFL.js +0 -482
  183. package/dist/chunk-ZD6OAMTH.js +0 -277
  184. package/dist/chunk-ZWKZCBO6.js +0 -317
  185. package/dist/contracts-DDrwxvJ-.d.cts +0 -245
  186. package/dist/contracts-DDrwxvJ-.d.ts +0 -245
  187. package/dist/contracts-DOrhwbke.d.cts +0 -245
  188. package/dist/contracts-DOrhwbke.d.ts +0 -245
  189. package/dist/contracts-xo5ckdRP.d.cts +0 -240
  190. package/dist/contracts-xo5ckdRP.d.ts +0 -240
  191. package/dist/customElement-BKQfbSZQ.d.cts +0 -262
  192. package/dist/customElement-BKQfbSZQ.d.ts +0 -262
  193. package/dist/customElement-D2DJp_xn.d.cts +0 -313
  194. package/dist/customElement-D2DJp_xn.d.ts +0 -313
  195. package/dist/customElement-yz8uyk-0.d.cts +0 -308
  196. package/dist/customElement-yz8uyk-0.d.ts +0 -308
  197. package/dist/introspect-Cb0zgpi2.d.cts +0 -477
  198. package/dist/introspect-Y2xNXGSf.d.ts +0 -477
  199. package/dist/plugin-Bek4RhJY.d.cts +0 -43
  200. package/dist/plugin-Bek4RhJY.d.ts +0 -43
  201. package/dist/ssr-3RXHP5ES.js +0 -38
  202. package/dist/ssr-6GIMY5MX.js +0 -38
  203. package/dist/ssr-BA6sxxUd.d.cts +0 -135
  204. package/dist/ssr-BA6sxxUd.d.ts +0 -135
  205. package/dist/ssr-WKUPVSSK.js +0 -36
  206. package/dist/tagFactory-Dl8QCLga.d.cts +0 -23
  207. package/dist/tagFactory-Dl8QCLga.d.ts +0 -23
package/dist/index.cjs CHANGED
@@ -99,6 +99,7 @@ __export(index_exports, {
99
99
  footer: () => footer,
100
100
  form: () => form,
101
101
  g: () => g,
102
+ getSSRStore: () => getSSRStore,
102
103
  getSlot: () => getSlot,
103
104
  h1: () => h1,
104
105
  h2: () => h2,
@@ -171,6 +172,7 @@ __export(index_exports, {
171
172
  rp: () => rp,
172
173
  rt: () => rt,
173
174
  ruby: () => ruby,
175
+ runInSSRContext: () => runInSSRContext,
174
176
  s: () => s,
175
177
  samp: () => samp,
176
178
  script: () => script,
@@ -196,6 +198,7 @@ __export(index_exports, {
196
198
  symbol: () => symbol,
197
199
  table: () => table,
198
200
  tagFactory: () => tagFactory,
201
+ takePendingError: () => takePendingError,
199
202
  tbody: () => tbody,
200
203
  td: () => td,
201
204
  template: () => template,
@@ -210,6 +213,7 @@ __export(index_exports, {
210
213
  track: () => track2,
211
214
  transition: () => transition,
212
215
  trapFocus: () => trapFocus,
216
+ trustHTML: () => trustHTML,
213
217
  tspan: () => tspan,
214
218
  u: () => u,
215
219
  ul: () => ul,
@@ -232,33 +236,80 @@ function isDev() {
232
236
  var _isDev = isDev();
233
237
  function devAssert(condition, message) {
234
238
  if (_isDev && !condition) {
235
- throw new Error(`[Sibu] ${message}`);
239
+ throw new Error(`[SibuJS] ${message}`);
236
240
  }
237
241
  }
238
242
  function devWarn(message) {
239
243
  if (_isDev) {
240
- console.warn(`[Sibu] ${message}`);
244
+ console.warn(`[SibuJS] ${message}`);
241
245
  }
242
246
  }
243
247
 
244
248
  // src/utils/sanitize.ts
249
+ var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
245
250
  function sanitizeUrl(url) {
246
251
  const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
247
252
  if (!trimmed) return "";
248
253
  const lower = trimmed.toLowerCase();
249
- if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
250
- return "";
254
+ let schemeEnd = -1;
255
+ for (let i2 = 0; i2 < lower.length; i2++) {
256
+ const ch = lower.charCodeAt(i2);
257
+ if (ch === 58) {
258
+ schemeEnd = i2;
259
+ break;
260
+ }
261
+ if (ch === 47 || ch === 63 || ch === 35) break;
251
262
  }
263
+ if (schemeEnd === -1) return trimmed;
264
+ const scheme = lower.slice(0, schemeEnd + 1);
265
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return trimmed;
266
+ if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
252
267
  return trimmed;
253
268
  }
269
+ function sanitizeSrcset(value) {
270
+ const parts = value.split(",");
271
+ const out = [];
272
+ for (let i2 = 0; i2 < parts.length; i2++) {
273
+ const part = parts[i2].trim();
274
+ if (!part) continue;
275
+ const m = part.match(/^(\S+)(\s+.+)?$/);
276
+ if (!m) continue;
277
+ const safe = sanitizeUrl(m[1]);
278
+ if (!safe) continue;
279
+ out.push(m[2] ? `${safe}${m[2]}` : safe);
280
+ }
281
+ return out.join(", ");
282
+ }
254
283
  function sanitizeCSSValue(value) {
255
- const lower = value.toLowerCase().replace(/\s+/g, "");
256
- if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("-moz-binding")) {
284
+ const decoded = value.replace(/\\([0-9a-fA-F]{1,6})\s?/g, (_m, hex) => {
285
+ const code2 = Number.parseInt(hex, 16);
286
+ if (!Number.isFinite(code2) || code2 < 0 || code2 > 1114111) return "";
287
+ try {
288
+ return String.fromCodePoint(code2);
289
+ } catch {
290
+ return "";
291
+ }
292
+ });
293
+ const lower = decoded.toLowerCase().replace(/\s+/g, "");
294
+ if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("vbscript:") || lower.includes("-moz-binding") || lower.includes("behavior:") || lower.includes("@import") || lower.includes("image-set(") || lower.includes("filter:progid")) {
257
295
  return "";
258
296
  }
259
297
  return value;
260
298
  }
261
- var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "cite", "poster", "background", "srcset"]);
299
+ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
300
+ "href",
301
+ "xlink:href",
302
+ "src",
303
+ "action",
304
+ "formaction",
305
+ "formtarget",
306
+ "cite",
307
+ "poster",
308
+ "background",
309
+ "srcset",
310
+ "ping",
311
+ "data"
312
+ ]);
262
313
  function isUrlAttribute(attr) {
263
314
  return URL_ATTRIBUTES.has(attr);
264
315
  }
@@ -269,11 +320,11 @@ var subscriberStack = new Array(32);
269
320
  var stackCapacity = 32;
270
321
  var stackTop = -1;
271
322
  var currentSubscriber = null;
272
- var signalSubscribers = /* @__PURE__ */ new WeakMap();
273
323
  var SUBS = "__s";
274
324
  var notifyDepth = 0;
275
325
  var pendingQueue = [];
276
326
  var pendingSet = /* @__PURE__ */ new Set();
327
+ var propagateStack = [];
277
328
  function safeInvoke(sub2) {
278
329
  try {
279
330
  sub2();
@@ -283,6 +334,15 @@ function safeInvoke(sub2) {
283
334
  }
284
335
  var suspendDepth = 0;
285
336
  var trackingSuspended = false;
337
+ function retrack(effectFn, subscriber) {
338
+ const prev = currentSubscriber;
339
+ currentSubscriber = subscriber;
340
+ try {
341
+ effectFn();
342
+ } finally {
343
+ currentSubscriber = prev;
344
+ }
345
+ }
286
346
  function track(effectFn, subscriber) {
287
347
  if (!subscriber) subscriber = effectFn;
288
348
  cleanup(subscriber);
@@ -350,7 +410,6 @@ function recordDependency(signal2) {
350
410
  let subs = signal2[SUBS];
351
411
  if (!subs) {
352
412
  subs = /* @__PURE__ */ new Set();
353
- signalSubscribers.set(signal2, subs);
354
413
  signal2[SUBS] = subs;
355
414
  }
356
415
  subs.add(currentSubscriber);
@@ -372,17 +431,17 @@ function queueSignalNotification(signal2) {
372
431
  }
373
432
  }
374
433
  }
375
- var MAX_DRAIN_ITERATIONS = 1e3;
434
+ var maxDrainIterations = 1e5;
376
435
  function drainNotificationQueue() {
377
436
  if (notifyDepth > 0) return;
378
437
  notifyDepth++;
379
438
  try {
380
439
  let i2 = 0;
381
440
  while (i2 < pendingQueue.length) {
382
- if (i2 >= MAX_DRAIN_ITERATIONS) {
441
+ if (i2 >= maxDrainIterations) {
383
442
  if (typeof console !== "undefined") {
384
443
  console.error(
385
- `[SibuJS] Notification queue exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
444
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
386
445
  );
387
446
  }
388
447
  break;
@@ -391,47 +450,52 @@ function drainNotificationQueue() {
391
450
  i2++;
392
451
  }
393
452
  } finally {
394
- pendingQueue.length = 0;
395
- pendingSet.clear();
396
453
  notifyDepth--;
454
+ if (notifyDepth === 0) {
455
+ pendingQueue.length = 0;
456
+ pendingSet.clear();
457
+ }
397
458
  }
398
459
  }
399
460
  function propagateDirty(sub2) {
400
461
  sub2();
401
- let sig = sub2._sig;
402
- while (sig) {
462
+ const rootSig = sub2._sig;
463
+ if (!rootSig) return;
464
+ const stack = propagateStack;
465
+ const baseLen = stack.length;
466
+ stack.push(rootSig);
467
+ while (stack.length > baseLen) {
468
+ const sig = stack.pop();
403
469
  const first = sig.__f;
404
470
  if (first) {
405
471
  if (first._c) {
406
472
  const nSig = first._sig;
407
- nSig._d = true;
408
- sig = nSig;
409
- continue;
410
- }
411
- if (!pendingSet.has(first)) {
473
+ if (!nSig._d) {
474
+ nSig._d = true;
475
+ stack.push(nSig);
476
+ }
477
+ } else if (!pendingSet.has(first)) {
412
478
  pendingSet.add(first);
413
479
  pendingQueue.push(first);
414
480
  }
415
- break;
481
+ continue;
416
482
  }
417
483
  const subs = sig[SUBS];
418
- if (!subs) break;
419
- let nextSig;
484
+ if (!subs) continue;
420
485
  for (const s2 of subs) {
421
486
  if (s2._c) {
422
- s2();
423
487
  const nSig = s2._sig;
424
- if (nSig && !nextSig) {
425
- nextSig = nSig;
426
- } else if (nSig) {
427
- propagateDirty(s2);
488
+ if (nSig && !nSig._d) {
489
+ nSig._d = true;
490
+ stack.push(nSig);
491
+ } else if (!nSig) {
492
+ s2();
428
493
  }
429
494
  } else if (!pendingSet.has(s2)) {
430
495
  pendingSet.add(s2);
431
496
  pendingQueue.push(s2);
432
497
  }
433
498
  }
434
- sig = nextSig;
435
499
  }
436
500
  }
437
501
  function notifySubscribers(signal2) {
@@ -455,13 +519,23 @@ function notifySubscribers(signal2) {
455
519
  }
456
520
  let i2 = 0;
457
521
  while (i2 < pendingQueue.length) {
522
+ if (i2 >= maxDrainIterations) {
523
+ if (typeof console !== "undefined") {
524
+ console.error(
525
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
526
+ );
527
+ }
528
+ break;
529
+ }
458
530
  safeInvoke(pendingQueue[i2]);
459
531
  i2++;
460
532
  }
461
533
  } finally {
462
- pendingQueue.length = 0;
463
- pendingSet.clear();
464
534
  notifyDepth--;
535
+ if (notifyDepth === 0) {
536
+ pendingQueue.length = 0;
537
+ pendingSet.clear();
538
+ }
465
539
  }
466
540
  return;
467
541
  }
@@ -481,30 +555,48 @@ function notifySubscribers(signal2) {
481
555
  notifyDepth++;
482
556
  try {
483
557
  let directCount = 0;
558
+ let hasComputedSub = false;
484
559
  for (const sub2 of subs) {
560
+ if (sub2._c) hasComputedSub = true;
485
561
  pendingQueue[directCount++] = sub2;
486
562
  }
487
- for (let i3 = 0; i3 < directCount; i3++) {
488
- if (pendingQueue[i3]._c) {
489
- propagateDirty(pendingQueue[i3]);
563
+ if (!hasComputedSub) {
564
+ for (let i3 = 0; i3 < directCount; i3++) {
565
+ safeInvoke(pendingQueue[i3]);
490
566
  }
491
- }
492
- for (let i3 = 0; i3 < directCount; i3++) {
493
- if (!pendingQueue[i3]._c) {
494
- if (!pendingSet.has(pendingQueue[i3])) {
495
- safeInvoke(pendingQueue[i3]);
567
+ } else {
568
+ for (let i3 = 0; i3 < directCount; i3++) {
569
+ if (pendingQueue[i3]._c) {
570
+ propagateDirty(pendingQueue[i3]);
571
+ }
572
+ }
573
+ for (let i3 = 0; i3 < directCount; i3++) {
574
+ const sub2 = pendingQueue[i3];
575
+ if (!sub2._c && !pendingSet.has(sub2)) {
576
+ pendingSet.add(sub2);
577
+ safeInvoke(sub2);
496
578
  }
497
579
  }
498
580
  }
499
581
  let i2 = directCount;
500
582
  while (i2 < pendingQueue.length) {
583
+ if (i2 - directCount >= maxDrainIterations) {
584
+ if (typeof console !== "undefined") {
585
+ console.error(
586
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
587
+ );
588
+ }
589
+ break;
590
+ }
501
591
  safeInvoke(pendingQueue[i2]);
502
592
  i2++;
503
593
  }
504
594
  } finally {
505
- pendingQueue.length = 0;
506
- pendingSet.clear();
507
595
  notifyDepth--;
596
+ if (notifyDepth === 0) {
597
+ pendingQueue.length = 0;
598
+ pendingSet.clear();
599
+ }
508
600
  }
509
601
  }
510
602
  function cleanup(subscriber) {
@@ -515,7 +607,9 @@ function cleanup(subscriber) {
515
607
  if (subs) {
516
608
  subs.delete(subscriber);
517
609
  if (singleDep.__f === subscriber) {
518
- singleDep.__f = void 0;
610
+ singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
611
+ } else if (subs.size === 1 && singleDep.__f === void 0) {
612
+ singleDep.__f = subs.values().next().value;
519
613
  }
520
614
  }
521
615
  sub2._dep = void 0;
@@ -528,7 +622,9 @@ function cleanup(subscriber) {
528
622
  if (subs) {
529
623
  subs.delete(subscriber);
530
624
  if (signal2.__f === subscriber) {
531
- signal2.__f = void 0;
625
+ signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
626
+ } else if (subs.size === 1 && signal2.__f === void 0) {
627
+ signal2.__f = subs.values().next().value;
532
628
  }
533
629
  }
534
630
  }
@@ -537,6 +633,9 @@ function cleanup(subscriber) {
537
633
 
538
634
  // src/reactivity/bindAttribute.ts
539
635
  var _isDev3 = isDev();
636
+ function setProp(el, key, val) {
637
+ el[key] = val;
638
+ }
540
639
  function isEventHandlerAttr(name) {
541
640
  if (name.length < 3) return false;
542
641
  const lower = name.toLowerCase();
@@ -562,7 +661,7 @@ function bindAttribute(el, attr, getter) {
562
661
  }
563
662
  if (typeof value === "boolean") {
564
663
  if (attr in el && (attr === "checked" || attr === "disabled" || attr === "selected")) {
565
- el[attr] = value;
664
+ setProp(el, attr, value);
566
665
  } else if (value) {
567
666
  el.setAttribute(attr, "");
568
667
  } else {
@@ -572,7 +671,7 @@ function bindAttribute(el, attr, getter) {
572
671
  }
573
672
  const str = String(value);
574
673
  if ((attr === "value" || attr === "checked") && attr in el) {
575
- el[attr] = attr === "checked" ? Boolean(value) : str;
674
+ setProp(el, attr, attr === "checked" ? Boolean(value) : str);
576
675
  } else {
577
676
  el.setAttribute(attr, isUrlAttribute(attr) ? sanitizeUrl(str) : str);
578
677
  }
@@ -603,7 +702,7 @@ function bindDynamic(el, nameGetter, valueGetter) {
603
702
  }
604
703
  const str = String(value);
605
704
  if ((name === "value" || name === "checked") && name in el) {
606
- el[name] = name === "checked" ? Boolean(value) : str;
705
+ setProp(el, name, name === "checked" ? Boolean(value) : str);
607
706
  } else {
608
707
  el.setAttribute(name, isUrlAttribute(name) ? sanitizeUrl(str) : str);
609
708
  }
@@ -646,24 +745,29 @@ function bindChildNode(placeholder, getter) {
646
745
  let newNodes;
647
746
  if (Array.isArray(result)) {
648
747
  newNodes = [];
748
+ const seen = /* @__PURE__ */ new Set();
649
749
  for (let i2 = 0; i2 < result.length; i2++) {
650
750
  const item = result[i2];
651
751
  if (item == null || typeof item === "boolean") continue;
652
- newNodes.push(item instanceof Node ? item : document.createTextNode(String(item)));
752
+ const node = item instanceof Node ? item : document.createTextNode(String(item));
753
+ if (seen.has(node)) {
754
+ if (_isDev4)
755
+ devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
756
+ continue;
757
+ }
758
+ seen.add(node);
759
+ newNodes.push(node);
653
760
  }
654
761
  } else {
655
762
  const node = result instanceof Node ? result : document.createTextNode(String(result));
656
763
  newNodes = [node];
657
764
  }
658
- const reused = lastNodes.length > 0 && newNodes.length > 0 ? /* @__PURE__ */ new Set() : void 0;
659
- if (reused) {
765
+ let reused;
766
+ if (lastNodes.length > 0 && newNodes.length > 0) {
767
+ const lastSet = new Set(lastNodes);
768
+ reused = /* @__PURE__ */ new Set();
660
769
  for (let i2 = 0; i2 < newNodes.length; i2++) {
661
- for (let j = 0; j < lastNodes.length; j++) {
662
- if (newNodes[i2] === lastNodes[j]) {
663
- reused.add(newNodes[i2]);
664
- break;
665
- }
666
- }
770
+ if (lastSet.has(newNodes[i2])) reused.add(newNodes[i2]);
667
771
  }
668
772
  }
669
773
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
@@ -706,7 +810,7 @@ function dispose(node) {
706
810
  while (stack.length > 0) {
707
811
  const current = stack.pop();
708
812
  order.push(current);
709
- const children = current.childNodes;
813
+ const children = Array.from(current.childNodes);
710
814
  for (let i2 = 0; i2 < children.length; i2++) {
711
815
  stack.push(children[i2]);
712
816
  }
@@ -715,8 +819,10 @@ function dispose(node) {
715
819
  const current = order[i2];
716
820
  const disposers = elementDisposers.get(current);
717
821
  if (disposers) {
718
- if (_isDev5) activeBindingCount -= disposers.length;
719
- for (const d of disposers) {
822
+ const snapshot = disposers.slice();
823
+ elementDisposers.delete(current);
824
+ if (_isDev5) activeBindingCount -= snapshot.length;
825
+ for (const d of snapshot) {
720
826
  try {
721
827
  d();
722
828
  } catch (err) {
@@ -725,7 +831,23 @@ function dispose(node) {
725
831
  }
726
832
  }
727
833
  }
728
- elementDisposers.delete(current);
834
+ let extraPasses = 0;
835
+ while (extraPasses++ < 8) {
836
+ const added = elementDisposers.get(current);
837
+ if (!added || added.length === 0) break;
838
+ const moreSnapshot = added.slice();
839
+ elementDisposers.delete(current);
840
+ if (_isDev5) activeBindingCount -= moreSnapshot.length;
841
+ for (const d of moreSnapshot) {
842
+ try {
843
+ d();
844
+ } catch (err) {
845
+ if (_isDev5 && typeof console !== "undefined") {
846
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
847
+ }
848
+ }
849
+ }
850
+ }
729
851
  }
730
852
  }
731
853
  }
@@ -741,6 +863,30 @@ function checkLeaks(warnThreshold = 0) {
741
863
 
742
864
  // src/core/rendering/tagFactory.ts
743
865
  var SVG_NS = "http://www.w3.org/2000/svg";
866
+ var _isDev6 = isDev();
867
+ var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
868
+ function validateTagName(tag) {
869
+ const lower = tag.toLowerCase();
870
+ if (BLOCKED_TAGS.has(lower)) {
871
+ throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
872
+ }
873
+ }
874
+ var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
875
+ "config",
876
+ "location",
877
+ "history",
878
+ "document",
879
+ "window",
880
+ "navigator",
881
+ "name",
882
+ "top",
883
+ "parent",
884
+ "self",
885
+ "frames"
886
+ ]);
887
+ function setProp2(el, key, val) {
888
+ el[key] = val;
889
+ }
744
890
  var kebabCache = /* @__PURE__ */ new Map();
745
891
  function toKebab(prop) {
746
892
  let cached = kebabCache.get(prop);
@@ -865,79 +1011,103 @@ function appendChildren(el, nodes) {
865
1011
  }
866
1012
  }
867
1013
  }
868
- var tagFactory = (tag, ns) => (first, second) => {
869
- const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
870
- if (first === void 0) return el;
871
- if (typeof first === "string") {
872
- if (second !== void 0) {
873
- el.setAttribute("class", first);
874
- appendChildren(el, second);
1014
+ var tagFactory = (tag, ns) => {
1015
+ return (first, second) => {
1016
+ validateTagName(tag);
1017
+ const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
1018
+ if (first === void 0) return el;
1019
+ if (typeof first === "string") {
1020
+ if (second !== void 0) {
1021
+ el.setAttribute("class", first);
1022
+ appendChildren(el, second);
1023
+ return el;
1024
+ }
1025
+ el.textContent = first;
875
1026
  return el;
876
1027
  }
877
- el.textContent = first;
878
- return el;
879
- }
880
- if (typeof first === "number") {
881
- el.textContent = String(first);
882
- return el;
883
- }
884
- if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
885
- appendChildren(el, first);
886
- return el;
887
- }
888
- const props = first;
889
- const pClass = props.class;
890
- if (pClass != null) applyClass(el, pClass);
891
- const pId = props.id;
892
- if (pId != null) el.id = pId;
893
- const pNodes = second !== void 0 ? second : props.nodes;
894
- if (pNodes != null) appendChildren(el, pNodes);
895
- const pOn = props.on;
896
- if (pOn) {
897
- for (const ev in pOn) {
898
- el.addEventListener(ev, pOn[ev]);
899
- }
900
- }
901
- const pStyle = props.style;
902
- if (pStyle != null) applyStyle(el, pStyle);
903
- const pRef = props.ref;
904
- if (pRef) pRef.current = el;
905
- for (const key in props) {
906
- switch (key) {
907
- case "class":
908
- case "id":
909
- case "nodes":
910
- case "on":
911
- case "style":
912
- case "ref":
913
- case "onElement":
914
- continue;
915
- // already handled above / below
916
- default: {
917
- const value = props[key];
918
- if (value == null) continue;
919
- if (key[0] === "o" && key[1] === "n") continue;
920
- if (typeof value === "function") {
921
- registerDisposer(el, bindAttribute(el, key, value));
922
- } else if (typeof value === "boolean") {
923
- if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
924
- el[key] = value;
925
- } else if (value) {
926
- el.setAttribute(key, "");
1028
+ if (typeof first === "number") {
1029
+ el.textContent = String(first);
1030
+ return el;
1031
+ }
1032
+ if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
1033
+ appendChildren(el, first);
1034
+ return el;
1035
+ }
1036
+ const props = first;
1037
+ const pClass = props.class;
1038
+ if (pClass != null) applyClass(el, pClass);
1039
+ const pId = props.id;
1040
+ if (pId != null) {
1041
+ if (_isDev6 && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
1042
+ devWarn(
1043
+ `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
1044
+ );
1045
+ }
1046
+ el.id = pId;
1047
+ }
1048
+ const pNodes = second !== void 0 ? second : props.nodes;
1049
+ if (pNodes != null) appendChildren(el, pNodes);
1050
+ const pOn = props.on;
1051
+ if (pOn) {
1052
+ for (const ev in pOn) {
1053
+ const handler = pOn[ev];
1054
+ if (typeof handler === "function") {
1055
+ el.addEventListener(ev, handler);
1056
+ } else if (_isDev6) {
1057
+ devWarn(
1058
+ `tagFactory: on.${ev} handler is not a function (got ${typeof handler}). Event listener was not attached.`
1059
+ );
1060
+ }
1061
+ }
1062
+ }
1063
+ const pStyle = props.style;
1064
+ if (pStyle != null) applyStyle(el, pStyle);
1065
+ const pRef = props.ref;
1066
+ if (pRef) pRef.current = el;
1067
+ for (const key in props) {
1068
+ switch (key) {
1069
+ case "class":
1070
+ case "id":
1071
+ case "nodes":
1072
+ case "on":
1073
+ case "style":
1074
+ case "ref":
1075
+ case "onElement":
1076
+ continue;
1077
+ // already handled above / below
1078
+ default: {
1079
+ const value = props[key];
1080
+ if (value == null) continue;
1081
+ const lkey = key.toLowerCase();
1082
+ if (lkey[0] === "o" && lkey[1] === "n") continue;
1083
+ if (typeof value === "function") {
1084
+ registerDisposer(el, bindAttribute(el, key, value));
1085
+ } else if (typeof value === "boolean") {
1086
+ if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
1087
+ setProp2(el, key, value);
1088
+ } else if (value) {
1089
+ el.setAttribute(key, "");
1090
+ } else {
1091
+ el.removeAttribute(key);
1092
+ }
927
1093
  } else {
928
- el.removeAttribute(key);
1094
+ const str = String(value);
1095
+ if (lkey === "srcset") {
1096
+ el.setAttribute(key, sanitizeSrcset(str));
1097
+ } else if (isUrlAttribute(lkey)) {
1098
+ el.setAttribute(key, sanitizeUrl(str));
1099
+ } else {
1100
+ el.setAttribute(key, str);
1101
+ }
929
1102
  }
930
- } else {
931
- const str = String(value);
932
- el.setAttribute(key, isUrlAttribute(key) ? sanitizeUrl(str) : str);
933
1103
  }
934
1104
  }
935
1105
  }
936
- }
937
- if (props.onElement && typeof props.onElement === "function") {
938
- props.onElement(el);
939
- }
940
- return el;
1106
+ if (props.onElement && typeof props.onElement === "function") {
1107
+ props.onElement(el);
1108
+ }
1109
+ return el;
1110
+ };
941
1111
  };
942
1112
 
943
1113
  // src/core/rendering/html.ts
@@ -1080,6 +1250,8 @@ var marquee = tagFactory("marquee");
1080
1250
  var customElement = (tagName) => tagFactory(tagName);
1081
1251
 
1082
1252
  // src/core/rendering/htm.ts
1253
+ var _isDev7 = isDev();
1254
+ var RAW_TEXT_TAGS = /* @__PURE__ */ new Set(["script", "style"]);
1083
1255
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
1084
1256
  "area",
1085
1257
  "base",
@@ -1264,6 +1436,15 @@ function parseTemplate(strings) {
1264
1436
  children.push({ t: 0, el: { tag, svg: SVG_TAGS.has(tag), attrs, children: [] } });
1265
1437
  } else {
1266
1438
  const inner = parseChildren();
1439
+ if (RAW_TEXT_TAGS.has(tag.toLowerCase())) {
1440
+ for (let i2 = 0; i2 < inner.length; i2++) {
1441
+ if (inner[i2].t === 2) {
1442
+ throw new Error(
1443
+ `html: dynamic \${...} expressions are not allowed inside <${tag}> (raw-text context). Build the content separately and append it as a Node.`
1444
+ );
1445
+ }
1446
+ }
1447
+ }
1267
1448
  if (template2[pos] === "<" && pos + 1 < len && template2[pos + 1] === "/") {
1268
1449
  pos += 2;
1269
1450
  readTagName();
@@ -1290,27 +1471,50 @@ function executeElement(tmpl, values) {
1290
1471
  break;
1291
1472
  case 1: {
1292
1473
  const name = attr.name;
1293
- if (name[0] === "o" && name[1] === "n") break;
1474
+ const lname = name.toLowerCase();
1475
+ if (lname[0] === "o" && lname[1] === "n") break;
1294
1476
  const val = values[attr.idx];
1295
1477
  if (typeof val === "function") {
1296
1478
  registerDisposer(el, bindAttribute(el, name, val));
1297
1479
  } else if (val != null) {
1298
1480
  const str = String(val);
1299
- el.setAttribute(name, isUrlAttribute(name) ? sanitizeUrl(str) : str);
1481
+ if (lname === "srcset") {
1482
+ el.setAttribute(name, sanitizeSrcset(str));
1483
+ } else if (isUrlAttribute(lname)) {
1484
+ el.setAttribute(name, sanitizeUrl(str));
1485
+ } else {
1486
+ el.setAttribute(name, str);
1487
+ }
1300
1488
  }
1301
1489
  break;
1302
1490
  }
1303
1491
  case 2: {
1304
1492
  let val = attr.statics[0];
1305
1493
  for (let j = 0; j < attr.exprs.length; j++) {
1306
- val += String(values[attr.exprs[j]]) + attr.statics[j + 1];
1494
+ const ev = values[attr.exprs[j]];
1495
+ val += (ev == null ? "" : String(ev)) + attr.statics[j + 1];
1496
+ }
1497
+ const lname2 = attr.name.toLowerCase();
1498
+ if (lname2 === "srcset") {
1499
+ el.setAttribute(attr.name, sanitizeSrcset(val));
1500
+ } else if (isUrlAttribute(lname2)) {
1501
+ el.setAttribute(attr.name, sanitizeUrl(val));
1502
+ } else {
1503
+ el.setAttribute(attr.name, val);
1307
1504
  }
1308
- el.setAttribute(attr.name, val);
1309
1505
  break;
1310
1506
  }
1311
- case 3:
1312
- el.addEventListener(attr.name, values[attr.idx]);
1507
+ case 3: {
1508
+ const fn = values[attr.idx];
1509
+ if (typeof fn === "function") {
1510
+ el.addEventListener(attr.name, fn);
1511
+ } else if (_isDev7) {
1512
+ devWarn(
1513
+ `html: on:${attr.name} handler is not a function (got ${typeof fn}). Event listener was not attached.`
1514
+ );
1515
+ }
1313
1516
  break;
1517
+ }
1314
1518
  case 4:
1315
1519
  el.setAttribute(attr.name, "");
1316
1520
  break;
@@ -1404,7 +1608,7 @@ function html2(strings, ...values) {
1404
1608
  function mount(component, container) {
1405
1609
  if (!container) {
1406
1610
  throw new Error(
1407
- "[Sibu] mount: container element not found. Make sure the DOM element exists before calling mount()."
1611
+ "[SibuJS mount] container element not found. Make sure the DOM element exists before calling mount()."
1408
1612
  );
1409
1613
  }
1410
1614
  devAssert(
@@ -1432,7 +1636,7 @@ function mount(component, container) {
1432
1636
  }
1433
1637
 
1434
1638
  // src/core/rendering/each.ts
1435
- var _isDev6 = isDev();
1639
+ var _isDev8 = isDev();
1436
1640
  function resolveNodeChild(child) {
1437
1641
  if (typeof child === "function") {
1438
1642
  return resolveNodeChild(child());
@@ -1527,17 +1731,31 @@ function each(getArray, render, options) {
1527
1731
  node = existing;
1528
1732
  } else {
1529
1733
  const itemKey = key;
1530
- const itemGetter = () => getArray()[keyIndexMap.get(itemKey)];
1734
+ const itemGetter = () => untracked(() => getArray()[keyIndexMap.get(itemKey)]);
1531
1735
  const indexGetter = () => keyIndexMap.get(itemKey);
1532
1736
  try {
1533
1737
  node = resolveNodeChild(render(itemGetter, indexGetter));
1534
1738
  } catch (err) {
1535
- if (_isDev6) {
1739
+ if (_isDev8) {
1536
1740
  devWarn(
1537
1741
  `each: render threw for item at index ${i2} (key="${newKeys[i2]}"): ${err instanceof Error ? err.message : String(err)}`
1538
1742
  );
1539
1743
  }
1540
1744
  node = document.createComment(`each:error:${i2}`);
1745
+ const errorObj = err instanceof Error ? err : new Error(String(err));
1746
+ queueMicrotask(() => {
1747
+ try {
1748
+ const target = anchor.parentNode;
1749
+ if (target?.dispatchEvent) {
1750
+ target.dispatchEvent(
1751
+ new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error: errorObj } })
1752
+ );
1753
+ } else if (_isDev8) {
1754
+ devWarn(`each: error not surfaced \u2014 anchor detached: ${errorObj.message}`);
1755
+ }
1756
+ } catch {
1757
+ }
1758
+ });
1541
1759
  }
1542
1760
  }
1543
1761
  workMap.set(key, node);
@@ -1606,7 +1824,8 @@ function each(getArray, render, options) {
1606
1824
  workMap = tmp;
1607
1825
  initialized = true;
1608
1826
  };
1609
- track(update);
1827
+ const untrack = track(update);
1828
+ registerDisposer(anchor, untrack);
1610
1829
  if (!initialized) {
1611
1830
  queueMicrotask(() => {
1612
1831
  if (!initialized && anchor.parentNode) {
@@ -1653,7 +1872,9 @@ function Portal(nodes, target) {
1653
1872
  const anchor = document.createComment("portal");
1654
1873
  const container = target || document.body;
1655
1874
  let portalContent = null;
1875
+ let disposed = false;
1656
1876
  queueMicrotask(() => {
1877
+ if (disposed) return;
1657
1878
  try {
1658
1879
  portalContent = nodes();
1659
1880
  container.appendChild(portalContent);
@@ -1661,9 +1882,22 @@ function Portal(nodes, target) {
1661
1882
  if (typeof console !== "undefined") {
1662
1883
  console.error("[Portal] Render error:", err);
1663
1884
  }
1885
+ const errorObj = err instanceof Error ? err : new Error(String(err));
1886
+ queueMicrotask(() => {
1887
+ try {
1888
+ const target2 = anchor.parentNode;
1889
+ if (target2?.dispatchEvent) {
1890
+ target2.dispatchEvent(
1891
+ new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error: errorObj } })
1892
+ );
1893
+ }
1894
+ } catch {
1895
+ }
1896
+ });
1664
1897
  }
1665
1898
  });
1666
1899
  registerDisposer(anchor, () => {
1900
+ disposed = true;
1667
1901
  if (portalContent) {
1668
1902
  dispose(portalContent);
1669
1903
  portalContent.remove();
@@ -1703,7 +1937,8 @@ function DynamicComponent(is) {
1703
1937
  }
1704
1938
  container.replaceChildren(el);
1705
1939
  }
1706
- track(render);
1940
+ const untrack = track(render);
1941
+ registerDisposer(container, untrack);
1707
1942
  return container;
1708
1943
  }
1709
1944
 
@@ -1793,11 +2028,16 @@ function KeepAlive(activeKey, cases, options) {
1793
2028
  const anchor = document.createComment("keep-alive");
1794
2029
  const cache2 = /* @__PURE__ */ new Map();
1795
2030
  const lruOrder = [];
1796
- const max = options?.max ?? 0;
2031
+ const max = options?.max ?? 10;
2032
+ if (max === 0 && isDev()) {
2033
+ devWarn("KeepAlive: unbounded cache (max: 0). Cached subtrees will never be evicted \u2014 set `max` to bound memory.");
2034
+ }
1797
2035
  let currentKey;
1798
2036
  let currentNode = null;
1799
2037
  let initialized = false;
2038
+ let disposed = false;
1800
2039
  const update = () => {
2040
+ if (disposed) return;
1801
2041
  const key = activeKey();
1802
2042
  const parent = anchor.parentNode;
1803
2043
  if (!parent) return;
@@ -1837,12 +2077,23 @@ function KeepAlive(activeKey, cases, options) {
1837
2077
  currentNode = node;
1838
2078
  initialized = true;
1839
2079
  };
1840
- track(update);
2080
+ const untrack = track(update);
1841
2081
  if (!initialized) {
1842
2082
  queueMicrotask(() => {
1843
2083
  if (!initialized && anchor.parentNode) update();
1844
2084
  });
1845
2085
  }
2086
+ registerDisposer(anchor, () => {
2087
+ disposed = true;
2088
+ untrack();
2089
+ for (const node of cache2.values()) {
2090
+ dispose(node);
2091
+ if (node.parentNode) node.parentNode.removeChild(node);
2092
+ }
2093
+ cache2.clear();
2094
+ lruOrder.length = 0;
2095
+ currentNode = null;
2096
+ });
1846
2097
  return anchor;
1847
2098
  }
1848
2099
 
@@ -2004,19 +2255,22 @@ function isBatching() {
2004
2255
  return batchDepth > 0;
2005
2256
  }
2006
2257
  function flushBatch() {
2007
- for (const signal2 of pendingSignals) {
2008
- queueSignalNotification(signal2);
2258
+ try {
2259
+ for (const signal2 of pendingSignals) {
2260
+ queueSignalNotification(signal2);
2261
+ }
2262
+ } finally {
2263
+ pendingSignals.clear();
2009
2264
  }
2010
- pendingSignals.clear();
2011
2265
  drainNotificationQueue();
2012
2266
  }
2013
2267
 
2014
2268
  // src/core/signals/signal.ts
2015
2269
  var _g = globalThis;
2016
- var _isDev7 = isDev();
2270
+ var _isDev9 = isDev();
2017
2271
  function signal(initial, options) {
2018
2272
  const state = { value: initial };
2019
- const debugName = _isDev7 ? options?.name : void 0;
2273
+ const debugName = _isDev9 ? options?.name : void 0;
2020
2274
  const equalsFn = options?.equals;
2021
2275
  if (debugName) {
2022
2276
  state.__name = debugName;
@@ -2030,7 +2284,7 @@ function signal(initial, options) {
2030
2284
  function set(next) {
2031
2285
  const newValue = typeof next === "function" ? next(state.value) : next;
2032
2286
  if (equalsFn ? equalsFn(state.value, newValue) : Object.is(newValue, state.value)) return;
2033
- if (_isDev7) {
2287
+ if (_isDev9) {
2034
2288
  const oldValue = state.value;
2035
2289
  state.value = newValue;
2036
2290
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
@@ -2042,7 +2296,7 @@ function signal(initial, options) {
2042
2296
  notifySubscribers(state);
2043
2297
  }
2044
2298
  }
2045
- if (_isDev7) {
2299
+ if (_isDev9) {
2046
2300
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2047
2301
  if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
2048
2302
  }
@@ -2050,23 +2304,59 @@ function signal(initial, options) {
2050
2304
  }
2051
2305
 
2052
2306
  // src/core/ssr-context.ts
2053
- var ssrMode = false;
2307
+ var als = null;
2308
+ try {
2309
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
2310
+ const req = Function("return typeof require==='function'?require:null")();
2311
+ if (req) {
2312
+ const mod = req("node:async_hooks");
2313
+ als = new mod.AsyncLocalStorage();
2314
+ }
2315
+ }
2316
+ } catch {
2317
+ als = null;
2318
+ }
2319
+ var fallbackStore = { ssr: false, suspenseIdCounter: 0 };
2320
+ function getSSRStore() {
2321
+ if (als) {
2322
+ const s2 = als.getStore();
2323
+ if (s2) return s2;
2324
+ }
2325
+ return fallbackStore;
2326
+ }
2054
2327
  function isSSR() {
2055
- return ssrMode;
2328
+ return getSSRStore().ssr;
2056
2329
  }
2057
2330
  function enableSSR() {
2058
- ssrMode = true;
2331
+ getSSRStore().ssr = true;
2059
2332
  }
2060
2333
  function disableSSR() {
2061
- ssrMode = false;
2334
+ getSSRStore().ssr = false;
2335
+ }
2336
+ function runInSSRContext(fn) {
2337
+ const store2 = { ssr: true, suspenseIdCounter: 0 };
2338
+ if (als) {
2339
+ return als.run(store2, fn);
2340
+ }
2341
+ const prevSSR = fallbackStore.ssr;
2342
+ const prevCounter = fallbackStore.suspenseIdCounter;
2343
+ fallbackStore.ssr = true;
2344
+ fallbackStore.suspenseIdCounter = 0;
2345
+ try {
2346
+ return fn();
2347
+ } finally {
2348
+ fallbackStore.ssr = prevSSR;
2349
+ fallbackStore.suspenseIdCounter = prevCounter;
2350
+ }
2062
2351
  }
2063
2352
  function withSSR(fn) {
2064
- const wasSSR = ssrMode;
2065
- enableSSR();
2353
+ const store2 = getSSRStore();
2354
+ const wasSSR = store2.ssr;
2355
+ store2.ssr = true;
2066
2356
  try {
2067
2357
  return fn();
2068
2358
  } finally {
2069
- if (!wasSSR) disableSSR();
2359
+ if (!wasSSR) store2.ssr = false;
2070
2360
  }
2071
2361
  }
2072
2362
 
@@ -2093,26 +2383,86 @@ function effect(effectFn, options) {
2093
2383
  if (isSSR()) return () => {
2094
2384
  };
2095
2385
  const onError = options?.onError;
2386
+ let userCleanups = [];
2387
+ const onCleanup2 = (fn) => {
2388
+ userCleanups.push(fn);
2389
+ };
2390
+ const runUserCleanups = () => {
2391
+ if (userCleanups.length === 0) return;
2392
+ const list = userCleanups;
2393
+ userCleanups = [];
2394
+ for (let i2 = list.length - 1; i2 >= 0; i2--) {
2395
+ try {
2396
+ list[i2]();
2397
+ } catch (err) {
2398
+ if (typeof console !== "undefined") {
2399
+ console.warn("[SibuJS effect] onCleanup threw:", err);
2400
+ }
2401
+ }
2402
+ }
2403
+ };
2404
+ const invokeBody = () => effectFn(onCleanup2);
2096
2405
  const wrappedFn = onError ? () => {
2097
2406
  try {
2098
- effectFn();
2407
+ invokeBody();
2099
2408
  } catch (err) {
2100
2409
  onError(err);
2101
2410
  }
2102
- } : effectFn;
2411
+ } : invokeBody;
2103
2412
  let cleanupHandle = () => {
2104
2413
  };
2414
+ let running = false;
2105
2415
  const subscriber = () => {
2106
- cleanupHandle();
2107
- cleanupHandle = track(wrappedFn, subscriber);
2416
+ if (running) {
2417
+ if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
2418
+ console.warn(
2419
+ "[SibuJS] effect re-entered itself while running \u2014 the triggering update will be ignored. Wrap mutual writes in `batch()` or split the effect to avoid this."
2420
+ );
2421
+ }
2422
+ return;
2423
+ }
2424
+ running = true;
2425
+ try {
2426
+ runUserCleanups();
2427
+ cleanupHandle();
2428
+ cleanupHandle = track(wrappedFn, subscriber);
2429
+ } finally {
2430
+ running = false;
2431
+ }
2108
2432
  };
2109
- cleanupHandle = track(wrappedFn, subscriber);
2433
+ running = true;
2434
+ try {
2435
+ cleanupHandle = track(wrappedFn, subscriber);
2436
+ } finally {
2437
+ running = false;
2438
+ }
2110
2439
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2111
2440
  if (hook) hook.emit("effect:create", { effectFn });
2441
+ let disposed = false;
2112
2442
  return () => {
2443
+ if (disposed) return;
2444
+ disposed = true;
2113
2445
  const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2114
- if (h) h.emit("effect:destroy", { effectFn });
2115
- cleanupHandle();
2446
+ if (h) {
2447
+ try {
2448
+ h.emit("effect:destroy", { effectFn });
2449
+ } catch {
2450
+ }
2451
+ }
2452
+ try {
2453
+ runUserCleanups();
2454
+ } catch (err) {
2455
+ if (typeof console !== "undefined") {
2456
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
2457
+ }
2458
+ }
2459
+ try {
2460
+ cleanupHandle();
2461
+ } catch (err) {
2462
+ if (typeof console !== "undefined") {
2463
+ console.warn("[SibuJS effect] dispose threw:", err);
2464
+ }
2465
+ }
2116
2466
  };
2117
2467
  }
2118
2468
 
@@ -2120,6 +2470,7 @@ function effect(effectFn, options) {
2120
2470
  function derived(getter, options) {
2121
2471
  devAssert(typeof getter === "function", "derived: argument must be a getter function.");
2122
2472
  const debugName = options?.name;
2473
+ const equals = options?.equals;
2123
2474
  const cs = {};
2124
2475
  cs._d = false;
2125
2476
  cs._g = getter;
@@ -2130,8 +2481,14 @@ function derived(getter, options) {
2130
2481
  markDirty._c = 1;
2131
2482
  markDirty._sig = cs;
2132
2483
  track(() => {
2133
- cs._d = false;
2134
- cs._v = getter();
2484
+ let threw = true;
2485
+ try {
2486
+ cs._v = getter();
2487
+ cs._d = false;
2488
+ threw = false;
2489
+ } finally {
2490
+ if (threw) cs._d = true;
2491
+ }
2135
2492
  }, markDirty);
2136
2493
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2137
2494
  let evaluating = false;
@@ -2144,11 +2501,16 @@ function derived(getter, options) {
2144
2501
  if (trackingSuspended) {
2145
2502
  if (cs._d) {
2146
2503
  evaluating = true;
2504
+ let threw = true;
2147
2505
  try {
2148
- cs._d = false;
2149
- cs._v = getter();
2506
+ retrack(() => {
2507
+ cs._v = getter();
2508
+ cs._d = false;
2509
+ threw = false;
2510
+ }, markDirty);
2150
2511
  } finally {
2151
2512
  evaluating = false;
2513
+ if (threw) cs._d = true;
2152
2514
  }
2153
2515
  }
2154
2516
  return cs._v;
@@ -2157,13 +2519,17 @@ function derived(getter, options) {
2157
2519
  if (cs._d) {
2158
2520
  const oldValue = cs._v;
2159
2521
  evaluating = true;
2522
+ let threw = true;
2160
2523
  try {
2161
- track(() => {
2524
+ retrack(() => {
2525
+ const next = getter();
2526
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
2162
2527
  cs._d = false;
2163
- cs._v = getter();
2528
+ threw = false;
2164
2529
  }, markDirty);
2165
2530
  } finally {
2166
2531
  evaluating = false;
2532
+ if (threw) cs._d = true;
2167
2533
  }
2168
2534
  if (hook && oldValue !== cs._v) {
2169
2535
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
@@ -2225,7 +2591,7 @@ function store(initialState) {
2225
2591
  },
2226
2592
  set() {
2227
2593
  throw new Error(
2228
- "[Sibu] store: Direct mutation is not allowed. Use actions.setState() to update store properties."
2594
+ "[SibuJS store] Direct mutation is not allowed. Use actions.setState() to update store properties."
2229
2595
  );
2230
2596
  }
2231
2597
  });
@@ -2239,16 +2605,20 @@ function store(initialState) {
2239
2605
  const setState = (patch) => {
2240
2606
  const current = getSnapshot();
2241
2607
  const nextState = typeof patch === "function" ? patch(current) : patch;
2242
- Object.entries(nextState).forEach(([key, value]) => {
2243
- if (key in signals) {
2244
- signals[key][1](value);
2245
- }
2608
+ batch(() => {
2609
+ Object.entries(nextState).forEach(([key, value]) => {
2610
+ if (key in signals) {
2611
+ signals[key][1](value);
2612
+ }
2613
+ });
2246
2614
  });
2247
2615
  };
2248
2616
  const reset = () => {
2249
- Object.keys(initialState).forEach((key) => {
2250
- const setter = signals[key][1];
2251
- setter(initialState[key]);
2617
+ batch(() => {
2618
+ Object.keys(initialState).forEach((key) => {
2619
+ const setter = signals[key][1];
2620
+ setter(initialState[key]);
2621
+ });
2252
2622
  });
2253
2623
  };
2254
2624
  const subscribe = (callback) => {
@@ -2382,7 +2752,8 @@ function reactiveArray(initial = []) {
2382
2752
  function get() {
2383
2753
  recordDependency(signal2);
2384
2754
  if (snapshot === null) {
2385
- snapshot = Object.freeze([...data2]);
2755
+ const copy = data2.slice();
2756
+ snapshot = Object.freeze(copy);
2386
2757
  }
2387
2758
  return snapshot;
2388
2759
  }
@@ -2578,26 +2949,34 @@ function asyncDerived(factory, initial) {
2578
2949
  effect(() => {
2579
2950
  tick();
2580
2951
  const currentRun = ++runId;
2581
- setLoading(true);
2582
- setError(null);
2952
+ batch(() => {
2953
+ setLoading(true);
2954
+ setError(null);
2955
+ });
2583
2956
  let promise;
2584
2957
  try {
2585
2958
  promise = factory();
2586
2959
  } catch (err) {
2587
- setError(err);
2588
- setLoading(false);
2960
+ batch(() => {
2961
+ setError(err);
2962
+ setLoading(false);
2963
+ });
2589
2964
  return;
2590
2965
  }
2591
2966
  promise.then(
2592
2967
  (result) => {
2593
2968
  if (currentRun !== runId) return;
2594
- setValue(result);
2595
- setLoading(false);
2969
+ batch(() => {
2970
+ setValue(result);
2971
+ setLoading(false);
2972
+ });
2596
2973
  },
2597
2974
  (err) => {
2598
2975
  if (currentRun !== runId) return;
2599
- setError(err);
2600
- setLoading(false);
2976
+ batch(() => {
2977
+ setError(err);
2978
+ setLoading(false);
2979
+ });
2601
2980
  }
2602
2981
  );
2603
2982
  });
@@ -2624,26 +3003,186 @@ function runMountCallback(callback, hookName, element) {
2624
3003
  registerDisposer(element, cleanup2);
2625
3004
  }
2626
3005
  }
3006
+ var mountWatchers = /* @__PURE__ */ new WeakMap();
3007
+ var unmountWatchers = /* @__PURE__ */ new WeakMap();
3008
+ var watchedMountElements = /* @__PURE__ */ new Set();
3009
+ var watchedUnmountElements = /* @__PURE__ */ new Set();
3010
+ var sharedObserver = null;
3011
+ var mutationCounter = 0;
3012
+ var FULL_SWEEP_INTERVAL = 256;
3013
+ function fireMount(el) {
3014
+ const cbs = mountWatchers.get(el);
3015
+ if (!cbs) return;
3016
+ mountWatchers.delete(el);
3017
+ watchedMountElements.delete(el);
3018
+ for (const cb of cbs) {
3019
+ try {
3020
+ cb();
3021
+ } catch {
3022
+ }
3023
+ }
3024
+ }
3025
+ function fireUnmount(el) {
3026
+ const cbs = unmountWatchers.get(el);
3027
+ if (!cbs) return;
3028
+ queueMicrotask(() => {
3029
+ if (el.isConnected) return;
3030
+ const stillCbs = unmountWatchers.get(el);
3031
+ if (!stillCbs) return;
3032
+ unmountWatchers.delete(el);
3033
+ watchedUnmountElements.delete(el);
3034
+ for (const cb of stillCbs) {
3035
+ try {
3036
+ cb();
3037
+ } catch {
3038
+ }
3039
+ }
3040
+ });
3041
+ }
3042
+ function visitAddedNode(node) {
3043
+ if (watchedMountElements.size === 0) return;
3044
+ if (node.nodeType !== 1) return;
3045
+ const el = node;
3046
+ if (watchedMountElements.has(el) && el.isConnected) {
3047
+ fireMount(el);
3048
+ }
3049
+ if (el.firstElementChild) {
3050
+ for (const watched of Array.from(watchedMountElements)) {
3051
+ if (watched !== el && watched.isConnected && el.contains(watched)) {
3052
+ fireMount(watched);
3053
+ }
3054
+ }
3055
+ }
3056
+ }
3057
+ function visitRemovedNode(node) {
3058
+ if (watchedUnmountElements.size === 0) return;
3059
+ if (node.nodeType !== 1) return;
3060
+ const el = node;
3061
+ if (watchedUnmountElements.has(el) && !el.isConnected) {
3062
+ fireUnmount(el);
3063
+ }
3064
+ if (el.firstElementChild) {
3065
+ for (const watched of Array.from(watchedUnmountElements)) {
3066
+ if (watched !== el && !watched.isConnected && el.contains(watched)) {
3067
+ fireUnmount(watched);
3068
+ }
3069
+ }
3070
+ }
3071
+ }
3072
+ function fullSweep() {
3073
+ if (watchedMountElements.size > 0) {
3074
+ for (const el of Array.from(watchedMountElements)) {
3075
+ if (el.isConnected) fireMount(el);
3076
+ }
3077
+ }
3078
+ if (watchedUnmountElements.size > 0) {
3079
+ for (const el of Array.from(watchedUnmountElements)) {
3080
+ if (!el.isConnected) fireUnmount(el);
3081
+ }
3082
+ }
3083
+ }
3084
+ function ensureObserver() {
3085
+ if (sharedObserver || typeof document === "undefined") return;
3086
+ sharedObserver = new MutationObserver((mutations) => {
3087
+ for (const m of mutations) {
3088
+ if (m.type !== "childList") continue;
3089
+ if (m.addedNodes.length > 0) {
3090
+ for (let i2 = 0; i2 < m.addedNodes.length; i2++) {
3091
+ visitAddedNode(m.addedNodes[i2]);
3092
+ }
3093
+ }
3094
+ if (m.removedNodes.length > 0) {
3095
+ for (let i2 = 0; i2 < m.removedNodes.length; i2++) {
3096
+ visitRemovedNode(m.removedNodes[i2]);
3097
+ }
3098
+ }
3099
+ }
3100
+ mutationCounter += mutations.length;
3101
+ if (mutationCounter >= FULL_SWEEP_INTERVAL) {
3102
+ mutationCounter = 0;
3103
+ fullSweep();
3104
+ }
3105
+ maybeDisconnectObserver();
3106
+ });
3107
+ sharedObserver.observe(document.body, { childList: true, subtree: true });
3108
+ }
3109
+ function maybeDisconnectObserver() {
3110
+ if (!sharedObserver) return;
3111
+ if (watchedMountElements.size === 0 && watchedUnmountElements.size === 0) {
3112
+ sharedObserver.disconnect();
3113
+ sharedObserver = null;
3114
+ mutationCounter = 0;
3115
+ }
3116
+ }
3117
+ function registerMountWatcher(element, cb) {
3118
+ let list = mountWatchers.get(element);
3119
+ if (!list) {
3120
+ list = [];
3121
+ mountWatchers.set(element, list);
3122
+ }
3123
+ list.push(cb);
3124
+ watchedMountElements.add(element);
3125
+ ensureObserver();
3126
+ return () => {
3127
+ const cbs = mountWatchers.get(element);
3128
+ if (cbs) {
3129
+ const idx = cbs.indexOf(cb);
3130
+ if (idx !== -1) cbs.splice(idx, 1);
3131
+ if (cbs.length === 0) {
3132
+ mountWatchers.delete(element);
3133
+ watchedMountElements.delete(element);
3134
+ }
3135
+ }
3136
+ maybeDisconnectObserver();
3137
+ };
3138
+ }
3139
+ function registerUnmountWatcher(element, cb) {
3140
+ let list = unmountWatchers.get(element);
3141
+ if (!list) {
3142
+ list = [];
3143
+ unmountWatchers.set(element, list);
3144
+ }
3145
+ list.push(cb);
3146
+ watchedUnmountElements.add(element);
3147
+ ensureObserver();
3148
+ return () => {
3149
+ const cbs = unmountWatchers.get(element);
3150
+ if (cbs) {
3151
+ const idx = cbs.indexOf(cb);
3152
+ if (idx !== -1) cbs.splice(idx, 1);
3153
+ if (cbs.length === 0) {
3154
+ unmountWatchers.delete(element);
3155
+ watchedUnmountElements.delete(element);
3156
+ }
3157
+ }
3158
+ maybeDisconnectObserver();
3159
+ };
3160
+ }
2627
3161
  function onMount(callback, element) {
2628
3162
  if (typeof document === "undefined") return;
2629
3163
  if (element) {
3164
+ let disposed = false;
3165
+ registerDisposer(element, () => {
3166
+ disposed = true;
3167
+ });
2630
3168
  if (element.isConnected) {
2631
- queueMicrotask(() => runMountCallback(callback, "onMount", element));
3169
+ queueMicrotask(() => {
3170
+ if (disposed) return;
3171
+ runMountCallback(callback, "onMount", element);
3172
+ });
2632
3173
  return;
2633
3174
  }
2634
- const observer = new MutationObserver(() => {
2635
- if (element.isConnected) {
2636
- observer.disconnect();
2637
- runMountCallback(callback, "onMount", element);
2638
- }
2639
- });
2640
- registerDisposer(element, () => observer.disconnect());
2641
3175
  queueMicrotask(() => {
3176
+ if (disposed) return;
2642
3177
  if (element.isConnected) {
2643
3178
  runMountCallback(callback, "onMount", element);
2644
- } else {
2645
- observer.observe(document.body, { childList: true, subtree: true });
3179
+ return;
2646
3180
  }
3181
+ const unregister = registerMountWatcher(element, () => {
3182
+ if (disposed) return;
3183
+ runMountCallback(callback, "onMount", element);
3184
+ });
3185
+ registerDisposer(element, unregister);
2647
3186
  });
2648
3187
  } else {
2649
3188
  queueMicrotask(() => {
@@ -2652,22 +3191,24 @@ function onMount(callback, element) {
2652
3191
  }
2653
3192
  }
2654
3193
  function onUnmount(callback, element) {
2655
- registerDisposer(element, () => safeCall(callback, "onUnmount"));
2656
- const startObserving = () => {
2657
- const observer = new MutationObserver(() => {
2658
- if (!element.isConnected) {
2659
- observer.disconnect();
2660
- safeCall(callback, "onUnmount");
2661
- }
2662
- });
2663
- observer.observe(document.body, { childList: true, subtree: true });
2664
- registerDisposer(element, () => observer.disconnect());
3194
+ if (typeof document === "undefined") return;
3195
+ let fired = false;
3196
+ const fireOnce = () => {
3197
+ if (fired) return;
3198
+ fired = true;
3199
+ safeCall(callback, "onUnmount");
3200
+ };
3201
+ registerDisposer(element, fireOnce);
3202
+ const startWatching = () => {
3203
+ if (fired) return;
3204
+ const unregister = registerUnmountWatcher(element, fireOnce);
3205
+ registerDisposer(element, unregister);
2665
3206
  };
2666
3207
  if (element.isConnected) {
2667
- startObserving();
3208
+ startWatching();
2668
3209
  } else {
2669
3210
  onMount(() => {
2670
- startObserving();
3211
+ startWatching();
2671
3212
  return void 0;
2672
3213
  }, element);
2673
3214
  }
@@ -2679,9 +3220,11 @@ function onCleanup(callback, element) {
2679
3220
  // src/core/rendering/context.ts
2680
3221
  function context(defaultValue) {
2681
3222
  const [getValue, setValue] = signal(defaultValue);
2682
- return {
3223
+ const ctx = {
2683
3224
  provide(value) {
3225
+ const previous = getValue();
2684
3226
  setValue(value);
3227
+ return () => setValue(previous);
2685
3228
  },
2686
3229
  use() {
2687
3230
  return getValue;
@@ -2691,8 +3234,18 @@ function context(defaultValue) {
2691
3234
  },
2692
3235
  set(value) {
2693
3236
  setValue(value);
3237
+ },
3238
+ withContext(value, fn) {
3239
+ const previous = getValue();
3240
+ setValue(value);
3241
+ try {
3242
+ return fn();
3243
+ } finally {
3244
+ setValue(previous);
3245
+ }
2694
3246
  }
2695
3247
  };
3248
+ return ctx;
2696
3249
  }
2697
3250
 
2698
3251
  // src/core/strict.ts
@@ -2703,7 +3256,7 @@ function strict(fn) {
2703
3256
  try {
2704
3257
  fn();
2705
3258
  } catch (err) {
2706
- console.warn("[Sibu strict] second run threw:", err);
3259
+ console.warn("[SibuJS strict] second run threw:", err);
2707
3260
  }
2708
3261
  });
2709
3262
  }
@@ -2719,7 +3272,7 @@ function strictEffect(fn) {
2719
3272
  try {
2720
3273
  secondTeardown = effect(fn);
2721
3274
  } catch (err) {
2722
- console.warn("[Sibu strictEffect] second run threw:", err);
3275
+ console.warn("[SibuJS strictEffect] second run threw:", err);
2723
3276
  }
2724
3277
  });
2725
3278
  return () => {
@@ -2745,9 +3298,11 @@ function nextTick() {
2745
3298
  function defer(getter) {
2746
3299
  const [value, setValue] = signal(getter());
2747
3300
  let pending = false;
3301
+ let disposed = false;
2748
3302
  let latest = value();
2749
3303
  const flush = () => {
2750
3304
  pending = false;
3305
+ if (disposed) return;
2751
3306
  setValue(latest);
2752
3307
  };
2753
3308
  const schedule = () => {
@@ -2761,11 +3316,17 @@ function defer(getter) {
2761
3316
  }
2762
3317
  });
2763
3318
  };
2764
- track(() => {
3319
+ const teardown = track(() => {
2765
3320
  latest = getter();
2766
3321
  schedule();
2767
3322
  });
2768
- return value;
3323
+ const accessor = (() => value());
3324
+ accessor.dispose = () => {
3325
+ if (disposed) return;
3326
+ disposed = true;
3327
+ teardown();
3328
+ };
3329
+ return accessor;
2769
3330
  }
2770
3331
  var IDLE_FALLBACK_MS = 16;
2771
3332
  function scheduleIdle(fn) {
@@ -2806,6 +3367,32 @@ function transition() {
2806
3367
  }
2807
3368
 
2808
3369
  // src/core/rendering/lazy.ts
3370
+ var PENDING_ERROR = "__sibuPendingError";
3371
+ function dispatchPropagate(node, error) {
3372
+ const fire = () => {
3373
+ try {
3374
+ if (!node.parentNode) return false;
3375
+ node.dispatchEvent(new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error } }));
3376
+ return true;
3377
+ } catch {
3378
+ return false;
3379
+ }
3380
+ };
3381
+ if (node.parentNode && fire()) return;
3382
+ queueMicrotask(() => {
3383
+ if (fire()) return;
3384
+ node[PENDING_ERROR] = error;
3385
+ });
3386
+ }
3387
+ function takePendingError(node) {
3388
+ const rec = node;
3389
+ const err = rec[PENDING_ERROR];
3390
+ if (err instanceof Error) {
3391
+ delete rec[PENDING_ERROR];
3392
+ return err;
3393
+ }
3394
+ return void 0;
3395
+ }
2809
3396
  function lazy(importFn) {
2810
3397
  let cached = null;
2811
3398
  return function LazyComponent() {
@@ -2822,14 +3409,14 @@ function lazy(importFn) {
2822
3409
  }).catch((err) => {
2823
3410
  if (disposed) return;
2824
3411
  const errorObj = err instanceof Error ? err : new Error(String(err));
2825
- container.replaceChildren(div("sibu-lazy-error", `Failed to load component: ${errorObj.message}`));
3412
+ devWarn(`[SibuJS] lazy() failed to load component: ${errorObj.message}`);
3413
+ container.replaceChildren(div({ class: "sibu-lazy-error" }, `Failed to load component: ${errorObj.message}`));
3414
+ dispatchPropagate(container, errorObj);
2826
3415
  });
2827
3416
  container.appendChild(span("sibu-lazy-loading", "Loading..."));
2828
- const origRemove = container.remove.bind(container);
2829
- container.remove = () => {
3417
+ registerDisposer(container, () => {
2830
3418
  disposed = true;
2831
- origRemove();
2832
- };
3419
+ });
2833
3420
  return container;
2834
3421
  };
2835
3422
  }
@@ -2837,32 +3424,55 @@ function Suspense({ nodes, fallback }) {
2837
3424
  const container = div({ class: "sibu-suspense" });
2838
3425
  const fallbackEl = fallback();
2839
3426
  container.appendChild(fallbackEl);
3427
+ let suspenseDisposed = false;
3428
+ let observer = null;
3429
+ registerDisposer(container, () => {
3430
+ suspenseDisposed = true;
3431
+ if (observer) {
3432
+ observer.disconnect();
3433
+ observer = null;
3434
+ }
3435
+ });
2840
3436
  queueMicrotask(() => {
3437
+ if (suspenseDisposed) return;
2841
3438
  try {
2842
3439
  const childEl = nodes();
2843
3440
  if (childEl.classList.contains("sibu-lazy")) {
2844
- const observer = new MutationObserver(() => {
3441
+ if (!childEl.querySelector(".sibu-lazy-loading")) {
3442
+ container.replaceChildren(childEl);
3443
+ return;
3444
+ }
3445
+ observer = new MutationObserver(() => {
3446
+ if (suspenseDisposed) return;
2845
3447
  const loading = childEl.querySelector(".sibu-lazy-loading");
2846
3448
  if (!loading) {
2847
- observer.disconnect();
3449
+ observer?.disconnect();
3450
+ observer = null;
2848
3451
  container.replaceChildren(childEl);
2849
3452
  }
2850
3453
  });
2851
3454
  observer.observe(childEl, { childList: true, subtree: true });
2852
- if (!childEl.querySelector(".sibu-lazy-loading")) {
2853
- container.replaceChildren(childEl);
2854
- }
2855
3455
  } else {
2856
3456
  container.replaceChildren(childEl);
2857
3457
  }
2858
- } catch {
3458
+ } catch (err) {
3459
+ const errorObj = err instanceof Error ? err : new Error(String(err));
3460
+ devWarn(`[SibuJS] Suspense nodes() threw: ${errorObj.message}`);
3461
+ dispatchPropagate(container, errorObj);
2859
3462
  }
2860
3463
  });
2861
3464
  return container;
2862
3465
  }
2863
3466
 
3467
+ // src/platform/ssr.ts
3468
+ var _isDev10 = isDev();
3469
+ function trustHTML(html3) {
3470
+ return html3;
3471
+ }
3472
+ var DEFAULT_MAX_SSR_BYTES = 1024 * 1024;
3473
+
2864
3474
  // src/components/ErrorDisplay.ts
2865
- var _isDev8 = isDev();
3475
+ var _isDev11 = isDev();
2866
3476
  var STYLES = `
2867
3477
  .sibu-error-display {
2868
3478
  border: 1px solid var(--sibu-err-border, #e5484d);
@@ -2948,20 +3558,21 @@ var STYLES = `
2948
3558
  font-weight: 600;
2949
3559
  }
2950
3560
  .sibu-error-display .sibu-err-copy-btn {
2951
- background: transparent;
2952
- border: 1px solid #3a3a4e;
3561
+ background: rgba(0, 0, 0, 0.22);
3562
+ border: 1px solid rgba(255, 255, 255, 0.15);
2953
3563
  border-radius: 4px;
2954
- color: #a0a3b8;
3564
+ color: rgba(255, 255, 255, 0.85);
2955
3565
  cursor: pointer;
2956
3566
  padding: 2px 10px;
2957
- font-size: 0.95em;
3567
+ font-size: 0.78em;
2958
3568
  font-family: inherit;
2959
3569
  transition: all 0.12s ease;
3570
+ flex-shrink: 0;
2960
3571
  }
2961
3572
  .sibu-error-display .sibu-err-copy-btn:hover {
2962
- background: #2a2a3e;
2963
- color: #e5e7eb;
2964
- border-color: #4a4a5e;
3573
+ background: rgba(0, 0, 0, 0.35);
3574
+ color: white;
3575
+ border-color: rgba(255, 255, 255, 0.3);
2965
3576
  }
2966
3577
 
2967
3578
  .sibu-error-display .sibu-err-stack {
@@ -3086,21 +3697,25 @@ function normalizeError(err) {
3086
3697
  cause: null
3087
3698
  };
3088
3699
  }
3089
- function buildCopyText(err, meta2) {
3700
+ function buildCopyText(err, meta2, headline) {
3090
3701
  const lines = [];
3702
+ lines.push(headline);
3091
3703
  lines.push(`[${err.code}] ${err.message}`);
3092
3704
  if (err.stack) {
3093
3705
  lines.push("");
3706
+ lines.push("Stack Trace:");
3094
3707
  lines.push(err.stack);
3095
3708
  }
3096
- if (err.cause) {
3709
+ let cause = err.cause;
3710
+ while (cause) {
3097
3711
  lines.push("");
3098
3712
  lines.push("Caused by:");
3099
- lines.push(` [${err.cause.code}] ${err.cause.message}`);
3100
- if (err.cause.stack) {
3101
- const indented = err.cause.stack.split("\n").map((l) => ` ${l}`).join("\n");
3713
+ lines.push(` [${cause.code}] ${cause.message}`);
3714
+ if (cause.stack) {
3715
+ const indented = cause.stack.split("\n").map((l) => ` ${l}`).join("\n");
3102
3716
  lines.push(indented);
3103
3717
  }
3718
+ cause = cause.cause;
3104
3719
  }
3105
3720
  if (meta2 && Object.keys(meta2).length > 0) {
3106
3721
  lines.push("");
@@ -3110,9 +3725,13 @@ function buildCopyText(err, meta2) {
3110
3725
  }
3111
3726
  }
3112
3727
  lines.push("");
3113
- lines.push(`At: ${(/* @__PURE__ */ new Date()).toISOString()}`);
3728
+ lines.push("Environment:");
3729
+ lines.push(` Timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}`);
3730
+ if (typeof location !== "undefined") {
3731
+ lines.push(` URL: ${location.href}`);
3732
+ }
3114
3733
  if (typeof navigator !== "undefined" && navigator.userAgent) {
3115
- lines.push(`UA: ${navigator.userAgent}`);
3734
+ lines.push(` User Agent: ${navigator.userAgent}`);
3116
3735
  }
3117
3736
  return lines.join("\n");
3118
3737
  }
@@ -3164,7 +3783,7 @@ function ErrorDisplay(props) {
3164
3783
  injectStyles();
3165
3784
  const severity = props.severity ?? "error";
3166
3785
  const normalized = normalizeError(props.error);
3167
- const showDetails = props.alwaysShowDetails ?? _isDev8;
3786
+ const showDetails = props.alwaysShowDetails ?? _isDev11;
3168
3787
  const headline = props.title ?? normalized.message;
3169
3788
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
3170
3789
  const [copyLabel, setCopyLabel] = signal("Copy");
@@ -3173,7 +3792,7 @@ function ErrorDisplay(props) {
3173
3792
  nodes: () => copyLabel(),
3174
3793
  on: {
3175
3794
  click: () => {
3176
- const text2 = buildCopyText(normalized, props.metadata);
3795
+ const text2 = buildCopyText(normalized, props.metadata, headline);
3177
3796
  if (typeof navigator !== "undefined" && navigator.clipboard) {
3178
3797
  navigator.clipboard.writeText(text2).then(
3179
3798
  () => {
@@ -3194,6 +3813,7 @@ function ErrorDisplay(props) {
3194
3813
  nodes: [
3195
3814
  code({ class: "sibu-err-icon", nodes: normalized.code }),
3196
3815
  h3({ class: "sibu-err-title", nodes: headline }),
3816
+ copyBtn,
3197
3817
  span({ class: "sibu-err-timestamp", nodes: timestamp })
3198
3818
  ]
3199
3819
  });
@@ -3205,25 +3825,12 @@ function ErrorDisplay(props) {
3205
3825
  nodes: [
3206
3826
  div({
3207
3827
  class: "sibu-err-section-head",
3208
- nodes: [span({ nodes: "Stack Trace" }), copyBtn]
3828
+ nodes: [span({ nodes: "Stack Trace" })]
3209
3829
  }),
3210
3830
  renderFrames(normalized.frames)
3211
3831
  ]
3212
3832
  })
3213
3833
  );
3214
- } else if (showDetails) {
3215
- bodyChildren.push(
3216
- div({
3217
- class: "sibu-err-section",
3218
- nodes: [
3219
- div({
3220
- class: "sibu-err-section-head",
3221
- nodes: [span({ nodes: "Details" }), copyBtn]
3222
- }),
3223
- div({ class: "sibu-err-stack", nodes: "(no stack available)" })
3224
- ]
3225
- })
3226
- );
3227
3834
  }
3228
3835
  if (showDetails) {
3229
3836
  bodyChildren.push(...renderCauseChain(normalized.cause));
@@ -3239,37 +3846,6 @@ function ErrorDisplay(props) {
3239
3846
  })
3240
3847
  );
3241
3848
  }
3242
- if (showDetails && typeof navigator !== "undefined" && navigator.userAgent) {
3243
- bodyChildren.push(
3244
- div({
3245
- class: "sibu-err-section",
3246
- nodes: [
3247
- div({ class: "sibu-err-section-head", nodes: [span({ nodes: "Environment" })] }),
3248
- div({
3249
- class: "sibu-err-meta",
3250
- nodes: (() => {
3251
- const dl2 = document.createElement("dl");
3252
- dl2.className = "sibu-err-meta";
3253
- const entries = [
3254
- ["User Agent", navigator.userAgent],
3255
- ["URL", typeof location !== "undefined" ? location.href : "(n/a)"],
3256
- ["Timestamp", (/* @__PURE__ */ new Date()).toISOString()]
3257
- ];
3258
- for (const [k, v] of entries) {
3259
- const dt2 = document.createElement("dt");
3260
- dt2.textContent = k;
3261
- const dd2 = document.createElement("dd");
3262
- dd2.textContent = v;
3263
- dl2.appendChild(dt2);
3264
- dl2.appendChild(dd2);
3265
- }
3266
- return dl2;
3267
- })()
3268
- })
3269
- ]
3270
- })
3271
- );
3272
- }
3273
3849
  const actionButtons = [];
3274
3850
  if (props.onRetry) {
3275
3851
  actionButtons.push(
@@ -3464,6 +4040,7 @@ function injectStyles2() {
3464
4040
  stylesInjected = true;
3465
4041
  }
3466
4042
  }
4043
+ var FALLBACK_CACHE_MAX = 50;
3467
4044
  var fallbackCache = /* @__PURE__ */ new WeakMap();
3468
4045
  function getMemoizedFallback(fallbackFn, error, retry) {
3469
4046
  let cache2 = fallbackCache.get(fallbackFn);
@@ -3472,27 +4049,42 @@ function getMemoizedFallback(fallbackFn, error, retry) {
3472
4049
  fallbackCache.set(fallbackFn, cache2);
3473
4050
  }
3474
4051
  const key = error.message;
3475
- if (!cache2.has(key)) {
3476
- cache2.set(key, fallbackFn(error, retry));
4052
+ let factory = cache2.get(key);
4053
+ if (factory) {
4054
+ cache2.delete(key);
4055
+ cache2.set(key, factory);
4056
+ } else {
4057
+ factory = () => fallbackFn(error, retry);
4058
+ cache2.set(key, factory);
4059
+ if (cache2.size > FALLBACK_CACHE_MAX) {
4060
+ const oldestKey = cache2.keys().next().value;
4061
+ if (oldestKey !== void 0) cache2.delete(oldestKey);
4062
+ }
3477
4063
  }
3478
- return cache2.get(key);
4064
+ return factory();
3479
4065
  }
3480
4066
  function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
3481
4067
  injectStyles2();
3482
4068
  const [error, setError] = signal(null);
3483
4069
  const retry = () => {
3484
4070
  if (fallback) {
3485
- fallbackCache.delete(fallback);
4071
+ const cur = error();
4072
+ const inner = fallbackCache.get(fallback);
4073
+ if (cur && inner) inner.delete(cur.message);
3486
4074
  }
3487
4075
  setError(null);
3488
4076
  };
4077
+ let resetKeysTeardown = null;
3489
4078
  if (resetKeys && resetKeys.length > 0) {
3490
4079
  let initialized = false;
3491
- effect(() => {
4080
+ resetKeysTeardown = effect(() => {
3492
4081
  for (const k of resetKeys) {
3493
4082
  try {
3494
4083
  k();
3495
- } catch {
4084
+ } catch (err) {
4085
+ if (typeof console !== "undefined") {
4086
+ console.warn("[SibuJS ErrorBoundary] resetKeys getter threw:", err);
4087
+ }
3496
4088
  }
3497
4089
  }
3498
4090
  if (!initialized) {
@@ -3505,7 +4097,15 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
3505
4097
  const handleError = (e) => {
3506
4098
  const errorObj = e instanceof Error ? e : new Error(String(e));
3507
4099
  setError(errorObj);
3508
- onError?.(errorObj);
4100
+ if (onError) {
4101
+ try {
4102
+ onError(errorObj);
4103
+ } catch (cbErr) {
4104
+ if (typeof console !== "undefined") {
4105
+ console.error("[SibuJS ErrorBoundary] onError callback threw:", cbErr);
4106
+ }
4107
+ }
4108
+ }
3509
4109
  return errorObj;
3510
4110
  };
3511
4111
  const defaultFallback = (err, retryFn) => {
@@ -3557,7 +4157,7 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
3557
4157
  }
3558
4158
  }
3559
4159
  });
3560
- container.addEventListener("sibu:error-propagate", (e) => {
4160
+ const propagateListener = (e) => {
3561
4161
  if (error()) return;
3562
4162
  e.stopPropagation();
3563
4163
  const customEvent = e;
@@ -3565,6 +4165,30 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
3565
4165
  if (propagatedError) {
3566
4166
  handleError(propagatedError);
3567
4167
  }
4168
+ };
4169
+ container.addEventListener("sibu:error-propagate", propagateListener);
4170
+ onMount(() => {
4171
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT);
4172
+ const collected = [];
4173
+ let node = walker.currentNode;
4174
+ while (node) {
4175
+ const pending = takePendingError(node);
4176
+ if (pending) collected.push(pending);
4177
+ node = walker.nextNode();
4178
+ }
4179
+ if (collected.length === 1) {
4180
+ handleError(collected[0]);
4181
+ } else if (collected.length > 1) {
4182
+ const Agg = globalThis.AggregateError;
4183
+ handleError(
4184
+ Agg ? new Agg(collected, `${collected.length} pre-mount errors caught by ErrorBoundary`) : new Error(collected.map((e) => e.message).join("; "))
4185
+ );
4186
+ }
4187
+ return void 0;
4188
+ }, container);
4189
+ registerDisposer(container, () => {
4190
+ if (resetKeysTeardown) resetKeysTeardown();
4191
+ container.removeEventListener("sibu:error-propagate", propagateListener);
3568
4192
  });
3569
4193
  return container;
3570
4194
  }
@@ -3740,6 +4364,7 @@ function Loading(props = {}) {
3740
4364
  footer,
3741
4365
  form,
3742
4366
  g,
4367
+ getSSRStore,
3743
4368
  getSlot,
3744
4369
  h1,
3745
4370
  h2,
@@ -3812,6 +4437,7 @@ function Loading(props = {}) {
3812
4437
  rp,
3813
4438
  rt,
3814
4439
  ruby,
4440
+ runInSSRContext,
3815
4441
  s,
3816
4442
  samp,
3817
4443
  script,
@@ -3837,6 +4463,7 @@ function Loading(props = {}) {
3837
4463
  symbol,
3838
4464
  table,
3839
4465
  tagFactory,
4466
+ takePendingError,
3840
4467
  tbody,
3841
4468
  td,
3842
4469
  template,
@@ -3851,6 +4478,7 @@ function Loading(props = {}) {
3851
4478
  track,
3852
4479
  transition,
3853
4480
  trapFocus,
4481
+ trustHTML,
3854
4482
  tspan,
3855
4483
  u,
3856
4484
  ul,