sibujs 4.5.0 → 4.7.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 (116) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.cjs +326 -133
  3. package/dist/browser.d.cts +46 -14
  4. package/dist/browser.d.ts +46 -14
  5. package/dist/browser.js +8 -6
  6. package/dist/build.cjs +2094 -1033
  7. package/dist/build.d.cts +133 -44
  8. package/dist/build.d.ts +133 -44
  9. package/dist/build.js +1372 -574
  10. package/dist/cdn.dev.global.js +10 -10
  11. package/dist/cdn.full.dev.global.js +11 -11
  12. package/dist/cdn.full.global.js +10 -10
  13. package/dist/cdn.global.js +10 -10
  14. package/dist/{chunk-2DCGACUU.js → chunk-2BPG2XDA.js} +251 -66
  15. package/dist/{chunk-IXKSNWV5.js → chunk-2INLLLMZ.js} +1 -1
  16. package/dist/{chunk-4PMLNECI.js → chunk-3QWBSL5R.js} +98 -29
  17. package/dist/{chunk-KKLW7YWL.js → chunk-4AWA2PVD.js} +275 -144
  18. package/dist/chunk-5HZXGZ6T.js +24 -0
  19. package/dist/{chunk-PCT43HW3.js → chunk-7LUJQAOJ.js} +1 -1
  20. package/dist/{chunk-5DXA2J44.js → chunk-7XHATCIH.js} +5 -2
  21. package/dist/chunk-BC2SECJD.js +44 -0
  22. package/dist/{chunk-TBYTO6BS.js → chunk-BSY63EM6.js} +136 -80
  23. package/dist/{chunk-FTIR4QW2.js → chunk-DKXRACVN.js} +6 -8
  24. package/dist/{chunk-ONOHFDLG.js → chunk-FNJXNGYZ.js} +3 -3
  25. package/dist/{chunk-XZZOBQAY.js → chunk-FQRUXCEE.js} +6 -5
  26. package/dist/{chunk-NIOYEGBQ.js → chunk-HURREPU2.js} +27 -13
  27. package/dist/chunk-J6FW5TV6.js +233 -0
  28. package/dist/{chunk-UGRX3S57.js → chunk-JWKYU5GV.js} +22 -4
  29. package/dist/chunk-NYNYSPK7.js +318 -0
  30. package/dist/{chunk-RBTPLM32.js → chunk-RUSSKG6G.js} +13 -12
  31. package/dist/{chunk-7LN645I6.js → chunk-SLM3IA34.js} +3 -3
  32. package/dist/{chunk-R25EFXXC.js → chunk-TUCPL2HB.js} +3 -3
  33. package/dist/{chunk-B3WHI2QA.js → chunk-UNWRJRKC.js} +55 -31
  34. package/dist/{chunk-KEISJXBU.js → chunk-UOL2ECCS.js} +44 -17
  35. package/dist/{chunk-RJE2BNI4.js → chunk-VZSG24LS.js} +108 -45
  36. package/dist/{chunk-RIXRAYIU.js → chunk-WEQ3DMVL.js} +10 -4
  37. package/dist/{chunk-3JZ4L5TJ.js → chunk-WOLJZUFQ.js} +354 -90
  38. package/dist/{chunk-S373NSMK.js → chunk-XYV3EDB7.js} +283 -123
  39. package/dist/{chunk-VZKNK2V7.js → chunk-XZR4PXRE.js} +296 -38
  40. package/dist/{chunk-GW3SCCZG.js → chunk-Z3OHK6QT.js} +153 -110
  41. package/dist/{chunk-OMJJM3KM.js → chunk-ZVL7TY4K.js} +287 -130
  42. package/dist/{contracts-DBdg9J_a.d.ts → contracts-CLqzJnOV.d.ts} +36 -17
  43. package/dist/{contracts-DBdg9J_a.d.cts → contracts-CTOJXu-x.d.cts} +36 -17
  44. package/dist/{customElement-OB9CIsc5.d.cts → customElement-MmInOW1U.d.cts} +21 -0
  45. package/dist/{customElement-OB9CIsc5.d.ts → customElement-MmInOW1U.d.ts} +21 -0
  46. package/dist/data.cjs +410 -155
  47. package/dist/data.d.cts +166 -12
  48. package/dist/data.d.ts +166 -12
  49. package/dist/data.js +12 -9
  50. package/dist/devtools.cjs +98 -61
  51. package/dist/devtools.js +7 -8
  52. package/dist/dispose-GEIG2KOF.js +28 -0
  53. package/dist/ecosystem.cjs +372 -126
  54. package/dist/ecosystem.d.cts +20 -3
  55. package/dist/ecosystem.d.ts +20 -3
  56. package/dist/ecosystem.js +12 -12
  57. package/dist/extras.cjs +2366 -909
  58. package/dist/extras.d.cts +11 -9
  59. package/dist/extras.d.ts +11 -9
  60. package/dist/extras.js +39 -28
  61. package/dist/index.cjs +421 -158
  62. package/dist/index.d.cts +213 -171
  63. package/dist/index.d.ts +213 -171
  64. package/dist/index.js +24 -27
  65. package/dist/motion.cjs +118 -44
  66. package/dist/motion.js +5 -5
  67. package/dist/patterns.cjs +344 -53
  68. package/dist/patterns.d.cts +28 -9
  69. package/dist/patterns.d.ts +28 -9
  70. package/dist/patterns.js +8 -8
  71. package/dist/performance.cjs +325 -220
  72. package/dist/performance.d.cts +2 -2
  73. package/dist/performance.d.ts +2 -2
  74. package/dist/performance.js +8 -9
  75. package/dist/plugin-DVgSnTfK.d.cts +112 -0
  76. package/dist/plugin-DVgSnTfK.d.ts +112 -0
  77. package/dist/plugins.cjs +664 -233
  78. package/dist/plugins.d.cts +127 -14
  79. package/dist/plugins.d.ts +127 -14
  80. package/dist/plugins.js +96 -42
  81. package/dist/signal-EotCj4hS.d.cts +110 -0
  82. package/dist/signal-EotCj4hS.d.ts +110 -0
  83. package/dist/{ssr-BiPRdZ6n.d.cts → ssr-Bli9XRW5.d.cts} +5 -0
  84. package/dist/{ssr-BiPRdZ6n.d.ts → ssr-Bli9XRW5.d.ts} +5 -0
  85. package/dist/{ssr-Y7XOEPEN.js → ssr-XOTUASDO.js} +4 -5
  86. package/dist/ssr.cjs +229 -84
  87. package/dist/ssr.d.cts +9 -3
  88. package/dist/ssr.d.ts +9 -3
  89. package/dist/ssr.js +11 -12
  90. package/dist/{startup-BMpaiMhP.d.ts → startup-BLfSeL15.d.cts} +73 -22
  91. package/dist/{startup-BMpaiMhP.d.cts → startup-BLfSeL15.d.ts} +73 -22
  92. package/dist/tagFactory-8qL9LCIx.d.cts +156 -0
  93. package/dist/tagFactory-BL2fymez.d.ts +156 -0
  94. package/dist/testing.cjs +2503 -2191
  95. package/dist/testing.d.cts +56 -5
  96. package/dist/testing.d.ts +56 -5
  97. package/dist/testing.js +580 -307
  98. package/dist/types-CJFViL6Q.d.cts +26 -0
  99. package/dist/types-CJFViL6Q.d.ts +26 -0
  100. package/dist/ui.cjs +732 -329
  101. package/dist/ui.d.cts +41 -7
  102. package/dist/ui.d.ts +41 -7
  103. package/dist/ui.js +151 -56
  104. package/dist/widgets.cjs +267 -291
  105. package/dist/widgets.js +9 -10
  106. package/package.json +4 -2
  107. package/dist/chunk-2WLZ6757.js +0 -149
  108. package/dist/chunk-CCSJMTRN.js +0 -15
  109. package/dist/chunk-QKRPLZ2V.js +0 -108
  110. package/dist/chunk-VUF4ALSW.js +0 -60
  111. package/dist/chunk-WWV3SJ3L.js +0 -131
  112. package/dist/dispose-46BOMMQJ.js +0 -19
  113. package/dist/plugin-D30wlGW5.d.cts +0 -71
  114. package/dist/plugin-D30wlGW5.d.ts +0 -71
  115. package/dist/tagFactory-DVoDpHye.d.cts +0 -215
  116. package/dist/tagFactory-DVoDpHye.d.ts +0 -215
@@ -141,6 +141,90 @@ function reportError(error, context) {
141
141
  });
142
142
  }
143
143
 
144
+ // src/core/rendering/dispose.ts
145
+ var elementDisposers = /* @__PURE__ */ new WeakMap();
146
+ var MAX_DRAIN_TEARDOWNS = 1e4;
147
+ function reportDrainRunaway(label, executed, remaining) {
148
+ if (typeof console === "undefined") return;
149
+ console.error(
150
+ `[SibuJS ${label}] runaway cleanup: stopped after running ${executed} teardowns with ${remaining} still queued. Cleanup production did not stabilize before the teardown safety ceiling was reached \u2014 the remaining work was NOT run.`,
151
+ { executed, remaining }
152
+ );
153
+ }
154
+ var activeBindingCount = 0;
155
+ function resolveCapture(capture) {
156
+ let frame = capture;
157
+ while (frame?.closed) frame = frame.forwardTo;
158
+ return frame ?? null;
159
+ }
160
+ var registrationCaptures = [];
161
+ function currentDisposerCapture() {
162
+ return resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
163
+ }
164
+ function beginDisposerCapture(capture) {
165
+ if (capture === null && registrationCaptures.length === 0) return false;
166
+ registrationCaptures.push(resolveCapture(capture));
167
+ return true;
168
+ }
169
+ function endDisposerCapture() {
170
+ registrationCaptures.pop();
171
+ }
172
+ function registerDisposer(node, teardown) {
173
+ let disposers = elementDisposers.get(node);
174
+ if (!disposers) {
175
+ disposers = [];
176
+ elementDisposers.set(node, disposers);
177
+ }
178
+ disposers.push(teardown);
179
+ if (DEV) activeBindingCount++;
180
+ const capture = resolveCapture(registrationCaptures[registrationCaptures.length - 1]);
181
+ if (capture) capture.entries.push([node, teardown]);
182
+ }
183
+ function dispose(node) {
184
+ const stack = [node];
185
+ const order = [];
186
+ while (stack.length > 0) {
187
+ const current = stack.pop();
188
+ order.push(current);
189
+ const children = Array.from(current.childNodes);
190
+ for (let i = 0; i < children.length; i++) {
191
+ stack.push(children[i]);
192
+ }
193
+ }
194
+ for (let i = order.length - 1; i >= 0; i--) {
195
+ disposeNodeOwn(order[i]);
196
+ }
197
+ }
198
+ function disposeNodeOwn(node) {
199
+ if (!elementDisposers.has(node)) return;
200
+ let executed = 0;
201
+ let runaway = false;
202
+ while (!runaway) {
203
+ const pending = elementDisposers.get(node);
204
+ if (!pending || pending.length === 0) break;
205
+ const snapshot = pending.slice();
206
+ elementDisposers.delete(node);
207
+ if (DEV) activeBindingCount -= snapshot.length;
208
+ for (let i = 0; i < snapshot.length; i++) {
209
+ if (executed >= MAX_DRAIN_TEARDOWNS) {
210
+ const rest = snapshot.slice(i);
211
+ const added = elementDisposers.get(node);
212
+ elementDisposers.set(node, added ? rest.concat(added) : rest);
213
+ if (DEV) activeBindingCount += rest.length;
214
+ reportDrainRunaway("dispose", executed, rest.length + (added?.length ?? 0));
215
+ runaway = true;
216
+ break;
217
+ }
218
+ executed++;
219
+ try {
220
+ snapshot[i]();
221
+ } catch (err) {
222
+ reportError(err, { phase: "cleanup", name: "disposer" });
223
+ }
224
+ }
225
+ }
226
+ }
227
+
144
228
  // src/reactivity/track-core.ts
145
229
  var POOL_MAX = 4096;
146
230
  var nodePool = [];
@@ -299,6 +383,7 @@ function retrack(effectFn, subscriber) {
299
383
  }
300
384
  currentSubscriber = subscriber;
301
385
  const sub = subscriber;
386
+ if (sub._cap === void 0) sub._cap = currentDisposerCapture();
302
387
  const epoch = ++subscriberEpochCounter;
303
388
  sub._epoch = epoch;
304
389
  sub._structDirty = false;
@@ -490,7 +575,12 @@ function drainQueue() {
490
575
  quarantined.add(sub);
491
576
  continue;
492
577
  }
493
- safeInvoke(sub);
578
+ const pushed = beginDisposerCapture(sub._cap ?? null);
579
+ try {
580
+ safeInvoke(sub);
581
+ } finally {
582
+ if (pushed) endDisposerCapture();
583
+ }
494
584
  }
495
585
  }
496
586
  function drainNotificationQueue() {
@@ -626,7 +716,7 @@ function forEachSubscriber(signal2, visit) {
626
716
  }
627
717
 
628
718
  // src/reactivity/track.ts
629
- var _runtimeVersion = true ? "4.5.0" : "dev";
719
+ var _runtimeVersion = true ? "4.7.0" : "dev";
630
720
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
631
721
  function resolveReactiveApi() {
632
722
  const g = globalThis;
@@ -682,6 +772,14 @@ var getSubscriberCount2 = API.getSubscriberCount;
682
772
  var getSubscriberDeps2 = API.getSubscriberDeps;
683
773
  var forEachSubscriber2 = API.forEachSubscriber;
684
774
 
775
+ // src/core/devtoolsHook.ts
776
+ function emitDevtools(hook, event, payload) {
777
+ try {
778
+ hook.emit(event, payload);
779
+ } catch {
780
+ }
781
+ }
782
+
685
783
  // src/core/ssr-context.ts
686
784
  var SSR_KEY = /* @__PURE__ */ Symbol.for("sibujs.ssr.v1");
687
785
  function detectSSRShared() {
@@ -836,12 +934,17 @@ function effect(effectFn, options) {
836
934
  try {
837
935
  retrack2(ctx.bodyFn, ctx.subscriber);
838
936
  if (ctx.rerunPending) drainReruns(ctx);
937
+ } catch (err) {
938
+ ctx.disposed = true;
939
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
940
+ cleanup2(ctx.subscriber);
941
+ throw err;
839
942
  } finally {
840
943
  ctx.running = false;
841
944
  ctx.rerunPending = false;
842
945
  }
843
946
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
844
- if (hook) hook.emit("effect:create", { effectFn });
947
+ if (hook) emitDevtools(hook, "effect:create", { effectFn });
845
948
  return () => disposeEffect(ctx);
846
949
  }
847
950
 
@@ -928,7 +1031,7 @@ function signal(initial, options) {
928
1031
  state2.__v++;
929
1032
  if (DEV) {
930
1033
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
931
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1034
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
932
1035
  }
933
1036
  if (!enqueueBatchedSignal(state2)) {
934
1037
  notifySubscribers2(state2);
@@ -942,7 +1045,7 @@ function signal(initial, options) {
942
1045
  state2.value = newValue;
943
1046
  state2.__v++;
944
1047
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
945
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1048
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
946
1049
  if (!enqueueBatchedSignal(state2)) {
947
1050
  notifySubscribers2(state2);
948
1051
  }
@@ -961,11 +1064,32 @@ function signal(initial, options) {
961
1064
  }
962
1065
  if (DEV) {
963
1066
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
964
- if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
1067
+ if (hook) emitDevtools(hook, "signal:create", { signal: state2, name: debugName, getter: get, initial });
965
1068
  }
966
1069
  return [get, set];
967
1070
  }
968
1071
 
1072
+ // src/utils/adoptThenable.ts
1073
+ function adoptThenable(value) {
1074
+ if (value === null || typeof value !== "object" && typeof value !== "function") return null;
1075
+ let then;
1076
+ try {
1077
+ then = value.then;
1078
+ } catch (error) {
1079
+ return Promise.reject(error);
1080
+ }
1081
+ if (typeof then !== "function") return null;
1082
+ return new Promise((resolve, reject) => {
1083
+ queueMicrotask(() => {
1084
+ try {
1085
+ Reflect.apply(then, value, [resolve, reject]);
1086
+ } catch (error) {
1087
+ reject(error);
1088
+ }
1089
+ });
1090
+ });
1091
+ }
1092
+
969
1093
  // src/utils/globalSingleton.ts
970
1094
  function globalSingleton(key, create) {
971
1095
  const g = globalThis;
@@ -973,37 +1097,127 @@ function globalSingleton(key, create) {
973
1097
  }
974
1098
 
975
1099
  // src/plugins/plugin.ts
1100
+ var CANCELLED_BRAND = /* @__PURE__ */ Symbol.for("sibujs.plugins.installCancelled.v1");
1101
+ var _a;
1102
+ var PluginInstallCancelledError = class extends Error {
1103
+ constructor(pluginName) {
1104
+ super(`[Plugin] Installation of "${pluginName}" was cancelled before it was committed (registry reset).`);
1105
+ /**
1106
+ * Brand, not identity: the default registry is shared across duplicate copies
1107
+ * of this module, so the error a caller receives may come from another copy's
1108
+ * constructor. `instanceof` is taught to recognise the brand instead.
1109
+ */
1110
+ this[_a] = true;
1111
+ this.name = "PluginInstallCancelledError";
1112
+ this.pluginName = pluginName;
1113
+ }
1114
+ static [(_a = CANCELLED_BRAND, Symbol.hasInstance)](value) {
1115
+ return isPluginInstallCancelledError(value);
1116
+ }
1117
+ };
1118
+ function isPluginInstallCancelledError(value) {
1119
+ return typeof value === "object" && value !== null && value[CANCELLED_BRAND] === true;
1120
+ }
976
1121
  function createPluginRegistry() {
977
1122
  const installedPlugins = /* @__PURE__ */ new Set();
978
1123
  const hooks = { init: [], mount: [], unmount: [], error: [] };
979
1124
  const provided = /* @__PURE__ */ new Map();
1125
+ const installStack = /* @__PURE__ */ new Set();
1126
+ const pendingInstalls = /* @__PURE__ */ new Map();
1127
+ let generation = 0;
980
1128
  const registry = {
981
1129
  installedPlugins,
982
1130
  hooks,
983
1131
  provided,
1132
+ /**
1133
+ * Install a plugin as a transaction.
1134
+ *
1135
+ * Hooks and providers registered through `ctx` are staged and committed
1136
+ * only if `install()` returns; a throwing install leaves the registry
1137
+ * exactly as it found it (and can simply be retried). The plugin is marked
1138
+ * installed at commit, before its init hooks run.
1139
+ *
1140
+ * Each `plugin()` call is its own transaction: a dependency installed by a
1141
+ * nested `plugin()` call commits on its own and stays installed even if the
1142
+ * outer installation later fails.
1143
+ */
984
1144
  plugin(p, options) {
985
1145
  if (installedPlugins.has(p.name)) {
986
1146
  console.warn(`[Plugin] "${p.name}" is already installed.`);
987
1147
  return;
988
1148
  }
1149
+ if (installStack.has(p.name)) {
1150
+ throw new Error(`[Plugin] "${p.name}" is already being installed (recursive installation).`);
1151
+ }
1152
+ if (pendingInstalls.has(p.name)) {
1153
+ throw new Error(`[Plugin] "${p.name}" is already being installed (asynchronous installation in flight).`);
1154
+ }
1155
+ const staged = { init: [], mount: [], unmount: [], error: [] };
1156
+ const stagedProvided = /* @__PURE__ */ new Map();
1157
+ let committed = false;
1158
+ const myGeneration = generation;
1159
+ const stale = () => {
1160
+ if (myGeneration === generation) return false;
1161
+ if (DEV) devWarn(`plugin: "${p.name}" registered after the registry was reset; the registration was ignored.`);
1162
+ return true;
1163
+ };
1164
+ const target = () => stale() ? null : committed ? hooks : staged;
989
1165
  const ctx = {
990
- onInit: (cb) => hooks.init.push(cb),
991
- onMount: (cb) => hooks.mount.push(cb),
992
- onUnmount: (cb) => hooks.unmount.push(cb),
993
- onError: (cb) => hooks.error.push(cb),
994
- provide: (key, value) => provided.set(key, value)
1166
+ onInit: (cb) => void target()?.init.push(cb),
1167
+ onMount: (cb) => void target()?.mount.push(cb),
1168
+ onUnmount: (cb) => void target()?.unmount.push(cb),
1169
+ onError: (cb) => void target()?.error.push(cb),
1170
+ provide: (key, value) => {
1171
+ if (stale()) return;
1172
+ (committed ? provided : stagedProvided).set(key, value);
1173
+ }
995
1174
  };
996
- const initHooksBefore = hooks.init.length;
997
- p.install(ctx, options);
998
- installedPlugins.add(p.name);
999
- const justAdded = hooks.init.slice(initHooksBefore);
1000
- for (const cb of justAdded) {
1001
- try {
1002
- cb();
1003
- } catch (e) {
1004
- console.error(`[Plugin] "${p.name}" init error:`, e);
1175
+ const commit = () => {
1176
+ if (myGeneration !== generation) return false;
1177
+ hooks.init.push(...staged.init);
1178
+ hooks.mount.push(...staged.mount);
1179
+ hooks.unmount.push(...staged.unmount);
1180
+ hooks.error.push(...staged.error);
1181
+ for (const [key, value] of stagedProvided) provided.set(key, value);
1182
+ installedPlugins.add(p.name);
1183
+ committed = true;
1184
+ if (pendingInstalls.get(p.name) === myGeneration) pendingInstalls.delete(p.name);
1185
+ for (const cb of staged.init.slice()) {
1186
+ try {
1187
+ cb();
1188
+ } catch (e) {
1189
+ console.error(`[Plugin] "${p.name}" init error:`, e);
1190
+ }
1005
1191
  }
1192
+ return true;
1193
+ };
1194
+ installStack.add(p.name);
1195
+ let pending;
1196
+ try {
1197
+ pending = adoptThenable(p.install(ctx, options));
1198
+ } finally {
1199
+ installStack.delete(p.name);
1200
+ }
1201
+ if (!pending) {
1202
+ if (!commit()) throw new PluginInstallCancelledError(p.name);
1203
+ return;
1006
1204
  }
1205
+ if (myGeneration === generation) pendingInstalls.set(p.name, myGeneration);
1206
+ const settled = pending.then(
1207
+ () => {
1208
+ if (commit()) return;
1209
+ if (pendingInstalls.get(p.name) === myGeneration) pendingInstalls.delete(p.name);
1210
+ throw new PluginInstallCancelledError(p.name);
1211
+ },
1212
+ (err) => {
1213
+ if (pendingInstalls.get(p.name) === myGeneration) pendingInstalls.delete(p.name);
1214
+ reportError(err, { phase: "async", name: `plugin(${p.name})` });
1215
+ throw err;
1216
+ }
1217
+ );
1218
+ settled.catch(() => {
1219
+ });
1220
+ return settled;
1007
1221
  },
1008
1222
  inject(key, defaultValue) {
1009
1223
  if (provided.has(key)) return provided.get(key);
@@ -1041,6 +1255,8 @@ function createPluginRegistry() {
1041
1255
  }
1042
1256
  },
1043
1257
  reset() {
1258
+ generation++;
1259
+ pendingInstalls.clear();
1044
1260
  installedPlugins.clear();
1045
1261
  hooks.init.length = 0;
1046
1262
  hooks.mount.length = 0;
@@ -1131,17 +1347,17 @@ function derived(getter, options) {
1131
1347
  cs._d = false;
1132
1348
  cs._init = true;
1133
1349
  };
1134
- track2(() => {
1135
- let threw = true;
1136
- try {
1350
+ try {
1351
+ track2(() => {
1137
1352
  cs._v = getter();
1138
1353
  cs._d = false;
1139
1354
  cs._init = true;
1140
- threw = false;
1141
- } finally {
1142
- if (threw) cs._d = true;
1143
- }
1144
- }, markDirty);
1355
+ }, markDirty);
1356
+ } catch (err) {
1357
+ disposed = true;
1358
+ cleanup2(markDirty);
1359
+ throw err;
1360
+ }
1145
1361
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1146
1362
  const validate = () => {
1147
1363
  if (!cs._d || disposed || pendingError !== void 0) return;
@@ -1160,7 +1376,7 @@ function derived(getter, options) {
1160
1376
  if (disposed) cleanup2(markDirty);
1161
1377
  }
1162
1378
  if (hook && !disposed && !Object.is(oldValue, cs._v)) {
1163
- hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
1379
+ emitDevtools(hook, "computed:update", { signal: cs, oldValue, newValue: cs._v });
1164
1380
  }
1165
1381
  };
1166
1382
  cs._validate = validate;
@@ -1211,7 +1427,7 @@ function derived(getter, options) {
1211
1427
  }
1212
1428
  }
1213
1429
  };
1214
- if (hook) hook.emit("computed:create", { signal: cs, name: debugName, getter: computedGetter });
1430
+ if (hook) emitDevtools(hook, "computed:create", { signal: cs, name: debugName, getter: computedGetter });
1215
1431
  return computedGetter;
1216
1432
  }
1217
1433
 
@@ -1581,70 +1797,6 @@ function bindAttribute(el, attr, getter) {
1581
1797
  return reactiveBinding2(commit, el);
1582
1798
  }
1583
1799
 
1584
- // src/core/rendering/dispose.ts
1585
- var elementDisposers = /* @__PURE__ */ new WeakMap();
1586
- var MAX_DRAIN_TEARDOWNS = 1e4;
1587
- function reportDrainRunaway(label, executed, remaining) {
1588
- if (typeof console === "undefined") return;
1589
- console.error(
1590
- `[SibuJS ${label}] runaway cleanup: stopped after running ${executed} teardowns with ${remaining} still queued. Cleanup production did not stabilize before the teardown safety ceiling was reached \u2014 the remaining work was NOT run.`,
1591
- { executed, remaining }
1592
- );
1593
- }
1594
- var activeBindingCount = 0;
1595
- function registerDisposer(node, teardown) {
1596
- let disposers = elementDisposers.get(node);
1597
- if (!disposers) {
1598
- disposers = [];
1599
- elementDisposers.set(node, disposers);
1600
- }
1601
- disposers.push(teardown);
1602
- if (DEV) activeBindingCount++;
1603
- }
1604
- function dispose(node) {
1605
- const stack = [node];
1606
- const order = [];
1607
- while (stack.length > 0) {
1608
- const current = stack.pop();
1609
- order.push(current);
1610
- const children = Array.from(current.childNodes);
1611
- for (let i = 0; i < children.length; i++) {
1612
- stack.push(children[i]);
1613
- }
1614
- }
1615
- for (let i = order.length - 1; i >= 0; i--) {
1616
- const current = order[i];
1617
- if (elementDisposers.has(current)) {
1618
- let executed = 0;
1619
- let runaway = false;
1620
- while (!runaway) {
1621
- const pending = elementDisposers.get(current);
1622
- if (!pending || pending.length === 0) break;
1623
- const snapshot = pending.slice();
1624
- elementDisposers.delete(current);
1625
- if (DEV) activeBindingCount -= snapshot.length;
1626
- for (let i2 = 0; i2 < snapshot.length; i2++) {
1627
- if (executed >= MAX_DRAIN_TEARDOWNS) {
1628
- const rest = snapshot.slice(i2);
1629
- const added = elementDisposers.get(current);
1630
- elementDisposers.set(current, added ? rest.concat(added) : rest);
1631
- if (DEV) activeBindingCount += rest.length;
1632
- reportDrainRunaway("dispose", executed, rest.length + (added?.length ?? 0));
1633
- runaway = true;
1634
- break;
1635
- }
1636
- executed++;
1637
- try {
1638
- snapshot[i2]();
1639
- } catch (err) {
1640
- reportError(err, { phase: "cleanup", name: "disposer" });
1641
- }
1642
- }
1643
- }
1644
- }
1645
- }
1646
- }
1647
-
1648
1800
  // src/core/rendering/focusPreservation.ts
1649
1801
  var IDENTITY_ATTRS = ["data-focus-key", "id", "name"];
1650
1802
  var SELECTABLE_TYPES = /* @__PURE__ */ new Set(["text", "search", "url", "tel", "password", ""]);
@@ -1844,8 +1996,34 @@ function toKebab(prop) {
1844
1996
  }
1845
1997
  function applyStyle(el, style) {
1846
1998
  if (typeof style === "function") {
1999
+ const getter = style;
2000
+ let written = [];
2001
+ let wroteString = false;
1847
2002
  const teardown = reactiveBinding2(() => {
1848
- el.setAttribute("style", sanitizeStyleAttribute(String(style()), { element: el }));
2003
+ const value = getter();
2004
+ if (value === null || typeof value !== "object") {
2005
+ written = [];
2006
+ wroteString = true;
2007
+ el.setAttribute("style", sanitizeStyleAttribute(String(value), { element: el }));
2008
+ return;
2009
+ }
2010
+ const decl = el.style;
2011
+ if (wroteString) {
2012
+ el.removeAttribute("style");
2013
+ wroteString = false;
2014
+ }
2015
+ const next = [];
2016
+ for (const prop in value) {
2017
+ const val = value[prop];
2018
+ if (val == null) continue;
2019
+ const name = toKebab(prop);
2020
+ next.push(name);
2021
+ decl.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
2022
+ }
2023
+ for (let i = 0; i < written.length; i++) {
2024
+ if (next.indexOf(written[i]) === -1) decl.removeProperty(written[i]);
2025
+ }
2026
+ written = next;
1849
2027
  }, el);
1850
2028
  registerDisposer(el, teardown);
1851
2029
  return;
@@ -1876,7 +2054,9 @@ function applyClass(el, cls) {
1876
2054
  }
1877
2055
  if (typeof cls === "function") {
1878
2056
  const teardown = reactiveBinding2(() => {
1879
- el.setAttribute("class", cls());
2057
+ const value = cls();
2058
+ if (value == null || value === false) el.removeAttribute("class");
2059
+ else el.setAttribute("class", value);
1880
2060
  }, el);
1881
2061
  registerDisposer(el, teardown);
1882
2062
  return;
@@ -1958,7 +2138,7 @@ function appendChildren(el, nodes) {
1958
2138
  }
1959
2139
  }
1960
2140
  }
1961
- var tagFactory = (tag, ns) => {
2141
+ function tagFactory(tag, ns) {
1962
2142
  const blocked = isBlockedTag(tag);
1963
2143
  return (first, second) => {
1964
2144
  if (blocked) {
@@ -2007,7 +2187,9 @@ var tagFactory = (tag, ns) => {
2007
2187
  const pClass = props.class;
2008
2188
  if (pClass != null) applyClass(el, pClass);
2009
2189
  const pId = props.id;
2010
- if (pId != null) {
2190
+ if (typeof pId === "function") {
2191
+ registerDisposer(el, bindAttribute(el, "id", pId));
2192
+ } else if (pId != null) {
2011
2193
  if (DEV && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
2012
2194
  devWarn(
2013
2195
  `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
@@ -2057,61 +2239,125 @@ var tagFactory = (tag, ns) => {
2057
2239
  }
2058
2240
  }
2059
2241
  }
2060
- if (props.onElement && typeof props.onElement === "function") {
2061
- props.onElement(el);
2062
- }
2242
+ if (typeof props.onElement === "function") props.onElement(el);
2063
2243
  return el;
2064
2244
  };
2065
- };
2245
+ }
2066
2246
 
2067
2247
  // src/ecosystem/ui/componentAdapter.ts
2248
+ var _themeLayers = globalSingleton(
2249
+ /* @__PURE__ */ Symbol.for("sibujs.themeLayers.v1"),
2250
+ () => /* @__PURE__ */ new WeakMap()
2251
+ );
2252
+ function commitThemeProperty(root, name, record) {
2253
+ const top = record.layers[record.layers.length - 1];
2254
+ if (top) {
2255
+ root.style.setProperty(name, top.value);
2256
+ return;
2257
+ }
2258
+ _themeLayers.get(root)?.delete(name);
2259
+ if (record.original.value === "") root.style.removeProperty(name);
2260
+ else root.style.setProperty(name, record.original.value, record.original.priority);
2261
+ }
2262
+ function setThemeLayer(root, name, handle, value) {
2263
+ let properties = _themeLayers.get(root);
2264
+ if (!properties) _themeLayers.set(root, properties = /* @__PURE__ */ new Map());
2265
+ let record = properties.get(name);
2266
+ if (!record) {
2267
+ record = {
2268
+ original: { value: root.style.getPropertyValue(name), priority: root.style.getPropertyPriority(name) },
2269
+ layers: []
2270
+ };
2271
+ properties.set(name, record);
2272
+ }
2273
+ const layer = record.layers.find((l) => l.handle === handle);
2274
+ if (layer) layer.value = value;
2275
+ else record.layers.push({ handle, value });
2276
+ commitThemeProperty(root, name, record);
2277
+ }
2278
+ function removeThemeLayer(root, name, handle) {
2279
+ const record = _themeLayers.get(root)?.get(name);
2280
+ if (!record) return;
2281
+ const index = record.layers.findIndex((l) => l.handle === handle);
2282
+ if (index === -1) return;
2283
+ record.layers.splice(index, 1);
2284
+ commitThemeProperty(root, name, record);
2285
+ }
2286
+ function ownString(map, key) {
2287
+ if (!map || key === void 0 || !Object.hasOwn(map, key)) return void 0;
2288
+ const value = map[key];
2289
+ return typeof value === "string" ? value : void 0;
2290
+ }
2068
2291
  function createTheme(initial) {
2069
2292
  const [getConfig, setConfig] = signal(initial);
2070
2293
  function resolveClass(component, variant) {
2071
2294
  const config = getConfig();
2072
2295
  const overrideKey = variant ? `${component}-${variant}` : component;
2073
- if (config.classOverrides?.[overrideKey]) {
2074
- return config.classOverrides[overrideKey];
2075
- }
2296
+ const override = ownString(config.classOverrides, overrideKey);
2297
+ if (override) return override;
2076
2298
  const base = `${config.prefix}-${component}`;
2077
2299
  return variant ? `${base} ${config.prefix}-${component}--${variant}` : base;
2078
2300
  }
2079
2301
  function setTheme(partial) {
2080
2302
  setConfig((prev) => ({ ...prev, ...partial }));
2081
2303
  }
2082
- return { config: getConfig, setTheme, resolveClass };
2304
+ function applyTo(root) {
2305
+ const handle = {};
2306
+ let applied = /* @__PURE__ */ new Set();
2307
+ const stop = effect(() => {
2308
+ const variables = getConfig().variables ?? {};
2309
+ const next = new Set(Object.keys(variables));
2310
+ for (const name of applied) {
2311
+ if (!next.has(name)) removeThemeLayer(root, name, handle);
2312
+ }
2313
+ for (const [name, value] of Object.entries(variables)) setThemeLayer(root, name, handle, value);
2314
+ applied = next;
2315
+ });
2316
+ let released = false;
2317
+ return () => {
2318
+ if (released) return;
2319
+ released = true;
2320
+ stop();
2321
+ for (const name of applied) removeThemeLayer(root, name, handle);
2322
+ applied.clear();
2323
+ };
2324
+ }
2325
+ return { config: getConfig, setTheme, resolveClass, applyTo };
2083
2326
  }
2084
2327
  function componentAdapter(config) {
2085
2328
  const theme = createTheme({ prefix: config.prefix });
2329
+ const reprefix = (cls, prefix) => prefix !== config.prefix && cls.startsWith(`${config.prefix}-`) ? `${prefix}-${cls.slice(config.prefix.length + 1)}` : cls;
2086
2330
  const components = {};
2087
2331
  for (const [name, mapping] of Object.entries(config.components)) {
2088
2332
  const factory = tagFactory(mapping.tag || "div");
2089
- components[name] = (props = {}) => {
2333
+ components[name] = (props = {}, children) => {
2090
2334
  const { variant, size, class: userClass, ...rest } = props;
2091
- const classes = [mapping.baseClass];
2092
- if (variant && mapping.variants?.[variant]) {
2093
- classes.push(mapping.variants[variant]);
2094
- }
2095
- if (size && mapping.sizes?.[size]) {
2096
- classes.push(mapping.sizes[size]);
2097
- }
2098
- let finalClass;
2099
- if (typeof userClass === "function") {
2100
- finalClass = () => {
2101
- const extra = userClass();
2102
- return extra ? `${classes.join(" ")} ${extra}` : classes.join(" ");
2103
- };
2104
- } else if (typeof userClass === "string" && userClass) {
2105
- finalClass = `${classes.join(" ")} ${userClass}`;
2106
- } else {
2107
- finalClass = classes.join(" ");
2108
- }
2335
+ const themeClasses = () => {
2336
+ const current = theme.config();
2337
+ const overrides = current.classOverrides ?? {};
2338
+ const pick = (key, fallback) => Object.hasOwn(overrides, key) ? overrides[key] : reprefix(fallback, current.prefix);
2339
+ const classes = [pick(name, mapping.baseClass)];
2340
+ const variantClass = ownString(mapping.variants, variant);
2341
+ if (variant && variantClass) {
2342
+ classes.push(pick(`${name}-${variant}`, variantClass));
2343
+ }
2344
+ const sizeClass = ownString(mapping.sizes, size);
2345
+ if (size && sizeClass) {
2346
+ classes.push(pick(`${name}-${size}`, sizeClass));
2347
+ }
2348
+ return classes.filter(Boolean).join(" ");
2349
+ };
2350
+ const finalClass = () => {
2351
+ const base = themeClasses();
2352
+ const extra = typeof userClass === "function" ? userClass() : userClass;
2353
+ return typeof extra === "string" && extra ? `${base} ${extra}` : base;
2354
+ };
2109
2355
  const mergedProps = {
2110
2356
  ...mapping.defaultProps,
2111
2357
  ...rest,
2112
2358
  class: finalClass
2113
2359
  };
2114
- return factory(mergedProps);
2360
+ return children === void 0 ? factory(mergedProps) : factory(mergedProps, children);
2115
2361
  };
2116
2362
  }
2117
2363
  return { name: config.name, components, theme };