sibujs 1.4.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 (190) hide show
  1. package/README.md +105 -119
  2. package/dist/browser.cjs +288 -80
  3. package/dist/browser.d.cts +19 -9
  4. package/dist/browser.d.ts +19 -9
  5. package/dist/browser.js +6 -6
  6. package/dist/build.cjs +1019 -313
  7. package/dist/build.d.cts +1 -1
  8. package/dist/build.d.ts +1 -1
  9. package/dist/build.js +15 -13
  10. package/dist/cdn.global.js +17 -16
  11. package/dist/chunk-2RA7SHDA.js +65 -0
  12. package/dist/chunk-2UPRY23K.js +80 -0
  13. package/dist/chunk-3JHCYHWN.js +125 -0
  14. package/dist/{chunk-ZWKZCBO6.js → chunk-3LR7GLWQ.js} +154 -33
  15. package/dist/{chunk-3AIRKM3B.js → chunk-3NSGB5JN.js} +115 -34
  16. package/dist/{chunk-3ARAQO7B.js → chunk-52YJLLRO.js} +29 -6
  17. package/dist/chunk-54EDRCEF.js +93 -0
  18. package/dist/chunk-7JDB7I65.js +1327 -0
  19. package/dist/{chunk-WZSPOOER.js → chunk-CC65Y57T.js} +8 -5
  20. package/dist/{chunk-23VV7YD3.js → chunk-DFPFITST.js} +25 -30
  21. package/dist/{chunk-WR5D4EGH.js → chunk-GTBNNBJ6.js} +14 -2
  22. package/dist/chunk-HB24TBAF.js +121 -0
  23. package/dist/{chunk-CZUGLNJS.js → chunk-ITX6OO3F.js} +3 -3
  24. package/dist/{chunk-JAKHTMQU.js → chunk-JA6667UN.js} +206 -46
  25. package/dist/{chunk-77L6NL3X.js → chunk-JXMMDLBY.js} +306 -183
  26. package/dist/{chunk-3X2YG6YM.js → chunk-JYD2PWXH.js} +59 -28
  27. package/dist/{chunk-F3FA4F32.js → chunk-KLRMB5ZS.js} +135 -79
  28. package/dist/{chunk-5X6PP2UK.js → chunk-LMLD24FC.js} +2 -2
  29. package/dist/{chunk-M4NLBH4I.js → chunk-LYTCUZ7H.js} +3 -2
  30. package/dist/{chunk-TSOKIX5Z.js → chunk-MIUAXB7K.js} +126 -74
  31. package/dist/{chunk-QWZG56ET.js → chunk-ND2664SF.js} +558 -190
  32. package/dist/{chunk-JCI5M6U6.js → chunk-O2MNQFLP.js} +261 -79
  33. package/dist/{chunk-EWFVA3TJ.js → chunk-R73P76YZ.js} +1 -1
  34. package/dist/{chunk-2BYQDGN3.js → chunk-SAHNHTFC.js} +234 -63
  35. package/dist/chunk-UCS6AMJ7.js +79 -0
  36. package/dist/{chunk-ZD6OAMTH.js → chunk-VLPPXTYG.js} +90 -35
  37. package/dist/{chunk-OUZZEE4S.js → chunk-WOMYAHHI.js} +17 -11
  38. package/dist/{contracts-xo5ckdRP.d.cts → contracts-ey_Qh8ef.d.cts} +7 -8
  39. package/dist/{contracts-xo5ckdRP.d.ts → contracts-ey_Qh8ef.d.ts} +7 -8
  40. package/dist/{customElement-D2DJp_xn.d.cts → customElement-CPfIrbvg.d.cts} +18 -9
  41. package/dist/{customElement-D2DJp_xn.d.ts → customElement-CPfIrbvg.d.ts} +18 -9
  42. package/dist/data.cjs +452 -100
  43. package/dist/data.d.cts +20 -2
  44. package/dist/data.d.ts +20 -2
  45. package/dist/data.js +11 -9
  46. package/dist/devtools.cjs +535 -247
  47. package/dist/devtools.d.cts +1 -1
  48. package/dist/devtools.d.ts +1 -1
  49. package/dist/devtools.js +34 -30
  50. package/dist/ecosystem.cjs +499 -143
  51. package/dist/ecosystem.d.cts +13 -11
  52. package/dist/ecosystem.d.ts +13 -11
  53. package/dist/ecosystem.js +12 -11
  54. package/dist/extras.cjs +3639 -1629
  55. package/dist/extras.d.cts +11 -11
  56. package/dist/extras.d.ts +11 -11
  57. package/dist/extras.js +58 -45
  58. package/dist/index.cjs +1023 -313
  59. package/dist/index.d.cts +128 -55
  60. package/dist/index.d.ts +128 -55
  61. package/dist/index.js +28 -16
  62. package/dist/{introspect-BumjnBKr.d.cts → introspect-BWNjNw64.d.cts} +22 -2
  63. package/dist/{introspect-CZrlcaYy.d.ts → introspect-cY2pg9pW.d.ts} +22 -2
  64. package/dist/motion.cjs +90 -36
  65. package/dist/motion.d.cts +1 -1
  66. package/dist/motion.d.ts +1 -1
  67. package/dist/motion.js +4 -4
  68. package/dist/patterns.cjs +414 -81
  69. package/dist/patterns.d.cts +53 -20
  70. package/dist/patterns.d.ts +53 -20
  71. package/dist/patterns.js +7 -7
  72. package/dist/performance.cjs +364 -108
  73. package/dist/performance.d.cts +29 -17
  74. package/dist/performance.d.ts +29 -17
  75. package/dist/performance.js +13 -6
  76. package/dist/plugin-D30wlGW5.d.cts +71 -0
  77. package/dist/plugin-D30wlGW5.d.ts +71 -0
  78. package/dist/plugins.cjs +652 -271
  79. package/dist/plugins.d.cts +13 -6
  80. package/dist/plugins.d.ts +13 -6
  81. package/dist/plugins.js +116 -50
  82. package/dist/{ssr-Do_SiVoL.d.cts → ssr-CrVNy6Pa.d.cts} +9 -15
  83. package/dist/{ssr-Do_SiVoL.d.ts → ssr-CrVNy6Pa.d.ts} +9 -15
  84. package/dist/{ssr-4PBXAOO3.js → ssr-FXD2PPMC.js} +4 -3
  85. package/dist/ssr.cjs +648 -219
  86. package/dist/ssr.d.cts +27 -7
  87. package/dist/ssr.d.ts +27 -7
  88. package/dist/ssr.js +12 -11
  89. package/dist/{tagFactory-DaJ0YWX6.d.ts → tagFactory-S17H2qxu.d.cts} +9 -1
  90. package/dist/{tagFactory-DaJ0YWX6.d.cts → tagFactory-S17H2qxu.d.ts} +9 -1
  91. package/dist/testing.cjs +252 -63
  92. package/dist/testing.d.cts +17 -4
  93. package/dist/testing.d.ts +17 -4
  94. package/dist/testing.js +100 -44
  95. package/dist/ui.cjs +576 -168
  96. package/dist/ui.d.cts +13 -16
  97. package/dist/ui.d.ts +13 -16
  98. package/dist/ui.js +20 -17
  99. package/dist/widgets.cjs +1001 -93
  100. package/dist/widgets.d.cts +104 -2
  101. package/dist/widgets.d.ts +104 -2
  102. package/dist/widgets.js +9 -7
  103. package/package.json +8 -2
  104. package/dist/chunk-32DY64NT.js +0 -282
  105. package/dist/chunk-3CRQALYP.js +0 -877
  106. package/dist/chunk-4EI4AG32.js +0 -482
  107. package/dist/chunk-4MYMUBRS.js +0 -21
  108. package/dist/chunk-6HLLIF3K.js +0 -398
  109. package/dist/chunk-6LSNVCS2.js +0 -937
  110. package/dist/chunk-6SA3QQES.js +0 -61
  111. package/dist/chunk-7BF6TK55.js +0 -1097
  112. package/dist/chunk-7TQKR4PP.js +0 -294
  113. package/dist/chunk-7V26P53V.js +0 -712
  114. package/dist/chunk-AZ3ISID5.js +0 -298
  115. package/dist/chunk-B7SWRFUT.js +0 -332
  116. package/dist/chunk-BGN5ZMP4.js +0 -26
  117. package/dist/chunk-BTU3TJDS.js +0 -365
  118. package/dist/chunk-BW3WT46K.js +0 -937
  119. package/dist/chunk-C6KFWOFV.js +0 -616
  120. package/dist/chunk-CHF5OHIA.js +0 -61
  121. package/dist/chunk-CHJ27IGK.js +0 -26
  122. package/dist/chunk-CMBFNA7L.js +0 -27
  123. package/dist/chunk-DAHRH4ON.js +0 -331
  124. package/dist/chunk-DKOHBI74.js +0 -924
  125. package/dist/chunk-DTCOOBMX.js +0 -725
  126. package/dist/chunk-EBGIRKQY.js +0 -616
  127. package/dist/chunk-EUZND3CB.js +0 -27
  128. package/dist/chunk-EVCZO745.js +0 -365
  129. package/dist/chunk-FGOEVHY3.js +0 -60
  130. package/dist/chunk-G3BOQPVO.js +0 -365
  131. package/dist/chunk-GCOK2LC3.js +0 -282
  132. package/dist/chunk-HGMJFBC7.js +0 -654
  133. package/dist/chunk-K5ZUMYVS.js +0 -89
  134. package/dist/chunk-KQPDEVVS.js +0 -398
  135. package/dist/chunk-L6JRBDNS.js +0 -60
  136. package/dist/chunk-LA6KQEDU.js +0 -712
  137. package/dist/chunk-MDVXJWFN.js +0 -304
  138. package/dist/chunk-MEZVEBPN.js +0 -2008
  139. package/dist/chunk-MK4ERFYL.js +0 -2249
  140. package/dist/chunk-MLKGABMK.js +0 -9
  141. package/dist/chunk-MQ5GOYPH.js +0 -2249
  142. package/dist/chunk-N6IZB6KJ.js +0 -567
  143. package/dist/chunk-NEKUBFPT.js +0 -60
  144. package/dist/chunk-NHUC2QWH.js +0 -282
  145. package/dist/chunk-NMRUZALC.js +0 -1097
  146. package/dist/chunk-NYVAC6P5.js +0 -37
  147. package/dist/chunk-OF7UZIVB.js +0 -725
  148. package/dist/chunk-P6W3STU4.js +0 -2249
  149. package/dist/chunk-PBHF5WKN.js +0 -616
  150. package/dist/chunk-PTQJDMRT.js +0 -146
  151. package/dist/chunk-PZEGYCF5.js +0 -61
  152. package/dist/chunk-QBMDLBU2.js +0 -975
  153. package/dist/chunk-RQGQSLQK.js +0 -725
  154. package/dist/chunk-SDLZDHKP.js +0 -107
  155. package/dist/chunk-TNQWPPE6.js +0 -37
  156. package/dist/chunk-UHNL42EF.js +0 -2730
  157. package/dist/chunk-UNXCEF6S.js +0 -21
  158. package/dist/chunk-V2XTI523.js +0 -347
  159. package/dist/chunk-VAU366PN.js +0 -2241
  160. package/dist/chunk-VMVDTCXB.js +0 -712
  161. package/dist/chunk-VRW3FULF.js +0 -725
  162. package/dist/chunk-WADYRCO2.js +0 -304
  163. package/dist/chunk-WILQZRO4.js +0 -282
  164. package/dist/chunk-WUHJISPP.js +0 -298
  165. package/dist/chunk-XYU6TZOW.js +0 -182
  166. package/dist/chunk-Y6GP4QGG.js +0 -276
  167. package/dist/chunk-YECR7UIA.js +0 -347
  168. package/dist/chunk-YUTWTI4B.js +0 -654
  169. package/dist/chunk-Z65KYU7I.js +0 -26
  170. package/dist/chunk-Z6POF5YC.js +0 -975
  171. package/dist/chunk-ZBJP6WFL.js +0 -482
  172. package/dist/contracts-DDrwxvJ-.d.cts +0 -245
  173. package/dist/contracts-DDrwxvJ-.d.ts +0 -245
  174. package/dist/contracts-DOrhwbke.d.cts +0 -245
  175. package/dist/contracts-DOrhwbke.d.ts +0 -245
  176. package/dist/customElement-BKQfbSZQ.d.cts +0 -262
  177. package/dist/customElement-BKQfbSZQ.d.ts +0 -262
  178. package/dist/customElement-yz8uyk-0.d.cts +0 -308
  179. package/dist/customElement-yz8uyk-0.d.ts +0 -308
  180. package/dist/introspect-Cb0zgpi2.d.cts +0 -477
  181. package/dist/introspect-Y2xNXGSf.d.ts +0 -477
  182. package/dist/plugin-Bek4RhJY.d.cts +0 -43
  183. package/dist/plugin-Bek4RhJY.d.ts +0 -43
  184. package/dist/ssr-3RXHP5ES.js +0 -38
  185. package/dist/ssr-6GIMY5MX.js +0 -38
  186. package/dist/ssr-BA6sxxUd.d.cts +0 -135
  187. package/dist/ssr-BA6sxxUd.d.ts +0 -135
  188. package/dist/ssr-WKUPVSSK.js +0 -36
  189. package/dist/tagFactory-Dl8QCLga.d.cts +0 -23
  190. package/dist/tagFactory-Dl8QCLga.d.ts +0 -23
package/dist/testing.cjs CHANGED
@@ -60,11 +60,75 @@ __export(testing_exports, {
60
60
  snapshotComponent: () => snapshotComponent,
61
61
  testComponent: () => testComponent,
62
62
  type: () => type,
63
+ unmountAll: () => unmountAll,
63
64
  waitFor: () => waitFor,
64
65
  waitForSignal: () => waitForSignal
65
66
  });
66
67
  module.exports = __toCommonJS(testing_exports);
67
68
 
69
+ // src/core/dev.ts
70
+ function isDev() {
71
+ return typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
72
+ }
73
+ var _isDev = isDev();
74
+ function devAssert(condition, message) {
75
+ if (_isDev && !condition) {
76
+ throw new Error(`[SibuJS] ${message}`);
77
+ }
78
+ }
79
+
80
+ // src/core/rendering/dispose.ts
81
+ var elementDisposers = /* @__PURE__ */ new WeakMap();
82
+ var _isDev2 = isDev();
83
+ var activeBindingCount = 0;
84
+ function dispose(node) {
85
+ const stack = [node];
86
+ const order = [];
87
+ while (stack.length > 0) {
88
+ const current = stack.pop();
89
+ order.push(current);
90
+ const children = Array.from(current.childNodes);
91
+ for (let i = 0; i < children.length; i++) {
92
+ stack.push(children[i]);
93
+ }
94
+ }
95
+ for (let i = order.length - 1; i >= 0; i--) {
96
+ const current = order[i];
97
+ const disposers = elementDisposers.get(current);
98
+ if (disposers) {
99
+ const snapshot = disposers.slice();
100
+ elementDisposers.delete(current);
101
+ if (_isDev2) activeBindingCount -= snapshot.length;
102
+ for (const d of snapshot) {
103
+ try {
104
+ d();
105
+ } catch (err) {
106
+ if (_isDev2 && typeof console !== "undefined") {
107
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
108
+ }
109
+ }
110
+ }
111
+ let extraPasses = 0;
112
+ while (extraPasses++ < 8) {
113
+ const added = elementDisposers.get(current);
114
+ if (!added || added.length === 0) break;
115
+ const moreSnapshot = added.slice();
116
+ elementDisposers.delete(current);
117
+ if (_isDev2) activeBindingCount -= moreSnapshot.length;
118
+ for (const d of moreSnapshot) {
119
+ try {
120
+ d();
121
+ } catch (err) {
122
+ if (_isDev2 && typeof console !== "undefined") {
123
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+
68
132
  // src/testing/a11y.ts
69
133
  var VALID_ARIA_ROLES = /* @__PURE__ */ new Set([
70
134
  "alert",
@@ -1200,8 +1264,9 @@ function createUniversalAdapter() {
1200
1264
  }
1201
1265
 
1202
1266
  // src/testing/e2e.ts
1203
- function createHttpMock(routes = []) {
1267
+ function createHttpMock(routes = [], options = {}) {
1204
1268
  const originalFetch = globalThis.fetch;
1269
+ const hadOriginalFetch = Object.hasOwn(globalThis, "fetch");
1205
1270
  const requestLog = [];
1206
1271
  const mockRoutes = [...routes];
1207
1272
  function matchRoute(url, method) {
@@ -1222,29 +1287,41 @@ function createHttpMock(routes = []) {
1222
1287
  return new Response(JSON.stringify({ error: "Not mocked" }), { status: 404 });
1223
1288
  }
1224
1289
  let mockResponse;
1225
- if (typeof route.response === "function") {
1226
- mockResponse = await route.response({ url, method, body, headers: new Headers(init?.headers) });
1227
- } else {
1228
- mockResponse = route.response;
1290
+ try {
1291
+ if (typeof route.response === "function") {
1292
+ mockResponse = await route.response({ url, method, body, headers: new Headers(init?.headers) });
1293
+ } else {
1294
+ mockResponse = route.response;
1295
+ }
1296
+ if (mockResponse.delay) {
1297
+ await new Promise((r) => setTimeout(r, mockResponse.delay));
1298
+ }
1299
+ return new Response(
1300
+ typeof mockResponse.body === "string" ? mockResponse.body : JSON.stringify(mockResponse.body),
1301
+ {
1302
+ status: mockResponse.status || 200,
1303
+ statusText: mockResponse.statusText || "OK",
1304
+ headers: mockResponse.headers
1305
+ }
1306
+ );
1307
+ } catch (err) {
1308
+ return new Response(JSON.stringify({ error: String(err) }), { status: 500 });
1229
1309
  }
1230
- if (mockResponse.delay) {
1231
- await new Promise((r) => setTimeout(r, mockResponse.delay));
1310
+ };
1311
+ const restore = () => {
1312
+ if (hadOriginalFetch) {
1313
+ globalThis.fetch = originalFetch;
1314
+ } else {
1315
+ delete globalThis.fetch;
1232
1316
  }
1233
- return new Response(typeof mockResponse.body === "string" ? mockResponse.body : JSON.stringify(mockResponse.body), {
1234
- status: mockResponse.status || 200,
1235
- statusText: mockResponse.statusText || "OK",
1236
- headers: mockResponse.headers
1237
- });
1238
1317
  };
1239
- return {
1318
+ const api = {
1240
1319
  /** Install the mock (replace global fetch) */
1241
1320
  install() {
1242
1321
  globalThis.fetch = mockFetch;
1243
1322
  },
1244
1323
  /** Restore original fetch */
1245
- restore() {
1246
- globalThis.fetch = originalFetch;
1247
- },
1324
+ restore,
1248
1325
  /** Add a mock route */
1249
1326
  addRoute(route) {
1250
1327
  mockRoutes.push(route);
@@ -1276,18 +1353,29 @@ function createHttpMock(routes = []) {
1276
1353
  return requestLog.filter((r) => r.url.includes(url) && r.method.toUpperCase() === method.toUpperCase()).length;
1277
1354
  }
1278
1355
  };
1356
+ if (typeof options.afterEach === "function") {
1357
+ options.afterEach(() => api.restore());
1358
+ }
1359
+ return api;
1279
1360
  }
1280
- function createTimerMock() {
1281
- const originalSetTimeout = globalThis.setTimeout;
1282
- const originalSetInterval = globalThis.setInterval;
1283
- const originalClearTimeout = globalThis.clearTimeout;
1284
- const originalClearInterval = globalThis.clearInterval;
1285
- const originalRAF = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : void 0;
1286
- const originalCAF = typeof cancelAnimationFrame !== "undefined" ? cancelAnimationFrame : void 0;
1361
+ function createTimerMock(options = {}) {
1362
+ const g = globalThis;
1363
+ const snapshot = (key) => ({
1364
+ had: Object.hasOwn(globalThis, key),
1365
+ value: g[key]
1366
+ });
1367
+ const saved = {
1368
+ setTimeout: snapshot("setTimeout"),
1369
+ setInterval: snapshot("setInterval"),
1370
+ clearTimeout: snapshot("clearTimeout"),
1371
+ clearInterval: snapshot("clearInterval"),
1372
+ requestAnimationFrame: snapshot("requestAnimationFrame"),
1373
+ cancelAnimationFrame: snapshot("cancelAnimationFrame")
1374
+ };
1287
1375
  let currentTime = 0;
1288
1376
  let nextId = 1;
1289
1377
  const timers = [];
1290
- return {
1378
+ const api = {
1291
1379
  install() {
1292
1380
  currentTime = 0;
1293
1381
  globalThis.setTimeout = (cb, delay = 0) => {
@@ -1319,12 +1407,13 @@ function createTimerMock() {
1319
1407
  };
1320
1408
  },
1321
1409
  restore() {
1322
- globalThis.setTimeout = originalSetTimeout;
1323
- globalThis.setInterval = originalSetInterval;
1324
- globalThis.clearTimeout = originalClearTimeout;
1325
- globalThis.clearInterval = originalClearInterval;
1326
- if (originalRAF) globalThis.requestAnimationFrame = originalRAF;
1327
- if (originalCAF) globalThis.cancelAnimationFrame = originalCAF;
1410
+ for (const [key, snap] of Object.entries(saved)) {
1411
+ if (snap.had) {
1412
+ g[key] = snap.value;
1413
+ } else {
1414
+ delete g[key];
1415
+ }
1416
+ }
1328
1417
  timers.length = 0;
1329
1418
  },
1330
1419
  /** Advance time by a given number of ms, running any timers that fire */
@@ -1370,6 +1459,10 @@ function createTimerMock() {
1370
1459
  return timers.length;
1371
1460
  }
1372
1461
  };
1462
+ if (typeof options.afterEach === "function") {
1463
+ options.afterEach(() => api.restore());
1464
+ }
1465
+ return api;
1373
1466
  }
1374
1467
  function createDOMSnapshot(element) {
1375
1468
  return serializeElement(element, 0);
@@ -1454,19 +1547,8 @@ function testComponent(component, options = {}) {
1454
1547
  };
1455
1548
  }
1456
1549
 
1457
- // src/core/dev.ts
1458
- function isDev() {
1459
- return typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
1460
- }
1461
- var _isDev = isDev();
1462
- function devAssert(condition, message) {
1463
- if (_isDev && !condition) {
1464
- throw new Error(`[Sibu] ${message}`);
1465
- }
1466
- }
1467
-
1468
1550
  // src/reactivity/track.ts
1469
- var _isDev2 = isDev();
1551
+ var _isDev3 = isDev();
1470
1552
  var subscriberStack = new Array(32);
1471
1553
  var stackCapacity = 32;
1472
1554
  var stackTop = -1;
@@ -1498,7 +1580,9 @@ function cleanup(subscriber) {
1498
1580
  if (subs) {
1499
1581
  subs.delete(subscriber);
1500
1582
  if (singleDep.__f === subscriber) {
1501
- singleDep.__f = void 0;
1583
+ singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
1584
+ } else if (subs.size === 1 && singleDep.__f === void 0) {
1585
+ singleDep.__f = subs.values().next().value;
1502
1586
  }
1503
1587
  }
1504
1588
  sub._dep = void 0;
@@ -1511,7 +1595,9 @@ function cleanup(subscriber) {
1511
1595
  if (subs) {
1512
1596
  subs.delete(subscriber);
1513
1597
  if (signal.__f === subscriber) {
1514
- signal.__f = void 0;
1598
+ signal.__f = subs.size === 1 ? subs.values().next().value : void 0;
1599
+ } else if (subs.size === 1 && signal.__f === void 0) {
1600
+ signal.__f = subs.values().next().value;
1515
1601
  }
1516
1602
  }
1517
1603
  }
@@ -1519,9 +1605,28 @@ function cleanup(subscriber) {
1519
1605
  }
1520
1606
 
1521
1607
  // src/core/ssr-context.ts
1522
- var ssrMode = false;
1608
+ var als = null;
1609
+ try {
1610
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
1611
+ const req = Function("return typeof require==='function'?require:null")();
1612
+ if (req) {
1613
+ const mod = req("node:async_hooks");
1614
+ als = new mod.AsyncLocalStorage();
1615
+ }
1616
+ }
1617
+ } catch {
1618
+ als = null;
1619
+ }
1620
+ var fallbackStore = { ssr: false, suspenseIdCounter: 0 };
1621
+ function getSSRStore() {
1622
+ if (als) {
1623
+ const s = als.getStore();
1624
+ if (s) return s;
1625
+ }
1626
+ return fallbackStore;
1627
+ }
1523
1628
  function isSSR() {
1524
- return ssrMode;
1629
+ return getSSRStore().ssr;
1525
1630
  }
1526
1631
 
1527
1632
  // src/core/signals/effect.ts
@@ -1531,26 +1636,86 @@ function effect(effectFn, options) {
1531
1636
  if (isSSR()) return () => {
1532
1637
  };
1533
1638
  const onError = options?.onError;
1639
+ let userCleanups = [];
1640
+ const onCleanup = (fn) => {
1641
+ userCleanups.push(fn);
1642
+ };
1643
+ const runUserCleanups = () => {
1644
+ if (userCleanups.length === 0) return;
1645
+ const list = userCleanups;
1646
+ userCleanups = [];
1647
+ for (let i = list.length - 1; i >= 0; i--) {
1648
+ try {
1649
+ list[i]();
1650
+ } catch (err) {
1651
+ if (typeof console !== "undefined") {
1652
+ console.warn("[SibuJS effect] onCleanup threw:", err);
1653
+ }
1654
+ }
1655
+ }
1656
+ };
1657
+ const invokeBody = () => effectFn(onCleanup);
1534
1658
  const wrappedFn = onError ? () => {
1535
1659
  try {
1536
- effectFn();
1660
+ invokeBody();
1537
1661
  } catch (err) {
1538
1662
  onError(err);
1539
1663
  }
1540
- } : effectFn;
1664
+ } : invokeBody;
1541
1665
  let cleanupHandle = () => {
1542
1666
  };
1667
+ let running = false;
1543
1668
  const subscriber = () => {
1544
- cleanupHandle();
1545
- cleanupHandle = track(wrappedFn, subscriber);
1669
+ if (running) {
1670
+ if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
1671
+ console.warn(
1672
+ "[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."
1673
+ );
1674
+ }
1675
+ return;
1676
+ }
1677
+ running = true;
1678
+ try {
1679
+ runUserCleanups();
1680
+ cleanupHandle();
1681
+ cleanupHandle = track(wrappedFn, subscriber);
1682
+ } finally {
1683
+ running = false;
1684
+ }
1546
1685
  };
1547
- cleanupHandle = track(wrappedFn, subscriber);
1686
+ running = true;
1687
+ try {
1688
+ cleanupHandle = track(wrappedFn, subscriber);
1689
+ } finally {
1690
+ running = false;
1691
+ }
1548
1692
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1549
1693
  if (hook) hook.emit("effect:create", { effectFn });
1694
+ let disposed = false;
1550
1695
  return () => {
1696
+ if (disposed) return;
1697
+ disposed = true;
1551
1698
  const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1552
- if (h) h.emit("effect:destroy", { effectFn });
1553
- cleanupHandle();
1699
+ if (h) {
1700
+ try {
1701
+ h.emit("effect:destroy", { effectFn });
1702
+ } catch {
1703
+ }
1704
+ }
1705
+ try {
1706
+ runUserCleanups();
1707
+ } catch (err) {
1708
+ if (typeof console !== "undefined") {
1709
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
1710
+ }
1711
+ }
1712
+ try {
1713
+ cleanupHandle();
1714
+ } catch (err) {
1715
+ if (typeof console !== "undefined") {
1716
+ console.warn("[SibuJS effect] dispose threw:", err);
1717
+ }
1718
+ }
1554
1719
  };
1555
1720
  }
1556
1721
 
@@ -1640,21 +1805,27 @@ function waitForSignal(getter, predicate, options = {}) {
1640
1805
  const timeoutMs = options.timeout ?? 1e3;
1641
1806
  return new Promise((resolve, reject) => {
1642
1807
  let resolved = false;
1643
- const timer = setTimeout(() => {
1644
- if (!resolved) {
1645
- resolved = true;
1808
+ let timer;
1809
+ const finish = (fn) => {
1810
+ if (resolved) return;
1811
+ resolved = true;
1812
+ if (timer !== void 0) clearTimeout(timer);
1813
+ fn();
1814
+ };
1815
+ timer = setTimeout(() => {
1816
+ finish(() => {
1646
1817
  teardown();
1647
1818
  reject(new Error(`waitForSignal: predicate did not match within ${timeoutMs}ms`));
1648
- }
1819
+ });
1649
1820
  }, timeoutMs);
1650
1821
  const teardown = effect(() => {
1651
1822
  if (resolved) return;
1652
1823
  const value = getter();
1653
1824
  if (predicate(value)) {
1654
- resolved = true;
1655
- clearTimeout(timer);
1656
- queueMicrotask(() => teardown());
1657
- resolve(value);
1825
+ finish(() => {
1826
+ queueMicrotask(() => teardown());
1827
+ resolve(value);
1828
+ });
1658
1829
  }
1659
1830
  });
1660
1831
  });
@@ -2005,9 +2176,24 @@ function truncate(str, maxLen) {
2005
2176
  }
2006
2177
 
2007
2178
  // src/testing/index.ts
2179
+ function escapeSelector(value) {
2180
+ const g = globalThis;
2181
+ if (g.CSS && typeof g.CSS.escape === "function") return g.CSS.escape(value);
2182
+ return value.replace(/[^\w-]/g, (m) => `\\${m.charCodeAt(0).toString(16)} `);
2183
+ }
2184
+ var _renderedContainers = /* @__PURE__ */ new Set();
2185
+ function unmountAll() {
2186
+ for (const container of _renderedContainers) {
2187
+ for (const child of Array.from(container.childNodes)) dispose(child);
2188
+ container.replaceChildren();
2189
+ if (container.parentNode) container.parentNode.removeChild(container);
2190
+ }
2191
+ _renderedContainers.clear();
2192
+ }
2008
2193
  function render(component) {
2009
2194
  const container = document.createElement("div");
2010
2195
  document.body.appendChild(container);
2196
+ _renderedContainers.add(container);
2011
2197
  const element = component();
2012
2198
  container.appendChild(element);
2013
2199
  function getByText(text) {
@@ -2024,7 +2210,7 @@ function render(component) {
2024
2210
  return walk(container);
2025
2211
  }
2026
2212
  function getByTestId(testId) {
2027
- return container.querySelector(`[data-testid="${testId}"]`);
2213
+ return container.querySelector(`[data-testid="${escapeSelector(testId)}"]`);
2028
2214
  }
2029
2215
  function getByRole(role) {
2030
2216
  return container.querySelector(`[role="${role}"]`);
@@ -2033,8 +2219,10 @@ function render(component) {
2033
2219
  return Array.from(container.querySelectorAll(selector));
2034
2220
  }
2035
2221
  function unmount() {
2036
- container.innerHTML = "";
2222
+ for (const child of Array.from(container.childNodes)) dispose(child);
2223
+ container.replaceChildren();
2037
2224
  if (container.parentNode) container.parentNode.removeChild(container);
2225
+ _renderedContainers.delete(container);
2038
2226
  }
2039
2227
  return { container, element, getByText, getByTestId, getByRole, queryAll, unmount };
2040
2228
  }
@@ -2145,6 +2333,7 @@ function mockStore(initialState) {
2145
2333
  snapshotComponent,
2146
2334
  testComponent,
2147
2335
  type,
2336
+ unmountAll,
2148
2337
  waitFor,
2149
2338
  waitForSignal
2150
2339
  });
@@ -242,11 +242,13 @@ interface MockRoute {
242
242
  * Create an HTTP mock server that intercepts fetch calls.
243
243
  * Useful for testing components that make API calls.
244
244
  */
245
- declare function createHttpMock(routes?: MockRoute[]): {
245
+ declare function createHttpMock(routes?: MockRoute[], options?: {
246
+ afterEach?: (cleanup: () => void) => void;
247
+ }): {
246
248
  /** Install the mock (replace global fetch) */
247
249
  install(): void;
248
250
  /** Restore original fetch */
249
- restore(): void;
251
+ restore: () => void;
250
252
  /** Add a mock route */
251
253
  addRoute(route: MockRoute): void;
252
254
  /** Remove all mock routes */
@@ -271,7 +273,9 @@ declare function createHttpMock(routes?: MockRoute[]): {
271
273
  * Create a fake timer system for testing time-dependent code.
272
274
  * Mocks setTimeout, setInterval, requestAnimationFrame.
273
275
  */
274
- declare function createTimerMock(): {
276
+ declare function createTimerMock(options?: {
277
+ afterEach?: (cleanup: () => void) => void;
278
+ }): {
275
279
  install(): void;
276
280
  restore(): void;
277
281
  /** Advance time by a given number of ms, running any timers that fire */
@@ -479,8 +483,17 @@ declare function createVisualSuite(): {
479
483
  clear: () => void;
480
484
  };
481
485
 
486
+ /**
487
+ * Unmount every container still alive from prior `render()` calls.
488
+ * Safe to call from an `afterEach` hook to guarantee teardown.
489
+ */
490
+ declare function unmountAll(): void;
482
491
  /**
483
492
  * render mounts a component into a test container and returns helpers.
493
+ *
494
+ * The caller is responsible for calling `unmount()` (typically from an
495
+ * `afterEach` hook). For bulk teardown across many renders, call
496
+ * `unmountAll()` instead — every live container is tracked internally.
484
497
  */
485
498
  declare function render(component: () => HTMLElement): {
486
499
  container: HTMLElement;
@@ -529,4 +542,4 @@ declare function mockStore<T extends Record<string, unknown>>(initialState: T):
529
542
  reset: () => void;
530
543
  };
531
544
 
532
- export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FindOptions, type FingerprintChange, type MockResponse, type MockRoute, type VisualFingerprint, assertA11y, assertDOMEquals, captureFingerprint, checkA11y, checkAriaAttributes, checkColorContrast, checkFormLabels, checkHeadingHierarchy, checkImageAlt, checkKeyboardAccess, checkLandmarks, checkLinksAndButtons, checkListSemantics, checkTabOrder, compareFingerprints, createCypressAdapter, createDOMSnapshot, createHttpMock, createJestAdapter, createPlaywrightAdapter, createSnapshotMatcher, createSnapshotStore, createTimerMock, createUniversalAdapter, createVisualSuite, findByRole, findByTestId, findByText, fireEvent, matchSnapshot, mockRouter, mockStore, queryByLabel, queryByRole, queryByTestId, queryByText, render, snapshotComponent, testComponent, type, waitFor, waitForSignal };
545
+ export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FindOptions, type FingerprintChange, type MockResponse, type MockRoute, type VisualFingerprint, assertA11y, assertDOMEquals, captureFingerprint, checkA11y, checkAriaAttributes, checkColorContrast, checkFormLabels, checkHeadingHierarchy, checkImageAlt, checkKeyboardAccess, checkLandmarks, checkLinksAndButtons, checkListSemantics, checkTabOrder, compareFingerprints, createCypressAdapter, createDOMSnapshot, createHttpMock, createJestAdapter, createPlaywrightAdapter, createSnapshotMatcher, createSnapshotStore, createTimerMock, createUniversalAdapter, createVisualSuite, findByRole, findByTestId, findByText, fireEvent, matchSnapshot, mockRouter, mockStore, queryByLabel, queryByRole, queryByTestId, queryByText, render, snapshotComponent, testComponent, type, unmountAll, waitFor, waitForSignal };
package/dist/testing.d.ts CHANGED
@@ -242,11 +242,13 @@ interface MockRoute {
242
242
  * Create an HTTP mock server that intercepts fetch calls.
243
243
  * Useful for testing components that make API calls.
244
244
  */
245
- declare function createHttpMock(routes?: MockRoute[]): {
245
+ declare function createHttpMock(routes?: MockRoute[], options?: {
246
+ afterEach?: (cleanup: () => void) => void;
247
+ }): {
246
248
  /** Install the mock (replace global fetch) */
247
249
  install(): void;
248
250
  /** Restore original fetch */
249
- restore(): void;
251
+ restore: () => void;
250
252
  /** Add a mock route */
251
253
  addRoute(route: MockRoute): void;
252
254
  /** Remove all mock routes */
@@ -271,7 +273,9 @@ declare function createHttpMock(routes?: MockRoute[]): {
271
273
  * Create a fake timer system for testing time-dependent code.
272
274
  * Mocks setTimeout, setInterval, requestAnimationFrame.
273
275
  */
274
- declare function createTimerMock(): {
276
+ declare function createTimerMock(options?: {
277
+ afterEach?: (cleanup: () => void) => void;
278
+ }): {
275
279
  install(): void;
276
280
  restore(): void;
277
281
  /** Advance time by a given number of ms, running any timers that fire */
@@ -479,8 +483,17 @@ declare function createVisualSuite(): {
479
483
  clear: () => void;
480
484
  };
481
485
 
486
+ /**
487
+ * Unmount every container still alive from prior `render()` calls.
488
+ * Safe to call from an `afterEach` hook to guarantee teardown.
489
+ */
490
+ declare function unmountAll(): void;
482
491
  /**
483
492
  * render mounts a component into a test container and returns helpers.
493
+ *
494
+ * The caller is responsible for calling `unmount()` (typically from an
495
+ * `afterEach` hook). For bulk teardown across many renders, call
496
+ * `unmountAll()` instead — every live container is tracked internally.
484
497
  */
485
498
  declare function render(component: () => HTMLElement): {
486
499
  container: HTMLElement;
@@ -529,4 +542,4 @@ declare function mockStore<T extends Record<string, unknown>>(initialState: T):
529
542
  reset: () => void;
530
543
  };
531
544
 
532
- export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FindOptions, type FingerprintChange, type MockResponse, type MockRoute, type VisualFingerprint, assertA11y, assertDOMEquals, captureFingerprint, checkA11y, checkAriaAttributes, checkColorContrast, checkFormLabels, checkHeadingHierarchy, checkImageAlt, checkKeyboardAccess, checkLandmarks, checkLinksAndButtons, checkListSemantics, checkTabOrder, compareFingerprints, createCypressAdapter, createDOMSnapshot, createHttpMock, createJestAdapter, createPlaywrightAdapter, createSnapshotMatcher, createSnapshotStore, createTimerMock, createUniversalAdapter, createVisualSuite, findByRole, findByTestId, findByText, fireEvent, matchSnapshot, mockRouter, mockStore, queryByLabel, queryByRole, queryByTestId, queryByText, render, snapshotComponent, testComponent, type, waitFor, waitForSignal };
545
+ export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FindOptions, type FingerprintChange, type MockResponse, type MockRoute, type VisualFingerprint, assertA11y, assertDOMEquals, captureFingerprint, checkA11y, checkAriaAttributes, checkColorContrast, checkFormLabels, checkHeadingHierarchy, checkImageAlt, checkKeyboardAccess, checkLandmarks, checkLinksAndButtons, checkListSemantics, checkTabOrder, compareFingerprints, createCypressAdapter, createDOMSnapshot, createHttpMock, createJestAdapter, createPlaywrightAdapter, createSnapshotMatcher, createSnapshotStore, createTimerMock, createUniversalAdapter, createVisualSuite, findByRole, findByTestId, findByText, fireEvent, matchSnapshot, mockRouter, mockStore, queryByLabel, queryByRole, queryByTestId, queryByText, render, snapshotComponent, testComponent, type, unmountAll, waitFor, waitForSignal };