sibujs 1.5.0 → 2.1.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 (208) hide show
  1. package/dist/browser.cjs +332 -121
  2. package/dist/browser.d.cts +5 -0
  3. package/dist/browser.d.ts +5 -0
  4. package/dist/browser.js +6 -6
  5. package/dist/build.cjs +1049 -344
  6. package/dist/build.js +15 -13
  7. package/dist/cdn.global.js +17 -16
  8. package/dist/chunk-2RA7SHDA.js +65 -0
  9. package/dist/chunk-2UPRY23K.js +80 -0
  10. package/dist/{chunk-BMPL52BF.js → chunk-3DZP6OIT.js} +118 -66
  11. package/dist/chunk-3JHCYHWN.js +125 -0
  12. package/dist/{chunk-CZUGLNJS.js → chunk-45YP72ZQ.js} +3 -3
  13. package/dist/{chunk-JCDUJN2F.js → chunk-AMK2TYNW.js} +490 -153
  14. package/dist/{chunk-NHUC2QWH.js → chunk-CWBVQML6.js} +1 -1
  15. package/dist/{chunk-XHK6BDAJ.js → chunk-DRUZZAK4.js} +25 -8
  16. package/dist/{chunk-RJ46C3CS.js → chunk-GWWURC5M.js} +71 -20
  17. package/dist/{chunk-3X2YG6YM.js → chunk-JYD2PWXH.js} +59 -28
  18. package/dist/{chunk-2BYQDGN3.js → chunk-KGYT6UO6.js} +234 -63
  19. package/dist/{chunk-5X6PP2UK.js → chunk-LMLD24FC.js} +2 -2
  20. package/dist/{chunk-M4NLBH4I.js → chunk-LYTCUZ7H.js} +3 -2
  21. package/dist/{chunk-XUEEGU5O.js → chunk-NASX6ST2.js} +16 -4
  22. package/dist/{chunk-VQDZK23A.js → chunk-O6EFQ3KT.js} +181 -66
  23. package/dist/{chunk-BGN5ZMP4.js → chunk-OJ3P4ECI.js} +14 -2
  24. package/dist/chunk-ON5MMR2J.js +1327 -0
  25. package/dist/{chunk-SFKNRVCU.js → chunk-P2HSJDDN.js} +135 -79
  26. package/dist/chunk-QO3WC6FS.js +384 -0
  27. package/dist/{chunk-WZSPOOER.js → chunk-RDTDJCAB.js} +8 -5
  28. package/dist/{chunk-7GRNSCFT.js → chunk-TH2ILCYW.js} +312 -185
  29. package/dist/chunk-UCS6AMJ7.js +79 -0
  30. package/dist/{chunk-VAPYJN4X.js → chunk-V6C4FADE.js} +93 -23
  31. package/dist/{chunk-OUZZEE4S.js → chunk-WANSMF2L.js} +17 -11
  32. package/dist/{chunk-23VV7YD3.js → chunk-WIPZPFBQ.js} +25 -30
  33. package/dist/chunk-WZA53FXU.js +149 -0
  34. package/dist/{chunk-BGTHZHJ5.js → chunk-ZAQSMOED.js} +188 -44
  35. package/dist/{customElement-BL3Uo8dL.d.cts → customElement-CPfIrbvg.d.cts} +14 -10
  36. package/dist/{customElement-BL3Uo8dL.d.ts → customElement-CPfIrbvg.d.ts} +14 -10
  37. package/dist/data.cjs +536 -151
  38. package/dist/data.d.cts +20 -2
  39. package/dist/data.d.ts +20 -2
  40. package/dist/data.js +11 -9
  41. package/dist/devtools.cjs +613 -266
  42. package/dist/devtools.d.cts +1 -1
  43. package/dist/devtools.d.ts +1 -1
  44. package/dist/devtools.js +12 -6
  45. package/dist/ecosystem.cjs +602 -197
  46. package/dist/ecosystem.d.cts +9 -7
  47. package/dist/ecosystem.d.ts +9 -7
  48. package/dist/ecosystem.js +12 -11
  49. package/dist/extras.cjs +3500 -1608
  50. package/dist/extras.d.cts +9 -9
  51. package/dist/extras.d.ts +9 -9
  52. package/dist/extras.js +58 -45
  53. package/dist/index.cjs +1055 -344
  54. package/dist/index.d.cts +85 -8
  55. package/dist/index.d.ts +85 -8
  56. package/dist/index.js +32 -16
  57. package/dist/{introspect-BumjnBKr.d.cts → introspect-2TOlQ7oa.d.cts} +25 -3
  58. package/dist/{introspect-CZrlcaYy.d.ts → introspect-DnIpHQQz.d.ts} +25 -3
  59. package/dist/motion.cjs +122 -63
  60. package/dist/motion.js +4 -4
  61. package/dist/patterns.cjs +450 -110
  62. package/dist/patterns.d.cts +11 -12
  63. package/dist/patterns.d.ts +11 -12
  64. package/dist/patterns.js +7 -7
  65. package/dist/performance.cjs +373 -149
  66. package/dist/performance.d.cts +23 -16
  67. package/dist/performance.d.ts +23 -16
  68. package/dist/performance.js +13 -8
  69. package/dist/plugin-D30wlGW5.d.cts +71 -0
  70. package/dist/plugin-D30wlGW5.d.ts +71 -0
  71. package/dist/plugins.cjs +729 -301
  72. package/dist/plugins.d.cts +10 -3
  73. package/dist/plugins.d.ts +10 -3
  74. package/dist/plugins.js +106 -38
  75. package/dist/{ssr-Do_SiVoL.d.cts → ssr-CrVNy6Pa.d.cts} +9 -15
  76. package/dist/{ssr-Do_SiVoL.d.ts → ssr-CrVNy6Pa.d.ts} +9 -15
  77. package/dist/{ssr-4PBXAOO3.js → ssr-FXD2PPMC.js} +4 -3
  78. package/dist/ssr.cjs +736 -274
  79. package/dist/ssr.d.cts +26 -6
  80. package/dist/ssr.d.ts +26 -6
  81. package/dist/ssr.js +12 -11
  82. package/dist/{tagFactory-DaJ0YWX6.d.cts → tagFactory-S17H2qxu.d.cts} +9 -1
  83. package/dist/{tagFactory-DaJ0YWX6.d.ts → tagFactory-S17H2qxu.d.ts} +9 -1
  84. package/dist/testing.cjs +303 -76
  85. package/dist/testing.d.cts +17 -4
  86. package/dist/testing.d.ts +17 -4
  87. package/dist/testing.js +100 -44
  88. package/dist/ui.cjs +589 -178
  89. package/dist/ui.d.cts +1 -1
  90. package/dist/ui.d.ts +1 -1
  91. package/dist/ui.js +20 -17
  92. package/dist/widgets.cjs +1103 -146
  93. package/dist/widgets.d.cts +104 -2
  94. package/dist/widgets.d.ts +104 -2
  95. package/dist/widgets.js +9 -7
  96. package/package.json +8 -2
  97. package/dist/chunk-32DY64NT.js +0 -282
  98. package/dist/chunk-3AIRKM3B.js +0 -1263
  99. package/dist/chunk-3ARAQO7B.js +0 -398
  100. package/dist/chunk-3CRQALYP.js +0 -877
  101. package/dist/chunk-4EI4AG32.js +0 -482
  102. package/dist/chunk-4MYMUBRS.js +0 -21
  103. package/dist/chunk-5ZYQ6KDD.js +0 -154
  104. package/dist/chunk-6BMPXPUW.js +0 -26
  105. package/dist/chunk-6HLLIF3K.js +0 -398
  106. package/dist/chunk-6LSNVCS2.js +0 -937
  107. package/dist/chunk-6SA3QQES.js +0 -61
  108. package/dist/chunk-77L6NL3X.js +0 -1097
  109. package/dist/chunk-7BF6TK55.js +0 -1097
  110. package/dist/chunk-7TQKR4PP.js +0 -294
  111. package/dist/chunk-7V26P53V.js +0 -712
  112. package/dist/chunk-AZ3ISID5.js +0 -298
  113. package/dist/chunk-B7SWRFUT.js +0 -332
  114. package/dist/chunk-BTU3TJDS.js +0 -365
  115. package/dist/chunk-BW3WT46K.js +0 -937
  116. package/dist/chunk-C6KFWOFV.js +0 -616
  117. package/dist/chunk-CHF5OHIA.js +0 -61
  118. package/dist/chunk-CHJ27IGK.js +0 -26
  119. package/dist/chunk-CMBFNA7L.js +0 -27
  120. package/dist/chunk-DAHRH4ON.js +0 -331
  121. package/dist/chunk-DKOHBI74.js +0 -924
  122. package/dist/chunk-DTCOOBMX.js +0 -725
  123. package/dist/chunk-EBGIRKQY.js +0 -616
  124. package/dist/chunk-EUZND3CB.js +0 -27
  125. package/dist/chunk-EVCZO745.js +0 -365
  126. package/dist/chunk-EWFVA3TJ.js +0 -282
  127. package/dist/chunk-F3FA4F32.js +0 -292
  128. package/dist/chunk-FGOEVHY3.js +0 -60
  129. package/dist/chunk-G3BOQPVO.js +0 -365
  130. package/dist/chunk-GCOK2LC3.js +0 -282
  131. package/dist/chunk-GJPXRJ45.js +0 -37
  132. package/dist/chunk-HGMJFBC7.js +0 -654
  133. package/dist/chunk-JAKHTMQU.js +0 -1000
  134. package/dist/chunk-JCI5M6U6.js +0 -956
  135. package/dist/chunk-K4G4ZQNR.js +0 -286
  136. package/dist/chunk-K5ZUMYVS.js +0 -89
  137. package/dist/chunk-KQPDEVVS.js +0 -398
  138. package/dist/chunk-L6JRBDNS.js +0 -60
  139. package/dist/chunk-LA6KQEDU.js +0 -712
  140. package/dist/chunk-MB6QFH3I.js +0 -2776
  141. package/dist/chunk-MDVXJWFN.js +0 -304
  142. package/dist/chunk-MEZVEBPN.js +0 -2008
  143. package/dist/chunk-MK4ERFYL.js +0 -2249
  144. package/dist/chunk-MLKGABMK.js +0 -9
  145. package/dist/chunk-MQ5GOYPH.js +0 -2249
  146. package/dist/chunk-MYRV7VDM.js +0 -742
  147. package/dist/chunk-N6IZB6KJ.js +0 -567
  148. package/dist/chunk-NEKUBFPT.js +0 -60
  149. package/dist/chunk-NMRUZALC.js +0 -1097
  150. package/dist/chunk-NYVAC6P5.js +0 -37
  151. package/dist/chunk-NZIIMDWI.js +0 -84
  152. package/dist/chunk-OF7UZIVB.js +0 -725
  153. package/dist/chunk-P3XWXJZU.js +0 -282
  154. package/dist/chunk-P6W3STU4.js +0 -2249
  155. package/dist/chunk-PBHF5WKN.js +0 -616
  156. package/dist/chunk-PDZQY43A.js +0 -616
  157. package/dist/chunk-PTQJDMRT.js +0 -146
  158. package/dist/chunk-PZEGYCF5.js +0 -61
  159. package/dist/chunk-QBMDLBU2.js +0 -975
  160. package/dist/chunk-QWZG56ET.js +0 -2744
  161. package/dist/chunk-RQGQSLQK.js +0 -725
  162. package/dist/chunk-SDLZDHKP.js +0 -107
  163. package/dist/chunk-TDGZL5CU.js +0 -365
  164. package/dist/chunk-TNQWPPE6.js +0 -37
  165. package/dist/chunk-TSOKIX5Z.js +0 -654
  166. package/dist/chunk-UHNL42EF.js +0 -2730
  167. package/dist/chunk-UNXCEF6S.js +0 -21
  168. package/dist/chunk-V2XTI523.js +0 -347
  169. package/dist/chunk-VAU366PN.js +0 -2241
  170. package/dist/chunk-VMVDTCXB.js +0 -712
  171. package/dist/chunk-VQNQZCWJ.js +0 -61
  172. package/dist/chunk-VRW3FULF.js +0 -725
  173. package/dist/chunk-WADYRCO2.js +0 -304
  174. package/dist/chunk-WILQZRO4.js +0 -282
  175. package/dist/chunk-WR5D4EGH.js +0 -26
  176. package/dist/chunk-WUHJISPP.js +0 -298
  177. package/dist/chunk-XYU6TZOW.js +0 -182
  178. package/dist/chunk-Y6GP4QGG.js +0 -276
  179. package/dist/chunk-YECR7UIA.js +0 -347
  180. package/dist/chunk-YUTWTI4B.js +0 -654
  181. package/dist/chunk-Z65KYU7I.js +0 -26
  182. package/dist/chunk-Z6POF5YC.js +0 -975
  183. package/dist/chunk-ZBJP6WFL.js +0 -482
  184. package/dist/chunk-ZD6OAMTH.js +0 -277
  185. package/dist/chunk-ZWKZCBO6.js +0 -317
  186. package/dist/contracts-DDrwxvJ-.d.cts +0 -245
  187. package/dist/contracts-DDrwxvJ-.d.ts +0 -245
  188. package/dist/contracts-DOrhwbke.d.cts +0 -245
  189. package/dist/contracts-DOrhwbke.d.ts +0 -245
  190. package/dist/contracts-xo5ckdRP.d.cts +0 -240
  191. package/dist/contracts-xo5ckdRP.d.ts +0 -240
  192. package/dist/customElement-BKQfbSZQ.d.cts +0 -262
  193. package/dist/customElement-BKQfbSZQ.d.ts +0 -262
  194. package/dist/customElement-D2DJp_xn.d.cts +0 -313
  195. package/dist/customElement-D2DJp_xn.d.ts +0 -313
  196. package/dist/customElement-yz8uyk-0.d.cts +0 -308
  197. package/dist/customElement-yz8uyk-0.d.ts +0 -308
  198. package/dist/introspect-Cb0zgpi2.d.cts +0 -477
  199. package/dist/introspect-Y2xNXGSf.d.ts +0 -477
  200. package/dist/plugin-Bek4RhJY.d.cts +0 -43
  201. package/dist/plugin-Bek4RhJY.d.ts +0 -43
  202. package/dist/ssr-3RXHP5ES.js +0 -38
  203. package/dist/ssr-6GIMY5MX.js +0 -38
  204. package/dist/ssr-BA6sxxUd.d.cts +0 -135
  205. package/dist/ssr-BA6sxxUd.d.ts +0 -135
  206. package/dist/ssr-WKUPVSSK.js +0 -36
  207. package/dist/tagFactory-Dl8QCLga.d.cts +0 -23
  208. package/dist/tagFactory-Dl8QCLga.d.ts +0 -23
package/dist/widgets.cjs CHANGED
@@ -39,26 +39,39 @@ function isDev() {
39
39
  var _isDev = isDev();
40
40
  function devAssert(condition, message) {
41
41
  if (_isDev && !condition) {
42
- throw new Error(`[Sibu] ${message}`);
42
+ throw new Error(`[SibuJS] ${message}`);
43
43
  }
44
44
  }
45
45
  function devWarn(message) {
46
46
  if (_isDev) {
47
- console.warn(`[Sibu] ${message}`);
47
+ console.warn(`[SibuJS] ${message}`);
48
48
  }
49
49
  }
50
50
 
51
51
  // src/reactivity/track.ts
52
52
  var _isDev2 = isDev();
53
- var subscriberStack = new Array(32);
54
- var stackCapacity = 32;
53
+ var STACK_INITIAL = 32;
54
+ var STACK_SHRINK_THRESHOLD = 128;
55
+ var subscriberStack = new Array(STACK_INITIAL);
56
+ var stackCapacity = STACK_INITIAL;
55
57
  var stackTop = -1;
56
58
  var currentSubscriber = null;
57
- var signalSubscribers = /* @__PURE__ */ new WeakMap();
58
59
  var SUBS = "__s";
60
+ function syncFastPath(signal2, subs) {
61
+ const size = subs.size;
62
+ if (size === 0) {
63
+ signal2.__f = void 0;
64
+ delete signal2[SUBS];
65
+ } else if (size === 1) {
66
+ signal2.__f = subs.values().next().value;
67
+ } else {
68
+ signal2.__f = void 0;
69
+ }
70
+ }
59
71
  var notifyDepth = 0;
60
72
  var pendingQueue = [];
61
73
  var pendingSet = /* @__PURE__ */ new Set();
74
+ var propagateStack = [];
62
75
  function safeInvoke(sub) {
63
76
  try {
64
77
  sub();
@@ -67,6 +80,47 @@ function safeInvoke(sub) {
67
80
  }
68
81
  }
69
82
  var trackingSuspended = false;
83
+ var subscriberEpochCounter = 0;
84
+ function retrack(effectFn, subscriber) {
85
+ const prev = currentSubscriber;
86
+ currentSubscriber = subscriber;
87
+ const sub = subscriber;
88
+ const epoch = ++subscriberEpochCounter;
89
+ sub._epoch = epoch;
90
+ try {
91
+ effectFn();
92
+ } finally {
93
+ currentSubscriber = prev;
94
+ pruneStaleDeps(sub, epoch);
95
+ }
96
+ }
97
+ function pruneStaleDeps(sub, currentEpoch) {
98
+ if (sub._dep !== void 0) {
99
+ if (sub._depEpoch !== currentEpoch) {
100
+ const sig = sub._dep;
101
+ const subs = sig[SUBS];
102
+ if (subs?.delete(sub)) syncFastPath(sig, subs);
103
+ sub._dep = void 0;
104
+ sub._depEpoch = void 0;
105
+ }
106
+ return;
107
+ }
108
+ const deps = sub._deps;
109
+ if (!deps || deps.size === 0) return;
110
+ let stales;
111
+ for (const [signal2, epoch] of deps) {
112
+ if (epoch !== currentEpoch) {
113
+ (stales ?? (stales = [])).push(signal2);
114
+ }
115
+ }
116
+ if (!stales) return;
117
+ for (const signal2 of stales) {
118
+ deps.delete(signal2);
119
+ const sig = signal2;
120
+ const subs = sig[SUBS];
121
+ if (subs?.delete(sub)) syncFastPath(sig, subs);
122
+ }
123
+ }
70
124
  function track(effectFn, subscriber) {
71
125
  if (!subscriber) subscriber = effectFn;
72
126
  cleanup(subscriber);
@@ -82,37 +136,49 @@ function track(effectFn, subscriber) {
82
136
  } finally {
83
137
  stackTop--;
84
138
  currentSubscriber = stackTop >= 0 ? subscriberStack[stackTop] : null;
139
+ if (stackTop < 0 && stackCapacity > STACK_SHRINK_THRESHOLD) {
140
+ stackCapacity = Math.max(STACK_INITIAL, stackCapacity >>> 1);
141
+ subscriberStack.length = stackCapacity;
142
+ }
85
143
  }
86
144
  return () => cleanup(subscriber);
87
145
  }
88
146
  function recordDependency(signal2) {
89
147
  if (!currentSubscriber) return;
90
148
  const sub = currentSubscriber;
91
- if (sub._dep === signal2) return;
149
+ const epoch = sub._epoch;
150
+ if (sub._dep === signal2) {
151
+ sub._depEpoch = epoch;
152
+ return;
153
+ }
92
154
  const deps = sub._deps;
93
155
  if (deps) {
94
- if (deps.has(signal2)) return;
95
- deps.add(signal2);
156
+ deps.set(signal2, epoch);
96
157
  } else if (sub._dep !== void 0) {
97
- const set = /* @__PURE__ */ new Set();
98
- set.add(sub._dep);
99
- set.add(signal2);
100
- sub._deps = set;
158
+ const map = /* @__PURE__ */ new Map();
159
+ map.set(sub._dep, sub._depEpoch);
160
+ map.set(signal2, epoch);
161
+ sub._deps = map;
101
162
  sub._dep = void 0;
163
+ sub._depEpoch = void 0;
102
164
  } else {
103
165
  sub._dep = signal2;
166
+ sub._depEpoch = epoch;
104
167
  }
105
- let subs = signal2[SUBS];
168
+ const sig = signal2;
169
+ let subs = sig[SUBS];
106
170
  if (!subs) {
107
171
  subs = /* @__PURE__ */ new Set();
108
- signalSubscribers.set(signal2, subs);
109
- signal2[SUBS] = subs;
172
+ sig[SUBS] = subs;
110
173
  }
174
+ const prevSize = subs.size;
111
175
  subs.add(currentSubscriber);
112
- if (subs.size === 1) {
113
- signal2.__f = currentSubscriber;
114
- } else if (signal2.__f !== void 0) {
115
- signal2.__f = void 0;
176
+ if (subs.size !== prevSize) {
177
+ if (subs.size === 1) {
178
+ sig.__f = currentSubscriber;
179
+ } else if (sig.__f !== void 0) {
180
+ sig.__f = void 0;
181
+ }
116
182
  }
117
183
  }
118
184
  function queueSignalNotification(signal2) {
@@ -127,66 +193,102 @@ function queueSignalNotification(signal2) {
127
193
  }
128
194
  }
129
195
  }
130
- var MAX_DRAIN_ITERATIONS = 1e3;
196
+ var maxSubscriberRepeats = 50;
197
+ var maxDrainIterations = 1e6;
198
+ var drainEpoch = 0;
199
+ function tickRepeat(sub) {
200
+ const s = sub;
201
+ if (s._runEpoch !== drainEpoch) {
202
+ s._runEpoch = drainEpoch;
203
+ s._runs = 1;
204
+ return false;
205
+ }
206
+ return ++s._runs > maxSubscriberRepeats;
207
+ }
208
+ function cycleError(sub) {
209
+ if (typeof console !== "undefined") {
210
+ const name = sub.__name ?? "<unnamed>";
211
+ console.error(
212
+ `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
213
+ );
214
+ }
215
+ }
216
+ function absoluteDrainError() {
217
+ if (typeof console !== "undefined") {
218
+ console.error(
219
+ `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
220
+ );
221
+ }
222
+ }
223
+ function drainQueue() {
224
+ let i = 0;
225
+ while (i < pendingQueue.length) {
226
+ if (i >= maxDrainIterations) {
227
+ absoluteDrainError();
228
+ break;
229
+ }
230
+ const sub = pendingQueue[i++];
231
+ if (tickRepeat(sub)) {
232
+ cycleError(sub);
233
+ break;
234
+ }
235
+ pendingSet.delete(sub);
236
+ safeInvoke(sub);
237
+ }
238
+ }
131
239
  function drainNotificationQueue() {
132
240
  if (notifyDepth > 0) return;
133
241
  notifyDepth++;
242
+ drainEpoch++;
134
243
  try {
135
- let i = 0;
136
- while (i < pendingQueue.length) {
137
- if (i >= MAX_DRAIN_ITERATIONS) {
138
- if (typeof console !== "undefined") {
139
- console.error(
140
- `[SibuJS] Notification queue exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
141
- );
142
- }
143
- break;
144
- }
145
- safeInvoke(pendingQueue[i]);
146
- i++;
147
- }
244
+ drainQueue();
148
245
  } finally {
149
- pendingQueue.length = 0;
150
- pendingSet.clear();
151
246
  notifyDepth--;
247
+ if (notifyDepth === 0) {
248
+ pendingQueue.length = 0;
249
+ pendingSet.clear();
250
+ }
152
251
  }
153
252
  }
154
253
  function propagateDirty(sub) {
155
254
  sub();
156
- let sig = sub._sig;
157
- while (sig) {
255
+ const rootSig = sub._sig;
256
+ if (!rootSig) return;
257
+ const stack = propagateStack;
258
+ const baseLen = stack.length;
259
+ stack.push(rootSig);
260
+ while (stack.length > baseLen) {
261
+ const sig = stack.pop();
158
262
  const first = sig.__f;
159
263
  if (first) {
160
264
  if (first._c) {
161
265
  const nSig = first._sig;
162
- nSig._d = true;
163
- sig = nSig;
164
- continue;
165
- }
166
- if (!pendingSet.has(first)) {
266
+ if (!nSig._d) {
267
+ nSig._d = true;
268
+ stack.push(nSig);
269
+ }
270
+ } else if (!pendingSet.has(first)) {
167
271
  pendingSet.add(first);
168
272
  pendingQueue.push(first);
169
273
  }
170
- break;
274
+ continue;
171
275
  }
172
276
  const subs = sig[SUBS];
173
- if (!subs) break;
174
- let nextSig;
277
+ if (!subs) continue;
175
278
  for (const s of subs) {
176
279
  if (s._c) {
177
- s();
178
280
  const nSig = s._sig;
179
- if (nSig && !nextSig) {
180
- nextSig = nSig;
181
- } else if (nSig) {
182
- propagateDirty(s);
281
+ if (nSig && !nSig._d) {
282
+ nSig._d = true;
283
+ stack.push(nSig);
284
+ } else if (!nSig) {
285
+ s();
183
286
  }
184
287
  } else if (!pendingSet.has(s)) {
185
288
  pendingSet.add(s);
186
289
  pendingQueue.push(s);
187
290
  }
188
291
  }
189
- sig = nextSig;
190
292
  }
191
293
  }
192
294
  function notifySubscribers(signal2) {
@@ -202,21 +304,22 @@ function notifySubscribers(signal2) {
202
304
  return;
203
305
  }
204
306
  notifyDepth++;
307
+ drainEpoch++;
205
308
  try {
206
309
  if (first._c) {
207
310
  propagateDirty(first);
311
+ } else if (tickRepeat(first)) {
312
+ cycleError(first);
208
313
  } else {
209
314
  safeInvoke(first);
210
315
  }
211
- let i = 0;
212
- while (i < pendingQueue.length) {
213
- safeInvoke(pendingQueue[i]);
214
- i++;
215
- }
316
+ drainQueue();
216
317
  } finally {
217
- pendingQueue.length = 0;
218
- pendingSet.clear();
219
318
  notifyDepth--;
319
+ if (notifyDepth === 0) {
320
+ pendingQueue.length = 0;
321
+ pendingSet.clear();
322
+ }
220
323
  }
221
324
  return;
222
325
  }
@@ -234,57 +337,45 @@ function notifySubscribers(signal2) {
234
337
  return;
235
338
  }
236
339
  notifyDepth++;
340
+ drainEpoch++;
237
341
  try {
238
- let directCount = 0;
239
342
  for (const sub of subs) {
240
- pendingQueue[directCount++] = sub;
241
- }
242
- for (let i2 = 0; i2 < directCount; i2++) {
243
- if (pendingQueue[i2]._c) {
244
- propagateDirty(pendingQueue[i2]);
245
- }
246
- }
247
- for (let i2 = 0; i2 < directCount; i2++) {
248
- if (!pendingQueue[i2]._c) {
249
- if (!pendingSet.has(pendingQueue[i2])) {
250
- safeInvoke(pendingQueue[i2]);
251
- }
343
+ if (sub._c) {
344
+ propagateDirty(sub);
345
+ } else if (!pendingSet.has(sub)) {
346
+ pendingSet.add(sub);
347
+ pendingQueue.push(sub);
252
348
  }
253
349
  }
254
- let i = directCount;
255
- while (i < pendingQueue.length) {
256
- safeInvoke(pendingQueue[i]);
257
- i++;
258
- }
350
+ drainQueue();
259
351
  } finally {
260
- pendingQueue.length = 0;
261
- pendingSet.clear();
262
352
  notifyDepth--;
353
+ if (notifyDepth === 0) {
354
+ pendingQueue.length = 0;
355
+ pendingSet.clear();
356
+ }
263
357
  }
264
358
  }
265
359
  function cleanup(subscriber) {
266
360
  const sub = subscriber;
267
361
  const singleDep = sub._dep;
268
362
  if (singleDep !== void 0) {
269
- const subs = singleDep[SUBS];
270
- if (subs) {
271
- subs.delete(subscriber);
272
- if (singleDep.__f === subscriber) {
273
- singleDep.__f = void 0;
274
- }
363
+ const sig = singleDep;
364
+ const subs = sig[SUBS];
365
+ if (subs?.delete(subscriber)) {
366
+ syncFastPath(sig, subs);
275
367
  }
276
368
  sub._dep = void 0;
369
+ sub._depEpoch = void 0;
277
370
  return;
278
371
  }
279
372
  const deps = sub._deps;
280
373
  if (!deps || deps.size === 0) return;
281
- for (const signal2 of deps) {
282
- const subs = signal2[SUBS];
283
- if (subs) {
284
- subs.delete(subscriber);
285
- if (signal2.__f === subscriber) {
286
- signal2.__f = void 0;
287
- }
374
+ for (const signal2 of deps.keys()) {
375
+ const sig = signal2;
376
+ const subs = sig[SUBS];
377
+ if (subs?.delete(subscriber)) {
378
+ syncFastPath(sig, subs);
288
379
  }
289
380
  }
290
381
  deps.clear();
@@ -294,6 +385,7 @@ function cleanup(subscriber) {
294
385
  function derived(getter, options) {
295
386
  devAssert(typeof getter === "function", "derived: argument must be a getter function.");
296
387
  const debugName = options?.name;
388
+ const equals = options?.equals;
297
389
  const cs = {};
298
390
  cs._d = false;
299
391
  cs._g = getter;
@@ -304,8 +396,14 @@ function derived(getter, options) {
304
396
  markDirty._c = 1;
305
397
  markDirty._sig = cs;
306
398
  track(() => {
307
- cs._d = false;
308
- cs._v = getter();
399
+ let threw = true;
400
+ try {
401
+ cs._v = getter();
402
+ cs._d = false;
403
+ threw = false;
404
+ } finally {
405
+ if (threw) cs._d = true;
406
+ }
309
407
  }, markDirty);
310
408
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
311
409
  let evaluating = false;
@@ -318,11 +416,16 @@ function derived(getter, options) {
318
416
  if (trackingSuspended) {
319
417
  if (cs._d) {
320
418
  evaluating = true;
419
+ let threw = true;
321
420
  try {
322
- cs._d = false;
323
- cs._v = getter();
421
+ retrack(() => {
422
+ cs._v = getter();
423
+ cs._d = false;
424
+ threw = false;
425
+ }, markDirty);
324
426
  } finally {
325
427
  evaluating = false;
428
+ if (threw) cs._d = true;
326
429
  }
327
430
  }
328
431
  return cs._v;
@@ -331,13 +434,17 @@ function derived(getter, options) {
331
434
  if (cs._d) {
332
435
  const oldValue = cs._v;
333
436
  evaluating = true;
437
+ let threw = true;
334
438
  try {
335
- track(() => {
439
+ retrack(() => {
440
+ const next = getter();
441
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
336
442
  cs._d = false;
337
- cs._v = getter();
443
+ threw = false;
338
444
  }, markDirty);
339
445
  } finally {
340
446
  evaluating = false;
447
+ if (threw) cs._d = true;
341
448
  }
342
449
  if (hook && oldValue !== cs._v) {
343
450
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
@@ -354,6 +461,149 @@ function derived(getter, options) {
354
461
  return computedGetter;
355
462
  }
356
463
 
464
+ // src/core/ssr-context.ts
465
+ var als = null;
466
+ try {
467
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
468
+ const req = Function("return typeof require==='function'?require:null")();
469
+ if (req) {
470
+ const mod = req("node:async_hooks");
471
+ als = new mod.AsyncLocalStorage();
472
+ }
473
+ }
474
+ } catch {
475
+ als = null;
476
+ }
477
+ var fallbackStore = { ssr: false, suspenseIdCounter: 0 };
478
+ function getSSRStore() {
479
+ if (als) {
480
+ const s = als.getStore();
481
+ if (s) return s;
482
+ }
483
+ return fallbackStore;
484
+ }
485
+ function isSSR() {
486
+ return getSSRStore().ssr;
487
+ }
488
+
489
+ // src/core/signals/effect.ts
490
+ var _g = globalThis;
491
+ function effect(effectFn, options) {
492
+ devAssert(typeof effectFn === "function", "effect: argument must be a function.");
493
+ if (isSSR()) return () => {
494
+ };
495
+ const onError = options?.onError;
496
+ let userCleanups = [];
497
+ const onCleanup = (fn) => {
498
+ userCleanups.push(fn);
499
+ };
500
+ const runUserCleanups = () => {
501
+ if (userCleanups.length === 0) return;
502
+ const list = userCleanups;
503
+ userCleanups = [];
504
+ for (let i = list.length - 1; i >= 0; i--) {
505
+ try {
506
+ list[i]();
507
+ } catch (err) {
508
+ if (typeof console !== "undefined") {
509
+ console.warn("[SibuJS effect] onCleanup threw:", err);
510
+ }
511
+ }
512
+ }
513
+ };
514
+ const invokeBody = () => effectFn(onCleanup);
515
+ const wrappedFn = onError ? () => {
516
+ try {
517
+ invokeBody();
518
+ } catch (err) {
519
+ onError(err);
520
+ }
521
+ } : invokeBody;
522
+ let cleanupHandle = () => {
523
+ };
524
+ let running = false;
525
+ let rerunPending = false;
526
+ const MAX_RERUNS = 100;
527
+ const subscriber = () => {
528
+ if (running) {
529
+ rerunPending = true;
530
+ return;
531
+ }
532
+ running = true;
533
+ try {
534
+ let reruns = 0;
535
+ do {
536
+ rerunPending = false;
537
+ runUserCleanups();
538
+ cleanupHandle();
539
+ cleanupHandle = track(wrappedFn, subscriber);
540
+ if (++reruns > MAX_RERUNS) {
541
+ if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
542
+ console.error(
543
+ `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
544
+ );
545
+ }
546
+ rerunPending = false;
547
+ break;
548
+ }
549
+ } while (rerunPending);
550
+ } finally {
551
+ running = false;
552
+ rerunPending = false;
553
+ }
554
+ };
555
+ running = true;
556
+ try {
557
+ let reruns = 0;
558
+ do {
559
+ rerunPending = false;
560
+ runUserCleanups();
561
+ cleanupHandle();
562
+ cleanupHandle = track(wrappedFn, subscriber);
563
+ if (++reruns > MAX_RERUNS) {
564
+ if (_g.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
565
+ console.error(
566
+ `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times on initial run \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
567
+ );
568
+ }
569
+ rerunPending = false;
570
+ break;
571
+ }
572
+ } while (rerunPending);
573
+ } finally {
574
+ running = false;
575
+ rerunPending = false;
576
+ }
577
+ const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
578
+ if (hook) hook.emit("effect:create", { effectFn });
579
+ let disposed = false;
580
+ return () => {
581
+ if (disposed) return;
582
+ disposed = true;
583
+ const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
584
+ if (h) {
585
+ try {
586
+ h.emit("effect:destroy", { effectFn });
587
+ } catch {
588
+ }
589
+ }
590
+ try {
591
+ runUserCleanups();
592
+ } catch (err) {
593
+ if (typeof console !== "undefined") {
594
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
595
+ }
596
+ }
597
+ try {
598
+ cleanupHandle();
599
+ } catch (err) {
600
+ if (typeof console !== "undefined") {
601
+ console.warn("[SibuJS effect] dispose threw:", err);
602
+ }
603
+ }
604
+ };
605
+ }
606
+
357
607
  // src/reactivity/batch.ts
358
608
  var batchDepth = 0;
359
609
  var pendingSignals = /* @__PURE__ */ new Set();
@@ -374,15 +624,18 @@ function enqueueBatchedSignal(signal2) {
374
624
  return true;
375
625
  }
376
626
  function flushBatch() {
377
- for (const signal2 of pendingSignals) {
378
- queueSignalNotification(signal2);
627
+ try {
628
+ for (const signal2 of pendingSignals) {
629
+ queueSignalNotification(signal2);
630
+ }
631
+ } finally {
632
+ pendingSignals.clear();
379
633
  }
380
- pendingSignals.clear();
381
634
  drainNotificationQueue();
382
635
  }
383
636
 
384
637
  // src/core/signals/signal.ts
385
- var _g = globalThis;
638
+ var _g2 = globalThis;
386
639
  var _isDev3 = isDev();
387
640
  function signal(initial, options) {
388
641
  const state = { value: initial };
@@ -403,7 +656,7 @@ function signal(initial, options) {
403
656
  if (_isDev3) {
404
657
  const oldValue = state.value;
405
658
  state.value = newValue;
406
- const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
659
+ const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
407
660
  if (hook) hook.emit("signal:update", { signal: state, name: debugName, oldValue, newValue });
408
661
  } else {
409
662
  state.value = newValue;
@@ -413,18 +666,12 @@ function signal(initial, options) {
413
666
  }
414
667
  }
415
668
  if (_isDev3) {
416
- const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
669
+ const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
417
670
  if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
418
671
  }
419
672
  return [get, set];
420
673
  }
421
674
 
422
- // src/core/ssr-context.ts
423
- var ssrMode = false;
424
- function isSSR() {
425
- return ssrMode;
426
- }
427
-
428
675
  // src/core/signals/watch.ts
429
676
  function watch(getter, callback) {
430
677
  devAssert(typeof getter === "function", "watch: first argument must be a getter function.");
@@ -450,6 +697,8 @@ function watch(getter, callback) {
450
697
  }
451
698
 
452
699
  // src/widgets/Combobox.ts
700
+ var comboboxIdCounter = 0;
701
+ var boundComboboxes = /* @__PURE__ */ new WeakMap();
453
702
  function combobox(options) {
454
703
  const { items, filterFn, itemToString } = options;
455
704
  const defaultFilterFn = (item, q) => {
@@ -506,6 +755,92 @@ function combobox(options) {
506
755
  function close() {
507
756
  setIsOpen(false);
508
757
  }
758
+ function bind(els) {
759
+ const existing = boundComboboxes.get(els.input);
760
+ if (existing) return existing;
761
+ const listboxId = `sibu-combobox-listbox-${++comboboxIdCounter}`;
762
+ els.listbox.id = listboxId;
763
+ els.listbox.setAttribute("role", "listbox");
764
+ els.input.setAttribute("role", "combobox");
765
+ els.input.setAttribute("aria-autocomplete", "list");
766
+ els.input.setAttribute("aria-controls", listboxId);
767
+ const fxTeardown = effect(() => {
768
+ const open2 = isOpen();
769
+ els.input.setAttribute("aria-expanded", open2 ? "true" : "false");
770
+ els.listbox.hidden = !open2;
771
+ const idx = highlightedIndex();
772
+ const filtered = filteredItems();
773
+ let activeId = "";
774
+ for (let i = 0; i < filtered.length; i++) {
775
+ const optEl = els.option(filtered[i], i);
776
+ if (!optEl) continue;
777
+ if (!optEl.id) optEl.id = `${listboxId}-opt-${i}`;
778
+ optEl.setAttribute("role", "option");
779
+ const isHighlighted = i === idx;
780
+ optEl.setAttribute("aria-selected", isHighlighted ? "true" : "false");
781
+ if (isHighlighted) activeId = optEl.id;
782
+ }
783
+ if (activeId) els.input.setAttribute("aria-activedescendant", activeId);
784
+ else els.input.removeAttribute("aria-activedescendant");
785
+ });
786
+ const onInput = () => {
787
+ setQuery(els.input.value);
788
+ open();
789
+ };
790
+ const onKey = (e) => {
791
+ if (e.key === "ArrowDown") {
792
+ e.preventDefault();
793
+ if (!isOpen()) open();
794
+ highlightNext();
795
+ } else if (e.key === "ArrowUp") {
796
+ e.preventDefault();
797
+ if (!isOpen()) open();
798
+ highlightPrev();
799
+ } else if (e.key === "Enter") {
800
+ if (highlightedIndex() >= 0) {
801
+ e.preventDefault();
802
+ selectHighlighted();
803
+ }
804
+ } else if (e.key === "Escape") {
805
+ e.preventDefault();
806
+ close();
807
+ } else if (e.key === "Home") {
808
+ e.preventDefault();
809
+ if (filteredItems().length > 0) setHighlightedIndex(0);
810
+ } else if (e.key === "End") {
811
+ e.preventDefault();
812
+ const len = filteredItems().length;
813
+ if (len > 0) setHighlightedIndex(len - 1);
814
+ }
815
+ };
816
+ let blurTimer = null;
817
+ const onFocus = () => open();
818
+ const onBlur = () => {
819
+ if (blurTimer !== null) clearTimeout(blurTimer);
820
+ blurTimer = setTimeout(() => {
821
+ blurTimer = null;
822
+ if (document.activeElement !== els.input) close();
823
+ }, 100);
824
+ };
825
+ els.input.addEventListener("input", onInput);
826
+ els.input.addEventListener("keydown", onKey);
827
+ els.input.addEventListener("focus", onFocus);
828
+ els.input.addEventListener("blur", onBlur);
829
+ const teardown = () => {
830
+ boundComboboxes.delete(els.input);
831
+ fxTeardown();
832
+ els.input.removeEventListener("input", onInput);
833
+ els.input.removeEventListener("keydown", onKey);
834
+ els.input.removeEventListener("focus", onFocus);
835
+ els.input.removeEventListener("blur", onBlur);
836
+ if (blurTimer !== null) {
837
+ clearTimeout(blurTimer);
838
+ blurTimer = null;
839
+ }
840
+ };
841
+ boundComboboxes.set(els.input, teardown);
842
+ return teardown;
843
+ }
509
844
  return {
510
845
  query,
511
846
  setQuery,
@@ -518,11 +853,13 @@ function combobox(options) {
518
853
  selectHighlighted,
519
854
  isOpen,
520
855
  open,
521
- close
856
+ close,
857
+ bind
522
858
  };
523
859
  }
524
860
 
525
861
  // src/widgets/Tabs.ts
862
+ var boundTablists = /* @__PURE__ */ new WeakMap();
526
863
  function tabs(options) {
527
864
  const { tabs: tabDefs, defaultTab } = options;
528
865
  const initialTab = defaultTab ?? tabDefs.find((t) => !t.disabled)?.id ?? tabDefs[0]?.id ?? "";
@@ -569,6 +906,118 @@ function tabs(options) {
569
906
  function isActive(id) {
570
907
  return activeTab() === id;
571
908
  }
909
+ function bind(els) {
910
+ const existing = boundTablists.get(els.tablist);
911
+ if (existing) return existing;
912
+ const restore = [];
913
+ const prevTablistRole = els.tablist.getAttribute("role");
914
+ els.tablist.setAttribute("role", "tablist");
915
+ restore.push(() => {
916
+ if (prevTablistRole === null) els.tablist.removeAttribute("role");
917
+ else els.tablist.setAttribute("role", prevTablistRole);
918
+ });
919
+ for (const def of tabDefs) {
920
+ const tabEl = els.tabs[def.id];
921
+ if (!tabEl) continue;
922
+ const prevRole = tabEl.getAttribute("role");
923
+ const prevId = tabEl.id;
924
+ const prevDisabled = tabEl.getAttribute("aria-disabled");
925
+ const prevControls = tabEl.getAttribute("aria-controls");
926
+ tabEl.setAttribute("role", "tab");
927
+ tabEl.setAttribute("id", `sibu-tab-${def.id}`);
928
+ if (def.disabled) tabEl.setAttribute("aria-disabled", "true");
929
+ const panelEl = els.panels?.[def.id];
930
+ let prevPanelRole = null;
931
+ let prevPanelId = "";
932
+ let prevPanelLabelledBy = null;
933
+ if (panelEl) {
934
+ prevPanelRole = panelEl.getAttribute("role");
935
+ prevPanelId = panelEl.id;
936
+ prevPanelLabelledBy = panelEl.getAttribute("aria-labelledby");
937
+ panelEl.setAttribute("role", "tabpanel");
938
+ panelEl.setAttribute("id", `sibu-tabpanel-${def.id}`);
939
+ panelEl.setAttribute("aria-labelledby", `sibu-tab-${def.id}`);
940
+ tabEl.setAttribute("aria-controls", `sibu-tabpanel-${def.id}`);
941
+ }
942
+ restore.push(() => {
943
+ if (prevRole === null) tabEl.removeAttribute("role");
944
+ else tabEl.setAttribute("role", prevRole);
945
+ if (prevId === "") tabEl.removeAttribute("id");
946
+ else tabEl.id = prevId;
947
+ if (prevDisabled === null) tabEl.removeAttribute("aria-disabled");
948
+ else tabEl.setAttribute("aria-disabled", prevDisabled);
949
+ if (prevControls === null) tabEl.removeAttribute("aria-controls");
950
+ else tabEl.setAttribute("aria-controls", prevControls);
951
+ tabEl.removeAttribute("aria-selected");
952
+ tabEl.removeAttribute("tabindex");
953
+ if (panelEl) {
954
+ if (prevPanelRole === null) panelEl.removeAttribute("role");
955
+ else panelEl.setAttribute("role", prevPanelRole);
956
+ if (prevPanelId === "") panelEl.removeAttribute("id");
957
+ else panelEl.id = prevPanelId;
958
+ if (prevPanelLabelledBy === null) panelEl.removeAttribute("aria-labelledby");
959
+ else panelEl.setAttribute("aria-labelledby", prevPanelLabelledBy);
960
+ }
961
+ });
962
+ }
963
+ const fxTeardown = effect(() => {
964
+ const active = activeTab();
965
+ for (const def of tabDefs) {
966
+ const tabEl = els.tabs[def.id];
967
+ if (!tabEl) continue;
968
+ const isAct = def.id === active;
969
+ tabEl.setAttribute("aria-selected", isAct ? "true" : "false");
970
+ tabEl.tabIndex = isAct ? 0 : -1;
971
+ const panelEl = els.panels?.[def.id];
972
+ if (panelEl) panelEl.hidden = !isAct;
973
+ }
974
+ });
975
+ const onKey = (e) => {
976
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
977
+ e.preventDefault();
978
+ nextTab();
979
+ els.tabs[activeTab()]?.focus();
980
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
981
+ e.preventDefault();
982
+ prevTab();
983
+ els.tabs[activeTab()]?.focus();
984
+ } else if (e.key === "Home") {
985
+ e.preventDefault();
986
+ const first = tabDefs.find((t) => !t.disabled);
987
+ if (first) {
988
+ setActiveTabState(first.id);
989
+ els.tabs[first.id]?.focus();
990
+ }
991
+ } else if (e.key === "End") {
992
+ e.preventDefault();
993
+ for (let i = tabDefs.length - 1; i >= 0; i--) {
994
+ if (!tabDefs[i].disabled) {
995
+ setActiveTabState(tabDefs[i].id);
996
+ els.tabs[tabDefs[i].id]?.focus();
997
+ break;
998
+ }
999
+ }
1000
+ }
1001
+ };
1002
+ els.tablist.addEventListener("keydown", onKey);
1003
+ const clickHandlers = [];
1004
+ for (const def of tabDefs) {
1005
+ const tabEl = els.tabs[def.id];
1006
+ if (!tabEl) continue;
1007
+ const fn = () => setActiveTab(def.id);
1008
+ tabEl.addEventListener("click", fn);
1009
+ clickHandlers.push({ el: tabEl, fn });
1010
+ }
1011
+ const teardown = () => {
1012
+ boundTablists.delete(els.tablist);
1013
+ fxTeardown();
1014
+ els.tablist.removeEventListener("keydown", onKey);
1015
+ for (const { el, fn } of clickHandlers) el.removeEventListener("click", fn);
1016
+ for (const r of restore) r();
1017
+ };
1018
+ boundTablists.set(els.tablist, teardown);
1019
+ return teardown;
1020
+ }
572
1021
  return {
573
1022
  activeTab,
574
1023
  setActiveTab,
@@ -576,11 +1025,13 @@ function tabs(options) {
576
1025
  nextTab,
577
1026
  prevTab,
578
1027
  /** Reactive check — use inside class/nodes bindings for per-tab reactivity */
579
- isActive
1028
+ isActive,
1029
+ bind
580
1030
  };
581
1031
  }
582
1032
 
583
1033
  // src/widgets/Accordion.ts
1034
+ var boundAccordions = /* @__PURE__ */ new WeakMap();
584
1035
  function accordion(options) {
585
1036
  const { items: itemDefs, multiple = false, defaultExpanded = [] } = options;
586
1037
  const [expandedIds, setExpandedIds] = signal(new Set(defaultExpanded));
@@ -627,6 +1078,86 @@ function accordion(options) {
627
1078
  function isExpanded(id) {
628
1079
  return expandedIds().has(id);
629
1080
  }
1081
+ function bind(els) {
1082
+ const idempotencyKey = els.root ?? (itemDefs.length > 0 ? els.triggers[itemDefs[0].id] : void 0);
1083
+ if (idempotencyKey) {
1084
+ const existing = boundAccordions.get(idempotencyKey);
1085
+ if (existing) return existing;
1086
+ }
1087
+ const restore = [];
1088
+ for (const item of itemDefs) {
1089
+ const trig = els.triggers[item.id];
1090
+ const panel = els.panels[item.id];
1091
+ if (!trig) continue;
1092
+ const prevTrigId = trig.id;
1093
+ const prevTrigControls = trig.getAttribute("aria-controls");
1094
+ trig.id = `sibu-accordion-trigger-${item.id}`;
1095
+ let prevPanelRole = null;
1096
+ let prevPanelId = "";
1097
+ let prevPanelLabelledBy = null;
1098
+ if (panel) {
1099
+ prevPanelRole = panel.getAttribute("role");
1100
+ prevPanelId = panel.id;
1101
+ prevPanelLabelledBy = panel.getAttribute("aria-labelledby");
1102
+ panel.setAttribute("role", "region");
1103
+ panel.id = `sibu-accordion-panel-${item.id}`;
1104
+ panel.setAttribute("aria-labelledby", trig.id);
1105
+ trig.setAttribute("aria-controls", panel.id);
1106
+ }
1107
+ restore.push(() => {
1108
+ if (prevTrigId === "") trig.removeAttribute("id");
1109
+ else trig.id = prevTrigId;
1110
+ if (prevTrigControls === null) trig.removeAttribute("aria-controls");
1111
+ else trig.setAttribute("aria-controls", prevTrigControls);
1112
+ trig.removeAttribute("aria-expanded");
1113
+ if (panel) {
1114
+ if (prevPanelRole === null) panel.removeAttribute("role");
1115
+ else panel.setAttribute("role", prevPanelRole);
1116
+ if (prevPanelId === "") panel.removeAttribute("id");
1117
+ else panel.id = prevPanelId;
1118
+ if (prevPanelLabelledBy === null) panel.removeAttribute("aria-labelledby");
1119
+ else panel.setAttribute("aria-labelledby", prevPanelLabelledBy);
1120
+ }
1121
+ });
1122
+ }
1123
+ const fxTeardown = effect(() => {
1124
+ const ids = expandedIds();
1125
+ for (const item of itemDefs) {
1126
+ const trig = els.triggers[item.id];
1127
+ const panel = els.panels[item.id];
1128
+ if (!trig) continue;
1129
+ const expanded = ids.has(item.id);
1130
+ trig.setAttribute("aria-expanded", expanded ? "true" : "false");
1131
+ if (panel) panel.hidden = !expanded;
1132
+ }
1133
+ });
1134
+ const handlers = [];
1135
+ for (const item of itemDefs) {
1136
+ const trig = els.triggers[item.id];
1137
+ if (!trig) continue;
1138
+ const click = () => toggle(item.id);
1139
+ const key = (e) => {
1140
+ if (e.key === "Enter" || e.key === " ") {
1141
+ e.preventDefault();
1142
+ toggle(item.id);
1143
+ }
1144
+ };
1145
+ trig.addEventListener("click", click);
1146
+ trig.addEventListener("keydown", key);
1147
+ handlers.push({ el: trig, click, key });
1148
+ }
1149
+ const teardown = () => {
1150
+ if (idempotencyKey) boundAccordions.delete(idempotencyKey);
1151
+ fxTeardown();
1152
+ for (const { el, click, key } of handlers) {
1153
+ el.removeEventListener("click", click);
1154
+ el.removeEventListener("keydown", key);
1155
+ }
1156
+ for (const r of restore) r();
1157
+ };
1158
+ if (idempotencyKey) boundAccordions.set(idempotencyKey, teardown);
1159
+ return teardown;
1160
+ }
630
1161
  return {
631
1162
  items,
632
1163
  toggle,
@@ -635,11 +1166,14 @@ function accordion(options) {
635
1166
  expandAll,
636
1167
  collapseAll,
637
1168
  /** Reactive check — use inside class/nodes bindings for per-item reactivity */
638
- isExpanded
1169
+ isExpanded,
1170
+ bind
639
1171
  };
640
1172
  }
641
1173
 
642
1174
  // src/widgets/Popover.ts
1175
+ var popoverIdCounter = 0;
1176
+ var boundPopovers = /* @__PURE__ */ new WeakMap();
643
1177
  function popover() {
644
1178
  const [isOpen, setIsOpen] = signal(false);
645
1179
  function open() {
@@ -651,12 +1185,88 @@ function popover() {
651
1185
  function toggle() {
652
1186
  setIsOpen((prev) => !prev);
653
1187
  }
654
- return { isOpen, open, close, toggle };
1188
+ function bind(els) {
1189
+ const existing = boundPopovers.get(els.trigger);
1190
+ if (existing) return existing;
1191
+ const id = `sibu-popover-${++popoverIdCounter}`;
1192
+ const prevPopoverRole = els.popover.getAttribute("role");
1193
+ const prevPopoverId = els.popover.id;
1194
+ const prevLabelledBy = els.popover.getAttribute("aria-labelledby");
1195
+ const prevTriggerHaspopup = els.trigger.getAttribute("aria-haspopup");
1196
+ const prevTriggerControls = els.trigger.getAttribute("aria-controls");
1197
+ els.popover.setAttribute("role", "dialog");
1198
+ els.popover.id = id;
1199
+ els.trigger.setAttribute("aria-haspopup", "dialog");
1200
+ els.trigger.setAttribute("aria-controls", id);
1201
+ let assignedLabelId = null;
1202
+ if (els.labelledBy) {
1203
+ if (!els.labelledBy.id) {
1204
+ els.labelledBy.id = `${id}-label`;
1205
+ assignedLabelId = els.labelledBy.id;
1206
+ }
1207
+ els.popover.setAttribute("aria-labelledby", els.labelledBy.id);
1208
+ }
1209
+ const fxTeardown = effect(() => {
1210
+ const open2 = isOpen();
1211
+ els.trigger.setAttribute("aria-expanded", open2 ? "true" : "false");
1212
+ els.popover.hidden = !open2;
1213
+ });
1214
+ const onTriggerClick = (e) => {
1215
+ e.preventDefault();
1216
+ toggle();
1217
+ };
1218
+ const onKey = (e) => {
1219
+ if (e.key === "Escape" && isOpen()) {
1220
+ e.stopPropagation();
1221
+ close();
1222
+ els.trigger.focus();
1223
+ }
1224
+ };
1225
+ const onDocPointer = (e) => {
1226
+ if (!isOpen()) return;
1227
+ const t = e.target;
1228
+ if (!t) return;
1229
+ if (els.trigger.contains(t) || els.popover.contains(t)) return;
1230
+ close();
1231
+ };
1232
+ els.trigger.addEventListener("click", onTriggerClick);
1233
+ els.popover.addEventListener("keydown", onKey);
1234
+ els.trigger.addEventListener("keydown", onKey);
1235
+ document.addEventListener("pointerdown", onDocPointer);
1236
+ const teardown = () => {
1237
+ boundPopovers.delete(els.trigger);
1238
+ fxTeardown();
1239
+ els.trigger.removeEventListener("click", onTriggerClick);
1240
+ els.popover.removeEventListener("keydown", onKey);
1241
+ els.trigger.removeEventListener("keydown", onKey);
1242
+ document.removeEventListener("pointerdown", onDocPointer);
1243
+ if (prevPopoverRole === null) els.popover.removeAttribute("role");
1244
+ else els.popover.setAttribute("role", prevPopoverRole);
1245
+ if (prevPopoverId === "") els.popover.removeAttribute("id");
1246
+ else els.popover.id = prevPopoverId;
1247
+ if (prevLabelledBy === null) els.popover.removeAttribute("aria-labelledby");
1248
+ else els.popover.setAttribute("aria-labelledby", prevLabelledBy);
1249
+ if (assignedLabelId && els.labelledBy?.id === assignedLabelId) {
1250
+ els.labelledBy.removeAttribute("id");
1251
+ }
1252
+ if (prevTriggerHaspopup === null) els.trigger.removeAttribute("aria-haspopup");
1253
+ else els.trigger.setAttribute("aria-haspopup", prevTriggerHaspopup);
1254
+ if (prevTriggerControls === null) els.trigger.removeAttribute("aria-controls");
1255
+ else els.trigger.setAttribute("aria-controls", prevTriggerControls);
1256
+ els.trigger.removeAttribute("aria-expanded");
1257
+ };
1258
+ boundPopovers.set(els.trigger, teardown);
1259
+ return teardown;
1260
+ }
1261
+ return { isOpen, open, close, toggle, bind };
655
1262
  }
656
1263
 
657
1264
  // src/widgets/Select.ts
1265
+ var selectIdCounter = 0;
1266
+ var boundSelects = /* @__PURE__ */ new WeakMap();
658
1267
  function select(options) {
659
- const { items, multiple = false } = options;
1268
+ const { items, multiple = false, itemToString, isDisabled } = options;
1269
+ const isItemDisabled = isDisabled ?? (() => false);
660
1270
  const [selectedItems, setSelectedItems] = signal([]);
661
1271
  const [isOpen, setIsOpen] = signal(false);
662
1272
  const [highlightedIndex, setHighlightedIndex] = signal(-1);
@@ -665,6 +1275,7 @@ function select(options) {
665
1275
  return sel.length > 0 ? sel[sel.length - 1] : null;
666
1276
  });
667
1277
  function select2(item) {
1278
+ if (isItemDisabled(item)) return;
668
1279
  if (multiple) {
669
1280
  setSelectedItems((prev) => {
670
1281
  if (prev.includes(item)) return prev;
@@ -696,18 +1307,28 @@ function select(options) {
696
1307
  function close() {
697
1308
  setIsOpen(false);
698
1309
  }
1310
+ function nextEnabled(from, dir) {
1311
+ const len = items.length;
1312
+ if (len === 0) return -1;
1313
+ let i = from;
1314
+ for (let n = 0; n < len; n++) {
1315
+ i = (i + dir + len) % len;
1316
+ if (!isItemDisabled(items[i])) return i;
1317
+ }
1318
+ return -1;
1319
+ }
699
1320
  function highlightNext() {
700
1321
  if (items.length === 0) return;
701
1322
  setHighlightedIndex((prev) => {
702
- const next = prev + 1;
703
- return next >= items.length ? 0 : next;
1323
+ const n = nextEnabled(prev < 0 ? -1 : prev, 1);
1324
+ return n === -1 ? prev : n;
704
1325
  });
705
1326
  }
706
1327
  function highlightPrev() {
707
1328
  if (items.length === 0) return;
708
1329
  setHighlightedIndex((prev) => {
709
- const next = prev - 1;
710
- return next < 0 ? items.length - 1 : next;
1330
+ const n = nextEnabled(prev < 0 ? items.length : prev, -1);
1331
+ return n === -1 ? prev : n;
711
1332
  });
712
1333
  }
713
1334
  function selectHighlighted() {
@@ -719,6 +1340,73 @@ function select(options) {
719
1340
  function clear() {
720
1341
  setSelectedItems([]);
721
1342
  }
1343
+ function bind(els) {
1344
+ const existing = boundSelects.get(els.listbox);
1345
+ if (existing) return existing;
1346
+ const listboxId = `sibu-select-${++selectIdCounter}`;
1347
+ els.listbox.id = listboxId;
1348
+ els.listbox.setAttribute("role", "listbox");
1349
+ els.listbox.setAttribute("aria-multiselectable", multiple ? "true" : "false");
1350
+ if (els.listbox.tabIndex < 0) els.listbox.tabIndex = 0;
1351
+ const toStr = els.itemToString ?? itemToString ?? ((it) => String(it));
1352
+ const fxTeardown = effect(() => {
1353
+ const idx = highlightedIndex();
1354
+ const sel = selectedItems();
1355
+ let activeId = "";
1356
+ for (let i = 0; i < items.length; i++) {
1357
+ const optEl = els.option(items[i], i);
1358
+ if (!optEl) continue;
1359
+ if (!optEl.id) optEl.id = `${listboxId}-opt-${i}`;
1360
+ optEl.setAttribute("role", "option");
1361
+ optEl.setAttribute("aria-selected", sel.includes(items[i]) ? "true" : "false");
1362
+ if (isItemDisabled(items[i])) optEl.setAttribute("aria-disabled", "true");
1363
+ else optEl.removeAttribute("aria-disabled");
1364
+ if (i === idx) activeId = optEl.id;
1365
+ }
1366
+ if (activeId) els.listbox.setAttribute("aria-activedescendant", activeId);
1367
+ else els.listbox.removeAttribute("aria-activedescendant");
1368
+ });
1369
+ let typeBuffer = "";
1370
+ let typeTimer = null;
1371
+ const onKey = (e) => {
1372
+ if (e.key === "ArrowDown") {
1373
+ e.preventDefault();
1374
+ highlightNext();
1375
+ } else if (e.key === "ArrowUp") {
1376
+ e.preventDefault();
1377
+ highlightPrev();
1378
+ } else if (e.key === "Home") {
1379
+ e.preventDefault();
1380
+ if (items.length > 0) setHighlightedIndex(0);
1381
+ } else if (e.key === "End") {
1382
+ e.preventDefault();
1383
+ if (items.length > 0) setHighlightedIndex(items.length - 1);
1384
+ } else if (e.key === "Enter" || e.key === " ") {
1385
+ if (highlightedIndex() >= 0) {
1386
+ e.preventDefault();
1387
+ selectHighlighted();
1388
+ }
1389
+ } else if (e.key.length === 1 && /\S/.test(e.key)) {
1390
+ typeBuffer += e.key.toLowerCase();
1391
+ if (typeTimer !== null) clearTimeout(typeTimer);
1392
+ typeTimer = setTimeout(() => {
1393
+ typeBuffer = "";
1394
+ typeTimer = null;
1395
+ }, 500);
1396
+ const found = items.findIndex((it) => !isItemDisabled(it) && toStr(it).toLowerCase().startsWith(typeBuffer));
1397
+ if (found !== -1) setHighlightedIndex(found);
1398
+ }
1399
+ };
1400
+ els.listbox.addEventListener("keydown", onKey);
1401
+ const teardown = () => {
1402
+ boundSelects.delete(els.listbox);
1403
+ fxTeardown();
1404
+ els.listbox.removeEventListener("keydown", onKey);
1405
+ if (typeTimer !== null) clearTimeout(typeTimer);
1406
+ };
1407
+ boundSelects.set(els.listbox, teardown);
1408
+ return teardown;
1409
+ }
722
1410
  return {
723
1411
  selectedItems,
724
1412
  selectedItem,
@@ -733,21 +1421,28 @@ function select(options) {
733
1421
  highlightNext,
734
1422
  highlightPrev,
735
1423
  selectHighlighted,
736
- clear
1424
+ clear,
1425
+ bind
737
1426
  };
738
1427
  }
739
1428
 
740
1429
  // src/widgets/Tooltip.ts
1430
+ var tooltipIdCounter = 0;
1431
+ var boundTriggers = /* @__PURE__ */ new WeakMap();
741
1432
  function tooltip(options) {
742
1433
  const delay = options?.delay ?? 0;
1434
+ const hideDelay = options?.hideDelay ?? 100;
743
1435
  const [isVisible, setIsVisible] = signal(false);
744
1436
  const [content, setContent] = signal("");
745
1437
  let delayTimer = null;
1438
+ let hideTimer = null;
746
1439
  function show() {
1440
+ if (hideTimer !== null) {
1441
+ clearTimeout(hideTimer);
1442
+ hideTimer = null;
1443
+ }
747
1444
  if (delay > 0) {
748
- if (delayTimer !== null) {
749
- clearTimeout(delayTimer);
750
- }
1445
+ if (delayTimer !== null) clearTimeout(delayTimer);
751
1446
  delayTimer = setTimeout(() => {
752
1447
  setIsVisible(true);
753
1448
  delayTimer = null;
@@ -763,10 +1458,82 @@ function tooltip(options) {
763
1458
  }
764
1459
  setIsVisible(false);
765
1460
  }
766
- return { isVisible, show, hide, content, setContent };
1461
+ function scheduleHide() {
1462
+ if (delayTimer !== null) {
1463
+ clearTimeout(delayTimer);
1464
+ delayTimer = null;
1465
+ }
1466
+ if (hideTimer !== null) clearTimeout(hideTimer);
1467
+ hideTimer = setTimeout(() => {
1468
+ hideTimer = null;
1469
+ setIsVisible(false);
1470
+ }, hideDelay);
1471
+ }
1472
+ function bind(els) {
1473
+ const existing = boundTriggers.get(els.trigger);
1474
+ if (existing) return existing;
1475
+ const id = `sibu-tooltip-${++tooltipIdCounter}`;
1476
+ els.tooltip.setAttribute("role", "tooltip");
1477
+ els.tooltip.id = id;
1478
+ const prevDescribedBy = els.trigger.getAttribute("aria-describedby");
1479
+ els.trigger.setAttribute("aria-describedby", prevDescribedBy ? `${prevDescribedBy} ${id}` : id);
1480
+ const fxTeardown = effect(() => {
1481
+ els.tooltip.hidden = !isVisible();
1482
+ });
1483
+ const onTriggerEnter = () => show();
1484
+ const onTriggerLeave = () => scheduleHide();
1485
+ const onTooltipEnter = () => {
1486
+ if (hideTimer !== null) {
1487
+ clearTimeout(hideTimer);
1488
+ hideTimer = null;
1489
+ }
1490
+ };
1491
+ const onTooltipLeave = () => scheduleHide();
1492
+ const onFocus = () => show();
1493
+ const onBlur = () => hide();
1494
+ const onKey = (e) => {
1495
+ if (e.key === "Escape" && isVisible()) {
1496
+ e.stopPropagation();
1497
+ hide();
1498
+ }
1499
+ };
1500
+ els.trigger.addEventListener("pointerenter", onTriggerEnter);
1501
+ els.trigger.addEventListener("pointerleave", onTriggerLeave);
1502
+ els.trigger.addEventListener("focus", onFocus);
1503
+ els.trigger.addEventListener("blur", onBlur);
1504
+ els.trigger.addEventListener("keydown", onKey);
1505
+ els.tooltip.addEventListener("pointerenter", onTooltipEnter);
1506
+ els.tooltip.addEventListener("pointerleave", onTooltipLeave);
1507
+ const teardown = () => {
1508
+ boundTriggers.delete(els.trigger);
1509
+ fxTeardown();
1510
+ els.trigger.removeEventListener("pointerenter", onTriggerEnter);
1511
+ els.trigger.removeEventListener("pointerleave", onTriggerLeave);
1512
+ els.trigger.removeEventListener("focus", onFocus);
1513
+ els.trigger.removeEventListener("blur", onBlur);
1514
+ els.trigger.removeEventListener("keydown", onKey);
1515
+ els.tooltip.removeEventListener("pointerenter", onTooltipEnter);
1516
+ els.tooltip.removeEventListener("pointerleave", onTooltipLeave);
1517
+ const cur = els.trigger.getAttribute("aria-describedby");
1518
+ if (cur) {
1519
+ const remaining = cur.split(/\s+/).filter((part) => part && part !== id);
1520
+ if (remaining.length > 0) els.trigger.setAttribute("aria-describedby", remaining.join(" "));
1521
+ else els.trigger.removeAttribute("aria-describedby");
1522
+ } else if (prevDescribedBy) {
1523
+ els.trigger.setAttribute("aria-describedby", prevDescribedBy);
1524
+ }
1525
+ if (delayTimer !== null) clearTimeout(delayTimer);
1526
+ if (hideTimer !== null) clearTimeout(hideTimer);
1527
+ };
1528
+ boundTriggers.set(els.trigger, teardown);
1529
+ return teardown;
1530
+ }
1531
+ return { isVisible, show, hide, content, setContent, bind };
767
1532
  }
768
1533
 
769
1534
  // src/widgets/FileUpload.ts
1535
+ var fileUploadIdCounter = 0;
1536
+ var boundFileUploads = /* @__PURE__ */ new WeakMap();
770
1537
  function fileUpload(options) {
771
1538
  const accept = options?.accept;
772
1539
  const multiple = options?.multiple ?? false;
@@ -834,6 +1601,107 @@ function fileUpload(options) {
834
1601
  setErrors([]);
835
1602
  });
836
1603
  }
1604
+ function bind(els) {
1605
+ const existing = boundFileUploads.get(els.input);
1606
+ if (existing) return existing;
1607
+ const id = `sibu-fileupload-${++fileUploadIdCounter}`;
1608
+ const restore = [];
1609
+ if (accept) els.input.accept = accept;
1610
+ els.input.multiple = multiple;
1611
+ let hintId = null;
1612
+ if (els.hint) {
1613
+ const assignedHintId = !els.hint.id;
1614
+ if (assignedHintId) els.hint.id = `${id}-hint`;
1615
+ hintId = els.hint.id;
1616
+ const prev = els.input.getAttribute("aria-describedby");
1617
+ els.input.setAttribute("aria-describedby", prev ? `${prev} ${hintId}` : hintId);
1618
+ restore.push(() => {
1619
+ const cur = els.input.getAttribute("aria-describedby");
1620
+ if (cur) {
1621
+ const parts = cur.split(/\s+/).filter((p) => p && p !== hintId);
1622
+ if (parts.length > 0) els.input.setAttribute("aria-describedby", parts.join(" "));
1623
+ else els.input.removeAttribute("aria-describedby");
1624
+ }
1625
+ if (assignedHintId && els.hint && els.hint.id === hintId) els.hint.removeAttribute("id");
1626
+ });
1627
+ }
1628
+ if (els.errorRegion) {
1629
+ const prevRole = els.errorRegion.getAttribute("role");
1630
+ const prevLive = els.errorRegion.getAttribute("aria-live");
1631
+ els.errorRegion.setAttribute("role", "status");
1632
+ els.errorRegion.setAttribute("aria-live", "polite");
1633
+ restore.push(() => {
1634
+ if (prevRole === null) els.errorRegion.removeAttribute("role");
1635
+ else els.errorRegion.setAttribute("role", prevRole);
1636
+ if (prevLive === null) els.errorRegion.removeAttribute("aria-live");
1637
+ else els.errorRegion.setAttribute("aria-live", prevLive);
1638
+ });
1639
+ }
1640
+ if (els.dropZone) {
1641
+ const prevDzRole = els.dropZone.getAttribute("role");
1642
+ const prevDzLabel = els.dropZone.getAttribute("aria-label");
1643
+ const prevDzTabindex = els.dropZone.hasAttribute("tabindex") ? els.dropZone.getAttribute("tabindex") : null;
1644
+ els.dropZone.setAttribute("role", "button");
1645
+ els.dropZone.setAttribute("aria-label", "File drop zone \u2014 click or press Enter to browse");
1646
+ if (els.dropZone.tabIndex < 0) els.dropZone.tabIndex = 0;
1647
+ restore.push(() => {
1648
+ if (prevDzRole === null) els.dropZone.removeAttribute("role");
1649
+ else els.dropZone.setAttribute("role", prevDzRole);
1650
+ if (prevDzLabel === null) els.dropZone.removeAttribute("aria-label");
1651
+ else els.dropZone.setAttribute("aria-label", prevDzLabel);
1652
+ if (prevDzTabindex === null) els.dropZone.removeAttribute("tabindex");
1653
+ else els.dropZone.setAttribute("tabindex", prevDzTabindex);
1654
+ });
1655
+ }
1656
+ const fxTeardown = effect(() => {
1657
+ const errs = errors();
1658
+ if (els.errorRegion) els.errorRegion.textContent = errs.join(". ");
1659
+ if (els.dropZone) els.dropZone.setAttribute("data-drag-over", isDragOver() ? "true" : "false");
1660
+ });
1661
+ const onChange = () => {
1662
+ if (els.input.files) addFiles(els.input.files);
1663
+ };
1664
+ const onDropClick = () => els.input.click();
1665
+ const onDropKey = (e) => {
1666
+ if (e.key === "Enter" || e.key === " ") {
1667
+ e.preventDefault();
1668
+ els.input.click();
1669
+ }
1670
+ };
1671
+ const onDragOver = (e) => {
1672
+ e.preventDefault();
1673
+ setDragOver(true);
1674
+ };
1675
+ const onDragLeave = () => setDragOver(false);
1676
+ const onDrop = (e) => {
1677
+ e.preventDefault();
1678
+ setDragOver(false);
1679
+ if (e.dataTransfer?.files) addFiles(e.dataTransfer.files);
1680
+ };
1681
+ els.input.addEventListener("change", onChange);
1682
+ if (els.dropZone) {
1683
+ els.dropZone.addEventListener("click", onDropClick);
1684
+ els.dropZone.addEventListener("keydown", onDropKey);
1685
+ els.dropZone.addEventListener("dragover", onDragOver);
1686
+ els.dropZone.addEventListener("dragleave", onDragLeave);
1687
+ els.dropZone.addEventListener("drop", onDrop);
1688
+ }
1689
+ const teardown = () => {
1690
+ boundFileUploads.delete(els.input);
1691
+ fxTeardown();
1692
+ els.input.removeEventListener("change", onChange);
1693
+ if (els.dropZone) {
1694
+ els.dropZone.removeEventListener("click", onDropClick);
1695
+ els.dropZone.removeEventListener("keydown", onDropKey);
1696
+ els.dropZone.removeEventListener("dragover", onDragOver);
1697
+ els.dropZone.removeEventListener("dragleave", onDragLeave);
1698
+ els.dropZone.removeEventListener("drop", onDrop);
1699
+ }
1700
+ for (const r of restore) r();
1701
+ };
1702
+ boundFileUploads.set(els.input, teardown);
1703
+ return teardown;
1704
+ }
837
1705
  return {
838
1706
  files,
839
1707
  addFiles,
@@ -841,14 +1709,36 @@ function fileUpload(options) {
841
1709
  clear,
842
1710
  errors,
843
1711
  isDragOver,
844
- setDragOver
1712
+ setDragOver,
1713
+ bind
845
1714
  };
846
1715
  }
847
1716
 
1717
+ // src/utils/sanitize.ts
1718
+ function stripHtml(html) {
1719
+ return String(html).replace(/<[^>]*>/g, "");
1720
+ }
1721
+
848
1722
  // src/widgets/contentEditable.ts
849
1723
  function contentEditable() {
850
- const [content, setContent] = signal("");
1724
+ const [content, setContentInternal] = signal("");
851
1725
  const [isFocused, setFocused] = signal(false);
1726
+ function setContent(input) {
1727
+ if (typeof input === "string") {
1728
+ setContentInternal(input);
1729
+ return;
1730
+ }
1731
+ if (typeof input.text === "string") {
1732
+ setContentInternal(input.text);
1733
+ return;
1734
+ }
1735
+ if (typeof input.html === "string") {
1736
+ const shouldSanitize = input.sanitize !== false;
1737
+ setContentInternal(shouldSanitize ? stripHtml(input.html) : input.html);
1738
+ return;
1739
+ }
1740
+ setContentInternal("");
1741
+ }
852
1742
  function wrapSelection(tagName) {
853
1743
  if (typeof window === "undefined") return;
854
1744
  const selection = window.getSelection();
@@ -925,6 +1815,7 @@ function contentEditable() {
925
1815
  }
926
1816
 
927
1817
  // src/widgets/datePicker.ts
1818
+ var boundDatePickers = /* @__PURE__ */ new WeakMap();
928
1819
  function datePicker(options) {
929
1820
  const minDate = options?.minDate;
930
1821
  const maxDate = options?.maxDate;
@@ -948,33 +1839,20 @@ function datePicker(options) {
948
1839
  setSelectedDate(date);
949
1840
  }
950
1841
  }
1842
+ function shiftMonth(prev, delta) {
1843
+ return new Date(prev.getFullYear(), prev.getMonth() + delta, 1);
1844
+ }
951
1845
  function nextMonth() {
952
- setViewDate((prev) => {
953
- const next = new Date(prev);
954
- next.setMonth(next.getMonth() + 1);
955
- return next;
956
- });
1846
+ setViewDate((prev) => shiftMonth(prev, 1));
957
1847
  }
958
1848
  function prevMonth() {
959
- setViewDate((prev) => {
960
- const next = new Date(prev);
961
- next.setMonth(next.getMonth() - 1);
962
- return next;
963
- });
1849
+ setViewDate((prev) => shiftMonth(prev, -1));
964
1850
  }
965
1851
  function nextYear() {
966
- setViewDate((prev) => {
967
- const next = new Date(prev);
968
- next.setFullYear(next.getFullYear() + 1);
969
- return next;
970
- });
1852
+ setViewDate((prev) => new Date(prev.getFullYear() + 1, prev.getMonth(), 1));
971
1853
  }
972
1854
  function prevYear() {
973
- setViewDate((prev) => {
974
- const next = new Date(prev);
975
- next.setFullYear(next.getFullYear() - 1);
976
- return next;
977
- });
1855
+ setViewDate((prev) => new Date(prev.getFullYear() - 1, prev.getMonth(), 1));
978
1856
  }
979
1857
  const daysInMonth = derived(() => {
980
1858
  const vd = viewDate();
@@ -1039,8 +1917,87 @@ function datePicker(options) {
1039
1917
  daysInMonth,
1040
1918
  isDateDisabled,
1041
1919
  /** Reactive check — use inside class bindings for per-day reactivity */
1042
- isSelected
1920
+ isSelected,
1921
+ bind
1043
1922
  };
1923
+ function bind(els) {
1924
+ const existing = boundDatePickers.get(els.grid);
1925
+ if (existing) return existing;
1926
+ els.grid.setAttribute("role", "grid");
1927
+ if (els.grid.tabIndex < 0) els.grid.tabIndex = 0;
1928
+ const fxTeardown = effect(() => {
1929
+ const sel = selectedDate();
1930
+ const view = viewDate();
1931
+ const days = daysInMonth();
1932
+ for (const d of days) {
1933
+ const cell = els.cell(d.date);
1934
+ if (!cell) continue;
1935
+ cell.setAttribute("role", "gridcell");
1936
+ cell.setAttribute("aria-selected", sel && isSameCalendarDay(sel, d.date) ? "true" : "false");
1937
+ if (d.isDisabled) cell.setAttribute("aria-disabled", "true");
1938
+ else cell.removeAttribute("aria-disabled");
1939
+ cell.tabIndex = isSameCalendarDay(view, d.date) ? 0 : -1;
1940
+ }
1941
+ });
1942
+ function isSameCalendarDay(a, b) {
1943
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1944
+ }
1945
+ function shiftDays(delta) {
1946
+ setViewDate((prev) => new Date(prev.getFullYear(), prev.getMonth(), prev.getDate() + delta));
1947
+ }
1948
+ const onKey = (e) => {
1949
+ switch (e.key) {
1950
+ case "ArrowLeft":
1951
+ e.preventDefault();
1952
+ shiftDays(-1);
1953
+ break;
1954
+ case "ArrowRight":
1955
+ e.preventDefault();
1956
+ shiftDays(1);
1957
+ break;
1958
+ case "ArrowUp":
1959
+ e.preventDefault();
1960
+ shiftDays(-7);
1961
+ break;
1962
+ case "ArrowDown":
1963
+ e.preventDefault();
1964
+ shiftDays(7);
1965
+ break;
1966
+ case "Home":
1967
+ e.preventDefault();
1968
+ setViewDate((p) => new Date(p.getFullYear(), p.getMonth(), p.getDate() - p.getDay()));
1969
+ break;
1970
+ case "End": {
1971
+ e.preventDefault();
1972
+ setViewDate((p) => new Date(p.getFullYear(), p.getMonth(), p.getDate() + (6 - p.getDay())));
1973
+ break;
1974
+ }
1975
+ case "PageUp":
1976
+ e.preventDefault();
1977
+ if (e.shiftKey) prevYear();
1978
+ else prevMonth();
1979
+ break;
1980
+ case "PageDown":
1981
+ e.preventDefault();
1982
+ if (e.shiftKey) nextYear();
1983
+ else nextMonth();
1984
+ break;
1985
+ case "Enter":
1986
+ case " ":
1987
+ e.preventDefault();
1988
+ select2(viewDate());
1989
+ break;
1990
+ }
1991
+ };
1992
+ els.grid.addEventListener("keydown", onKey);
1993
+ const teardown = () => {
1994
+ boundDatePickers.delete(els.grid);
1995
+ fxTeardown();
1996
+ els.grid.removeEventListener("keydown", onKey);
1997
+ };
1998
+ boundDatePickers.set(els.grid, teardown);
1999
+ return teardown;
2000
+ }
1044
2001
  }
1045
2002
  // Annotate the CommonJS export names for ESM import in node:
1046
2003
  0 && (module.exports = {