sibujs 4.0.1 → 4.2.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 (80) hide show
  1. package/README.md +29 -1
  2. package/dist/browser.cjs +21 -22
  3. package/dist/browser.js +6 -6
  4. package/dist/build.cjs +363 -110
  5. package/dist/build.js +15 -15
  6. package/dist/cdn.dev.global.js +398 -0
  7. package/dist/cdn.global.js +10 -10
  8. package/dist/{chunk-AD5YSZAN.js → chunk-2LOYNGBY.js} +2 -2
  9. package/dist/{chunk-VPP2FONR.js → chunk-2WLZ6757.js} +17 -10
  10. package/dist/{chunk-UNAYBND2.js → chunk-3VTSPNC5.js} +2 -2
  11. package/dist/{chunk-TIRZCERI.js → chunk-5DXA2J44.js} +1 -1
  12. package/dist/{chunk-QD4I7ZGZ.js → chunk-6ZGI3RBE.js} +149 -33
  13. package/dist/{chunk-JYXQCELN.js → chunk-7ALC7KGE.js} +13 -7
  14. package/dist/{chunk-XDOMTQHM.js → chunk-7VEC3UD2.js} +3 -3
  15. package/dist/{chunk-PTLJ7NQX.js → chunk-BMYYZWHX.js} +3 -3
  16. package/dist/{chunk-PHB3MCK4.js → chunk-BNKV4REK.js} +7 -8
  17. package/dist/{chunk-XET2RUJF.js → chunk-FOWBJZDL.js} +7 -8
  18. package/dist/{chunk-5BVKQX2F.js → chunk-GDUNQCCW.js} +4 -4
  19. package/dist/{chunk-TVURNWDK.js → chunk-HECVW343.js} +20 -9
  20. package/dist/{chunk-PHRH66Y7.js → chunk-HJEPKZ2O.js} +7 -8
  21. package/dist/{chunk-PJDSI2K7.js → chunk-HZEBO22J.js} +125 -42
  22. package/dist/{chunk-5INI7D2L.js → chunk-IXKSNWV5.js} +74 -11
  23. package/dist/{chunk-IKVUUE7D.js → chunk-KE4KWRDY.js} +3 -3
  24. package/dist/{chunk-AW6HPJ3L.js → chunk-KKLW7YWL.js} +1 -1
  25. package/dist/{chunk-XZMCVZW6.js → chunk-NEYWS4EX.js} +6 -6
  26. package/dist/{chunk-HYCCIYNS.js → chunk-NIOYEGBQ.js} +13 -14
  27. package/dist/{chunk-YLV26GAJ.js → chunk-OUWAIXIK.js} +3 -3
  28. package/dist/{chunk-E72G5JSK.js → chunk-P3DHXBR6.js} +1 -1
  29. package/dist/{chunk-J4WZMCIW.js → chunk-QCFLDD7C.js} +5 -5
  30. package/dist/{chunk-DHDZ7IVN.js → chunk-QKRPLZ2V.js} +7 -8
  31. package/dist/{chunk-52ERIVFD.js → chunk-QL4UREZO.js} +6 -6
  32. package/dist/{chunk-TNEHT3NG.js → chunk-SF3TB4K6.js} +8 -8
  33. package/dist/{chunk-HPXTUPJD.js → chunk-XZ2N525U.js} +6 -6
  34. package/dist/{customElement-CKLoO63w.d.ts → customElement-CNZxEB9G.d.cts} +77 -0
  35. package/dist/{customElement-CKLoO63w.d.cts → customElement-CNZxEB9G.d.ts} +77 -0
  36. package/dist/data.cjs +23 -25
  37. package/dist/data.js +9 -9
  38. package/dist/devtools.cjs +26 -23
  39. package/dist/devtools.d.cts +1 -1
  40. package/dist/devtools.d.ts +1 -1
  41. package/dist/devtools.js +8 -8
  42. package/dist/{dispose-4FZ6OFXR.js → dispose-46BOMMQJ.js} +2 -2
  43. package/dist/ecosystem.cjs +230 -68
  44. package/dist/ecosystem.js +11 -11
  45. package/dist/extras.cjs +251 -87
  46. package/dist/extras.d.cts +2 -2
  47. package/dist/extras.d.ts +2 -2
  48. package/dist/extras.js +25 -25
  49. package/dist/index.cjs +358 -104
  50. package/dist/index.d.cts +287 -34
  51. package/dist/index.d.ts +287 -34
  52. package/dist/index.js +17 -15
  53. package/dist/{introspect-RjLfIFpL.d.cts → introspect-D5xF-0Es.d.cts} +7 -0
  54. package/dist/{introspect-DOZfmC-4.d.ts → introspect-rSBIxEQi.d.ts} +7 -0
  55. package/dist/motion.cjs +21 -23
  56. package/dist/motion.js +5 -5
  57. package/dist/patterns.cjs +24 -25
  58. package/dist/patterns.d.cts +8 -0
  59. package/dist/patterns.d.ts +8 -0
  60. package/dist/patterns.js +7 -7
  61. package/dist/performance.cjs +24 -28
  62. package/dist/performance.js +9 -9
  63. package/dist/plugins.cjs +268 -87
  64. package/dist/plugins.d.cts +92 -0
  65. package/dist/plugins.d.ts +92 -0
  66. package/dist/plugins.js +18 -17
  67. package/dist/{ssr-JFP5535P.js → ssr-Y7XOEPEN.js} +5 -5
  68. package/dist/ssr.cjs +241 -80
  69. package/dist/ssr.js +12 -12
  70. package/dist/testing.cjs +19 -20
  71. package/dist/testing.js +5 -5
  72. package/dist/ui.cjs +106 -44
  73. package/dist/ui.d.cts +7 -1
  74. package/dist/ui.d.ts +7 -1
  75. package/dist/ui.js +10 -10
  76. package/dist/widgets.cjs +21 -22
  77. package/dist/widgets.d.cts +8 -0
  78. package/dist/widgets.d.ts +8 -0
  79. package/dist/widgets.js +9 -9
  80. package/package.json +11 -2
package/dist/testing.cjs CHANGED
@@ -67,23 +67,17 @@ __export(testing_exports, {
67
67
  module.exports = __toCommonJS(testing_exports);
68
68
 
69
69
  // src/core/dev.ts
70
- function isDev() {
71
- return typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
72
- // The bare `__SIBU_DEV__` is a bundler define that only exists in
73
- // production builds; under the test runner it is always undefined, so
74
- // this branch is unreachable here.
75
- /* v8 ignore next 2 */
76
- typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : typeof process !== "undefined" && process.env?.NODE_ENV !== "production"
77
- );
78
- }
79
- var _isDev = isDev();
70
+ var DEV = typeof __SIBU_DEV__ !== "undefined" ? !!__SIBU_DEV__ : typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
71
+ // safe default: off in browser, on in test/dev Node
72
+ typeof process !== "undefined" && process.env?.NODE_ENV !== "production"
73
+ );
80
74
  function devAssert(condition, message) {
81
- if (_isDev && !condition) {
75
+ if ((typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) && !condition) {
82
76
  throw new Error(`[SibuJS] ${message}`);
83
77
  }
84
78
  }
85
79
  function devWarn(message) {
86
- if (_isDev) {
80
+ if (typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) {
87
81
  console.warn(`[SibuJS] ${message}`);
88
82
  }
89
83
  }
@@ -186,7 +180,6 @@ function reportDrainRunaway(label, executed, remaining) {
186
180
  { executed, remaining }
187
181
  );
188
182
  }
189
- var _isDev2 = isDev();
190
183
  var activeBindingCount = 0;
191
184
  function dispose(node) {
192
185
  const stack = [node];
@@ -209,13 +202,13 @@ function dispose(node) {
209
202
  if (!pending || pending.length === 0) break;
210
203
  const snapshot = pending.slice();
211
204
  elementDisposers.delete(current);
212
- if (_isDev2) activeBindingCount -= snapshot.length;
205
+ if (DEV) activeBindingCount -= snapshot.length;
213
206
  for (let i2 = 0; i2 < snapshot.length; i2++) {
214
207
  if (executed >= MAX_DRAIN_TEARDOWNS) {
215
208
  const rest = snapshot.slice(i2);
216
209
  const added = elementDisposers.get(current);
217
210
  elementDisposers.set(current, added ? rest.concat(added) : rest);
218
- if (_isDev2) activeBindingCount += rest.length;
211
+ if (DEV) activeBindingCount += rest.length;
219
212
  reportDrainRunaway("dispose", executed, rest.length + (added?.length ?? 0));
220
213
  runaway = true;
221
214
  break;
@@ -1899,7 +1892,14 @@ function reactiveBinding(commit, ownerNode) {
1899
1892
  subscriber._disposed = false;
1900
1893
  subscriber._errorPhase = "binding";
1901
1894
  subscriber._errorNode = ownerNode;
1902
- run();
1895
+ try {
1896
+ run();
1897
+ } catch (err) {
1898
+ subscriber._disposed = true;
1899
+ subscriber._errorNode = void 0;
1900
+ cleanup(subscriber);
1901
+ throw err;
1902
+ }
1903
1903
  return subscriber._dispose ?? (subscriber._dispose = () => {
1904
1904
  subscriber._disposed = true;
1905
1905
  subscriber._errorNode = void 0;
@@ -2130,17 +2130,16 @@ function forEachSubscriber(signal, visit) {
2130
2130
  }
2131
2131
 
2132
2132
  // src/reactivity/track.ts
2133
- var _isDev3 = isDev();
2134
- var _runtimeVersion = true ? "4.0.1" : "dev";
2133
+ var _runtimeVersion = true ? "4.2.0" : "dev";
2135
2134
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
2136
2135
  function resolveReactiveApi() {
2137
2136
  const g = globalThis;
2138
2137
  const existing = g[REGISTRY_KEY];
2139
2138
  if (existing) {
2140
- if (_isDev3 && !existing.__dupWarned) {
2139
+ if (DEV && !existing.__dupWarned) {
2141
2140
  existing.__dupWarned = true;
2142
2141
  devWarn(
2143
- `Multiple instances of the reactive runtime detected on this page (active: ${existing.version}, duplicate: ${_runtimeVersion}). Reactivity still works \u2014 all copies share the first one \u2014 but de-duplicate sibujs in your bundler (e.g. Vite optimizeDeps.exclude: ['sibujs'] or resolve.dedupe: ['sibujs']).`
2142
+ `Multiple instances of the reactive runtime detected on this page (active: ${existing.version}, duplicate: ${_runtimeVersion}). Reactivity is NOT broken and no bundler configuration is required: every copy routes through the first one via a global registry, so signal writes still reach subscribers registered by any copy. This is only a SIZE issue \u2014 the duplicate copy's bytes are downloaded and parsed for nothing. If you want them back, de-duplicate sibujs in your bundler (Vite: resolve.dedupe: ['sibujs']). Mismatched VERSIONS above are worth fixing regardless, since only the first one loaded is used.`
2144
2143
  );
2145
2144
  }
2146
2145
  return existing;
package/dist/testing.js CHANGED
@@ -3,13 +3,13 @@ import {
3
3
  } from "./chunk-7ZHH77QA.js";
4
4
  import {
5
5
  effect
6
- } from "./chunk-XDOMTQHM.js";
7
- import "./chunk-TIRZCERI.js";
6
+ } from "./chunk-7VEC3UD2.js";
7
+ import "./chunk-5DXA2J44.js";
8
8
  import {
9
9
  replaceChildrenSafely
10
- } from "./chunk-DHDZ7IVN.js";
11
- import "./chunk-JYXQCELN.js";
12
- import "./chunk-VPP2FONR.js";
10
+ } from "./chunk-QKRPLZ2V.js";
11
+ import "./chunk-7ALC7KGE.js";
12
+ import "./chunk-2WLZ6757.js";
13
13
 
14
14
  // src/testing/a11y.ts
15
15
  function escSel(value) {
package/dist/ui.cjs CHANGED
@@ -86,23 +86,23 @@ __export(ui_exports, {
86
86
  module.exports = __toCommonJS(ui_exports);
87
87
 
88
88
  // src/core/dev.ts
89
- function isDev() {
90
- return typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
91
- // The bare `__SIBU_DEV__` is a bundler define that only exists in
92
- // production builds; under the test runner it is always undefined, so
93
- // this branch is unreachable here.
94
- /* v8 ignore next 2 */
95
- typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : typeof process !== "undefined" && process.env?.NODE_ENV !== "production"
96
- );
97
- }
98
- var _isDev = isDev();
89
+ var DEV = typeof __SIBU_DEV__ !== "undefined" ? !!__SIBU_DEV__ : typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
90
+ // safe default: off in browser, on in test/dev Node
91
+ typeof process !== "undefined" && process.env?.NODE_ENV !== "production"
92
+ );
99
93
  function devAssert(condition, message) {
100
- if (_isDev && !condition) {
94
+ if ((typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) && !condition) {
101
95
  throw new Error(`[SibuJS] ${message}`);
102
96
  }
103
97
  }
98
+ function devWarnLazy(build) {
99
+ if (typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) {
100
+ const message = build();
101
+ if (message) console.warn(`[SibuJS] ${message}`);
102
+ }
103
+ }
104
104
  function devWarn(message) {
105
- if (_isDev) {
105
+ if (typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) {
106
106
  console.warn(`[SibuJS] ${message}`);
107
107
  }
108
108
  }
@@ -205,7 +205,6 @@ function reportDrainRunaway(label, executed, remaining) {
205
205
  { executed, remaining }
206
206
  );
207
207
  }
208
- var _isDev2 = isDev();
209
208
  var activeBindingCount = 0;
210
209
  function registerDisposer(node, teardown) {
211
210
  let disposers = elementDisposers.get(node);
@@ -214,7 +213,7 @@ function registerDisposer(node, teardown) {
214
213
  elementDisposers.set(node, disposers);
215
214
  }
216
215
  disposers.push(teardown);
217
- if (_isDev2) activeBindingCount++;
216
+ if (DEV) activeBindingCount++;
218
217
  }
219
218
  function dispose(node) {
220
219
  const stack = [node];
@@ -237,13 +236,13 @@ function dispose(node) {
237
236
  if (!pending || pending.length === 0) break;
238
237
  const snapshot = pending.slice();
239
238
  elementDisposers.delete(current);
240
- if (_isDev2) activeBindingCount -= snapshot.length;
239
+ if (DEV) activeBindingCount -= snapshot.length;
241
240
  for (let i2 = 0; i2 < snapshot.length; i2++) {
242
241
  if (executed >= MAX_DRAIN_TEARDOWNS) {
243
242
  const rest = snapshot.slice(i2);
244
243
  const added = elementDisposers.get(current);
245
244
  elementDisposers.set(current, added ? rest.concat(added) : rest);
246
- if (_isDev2) activeBindingCount += rest.length;
245
+ if (DEV) activeBindingCount += rest.length;
247
246
  reportDrainRunaway("dispose", executed, rest.length + (added?.length ?? 0));
248
247
  runaway = true;
249
248
  break;
@@ -489,7 +488,14 @@ function reactiveBinding(commit, ownerNode) {
489
488
  subscriber._disposed = false;
490
489
  subscriber._errorPhase = "binding";
491
490
  subscriber._errorNode = ownerNode;
492
- run();
491
+ try {
492
+ run();
493
+ } catch (err) {
494
+ subscriber._disposed = true;
495
+ subscriber._errorNode = void 0;
496
+ cleanup(subscriber);
497
+ throw err;
498
+ }
493
499
  return subscriber._dispose ?? (subscriber._dispose = () => {
494
500
  subscriber._disposed = true;
495
501
  subscriber._errorNode = void 0;
@@ -720,17 +726,16 @@ function forEachSubscriber(signal2, visit) {
720
726
  }
721
727
 
722
728
  // src/reactivity/track.ts
723
- var _isDev3 = isDev();
724
- var _runtimeVersion = true ? "4.0.1" : "dev";
729
+ var _runtimeVersion = true ? "4.2.0" : "dev";
725
730
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
726
731
  function resolveReactiveApi() {
727
732
  const g = globalThis;
728
733
  const existing = g[REGISTRY_KEY];
729
734
  if (existing) {
730
- if (_isDev3 && !existing.__dupWarned) {
735
+ if (DEV && !existing.__dupWarned) {
731
736
  existing.__dupWarned = true;
732
737
  devWarn(
733
- `Multiple instances of the reactive runtime detected on this page (active: ${existing.version}, duplicate: ${_runtimeVersion}). Reactivity still works \u2014 all copies share the first one \u2014 but de-duplicate sibujs in your bundler (e.g. Vite optimizeDeps.exclude: ['sibujs'] or resolve.dedupe: ['sibujs']).`
738
+ `Multiple instances of the reactive runtime detected on this page (active: ${existing.version}, duplicate: ${_runtimeVersion}). Reactivity is NOT broken and no bundler configuration is required: every copy routes through the first one via a global registry, so signal writes still reach subscribers registered by any copy. This is only a SIZE issue \u2014 the duplicate copy's bytes are downloaded and parsed for nothing. If you want them back, de-duplicate sibujs in your bundler (Vite: resolve.dedupe: ['sibujs']). Mismatched VERSIONS above are worth fixing regardless, since only the first one loaded is used.`
734
739
  );
735
740
  }
736
741
  return existing;
@@ -1065,7 +1070,6 @@ var isBatching = API2.isBatching;
1065
1070
 
1066
1071
  // src/core/signals/signal.ts
1067
1072
  var _g2 = globalThis;
1068
- var _isDev4 = isDev();
1069
1073
  function signal(initial, options) {
1070
1074
  const state2 = {
1071
1075
  value: initial,
@@ -1078,7 +1082,7 @@ function signal(initial, options) {
1078
1082
  _d: false,
1079
1083
  _validate: void 0
1080
1084
  };
1081
- const debugName = _isDev4 ? options?.name : void 0;
1085
+ const debugName = DEV ? options?.name : void 0;
1082
1086
  const equalsFn = options?.equals;
1083
1087
  if (debugName) state2.__name = debugName;
1084
1088
  function get() {
@@ -1095,7 +1099,7 @@ function signal(initial, options) {
1095
1099
  if (equalsFn(prev, newValue)) return;
1096
1100
  state2.value = newValue;
1097
1101
  state2.__v++;
1098
- if (_isDev4) {
1102
+ if (DEV) {
1099
1103
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1100
1104
  if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1101
1105
  }
@@ -1103,7 +1107,7 @@ function signal(initial, options) {
1103
1107
  notifySubscribers2(state2);
1104
1108
  }
1105
1109
  };
1106
- } else if (_isDev4) {
1110
+ } else if (DEV) {
1107
1111
  set = (next) => {
1108
1112
  const prev = state2.value;
1109
1113
  const newValue = typeof next === "function" ? next(prev) : next;
@@ -1128,7 +1132,7 @@ function signal(initial, options) {
1128
1132
  }
1129
1133
  };
1130
1134
  }
1131
- if (_isDev4) {
1135
+ if (DEV) {
1132
1136
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1133
1137
  if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
1134
1138
  }
@@ -2313,20 +2317,75 @@ function decodeCssEscapes2(value) {
2313
2317
  }
2314
2318
  return out;
2315
2319
  }
2316
- function sanitizeCSSValue(value) {
2317
- if (!CSS_DANGER_GATE.test(value)) return value;
2320
+ var BLOCKED_CSS_NEEDLES = [
2321
+ "url(",
2322
+ "expression(",
2323
+ "javascript:",
2324
+ "vbscript:",
2325
+ "-moz-binding",
2326
+ "behavior:",
2327
+ "@import",
2328
+ "image-set(",
2329
+ "filter:progid"
2330
+ ];
2331
+ function describeElement(el) {
2332
+ if (!el) return "an element";
2333
+ let out = el.tagName.toLowerCase();
2334
+ const id = el.getAttribute("id");
2335
+ if (id) out += `#${id}`;
2336
+ const cls = el.getAttribute("class");
2337
+ if (cls) out += `.${cls.trim().split(/\s+/).slice(0, 3).join(".")}`;
2338
+ return `<${out}>`;
2339
+ }
2340
+ var warnedDeclarations = /* @__PURE__ */ new Set();
2341
+ var MAX_WARNED_DECLARATIONS = 100;
2342
+ var declarationCapAnnounced = false;
2343
+ function warnDroppedDeclaration(value, reason, ctx) {
2344
+ devWarnLazy(() => {
2345
+ const reasons = [
2346
+ "url() in an inline style is an exfiltration channel: the browser fetches it immediately and the URL's path or query can carry page data off-origin",
2347
+ "expression() executes arbitrary script in legacy engines",
2348
+ "the javascript: scheme executes script",
2349
+ "the vbscript: scheme executes script",
2350
+ "-moz-binding loads and runs remote XBL",
2351
+ "behavior: loads and runs a remote HTC script",
2352
+ "@import pulls in a remote stylesheet",
2353
+ "image-set() fetches remote images, the same exfiltration channel as url()",
2354
+ "filter:progid activates legacy scriptable filters"
2355
+ ];
2356
+ const declaration = ctx?.property ? `${ctx.property}: ${value}` : value;
2357
+ const where = describeElement(ctx?.element);
2358
+ const key = `${where}|${declaration}`;
2359
+ if (warnedDeclarations.has(key)) return "";
2360
+ if (warnedDeclarations.size >= MAX_WARNED_DECLARATIONS) {
2361
+ if (declarationCapAnnounced) return "";
2362
+ declarationCapAnnounced = true;
2363
+ return `${MAX_WARNED_DECLARATIONS} distinct dropped style declarations have been reported; suppressing further ones for the rest of this session so they cannot flood the console. The declarations are still dropped \u2014 only the reporting stops. Fix the reported ones and reload to see any that remain.`;
2364
+ }
2365
+ warnedDeclarations.add(key);
2366
+ return `style declaration "${declaration}" was dropped by the style sanitizer on ${where} \u2014 ${reasons[reason]}. The element renders without it, which usually looks like a missing image rather than an error. Sanctioned alternatives: for a content image use an <img> element (or a <picture>); for decoration, put the rule in a stylesheet and set a class instead \u2014 stylesheet CSS is not sanitized, only inline style attributes are. If this declaration is not yours, it came from untrusted data and the drop just prevented an exfiltration.`;
2367
+ });
2368
+ }
2369
+ function blockedCssReason(value) {
2370
+ if (!CSS_DANGER_GATE.test(value)) return -1;
2318
2371
  const normalized = value.includes("\\") ? decodeCssEscapes2(value) : value;
2319
2372
  const lower = normalized.toLowerCase().replace(/\s+/g, "");
2320
- if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("vbscript:") || lower.includes("-moz-binding") || lower.includes("behavior:") || lower.includes("@import") || lower.includes("image-set(") || lower.includes("filter:progid")) {
2321
- return "";
2373
+ for (let i = 0; i < BLOCKED_CSS_NEEDLES.length; i++) {
2374
+ if (lower.includes(BLOCKED_CSS_NEEDLES[i])) return i;
2322
2375
  }
2323
- return value;
2376
+ return -1;
2377
+ }
2378
+ function sanitizeCSSValue(value, context) {
2379
+ const reason = blockedCssReason(value);
2380
+ if (reason === -1) return value;
2381
+ warnDroppedDeclaration(value, reason, context);
2382
+ return "";
2324
2383
  }
2325
- function sanitizeStyleAttribute(cssText) {
2384
+ function sanitizeStyleAttribute(cssText, context) {
2326
2385
  const input = String(cssText);
2327
2386
  if (input.trim() === "") return "";
2328
2387
  if (typeof document === "undefined") {
2329
- return sanitizeCSSValue(input) === "" ? "" : input;
2388
+ return sanitizeCSSValue(input, context) === "" ? "" : input;
2330
2389
  }
2331
2390
  const probe = document.createElement("div");
2332
2391
  try {
@@ -2338,8 +2397,12 @@ function sanitizeStyleAttribute(cssText) {
2338
2397
  for (let i = 0; i < probe.style.length; i++) {
2339
2398
  const property = probe.style[i];
2340
2399
  const value = probe.style.getPropertyValue(property);
2341
- if (sanitizeCSSValue(value) === "") continue;
2342
- if (sanitizeCSSValue(`${property}:${value}`) === "") continue;
2400
+ let reason = blockedCssReason(value);
2401
+ if (reason === -1) reason = blockedCssReason(`${property}:${value}`);
2402
+ if (reason !== -1) {
2403
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
2404
+ continue;
2405
+ }
2343
2406
  const priority = probe.style.getPropertyPriority(property);
2344
2407
  declarations.push(`${property}: ${value}${priority ? ` !${priority}` : ""}`);
2345
2408
  }
@@ -2367,16 +2430,15 @@ var HTML_CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set(["srcdoc"]);
2367
2430
  function isHtmlContentAttribute(name) {
2368
2431
  return HTML_CONTENT_ATTRIBUTES.has(name.toLowerCase());
2369
2432
  }
2370
- function sanitizeAttributeString(attr, value) {
2433
+ function sanitizeAttributeString(attr, value, context) {
2371
2434
  const lower = attr.toLowerCase();
2372
2435
  if (lower === "srcset") return sanitizeSrcset(value);
2373
2436
  if (URL_ATTRIBUTES.has(lower)) return sanitizeUrl(value);
2374
- if (lower === "style") return sanitizeStyleAttribute(value);
2437
+ if (lower === "style") return sanitizeStyleAttribute(value, context);
2375
2438
  return value;
2376
2439
  }
2377
2440
 
2378
2441
  // src/utils/setSafeAttribute.ts
2379
- var _isDev5 = isDev();
2380
2442
  var HTML_NS = "http://www.w3.org/1999/xhtml";
2381
2443
  var SVG_NS = "http://www.w3.org/2000/svg";
2382
2444
  var XLINK_NS = "http://www.w3.org/1999/xlink";
@@ -2399,7 +2461,7 @@ function setSafeAttribute(el, name, value, options = {}) {
2399
2461
  const ns = namespaceFor(el, name);
2400
2462
  const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
2401
2463
  if (isEventHandlerAttr(name)) {
2402
- if (_isDev5) {
2464
+ if (DEV) {
2403
2465
  devWarn(
2404
2466
  `${options.label ?? "setSafeAttribute"}: refusing to set event-handler attribute "${name}". Its value would be evaluated as JavaScript. Use on:{ ${name.slice(2)}: fn } instead.`
2405
2467
  );
@@ -2414,7 +2476,7 @@ function setSafeAttribute(el, name, value, options = {}) {
2414
2476
  return true;
2415
2477
  }
2416
2478
  if (isHtmlContentAttribute(name)) {
2417
- if (_isDev5) {
2479
+ if (DEV) {
2418
2480
  devWarn(
2419
2481
  `${options.label ?? "setSafeAttribute"}: refusing to set "${name}". The browser parses this attribute as a nested HTML document, so a generic string value cannot be made safe \u2014 attribute escaping is undone before the parse. Build the frame's content another way.`
2420
2482
  );
@@ -2443,7 +2505,7 @@ function setSafeAttribute(el, name, value, options = {}) {
2443
2505
  setProp(el, idlName, idlName === "checked" ? Boolean(value) : str);
2444
2506
  return true;
2445
2507
  }
2446
- const safe = sanitizeAttributeString(name, str);
2508
+ const safe = sanitizeAttributeString(name, str, { element: el });
2447
2509
  const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
2448
2510
  if (current === safe) return true;
2449
2511
  if (ns) el.setAttributeNS(ns, name, safe);
@@ -3127,13 +3189,13 @@ var validators = {
3127
3189
  function validateProps(props, schema) {
3128
3190
  const result = { ...props };
3129
3191
  const errors = [];
3130
- const isDev2 = typeof process === "undefined" || process?.env?.NODE_ENV !== "production";
3192
+ const isDev = typeof process === "undefined" || process?.env?.NODE_ENV !== "production";
3131
3193
  for (const [key, def] of Object.entries(schema)) {
3132
3194
  const propDef = typeof def === "function" ? { type: def } : def;
3133
3195
  if (result[key] == null && propDef.default !== void 0) {
3134
3196
  result[key] = typeof propDef.default === "function" ? propDef.default() : propDef.default;
3135
3197
  }
3136
- if (!isDev2) continue;
3198
+ if (!isDev) continue;
3137
3199
  if (propDef.required && result[key] == null) {
3138
3200
  errors.push(`Prop '${key}' is required`);
3139
3201
  continue;
@@ -3148,7 +3210,7 @@ function validateProps(props, schema) {
3148
3210
  if (validResult !== true) errors.push(validResult);
3149
3211
  }
3150
3212
  }
3151
- if (errors.length > 0 && isDev2) {
3213
+ if (errors.length > 0 && isDev) {
3152
3214
  console.warn(`[SibuJS] Prop validation errors:
3153
3215
  ${errors.map((e) => ` - ${e}`).join("\n")}`);
3154
3216
  }
package/dist/ui.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AttributeSource, a as AttributeValue, B as BoundFieldProps, C as CustomElementOptions, F as FieldConfig, b as FocusTrap, c as FormConfig, d as FormField, e as FormReturn, I as IntersectionResult, M as MaskOptions, T as Toast, f as ToastInstance, V as ValidatorFn, g as VirtualList, h as VirtualListProps, _ as __resetDialogStack, i as announce, j as aria, k as bindAttrs, l as bindBoolAttr, m as bindData, n as bindField, o as creditCardMask, p as custom, q as dateMask, r as defineElement, s as dialog, t as email, u as eventBus, v as focus, w as form, x as hotkey, y as infiniteScroll, z as inputMask, D as intersection, E as lazyLoad, G as matchesPattern, H as max, J as maxLength, K as min, L as minLength, N as pagination, O as phoneMask, P as removeScopedStyle, Q as required, R as scopedStyle, S as ssnMask, U as svgElement, W as timeMask, X as toast, Y as withScopedStyle, Z as zipMask } from './customElement-CKLoO63w.cjs';
1
+ export { A as AttributeSource, a as AttributeValue, B as BoundFieldProps, C as CustomElementOptions, F as FieldConfig, b as FocusTrap, c as FormConfig, d as FormField, e as FormReturn, I as IntersectionResult, M as MaskOptions, T as Toast, f as ToastInstance, V as ValidatorFn, g as VirtualList, h as VirtualListProps, _ as __resetDialogStack, i as announce, j as aria, k as bindAttrs, l as bindBoolAttr, m as bindData, n as bindField, o as creditCardMask, p as custom, q as dateMask, r as defineElement, s as dialog, t as email, u as eventBus, v as focus, w as form, x as hotkey, y as infiniteScroll, z as inputMask, D as intersection, E as lazyLoad, G as matchesPattern, H as max, J as maxLength, K as min, L as minLength, N as pagination, O as phoneMask, P as removeScopedStyle, Q as required, R as scopedStyle, S as ssnMask, U as svgElement, W as timeMask, X as toast, Y as withScopedStyle, Z as zipMask } from './customElement-CNZxEB9G.cjs';
2
2
  export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V as Validator, b as assertType, c as compose, d as createGuard, e as createSlots, f as defineComponent, g as defineSlottedComponent, h as defineStrictComponent, v as validateProps, i as validators, w as withBoundary, j as withDefaults, k as withProps, l as withWrapper } from './contracts-Hg6ITAEF.cjs';
3
3
 
4
4
  interface FormActionHandle<TArgs extends unknown[], TResult> {
@@ -250,6 +250,12 @@ interface ScrollLockHandle {
250
250
  /** Release this handle's lock. Idempotent. */
251
251
  unlock: () => void;
252
252
  }
253
+ /**
254
+ * Acquire a reference-counted lock on document scrolling, so nested overlays
255
+ * can each lock and unlock without one release re-enabling scroll for all.
256
+ *
257
+ * @returns A handle with `lock`, `unlock` and `isLocked`.
258
+ */
253
259
  declare function scrollLock(): ScrollLockHandle;
254
260
 
255
261
  export { type DialogAriaHandle, type DialogAriaOptions, type FocusManagerHandle, type FocusManagerOptions, type FormActionHandle, type IntervalHandle, type ListboxHandle, type ListboxOptions, type ScrollLockHandle, type TimeoutHandle, createDialogAria, createFocusManager, createListbox, formAction, hover, interval, lazyEffect, scrollLock, timeout };
package/dist/ui.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AttributeSource, a as AttributeValue, B as BoundFieldProps, C as CustomElementOptions, F as FieldConfig, b as FocusTrap, c as FormConfig, d as FormField, e as FormReturn, I as IntersectionResult, M as MaskOptions, T as Toast, f as ToastInstance, V as ValidatorFn, g as VirtualList, h as VirtualListProps, _ as __resetDialogStack, i as announce, j as aria, k as bindAttrs, l as bindBoolAttr, m as bindData, n as bindField, o as creditCardMask, p as custom, q as dateMask, r as defineElement, s as dialog, t as email, u as eventBus, v as focus, w as form, x as hotkey, y as infiniteScroll, z as inputMask, D as intersection, E as lazyLoad, G as matchesPattern, H as max, J as maxLength, K as min, L as minLength, N as pagination, O as phoneMask, P as removeScopedStyle, Q as required, R as scopedStyle, S as ssnMask, U as svgElement, W as timeMask, X as toast, Y as withScopedStyle, Z as zipMask } from './customElement-CKLoO63w.js';
1
+ export { A as AttributeSource, a as AttributeValue, B as BoundFieldProps, C as CustomElementOptions, F as FieldConfig, b as FocusTrap, c as FormConfig, d as FormField, e as FormReturn, I as IntersectionResult, M as MaskOptions, T as Toast, f as ToastInstance, V as ValidatorFn, g as VirtualList, h as VirtualListProps, _ as __resetDialogStack, i as announce, j as aria, k as bindAttrs, l as bindBoolAttr, m as bindData, n as bindField, o as creditCardMask, p as custom, q as dateMask, r as defineElement, s as dialog, t as email, u as eventBus, v as focus, w as form, x as hotkey, y as infiniteScroll, z as inputMask, D as intersection, E as lazyLoad, G as matchesPattern, H as max, J as maxLength, K as min, L as minLength, N as pagination, O as phoneMask, P as removeScopedStyle, Q as required, R as scopedStyle, S as ssnMask, U as svgElement, W as timeMask, X as toast, Y as withScopedStyle, Z as zipMask } from './customElement-CNZxEB9G.js';
2
2
  export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V as Validator, b as assertType, c as compose, d as createGuard, e as createSlots, f as defineComponent, g as defineSlottedComponent, h as defineStrictComponent, v as validateProps, i as validators, w as withBoundary, j as withDefaults, k as withProps, l as withWrapper } from './contracts-Hg6ITAEF.js';
3
3
 
4
4
  interface FormActionHandle<TArgs extends unknown[], TResult> {
@@ -250,6 +250,12 @@ interface ScrollLockHandle {
250
250
  /** Release this handle's lock. Idempotent. */
251
251
  unlock: () => void;
252
252
  }
253
+ /**
254
+ * Acquire a reference-counted lock on document scrolling, so nested overlays
255
+ * can each lock and unlock without one release re-enabling scroll for all.
256
+ *
257
+ * @returns A handle with `lock`, `unlock` and `isLocked`.
258
+ */
253
259
  declare function scrollLock(): ScrollLockHandle;
254
260
 
255
261
  export { type DialogAriaHandle, type DialogAriaOptions, type FocusManagerHandle, type FocusManagerOptions, type FormActionHandle, type IntervalHandle, type ListboxHandle, type ListboxOptions, type ScrollLockHandle, type TimeoutHandle, createDialogAria, createFocusManager, createListbox, formAction, hover, interval, lazyEffect, scrollLock, timeout };
package/dist/ui.js CHANGED
@@ -38,7 +38,7 @@ import {
38
38
  toast,
39
39
  withScopedStyle,
40
40
  zipMask
41
- } from "./chunk-TNEHT3NG.js";
41
+ } from "./chunk-SF3TB4K6.js";
42
42
  import {
43
43
  RenderProp,
44
44
  assertType,
@@ -58,24 +58,24 @@ import {
58
58
  import {
59
59
  createId
60
60
  } from "./chunk-CCSJMTRN.js";
61
- import "./chunk-PHRH66Y7.js";
62
- import "./chunk-AD5YSZAN.js";
63
- import "./chunk-5INI7D2L.js";
61
+ import "./chunk-HJEPKZ2O.js";
62
+ import "./chunk-2LOYNGBY.js";
63
+ import "./chunk-IXKSNWV5.js";
64
64
  import {
65
65
  globalSingleton
66
66
  } from "./chunk-7ZHH77QA.js";
67
67
  import {
68
68
  effect
69
- } from "./chunk-XDOMTQHM.js";
70
- import "./chunk-TIRZCERI.js";
69
+ } from "./chunk-7VEC3UD2.js";
70
+ import "./chunk-5DXA2J44.js";
71
71
  import {
72
72
  registerDisposer
73
- } from "./chunk-DHDZ7IVN.js";
73
+ } from "./chunk-QKRPLZ2V.js";
74
74
  import {
75
75
  signal
76
- } from "./chunk-TVURNWDK.js";
77
- import "./chunk-JYXQCELN.js";
78
- import "./chunk-VPP2FONR.js";
76
+ } from "./chunk-HECVW343.js";
77
+ import "./chunk-7ALC7KGE.js";
78
+ import "./chunk-2WLZ6757.js";
79
79
 
80
80
  // src/ui/formAction.ts
81
81
  function formAction(fn) {
package/dist/widgets.cjs CHANGED
@@ -41,23 +41,17 @@ function createId(prefix = "sibu") {
41
41
  }
42
42
 
43
43
  // src/core/dev.ts
44
- function isDev() {
45
- return typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
46
- // The bare `__SIBU_DEV__` is a bundler define that only exists in
47
- // production builds; under the test runner it is always undefined, so
48
- // this branch is unreachable here.
49
- /* v8 ignore next 2 */
50
- typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : typeof process !== "undefined" && process.env?.NODE_ENV !== "production"
51
- );
52
- }
53
- var _isDev = isDev();
44
+ var DEV = typeof __SIBU_DEV__ !== "undefined" ? !!__SIBU_DEV__ : typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
45
+ // safe default: off in browser, on in test/dev Node
46
+ typeof process !== "undefined" && process.env?.NODE_ENV !== "production"
47
+ );
54
48
  function devAssert(condition, message) {
55
- if (_isDev && !condition) {
49
+ if ((typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) && !condition) {
56
50
  throw new Error(`[SibuJS] ${message}`);
57
51
  }
58
52
  }
59
53
  function devWarn(message) {
60
- if (_isDev) {
54
+ if (typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) {
61
55
  console.warn(`[SibuJS] ${message}`);
62
56
  }
63
57
  }
@@ -368,7 +362,14 @@ function reactiveBinding(commit, ownerNode) {
368
362
  subscriber._disposed = false;
369
363
  subscriber._errorPhase = "binding";
370
364
  subscriber._errorNode = ownerNode;
371
- run();
365
+ try {
366
+ run();
367
+ } catch (err) {
368
+ subscriber._disposed = true;
369
+ subscriber._errorNode = void 0;
370
+ cleanup(subscriber);
371
+ throw err;
372
+ }
372
373
  return subscriber._dispose ?? (subscriber._dispose = () => {
373
374
  subscriber._disposed = true;
374
375
  subscriber._errorNode = void 0;
@@ -599,17 +600,16 @@ function forEachSubscriber(signal2, visit) {
599
600
  }
600
601
 
601
602
  // src/reactivity/track.ts
602
- var _isDev2 = isDev();
603
- var _runtimeVersion = true ? "4.0.1" : "dev";
603
+ var _runtimeVersion = true ? "4.2.0" : "dev";
604
604
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
605
605
  function resolveReactiveApi() {
606
606
  const g = globalThis;
607
607
  const existing = g[REGISTRY_KEY];
608
608
  if (existing) {
609
- if (_isDev2 && !existing.__dupWarned) {
609
+ if (DEV && !existing.__dupWarned) {
610
610
  existing.__dupWarned = true;
611
611
  devWarn(
612
- `Multiple instances of the reactive runtime detected on this page (active: ${existing.version}, duplicate: ${_runtimeVersion}). Reactivity still works \u2014 all copies share the first one \u2014 but de-duplicate sibujs in your bundler (e.g. Vite optimizeDeps.exclude: ['sibujs'] or resolve.dedupe: ['sibujs']).`
612
+ `Multiple instances of the reactive runtime detected on this page (active: ${existing.version}, duplicate: ${_runtimeVersion}). Reactivity is NOT broken and no bundler configuration is required: every copy routes through the first one via a global registry, so signal writes still reach subscribers registered by any copy. This is only a SIZE issue \u2014 the duplicate copy's bytes are downloaded and parsed for nothing. If you want them back, de-duplicate sibujs in your bundler (Vite: resolve.dedupe: ['sibujs']). Mismatched VERSIONS above are worth fixing regardless, since only the first one loaded is used.`
613
613
  );
614
614
  }
615
615
  return existing;
@@ -944,7 +944,6 @@ var isBatching = API2.isBatching;
944
944
 
945
945
  // src/core/signals/signal.ts
946
946
  var _g2 = globalThis;
947
- var _isDev3 = isDev();
948
947
  function signal(initial, options) {
949
948
  const state2 = {
950
949
  value: initial,
@@ -957,7 +956,7 @@ function signal(initial, options) {
957
956
  _d: false,
958
957
  _validate: void 0
959
958
  };
960
- const debugName = _isDev3 ? options?.name : void 0;
959
+ const debugName = DEV ? options?.name : void 0;
961
960
  const equalsFn = options?.equals;
962
961
  if (debugName) state2.__name = debugName;
963
962
  function get() {
@@ -974,7 +973,7 @@ function signal(initial, options) {
974
973
  if (equalsFn(prev, newValue)) return;
975
974
  state2.value = newValue;
976
975
  state2.__v++;
977
- if (_isDev3) {
976
+ if (DEV) {
978
977
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
979
978
  if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
980
979
  }
@@ -982,7 +981,7 @@ function signal(initial, options) {
982
981
  notifySubscribers2(state2);
983
982
  }
984
983
  };
985
- } else if (_isDev3) {
984
+ } else if (DEV) {
986
985
  set = (next) => {
987
986
  const prev = state2.value;
988
987
  const newValue = typeof next === "function" ? next(prev) : next;
@@ -1007,7 +1006,7 @@ function signal(initial, options) {
1007
1006
  }
1008
1007
  };
1009
1008
  }
1010
- if (_isDev3) {
1009
+ if (DEV) {
1011
1010
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1012
1011
  if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
1013
1012
  }
@@ -119,6 +119,14 @@ interface SelectOptions<T> {
119
119
  * by `highlightNext`/`highlightPrev`/typeahead and rejected by `select`. */
120
120
  isDisabled?: (item: T) => boolean;
121
121
  }
122
+ /**
123
+ * Selection state for a list — single or multiple, with keyboard-friendly
124
+ * helpers. Headless: it owns state, not markup.
125
+ *
126
+ * @param options Item list, selection mode, and comparison strategy.
127
+ * @returns A handle exposing `selectedItem`/`selectedItems` accessors and the
128
+ * `select`, `deselect`, `toggle` and `clear` operations.
129
+ */
122
130
  declare function select<T>(options: SelectOptions<T>): {
123
131
  selectedItems: () => T[];
124
132
  selectedItem: () => T | null;
package/dist/widgets.d.ts CHANGED
@@ -119,6 +119,14 @@ interface SelectOptions<T> {
119
119
  * by `highlightNext`/`highlightPrev`/typeahead and rejected by `select`. */
120
120
  isDisabled?: (item: T) => boolean;
121
121
  }
122
+ /**
123
+ * Selection state for a list — single or multiple, with keyboard-friendly
124
+ * helpers. Headless: it owns state, not markup.
125
+ *
126
+ * @param options Item list, selection mode, and comparison strategy.
127
+ * @returns A handle exposing `selectedItem`/`selectedItems` accessors and the
128
+ * `select`, `deselect`, `toggle` and `clear` operations.
129
+ */
122
130
  declare function select<T>(options: SelectOptions<T>): {
123
131
  selectedItems: () => T[];
124
132
  selectedItem: () => T | null;