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/build.cjs CHANGED
@@ -1134,6 +1134,7 @@ __export(index_exports, {
1134
1134
  footer: () => footer,
1135
1135
  form: () => form,
1136
1136
  g: () => g,
1137
+ getSSRStore: () => getSSRStore,
1137
1138
  getSlot: () => getSlot,
1138
1139
  h1: () => h1,
1139
1140
  h2: () => h2,
@@ -1206,6 +1207,7 @@ __export(index_exports, {
1206
1207
  rp: () => rp,
1207
1208
  rt: () => rt,
1208
1209
  ruby: () => ruby,
1210
+ runInSSRContext: () => runInSSRContext,
1209
1211
  s: () => s,
1210
1212
  samp: () => samp,
1211
1213
  script: () => script,
@@ -1231,6 +1233,7 @@ __export(index_exports, {
1231
1233
  symbol: () => symbol,
1232
1234
  table: () => table,
1233
1235
  tagFactory: () => tagFactory,
1236
+ takePendingError: () => takePendingError,
1234
1237
  tbody: () => tbody,
1235
1238
  td: () => td,
1236
1239
  template: () => template,
@@ -1245,6 +1248,7 @@ __export(index_exports, {
1245
1248
  track: () => track2,
1246
1249
  transition: () => transition,
1247
1250
  trapFocus: () => trapFocus,
1251
+ trustHTML: () => trustHTML,
1248
1252
  tspan: () => tspan,
1249
1253
  u: () => u,
1250
1254
  ul: () => ul,
@@ -1266,33 +1270,80 @@ function isDev() {
1266
1270
  var _isDev = isDev();
1267
1271
  function devAssert(condition, message) {
1268
1272
  if (_isDev && !condition) {
1269
- throw new Error(`[Sibu] ${message}`);
1273
+ throw new Error(`[SibuJS] ${message}`);
1270
1274
  }
1271
1275
  }
1272
1276
  function devWarn(message) {
1273
1277
  if (_isDev) {
1274
- console.warn(`[Sibu] ${message}`);
1278
+ console.warn(`[SibuJS] ${message}`);
1275
1279
  }
1276
1280
  }
1277
1281
 
1278
1282
  // src/utils/sanitize.ts
1283
+ var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
1279
1284
  function sanitizeUrl(url) {
1280
1285
  const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
1281
1286
  if (!trimmed) return "";
1282
1287
  const lower = trimmed.toLowerCase();
1283
- if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
1284
- return "";
1288
+ let schemeEnd = -1;
1289
+ for (let i2 = 0; i2 < lower.length; i2++) {
1290
+ const ch = lower.charCodeAt(i2);
1291
+ if (ch === 58) {
1292
+ schemeEnd = i2;
1293
+ break;
1294
+ }
1295
+ if (ch === 47 || ch === 63 || ch === 35) break;
1285
1296
  }
1297
+ if (schemeEnd === -1) return trimmed;
1298
+ const scheme = lower.slice(0, schemeEnd + 1);
1299
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return trimmed;
1300
+ if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
1286
1301
  return trimmed;
1287
1302
  }
1303
+ function sanitizeSrcset(value) {
1304
+ const parts = value.split(",");
1305
+ const out = [];
1306
+ for (let i2 = 0; i2 < parts.length; i2++) {
1307
+ const part = parts[i2].trim();
1308
+ if (!part) continue;
1309
+ const m = part.match(/^(\S+)(\s+.+)?$/);
1310
+ if (!m) continue;
1311
+ const safe = sanitizeUrl(m[1]);
1312
+ if (!safe) continue;
1313
+ out.push(m[2] ? `${safe}${m[2]}` : safe);
1314
+ }
1315
+ return out.join(", ");
1316
+ }
1288
1317
  function sanitizeCSSValue(value) {
1289
- const lower = value.toLowerCase().replace(/\s+/g, "");
1290
- if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("-moz-binding")) {
1318
+ const decoded = value.replace(/\\([0-9a-fA-F]{1,6})\s?/g, (_m, hex) => {
1319
+ const code2 = Number.parseInt(hex, 16);
1320
+ if (!Number.isFinite(code2) || code2 < 0 || code2 > 1114111) return "";
1321
+ try {
1322
+ return String.fromCodePoint(code2);
1323
+ } catch {
1324
+ return "";
1325
+ }
1326
+ });
1327
+ const lower = decoded.toLowerCase().replace(/\s+/g, "");
1328
+ 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")) {
1291
1329
  return "";
1292
1330
  }
1293
1331
  return value;
1294
1332
  }
1295
- var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "cite", "poster", "background", "srcset"]);
1333
+ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
1334
+ "href",
1335
+ "xlink:href",
1336
+ "src",
1337
+ "action",
1338
+ "formaction",
1339
+ "formtarget",
1340
+ "cite",
1341
+ "poster",
1342
+ "background",
1343
+ "srcset",
1344
+ "ping",
1345
+ "data"
1346
+ ]);
1296
1347
  function isUrlAttribute(attr) {
1297
1348
  return URL_ATTRIBUTES.has(attr);
1298
1349
  }
@@ -1303,11 +1354,11 @@ var subscriberStack = new Array(32);
1303
1354
  var stackCapacity = 32;
1304
1355
  var stackTop = -1;
1305
1356
  var currentSubscriber = null;
1306
- var signalSubscribers = /* @__PURE__ */ new WeakMap();
1307
1357
  var SUBS = "__s";
1308
1358
  var notifyDepth = 0;
1309
1359
  var pendingQueue = [];
1310
1360
  var pendingSet = /* @__PURE__ */ new Set();
1361
+ var propagateStack = [];
1311
1362
  function safeInvoke(sub2) {
1312
1363
  try {
1313
1364
  sub2();
@@ -1317,6 +1368,15 @@ function safeInvoke(sub2) {
1317
1368
  }
1318
1369
  var suspendDepth = 0;
1319
1370
  var trackingSuspended = false;
1371
+ function retrack(effectFn, subscriber) {
1372
+ const prev = currentSubscriber;
1373
+ currentSubscriber = subscriber;
1374
+ try {
1375
+ effectFn();
1376
+ } finally {
1377
+ currentSubscriber = prev;
1378
+ }
1379
+ }
1320
1380
  function track(effectFn, subscriber) {
1321
1381
  if (!subscriber) subscriber = effectFn;
1322
1382
  cleanup(subscriber);
@@ -1384,7 +1444,6 @@ function recordDependency(signal2) {
1384
1444
  let subs = signal2[SUBS];
1385
1445
  if (!subs) {
1386
1446
  subs = /* @__PURE__ */ new Set();
1387
- signalSubscribers.set(signal2, subs);
1388
1447
  signal2[SUBS] = subs;
1389
1448
  }
1390
1449
  subs.add(currentSubscriber);
@@ -1406,17 +1465,17 @@ function queueSignalNotification(signal2) {
1406
1465
  }
1407
1466
  }
1408
1467
  }
1409
- var MAX_DRAIN_ITERATIONS = 1e3;
1468
+ var maxDrainIterations = 1e5;
1410
1469
  function drainNotificationQueue() {
1411
1470
  if (notifyDepth > 0) return;
1412
1471
  notifyDepth++;
1413
1472
  try {
1414
1473
  let i2 = 0;
1415
1474
  while (i2 < pendingQueue.length) {
1416
- if (i2 >= MAX_DRAIN_ITERATIONS) {
1475
+ if (i2 >= maxDrainIterations) {
1417
1476
  if (typeof console !== "undefined") {
1418
1477
  console.error(
1419
- `[SibuJS] Notification queue exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
1478
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
1420
1479
  );
1421
1480
  }
1422
1481
  break;
@@ -1425,47 +1484,52 @@ function drainNotificationQueue() {
1425
1484
  i2++;
1426
1485
  }
1427
1486
  } finally {
1428
- pendingQueue.length = 0;
1429
- pendingSet.clear();
1430
1487
  notifyDepth--;
1488
+ if (notifyDepth === 0) {
1489
+ pendingQueue.length = 0;
1490
+ pendingSet.clear();
1491
+ }
1431
1492
  }
1432
1493
  }
1433
1494
  function propagateDirty(sub2) {
1434
1495
  sub2();
1435
- let sig = sub2._sig;
1436
- while (sig) {
1496
+ const rootSig = sub2._sig;
1497
+ if (!rootSig) return;
1498
+ const stack = propagateStack;
1499
+ const baseLen = stack.length;
1500
+ stack.push(rootSig);
1501
+ while (stack.length > baseLen) {
1502
+ const sig = stack.pop();
1437
1503
  const first = sig.__f;
1438
1504
  if (first) {
1439
1505
  if (first._c) {
1440
1506
  const nSig = first._sig;
1441
- nSig._d = true;
1442
- sig = nSig;
1443
- continue;
1444
- }
1445
- if (!pendingSet.has(first)) {
1507
+ if (!nSig._d) {
1508
+ nSig._d = true;
1509
+ stack.push(nSig);
1510
+ }
1511
+ } else if (!pendingSet.has(first)) {
1446
1512
  pendingSet.add(first);
1447
1513
  pendingQueue.push(first);
1448
1514
  }
1449
- break;
1515
+ continue;
1450
1516
  }
1451
1517
  const subs = sig[SUBS];
1452
- if (!subs) break;
1453
- let nextSig;
1518
+ if (!subs) continue;
1454
1519
  for (const s2 of subs) {
1455
1520
  if (s2._c) {
1456
- s2();
1457
1521
  const nSig = s2._sig;
1458
- if (nSig && !nextSig) {
1459
- nextSig = nSig;
1460
- } else if (nSig) {
1461
- propagateDirty(s2);
1522
+ if (nSig && !nSig._d) {
1523
+ nSig._d = true;
1524
+ stack.push(nSig);
1525
+ } else if (!nSig) {
1526
+ s2();
1462
1527
  }
1463
1528
  } else if (!pendingSet.has(s2)) {
1464
1529
  pendingSet.add(s2);
1465
1530
  pendingQueue.push(s2);
1466
1531
  }
1467
1532
  }
1468
- sig = nextSig;
1469
1533
  }
1470
1534
  }
1471
1535
  function notifySubscribers(signal2) {
@@ -1489,13 +1553,23 @@ function notifySubscribers(signal2) {
1489
1553
  }
1490
1554
  let i2 = 0;
1491
1555
  while (i2 < pendingQueue.length) {
1556
+ if (i2 >= maxDrainIterations) {
1557
+ if (typeof console !== "undefined") {
1558
+ console.error(
1559
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
1560
+ );
1561
+ }
1562
+ break;
1563
+ }
1492
1564
  safeInvoke(pendingQueue[i2]);
1493
1565
  i2++;
1494
1566
  }
1495
1567
  } finally {
1496
- pendingQueue.length = 0;
1497
- pendingSet.clear();
1498
1568
  notifyDepth--;
1569
+ if (notifyDepth === 0) {
1570
+ pendingQueue.length = 0;
1571
+ pendingSet.clear();
1572
+ }
1499
1573
  }
1500
1574
  return;
1501
1575
  }
@@ -1515,30 +1589,48 @@ function notifySubscribers(signal2) {
1515
1589
  notifyDepth++;
1516
1590
  try {
1517
1591
  let directCount = 0;
1592
+ let hasComputedSub = false;
1518
1593
  for (const sub2 of subs) {
1594
+ if (sub2._c) hasComputedSub = true;
1519
1595
  pendingQueue[directCount++] = sub2;
1520
1596
  }
1521
- for (let i3 = 0; i3 < directCount; i3++) {
1522
- if (pendingQueue[i3]._c) {
1523
- propagateDirty(pendingQueue[i3]);
1597
+ if (!hasComputedSub) {
1598
+ for (let i3 = 0; i3 < directCount; i3++) {
1599
+ safeInvoke(pendingQueue[i3]);
1524
1600
  }
1525
- }
1526
- for (let i3 = 0; i3 < directCount; i3++) {
1527
- if (!pendingQueue[i3]._c) {
1528
- if (!pendingSet.has(pendingQueue[i3])) {
1529
- safeInvoke(pendingQueue[i3]);
1601
+ } else {
1602
+ for (let i3 = 0; i3 < directCount; i3++) {
1603
+ if (pendingQueue[i3]._c) {
1604
+ propagateDirty(pendingQueue[i3]);
1605
+ }
1606
+ }
1607
+ for (let i3 = 0; i3 < directCount; i3++) {
1608
+ const sub2 = pendingQueue[i3];
1609
+ if (!sub2._c && !pendingSet.has(sub2)) {
1610
+ pendingSet.add(sub2);
1611
+ safeInvoke(sub2);
1530
1612
  }
1531
1613
  }
1532
1614
  }
1533
1615
  let i2 = directCount;
1534
1616
  while (i2 < pendingQueue.length) {
1617
+ if (i2 - directCount >= maxDrainIterations) {
1618
+ if (typeof console !== "undefined") {
1619
+ console.error(
1620
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
1621
+ );
1622
+ }
1623
+ break;
1624
+ }
1535
1625
  safeInvoke(pendingQueue[i2]);
1536
1626
  i2++;
1537
1627
  }
1538
1628
  } finally {
1539
- pendingQueue.length = 0;
1540
- pendingSet.clear();
1541
1629
  notifyDepth--;
1630
+ if (notifyDepth === 0) {
1631
+ pendingQueue.length = 0;
1632
+ pendingSet.clear();
1633
+ }
1542
1634
  }
1543
1635
  }
1544
1636
  function cleanup(subscriber) {
@@ -1549,7 +1641,9 @@ function cleanup(subscriber) {
1549
1641
  if (subs) {
1550
1642
  subs.delete(subscriber);
1551
1643
  if (singleDep.__f === subscriber) {
1552
- singleDep.__f = void 0;
1644
+ singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
1645
+ } else if (subs.size === 1 && singleDep.__f === void 0) {
1646
+ singleDep.__f = subs.values().next().value;
1553
1647
  }
1554
1648
  }
1555
1649
  sub2._dep = void 0;
@@ -1562,7 +1656,9 @@ function cleanup(subscriber) {
1562
1656
  if (subs) {
1563
1657
  subs.delete(subscriber);
1564
1658
  if (signal2.__f === subscriber) {
1565
- signal2.__f = void 0;
1659
+ signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
1660
+ } else if (subs.size === 1 && signal2.__f === void 0) {
1661
+ signal2.__f = subs.values().next().value;
1566
1662
  }
1567
1663
  }
1568
1664
  }
@@ -1571,6 +1667,9 @@ function cleanup(subscriber) {
1571
1667
 
1572
1668
  // src/reactivity/bindAttribute.ts
1573
1669
  var _isDev3 = isDev();
1670
+ function setProp(el, key, val) {
1671
+ el[key] = val;
1672
+ }
1574
1673
  function isEventHandlerAttr(name) {
1575
1674
  if (name.length < 3) return false;
1576
1675
  const lower = name.toLowerCase();
@@ -1596,7 +1695,7 @@ function bindAttribute(el, attr, getter) {
1596
1695
  }
1597
1696
  if (typeof value === "boolean") {
1598
1697
  if (attr in el && (attr === "checked" || attr === "disabled" || attr === "selected")) {
1599
- el[attr] = value;
1698
+ setProp(el, attr, value);
1600
1699
  } else if (value) {
1601
1700
  el.setAttribute(attr, "");
1602
1701
  } else {
@@ -1606,7 +1705,7 @@ function bindAttribute(el, attr, getter) {
1606
1705
  }
1607
1706
  const str = String(value);
1608
1707
  if ((attr === "value" || attr === "checked") && attr in el) {
1609
- el[attr] = attr === "checked" ? Boolean(value) : str;
1708
+ setProp(el, attr, attr === "checked" ? Boolean(value) : str);
1610
1709
  } else {
1611
1710
  el.setAttribute(attr, isUrlAttribute(attr) ? sanitizeUrl(str) : str);
1612
1711
  }
@@ -1637,7 +1736,7 @@ function bindDynamic(el, nameGetter, valueGetter) {
1637
1736
  }
1638
1737
  const str = String(value);
1639
1738
  if ((name === "value" || name === "checked") && name in el) {
1640
- el[name] = name === "checked" ? Boolean(value) : str;
1739
+ setProp(el, name, name === "checked" ? Boolean(value) : str);
1641
1740
  } else {
1642
1741
  el.setAttribute(name, isUrlAttribute(name) ? sanitizeUrl(str) : str);
1643
1742
  }
@@ -1680,24 +1779,29 @@ function bindChildNode(placeholder, getter) {
1680
1779
  let newNodes;
1681
1780
  if (Array.isArray(result)) {
1682
1781
  newNodes = [];
1782
+ const seen = /* @__PURE__ */ new Set();
1683
1783
  for (let i2 = 0; i2 < result.length; i2++) {
1684
1784
  const item = result[i2];
1685
1785
  if (item == null || typeof item === "boolean") continue;
1686
- newNodes.push(item instanceof Node ? item : document.createTextNode(String(item)));
1786
+ const node = item instanceof Node ? item : document.createTextNode(String(item));
1787
+ if (seen.has(node)) {
1788
+ if (_isDev4)
1789
+ devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
1790
+ continue;
1791
+ }
1792
+ seen.add(node);
1793
+ newNodes.push(node);
1687
1794
  }
1688
1795
  } else {
1689
1796
  const node = result instanceof Node ? result : document.createTextNode(String(result));
1690
1797
  newNodes = [node];
1691
1798
  }
1692
- const reused = lastNodes.length > 0 && newNodes.length > 0 ? /* @__PURE__ */ new Set() : void 0;
1693
- if (reused) {
1799
+ let reused;
1800
+ if (lastNodes.length > 0 && newNodes.length > 0) {
1801
+ const lastSet = new Set(lastNodes);
1802
+ reused = /* @__PURE__ */ new Set();
1694
1803
  for (let i2 = 0; i2 < newNodes.length; i2++) {
1695
- for (let j = 0; j < lastNodes.length; j++) {
1696
- if (newNodes[i2] === lastNodes[j]) {
1697
- reused.add(newNodes[i2]);
1698
- break;
1699
- }
1700
- }
1804
+ if (lastSet.has(newNodes[i2])) reused.add(newNodes[i2]);
1701
1805
  }
1702
1806
  }
1703
1807
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
@@ -1740,7 +1844,7 @@ function dispose(node) {
1740
1844
  while (stack.length > 0) {
1741
1845
  const current = stack.pop();
1742
1846
  order.push(current);
1743
- const children = current.childNodes;
1847
+ const children = Array.from(current.childNodes);
1744
1848
  for (let i2 = 0; i2 < children.length; i2++) {
1745
1849
  stack.push(children[i2]);
1746
1850
  }
@@ -1749,8 +1853,10 @@ function dispose(node) {
1749
1853
  const current = order[i2];
1750
1854
  const disposers = elementDisposers.get(current);
1751
1855
  if (disposers) {
1752
- if (_isDev5) activeBindingCount -= disposers.length;
1753
- for (const d of disposers) {
1856
+ const snapshot = disposers.slice();
1857
+ elementDisposers.delete(current);
1858
+ if (_isDev5) activeBindingCount -= snapshot.length;
1859
+ for (const d of snapshot) {
1754
1860
  try {
1755
1861
  d();
1756
1862
  } catch (err) {
@@ -1759,7 +1865,23 @@ function dispose(node) {
1759
1865
  }
1760
1866
  }
1761
1867
  }
1762
- elementDisposers.delete(current);
1868
+ let extraPasses = 0;
1869
+ while (extraPasses++ < 8) {
1870
+ const added = elementDisposers.get(current);
1871
+ if (!added || added.length === 0) break;
1872
+ const moreSnapshot = added.slice();
1873
+ elementDisposers.delete(current);
1874
+ if (_isDev5) activeBindingCount -= moreSnapshot.length;
1875
+ for (const d of moreSnapshot) {
1876
+ try {
1877
+ d();
1878
+ } catch (err) {
1879
+ if (_isDev5 && typeof console !== "undefined") {
1880
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
1881
+ }
1882
+ }
1883
+ }
1884
+ }
1763
1885
  }
1764
1886
  }
1765
1887
  }
@@ -1775,6 +1897,30 @@ function checkLeaks(warnThreshold = 0) {
1775
1897
 
1776
1898
  // src/core/rendering/tagFactory.ts
1777
1899
  var SVG_NS = "http://www.w3.org/2000/svg";
1900
+ var _isDev6 = isDev();
1901
+ var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
1902
+ function validateTagName(tag) {
1903
+ const lower = tag.toLowerCase();
1904
+ if (BLOCKED_TAGS.has(lower)) {
1905
+ throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
1906
+ }
1907
+ }
1908
+ var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
1909
+ "config",
1910
+ "location",
1911
+ "history",
1912
+ "document",
1913
+ "window",
1914
+ "navigator",
1915
+ "name",
1916
+ "top",
1917
+ "parent",
1918
+ "self",
1919
+ "frames"
1920
+ ]);
1921
+ function setProp2(el, key, val) {
1922
+ el[key] = val;
1923
+ }
1778
1924
  var kebabCache = /* @__PURE__ */ new Map();
1779
1925
  function toKebab(prop) {
1780
1926
  let cached = kebabCache.get(prop);
@@ -1899,79 +2045,103 @@ function appendChildren(el, nodes) {
1899
2045
  }
1900
2046
  }
1901
2047
  }
1902
- var tagFactory = (tag, ns) => (first, second) => {
1903
- const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
1904
- if (first === void 0) return el;
1905
- if (typeof first === "string") {
1906
- if (second !== void 0) {
1907
- el.setAttribute("class", first);
1908
- appendChildren(el, second);
2048
+ var tagFactory = (tag, ns) => {
2049
+ return (first, second) => {
2050
+ validateTagName(tag);
2051
+ const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
2052
+ if (first === void 0) return el;
2053
+ if (typeof first === "string") {
2054
+ if (second !== void 0) {
2055
+ el.setAttribute("class", first);
2056
+ appendChildren(el, second);
2057
+ return el;
2058
+ }
2059
+ el.textContent = first;
1909
2060
  return el;
1910
2061
  }
1911
- el.textContent = first;
1912
- return el;
1913
- }
1914
- if (typeof first === "number") {
1915
- el.textContent = String(first);
1916
- return el;
1917
- }
1918
- if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
1919
- appendChildren(el, first);
1920
- return el;
1921
- }
1922
- const props = first;
1923
- const pClass = props.class;
1924
- if (pClass != null) applyClass(el, pClass);
1925
- const pId = props.id;
1926
- if (pId != null) el.id = pId;
1927
- const pNodes = second !== void 0 ? second : props.nodes;
1928
- if (pNodes != null) appendChildren(el, pNodes);
1929
- const pOn = props.on;
1930
- if (pOn) {
1931
- for (const ev in pOn) {
1932
- el.addEventListener(ev, pOn[ev]);
1933
- }
1934
- }
1935
- const pStyle = props.style;
1936
- if (pStyle != null) applyStyle(el, pStyle);
1937
- const pRef = props.ref;
1938
- if (pRef) pRef.current = el;
1939
- for (const key in props) {
1940
- switch (key) {
1941
- case "class":
1942
- case "id":
1943
- case "nodes":
1944
- case "on":
1945
- case "style":
1946
- case "ref":
1947
- case "onElement":
1948
- continue;
1949
- // already handled above / below
1950
- default: {
1951
- const value = props[key];
1952
- if (value == null) continue;
1953
- if (key[0] === "o" && key[1] === "n") continue;
1954
- if (typeof value === "function") {
1955
- registerDisposer(el, bindAttribute(el, key, value));
1956
- } else if (typeof value === "boolean") {
1957
- if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
1958
- el[key] = value;
1959
- } else if (value) {
1960
- el.setAttribute(key, "");
2062
+ if (typeof first === "number") {
2063
+ el.textContent = String(first);
2064
+ return el;
2065
+ }
2066
+ if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
2067
+ appendChildren(el, first);
2068
+ return el;
2069
+ }
2070
+ const props = first;
2071
+ const pClass = props.class;
2072
+ if (pClass != null) applyClass(el, pClass);
2073
+ const pId = props.id;
2074
+ if (pId != null) {
2075
+ if (_isDev6 && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
2076
+ devWarn(
2077
+ `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
2078
+ );
2079
+ }
2080
+ el.id = pId;
2081
+ }
2082
+ const pNodes = second !== void 0 ? second : props.nodes;
2083
+ if (pNodes != null) appendChildren(el, pNodes);
2084
+ const pOn = props.on;
2085
+ if (pOn) {
2086
+ for (const ev in pOn) {
2087
+ const handler = pOn[ev];
2088
+ if (typeof handler === "function") {
2089
+ el.addEventListener(ev, handler);
2090
+ } else if (_isDev6) {
2091
+ devWarn(
2092
+ `tagFactory: on.${ev} handler is not a function (got ${typeof handler}). Event listener was not attached.`
2093
+ );
2094
+ }
2095
+ }
2096
+ }
2097
+ const pStyle = props.style;
2098
+ if (pStyle != null) applyStyle(el, pStyle);
2099
+ const pRef = props.ref;
2100
+ if (pRef) pRef.current = el;
2101
+ for (const key in props) {
2102
+ switch (key) {
2103
+ case "class":
2104
+ case "id":
2105
+ case "nodes":
2106
+ case "on":
2107
+ case "style":
2108
+ case "ref":
2109
+ case "onElement":
2110
+ continue;
2111
+ // already handled above / below
2112
+ default: {
2113
+ const value = props[key];
2114
+ if (value == null) continue;
2115
+ const lkey = key.toLowerCase();
2116
+ if (lkey[0] === "o" && lkey[1] === "n") continue;
2117
+ if (typeof value === "function") {
2118
+ registerDisposer(el, bindAttribute(el, key, value));
2119
+ } else if (typeof value === "boolean") {
2120
+ if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
2121
+ setProp2(el, key, value);
2122
+ } else if (value) {
2123
+ el.setAttribute(key, "");
2124
+ } else {
2125
+ el.removeAttribute(key);
2126
+ }
1961
2127
  } else {
1962
- el.removeAttribute(key);
2128
+ const str = String(value);
2129
+ if (lkey === "srcset") {
2130
+ el.setAttribute(key, sanitizeSrcset(str));
2131
+ } else if (isUrlAttribute(lkey)) {
2132
+ el.setAttribute(key, sanitizeUrl(str));
2133
+ } else {
2134
+ el.setAttribute(key, str);
2135
+ }
1963
2136
  }
1964
- } else {
1965
- const str = String(value);
1966
- el.setAttribute(key, isUrlAttribute(key) ? sanitizeUrl(str) : str);
1967
2137
  }
1968
2138
  }
1969
2139
  }
1970
- }
1971
- if (props.onElement && typeof props.onElement === "function") {
1972
- props.onElement(el);
1973
- }
1974
- return el;
2140
+ if (props.onElement && typeof props.onElement === "function") {
2141
+ props.onElement(el);
2142
+ }
2143
+ return el;
2144
+ };
1975
2145
  };
1976
2146
 
1977
2147
  // src/core/rendering/html.ts
@@ -2114,6 +2284,8 @@ var marquee = tagFactory("marquee");
2114
2284
  var customElement = (tagName) => tagFactory(tagName);
2115
2285
 
2116
2286
  // src/core/rendering/htm.ts
2287
+ var _isDev7 = isDev();
2288
+ var RAW_TEXT_TAGS = /* @__PURE__ */ new Set(["script", "style"]);
2117
2289
  var VOID_ELEMENTS3 = /* @__PURE__ */ new Set([
2118
2290
  "area",
2119
2291
  "base",
@@ -2298,6 +2470,15 @@ function parseTemplate(strings) {
2298
2470
  children.push({ t: 0, el: { tag, svg: SVG_TAGS2.has(tag), attrs, children: [] } });
2299
2471
  } else {
2300
2472
  const inner = parseChildren();
2473
+ if (RAW_TEXT_TAGS.has(tag.toLowerCase())) {
2474
+ for (let i2 = 0; i2 < inner.length; i2++) {
2475
+ if (inner[i2].t === 2) {
2476
+ throw new Error(
2477
+ `html: dynamic \${...} expressions are not allowed inside <${tag}> (raw-text context). Build the content separately and append it as a Node.`
2478
+ );
2479
+ }
2480
+ }
2481
+ }
2301
2482
  if (template2[pos] === "<" && pos + 1 < len && template2[pos + 1] === "/") {
2302
2483
  pos += 2;
2303
2484
  readTagName();
@@ -2324,27 +2505,50 @@ function executeElement(tmpl, values) {
2324
2505
  break;
2325
2506
  case 1: {
2326
2507
  const name = attr.name;
2327
- if (name[0] === "o" && name[1] === "n") break;
2508
+ const lname = name.toLowerCase();
2509
+ if (lname[0] === "o" && lname[1] === "n") break;
2328
2510
  const val = values[attr.idx];
2329
2511
  if (typeof val === "function") {
2330
2512
  registerDisposer(el, bindAttribute(el, name, val));
2331
2513
  } else if (val != null) {
2332
2514
  const str = String(val);
2333
- el.setAttribute(name, isUrlAttribute(name) ? sanitizeUrl(str) : str);
2515
+ if (lname === "srcset") {
2516
+ el.setAttribute(name, sanitizeSrcset(str));
2517
+ } else if (isUrlAttribute(lname)) {
2518
+ el.setAttribute(name, sanitizeUrl(str));
2519
+ } else {
2520
+ el.setAttribute(name, str);
2521
+ }
2334
2522
  }
2335
2523
  break;
2336
2524
  }
2337
2525
  case 2: {
2338
2526
  let val = attr.statics[0];
2339
2527
  for (let j = 0; j < attr.exprs.length; j++) {
2340
- val += String(values[attr.exprs[j]]) + attr.statics[j + 1];
2528
+ const ev = values[attr.exprs[j]];
2529
+ val += (ev == null ? "" : String(ev)) + attr.statics[j + 1];
2530
+ }
2531
+ const lname2 = attr.name.toLowerCase();
2532
+ if (lname2 === "srcset") {
2533
+ el.setAttribute(attr.name, sanitizeSrcset(val));
2534
+ } else if (isUrlAttribute(lname2)) {
2535
+ el.setAttribute(attr.name, sanitizeUrl(val));
2536
+ } else {
2537
+ el.setAttribute(attr.name, val);
2341
2538
  }
2342
- el.setAttribute(attr.name, val);
2343
2539
  break;
2344
2540
  }
2345
- case 3:
2346
- el.addEventListener(attr.name, values[attr.idx]);
2541
+ case 3: {
2542
+ const fn = values[attr.idx];
2543
+ if (typeof fn === "function") {
2544
+ el.addEventListener(attr.name, fn);
2545
+ } else if (_isDev7) {
2546
+ devWarn(
2547
+ `html: on:${attr.name} handler is not a function (got ${typeof fn}). Event listener was not attached.`
2548
+ );
2549
+ }
2347
2550
  break;
2551
+ }
2348
2552
  case 4:
2349
2553
  el.setAttribute(attr.name, "");
2350
2554
  break;
@@ -2438,7 +2642,7 @@ function html2(strings, ...values) {
2438
2642
  function mount(component, container) {
2439
2643
  if (!container) {
2440
2644
  throw new Error(
2441
- "[Sibu] mount: container element not found. Make sure the DOM element exists before calling mount()."
2645
+ "[SibuJS mount] container element not found. Make sure the DOM element exists before calling mount()."
2442
2646
  );
2443
2647
  }
2444
2648
  devAssert(
@@ -2466,7 +2670,7 @@ function mount(component, container) {
2466
2670
  }
2467
2671
 
2468
2672
  // src/core/rendering/each.ts
2469
- var _isDev6 = isDev();
2673
+ var _isDev8 = isDev();
2470
2674
  function resolveNodeChild(child) {
2471
2675
  if (typeof child === "function") {
2472
2676
  return resolveNodeChild(child());
@@ -2561,17 +2765,31 @@ function each(getArray, render, options) {
2561
2765
  node = existing;
2562
2766
  } else {
2563
2767
  const itemKey = key;
2564
- const itemGetter = () => getArray()[keyIndexMap.get(itemKey)];
2768
+ const itemGetter = () => untracked(() => getArray()[keyIndexMap.get(itemKey)]);
2565
2769
  const indexGetter = () => keyIndexMap.get(itemKey);
2566
2770
  try {
2567
2771
  node = resolveNodeChild(render(itemGetter, indexGetter));
2568
2772
  } catch (err) {
2569
- if (_isDev6) {
2773
+ if (_isDev8) {
2570
2774
  devWarn(
2571
2775
  `each: render threw for item at index ${i2} (key="${newKeys[i2]}"): ${err instanceof Error ? err.message : String(err)}`
2572
2776
  );
2573
2777
  }
2574
2778
  node = document.createComment(`each:error:${i2}`);
2779
+ const errorObj = err instanceof Error ? err : new Error(String(err));
2780
+ queueMicrotask(() => {
2781
+ try {
2782
+ const target = anchor.parentNode;
2783
+ if (target?.dispatchEvent) {
2784
+ target.dispatchEvent(
2785
+ new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error: errorObj } })
2786
+ );
2787
+ } else if (_isDev8) {
2788
+ devWarn(`each: error not surfaced \u2014 anchor detached: ${errorObj.message}`);
2789
+ }
2790
+ } catch {
2791
+ }
2792
+ });
2575
2793
  }
2576
2794
  }
2577
2795
  workMap.set(key, node);
@@ -2640,7 +2858,8 @@ function each(getArray, render, options) {
2640
2858
  workMap = tmp;
2641
2859
  initialized = true;
2642
2860
  };
2643
- track(update);
2861
+ const untrack = track(update);
2862
+ registerDisposer(anchor, untrack);
2644
2863
  if (!initialized) {
2645
2864
  queueMicrotask(() => {
2646
2865
  if (!initialized && anchor.parentNode) {
@@ -2687,7 +2906,9 @@ function Portal(nodes, target) {
2687
2906
  const anchor = document.createComment("portal");
2688
2907
  const container = target || document.body;
2689
2908
  let portalContent = null;
2909
+ let disposed = false;
2690
2910
  queueMicrotask(() => {
2911
+ if (disposed) return;
2691
2912
  try {
2692
2913
  portalContent = nodes();
2693
2914
  container.appendChild(portalContent);
@@ -2695,9 +2916,22 @@ function Portal(nodes, target) {
2695
2916
  if (typeof console !== "undefined") {
2696
2917
  console.error("[Portal] Render error:", err);
2697
2918
  }
2919
+ const errorObj = err instanceof Error ? err : new Error(String(err));
2920
+ queueMicrotask(() => {
2921
+ try {
2922
+ const target2 = anchor.parentNode;
2923
+ if (target2?.dispatchEvent) {
2924
+ target2.dispatchEvent(
2925
+ new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error: errorObj } })
2926
+ );
2927
+ }
2928
+ } catch {
2929
+ }
2930
+ });
2698
2931
  }
2699
2932
  });
2700
2933
  registerDisposer(anchor, () => {
2934
+ disposed = true;
2701
2935
  if (portalContent) {
2702
2936
  dispose(portalContent);
2703
2937
  portalContent.remove();
@@ -2737,7 +2971,8 @@ function DynamicComponent(is) {
2737
2971
  }
2738
2972
  container.replaceChildren(el);
2739
2973
  }
2740
- track(render);
2974
+ const untrack = track(render);
2975
+ registerDisposer(container, untrack);
2741
2976
  return container;
2742
2977
  }
2743
2978
 
@@ -2827,11 +3062,16 @@ function KeepAlive(activeKey, cases, options) {
2827
3062
  const anchor = document.createComment("keep-alive");
2828
3063
  const cache2 = /* @__PURE__ */ new Map();
2829
3064
  const lruOrder = [];
2830
- const max = options?.max ?? 0;
3065
+ const max = options?.max ?? 10;
3066
+ if (max === 0 && isDev()) {
3067
+ devWarn("KeepAlive: unbounded cache (max: 0). Cached subtrees will never be evicted \u2014 set `max` to bound memory.");
3068
+ }
2831
3069
  let currentKey;
2832
3070
  let currentNode = null;
2833
3071
  let initialized = false;
3072
+ let disposed = false;
2834
3073
  const update = () => {
3074
+ if (disposed) return;
2835
3075
  const key = activeKey();
2836
3076
  const parent = anchor.parentNode;
2837
3077
  if (!parent) return;
@@ -2871,12 +3111,23 @@ function KeepAlive(activeKey, cases, options) {
2871
3111
  currentNode = node;
2872
3112
  initialized = true;
2873
3113
  };
2874
- track(update);
3114
+ const untrack = track(update);
2875
3115
  if (!initialized) {
2876
3116
  queueMicrotask(() => {
2877
3117
  if (!initialized && anchor.parentNode) update();
2878
3118
  });
2879
3119
  }
3120
+ registerDisposer(anchor, () => {
3121
+ disposed = true;
3122
+ untrack();
3123
+ for (const node of cache2.values()) {
3124
+ dispose(node);
3125
+ if (node.parentNode) node.parentNode.removeChild(node);
3126
+ }
3127
+ cache2.clear();
3128
+ lruOrder.length = 0;
3129
+ currentNode = null;
3130
+ });
2880
3131
  return anchor;
2881
3132
  }
2882
3133
 
@@ -3038,19 +3289,22 @@ function isBatching() {
3038
3289
  return batchDepth > 0;
3039
3290
  }
3040
3291
  function flushBatch() {
3041
- for (const signal2 of pendingSignals) {
3042
- queueSignalNotification(signal2);
3292
+ try {
3293
+ for (const signal2 of pendingSignals) {
3294
+ queueSignalNotification(signal2);
3295
+ }
3296
+ } finally {
3297
+ pendingSignals.clear();
3043
3298
  }
3044
- pendingSignals.clear();
3045
3299
  drainNotificationQueue();
3046
3300
  }
3047
3301
 
3048
3302
  // src/core/signals/signal.ts
3049
3303
  var _g = globalThis;
3050
- var _isDev7 = isDev();
3304
+ var _isDev9 = isDev();
3051
3305
  function signal(initial, options) {
3052
3306
  const state = { value: initial };
3053
- const debugName = _isDev7 ? options?.name : void 0;
3307
+ const debugName = _isDev9 ? options?.name : void 0;
3054
3308
  const equalsFn = options?.equals;
3055
3309
  if (debugName) {
3056
3310
  state.__name = debugName;
@@ -3064,7 +3318,7 @@ function signal(initial, options) {
3064
3318
  function set(next) {
3065
3319
  const newValue = typeof next === "function" ? next(state.value) : next;
3066
3320
  if (equalsFn ? equalsFn(state.value, newValue) : Object.is(newValue, state.value)) return;
3067
- if (_isDev7) {
3321
+ if (_isDev9) {
3068
3322
  const oldValue = state.value;
3069
3323
  state.value = newValue;
3070
3324
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
@@ -3076,7 +3330,7 @@ function signal(initial, options) {
3076
3330
  notifySubscribers(state);
3077
3331
  }
3078
3332
  }
3079
- if (_isDev7) {
3333
+ if (_isDev9) {
3080
3334
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3081
3335
  if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
3082
3336
  }
@@ -3084,23 +3338,59 @@ function signal(initial, options) {
3084
3338
  }
3085
3339
 
3086
3340
  // src/core/ssr-context.ts
3087
- var ssrMode = false;
3341
+ var als = null;
3342
+ try {
3343
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
3344
+ const req = Function("return typeof require==='function'?require:null")();
3345
+ if (req) {
3346
+ const mod = req("node:async_hooks");
3347
+ als = new mod.AsyncLocalStorage();
3348
+ }
3349
+ }
3350
+ } catch {
3351
+ als = null;
3352
+ }
3353
+ var fallbackStore = { ssr: false, suspenseIdCounter: 0 };
3354
+ function getSSRStore() {
3355
+ if (als) {
3356
+ const s2 = als.getStore();
3357
+ if (s2) return s2;
3358
+ }
3359
+ return fallbackStore;
3360
+ }
3088
3361
  function isSSR() {
3089
- return ssrMode;
3362
+ return getSSRStore().ssr;
3090
3363
  }
3091
3364
  function enableSSR() {
3092
- ssrMode = true;
3365
+ getSSRStore().ssr = true;
3093
3366
  }
3094
3367
  function disableSSR() {
3095
- ssrMode = false;
3368
+ getSSRStore().ssr = false;
3369
+ }
3370
+ function runInSSRContext(fn) {
3371
+ const store2 = { ssr: true, suspenseIdCounter: 0 };
3372
+ if (als) {
3373
+ return als.run(store2, fn);
3374
+ }
3375
+ const prevSSR = fallbackStore.ssr;
3376
+ const prevCounter = fallbackStore.suspenseIdCounter;
3377
+ fallbackStore.ssr = true;
3378
+ fallbackStore.suspenseIdCounter = 0;
3379
+ try {
3380
+ return fn();
3381
+ } finally {
3382
+ fallbackStore.ssr = prevSSR;
3383
+ fallbackStore.suspenseIdCounter = prevCounter;
3384
+ }
3096
3385
  }
3097
3386
  function withSSR(fn) {
3098
- const wasSSR = ssrMode;
3099
- enableSSR();
3387
+ const store2 = getSSRStore();
3388
+ const wasSSR = store2.ssr;
3389
+ store2.ssr = true;
3100
3390
  try {
3101
3391
  return fn();
3102
3392
  } finally {
3103
- if (!wasSSR) disableSSR();
3393
+ if (!wasSSR) store2.ssr = false;
3104
3394
  }
3105
3395
  }
3106
3396
 
@@ -3127,26 +3417,86 @@ function effect(effectFn, options) {
3127
3417
  if (isSSR()) return () => {
3128
3418
  };
3129
3419
  const onError = options?.onError;
3420
+ let userCleanups = [];
3421
+ const onCleanup2 = (fn) => {
3422
+ userCleanups.push(fn);
3423
+ };
3424
+ const runUserCleanups = () => {
3425
+ if (userCleanups.length === 0) return;
3426
+ const list = userCleanups;
3427
+ userCleanups = [];
3428
+ for (let i2 = list.length - 1; i2 >= 0; i2--) {
3429
+ try {
3430
+ list[i2]();
3431
+ } catch (err) {
3432
+ if (typeof console !== "undefined") {
3433
+ console.warn("[SibuJS effect] onCleanup threw:", err);
3434
+ }
3435
+ }
3436
+ }
3437
+ };
3438
+ const invokeBody = () => effectFn(onCleanup2);
3130
3439
  const wrappedFn = onError ? () => {
3131
3440
  try {
3132
- effectFn();
3441
+ invokeBody();
3133
3442
  } catch (err) {
3134
3443
  onError(err);
3135
3444
  }
3136
- } : effectFn;
3445
+ } : invokeBody;
3137
3446
  let cleanupHandle = () => {
3138
3447
  };
3448
+ let running = false;
3139
3449
  const subscriber = () => {
3140
- cleanupHandle();
3141
- cleanupHandle = track(wrappedFn, subscriber);
3450
+ if (running) {
3451
+ if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
3452
+ console.warn(
3453
+ "[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."
3454
+ );
3455
+ }
3456
+ return;
3457
+ }
3458
+ running = true;
3459
+ try {
3460
+ runUserCleanups();
3461
+ cleanupHandle();
3462
+ cleanupHandle = track(wrappedFn, subscriber);
3463
+ } finally {
3464
+ running = false;
3465
+ }
3142
3466
  };
3143
- cleanupHandle = track(wrappedFn, subscriber);
3467
+ running = true;
3468
+ try {
3469
+ cleanupHandle = track(wrappedFn, subscriber);
3470
+ } finally {
3471
+ running = false;
3472
+ }
3144
3473
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3145
3474
  if (hook) hook.emit("effect:create", { effectFn });
3475
+ let disposed = false;
3146
3476
  return () => {
3477
+ if (disposed) return;
3478
+ disposed = true;
3147
3479
  const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3148
- if (h) h.emit("effect:destroy", { effectFn });
3149
- cleanupHandle();
3480
+ if (h) {
3481
+ try {
3482
+ h.emit("effect:destroy", { effectFn });
3483
+ } catch {
3484
+ }
3485
+ }
3486
+ try {
3487
+ runUserCleanups();
3488
+ } catch (err) {
3489
+ if (typeof console !== "undefined") {
3490
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
3491
+ }
3492
+ }
3493
+ try {
3494
+ cleanupHandle();
3495
+ } catch (err) {
3496
+ if (typeof console !== "undefined") {
3497
+ console.warn("[SibuJS effect] dispose threw:", err);
3498
+ }
3499
+ }
3150
3500
  };
3151
3501
  }
3152
3502
 
@@ -3154,6 +3504,7 @@ function effect(effectFn, options) {
3154
3504
  function derived(getter, options) {
3155
3505
  devAssert(typeof getter === "function", "derived: argument must be a getter function.");
3156
3506
  const debugName = options?.name;
3507
+ const equals = options?.equals;
3157
3508
  const cs = {};
3158
3509
  cs._d = false;
3159
3510
  cs._g = getter;
@@ -3164,8 +3515,14 @@ function derived(getter, options) {
3164
3515
  markDirty._c = 1;
3165
3516
  markDirty._sig = cs;
3166
3517
  track(() => {
3167
- cs._d = false;
3168
- cs._v = getter();
3518
+ let threw = true;
3519
+ try {
3520
+ cs._v = getter();
3521
+ cs._d = false;
3522
+ threw = false;
3523
+ } finally {
3524
+ if (threw) cs._d = true;
3525
+ }
3169
3526
  }, markDirty);
3170
3527
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3171
3528
  let evaluating = false;
@@ -3178,11 +3535,16 @@ function derived(getter, options) {
3178
3535
  if (trackingSuspended) {
3179
3536
  if (cs._d) {
3180
3537
  evaluating = true;
3538
+ let threw = true;
3181
3539
  try {
3182
- cs._d = false;
3183
- cs._v = getter();
3540
+ retrack(() => {
3541
+ cs._v = getter();
3542
+ cs._d = false;
3543
+ threw = false;
3544
+ }, markDirty);
3184
3545
  } finally {
3185
3546
  evaluating = false;
3547
+ if (threw) cs._d = true;
3186
3548
  }
3187
3549
  }
3188
3550
  return cs._v;
@@ -3191,13 +3553,17 @@ function derived(getter, options) {
3191
3553
  if (cs._d) {
3192
3554
  const oldValue = cs._v;
3193
3555
  evaluating = true;
3556
+ let threw = true;
3194
3557
  try {
3195
- track(() => {
3558
+ retrack(() => {
3559
+ const next = getter();
3560
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
3196
3561
  cs._d = false;
3197
- cs._v = getter();
3562
+ threw = false;
3198
3563
  }, markDirty);
3199
3564
  } finally {
3200
3565
  evaluating = false;
3566
+ if (threw) cs._d = true;
3201
3567
  }
3202
3568
  if (hook && oldValue !== cs._v) {
3203
3569
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
@@ -3259,7 +3625,7 @@ function store(initialState) {
3259
3625
  },
3260
3626
  set() {
3261
3627
  throw new Error(
3262
- "[Sibu] store: Direct mutation is not allowed. Use actions.setState() to update store properties."
3628
+ "[SibuJS store] Direct mutation is not allowed. Use actions.setState() to update store properties."
3263
3629
  );
3264
3630
  }
3265
3631
  });
@@ -3273,16 +3639,20 @@ function store(initialState) {
3273
3639
  const setState = (patch) => {
3274
3640
  const current = getSnapshot();
3275
3641
  const nextState = typeof patch === "function" ? patch(current) : patch;
3276
- Object.entries(nextState).forEach(([key, value]) => {
3277
- if (key in signals) {
3278
- signals[key][1](value);
3279
- }
3642
+ batch(() => {
3643
+ Object.entries(nextState).forEach(([key, value]) => {
3644
+ if (key in signals) {
3645
+ signals[key][1](value);
3646
+ }
3647
+ });
3280
3648
  });
3281
3649
  };
3282
3650
  const reset = () => {
3283
- Object.keys(initialState).forEach((key) => {
3284
- const setter = signals[key][1];
3285
- setter(initialState[key]);
3651
+ batch(() => {
3652
+ Object.keys(initialState).forEach((key) => {
3653
+ const setter = signals[key][1];
3654
+ setter(initialState[key]);
3655
+ });
3286
3656
  });
3287
3657
  };
3288
3658
  const subscribe = (callback) => {
@@ -3416,7 +3786,8 @@ function reactiveArray(initial = []) {
3416
3786
  function get() {
3417
3787
  recordDependency(signal2);
3418
3788
  if (snapshot === null) {
3419
- snapshot = Object.freeze([...data2]);
3789
+ const copy = data2.slice();
3790
+ snapshot = Object.freeze(copy);
3420
3791
  }
3421
3792
  return snapshot;
3422
3793
  }
@@ -3612,26 +3983,34 @@ function asyncDerived(factory, initial) {
3612
3983
  effect(() => {
3613
3984
  tick();
3614
3985
  const currentRun = ++runId;
3615
- setLoading(true);
3616
- setError(null);
3986
+ batch(() => {
3987
+ setLoading(true);
3988
+ setError(null);
3989
+ });
3617
3990
  let promise;
3618
3991
  try {
3619
3992
  promise = factory();
3620
3993
  } catch (err) {
3621
- setError(err);
3622
- setLoading(false);
3994
+ batch(() => {
3995
+ setError(err);
3996
+ setLoading(false);
3997
+ });
3623
3998
  return;
3624
3999
  }
3625
4000
  promise.then(
3626
4001
  (result) => {
3627
4002
  if (currentRun !== runId) return;
3628
- setValue(result);
3629
- setLoading(false);
4003
+ batch(() => {
4004
+ setValue(result);
4005
+ setLoading(false);
4006
+ });
3630
4007
  },
3631
4008
  (err) => {
3632
4009
  if (currentRun !== runId) return;
3633
- setError(err);
3634
- setLoading(false);
4010
+ batch(() => {
4011
+ setError(err);
4012
+ setLoading(false);
4013
+ });
3635
4014
  }
3636
4015
  );
3637
4016
  });
@@ -3658,26 +4037,186 @@ function runMountCallback(callback, hookName, element) {
3658
4037
  registerDisposer(element, cleanup2);
3659
4038
  }
3660
4039
  }
4040
+ var mountWatchers = /* @__PURE__ */ new WeakMap();
4041
+ var unmountWatchers = /* @__PURE__ */ new WeakMap();
4042
+ var watchedMountElements = /* @__PURE__ */ new Set();
4043
+ var watchedUnmountElements = /* @__PURE__ */ new Set();
4044
+ var sharedObserver = null;
4045
+ var mutationCounter = 0;
4046
+ var FULL_SWEEP_INTERVAL = 256;
4047
+ function fireMount(el) {
4048
+ const cbs = mountWatchers.get(el);
4049
+ if (!cbs) return;
4050
+ mountWatchers.delete(el);
4051
+ watchedMountElements.delete(el);
4052
+ for (const cb of cbs) {
4053
+ try {
4054
+ cb();
4055
+ } catch {
4056
+ }
4057
+ }
4058
+ }
4059
+ function fireUnmount(el) {
4060
+ const cbs = unmountWatchers.get(el);
4061
+ if (!cbs) return;
4062
+ queueMicrotask(() => {
4063
+ if (el.isConnected) return;
4064
+ const stillCbs = unmountWatchers.get(el);
4065
+ if (!stillCbs) return;
4066
+ unmountWatchers.delete(el);
4067
+ watchedUnmountElements.delete(el);
4068
+ for (const cb of stillCbs) {
4069
+ try {
4070
+ cb();
4071
+ } catch {
4072
+ }
4073
+ }
4074
+ });
4075
+ }
4076
+ function visitAddedNode(node) {
4077
+ if (watchedMountElements.size === 0) return;
4078
+ if (node.nodeType !== 1) return;
4079
+ const el = node;
4080
+ if (watchedMountElements.has(el) && el.isConnected) {
4081
+ fireMount(el);
4082
+ }
4083
+ if (el.firstElementChild) {
4084
+ for (const watched of Array.from(watchedMountElements)) {
4085
+ if (watched !== el && watched.isConnected && el.contains(watched)) {
4086
+ fireMount(watched);
4087
+ }
4088
+ }
4089
+ }
4090
+ }
4091
+ function visitRemovedNode(node) {
4092
+ if (watchedUnmountElements.size === 0) return;
4093
+ if (node.nodeType !== 1) return;
4094
+ const el = node;
4095
+ if (watchedUnmountElements.has(el) && !el.isConnected) {
4096
+ fireUnmount(el);
4097
+ }
4098
+ if (el.firstElementChild) {
4099
+ for (const watched of Array.from(watchedUnmountElements)) {
4100
+ if (watched !== el && !watched.isConnected && el.contains(watched)) {
4101
+ fireUnmount(watched);
4102
+ }
4103
+ }
4104
+ }
4105
+ }
4106
+ function fullSweep() {
4107
+ if (watchedMountElements.size > 0) {
4108
+ for (const el of Array.from(watchedMountElements)) {
4109
+ if (el.isConnected) fireMount(el);
4110
+ }
4111
+ }
4112
+ if (watchedUnmountElements.size > 0) {
4113
+ for (const el of Array.from(watchedUnmountElements)) {
4114
+ if (!el.isConnected) fireUnmount(el);
4115
+ }
4116
+ }
4117
+ }
4118
+ function ensureObserver() {
4119
+ if (sharedObserver || typeof document === "undefined") return;
4120
+ sharedObserver = new MutationObserver((mutations) => {
4121
+ for (const m of mutations) {
4122
+ if (m.type !== "childList") continue;
4123
+ if (m.addedNodes.length > 0) {
4124
+ for (let i2 = 0; i2 < m.addedNodes.length; i2++) {
4125
+ visitAddedNode(m.addedNodes[i2]);
4126
+ }
4127
+ }
4128
+ if (m.removedNodes.length > 0) {
4129
+ for (let i2 = 0; i2 < m.removedNodes.length; i2++) {
4130
+ visitRemovedNode(m.removedNodes[i2]);
4131
+ }
4132
+ }
4133
+ }
4134
+ mutationCounter += mutations.length;
4135
+ if (mutationCounter >= FULL_SWEEP_INTERVAL) {
4136
+ mutationCounter = 0;
4137
+ fullSweep();
4138
+ }
4139
+ maybeDisconnectObserver();
4140
+ });
4141
+ sharedObserver.observe(document.body, { childList: true, subtree: true });
4142
+ }
4143
+ function maybeDisconnectObserver() {
4144
+ if (!sharedObserver) return;
4145
+ if (watchedMountElements.size === 0 && watchedUnmountElements.size === 0) {
4146
+ sharedObserver.disconnect();
4147
+ sharedObserver = null;
4148
+ mutationCounter = 0;
4149
+ }
4150
+ }
4151
+ function registerMountWatcher(element, cb) {
4152
+ let list = mountWatchers.get(element);
4153
+ if (!list) {
4154
+ list = [];
4155
+ mountWatchers.set(element, list);
4156
+ }
4157
+ list.push(cb);
4158
+ watchedMountElements.add(element);
4159
+ ensureObserver();
4160
+ return () => {
4161
+ const cbs = mountWatchers.get(element);
4162
+ if (cbs) {
4163
+ const idx = cbs.indexOf(cb);
4164
+ if (idx !== -1) cbs.splice(idx, 1);
4165
+ if (cbs.length === 0) {
4166
+ mountWatchers.delete(element);
4167
+ watchedMountElements.delete(element);
4168
+ }
4169
+ }
4170
+ maybeDisconnectObserver();
4171
+ };
4172
+ }
4173
+ function registerUnmountWatcher(element, cb) {
4174
+ let list = unmountWatchers.get(element);
4175
+ if (!list) {
4176
+ list = [];
4177
+ unmountWatchers.set(element, list);
4178
+ }
4179
+ list.push(cb);
4180
+ watchedUnmountElements.add(element);
4181
+ ensureObserver();
4182
+ return () => {
4183
+ const cbs = unmountWatchers.get(element);
4184
+ if (cbs) {
4185
+ const idx = cbs.indexOf(cb);
4186
+ if (idx !== -1) cbs.splice(idx, 1);
4187
+ if (cbs.length === 0) {
4188
+ unmountWatchers.delete(element);
4189
+ watchedUnmountElements.delete(element);
4190
+ }
4191
+ }
4192
+ maybeDisconnectObserver();
4193
+ };
4194
+ }
3661
4195
  function onMount(callback, element) {
3662
4196
  if (typeof document === "undefined") return;
3663
4197
  if (element) {
4198
+ let disposed = false;
4199
+ registerDisposer(element, () => {
4200
+ disposed = true;
4201
+ });
3664
4202
  if (element.isConnected) {
3665
- queueMicrotask(() => runMountCallback(callback, "onMount", element));
4203
+ queueMicrotask(() => {
4204
+ if (disposed) return;
4205
+ runMountCallback(callback, "onMount", element);
4206
+ });
3666
4207
  return;
3667
4208
  }
3668
- const observer = new MutationObserver(() => {
3669
- if (element.isConnected) {
3670
- observer.disconnect();
3671
- runMountCallback(callback, "onMount", element);
3672
- }
3673
- });
3674
- registerDisposer(element, () => observer.disconnect());
3675
4209
  queueMicrotask(() => {
4210
+ if (disposed) return;
3676
4211
  if (element.isConnected) {
3677
4212
  runMountCallback(callback, "onMount", element);
3678
- } else {
3679
- observer.observe(document.body, { childList: true, subtree: true });
4213
+ return;
3680
4214
  }
4215
+ const unregister = registerMountWatcher(element, () => {
4216
+ if (disposed) return;
4217
+ runMountCallback(callback, "onMount", element);
4218
+ });
4219
+ registerDisposer(element, unregister);
3681
4220
  });
3682
4221
  } else {
3683
4222
  queueMicrotask(() => {
@@ -3686,22 +4225,24 @@ function onMount(callback, element) {
3686
4225
  }
3687
4226
  }
3688
4227
  function onUnmount(callback, element) {
3689
- registerDisposer(element, () => safeCall(callback, "onUnmount"));
3690
- const startObserving = () => {
3691
- const observer = new MutationObserver(() => {
3692
- if (!element.isConnected) {
3693
- observer.disconnect();
3694
- safeCall(callback, "onUnmount");
3695
- }
3696
- });
3697
- observer.observe(document.body, { childList: true, subtree: true });
3698
- registerDisposer(element, () => observer.disconnect());
4228
+ if (typeof document === "undefined") return;
4229
+ let fired = false;
4230
+ const fireOnce = () => {
4231
+ if (fired) return;
4232
+ fired = true;
4233
+ safeCall(callback, "onUnmount");
4234
+ };
4235
+ registerDisposer(element, fireOnce);
4236
+ const startWatching = () => {
4237
+ if (fired) return;
4238
+ const unregister = registerUnmountWatcher(element, fireOnce);
4239
+ registerDisposer(element, unregister);
3699
4240
  };
3700
4241
  if (element.isConnected) {
3701
- startObserving();
4242
+ startWatching();
3702
4243
  } else {
3703
4244
  onMount(() => {
3704
- startObserving();
4245
+ startWatching();
3705
4246
  return void 0;
3706
4247
  }, element);
3707
4248
  }
@@ -3713,9 +4254,11 @@ function onCleanup(callback, element) {
3713
4254
  // src/core/rendering/context.ts
3714
4255
  function context(defaultValue) {
3715
4256
  const [getValue, setValue] = signal(defaultValue);
3716
- return {
4257
+ const ctx = {
3717
4258
  provide(value) {
4259
+ const previous = getValue();
3718
4260
  setValue(value);
4261
+ return () => setValue(previous);
3719
4262
  },
3720
4263
  use() {
3721
4264
  return getValue;
@@ -3725,8 +4268,18 @@ function context(defaultValue) {
3725
4268
  },
3726
4269
  set(value) {
3727
4270
  setValue(value);
4271
+ },
4272
+ withContext(value, fn) {
4273
+ const previous = getValue();
4274
+ setValue(value);
4275
+ try {
4276
+ return fn();
4277
+ } finally {
4278
+ setValue(previous);
4279
+ }
3728
4280
  }
3729
4281
  };
4282
+ return ctx;
3730
4283
  }
3731
4284
 
3732
4285
  // src/core/strict.ts
@@ -3737,7 +4290,7 @@ function strict(fn) {
3737
4290
  try {
3738
4291
  fn();
3739
4292
  } catch (err) {
3740
- console.warn("[Sibu strict] second run threw:", err);
4293
+ console.warn("[SibuJS strict] second run threw:", err);
3741
4294
  }
3742
4295
  });
3743
4296
  }
@@ -3753,7 +4306,7 @@ function strictEffect(fn) {
3753
4306
  try {
3754
4307
  secondTeardown = effect(fn);
3755
4308
  } catch (err) {
3756
- console.warn("[Sibu strictEffect] second run threw:", err);
4309
+ console.warn("[SibuJS strictEffect] second run threw:", err);
3757
4310
  }
3758
4311
  });
3759
4312
  return () => {
@@ -3779,9 +4332,11 @@ function nextTick() {
3779
4332
  function defer(getter) {
3780
4333
  const [value, setValue] = signal(getter());
3781
4334
  let pending = false;
4335
+ let disposed = false;
3782
4336
  let latest = value();
3783
4337
  const flush = () => {
3784
4338
  pending = false;
4339
+ if (disposed) return;
3785
4340
  setValue(latest);
3786
4341
  };
3787
4342
  const schedule = () => {
@@ -3795,11 +4350,17 @@ function defer(getter) {
3795
4350
  }
3796
4351
  });
3797
4352
  };
3798
- track(() => {
4353
+ const teardown = track(() => {
3799
4354
  latest = getter();
3800
4355
  schedule();
3801
4356
  });
3802
- return value;
4357
+ const accessor = (() => value());
4358
+ accessor.dispose = () => {
4359
+ if (disposed) return;
4360
+ disposed = true;
4361
+ teardown();
4362
+ };
4363
+ return accessor;
3803
4364
  }
3804
4365
  var IDLE_FALLBACK_MS = 16;
3805
4366
  function scheduleIdle(fn) {
@@ -3840,6 +4401,32 @@ function transition() {
3840
4401
  }
3841
4402
 
3842
4403
  // src/core/rendering/lazy.ts
4404
+ var PENDING_ERROR = "__sibuPendingError";
4405
+ function dispatchPropagate(node, error) {
4406
+ const fire = () => {
4407
+ try {
4408
+ if (!node.parentNode) return false;
4409
+ node.dispatchEvent(new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error } }));
4410
+ return true;
4411
+ } catch {
4412
+ return false;
4413
+ }
4414
+ };
4415
+ if (node.parentNode && fire()) return;
4416
+ queueMicrotask(() => {
4417
+ if (fire()) return;
4418
+ node[PENDING_ERROR] = error;
4419
+ });
4420
+ }
4421
+ function takePendingError(node) {
4422
+ const rec = node;
4423
+ const err = rec[PENDING_ERROR];
4424
+ if (err instanceof Error) {
4425
+ delete rec[PENDING_ERROR];
4426
+ return err;
4427
+ }
4428
+ return void 0;
4429
+ }
3843
4430
  function lazy(importFn) {
3844
4431
  let cached = null;
3845
4432
  return function LazyComponent() {
@@ -3856,14 +4443,14 @@ function lazy(importFn) {
3856
4443
  }).catch((err) => {
3857
4444
  if (disposed) return;
3858
4445
  const errorObj = err instanceof Error ? err : new Error(String(err));
3859
- container.replaceChildren(div("sibu-lazy-error", `Failed to load component: ${errorObj.message}`));
4446
+ devWarn(`[SibuJS] lazy() failed to load component: ${errorObj.message}`);
4447
+ container.replaceChildren(div({ class: "sibu-lazy-error" }, `Failed to load component: ${errorObj.message}`));
4448
+ dispatchPropagate(container, errorObj);
3860
4449
  });
3861
4450
  container.appendChild(span("sibu-lazy-loading", "Loading..."));
3862
- const origRemove = container.remove.bind(container);
3863
- container.remove = () => {
4451
+ registerDisposer(container, () => {
3864
4452
  disposed = true;
3865
- origRemove();
3866
- };
4453
+ });
3867
4454
  return container;
3868
4455
  };
3869
4456
  }
@@ -3871,32 +4458,55 @@ function Suspense({ nodes, fallback }) {
3871
4458
  const container = div({ class: "sibu-suspense" });
3872
4459
  const fallbackEl = fallback();
3873
4460
  container.appendChild(fallbackEl);
4461
+ let suspenseDisposed = false;
4462
+ let observer = null;
4463
+ registerDisposer(container, () => {
4464
+ suspenseDisposed = true;
4465
+ if (observer) {
4466
+ observer.disconnect();
4467
+ observer = null;
4468
+ }
4469
+ });
3874
4470
  queueMicrotask(() => {
4471
+ if (suspenseDisposed) return;
3875
4472
  try {
3876
4473
  const childEl = nodes();
3877
4474
  if (childEl.classList.contains("sibu-lazy")) {
3878
- const observer = new MutationObserver(() => {
4475
+ if (!childEl.querySelector(".sibu-lazy-loading")) {
4476
+ container.replaceChildren(childEl);
4477
+ return;
4478
+ }
4479
+ observer = new MutationObserver(() => {
4480
+ if (suspenseDisposed) return;
3879
4481
  const loading = childEl.querySelector(".sibu-lazy-loading");
3880
4482
  if (!loading) {
3881
- observer.disconnect();
4483
+ observer?.disconnect();
4484
+ observer = null;
3882
4485
  container.replaceChildren(childEl);
3883
4486
  }
3884
4487
  });
3885
4488
  observer.observe(childEl, { childList: true, subtree: true });
3886
- if (!childEl.querySelector(".sibu-lazy-loading")) {
3887
- container.replaceChildren(childEl);
3888
- }
3889
4489
  } else {
3890
4490
  container.replaceChildren(childEl);
3891
4491
  }
3892
- } catch {
4492
+ } catch (err) {
4493
+ const errorObj = err instanceof Error ? err : new Error(String(err));
4494
+ devWarn(`[SibuJS] Suspense nodes() threw: ${errorObj.message}`);
4495
+ dispatchPropagate(container, errorObj);
3893
4496
  }
3894
4497
  });
3895
4498
  return container;
3896
4499
  }
3897
4500
 
4501
+ // src/platform/ssr.ts
4502
+ var _isDev10 = isDev();
4503
+ function trustHTML(html3) {
4504
+ return html3;
4505
+ }
4506
+ var DEFAULT_MAX_SSR_BYTES = 1024 * 1024;
4507
+
3898
4508
  // src/components/ErrorDisplay.ts
3899
- var _isDev8 = isDev();
4509
+ var _isDev11 = isDev();
3900
4510
  var STYLES = `
3901
4511
  .sibu-error-display {
3902
4512
  border: 1px solid var(--sibu-err-border, #e5484d);
@@ -3982,20 +4592,21 @@ var STYLES = `
3982
4592
  font-weight: 600;
3983
4593
  }
3984
4594
  .sibu-error-display .sibu-err-copy-btn {
3985
- background: transparent;
3986
- border: 1px solid #3a3a4e;
4595
+ background: rgba(0, 0, 0, 0.22);
4596
+ border: 1px solid rgba(255, 255, 255, 0.15);
3987
4597
  border-radius: 4px;
3988
- color: #a0a3b8;
4598
+ color: rgba(255, 255, 255, 0.85);
3989
4599
  cursor: pointer;
3990
4600
  padding: 2px 10px;
3991
- font-size: 0.95em;
4601
+ font-size: 0.78em;
3992
4602
  font-family: inherit;
3993
4603
  transition: all 0.12s ease;
4604
+ flex-shrink: 0;
3994
4605
  }
3995
4606
  .sibu-error-display .sibu-err-copy-btn:hover {
3996
- background: #2a2a3e;
3997
- color: #e5e7eb;
3998
- border-color: #4a4a5e;
4607
+ background: rgba(0, 0, 0, 0.35);
4608
+ color: white;
4609
+ border-color: rgba(255, 255, 255, 0.3);
3999
4610
  }
4000
4611
 
4001
4612
  .sibu-error-display .sibu-err-stack {
@@ -4120,21 +4731,25 @@ function normalizeError(err) {
4120
4731
  cause: null
4121
4732
  };
4122
4733
  }
4123
- function buildCopyText(err, meta2) {
4734
+ function buildCopyText(err, meta2, headline) {
4124
4735
  const lines = [];
4736
+ lines.push(headline);
4125
4737
  lines.push(`[${err.code}] ${err.message}`);
4126
4738
  if (err.stack) {
4127
4739
  lines.push("");
4740
+ lines.push("Stack Trace:");
4128
4741
  lines.push(err.stack);
4129
4742
  }
4130
- if (err.cause) {
4743
+ let cause = err.cause;
4744
+ while (cause) {
4131
4745
  lines.push("");
4132
4746
  lines.push("Caused by:");
4133
- lines.push(` [${err.cause.code}] ${err.cause.message}`);
4134
- if (err.cause.stack) {
4135
- const indented = err.cause.stack.split("\n").map((l) => ` ${l}`).join("\n");
4747
+ lines.push(` [${cause.code}] ${cause.message}`);
4748
+ if (cause.stack) {
4749
+ const indented = cause.stack.split("\n").map((l) => ` ${l}`).join("\n");
4136
4750
  lines.push(indented);
4137
4751
  }
4752
+ cause = cause.cause;
4138
4753
  }
4139
4754
  if (meta2 && Object.keys(meta2).length > 0) {
4140
4755
  lines.push("");
@@ -4144,9 +4759,13 @@ function buildCopyText(err, meta2) {
4144
4759
  }
4145
4760
  }
4146
4761
  lines.push("");
4147
- lines.push(`At: ${(/* @__PURE__ */ new Date()).toISOString()}`);
4762
+ lines.push("Environment:");
4763
+ lines.push(` Timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}`);
4764
+ if (typeof location !== "undefined") {
4765
+ lines.push(` URL: ${location.href}`);
4766
+ }
4148
4767
  if (typeof navigator !== "undefined" && navigator.userAgent) {
4149
- lines.push(`UA: ${navigator.userAgent}`);
4768
+ lines.push(` User Agent: ${navigator.userAgent}`);
4150
4769
  }
4151
4770
  return lines.join("\n");
4152
4771
  }
@@ -4198,7 +4817,7 @@ function ErrorDisplay(props) {
4198
4817
  injectStyles();
4199
4818
  const severity = props.severity ?? "error";
4200
4819
  const normalized = normalizeError(props.error);
4201
- const showDetails = props.alwaysShowDetails ?? _isDev8;
4820
+ const showDetails = props.alwaysShowDetails ?? _isDev11;
4202
4821
  const headline = props.title ?? normalized.message;
4203
4822
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
4204
4823
  const [copyLabel, setCopyLabel] = signal("Copy");
@@ -4207,7 +4826,7 @@ function ErrorDisplay(props) {
4207
4826
  nodes: () => copyLabel(),
4208
4827
  on: {
4209
4828
  click: () => {
4210
- const text2 = buildCopyText(normalized, props.metadata);
4829
+ const text2 = buildCopyText(normalized, props.metadata, headline);
4211
4830
  if (typeof navigator !== "undefined" && navigator.clipboard) {
4212
4831
  navigator.clipboard.writeText(text2).then(
4213
4832
  () => {
@@ -4228,6 +4847,7 @@ function ErrorDisplay(props) {
4228
4847
  nodes: [
4229
4848
  code({ class: "sibu-err-icon", nodes: normalized.code }),
4230
4849
  h3({ class: "sibu-err-title", nodes: headline }),
4850
+ copyBtn,
4231
4851
  span({ class: "sibu-err-timestamp", nodes: timestamp })
4232
4852
  ]
4233
4853
  });
@@ -4239,25 +4859,12 @@ function ErrorDisplay(props) {
4239
4859
  nodes: [
4240
4860
  div({
4241
4861
  class: "sibu-err-section-head",
4242
- nodes: [span({ nodes: "Stack Trace" }), copyBtn]
4862
+ nodes: [span({ nodes: "Stack Trace" })]
4243
4863
  }),
4244
4864
  renderFrames(normalized.frames)
4245
4865
  ]
4246
4866
  })
4247
4867
  );
4248
- } else if (showDetails) {
4249
- bodyChildren.push(
4250
- div({
4251
- class: "sibu-err-section",
4252
- nodes: [
4253
- div({
4254
- class: "sibu-err-section-head",
4255
- nodes: [span({ nodes: "Details" }), copyBtn]
4256
- }),
4257
- div({ class: "sibu-err-stack", nodes: "(no stack available)" })
4258
- ]
4259
- })
4260
- );
4261
4868
  }
4262
4869
  if (showDetails) {
4263
4870
  bodyChildren.push(...renderCauseChain(normalized.cause));
@@ -4273,37 +4880,6 @@ function ErrorDisplay(props) {
4273
4880
  })
4274
4881
  );
4275
4882
  }
4276
- if (showDetails && typeof navigator !== "undefined" && navigator.userAgent) {
4277
- bodyChildren.push(
4278
- div({
4279
- class: "sibu-err-section",
4280
- nodes: [
4281
- div({ class: "sibu-err-section-head", nodes: [span({ nodes: "Environment" })] }),
4282
- div({
4283
- class: "sibu-err-meta",
4284
- nodes: (() => {
4285
- const dl2 = document.createElement("dl");
4286
- dl2.className = "sibu-err-meta";
4287
- const entries = [
4288
- ["User Agent", navigator.userAgent],
4289
- ["URL", typeof location !== "undefined" ? location.href : "(n/a)"],
4290
- ["Timestamp", (/* @__PURE__ */ new Date()).toISOString()]
4291
- ];
4292
- for (const [k, v] of entries) {
4293
- const dt2 = document.createElement("dt");
4294
- dt2.textContent = k;
4295
- const dd2 = document.createElement("dd");
4296
- dd2.textContent = v;
4297
- dl2.appendChild(dt2);
4298
- dl2.appendChild(dd2);
4299
- }
4300
- return dl2;
4301
- })()
4302
- })
4303
- ]
4304
- })
4305
- );
4306
- }
4307
4883
  const actionButtons = [];
4308
4884
  if (props.onRetry) {
4309
4885
  actionButtons.push(
@@ -4498,6 +5074,7 @@ function injectStyles2() {
4498
5074
  stylesInjected = true;
4499
5075
  }
4500
5076
  }
5077
+ var FALLBACK_CACHE_MAX = 50;
4501
5078
  var fallbackCache = /* @__PURE__ */ new WeakMap();
4502
5079
  function getMemoizedFallback(fallbackFn, error, retry) {
4503
5080
  let cache2 = fallbackCache.get(fallbackFn);
@@ -4506,27 +5083,42 @@ function getMemoizedFallback(fallbackFn, error, retry) {
4506
5083
  fallbackCache.set(fallbackFn, cache2);
4507
5084
  }
4508
5085
  const key = error.message;
4509
- if (!cache2.has(key)) {
4510
- cache2.set(key, fallbackFn(error, retry));
5086
+ let factory = cache2.get(key);
5087
+ if (factory) {
5088
+ cache2.delete(key);
5089
+ cache2.set(key, factory);
5090
+ } else {
5091
+ factory = () => fallbackFn(error, retry);
5092
+ cache2.set(key, factory);
5093
+ if (cache2.size > FALLBACK_CACHE_MAX) {
5094
+ const oldestKey = cache2.keys().next().value;
5095
+ if (oldestKey !== void 0) cache2.delete(oldestKey);
5096
+ }
4511
5097
  }
4512
- return cache2.get(key);
5098
+ return factory();
4513
5099
  }
4514
5100
  function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
4515
5101
  injectStyles2();
4516
5102
  const [error, setError] = signal(null);
4517
5103
  const retry = () => {
4518
5104
  if (fallback) {
4519
- fallbackCache.delete(fallback);
5105
+ const cur = error();
5106
+ const inner = fallbackCache.get(fallback);
5107
+ if (cur && inner) inner.delete(cur.message);
4520
5108
  }
4521
5109
  setError(null);
4522
5110
  };
5111
+ let resetKeysTeardown = null;
4523
5112
  if (resetKeys && resetKeys.length > 0) {
4524
5113
  let initialized = false;
4525
- effect(() => {
5114
+ resetKeysTeardown = effect(() => {
4526
5115
  for (const k of resetKeys) {
4527
5116
  try {
4528
5117
  k();
4529
- } catch {
5118
+ } catch (err) {
5119
+ if (typeof console !== "undefined") {
5120
+ console.warn("[SibuJS ErrorBoundary] resetKeys getter threw:", err);
5121
+ }
4530
5122
  }
4531
5123
  }
4532
5124
  if (!initialized) {
@@ -4539,7 +5131,15 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
4539
5131
  const handleError = (e) => {
4540
5132
  const errorObj = e instanceof Error ? e : new Error(String(e));
4541
5133
  setError(errorObj);
4542
- onError?.(errorObj);
5134
+ if (onError) {
5135
+ try {
5136
+ onError(errorObj);
5137
+ } catch (cbErr) {
5138
+ if (typeof console !== "undefined") {
5139
+ console.error("[SibuJS ErrorBoundary] onError callback threw:", cbErr);
5140
+ }
5141
+ }
5142
+ }
4543
5143
  return errorObj;
4544
5144
  };
4545
5145
  const defaultFallback = (err, retryFn) => {
@@ -4591,7 +5191,7 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
4591
5191
  }
4592
5192
  }
4593
5193
  });
4594
- container.addEventListener("sibu:error-propagate", (e) => {
5194
+ const propagateListener = (e) => {
4595
5195
  if (error()) return;
4596
5196
  e.stopPropagation();
4597
5197
  const customEvent = e;
@@ -4599,6 +5199,30 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
4599
5199
  if (propagatedError) {
4600
5200
  handleError(propagatedError);
4601
5201
  }
5202
+ };
5203
+ container.addEventListener("sibu:error-propagate", propagateListener);
5204
+ onMount(() => {
5205
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT);
5206
+ const collected = [];
5207
+ let node = walker.currentNode;
5208
+ while (node) {
5209
+ const pending = takePendingError(node);
5210
+ if (pending) collected.push(pending);
5211
+ node = walker.nextNode();
5212
+ }
5213
+ if (collected.length === 1) {
5214
+ handleError(collected[0]);
5215
+ } else if (collected.length > 1) {
5216
+ const Agg = globalThis.AggregateError;
5217
+ handleError(
5218
+ Agg ? new Agg(collected, `${collected.length} pre-mount errors caught by ErrorBoundary`) : new Error(collected.map((e) => e.message).join("; "))
5219
+ );
5220
+ }
5221
+ return void 0;
5222
+ }, container);
5223
+ registerDisposer(container, () => {
5224
+ if (resetKeysTeardown) resetKeysTeardown();
5225
+ container.removeEventListener("sibu:error-propagate", propagateListener);
4602
5226
  });
4603
5227
  return container;
4604
5228
  }