sibujs 4.5.0 → 4.6.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 (109) hide show
  1. package/dist/browser.cjs +309 -129
  2. package/dist/browser.d.cts +46 -14
  3. package/dist/browser.d.ts +46 -14
  4. package/dist/browser.js +8 -6
  5. package/dist/build.cjs +184 -72
  6. package/dist/build.js +15 -16
  7. package/dist/cdn.dev.global.js +12 -12
  8. package/dist/cdn.full.dev.global.js +11 -11
  9. package/dist/cdn.full.global.js +10 -10
  10. package/dist/cdn.global.js +12 -12
  11. package/dist/{chunk-IXKSNWV5.js → chunk-2INLLLMZ.js} +1 -1
  12. package/dist/chunk-3ISI6ACU.js +44 -0
  13. package/dist/{chunk-ONOHFDLG.js → chunk-47M47FOM.js} +3 -3
  14. package/dist/chunk-5HZXGZ6T.js +24 -0
  15. package/dist/{chunk-KKLW7YWL.js → chunk-5MT6SJ3P.js} +275 -144
  16. package/dist/{chunk-UGRX3S57.js → chunk-5Q4R7HCL.js} +12 -3
  17. package/dist/{chunk-S373NSMK.js → chunk-7GQCWFOE.js} +150 -59
  18. package/dist/{chunk-FTIR4QW2.js → chunk-7NCARGJW.js} +6 -8
  19. package/dist/{chunk-5DXA2J44.js → chunk-7XHATCIH.js} +5 -2
  20. package/dist/{chunk-VZKNK2V7.js → chunk-AIF3Z2T7.js} +296 -38
  21. package/dist/{chunk-4PMLNECI.js → chunk-BAAG6ZTI.js} +98 -29
  22. package/dist/{chunk-XZZOBQAY.js → chunk-D33YTSX5.js} +2 -2
  23. package/dist/{chunk-TBYTO6BS.js → chunk-DKGBBKOF.js} +136 -80
  24. package/dist/{chunk-NIOYEGBQ.js → chunk-HURREPU2.js} +27 -13
  25. package/dist/{chunk-GW3SCCZG.js → chunk-HYXDKS4N.js} +153 -110
  26. package/dist/chunk-J6FW5TV6.js +233 -0
  27. package/dist/{chunk-PCT43HW3.js → chunk-JSXPZCET.js} +1 -1
  28. package/dist/chunk-NYNYSPK7.js +318 -0
  29. package/dist/{chunk-RJE2BNI4.js → chunk-ORMZXBKQ.js} +108 -45
  30. package/dist/{chunk-OMJJM3KM.js → chunk-OXUY2A6L.js} +287 -130
  31. package/dist/{chunk-3JZ4L5TJ.js → chunk-PK6FK2G2.js} +181 -66
  32. package/dist/{chunk-RBTPLM32.js → chunk-QE4TTDU3.js} +2 -2
  33. package/dist/{chunk-2DCGACUU.js → chunk-SXXVZMKZ.js} +251 -66
  34. package/dist/{chunk-R25EFXXC.js → chunk-TUCPL2HB.js} +3 -3
  35. package/dist/{chunk-7LN645I6.js → chunk-UZQ6ALFS.js} +3 -3
  36. package/dist/{chunk-KEISJXBU.js → chunk-VVWPJ543.js} +6 -8
  37. package/dist/{chunk-RIXRAYIU.js → chunk-XC4MEKGA.js} +3 -3
  38. package/dist/{chunk-B3WHI2QA.js → chunk-XRRZKZYX.js} +55 -31
  39. package/dist/{contracts-DBdg9J_a.d.cts → contracts-DRIuclVT.d.cts} +10 -2
  40. package/dist/{contracts-DBdg9J_a.d.ts → contracts-DRIuclVT.d.ts} +10 -2
  41. package/dist/{customElement-OB9CIsc5.d.cts → customElement-MmInOW1U.d.cts} +21 -0
  42. package/dist/{customElement-OB9CIsc5.d.ts → customElement-MmInOW1U.d.ts} +21 -0
  43. package/dist/data.cjs +213 -117
  44. package/dist/data.d.cts +47 -9
  45. package/dist/data.d.ts +47 -9
  46. package/dist/data.js +12 -9
  47. package/dist/devtools.cjs +81 -57
  48. package/dist/devtools.js +7 -8
  49. package/dist/dispose-GEIG2KOF.js +28 -0
  50. package/dist/ecosystem.cjs +309 -104
  51. package/dist/ecosystem.d.cts +19 -3
  52. package/dist/ecosystem.d.ts +19 -3
  53. package/dist/ecosystem.js +12 -12
  54. package/dist/extras.cjs +2133 -863
  55. package/dist/extras.d.cts +10 -9
  56. package/dist/extras.d.ts +10 -9
  57. package/dist/extras.js +39 -28
  58. package/dist/index.cjs +184 -72
  59. package/dist/index.d.cts +22 -5
  60. package/dist/index.d.ts +22 -5
  61. package/dist/index.js +24 -27
  62. package/dist/motion.cjs +107 -41
  63. package/dist/motion.js +5 -5
  64. package/dist/patterns.cjs +317 -39
  65. package/dist/patterns.d.cts +27 -9
  66. package/dist/patterns.d.ts +27 -9
  67. package/dist/patterns.js +8 -8
  68. package/dist/performance.cjs +308 -216
  69. package/dist/performance.d.cts +2 -2
  70. package/dist/performance.d.ts +2 -2
  71. package/dist/performance.js +8 -9
  72. package/dist/plugin-DVgSnTfK.d.cts +112 -0
  73. package/dist/plugin-DVgSnTfK.d.ts +112 -0
  74. package/dist/plugins.cjs +549 -202
  75. package/dist/plugins.d.cts +3 -3
  76. package/dist/plugins.d.ts +3 -3
  77. package/dist/plugins.js +35 -23
  78. package/dist/signal-EotCj4hS.d.cts +110 -0
  79. package/dist/signal-EotCj4hS.d.ts +110 -0
  80. package/dist/{ssr-BiPRdZ6n.d.cts → ssr-Bli9XRW5.d.cts} +5 -0
  81. package/dist/{ssr-BiPRdZ6n.d.ts → ssr-Bli9XRW5.d.ts} +5 -0
  82. package/dist/{ssr-Y7XOEPEN.js → ssr-XOTUASDO.js} +4 -5
  83. package/dist/ssr.cjs +176 -72
  84. package/dist/ssr.d.cts +9 -3
  85. package/dist/ssr.d.ts +9 -3
  86. package/dist/ssr.js +11 -12
  87. package/dist/{startup-BMpaiMhP.d.ts → startup-BLfSeL15.d.cts} +73 -22
  88. package/dist/{startup-BMpaiMhP.d.cts → startup-BLfSeL15.d.ts} +73 -22
  89. package/dist/{tagFactory-DVoDpHye.d.ts → tagFactory-DFstCLQV.d.cts} +2 -100
  90. package/dist/{tagFactory-DVoDpHye.d.cts → tagFactory-DkaNVUNV.d.ts} +2 -100
  91. package/dist/testing.cjs +2484 -2185
  92. package/dist/testing.d.cts +56 -5
  93. package/dist/testing.d.ts +56 -5
  94. package/dist/testing.js +580 -307
  95. package/dist/ui.cjs +707 -317
  96. package/dist/ui.d.cts +40 -7
  97. package/dist/ui.d.ts +40 -7
  98. package/dist/ui.js +151 -56
  99. package/dist/widgets.cjs +246 -278
  100. package/dist/widgets.js +9 -10
  101. package/package.json +1 -1
  102. package/dist/chunk-2WLZ6757.js +0 -149
  103. package/dist/chunk-CCSJMTRN.js +0 -15
  104. package/dist/chunk-QKRPLZ2V.js +0 -108
  105. package/dist/chunk-VUF4ALSW.js +0 -60
  106. package/dist/chunk-WWV3SJ3L.js +0 -131
  107. package/dist/dispose-46BOMMQJ.js +0 -19
  108. package/dist/plugin-D30wlGW5.d.cts +0 -71
  109. package/dist/plugin-D30wlGW5.d.ts +0 -71
package/dist/ui.cjs CHANGED
@@ -85,28 +85,6 @@ __export(ui_exports, {
85
85
  });
86
86
  module.exports = __toCommonJS(ui_exports);
87
87
 
88
- // src/core/dev.ts
89
- var DEV = typeof __SIBU_DEV__ !== "undefined" ? !!__SIBU_DEV__ : typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
90
- // safe default: off in browser, on in test/dev Node
91
- typeof process !== "undefined" && process.env?.NODE_ENV !== "production"
92
- );
93
- function devAssert(condition, message) {
94
- if ((typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) && !condition) {
95
- throw new Error(`[SibuJS] ${message}`);
96
- }
97
- }
98
- function devWarnLazy(build) {
99
- if (typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) {
100
- const message = build();
101
- if (message) console.warn(`[SibuJS] ${message}`);
102
- }
103
- }
104
- function devWarn(message) {
105
- if (typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) {
106
- console.warn(`[SibuJS] ${message}`);
107
- }
108
- }
109
-
110
88
  // src/core/errors.ts
111
89
  var ERROR_STATE_KEY = /* @__PURE__ */ Symbol.for("sibujs.runtime-errors.v1");
112
90
  function state() {
@@ -195,6 +173,28 @@ function reportError(error, context) {
195
173
  });
196
174
  }
197
175
 
176
+ // src/core/dev.ts
177
+ var DEV = typeof __SIBU_DEV__ !== "undefined" ? !!__SIBU_DEV__ : typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
178
+ // safe default: off in browser, on in test/dev Node
179
+ typeof process !== "undefined" && process.env?.NODE_ENV !== "production"
180
+ );
181
+ function devAssert(condition, message) {
182
+ if ((typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) && !condition) {
183
+ throw new Error(`[SibuJS] ${message}`);
184
+ }
185
+ }
186
+ function devWarnLazy(build) {
187
+ if (typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) {
188
+ const message = build();
189
+ if (message) console.warn(`[SibuJS] ${message}`);
190
+ }
191
+ }
192
+ function devWarn(message) {
193
+ if (typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) {
194
+ console.warn(`[SibuJS] ${message}`);
195
+ }
196
+ }
197
+
198
198
  // src/core/rendering/dispose.ts
199
199
  var elementDisposers = /* @__PURE__ */ new WeakMap();
200
200
  var MAX_DRAIN_TEARDOWNS = 1e4;
@@ -206,6 +206,23 @@ function reportDrainRunaway(label, executed, remaining) {
206
206
  );
207
207
  }
208
208
  var activeBindingCount = 0;
209
+ function resolveCapture(capture) {
210
+ let frame = capture;
211
+ while (frame?.closed) frame = frame.forwardTo;
212
+ return frame ?? null;
213
+ }
214
+ var registrationCaptures = [];
215
+ function currentDisposerCapture() {
216
+ return resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
217
+ }
218
+ function beginDisposerCapture(capture) {
219
+ if (capture === null && registrationCaptures.length === 0) return false;
220
+ registrationCaptures.push(resolveCapture(capture));
221
+ return true;
222
+ }
223
+ function endDisposerCapture() {
224
+ registrationCaptures.pop();
225
+ }
209
226
  function registerDisposer(node, teardown) {
210
227
  let disposers = elementDisposers.get(node);
211
228
  if (!disposers) {
@@ -214,6 +231,61 @@ function registerDisposer(node, teardown) {
214
231
  }
215
232
  disposers.push(teardown);
216
233
  if (DEV) activeBindingCount++;
234
+ const capture = resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
235
+ if (capture) capture.entries.push([node, teardown]);
236
+ }
237
+ function withDisposerRollback(build) {
238
+ const frame = { entries: [], closed: false };
239
+ const captured = frame.entries;
240
+ registrationCaptures.push(frame);
241
+ let result;
242
+ try {
243
+ result = build();
244
+ } catch (err) {
245
+ let executed = 0;
246
+ try {
247
+ while (captured.length > 0) {
248
+ if (executed >= MAX_DRAIN_TEARDOWNS) {
249
+ reportDrainRunaway("rollback", executed, captured.length);
250
+ break;
251
+ }
252
+ const [node, teardown] = captured.pop();
253
+ if (!unregisterDisposer(node, teardown)) continue;
254
+ executed++;
255
+ try {
256
+ teardown();
257
+ } catch (cleanupErr) {
258
+ reportError(cleanupErr, { phase: "cleanup", name: "disposer" });
259
+ }
260
+ }
261
+ } finally {
262
+ frame.closed = true;
263
+ captured.length = 0;
264
+ registrationCaptures.pop();
265
+ }
266
+ throw err;
267
+ }
268
+ frame.closed = true;
269
+ registrationCaptures.pop();
270
+ const parent = resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
271
+ if (parent) {
272
+ for (const entry of captured) {
273
+ if (elementDisposers.get(entry[0])?.includes(entry[1])) parent.entries.push(entry);
274
+ }
275
+ frame.forwardTo = parent;
276
+ }
277
+ captured.length = 0;
278
+ return result;
279
+ }
280
+ function unregisterDisposer(node, teardown) {
281
+ const disposers = elementDisposers.get(node);
282
+ if (!disposers) return false;
283
+ const index = disposers.indexOf(teardown);
284
+ if (index === -1) return false;
285
+ disposers.splice(index, 1);
286
+ if (DEV) activeBindingCount--;
287
+ if (disposers.length === 0) elementDisposers.delete(node);
288
+ return true;
217
289
  }
218
290
  function dispose(node) {
219
291
  const stack = [node];
@@ -227,33 +299,34 @@ function dispose(node) {
227
299
  }
228
300
  }
229
301
  for (let i = order.length - 1; i >= 0; i--) {
230
- const current = order[i];
231
- if (elementDisposers.has(current)) {
232
- let executed = 0;
233
- let runaway = false;
234
- while (!runaway) {
235
- const pending = elementDisposers.get(current);
236
- if (!pending || pending.length === 0) break;
237
- const snapshot = pending.slice();
238
- elementDisposers.delete(current);
239
- if (DEV) activeBindingCount -= snapshot.length;
240
- for (let i2 = 0; i2 < snapshot.length; i2++) {
241
- if (executed >= MAX_DRAIN_TEARDOWNS) {
242
- const rest = snapshot.slice(i2);
243
- const added = elementDisposers.get(current);
244
- elementDisposers.set(current, added ? rest.concat(added) : rest);
245
- if (DEV) activeBindingCount += rest.length;
246
- reportDrainRunaway("dispose", executed, rest.length + (added?.length ?? 0));
247
- runaway = true;
248
- break;
249
- }
250
- executed++;
251
- try {
252
- snapshot[i2]();
253
- } catch (err) {
254
- reportError(err, { phase: "cleanup", name: "disposer" });
255
- }
256
- }
302
+ disposeNodeOwn(order[i]);
303
+ }
304
+ }
305
+ function disposeNodeOwn(node) {
306
+ if (!elementDisposers.has(node)) return;
307
+ let executed = 0;
308
+ let runaway = false;
309
+ while (!runaway) {
310
+ const pending = elementDisposers.get(node);
311
+ if (!pending || pending.length === 0) break;
312
+ const snapshot = pending.slice();
313
+ elementDisposers.delete(node);
314
+ if (DEV) activeBindingCount -= snapshot.length;
315
+ for (let i = 0; i < snapshot.length; i++) {
316
+ if (executed >= MAX_DRAIN_TEARDOWNS) {
317
+ const rest = snapshot.slice(i);
318
+ const added = elementDisposers.get(node);
319
+ elementDisposers.set(node, added ? rest.concat(added) : rest);
320
+ if (DEV) activeBindingCount += rest.length;
321
+ reportDrainRunaway("dispose", executed, rest.length + (added?.length ?? 0));
322
+ runaway = true;
323
+ break;
324
+ }
325
+ executed++;
326
+ try {
327
+ snapshot[i]();
328
+ } catch (err) {
329
+ reportError(err, { phase: "cleanup", name: "disposer" });
257
330
  }
258
331
  }
259
332
  }
@@ -428,6 +501,7 @@ function retrack(effectFn, subscriber) {
428
501
  }
429
502
  currentSubscriber = subscriber;
430
503
  const sub = subscriber;
504
+ if (sub._cap === void 0) sub._cap = currentDisposerCapture();
431
505
  const epoch = ++subscriberEpochCounter;
432
506
  sub._epoch = epoch;
433
507
  sub._structDirty = false;
@@ -619,7 +693,12 @@ function drainQueue() {
619
693
  quarantined.add(sub);
620
694
  continue;
621
695
  }
622
- safeInvoke(sub);
696
+ const pushed = beginDisposerCapture(sub._cap ?? null);
697
+ try {
698
+ safeInvoke(sub);
699
+ } finally {
700
+ if (pushed) endDisposerCapture();
701
+ }
623
702
  }
624
703
  }
625
704
  function drainNotificationQueue() {
@@ -755,7 +834,7 @@ function forEachSubscriber(signal2, visit) {
755
834
  }
756
835
 
757
836
  // src/reactivity/track.ts
758
- var _runtimeVersion = true ? "4.5.0" : "dev";
837
+ var _runtimeVersion = true ? "4.6.0" : "dev";
759
838
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
760
839
  function resolveReactiveApi() {
761
840
  const g = globalThis;
@@ -921,169 +1000,6 @@ function derived(getter, options) {
921
1000
  return computedGetter;
922
1001
  }
923
1002
 
924
- // src/core/ssr-context.ts
925
- var SSR_KEY = /* @__PURE__ */ Symbol.for("sibujs.ssr.v1");
926
- function detectSSRShared() {
927
- let detected = null;
928
- try {
929
- if (typeof process !== "undefined" && process.versions && process.versions.node) {
930
- let mod = null;
931
- const getBuiltin = process.getBuiltinModule;
932
- if (typeof getBuiltin === "function") {
933
- mod = getBuiltin("node:async_hooks");
934
- } else if (typeof require === "function") {
935
- mod = require("async_hooks");
936
- }
937
- if (mod) detected = new mod.AsyncLocalStorage();
938
- }
939
- } catch {
940
- detected = null;
941
- }
942
- return {
943
- als: detected,
944
- fallbackStore: { ssr: false, suspenseIdCounter: 0, locale: void 0 },
945
- fallbackDepth: 0
946
- };
947
- }
948
- var _shared = globalThis[SSR_KEY] ?? (globalThis[SSR_KEY] = detectSSRShared());
949
- var als = _shared.als;
950
- var fallbackStore = _shared.fallbackStore;
951
- _shared.fallbackDepth ?? (_shared.fallbackDepth = 0);
952
- function getSSRStore() {
953
- if (als) {
954
- const s = als.getStore();
955
- if (s) return s;
956
- }
957
- return fallbackStore;
958
- }
959
- function isSSR() {
960
- return getSSRStore().ssr;
961
- }
962
-
963
- // src/core/signals/effect.ts
964
- var _g = globalThis;
965
- var MAX_RERUNS = 100;
966
- function flushUserCleanups(ctx) {
967
- const list = ctx.userCleanups;
968
- if (list.length === 0) return;
969
- ctx.userCleanups = [];
970
- for (let i = list.length - 1; i >= 0; i--) {
971
- try {
972
- list[i]();
973
- } catch (err) {
974
- reportError(err, { phase: "cleanup" });
975
- }
976
- }
977
- }
978
- function drainReruns(ctx) {
979
- let reruns = 1;
980
- do {
981
- ctx.rerunPending = false;
982
- if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
983
- retrack2(ctx.bodyFn, ctx.subscriber);
984
- } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
985
- if (ctx.rerunPending) {
986
- ctx.rerunPending = false;
987
- reportError(
988
- new Error(
989
- `effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
990
- ),
991
- { phase: "effect" }
992
- );
993
- }
994
- }
995
- function disposeEffect(ctx) {
996
- if (ctx.disposed) return;
997
- ctx.disposed = true;
998
- const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
999
- if (h) {
1000
- try {
1001
- h.emit("effect:destroy", { effectFn: ctx.fn });
1002
- } catch {
1003
- }
1004
- }
1005
- try {
1006
- if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
1007
- } catch (err) {
1008
- if (typeof console !== "undefined") {
1009
- console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
1010
- }
1011
- }
1012
- try {
1013
- cleanup2(ctx.subscriber);
1014
- } catch (err) {
1015
- if (typeof console !== "undefined") {
1016
- console.warn("[SibuJS effect] dispose threw:", err);
1017
- }
1018
- }
1019
- }
1020
- function effect(effectFn, options) {
1021
- devAssert(typeof effectFn === "function", "effect: argument must be a function.");
1022
- if (isSSR()) return () => {
1023
- };
1024
- const ctx = {
1025
- fn: effectFn,
1026
- onError: options?.onError,
1027
- userCleanups: [],
1028
- running: false,
1029
- rerunPending: false,
1030
- disposed: false,
1031
- onCleanup: null,
1032
- subscriber: null,
1033
- bodyFn: null
1034
- };
1035
- ctx.onCleanup = (fn) => {
1036
- ctx.userCleanups.push(fn);
1037
- };
1038
- const onErrorCaptured = ctx.onError;
1039
- ctx.bodyFn = onErrorCaptured ? () => {
1040
- try {
1041
- ctx.fn(ctx.onCleanup);
1042
- } catch (err) {
1043
- onErrorCaptured(err);
1044
- }
1045
- } : () => {
1046
- ctx.fn(ctx.onCleanup);
1047
- };
1048
- const sub = (() => {
1049
- if (ctx.disposed) return;
1050
- if (ctx.running) {
1051
- ctx.rerunPending = true;
1052
- return;
1053
- }
1054
- ctx.running = true;
1055
- try {
1056
- ctx.rerunPending = false;
1057
- if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
1058
- retrack2(ctx.bodyFn, sub);
1059
- if (ctx.rerunPending) drainReruns(ctx);
1060
- } finally {
1061
- ctx.running = false;
1062
- ctx.rerunPending = false;
1063
- }
1064
- });
1065
- sub.depsHead = null;
1066
- sub.depsTail = null;
1067
- sub._epoch = 0;
1068
- sub._structDirty = false;
1069
- sub._hasComputedDep = false;
1070
- sub._errorPhase = "effect";
1071
- sub._runEpoch = 0;
1072
- sub._runs = 0;
1073
- ctx.subscriber = sub;
1074
- ctx.running = true;
1075
- try {
1076
- retrack2(ctx.bodyFn, ctx.subscriber);
1077
- if (ctx.rerunPending) drainReruns(ctx);
1078
- } finally {
1079
- ctx.running = false;
1080
- ctx.rerunPending = false;
1081
- }
1082
- const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1083
- if (hook) hook.emit("effect:create", { effectFn });
1084
- return () => disposeEffect(ctx);
1085
- }
1086
-
1087
1003
  // src/reactivity/batch.ts
1088
1004
  var batchDepth = 0;
1089
1005
  var pendingSignals = /* @__PURE__ */ new Set();
@@ -1135,7 +1051,7 @@ var enqueueBatchedSignal = API2.enqueueBatchedSignal;
1135
1051
  var isBatching = API2.isBatching;
1136
1052
 
1137
1053
  // src/core/signals/signal.ts
1138
- var _g2 = globalThis;
1054
+ var _g = globalThis;
1139
1055
  function signal(initial, options) {
1140
1056
  const state2 = {
1141
1057
  value: initial,
@@ -1166,7 +1082,7 @@ function signal(initial, options) {
1166
1082
  state2.value = newValue;
1167
1083
  state2.__v++;
1168
1084
  if (DEV) {
1169
- const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1085
+ const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1170
1086
  if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1171
1087
  }
1172
1088
  if (!enqueueBatchedSignal(state2)) {
@@ -1180,7 +1096,7 @@ function signal(initial, options) {
1180
1096
  if (Object.is(newValue, prev)) return;
1181
1097
  state2.value = newValue;
1182
1098
  state2.__v++;
1183
- const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1099
+ const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1184
1100
  if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1185
1101
  if (!enqueueBatchedSignal(state2)) {
1186
1102
  notifySubscribers2(state2);
@@ -1198,11 +1114,82 @@ function signal(initial, options) {
1198
1114
  }
1199
1115
  };
1200
1116
  }
1201
- if (DEV) {
1202
- const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1203
- if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
1117
+ if (DEV) {
1118
+ const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1119
+ if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
1120
+ }
1121
+ return [get, set];
1122
+ }
1123
+
1124
+ // src/core/ssr-context.ts
1125
+ var SSR_KEY = /* @__PURE__ */ Symbol.for("sibujs.ssr.v1");
1126
+ function detectSSRShared() {
1127
+ let detected = null;
1128
+ try {
1129
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
1130
+ let mod = null;
1131
+ const getBuiltin = process.getBuiltinModule;
1132
+ if (typeof getBuiltin === "function") {
1133
+ mod = getBuiltin("node:async_hooks");
1134
+ } else if (typeof require === "function") {
1135
+ mod = require("async_hooks");
1136
+ }
1137
+ if (mod) detected = new mod.AsyncLocalStorage();
1138
+ }
1139
+ } catch {
1140
+ detected = null;
1141
+ }
1142
+ return {
1143
+ als: detected,
1144
+ fallbackStore: { ssr: false, suspenseIdCounter: 0, locale: void 0 },
1145
+ fallbackDepth: 0
1146
+ };
1147
+ }
1148
+ var _shared = globalThis[SSR_KEY] ?? (globalThis[SSR_KEY] = detectSSRShared());
1149
+ var als = _shared.als;
1150
+ var fallbackStore = _shared.fallbackStore;
1151
+ _shared.fallbackDepth ?? (_shared.fallbackDepth = 0);
1152
+ function getSSRStore() {
1153
+ if (als) {
1154
+ const s = als.getStore();
1155
+ if (s) return s;
1156
+ }
1157
+ return fallbackStore;
1158
+ }
1159
+ function getRequestStore() {
1160
+ if (als) return als.getStore() ?? null;
1161
+ return _shared.fallbackDepth > 0 ? fallbackStore : null;
1162
+ }
1163
+ function isSSR() {
1164
+ return getSSRStore().ssr;
1165
+ }
1166
+
1167
+ // src/reactivity/domBinding.ts
1168
+ function domBinding(commit, ownerNode) {
1169
+ if (isSSR()) return () => {
1170
+ };
1171
+ return reactiveBinding2(commit, ownerNode);
1172
+ }
1173
+
1174
+ // src/utils/adoptThenable.ts
1175
+ function adoptThenable(value) {
1176
+ if (value === null || typeof value !== "object" && typeof value !== "function") return null;
1177
+ let then;
1178
+ try {
1179
+ then = value.then;
1180
+ } catch (error) {
1181
+ return Promise.reject(error);
1204
1182
  }
1205
- return [get, set];
1183
+ if (typeof then !== "function") return null;
1184
+ return new Promise((resolve, reject) => {
1185
+ queueMicrotask(() => {
1186
+ try {
1187
+ Reflect.apply(then, value, [resolve, reject]);
1188
+ } catch (error) {
1189
+ reject(error);
1190
+ }
1191
+ });
1192
+ });
1206
1193
  }
1207
1194
 
1208
1195
  // src/ui/form.ts
@@ -1232,10 +1219,14 @@ function maxLength(max2, message) {
1232
1219
  }
1233
1220
  function matchesPattern(regex, message = "Invalid format") {
1234
1221
  return (value) => {
1235
- if (value && !regex.test(value)) {
1236
- return message;
1222
+ if (!value) return null;
1223
+ const savedLastIndex = regex.lastIndex;
1224
+ regex.lastIndex = 0;
1225
+ try {
1226
+ return regex.test(value) ? null : message;
1227
+ } finally {
1228
+ regex.lastIndex = savedLastIndex;
1237
1229
  }
1238
- return null;
1239
1230
  };
1240
1231
  }
1241
1232
  function email(message = "Invalid email address") {
@@ -1284,13 +1275,13 @@ function bindField(field, extras) {
1284
1275
  const mergedOn = extraOn && typeof extraOn === "object" ? { ...extraOn, ...fieldOn } : fieldOn;
1285
1276
  const onElement = (el) => {
1286
1277
  if (el instanceof HTMLSelectElement && el.multiple) {
1287
- const stop = effect(() => {
1278
+ const stop = domBinding(() => {
1288
1279
  const v = field.value();
1289
1280
  const selected = Array.isArray(v) ? v.map(String) : [];
1290
1281
  for (const opt of Array.from(el.options)) {
1291
1282
  opt.selected = selected.includes(opt.value);
1292
1283
  }
1293
- });
1284
+ }, el);
1294
1285
  registerDisposer(el, stop);
1295
1286
  }
1296
1287
  if (typeof extraOnElement === "function") {
@@ -1310,6 +1301,7 @@ function form(config) {
1310
1301
  const fieldEntries = Object.entries(config);
1311
1302
  const fieldMap = {};
1312
1303
  const [manualErrors, setManualErrors] = signal({});
1304
+ const fieldErrors = [];
1313
1305
  for (const [name, cfg] of fieldEntries) {
1314
1306
  const [value, setValue] = signal(cfg.initial);
1315
1307
  const [isTouched, setTouched] = signal(false);
@@ -1324,6 +1316,7 @@ function form(config) {
1324
1316
  }
1325
1317
  return null;
1326
1318
  });
1319
+ fieldErrors.push(error);
1327
1320
  const wrappedSet = (next) => {
1328
1321
  setValue(next);
1329
1322
  setManualErrors((prev) => {
@@ -1400,12 +1393,22 @@ function form(config) {
1400
1393
  field.touch();
1401
1394
  }
1402
1395
  if (isValid()) {
1403
- const result = onSubmit(values());
1404
- if (result && typeof result.then === "function") {
1396
+ const report = (error) => reportError(error, { phase: "async", name: "form.handleSubmit" });
1397
+ let pending;
1398
+ try {
1399
+ pending = adoptThenable(onSubmit(values()));
1400
+ } catch (error) {
1401
+ report(error);
1402
+ return;
1403
+ }
1404
+ if (pending) {
1405
1405
  setSubmitting(true);
1406
- result.then(
1406
+ pending.then(
1407
1407
  () => setSubmitting(false),
1408
- () => setSubmitting(false)
1408
+ (error) => {
1409
+ setSubmitting(false);
1410
+ report(error);
1411
+ }
1409
1412
  );
1410
1413
  }
1411
1414
  }
@@ -1419,6 +1422,17 @@ function form(config) {
1419
1422
  function setError(field, message) {
1420
1423
  setManualErrors((prev) => ({ ...prev, [field]: message }));
1421
1424
  }
1425
+ let disposed = false;
1426
+ function dispose2() {
1427
+ if (disposed) return;
1428
+ disposed = true;
1429
+ errors.dispose();
1430
+ isValid.dispose();
1431
+ isDirty.dispose();
1432
+ touchedState.dispose();
1433
+ values.dispose();
1434
+ for (const error of fieldErrors) error.dispose();
1435
+ }
1422
1436
  return {
1423
1437
  fields: fieldMap,
1424
1438
  errors,
@@ -1429,7 +1443,8 @@ function form(config) {
1429
1443
  values,
1430
1444
  handleSubmit,
1431
1445
  reset,
1432
- setError
1446
+ setError,
1447
+ dispose: dispose2
1433
1448
  };
1434
1449
  }
1435
1450
 
@@ -1508,7 +1523,7 @@ function VirtualList(props) {
1508
1523
  content.appendChild(itemEl);
1509
1524
  }
1510
1525
  };
1511
- registerDisposer(container, effect(update));
1526
+ registerDisposer(container, domBinding(update, container));
1512
1527
  return container;
1513
1528
  }
1514
1529
 
@@ -1518,11 +1533,14 @@ function intersection(options) {
1518
1533
  const [ratio, setRatio] = signal(0);
1519
1534
  let observer = null;
1520
1535
  let currentElement = null;
1536
+ let generation = 0;
1521
1537
  function observe(element) {
1522
1538
  if (typeof IntersectionObserver === "undefined") return;
1523
1539
  unobserve();
1524
1540
  currentElement = element;
1541
+ const observation = ++generation;
1525
1542
  observer = new IntersectionObserver((entries) => {
1543
+ if (observation !== generation) return;
1526
1544
  const entry = entries[0];
1527
1545
  if (entry) {
1528
1546
  setIsIntersecting(entry.isIntersecting);
@@ -1532,8 +1550,10 @@ function intersection(options) {
1532
1550
  observer.observe(element);
1533
1551
  }
1534
1552
  function unobserve() {
1553
+ generation++;
1535
1554
  if (observer) {
1536
1555
  if (currentElement) observer.unobserve(currentElement);
1556
+ observer.takeRecords();
1537
1557
  observer.disconnect();
1538
1558
  observer = null;
1539
1559
  currentElement = null;
@@ -1552,17 +1572,26 @@ function lazyLoad(element, loader, options) {
1552
1572
  return () => {
1553
1573
  };
1554
1574
  }
1575
+ let done = false;
1576
+ const stop = () => {
1577
+ done = true;
1578
+ observer.takeRecords();
1579
+ observer.disconnect();
1580
+ };
1555
1581
  const observer = new IntersectionObserver((entries) => {
1582
+ if (done) return;
1556
1583
  for (const entry of entries) {
1557
1584
  if (entry.isIntersecting) {
1585
+ stop();
1558
1586
  loader();
1559
- observer.disconnect();
1560
1587
  break;
1561
1588
  }
1562
1589
  }
1563
1590
  }, options);
1564
1591
  observer.observe(element);
1565
- return () => observer.disconnect();
1592
+ return () => {
1593
+ if (!done) stop();
1594
+ };
1566
1595
  }
1567
1596
 
1568
1597
  // src/ui/inputMask.ts
@@ -1717,6 +1746,7 @@ function focus() {
1717
1746
  const dispose2 = () => {
1718
1747
  if (disposed) return;
1719
1748
  disposed = true;
1749
+ unregisterDisposer(element, dispose2);
1720
1750
  element.removeEventListener("focus", onFocus);
1721
1751
  element.removeEventListener("blur", onBlur);
1722
1752
  if (currentElement === element) currentElement = null;
@@ -1785,14 +1815,19 @@ function FocusTrap(nodes, options = {}) {
1785
1815
  }
1786
1816
  };
1787
1817
  container.addEventListener("keydown", onTrapKeydown);
1818
+ let disposed = false;
1788
1819
  if (options.autoFocus !== false) {
1789
1820
  queueMicrotask(() => {
1821
+ if (disposed || !container.isConnected) return;
1790
1822
  const first = getFocusable()[0];
1791
1823
  first?.focus();
1792
1824
  });
1793
1825
  }
1794
1826
  let trapObserver = null;
1795
1827
  function restoreFocusAndCleanup() {
1828
+ if (disposed) return;
1829
+ disposed = true;
1830
+ unregisterDisposer(container, restoreFocusAndCleanup);
1796
1831
  if (options.restoreFocus !== false) previouslyFocused?.focus();
1797
1832
  container.removeEventListener("keydown", onTrapKeydown);
1798
1833
  if (trapObserver) {
@@ -1807,29 +1842,45 @@ function FocusTrap(nodes, options = {}) {
1807
1842
  }
1808
1843
  });
1809
1844
  queueMicrotask(() => {
1810
- if (container.isConnected) {
1811
- trapObserver.observe(container, { childList: true, subtree: true });
1812
- }
1845
+ const observer = trapObserver;
1846
+ if (disposed || !container.isConnected || !observer) return;
1847
+ observer.observe(container, { childList: true, subtree: true });
1813
1848
  });
1814
1849
  }
1815
1850
  registerDisposer(container, restoreFocusAndCleanup);
1816
1851
  return container;
1817
1852
  }
1853
+ function isApplePlatform() {
1854
+ if (typeof navigator === "undefined") return false;
1855
+ const nav = navigator;
1856
+ return /mac|iphone|ipad|ipod/i.test(nav.userAgentData?.platform || nav.platform || "");
1857
+ }
1818
1858
  function hotkey(combo, handler, options = {}) {
1819
1859
  let key = combo;
1820
1860
  let needCtrl = options.ctrl ?? false;
1821
1861
  let needShift = options.shift ?? false;
1822
1862
  let needAlt = options.alt ?? false;
1823
1863
  let needMeta = options.meta ?? false;
1824
- if (combo.includes("+")) {
1825
- const parts = combo.toLowerCase().split("+");
1864
+ if (combo === "+") {
1865
+ key = "+";
1866
+ } else if (combo.includes("+")) {
1867
+ const literalPlus = combo.endsWith("++");
1868
+ const parts = (literalPlus ? combo.slice(0, -2) : combo).toLowerCase().split("+");
1869
+ if (literalPlus) parts.push("+");
1826
1870
  key = parts[parts.length - 1];
1871
+ if (key === "") throw new Error(`hotkey("${combo}"): missing key`);
1827
1872
  for (let i = 0; i < parts.length - 1; i++) {
1828
1873
  const mod = parts[i];
1829
1874
  if (mod === "ctrl" || mod === "control") needCtrl = true;
1830
1875
  else if (mod === "shift") needShift = true;
1831
- else if (mod === "alt") needAlt = true;
1876
+ else if (mod === "alt" || mod === "option") needAlt = true;
1832
1877
  else if (mod === "meta" || mod === "cmd" || mod === "command") needMeta = true;
1878
+ else if (mod === "mod") {
1879
+ if (isApplePlatform()) needMeta = true;
1880
+ else needCtrl = true;
1881
+ } else {
1882
+ throw new Error(`hotkey("${combo}"): unknown modifier "${mod}"`);
1883
+ }
1833
1884
  }
1834
1885
  }
1835
1886
  const listener = (e) => {
@@ -1895,6 +1946,11 @@ function announce(message, priority = "polite") {
1895
1946
  var COUNTER_KEY = /* @__PURE__ */ Symbol.for("sibujs.createId.v1");
1896
1947
  var _counter = globalThis[COUNTER_KEY] ?? (globalThis[COUNTER_KEY] = { n: 0 });
1897
1948
  function createId(prefix = "sibu") {
1949
+ const request = getRequestStore();
1950
+ if (request) {
1951
+ request.idCounter = (request.idCounter ?? 0) + 1;
1952
+ return `${prefix}-${request.idCounter}`;
1953
+ }
1898
1954
  _counter.n++;
1899
1955
  return `${prefix}-${_counter.n}`;
1900
1956
  }
@@ -1954,22 +2010,43 @@ function createListbox(container, options = {}) {
1954
2010
  if (multiple) container.setAttribute("aria-multiselectable", "true");
1955
2011
  if (!container.hasAttribute("tabindex")) container.setAttribute("tabindex", "0");
1956
2012
  const [activeValue, setActiveValue] = signal(null);
2013
+ const [selectedValues, setSelectedValues] = signal([]);
1957
2014
  const [selectedValue, setSelectedValue] = signal(null);
1958
2015
  const [activeDescendantId, setActiveDescendantId] = signal(null);
1959
- function stampIds() {
2016
+ let activeValueRef = null;
2017
+ let currentSelection = [];
2018
+ function reconcileOptions() {
1960
2019
  const opts = Array.from(container.querySelectorAll(optionSelector));
2020
+ const selectedSet = new Set(currentSelection);
2021
+ let activeStillPresent = false;
2022
+ const active = activeValueRef;
1961
2023
  for (const opt of opts) {
1962
2024
  if (!opt.id) opt.id = createId("listbox-option");
1963
- if (!opt.hasAttribute("aria-selected")) opt.setAttribute("aria-selected", "false");
2025
+ const ov = opt.dataset.value;
2026
+ if (!opt.hasAttribute("aria-selected")) {
2027
+ opt.setAttribute("aria-selected", ov !== void 0 && selectedSet.has(ov) ? "true" : "false");
2028
+ }
2029
+ if (active !== null && ov === active) activeStillPresent = true;
1964
2030
  }
2031
+ if (active !== null && !activeStillPresent) setActive(null, opts);
2032
+ return opts;
1965
2033
  }
1966
- stampIds();
1967
2034
  function getOptions() {
1968
- return Array.from(container.querySelectorAll(optionSelector));
2035
+ return reconcileOptions();
2036
+ }
2037
+ function isDisabled(opt) {
2038
+ return opt.getAttribute("aria-disabled") === "true" || opt.hasAttribute("disabled");
2039
+ }
2040
+ function enabledOptions() {
2041
+ return getOptions().filter((opt) => !isDisabled(opt) && opt.dataset.value !== void 0);
1969
2042
  }
1970
- function setActive(value) {
2043
+ function optionByValue(value) {
2044
+ return getOptions().find((opt) => opt.dataset.value === value);
2045
+ }
2046
+ function setActive(value, known) {
2047
+ activeValueRef = value;
1971
2048
  setActiveValue(value);
1972
- const opts = getOptions();
2049
+ const opts = known ?? getOptions();
1973
2050
  for (const opt of opts) {
1974
2051
  if (opt.dataset.value === value) {
1975
2052
  opt.setAttribute("data-highlighted", "");
@@ -1985,29 +2062,33 @@ function createListbox(container, options = {}) {
1985
2062
  }
1986
2063
  }
1987
2064
  function select(value) {
1988
- const previous = selectedValue();
1989
- let nextSelectedSet;
2065
+ const target = optionByValue(value);
2066
+ if (!target || isDisabled(target)) return;
2067
+ const previous = selectedValues();
2068
+ let next;
1990
2069
  if (multiple) {
1991
- nextSelectedSet = new Set((previous ?? "").split(",").filter(Boolean));
1992
- if (nextSelectedSet.has(value)) nextSelectedSet.delete(value);
1993
- else nextSelectedSet.add(value);
1994
- setSelectedValue(Array.from(nextSelectedSet).join(","));
2070
+ next = previous.includes(value) ? previous.filter((v) => v !== value) : [...previous, value];
1995
2071
  } else {
1996
- nextSelectedSet = /* @__PURE__ */ new Set([value]);
1997
- setSelectedValue(value);
2072
+ next = [value];
1998
2073
  }
2074
+ currentSelection = next;
2075
+ batch(() => {
2076
+ setSelectedValues(next);
2077
+ setSelectedValue(multiple ? next.join(",") : value);
2078
+ });
1999
2079
  options.onSelect?.(value);
2080
+ const nextSelectedSet = new Set(next);
2000
2081
  const opts = getOptions();
2001
2082
  for (const opt of opts) {
2002
- const ov = opt.dataset.value ?? "";
2003
- opt.setAttribute("aria-selected", nextSelectedSet.has(ov) ? "true" : "false");
2083
+ const ov = opt.dataset.value;
2084
+ opt.setAttribute("aria-selected", ov !== void 0 && nextSelectedSet.has(ov) ? "true" : "false");
2004
2085
  }
2005
2086
  }
2006
2087
  function moveActive(delta) {
2007
- const opts = getOptions();
2088
+ const opts = enabledOptions();
2008
2089
  if (opts.length === 0) return;
2009
- const currentIdx = opts.findIndex((o) => o.dataset.value === activeValue());
2010
- let next = currentIdx + delta;
2090
+ const currentIdx = opts.findIndex((o) => o.dataset.value === activeValueRef);
2091
+ let next = currentIdx === -1 ? delta > 0 ? 0 : opts.length - 1 : currentIdx + delta;
2011
2092
  if (next < 0) next = opts.length - 1;
2012
2093
  if (next >= opts.length) next = 0;
2013
2094
  const nextValue = opts[next].dataset.value ?? null;
@@ -2028,13 +2109,13 @@ function createListbox(container, options = {}) {
2028
2109
  break;
2029
2110
  case "Home": {
2030
2111
  e.preventDefault();
2031
- const opts = getOptions();
2112
+ const opts = enabledOptions();
2032
2113
  if (opts.length > 0) setActive(opts[0].dataset.value ?? null);
2033
2114
  break;
2034
2115
  }
2035
2116
  case "End": {
2036
2117
  e.preventDefault();
2037
- const opts = getOptions();
2118
+ const opts = enabledOptions();
2038
2119
  if (opts.length > 0) setActive(opts[opts.length - 1].dataset.value ?? null);
2039
2120
  break;
2040
2121
  }
@@ -2049,7 +2130,7 @@ function createListbox(container, options = {}) {
2049
2130
  }
2050
2131
  function onClick(e) {
2051
2132
  const target = e.target.closest(optionSelector);
2052
- if (!target || !container.contains(target)) return;
2133
+ if (!target || !container.contains(target) || isDisabled(target)) return;
2053
2134
  const value = target.dataset.value ?? null;
2054
2135
  if (value !== null) {
2055
2136
  setActive(value);
@@ -2058,12 +2139,26 @@ function createListbox(container, options = {}) {
2058
2139
  }
2059
2140
  container.addEventListener("keydown", onKeyDown);
2060
2141
  container.addEventListener("click", onClick);
2142
+ reconcileOptions();
2143
+ let observer = null;
2144
+ if (typeof MutationObserver !== "undefined") {
2145
+ observer = new MutationObserver(() => {
2146
+ if (!disposed) reconcileOptions();
2147
+ });
2148
+ observer.observe(container, { childList: true, subtree: true });
2149
+ }
2150
+ let disposed = false;
2061
2151
  function dispose2() {
2152
+ if (disposed) return;
2153
+ disposed = true;
2154
+ unregisterDisposer(container, dispose2);
2155
+ observer?.disconnect();
2156
+ observer = null;
2062
2157
  container.removeEventListener("keydown", onKeyDown);
2063
2158
  container.removeEventListener("click", onClick);
2064
2159
  }
2065
2160
  registerDisposer(container, dispose2);
2066
- return { activeValue, selectedValue, activeDescendantId, dispose: dispose2 };
2161
+ return { activeValue, selectedValues, selectedValue, activeDescendantId, refresh: reconcileOptions, dispose: dispose2 };
2067
2162
  }
2068
2163
  function createDialogAria(element, options = {}) {
2069
2164
  const titleId = options.labelledBy ?? createId("dialog-title");
@@ -2686,6 +2781,8 @@ function removeGlobalListenerIfIdle() {
2686
2781
  function dialog() {
2687
2782
  const [isOpen, setIsOpen] = signal(false);
2688
2783
  const entry = { close: () => close() };
2784
+ let opened = false;
2785
+ let disposed = false;
2689
2786
  function pushOnStack() {
2690
2787
  if (dialogStack.indexOf(entry) !== -1) return;
2691
2788
  dialogStack.push(entry);
@@ -2697,23 +2794,27 @@ function dialog() {
2697
2794
  removeGlobalListenerIfIdle();
2698
2795
  }
2699
2796
  function open() {
2700
- if (isOpen()) return;
2701
- setIsOpen(true);
2797
+ if (disposed || opened) return;
2798
+ opened = true;
2702
2799
  pushOnStack();
2800
+ setIsOpen(true);
2703
2801
  }
2704
2802
  function close() {
2705
- if (!isOpen()) {
2803
+ if (!opened) {
2706
2804
  removeFromStack();
2707
2805
  return;
2708
2806
  }
2709
- setIsOpen(false);
2807
+ opened = false;
2710
2808
  removeFromStack();
2809
+ setIsOpen(false);
2711
2810
  }
2712
2811
  function toggle() {
2713
- if (isOpen()) close();
2812
+ if (opened) close();
2714
2813
  else open();
2715
2814
  }
2716
2815
  function dispose2() {
2816
+ disposed = true;
2817
+ opened = false;
2717
2818
  removeFromStack();
2718
2819
  setIsOpen(false);
2719
2820
  }
@@ -2918,37 +3019,178 @@ function pagination(options) {
2918
3019
  function eventBus() {
2919
3020
  const listeners = /* @__PURE__ */ new Map();
2920
3021
  function on(event, handler) {
2921
- let set = listeners.get(event);
2922
- if (!set) {
2923
- set = /* @__PURE__ */ new Set();
2924
- listeners.set(event, set);
3022
+ let subs = listeners.get(event);
3023
+ if (!subs) {
3024
+ subs = /* @__PURE__ */ new Map();
3025
+ listeners.set(event, subs);
2925
3026
  }
2926
- set.add(handler);
2927
- return () => off(event, handler);
3027
+ let subscription = subs.get(handler);
3028
+ if (!subscription) {
3029
+ subscription = { handler, active: true };
3030
+ subs.set(handler, subscription);
3031
+ }
3032
+ const own = subscription;
3033
+ return () => {
3034
+ if (listeners.get(event)?.get(handler) !== own) return;
3035
+ off(event, handler);
3036
+ };
2928
3037
  }
2929
3038
  function emit(event, data) {
2930
- const set = listeners.get(event);
2931
- if (set) {
2932
- for (const handler of set) {
2933
- handler(data);
3039
+ const subs = listeners.get(event);
3040
+ if (!subs || subs.size === 0) return;
3041
+ for (const subscription of Array.from(subs.values())) {
3042
+ if (!subscription.active) continue;
3043
+ try {
3044
+ subscription.handler(data);
3045
+ } catch (err) {
3046
+ reportError(err, { phase: "event", name: `eventBus(${String(event)})` });
2934
3047
  }
2935
3048
  }
2936
3049
  }
2937
3050
  function off(event, handler) {
2938
- const set = listeners.get(event);
2939
- if (set) {
2940
- set.delete(handler);
2941
- if (set.size === 0) {
2942
- listeners.delete(event);
2943
- }
3051
+ const subs = listeners.get(event);
3052
+ if (!subs) return;
3053
+ const subscription = subs.get(handler);
3054
+ if (!subscription) return;
3055
+ subscription.active = false;
3056
+ subs.delete(handler);
3057
+ if (subs.size === 0) {
3058
+ listeners.delete(event);
2944
3059
  }
2945
3060
  }
2946
3061
  function clear() {
3062
+ for (const subs of listeners.values()) {
3063
+ for (const subscription of subs.values()) subscription.active = false;
3064
+ }
2947
3065
  listeners.clear();
2948
3066
  }
2949
3067
  return { on, emit, off, clear };
2950
3068
  }
2951
3069
 
3070
+ // src/core/signals/effect.ts
3071
+ var _g2 = globalThis;
3072
+ var MAX_RERUNS = 100;
3073
+ function flushUserCleanups(ctx) {
3074
+ const list = ctx.userCleanups;
3075
+ if (list.length === 0) return;
3076
+ ctx.userCleanups = [];
3077
+ for (let i = list.length - 1; i >= 0; i--) {
3078
+ try {
3079
+ list[i]();
3080
+ } catch (err) {
3081
+ reportError(err, { phase: "cleanup" });
3082
+ }
3083
+ }
3084
+ }
3085
+ function drainReruns(ctx) {
3086
+ let reruns = 1;
3087
+ do {
3088
+ ctx.rerunPending = false;
3089
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
3090
+ retrack2(ctx.bodyFn, ctx.subscriber);
3091
+ } while (ctx.rerunPending && ++reruns <= MAX_RERUNS);
3092
+ if (ctx.rerunPending) {
3093
+ ctx.rerunPending = false;
3094
+ reportError(
3095
+ new Error(
3096
+ `effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
3097
+ ),
3098
+ { phase: "effect" }
3099
+ );
3100
+ }
3101
+ }
3102
+ function disposeEffect(ctx) {
3103
+ if (ctx.disposed) return;
3104
+ ctx.disposed = true;
3105
+ const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3106
+ if (h) {
3107
+ try {
3108
+ h.emit("effect:destroy", { effectFn: ctx.fn });
3109
+ } catch {
3110
+ }
3111
+ }
3112
+ try {
3113
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
3114
+ } catch (err) {
3115
+ if (typeof console !== "undefined") {
3116
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
3117
+ }
3118
+ }
3119
+ try {
3120
+ cleanup2(ctx.subscriber);
3121
+ } catch (err) {
3122
+ if (typeof console !== "undefined") {
3123
+ console.warn("[SibuJS effect] dispose threw:", err);
3124
+ }
3125
+ }
3126
+ }
3127
+ function effect(effectFn, options) {
3128
+ devAssert(typeof effectFn === "function", "effect: argument must be a function.");
3129
+ if (isSSR()) return () => {
3130
+ };
3131
+ const ctx = {
3132
+ fn: effectFn,
3133
+ onError: options?.onError,
3134
+ userCleanups: [],
3135
+ running: false,
3136
+ rerunPending: false,
3137
+ disposed: false,
3138
+ onCleanup: null,
3139
+ subscriber: null,
3140
+ bodyFn: null
3141
+ };
3142
+ ctx.onCleanup = (fn) => {
3143
+ ctx.userCleanups.push(fn);
3144
+ };
3145
+ const onErrorCaptured = ctx.onError;
3146
+ ctx.bodyFn = onErrorCaptured ? () => {
3147
+ try {
3148
+ ctx.fn(ctx.onCleanup);
3149
+ } catch (err) {
3150
+ onErrorCaptured(err);
3151
+ }
3152
+ } : () => {
3153
+ ctx.fn(ctx.onCleanup);
3154
+ };
3155
+ const sub = (() => {
3156
+ if (ctx.disposed) return;
3157
+ if (ctx.running) {
3158
+ ctx.rerunPending = true;
3159
+ return;
3160
+ }
3161
+ ctx.running = true;
3162
+ try {
3163
+ ctx.rerunPending = false;
3164
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
3165
+ retrack2(ctx.bodyFn, sub);
3166
+ if (ctx.rerunPending) drainReruns(ctx);
3167
+ } finally {
3168
+ ctx.running = false;
3169
+ ctx.rerunPending = false;
3170
+ }
3171
+ });
3172
+ sub.depsHead = null;
3173
+ sub.depsTail = null;
3174
+ sub._epoch = 0;
3175
+ sub._structDirty = false;
3176
+ sub._hasComputedDep = false;
3177
+ sub._errorPhase = "effect";
3178
+ sub._runEpoch = 0;
3179
+ sub._runs = 0;
3180
+ ctx.subscriber = sub;
3181
+ ctx.running = true;
3182
+ try {
3183
+ retrack2(ctx.bodyFn, ctx.subscriber);
3184
+ if (ctx.rerunPending) drainReruns(ctx);
3185
+ } finally {
3186
+ ctx.running = false;
3187
+ ctx.rerunPending = false;
3188
+ }
3189
+ const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3190
+ if (hook) hook.emit("effect:create", { effectFn });
3191
+ return () => disposeEffect(ctx);
3192
+ }
3193
+
2952
3194
  // src/ui/lazyEffect.ts
2953
3195
  function lazyEffect(element, effectFn, options) {
2954
3196
  if (typeof IntersectionObserver === "undefined") {
@@ -2984,25 +3226,59 @@ function lazyEffect(element, effectFn, options) {
2984
3226
 
2985
3227
  // src/ui/timers.ts
2986
3228
  function interval(fn, ms) {
2987
- let id = null;
3229
+ let intervalId = null;
3230
+ let resumeId = null;
2988
3231
  let running = false;
2989
- function start() {
3232
+ let nextDue = 0;
3233
+ let remaining = ms;
3234
+ function tick() {
3235
+ nextDue = Date.now() + ms;
3236
+ fn();
3237
+ }
3238
+ function clearTimers() {
3239
+ if (intervalId !== null) {
3240
+ clearInterval(intervalId);
3241
+ intervalId = null;
3242
+ }
3243
+ if (resumeId !== null) {
3244
+ clearTimeout(resumeId);
3245
+ resumeId = null;
3246
+ }
3247
+ }
3248
+ function startRegular() {
3249
+ nextDue = Date.now() + ms;
3250
+ intervalId = setInterval(tick, ms);
3251
+ }
3252
+ function resume() {
2990
3253
  if (running) return;
2991
- id = setInterval(fn, ms);
2992
3254
  running = true;
3255
+ if (remaining >= ms) {
3256
+ startRegular();
3257
+ return;
3258
+ }
3259
+ nextDue = Date.now() + remaining;
3260
+ resumeId = setTimeout(() => {
3261
+ resumeId = null;
3262
+ startRegular();
3263
+ fn();
3264
+ }, remaining);
3265
+ }
3266
+ function pause() {
3267
+ if (!running) return;
3268
+ remaining = Math.min(ms, Math.max(0, nextDue - Date.now()));
3269
+ clearTimers();
3270
+ running = false;
2993
3271
  }
2994
3272
  function stop() {
2995
- if (id !== null) {
2996
- clearInterval(id);
2997
- id = null;
2998
- }
3273
+ clearTimers();
2999
3274
  running = false;
3275
+ remaining = ms;
3000
3276
  }
3001
- start();
3277
+ resume();
3002
3278
  return {
3003
3279
  stop,
3004
- pause: stop,
3005
- resume: start,
3280
+ pause,
3281
+ resume,
3006
3282
  isRunning: () => running
3007
3283
  };
3008
3284
  }
@@ -3034,7 +3310,11 @@ function hover(target) {
3034
3310
  const onLeave = () => setHovered(false);
3035
3311
  target.addEventListener("pointerenter", onEnter);
3036
3312
  target.addEventListener("pointerleave", onLeave);
3313
+ let disposed = false;
3037
3314
  function dispose2() {
3315
+ if (disposed) return;
3316
+ disposed = true;
3317
+ unregisterDisposer(target, dispose2);
3038
3318
  target.removeEventListener("pointerenter", onEnter);
3039
3319
  target.removeEventListener("pointerleave", onLeave);
3040
3320
  }
@@ -3048,21 +3328,37 @@ var _lock = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.scrollLock.v1"),
3048
3328
  savedOverflow: null,
3049
3329
  savedPaddingRight: null
3050
3330
  }));
3331
+ function applyBodyLock() {
3332
+ const body = document.body;
3333
+ if (!body) throw new Error("[scrollLock] document.body is not available yet");
3334
+ const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth;
3335
+ const savedOverflow = body.style.overflow;
3336
+ const savedPaddingRight = body.style.paddingRight;
3337
+ try {
3338
+ body.style.overflow = "hidden";
3339
+ if (scrollBarWidth > 0) {
3340
+ body.style.paddingRight = `${scrollBarWidth}px`;
3341
+ }
3342
+ } catch (err) {
3343
+ try {
3344
+ body.style.overflow = savedOverflow;
3345
+ body.style.paddingRight = savedPaddingRight;
3346
+ } catch {
3347
+ }
3348
+ throw err;
3349
+ }
3350
+ _lock.savedOverflow = savedOverflow;
3351
+ _lock.savedPaddingRight = savedPaddingRight;
3352
+ }
3051
3353
  function scrollLock() {
3052
3354
  let owned = false;
3053
3355
  function lock() {
3054
3356
  if (owned) return;
3357
+ if (_lock.count === 0 && typeof document !== "undefined") {
3358
+ applyBodyLock();
3359
+ }
3055
3360
  owned = true;
3056
3361
  _lock.count++;
3057
- if (_lock.count !== 1 || typeof document === "undefined") return;
3058
- const body = document.body;
3059
- const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth;
3060
- _lock.savedOverflow = body.style.overflow;
3061
- _lock.savedPaddingRight = body.style.paddingRight;
3062
- body.style.overflow = "hidden";
3063
- if (scrollBarWidth > 0) {
3064
- body.style.paddingRight = `${scrollBarWidth}px`;
3065
- }
3066
3362
  }
3067
3363
  function unlock() {
3068
3364
  if (!owned) return;
@@ -3080,13 +3376,33 @@ function scrollLock() {
3080
3376
 
3081
3377
  // src/platform/customElement.ts
3082
3378
  var SVG_NS2 = "http://www.w3.org/2000/svg";
3379
+ var MAX_RENDER_PASSES = 10;
3380
+ var ABANDONED_RENDER = /* @__PURE__ */ Symbol("sibujs.defineElement.abandonedRender");
3083
3381
  function defineElement(name, component, options = {}) {
3084
3382
  if (customElements.get(name)) return;
3085
3383
  const observed = options.observedAttributes || [];
3086
3384
  class SibuElement extends HTMLElement {
3087
3385
  constructor() {
3088
3386
  super();
3089
- this._rendered = null;
3387
+ // Re-rendering is keyed on connection, not on a previous render: a first render
3388
+ // that throws leaves nothing rendered, and the element must still re-render
3389
+ // when a later attribute change fixes the input.
3390
+ this._connected = false;
3391
+ // A render in progress, and whether an attribute changed during it. The
3392
+ // component may write its host's observed attributes while rendering; with
3393
+ // the old subtree kept during the build, rendering again from inside the
3394
+ // callback recursed until the stack overflowed.
3395
+ this._rendering = false;
3396
+ this._dirty = false;
3397
+ // How many teardowns are draining. A host teardown may reconnect the element
3398
+ // (and a nested disconnect may run inside it), and rendering the next
3399
+ // generation from inside that drain would let the drain tear down what the
3400
+ // new render registers. A counter, not a flag: a nested teardown returning
3401
+ // must not declare the outer one finished.
3402
+ this._teardownDepth = 0;
3403
+ // Disconnects so far. A render compares it across its old-subtree teardown
3404
+ // to detect a disconnect that happened inside it.
3405
+ this._disconnects = 0;
3090
3406
  if (options.shadow !== false) {
3091
3407
  this._root = this.attachShadow({ mode: options.mode || "open" });
3092
3408
  } else {
@@ -3096,32 +3412,106 @@ function defineElement(name, component, options = {}) {
3096
3412
  static get observedAttributes() {
3097
3413
  return observed;
3098
3414
  }
3415
+ /** Whether rendering must wait for work already in progress. */
3416
+ get _busy() {
3417
+ return this._rendering || this._teardownDepth > 0;
3418
+ }
3099
3419
  connectedCallback() {
3420
+ this._connected = true;
3421
+ if (this._busy) {
3422
+ this._dirty = true;
3423
+ return;
3424
+ }
3100
3425
  this._render();
3101
3426
  }
3102
3427
  disconnectedCallback() {
3103
- this._teardown();
3428
+ this._connected = false;
3429
+ this._disconnects++;
3430
+ this._teardownDepth++;
3431
+ try {
3432
+ this._teardown();
3433
+ } finally {
3434
+ this._teardownDepth--;
3435
+ if (this._teardownDepth === 0 && this._connected && this._dirty) {
3436
+ this._dirty = false;
3437
+ if (!this._rendering) this._render();
3438
+ }
3439
+ }
3104
3440
  }
3105
- attributeChangedCallback() {
3106
- if (this._rendered) {
3107
- this._render();
3441
+ attributeChangedCallback(_name, oldValue, newValue) {
3442
+ if (oldValue === newValue || !this._connected) return;
3443
+ if (this._busy) {
3444
+ this._dirty = true;
3445
+ return;
3108
3446
  }
3447
+ this._render();
3109
3448
  }
3110
3449
  _teardown() {
3111
- this._rendered = null;
3112
3450
  replaceChildrenSafely(this._root);
3113
- }
3451
+ disposeNodeOwn(this);
3452
+ }
3453
+ /**
3454
+ * Render as a transaction: build the replacement first, commit only if that
3455
+ * succeeds. Tearing the current subtree down before calling the factory
3456
+ * meant a throwing rerender (an invalid attribute, say) left the element
3457
+ * blank with its live state already disposed. Now a failure keeps the
3458
+ * working subtree, releases whatever the failed attempt registered, and is
3459
+ * reported with this element as its node so an enclosing ErrorBoundary can
3460
+ * claim it.
3461
+ */
3114
3462
  _render() {
3115
- this._teardown();
3463
+ this._rendering = true;
3464
+ try {
3465
+ let passes = 0;
3466
+ do {
3467
+ this._dirty = false;
3468
+ if (++passes > MAX_RENDER_PASSES) {
3469
+ reportError(
3470
+ new Error(
3471
+ `[SibuJS] defineElement(${name}): the component changed its own observed attributes on ${MAX_RENDER_PASSES} consecutive renders; stopped re-rendering.`
3472
+ ),
3473
+ { phase: "render", name: `defineElement(${name})`, node: this }
3474
+ );
3475
+ break;
3476
+ }
3477
+ this._renderOnce();
3478
+ } while (this._dirty && this._connected);
3479
+ } finally {
3480
+ this._rendering = false;
3481
+ this._dirty = false;
3482
+ }
3483
+ }
3484
+ _renderOnce() {
3116
3485
  const props = this._getProps();
3486
+ let el;
3487
+ try {
3488
+ el = withDisposerRollback(() => {
3489
+ const built = component(props, this);
3490
+ if (!this._connected) throw ABANDONED_RENDER;
3491
+ return built;
3492
+ });
3493
+ } catch (err) {
3494
+ if (err !== ABANDONED_RENDER) {
3495
+ reportError(err, { phase: "render", name: `defineElement(${name})`, node: this });
3496
+ }
3497
+ return;
3498
+ }
3499
+ const next = [];
3117
3500
  if (options.styles && this._root instanceof ShadowRoot) {
3118
3501
  const styleEl = document.createElement("style");
3119
3502
  styleEl.textContent = options.styles;
3120
- this._root.appendChild(styleEl);
3503
+ next.push(styleEl);
3504
+ }
3505
+ next.push(el);
3506
+ const disconnects = this._disconnects;
3507
+ for (const node of next) node.parentNode?.removeChild(node);
3508
+ replaceChildrenSafely(this._root);
3509
+ if (this._disconnects !== disconnects) {
3510
+ for (const node of next) dispose(node);
3511
+ if (this._connected) this._dirty = true;
3512
+ return;
3121
3513
  }
3122
- const el = component(props, this);
3123
- this._root.appendChild(el);
3124
- this._rendered = el;
3514
+ this._root.replaceChildren(...next);
3125
3515
  }
3126
3516
  _getProps() {
3127
3517
  const props = {};
@@ -3272,7 +3662,7 @@ function validateProps(props, schema) {
3272
3662
  const errors = [];
3273
3663
  for (const [key, def] of Object.entries(schema)) {
3274
3664
  const propDef = typeof def === "function" ? { type: def } : def;
3275
- if (result[key] == null && propDef.default !== void 0) {
3665
+ if (result[key] === void 0 && propDef.default !== void 0) {
3276
3666
  result[key] = typeof propDef.default === "function" ? propDef.default() : propDef.default;
3277
3667
  }
3278
3668
  if (propDef.required && result[key] == null) {
@@ -3298,7 +3688,7 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`);
3298
3688
  for (const [key, def] of Object.entries(schema)) {
3299
3689
  if (typeof def === "function") continue;
3300
3690
  const fallback = def.default;
3301
- if (result[key] == null && fallback !== void 0) {
3691
+ if (result[key] === void 0 && fallback !== void 0) {
3302
3692
  result[key] = typeof fallback === "function" ? fallback() : fallback;
3303
3693
  }
3304
3694
  }