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
@@ -38,12 +38,12 @@ function isDev() {
38
38
  var _isDev = isDev();
39
39
  function devAssert(condition, message) {
40
40
  if (_isDev && !condition) {
41
- throw new Error(`[Sibu] ${message}`);
41
+ throw new Error(`[SibuJS] ${message}`);
42
42
  }
43
43
  }
44
44
  function devWarn(message) {
45
45
  if (_isDev) {
46
- console.warn(`[Sibu] ${message}`);
46
+ console.warn(`[SibuJS] ${message}`);
47
47
  }
48
48
  }
49
49
 
@@ -53,11 +53,11 @@ var subscriberStack = new Array(32);
53
53
  var stackCapacity = 32;
54
54
  var stackTop = -1;
55
55
  var currentSubscriber = null;
56
- var signalSubscribers = /* @__PURE__ */ new WeakMap();
57
56
  var SUBS = "__s";
58
57
  var notifyDepth = 0;
59
58
  var pendingQueue = [];
60
59
  var pendingSet = /* @__PURE__ */ new Set();
60
+ var propagateStack = [];
61
61
  function safeInvoke(sub) {
62
62
  try {
63
63
  sub();
@@ -66,6 +66,15 @@ function safeInvoke(sub) {
66
66
  }
67
67
  }
68
68
  var trackingSuspended = false;
69
+ function retrack(effectFn, subscriber) {
70
+ const prev = currentSubscriber;
71
+ currentSubscriber = subscriber;
72
+ try {
73
+ effectFn();
74
+ } finally {
75
+ currentSubscriber = prev;
76
+ }
77
+ }
69
78
  function track(effectFn, subscriber) {
70
79
  if (!subscriber) subscriber = effectFn;
71
80
  cleanup(subscriber);
@@ -104,7 +113,6 @@ function recordDependency(signal2) {
104
113
  let subs = signal2[SUBS];
105
114
  if (!subs) {
106
115
  subs = /* @__PURE__ */ new Set();
107
- signalSubscribers.set(signal2, subs);
108
116
  signal2[SUBS] = subs;
109
117
  }
110
118
  subs.add(currentSubscriber);
@@ -126,57 +134,71 @@ function queueSignalNotification(signal2) {
126
134
  }
127
135
  }
128
136
  }
137
+ var maxDrainIterations = 1e5;
129
138
  function drainNotificationQueue() {
130
139
  if (notifyDepth > 0) return;
131
140
  notifyDepth++;
132
141
  try {
133
142
  let i = 0;
134
143
  while (i < pendingQueue.length) {
144
+ if (i >= maxDrainIterations) {
145
+ if (typeof console !== "undefined") {
146
+ console.error(
147
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
148
+ );
149
+ }
150
+ break;
151
+ }
135
152
  safeInvoke(pendingQueue[i]);
136
153
  i++;
137
154
  }
138
155
  } finally {
139
- pendingQueue.length = 0;
140
- pendingSet.clear();
141
156
  notifyDepth--;
157
+ if (notifyDepth === 0) {
158
+ pendingQueue.length = 0;
159
+ pendingSet.clear();
160
+ }
142
161
  }
143
162
  }
144
163
  function propagateDirty(sub) {
145
164
  sub();
146
- let sig = sub._sig;
147
- while (sig) {
165
+ const rootSig = sub._sig;
166
+ if (!rootSig) return;
167
+ const stack = propagateStack;
168
+ const baseLen = stack.length;
169
+ stack.push(rootSig);
170
+ while (stack.length > baseLen) {
171
+ const sig = stack.pop();
148
172
  const first = sig.__f;
149
173
  if (first) {
150
174
  if (first._c) {
151
175
  const nSig = first._sig;
152
- nSig._d = true;
153
- sig = nSig;
154
- continue;
155
- }
156
- if (!pendingSet.has(first)) {
176
+ if (!nSig._d) {
177
+ nSig._d = true;
178
+ stack.push(nSig);
179
+ }
180
+ } else if (!pendingSet.has(first)) {
157
181
  pendingSet.add(first);
158
182
  pendingQueue.push(first);
159
183
  }
160
- break;
184
+ continue;
161
185
  }
162
186
  const subs = sig[SUBS];
163
- if (!subs) break;
164
- let nextSig;
187
+ if (!subs) continue;
165
188
  for (const s of subs) {
166
189
  if (s._c) {
167
- s();
168
190
  const nSig = s._sig;
169
- if (nSig && !nextSig) {
170
- nextSig = nSig;
171
- } else if (nSig) {
172
- propagateDirty(s);
191
+ if (nSig && !nSig._d) {
192
+ nSig._d = true;
193
+ stack.push(nSig);
194
+ } else if (!nSig) {
195
+ s();
173
196
  }
174
197
  } else if (!pendingSet.has(s)) {
175
198
  pendingSet.add(s);
176
199
  pendingQueue.push(s);
177
200
  }
178
201
  }
179
- sig = nextSig;
180
202
  }
181
203
  }
182
204
  function notifySubscribers(signal2) {
@@ -200,13 +222,23 @@ function notifySubscribers(signal2) {
200
222
  }
201
223
  let i = 0;
202
224
  while (i < pendingQueue.length) {
225
+ if (i >= maxDrainIterations) {
226
+ if (typeof console !== "undefined") {
227
+ console.error(
228
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
229
+ );
230
+ }
231
+ break;
232
+ }
203
233
  safeInvoke(pendingQueue[i]);
204
234
  i++;
205
235
  }
206
236
  } finally {
207
- pendingQueue.length = 0;
208
- pendingSet.clear();
209
237
  notifyDepth--;
238
+ if (notifyDepth === 0) {
239
+ pendingQueue.length = 0;
240
+ pendingSet.clear();
241
+ }
210
242
  }
211
243
  return;
212
244
  }
@@ -226,30 +258,48 @@ function notifySubscribers(signal2) {
226
258
  notifyDepth++;
227
259
  try {
228
260
  let directCount = 0;
261
+ let hasComputedSub = false;
229
262
  for (const sub of subs) {
263
+ if (sub._c) hasComputedSub = true;
230
264
  pendingQueue[directCount++] = sub;
231
265
  }
232
- for (let i2 = 0; i2 < directCount; i2++) {
233
- if (pendingQueue[i2]._c) {
234
- propagateDirty(pendingQueue[i2]);
266
+ if (!hasComputedSub) {
267
+ for (let i2 = 0; i2 < directCount; i2++) {
268
+ safeInvoke(pendingQueue[i2]);
235
269
  }
236
- }
237
- for (let i2 = 0; i2 < directCount; i2++) {
238
- if (!pendingQueue[i2]._c) {
239
- if (!pendingSet.has(pendingQueue[i2])) {
240
- safeInvoke(pendingQueue[i2]);
270
+ } else {
271
+ for (let i2 = 0; i2 < directCount; i2++) {
272
+ if (pendingQueue[i2]._c) {
273
+ propagateDirty(pendingQueue[i2]);
274
+ }
275
+ }
276
+ for (let i2 = 0; i2 < directCount; i2++) {
277
+ const sub = pendingQueue[i2];
278
+ if (!sub._c && !pendingSet.has(sub)) {
279
+ pendingSet.add(sub);
280
+ safeInvoke(sub);
241
281
  }
242
282
  }
243
283
  }
244
284
  let i = directCount;
245
285
  while (i < pendingQueue.length) {
286
+ if (i - directCount >= maxDrainIterations) {
287
+ if (typeof console !== "undefined") {
288
+ console.error(
289
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
290
+ );
291
+ }
292
+ break;
293
+ }
246
294
  safeInvoke(pendingQueue[i]);
247
295
  i++;
248
296
  }
249
297
  } finally {
250
- pendingQueue.length = 0;
251
- pendingSet.clear();
252
298
  notifyDepth--;
299
+ if (notifyDepth === 0) {
300
+ pendingQueue.length = 0;
301
+ pendingSet.clear();
302
+ }
253
303
  }
254
304
  }
255
305
  function cleanup(subscriber) {
@@ -260,7 +310,9 @@ function cleanup(subscriber) {
260
310
  if (subs) {
261
311
  subs.delete(subscriber);
262
312
  if (singleDep.__f === subscriber) {
263
- singleDep.__f = void 0;
313
+ singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
314
+ } else if (subs.size === 1 && singleDep.__f === void 0) {
315
+ singleDep.__f = subs.values().next().value;
264
316
  }
265
317
  }
266
318
  sub._dep = void 0;
@@ -273,7 +325,9 @@ function cleanup(subscriber) {
273
325
  if (subs) {
274
326
  subs.delete(subscriber);
275
327
  if (signal2.__f === subscriber) {
276
- signal2.__f = void 0;
328
+ signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
329
+ } else if (subs.size === 1 && signal2.__f === void 0) {
330
+ signal2.__f = subs.values().next().value;
277
331
  }
278
332
  }
279
333
  }
@@ -281,9 +335,28 @@ function cleanup(subscriber) {
281
335
  }
282
336
 
283
337
  // src/core/ssr-context.ts
284
- var ssrMode = false;
338
+ var als = null;
339
+ try {
340
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
341
+ const req = Function("return typeof require==='function'?require:null")();
342
+ if (req) {
343
+ const mod = req("node:async_hooks");
344
+ als = new mod.AsyncLocalStorage();
345
+ }
346
+ }
347
+ } catch {
348
+ als = null;
349
+ }
350
+ var fallbackStore = { ssr: false, suspenseIdCounter: 0 };
351
+ function getSSRStore() {
352
+ if (als) {
353
+ const s = als.getStore();
354
+ if (s) return s;
355
+ }
356
+ return fallbackStore;
357
+ }
285
358
  function isSSR() {
286
- return ssrMode;
359
+ return getSSRStore().ssr;
287
360
  }
288
361
 
289
362
  // src/core/signals/effect.ts
@@ -293,26 +366,86 @@ function effect(effectFn, options) {
293
366
  if (isSSR()) return () => {
294
367
  };
295
368
  const onError = options?.onError;
369
+ let userCleanups = [];
370
+ const onCleanup = (fn) => {
371
+ userCleanups.push(fn);
372
+ };
373
+ const runUserCleanups = () => {
374
+ if (userCleanups.length === 0) return;
375
+ const list = userCleanups;
376
+ userCleanups = [];
377
+ for (let i = list.length - 1; i >= 0; i--) {
378
+ try {
379
+ list[i]();
380
+ } catch (err) {
381
+ if (typeof console !== "undefined") {
382
+ console.warn("[SibuJS effect] onCleanup threw:", err);
383
+ }
384
+ }
385
+ }
386
+ };
387
+ const invokeBody = () => effectFn(onCleanup);
296
388
  const wrappedFn = onError ? () => {
297
389
  try {
298
- effectFn();
390
+ invokeBody();
299
391
  } catch (err) {
300
392
  onError(err);
301
393
  }
302
- } : effectFn;
394
+ } : invokeBody;
303
395
  let cleanupHandle = () => {
304
396
  };
397
+ let running = false;
305
398
  const subscriber = () => {
306
- cleanupHandle();
307
- cleanupHandle = track(wrappedFn, subscriber);
399
+ if (running) {
400
+ if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
401
+ console.warn(
402
+ "[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."
403
+ );
404
+ }
405
+ return;
406
+ }
407
+ running = true;
408
+ try {
409
+ runUserCleanups();
410
+ cleanupHandle();
411
+ cleanupHandle = track(wrappedFn, subscriber);
412
+ } finally {
413
+ running = false;
414
+ }
308
415
  };
309
- cleanupHandle = track(wrappedFn, subscriber);
416
+ running = true;
417
+ try {
418
+ cleanupHandle = track(wrappedFn, subscriber);
419
+ } finally {
420
+ running = false;
421
+ }
310
422
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
311
423
  if (hook) hook.emit("effect:create", { effectFn });
424
+ let disposed = false;
312
425
  return () => {
426
+ if (disposed) return;
427
+ disposed = true;
313
428
  const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
314
- if (h) h.emit("effect:destroy", { effectFn });
315
- cleanupHandle();
429
+ if (h) {
430
+ try {
431
+ h.emit("effect:destroy", { effectFn });
432
+ } catch {
433
+ }
434
+ }
435
+ try {
436
+ runUserCleanups();
437
+ } catch (err) {
438
+ if (typeof console !== "undefined") {
439
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
440
+ }
441
+ }
442
+ try {
443
+ cleanupHandle();
444
+ } catch (err) {
445
+ if (typeof console !== "undefined") {
446
+ console.warn("[SibuJS effect] dispose threw:", err);
447
+ }
448
+ }
316
449
  };
317
450
  }
318
451
 
@@ -336,10 +469,13 @@ function enqueueBatchedSignal(signal2) {
336
469
  return true;
337
470
  }
338
471
  function flushBatch() {
339
- for (const signal2 of pendingSignals) {
340
- queueSignalNotification(signal2);
472
+ try {
473
+ for (const signal2 of pendingSignals) {
474
+ queueSignalNotification(signal2);
475
+ }
476
+ } finally {
477
+ pendingSignals.clear();
341
478
  }
342
- pendingSignals.clear();
343
479
  drainNotificationQueue();
344
480
  }
345
481
 
@@ -382,6 +518,85 @@ function signal(initial, options) {
382
518
  }
383
519
 
384
520
  // src/plugins/plugin.ts
521
+ function createPluginRegistry() {
522
+ const installedPlugins = /* @__PURE__ */ new Set();
523
+ const hooks = { init: [], mount: [], unmount: [], error: [] };
524
+ const provided = /* @__PURE__ */ new Map();
525
+ const registry = {
526
+ installedPlugins,
527
+ hooks,
528
+ provided,
529
+ plugin(p, options) {
530
+ if (installedPlugins.has(p.name)) {
531
+ console.warn(`[Plugin] "${p.name}" is already installed.`);
532
+ return;
533
+ }
534
+ const ctx = {
535
+ onInit: (cb) => hooks.init.push(cb),
536
+ onMount: (cb) => hooks.mount.push(cb),
537
+ onUnmount: (cb) => hooks.unmount.push(cb),
538
+ onError: (cb) => hooks.error.push(cb),
539
+ provide: (key, value) => provided.set(key, value)
540
+ };
541
+ const initHooksBefore = hooks.init.length;
542
+ p.install(ctx, options);
543
+ installedPlugins.add(p.name);
544
+ const justAdded = hooks.init.slice(initHooksBefore);
545
+ for (const cb of justAdded) {
546
+ try {
547
+ cb();
548
+ } catch (e) {
549
+ console.error(`[Plugin] "${p.name}" init error:`, e);
550
+ }
551
+ }
552
+ },
553
+ inject(key, defaultValue) {
554
+ if (provided.has(key)) return provided.get(key);
555
+ if (defaultValue !== void 0) return defaultValue;
556
+ throw new Error(`[Plugin] No provider found for key "${key}"`);
557
+ },
558
+ triggerMount(element) {
559
+ const snapshot = hooks.mount.slice();
560
+ for (const hook of snapshot) {
561
+ try {
562
+ hook(element);
563
+ } catch (e) {
564
+ console.error("[Plugin] Mount hook error:", e);
565
+ }
566
+ }
567
+ },
568
+ triggerUnmount(element) {
569
+ const snapshot = hooks.unmount.slice();
570
+ for (const hook of snapshot) {
571
+ try {
572
+ hook(element);
573
+ } catch (e) {
574
+ console.error("[Plugin] Unmount hook error:", e);
575
+ }
576
+ }
577
+ },
578
+ triggerError(error) {
579
+ const snapshot = hooks.error.slice();
580
+ for (const hook of snapshot) {
581
+ try {
582
+ hook(error);
583
+ } catch (e) {
584
+ console.error("[Plugin] Error hook error:", e);
585
+ }
586
+ }
587
+ },
588
+ reset() {
589
+ installedPlugins.clear();
590
+ hooks.init.length = 0;
591
+ hooks.mount.length = 0;
592
+ hooks.unmount.length = 0;
593
+ hooks.error.length = 0;
594
+ provided.clear();
595
+ }
596
+ };
597
+ return registry;
598
+ }
599
+ var defaultRegistry = createPluginRegistry();
385
600
  function createPlugin(name, install) {
386
601
  return { name, install };
387
602
  }
@@ -392,7 +607,7 @@ function mobXAdapter(options) {
392
607
  const { autorun } = options;
393
608
  const disposers = [];
394
609
  function fromMobX(expression) {
395
- const [getValue, setValue] = signal(expression());
610
+ const [getValue, setValue] = signal(void 0);
396
611
  const disposer = autorun(() => {
397
612
  const newValue = expression();
398
613
  batch(() => {
@@ -400,7 +615,13 @@ function mobXAdapter(options) {
400
615
  });
401
616
  });
402
617
  disposers.push(disposer);
403
- return getValue;
618
+ const getter = (() => getValue());
619
+ getter.dispose = () => {
620
+ const i = disposers.indexOf(disposer);
621
+ if (i >= 0) disposers.splice(i, 1);
622
+ disposer();
623
+ };
624
+ return getter;
404
625
  }
405
626
  function toMobX(sibuGetter, callback) {
406
627
  return effect(() => {
@@ -422,6 +643,7 @@ function mobXAdapter(options) {
422
643
  function derived(getter, options) {
423
644
  devAssert(typeof getter === "function", "derived: argument must be a getter function.");
424
645
  const debugName = options?.name;
646
+ const equals = options?.equals;
425
647
  const cs = {};
426
648
  cs._d = false;
427
649
  cs._g = getter;
@@ -432,25 +654,56 @@ function derived(getter, options) {
432
654
  markDirty._c = 1;
433
655
  markDirty._sig = cs;
434
656
  track(() => {
435
- cs._d = false;
436
- cs._v = getter();
657
+ let threw = true;
658
+ try {
659
+ cs._v = getter();
660
+ cs._d = false;
661
+ threw = false;
662
+ } finally {
663
+ if (threw) cs._d = true;
664
+ }
437
665
  }, markDirty);
438
666
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
667
+ let evaluating = false;
439
668
  function computedGetter() {
669
+ if (evaluating) {
670
+ throw new Error(
671
+ `[SibuJS] Circular dependency detected in derived${debugName ? ` "${debugName}"` : ""}. A derived signal cannot read itself (directly or through a chain).`
672
+ );
673
+ }
440
674
  if (trackingSuspended) {
441
675
  if (cs._d) {
442
- cs._d = false;
443
- cs._v = getter();
676
+ evaluating = true;
677
+ let threw = true;
678
+ try {
679
+ retrack(() => {
680
+ cs._v = getter();
681
+ cs._d = false;
682
+ threw = false;
683
+ }, markDirty);
684
+ } finally {
685
+ evaluating = false;
686
+ if (threw) cs._d = true;
687
+ }
444
688
  }
445
689
  return cs._v;
446
690
  }
447
691
  recordDependency(cs);
448
692
  if (cs._d) {
449
693
  const oldValue = cs._v;
450
- track(() => {
451
- cs._d = false;
452
- cs._v = getter();
453
- }, markDirty);
694
+ evaluating = true;
695
+ let threw = true;
696
+ try {
697
+ retrack(() => {
698
+ const next = getter();
699
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
700
+ cs._d = false;
701
+ threw = false;
702
+ }, markDirty);
703
+ } finally {
704
+ evaluating = false;
705
+ if (threw) cs._d = true;
706
+ }
454
707
  if (hook && oldValue !== cs._v) {
455
708
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
456
709
  }
@@ -476,12 +729,12 @@ function reduxAdapter(options) {
476
729
  setState(store.getState());
477
730
  });
478
731
  });
479
- function useSelector(selector) {
732
+ function select(selector) {
480
733
  return derived(() => selector(getState()));
481
734
  }
482
735
  const api = {
483
736
  getState,
484
- useSelector,
737
+ select,
485
738
  dispatch: store.dispatch.bind(store),
486
739
  destroy: unsubscribe
487
740
  };
@@ -499,12 +752,12 @@ function zustandAdapter(options) {
499
752
  setSibuState(state);
500
753
  });
501
754
  });
502
- function useSelector(selector) {
755
+ function select(selector) {
503
756
  return derived(() => selector(getState()));
504
757
  }
505
758
  const api = {
506
759
  getState,
507
- useSelector,
760
+ select,
508
761
  setState: store.setState.bind(store),
509
762
  destroy() {
510
763
  unsubscribe();
@@ -516,29 +769,79 @@ function zustandAdapter(options) {
516
769
  }
517
770
 
518
771
  // src/utils/sanitize.ts
772
+ var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
519
773
  function sanitizeUrl(url) {
520
774
  const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
521
775
  if (!trimmed) return "";
522
776
  const lower = trimmed.toLowerCase();
523
- if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
524
- return "";
777
+ let schemeEnd = -1;
778
+ for (let i = 0; i < lower.length; i++) {
779
+ const ch = lower.charCodeAt(i);
780
+ if (ch === 58) {
781
+ schemeEnd = i;
782
+ break;
783
+ }
784
+ if (ch === 47 || ch === 63 || ch === 35) break;
525
785
  }
786
+ if (schemeEnd === -1) return trimmed;
787
+ const scheme = lower.slice(0, schemeEnd + 1);
788
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return trimmed;
789
+ if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
526
790
  return trimmed;
527
791
  }
792
+ function sanitizeSrcset(value) {
793
+ const parts = value.split(",");
794
+ const out = [];
795
+ for (let i = 0; i < parts.length; i++) {
796
+ const part = parts[i].trim();
797
+ if (!part) continue;
798
+ const m = part.match(/^(\S+)(\s+.+)?$/);
799
+ if (!m) continue;
800
+ const safe = sanitizeUrl(m[1]);
801
+ if (!safe) continue;
802
+ out.push(m[2] ? `${safe}${m[2]}` : safe);
803
+ }
804
+ return out.join(", ");
805
+ }
528
806
  function sanitizeCSSValue(value) {
529
- const lower = value.toLowerCase().replace(/\s+/g, "");
530
- if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("-moz-binding")) {
807
+ const decoded = value.replace(/\\([0-9a-fA-F]{1,6})\s?/g, (_m, hex) => {
808
+ const code = Number.parseInt(hex, 16);
809
+ if (!Number.isFinite(code) || code < 0 || code > 1114111) return "";
810
+ try {
811
+ return String.fromCodePoint(code);
812
+ } catch {
813
+ return "";
814
+ }
815
+ });
816
+ const lower = decoded.toLowerCase().replace(/\s+/g, "");
817
+ 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")) {
531
818
  return "";
532
819
  }
533
820
  return value;
534
821
  }
535
- var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "cite", "poster", "background", "srcset"]);
822
+ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
823
+ "href",
824
+ "xlink:href",
825
+ "src",
826
+ "action",
827
+ "formaction",
828
+ "formtarget",
829
+ "cite",
830
+ "poster",
831
+ "background",
832
+ "srcset",
833
+ "ping",
834
+ "data"
835
+ ]);
536
836
  function isUrlAttribute(attr) {
537
837
  return URL_ATTRIBUTES.has(attr);
538
838
  }
539
839
 
540
840
  // src/reactivity/bindAttribute.ts
541
841
  var _isDev4 = isDev();
842
+ function setProp(el, key, val) {
843
+ el[key] = val;
844
+ }
542
845
  function isEventHandlerAttr(name) {
543
846
  if (name.length < 3) return false;
544
847
  const lower = name.toLowerCase();
@@ -564,7 +867,7 @@ function bindAttribute(el, attr, getter) {
564
867
  }
565
868
  if (typeof value === "boolean") {
566
869
  if (attr in el && (attr === "checked" || attr === "disabled" || attr === "selected")) {
567
- el[attr] = value;
870
+ setProp(el, attr, value);
568
871
  } else if (value) {
569
872
  el.setAttribute(attr, "");
570
873
  } else {
@@ -574,7 +877,7 @@ function bindAttribute(el, attr, getter) {
574
877
  }
575
878
  const str = String(value);
576
879
  if ((attr === "value" || attr === "checked") && attr in el) {
577
- el[attr] = attr === "checked" ? Boolean(value) : str;
880
+ setProp(el, attr, attr === "checked" ? Boolean(value) : str);
578
881
  } else {
579
882
  el.setAttribute(attr, isUrlAttribute(attr) ? sanitizeUrl(str) : str);
580
883
  }
@@ -611,24 +914,29 @@ function bindChildNode(placeholder, getter) {
611
914
  let newNodes;
612
915
  if (Array.isArray(result)) {
613
916
  newNodes = [];
917
+ const seen = /* @__PURE__ */ new Set();
614
918
  for (let i = 0; i < result.length; i++) {
615
919
  const item = result[i];
616
920
  if (item == null || typeof item === "boolean") continue;
617
- newNodes.push(item instanceof Node ? item : document.createTextNode(String(item)));
921
+ const node = item instanceof Node ? item : document.createTextNode(String(item));
922
+ if (seen.has(node)) {
923
+ if (_isDev5)
924
+ devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
925
+ continue;
926
+ }
927
+ seen.add(node);
928
+ newNodes.push(node);
618
929
  }
619
930
  } else {
620
931
  const node = result instanceof Node ? result : document.createTextNode(String(result));
621
932
  newNodes = [node];
622
933
  }
623
- const reused = lastNodes.length > 0 && newNodes.length > 0 ? /* @__PURE__ */ new Set() : void 0;
624
- if (reused) {
934
+ let reused;
935
+ if (lastNodes.length > 0 && newNodes.length > 0) {
936
+ const lastSet = new Set(lastNodes);
937
+ reused = /* @__PURE__ */ new Set();
625
938
  for (let i = 0; i < newNodes.length; i++) {
626
- for (let j = 0; j < lastNodes.length; j++) {
627
- if (newNodes[i] === lastNodes[j]) {
628
- reused.add(newNodes[i]);
629
- break;
630
- }
631
- }
939
+ if (lastSet.has(newNodes[i])) reused.add(newNodes[i]);
632
940
  }
633
941
  }
634
942
  for (let i = 0; i < lastNodes.length; i++) {
@@ -667,6 +975,30 @@ function registerDisposer(node, teardown) {
667
975
  }
668
976
 
669
977
  // src/core/rendering/tagFactory.ts
978
+ var _isDev7 = isDev();
979
+ var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
980
+ function validateTagName(tag) {
981
+ const lower = tag.toLowerCase();
982
+ if (BLOCKED_TAGS.has(lower)) {
983
+ throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
984
+ }
985
+ }
986
+ var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
987
+ "config",
988
+ "location",
989
+ "history",
990
+ "document",
991
+ "window",
992
+ "navigator",
993
+ "name",
994
+ "top",
995
+ "parent",
996
+ "self",
997
+ "frames"
998
+ ]);
999
+ function setProp2(el, key, val) {
1000
+ el[key] = val;
1001
+ }
670
1002
  var kebabCache = /* @__PURE__ */ new Map();
671
1003
  function toKebab(prop) {
672
1004
  let cached = kebabCache.get(prop);
@@ -791,79 +1123,103 @@ function appendChildren(el, nodes) {
791
1123
  }
792
1124
  }
793
1125
  }
794
- var tagFactory = (tag, ns) => (first, second) => {
795
- const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
796
- if (first === void 0) return el;
797
- if (typeof first === "string") {
798
- if (second !== void 0) {
799
- el.setAttribute("class", first);
800
- appendChildren(el, second);
1126
+ var tagFactory = (tag, ns) => {
1127
+ return (first, second) => {
1128
+ validateTagName(tag);
1129
+ const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
1130
+ if (first === void 0) return el;
1131
+ if (typeof first === "string") {
1132
+ if (second !== void 0) {
1133
+ el.setAttribute("class", first);
1134
+ appendChildren(el, second);
1135
+ return el;
1136
+ }
1137
+ el.textContent = first;
801
1138
  return el;
802
1139
  }
803
- el.textContent = first;
804
- return el;
805
- }
806
- if (typeof first === "number") {
807
- el.textContent = String(first);
808
- return el;
809
- }
810
- if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
811
- appendChildren(el, first);
812
- return el;
813
- }
814
- const props = first;
815
- const pClass = props.class;
816
- if (pClass != null) applyClass(el, pClass);
817
- const pId = props.id;
818
- if (pId != null) el.id = pId;
819
- const pNodes = second !== void 0 ? second : props.nodes;
820
- if (pNodes != null) appendChildren(el, pNodes);
821
- const pOn = props.on;
822
- if (pOn) {
823
- for (const ev in pOn) {
824
- el.addEventListener(ev, pOn[ev]);
1140
+ if (typeof first === "number") {
1141
+ el.textContent = String(first);
1142
+ return el;
825
1143
  }
826
- }
827
- const pStyle = props.style;
828
- if (pStyle != null) applyStyle(el, pStyle);
829
- const pRef = props.ref;
830
- if (pRef) pRef.current = el;
831
- for (const key in props) {
832
- switch (key) {
833
- case "class":
834
- case "id":
835
- case "nodes":
836
- case "on":
837
- case "style":
838
- case "ref":
839
- case "onElement":
840
- continue;
841
- // already handled above / below
842
- default: {
843
- const value = props[key];
844
- if (value == null) continue;
845
- if (key[0] === "o" && key[1] === "n") continue;
846
- if (typeof value === "function") {
847
- registerDisposer(el, bindAttribute(el, key, value));
848
- } else if (typeof value === "boolean") {
849
- if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
850
- el[key] = value;
851
- } else if (value) {
852
- el.setAttribute(key, "");
1144
+ if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
1145
+ appendChildren(el, first);
1146
+ return el;
1147
+ }
1148
+ const props = first;
1149
+ const pClass = props.class;
1150
+ if (pClass != null) applyClass(el, pClass);
1151
+ const pId = props.id;
1152
+ if (pId != null) {
1153
+ if (_isDev7 && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
1154
+ devWarn(
1155
+ `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
1156
+ );
1157
+ }
1158
+ el.id = pId;
1159
+ }
1160
+ const pNodes = second !== void 0 ? second : props.nodes;
1161
+ if (pNodes != null) appendChildren(el, pNodes);
1162
+ const pOn = props.on;
1163
+ if (pOn) {
1164
+ for (const ev in pOn) {
1165
+ const handler = pOn[ev];
1166
+ if (typeof handler === "function") {
1167
+ el.addEventListener(ev, handler);
1168
+ } else if (_isDev7) {
1169
+ devWarn(
1170
+ `tagFactory: on.${ev} handler is not a function (got ${typeof handler}). Event listener was not attached.`
1171
+ );
1172
+ }
1173
+ }
1174
+ }
1175
+ const pStyle = props.style;
1176
+ if (pStyle != null) applyStyle(el, pStyle);
1177
+ const pRef = props.ref;
1178
+ if (pRef) pRef.current = el;
1179
+ for (const key in props) {
1180
+ switch (key) {
1181
+ case "class":
1182
+ case "id":
1183
+ case "nodes":
1184
+ case "on":
1185
+ case "style":
1186
+ case "ref":
1187
+ case "onElement":
1188
+ continue;
1189
+ // already handled above / below
1190
+ default: {
1191
+ const value = props[key];
1192
+ if (value == null) continue;
1193
+ const lkey = key.toLowerCase();
1194
+ if (lkey[0] === "o" && lkey[1] === "n") continue;
1195
+ if (typeof value === "function") {
1196
+ registerDisposer(el, bindAttribute(el, key, value));
1197
+ } else if (typeof value === "boolean") {
1198
+ if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
1199
+ setProp2(el, key, value);
1200
+ } else if (value) {
1201
+ el.setAttribute(key, "");
1202
+ } else {
1203
+ el.removeAttribute(key);
1204
+ }
853
1205
  } else {
854
- el.removeAttribute(key);
1206
+ const str = String(value);
1207
+ if (lkey === "srcset") {
1208
+ el.setAttribute(key, sanitizeSrcset(str));
1209
+ } else if (isUrlAttribute(lkey)) {
1210
+ el.setAttribute(key, sanitizeUrl(str));
1211
+ } else {
1212
+ el.setAttribute(key, str);
1213
+ }
855
1214
  }
856
- } else {
857
- const str = String(value);
858
- el.setAttribute(key, isUrlAttribute(key) ? sanitizeUrl(str) : str);
859
1215
  }
860
1216
  }
861
1217
  }
862
- }
863
- if (props.onElement && typeof props.onElement === "function") {
864
- props.onElement(el);
865
- }
866
- return el;
1218
+ if (props.onElement && typeof props.onElement === "function") {
1219
+ props.onElement(el);
1220
+ }
1221
+ return el;
1222
+ };
867
1223
  };
868
1224
 
869
1225
  // src/ecosystem/ui/componentAdapter.ts