sibujs 4.7.0 → 4.9.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 (83) hide show
  1. package/dist/browser.cjs +48 -6
  2. package/dist/browser.d.cts +26 -2
  3. package/dist/browser.d.ts +26 -2
  4. package/dist/browser.js +6 -5
  5. package/dist/build.cjs +539 -96
  6. package/dist/build.d.cts +22 -8
  7. package/dist/build.d.ts +22 -8
  8. package/dist/build.js +68 -21
  9. package/dist/cdn.dev.global.js +15 -13
  10. package/dist/cdn.full.dev.global.js +15 -13
  11. package/dist/cdn.full.global.js +14 -12
  12. package/dist/cdn.global.js +14 -12
  13. package/dist/{chunk-Z3OHK6QT.js → chunk-2K3Z3XO4.js} +5 -5
  14. package/dist/chunk-3ACAAQIN.js +224 -0
  15. package/dist/{chunk-WOLJZUFQ.js → chunk-562UVHMM.js} +32 -15
  16. package/dist/{chunk-UOL2ECCS.js → chunk-5MLWTIQE.js} +18 -12
  17. package/dist/{chunk-H3SRKIYX.js → chunk-62BDYGAG.js} +8 -0
  18. package/dist/{chunk-3QWBSL5R.js → chunk-7OV5Q2UU.js} +8 -5
  19. package/dist/{chunk-SLM3IA34.js → chunk-CDBKFGXN.js} +1 -1
  20. package/dist/{chunk-4AWA2PVD.js → chunk-CF2KCWRE.js} +10 -5
  21. package/dist/{chunk-BSY63EM6.js → chunk-DRMLIF6M.js} +20 -5
  22. package/dist/{chunk-2INLLLMZ.js → chunk-EXXSN3BW.js} +202 -26
  23. package/dist/{chunk-HURREPU2.js → chunk-FC6KCJSA.js} +87 -150
  24. package/dist/{chunk-BC2SECJD.js → chunk-FHCZMHHE.js} +1 -1
  25. package/dist/{chunk-XYV3EDB7.js → chunk-FNOK662L.js} +63 -38
  26. package/dist/{chunk-JWKYU5GV.js → chunk-G2WHYEL7.js} +1 -1
  27. package/dist/{chunk-DKXRACVN.js → chunk-K6K5ACO6.js} +6 -6
  28. package/dist/{chunk-VZSG24LS.js → chunk-KG25XMGP.js} +45 -17
  29. package/dist/{chunk-FNJXNGYZ.js → chunk-MPWDSTYK.js} +58 -6
  30. package/dist/{chunk-UNWRJRKC.js → chunk-NQMKWBKU.js} +1 -1
  31. package/dist/{chunk-7LUJQAOJ.js → chunk-QBKCK6MJ.js} +1 -1
  32. package/dist/{chunk-WEQ3DMVL.js → chunk-SBTZEYNI.js} +1 -1
  33. package/dist/{chunk-FQRUXCEE.js → chunk-SYHGGDNW.js} +1 -1
  34. package/dist/{chunk-XZR4PXRE.js → chunk-TV464N2M.js} +13 -10
  35. package/dist/{chunk-RUSSKG6G.js → chunk-X7NRHFZR.js} +1 -1
  36. package/dist/{chunk-2BPG2XDA.js → chunk-XJVTQUIV.js} +18 -36
  37. package/dist/{chunk-ZVL7TY4K.js → chunk-YW6HQKGO.js} +25 -10
  38. package/dist/{customElement-MmInOW1U.d.ts → customElement-pYDKUzWl.d.cts} +11 -3
  39. package/dist/{customElement-MmInOW1U.d.cts → customElement-pYDKUzWl.d.ts} +11 -3
  40. package/dist/data.cjs +29 -9
  41. package/dist/data.d.cts +23 -4
  42. package/dist/data.d.ts +23 -4
  43. package/dist/data.js +6 -6
  44. package/dist/devtools.cjs +1 -1
  45. package/dist/devtools.js +5 -5
  46. package/dist/ecosystem.cjs +400 -37
  47. package/dist/ecosystem.js +9 -8
  48. package/dist/extras.cjs +672 -269
  49. package/dist/extras.d.cts +2 -2
  50. package/dist/extras.d.ts +2 -2
  51. package/dist/extras.js +24 -23
  52. package/dist/index.cjs +490 -88
  53. package/dist/index.d.cts +34 -2
  54. package/dist/index.d.ts +34 -2
  55. package/dist/index.js +16 -12
  56. package/dist/motion.cjs +1 -1
  57. package/dist/motion.js +3 -3
  58. package/dist/patterns.cjs +10 -7
  59. package/dist/patterns.js +6 -6
  60. package/dist/performance.cjs +31 -2
  61. package/dist/performance.d.cts +12 -2
  62. package/dist/performance.d.ts +12 -2
  63. package/dist/performance.js +8 -6
  64. package/dist/plugins.cjs +571 -240
  65. package/dist/plugins.d.cts +14 -3
  66. package/dist/plugins.d.ts +14 -3
  67. package/dist/plugins.js +35 -37
  68. package/dist/{ssr-XOTUASDO.js → ssr-5OWAH3U2.js} +4 -2
  69. package/dist/{ssr-Bli9XRW5.d.ts → ssr-C8hgIAw7.d.cts} +6 -0
  70. package/dist/{ssr-Bli9XRW5.d.cts → ssr-C8hgIAw7.d.ts} +6 -0
  71. package/dist/ssr.cjs +484 -138
  72. package/dist/ssr.d.cts +59 -5
  73. package/dist/ssr.d.ts +59 -5
  74. package/dist/ssr.js +11 -9
  75. package/dist/testing.cjs +3 -3
  76. package/dist/testing.js +4 -4
  77. package/dist/ui.cjs +609 -241
  78. package/dist/ui.d.cts +1 -1
  79. package/dist/ui.d.ts +1 -1
  80. package/dist/ui.js +9 -8
  81. package/dist/widgets.cjs +1 -1
  82. package/dist/widgets.js +7 -7
  83. package/package.json +5 -4
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  isUnsafeKey,
3
3
  stripUnsafeKeys
4
- } from "./chunk-H3SRKIYX.js";
4
+ } from "./chunk-62BDYGAG.js";
5
5
  import {
6
6
  derived
7
- } from "./chunk-RUSSKG6G.js";
7
+ } from "./chunk-X7NRHFZR.js";
8
8
  import {
9
9
  effect
10
- } from "./chunk-WEQ3DMVL.js";
10
+ } from "./chunk-SBTZEYNI.js";
11
11
  import {
12
12
  batch,
13
13
  signal
14
- } from "./chunk-FQRUXCEE.js";
14
+ } from "./chunk-SYHGGDNW.js";
15
15
  import {
16
16
  DEV,
17
17
  devWarn,
@@ -19,6 +19,9 @@ import {
19
19
  } from "./chunk-NYNYSPK7.js";
20
20
 
21
21
  // src/patterns/machine.ts
22
+ function ownEntry(table, key) {
23
+ return Object.hasOwn(table, key) ? table[key] : void 0;
24
+ }
22
25
  function machine(config) {
23
26
  const [state, setState] = signal(config.initial);
24
27
  const [context, setContext] = signal(config.context || {});
@@ -40,9 +43,9 @@ function machine(config) {
40
43
  }
41
44
  }
42
45
  function step(event) {
43
- const stateDef = config.states[currentState];
46
+ const stateDef = ownEntry(config.states, currentState);
44
47
  if (!stateDef?.on) return;
45
- const transition = stateDef.on[event];
48
+ const transition = ownEntry(stateDef.on, event);
46
49
  if (!transition) return;
47
50
  let target;
48
51
  let guard;
@@ -70,7 +73,7 @@ function machine(config) {
70
73
  if (action) setContext(nextContext);
71
74
  setState(target);
72
75
  });
73
- const targetDef = config.states[target];
76
+ const targetDef = ownEntry(config.states, target);
74
77
  if (targetDef?.entry) {
75
78
  targetDef.entry(currentContext);
76
79
  }
@@ -80,7 +83,7 @@ function machine(config) {
80
83
  if (processing) return;
81
84
  drain();
82
85
  }
83
- const initialDef = config.states[config.initial];
86
+ const initialDef = ownEntry(config.states, config.initial);
84
87
  if (initialDef?.entry) {
85
88
  processing = true;
86
89
  try {
@@ -98,9 +101,9 @@ function machine(config) {
98
101
  }
99
102
  function can(event) {
100
103
  const currentState2 = state();
101
- const stateDef = config.states[currentState2];
104
+ const stateDef = ownEntry(config.states, currentState2);
102
105
  if (!stateDef?.on) return false;
103
- const transition = stateDef.on[event];
106
+ const transition = ownEntry(stateDef.on, event);
104
107
  if (!transition) return false;
105
108
  if (typeof transition === "string") return true;
106
109
  if (transition.guard) {
@@ -5,7 +5,7 @@ import {
5
5
  recordDependency,
6
6
  retrack,
7
7
  track
8
- } from "./chunk-JWKYU5GV.js";
8
+ } from "./chunk-G2WHYEL7.js";
9
9
  import {
10
10
  devAssert
11
11
  } from "./chunk-NYNYSPK7.js";
@@ -1,29 +1,33 @@
1
1
  import {
2
2
  domBinding
3
- } from "./chunk-BC2SECJD.js";
3
+ } from "./chunk-FHCZMHHE.js";
4
4
  import {
5
5
  bindAttribute,
6
6
  setSafeAttribute
7
- } from "./chunk-FNJXNGYZ.js";
7
+ } from "./chunk-MPWDSTYK.js";
8
8
  import {
9
- derived
10
- } from "./chunk-RUSSKG6G.js";
9
+ isBlockedElement
10
+ } from "./chunk-3ACAAQIN.js";
11
11
  import {
12
- isEventHandlerAttr
13
- } from "./chunk-2INLLLMZ.js";
12
+ derived
13
+ } from "./chunk-X7NRHFZR.js";
14
14
  import {
15
15
  globalSingleton
16
16
  } from "./chunk-7ZHH77QA.js";
17
+ import {
18
+ isEventHandlerAttr,
19
+ sanitizeStylesheetText
20
+ } from "./chunk-EXXSN3BW.js";
17
21
  import {
18
22
  adoptThenable
19
23
  } from "./chunk-5HZXGZ6T.js";
20
24
  import {
21
25
  signal
22
- } from "./chunk-FQRUXCEE.js";
26
+ } from "./chunk-SYHGGDNW.js";
23
27
  import {
24
28
  reactiveBinding,
25
29
  track
26
- } from "./chunk-JWKYU5GV.js";
30
+ } from "./chunk-G2WHYEL7.js";
27
31
  import {
28
32
  dispose,
29
33
  disposeNodeOwn,
@@ -745,31 +749,6 @@ function announce(message, priority = "polite") {
745
749
 
746
750
  // src/ui/scopedStyle.ts
747
751
  var _scope = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.scopedStyle.v1"), () => ({ n: 0 }));
748
- function decodeCssEscapes(css) {
749
- return css.replace(/\\([0-9a-f]{1,6})[ \t\n\r\f]?|\\([^\n])/gi, (_match, hex, ch) => {
750
- if (hex) {
751
- const code = Number.parseInt(hex, 16);
752
- if (Number.isFinite(code) && code > 0 && code <= 1114111) {
753
- try {
754
- return String.fromCodePoint(code);
755
- } catch {
756
- return "";
757
- }
758
- }
759
- return "";
760
- }
761
- return ch || "";
762
- });
763
- }
764
- function sanitizeCSS(css) {
765
- let sanitized = decodeCssEscapes(css);
766
- sanitized = sanitized.replace(/@import\s+[^;]+;/gi, "/* @import removed */");
767
- sanitized = sanitized.replace(/url\s*\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)/gi, "/* url() removed */");
768
- sanitized = sanitized.replace(/expression\s*\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)/gi, "/* expression() removed */");
769
- sanitized = sanitized.replace(/-moz-binding\s*:[^;]+;/gi, "/* -moz-binding removed */");
770
- sanitized = sanitized.replace(/behavior\s*:[^;]+;/gi, "/* behavior removed */");
771
- return sanitized;
772
- }
773
752
  function firstCompoundEnd(selector) {
774
753
  let parens = 0;
775
754
  let brackets = 0;
@@ -931,7 +910,7 @@ function scopeStylesheet(css, attr) {
931
910
  function scopedStyle(css) {
932
911
  const id = `sibu-s${_scope.n++}`;
933
912
  const attr = `data-${id}`;
934
- const safeCss = sanitizeCSS(css);
913
+ const safeCss = sanitizeStylesheetText(css);
935
914
  const scopedCSS = scopeStylesheet(safeCss, attr);
936
915
  if (typeof document !== "undefined") {
937
916
  const styleEl = document.createElement("style");
@@ -989,7 +968,7 @@ function bindBoolAttr(el, attr, getter) {
989
968
  reportError(err, { phase: "binding", name: "bindBoolAttr", node: el });
990
969
  return;
991
970
  }
992
- setSafeAttribute(el, attr, Boolean(value), { label: "bindBoolAttr" });
971
+ setSafeAttribute(el, attr, Boolean(value), { label: "bindBoolAttr", reactive: true });
993
972
  }
994
973
  return reactiveBinding(commit, el);
995
974
  }
@@ -1472,6 +1451,9 @@ function defineElement(name, component, options = {}) {
1472
1451
  customElements.define(name, SibuElement);
1473
1452
  }
1474
1453
  function svgElement(tag, props = {}, ...nodes) {
1454
+ if (isBlockedElement(tag)) {
1455
+ throw new Error(`svgElement: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
1456
+ }
1475
1457
  const el = document.createElementNS(SVG_NS, tag);
1476
1458
  for (const [key, value] of Object.entries(props)) {
1477
1459
  if (key === "nodes") continue;
@@ -1484,7 +1466,7 @@ function svgElement(tag, props = {}, ...nodes) {
1484
1466
  for (const child of nodes) {
1485
1467
  if (typeof child === "string") {
1486
1468
  el.appendChild(document.createTextNode(child));
1487
- } else if (child instanceof Node) {
1469
+ } else if (typeof child?.nodeType === "number") {
1488
1470
  el.appendChild(child);
1489
1471
  }
1490
1472
  }
@@ -1,19 +1,22 @@
1
- import {
2
- isUnsafeKey
3
- } from "./chunk-H3SRKIYX.js";
4
1
  import {
5
2
  acquireTitle
6
3
  } from "./chunk-52XFPGSN.js";
4
+ import {
5
+ isUnsafeKey
6
+ } from "./chunk-62BDYGAG.js";
7
7
  import {
8
8
  globalSingleton
9
9
  } from "./chunk-7ZHH77QA.js";
10
10
  import {
11
11
  effect
12
- } from "./chunk-WEQ3DMVL.js";
12
+ } from "./chunk-SBTZEYNI.js";
13
+ import {
14
+ sanitizeUrl
15
+ } from "./chunk-EXXSN3BW.js";
13
16
  import {
14
17
  batch,
15
18
  signal
16
- } from "./chunk-FQRUXCEE.js";
19
+ } from "./chunk-SYHGGDNW.js";
17
20
  import {
18
21
  reportError
19
22
  } from "./chunk-NYNYSPK7.js";
@@ -533,13 +536,19 @@ function colorScheme() {
533
536
  }
534
537
 
535
538
  // src/browser/format.ts
539
+ var I18N_STATE = /* @__PURE__ */ Symbol.for("sibujs.i18n.v1");
540
+ function activeI18nLocale() {
541
+ const state = globalThis[I18N_STATE];
542
+ if (!state) return void 0;
543
+ return state.getLocale ? state.getLocale() : state.locale?.[0]();
544
+ }
536
545
  function formatNumber(value, options) {
537
546
  const { locale, ...formatOptions } = options ?? {};
538
- return new Intl.NumberFormat(locale, formatOptions).format(value);
547
+ return new Intl.NumberFormat(locale ?? activeI18nLocale(), formatOptions).format(value);
539
548
  }
540
549
  function formatCurrency(value, currency, options) {
541
550
  const { locale, ...formatOptions } = options ?? {};
542
- return new Intl.NumberFormat(locale, {
551
+ return new Intl.NumberFormat(locale ?? activeI18nLocale(), {
543
552
  ...formatOptions,
544
553
  style: "currency",
545
554
  currency
@@ -1133,6 +1142,7 @@ function keyboard(options = {}) {
1133
1142
  };
1134
1143
  }
1135
1144
  const target = options.target ?? window;
1145
+ const view = options.target?.ownerDocument?.defaultView ?? window;
1136
1146
  const filter = options.keys ? new Set(options.keys) : null;
1137
1147
  const onDown = (e) => {
1138
1148
  if (filter && !filter.has(e.key)) return;
@@ -1155,14 +1165,14 @@ function keyboard(options = {}) {
1155
1165
  const onBlur = () => setPressed(/* @__PURE__ */ new Set());
1156
1166
  target.addEventListener("keydown", onDown);
1157
1167
  target.addEventListener("keyup", onUp);
1158
- window.addEventListener("blur", onBlur);
1168
+ view.addEventListener("blur", onBlur);
1159
1169
  function isPressed(key) {
1160
1170
  return pressed().has(key);
1161
1171
  }
1162
1172
  function dispose() {
1163
1173
  target.removeEventListener("keydown", onDown);
1164
1174
  target.removeEventListener("keyup", onUp);
1165
- window.removeEventListener("blur", onBlur);
1175
+ view.removeEventListener("blur", onBlur);
1166
1176
  }
1167
1177
  return { pressed, isPressed, dispose };
1168
1178
  }
@@ -1508,6 +1518,11 @@ function imageLoader(src) {
1508
1518
  abandonCurrent();
1509
1519
  resetState();
1510
1520
  if (disposed || token !== startToken) return;
1521
+ const safe = sanitizeUrl(String(url));
1522
+ if (!safe) {
1523
+ setStatus("error");
1524
+ return;
1525
+ }
1511
1526
  const img = new Image();
1512
1527
  current = img;
1513
1528
  currentSettled = false;
@@ -1526,7 +1541,7 @@ function imageLoader(src) {
1526
1541
  currentSettled = true;
1527
1542
  setStatus("error");
1528
1543
  };
1529
- img.src = url;
1544
+ img.src = safe;
1530
1545
  }
1531
1546
  let srcEffectTeardown = null;
1532
1547
  if (typeof src === "function") {
@@ -277,9 +277,11 @@ declare function announce(message: string, priority?: "polite" | "assertive"): v
277
277
  * and prefixing all selectors.
278
278
  * Returns the scope attribute name and injects the CSS into the document.
279
279
  *
280
- * CSS is sanitized to remove dangerous patterns (`url()`, `@import`,
281
- * `expression()`, `-moz-binding`, `behavior`). If you need `url()` for
282
- * background images, use inline styles via the `style` prop instead.
280
+ * CSS is sanitized by the framework's stylesheet-text policy
281
+ * (`sanitizeStylesheetText`): `url()`, `image-set()`, `image()`, `src()`,
282
+ * `expression()`, `@import`, `behavior`, `-moz-binding` and `progid:` filters
283
+ * are removed. For a content image use an `<img>`; for a decorative one, a
284
+ * stylesheet you ship.
283
285
  */
284
286
  declare function scopedStyle(css: string): {
285
287
  scope: string;
@@ -484,6 +486,7 @@ declare function eventBus<T extends object>(): {
484
486
  interface CustomElementOptions {
485
487
  shadow?: boolean;
486
488
  mode?: "open" | "closed";
489
+ /** Stylesheet source for the element's shadow root, applied unsanitized — developer CSS, not runtime data. */
487
490
  styles?: string;
488
491
  observedAttributes?: string[];
489
492
  }
@@ -504,6 +507,11 @@ declare function defineElement(name: string, component: (props: Record<string, u
504
507
  *
505
508
  * Function-valued `on*` props keep their existing meaning — `addEventListener`,
506
509
  * never an attribute.
510
+ *
511
+ * The TAG is policed like a tag factory's, through the shared element policy:
512
+ * `svgElement("script", { href })` is refused. SVG has its own `<script>`, which
513
+ * the browser fetches and runs when it is inserted, so an unrestricted tag name
514
+ * here was a way around the tag factories' block on script elements.
507
515
  */
508
516
  declare function svgElement(tag: string, props?: Record<string, unknown>, ...nodes: (SVGElement | string)[]): SVGElement;
509
517
 
@@ -277,9 +277,11 @@ declare function announce(message: string, priority?: "polite" | "assertive"): v
277
277
  * and prefixing all selectors.
278
278
  * Returns the scope attribute name and injects the CSS into the document.
279
279
  *
280
- * CSS is sanitized to remove dangerous patterns (`url()`, `@import`,
281
- * `expression()`, `-moz-binding`, `behavior`). If you need `url()` for
282
- * background images, use inline styles via the `style` prop instead.
280
+ * CSS is sanitized by the framework's stylesheet-text policy
281
+ * (`sanitizeStylesheetText`): `url()`, `image-set()`, `image()`, `src()`,
282
+ * `expression()`, `@import`, `behavior`, `-moz-binding` and `progid:` filters
283
+ * are removed. For a content image use an `<img>`; for a decorative one, a
284
+ * stylesheet you ship.
283
285
  */
284
286
  declare function scopedStyle(css: string): {
285
287
  scope: string;
@@ -484,6 +486,7 @@ declare function eventBus<T extends object>(): {
484
486
  interface CustomElementOptions {
485
487
  shadow?: boolean;
486
488
  mode?: "open" | "closed";
489
+ /** Stylesheet source for the element's shadow root, applied unsanitized — developer CSS, not runtime data. */
487
490
  styles?: string;
488
491
  observedAttributes?: string[];
489
492
  }
@@ -504,6 +507,11 @@ declare function defineElement(name: string, component: (props: Record<string, u
504
507
  *
505
508
  * Function-valued `on*` props keep their existing meaning — `addEventListener`,
506
509
  * never an attribute.
510
+ *
511
+ * The TAG is policed like a tag factory's, through the shared element policy:
512
+ * `svgElement("script", { href })` is refused. SVG has its own `<script>`, which
513
+ * the browser fetches and runs when it is inserted, so an unrestricted tag name
514
+ * here was a way around the tag factories' block on script elements.
507
515
  */
508
516
  declare function svgElement(tag: string, props?: Record<string, unknown>, ...nodes: (SVGElement | string)[]): SVGElement;
509
517
 
package/dist/data.cjs CHANGED
@@ -660,7 +660,7 @@ function forEachSubscriber(signal2, visit) {
660
660
  }
661
661
 
662
662
  // src/reactivity/track.ts
663
- var _runtimeVersion = true ? "4.7.0" : "dev";
663
+ var _runtimeVersion = true ? "4.9.0" : "dev";
664
664
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
665
665
  function resolveReactiveApi() {
666
666
  const g = globalThis;
@@ -1289,7 +1289,11 @@ function reconcile(prev, next, depth, walk, newRoot) {
1289
1289
  const nextChild = nextRecord[key];
1290
1290
  const hasPrev = Object.hasOwn(prevRecord, key);
1291
1291
  const shared = hasPrev ? reconcile(prevRecord[key], nextChild, depth + 1, walk, false) : nextChild;
1292
- copy[key] = shared;
1292
+ if (key === "__proto__") {
1293
+ Object.defineProperty(copy, key, { value: shared, writable: true, enumerable: true, configurable: true });
1294
+ } else {
1295
+ copy[key] = shared;
1296
+ }
1293
1297
  if (!hasPrev || !Object.is(shared, prevRecord[key])) allFromPrev = false;
1294
1298
  if (!Object.is(shared, nextChild)) allFromNext = false;
1295
1299
  }
@@ -1380,6 +1384,8 @@ function query(key, fetcher, options = {}) {
1380
1384
  structuralSharing = true
1381
1385
  } = options;
1382
1386
  const resolveKey = typeof key === "function" ? key : () => key;
1387
+ const enabledGate = typeof enabled === "function" ? derived(() => Boolean(enabled())) : null;
1388
+ const isEnabled = enabledGate ?? (() => enabled);
1383
1389
  const cache = getActiveQueryCache();
1384
1390
  const [data, setData] = signal(initialData);
1385
1391
  const [isFetching, setIsFetching] = signal(false);
@@ -1494,7 +1500,7 @@ function query(key, fetcher, options = {}) {
1494
1500
  }
1495
1501
  }
1496
1502
  async function doFetch() {
1497
- if (disposed || !currentKey || !enabled) return;
1503
+ if (disposed || !currentKey || !untracked2(isEnabled)) return;
1498
1504
  const key2 = currentKey;
1499
1505
  const entry = getOrCreateEntry(cache, key2);
1500
1506
  attachToEntry(entry, key2);
@@ -1596,10 +1602,17 @@ function query(key, fetcher, options = {}) {
1596
1602
  }
1597
1603
  const effectCleanup = effect(() => {
1598
1604
  const key2 = resolveKey();
1605
+ const on = isEnabled();
1599
1606
  const keyChanged = currentKey !== key2;
1600
1607
  currentKey = key2;
1608
+ if (key2 === "") {
1609
+ detachFromEntry();
1610
+ setIsFetching(false);
1611
+ return;
1612
+ }
1601
1613
  const entry = getOrCreateEntry(cache, key2, initialData);
1602
1614
  attachToEntry(entry, key2);
1615
+ if (keyChanged) setIsFetching(entry.promise !== null);
1603
1616
  if (entry.data !== void 0) {
1604
1617
  const view = viewOf(entry);
1605
1618
  batch(() => {
@@ -1610,17 +1623,17 @@ function query(key, fetcher, options = {}) {
1610
1623
  }
1611
1624
  if (!keyChanged && currentKey === key2 && entry.data !== void 0) {
1612
1625
  const isDataStale2 = entry.dataUpdatedAt === 0 || Date.now() - entry.dataUpdatedAt >= staleTime;
1613
- if (enabled && isDataStale2 && !entry.promise) doFetch();
1626
+ if (on && isDataStale2 && !entry.promise) doFetch();
1614
1627
  return;
1615
1628
  }
1616
1629
  const isDataStale = entry.dataUpdatedAt === 0 || Date.now() - entry.dataUpdatedAt >= staleTime;
1617
- if (enabled && (entry.data === void 0 || isDataStale)) {
1630
+ if (on && (entry.data === void 0 || isDataStale)) {
1618
1631
  doFetch();
1619
1632
  }
1620
1633
  });
1621
1634
  if (refetchInterval && refetchInterval > 0) {
1622
1635
  intervalTimer = setInterval(() => {
1623
- if (!disposed && currentKey && enabled) doFetch();
1636
+ if (!disposed && currentKey) doFetch();
1624
1637
  }, refetchInterval);
1625
1638
  }
1626
1639
  let focusHandler = null;
@@ -1628,13 +1641,13 @@ function query(key, fetcher, options = {}) {
1628
1641
  if (typeof globalThis !== "undefined" && typeof globalThis.addEventListener === "function") {
1629
1642
  if (refetchOnWindowFocus) {
1630
1643
  focusHandler = () => {
1631
- if (!disposed && currentKey && enabled) doFetch();
1644
+ if (!disposed && currentKey) doFetch();
1632
1645
  };
1633
1646
  globalThis.addEventListener("focus", focusHandler);
1634
1647
  }
1635
1648
  if (refetchOnReconnect) {
1636
1649
  onlineHandler = () => {
1637
- if (!disposed && currentKey && enabled) doFetch();
1650
+ if (!disposed && currentKey) doFetch();
1638
1651
  };
1639
1652
  globalThis.addEventListener("online", onlineHandler);
1640
1653
  }
@@ -1646,6 +1659,7 @@ function query(key, fetcher, options = {}) {
1646
1659
  stopSelect?.();
1647
1660
  loading.dispose();
1648
1661
  isStale.dispose();
1662
+ enabledGate?.dispose();
1649
1663
  if (intervalTimer) clearInterval(intervalTimer);
1650
1664
  detachFromEntry();
1651
1665
  if (focusHandler && typeof globalThis.removeEventListener === "function") {
@@ -1825,6 +1839,8 @@ function infiniteQuery(key, fetcher, options) {
1825
1839
  onError
1826
1840
  } = options;
1827
1841
  const resolveKey = typeof key === "function" ? key : () => key;
1842
+ const enabledGate = typeof enabled === "function" ? derived(() => Boolean(enabled())) : null;
1843
+ const isEnabled = enabledGate ?? (() => enabled);
1828
1844
  const [pages, setPages] = signal([]);
1829
1845
  const [isFetching, setIsFetching] = signal(false);
1830
1846
  const [isFetchingNext, setIsFetchingNext] = signal(false);
@@ -1903,7 +1919,7 @@ function infiniteQuery(key, fetcher, options) {
1903
1919
  }
1904
1920
  const effectCleanup = effect(() => {
1905
1921
  resolveKey();
1906
- if (enabled) {
1922
+ if (isEnabled()) {
1907
1923
  abortController?.abort();
1908
1924
  batch(() => {
1909
1925
  setPages([]);
@@ -1944,6 +1960,7 @@ function infiniteQuery(key, fetcher, options) {
1944
1960
  loading.dispose();
1945
1961
  hasNextPage.dispose();
1946
1962
  hasPreviousPage.dispose();
1963
+ enabledGate?.dispose();
1947
1964
  }
1948
1965
  return {
1949
1966
  data,
@@ -2660,6 +2677,9 @@ function sanitizeUrl(url) {
2660
2677
  const probe = stripControlChars(url).trim();
2661
2678
  if (!probe) return "";
2662
2679
  const output = url.replace(OUTER_TRIM, "");
2680
+ if (probe.length >= 2 && (probe[0] === "\\" || probe[0] === "/") && (probe[1] === "\\" || probe[1] === "/") && (probe[0] === "\\" || probe[1] === "\\")) {
2681
+ return "";
2682
+ }
2663
2683
  const lower = probe.toLowerCase();
2664
2684
  let schemeEnd = -1;
2665
2685
  for (let i = 0; i < lower.length; i++) {
package/dist/data.d.cts CHANGED
@@ -137,8 +137,21 @@ interface QueryOptions<T> {
137
137
  staleTime?: number;
138
138
  /** Time in ms to keep unused cache entries. Default: 300000 (5 min) */
139
139
  cacheTime?: number;
140
- /** Whether to fetch on creation. Default: true */
141
- enabled?: boolean;
140
+ /**
141
+ * Whether the query may fetch. Default: `true`.
142
+ *
143
+ * A function is reactive: while it returns `false` the query stays idle
144
+ * (nothing fetches — not the key effect, `refetch()`, `invalidateQueries`,
145
+ * the interval, focus or reconnect), and it fetches as soon as it returns
146
+ * `true`, following the usual staleness rules.
147
+ *
148
+ * ```ts
149
+ * query(() => `user:${userId()}`, fetchUser, { enabled: () => userId() != null });
150
+ * ```
151
+ *
152
+ * An empty key (`""`) also holds a query idle, without creating a cache entry.
153
+ */
154
+ enabled?: boolean | (() => boolean);
142
155
  /** Retry options for failed fetches */
143
156
  retry?: RetryOptions;
144
157
  /** Initial data before first fetch */
@@ -325,8 +338,14 @@ interface InfiniteQueryOptions<TData, TPageParam = number> {
325
338
  * unbounded.
326
339
  */
327
340
  maxPages?: number;
328
- /** Whether to fetch on creation. Default: true */
329
- enabled?: boolean;
341
+ /**
342
+ * Whether the query may load its first page. Default: `true`.
343
+ *
344
+ * A function is reactive: while it returns `false` the query stays idle, and
345
+ * each time it turns `true` the query loads from `initialPageParam`, as a key
346
+ * change does.
347
+ */
348
+ enabled?: boolean | (() => boolean);
330
349
  /** Retry options */
331
350
  retry?: RetryOptions;
332
351
  /** Called on successful page fetch */
package/dist/data.d.ts CHANGED
@@ -137,8 +137,21 @@ interface QueryOptions<T> {
137
137
  staleTime?: number;
138
138
  /** Time in ms to keep unused cache entries. Default: 300000 (5 min) */
139
139
  cacheTime?: number;
140
- /** Whether to fetch on creation. Default: true */
141
- enabled?: boolean;
140
+ /**
141
+ * Whether the query may fetch. Default: `true`.
142
+ *
143
+ * A function is reactive: while it returns `false` the query stays idle
144
+ * (nothing fetches — not the key effect, `refetch()`, `invalidateQueries`,
145
+ * the interval, focus or reconnect), and it fetches as soon as it returns
146
+ * `true`, following the usual staleness rules.
147
+ *
148
+ * ```ts
149
+ * query(() => `user:${userId()}`, fetchUser, { enabled: () => userId() != null });
150
+ * ```
151
+ *
152
+ * An empty key (`""`) also holds a query idle, without creating a cache entry.
153
+ */
154
+ enabled?: boolean | (() => boolean);
142
155
  /** Retry options for failed fetches */
143
156
  retry?: RetryOptions;
144
157
  /** Initial data before first fetch */
@@ -325,8 +338,14 @@ interface InfiniteQueryOptions<TData, TPageParam = number> {
325
338
  * unbounded.
326
339
  */
327
340
  maxPages?: number;
328
- /** Whether to fetch on creation. Default: true */
329
- enabled?: boolean;
341
+ /**
342
+ * Whether the query may load its first page. Default: `true`.
343
+ *
344
+ * A function is reactive: while it returns `false` the query stays idle, and
345
+ * each time it turns `true` the query loads from `initialPageParam`, as a key
346
+ * change does.
347
+ */
348
+ enabled?: boolean | (() => boolean);
330
349
  /** Retry options */
331
350
  retry?: RetryOptions;
332
351
  /** Called on successful page fetch */
package/dist/data.js CHANGED
@@ -22,14 +22,14 @@ import {
22
22
  throttle,
23
23
  withLoader,
24
24
  withRetry
25
- } from "./chunk-WOLJZUFQ.js";
26
- import "./chunk-RUSSKG6G.js";
27
- import "./chunk-2INLLLMZ.js";
25
+ } from "./chunk-562UVHMM.js";
26
+ import "./chunk-X7NRHFZR.js";
28
27
  import "./chunk-7ZHH77QA.js";
29
- import "./chunk-WEQ3DMVL.js";
28
+ import "./chunk-SBTZEYNI.js";
29
+ import "./chunk-EXXSN3BW.js";
30
30
  import "./chunk-7XHATCIH.js";
31
- import "./chunk-FQRUXCEE.js";
32
- import "./chunk-JWKYU5GV.js";
31
+ import "./chunk-SYHGGDNW.js";
32
+ import "./chunk-G2WHYEL7.js";
33
33
  import "./chunk-NYNYSPK7.js";
34
34
  export {
35
35
  __resetQueryCache,
package/dist/devtools.cjs CHANGED
@@ -844,7 +844,7 @@ function forEachSubscriber(signal2, visit) {
844
844
  }
845
845
 
846
846
  // src/reactivity/track.ts
847
- var _runtimeVersion = true ? "4.7.0" : "dev";
847
+ var _runtimeVersion = true ? "4.9.0" : "dev";
848
848
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
849
849
  function resolveReactiveApi() {
850
850
  const g = globalThis;
package/dist/devtools.js CHANGED
@@ -35,13 +35,13 @@ import {
35
35
  trackCleanup,
36
36
  walkDependencyGraph,
37
37
  withErrorTracking
38
- } from "./chunk-DKXRACVN.js";
39
- import "./chunk-2INLLLMZ.js";
38
+ } from "./chunk-K6K5ACO6.js";
40
39
  import "./chunk-7ZHH77QA.js";
41
- import "./chunk-WEQ3DMVL.js";
40
+ import "./chunk-SBTZEYNI.js";
41
+ import "./chunk-EXXSN3BW.js";
42
42
  import "./chunk-7XHATCIH.js";
43
- import "./chunk-FQRUXCEE.js";
44
- import "./chunk-JWKYU5GV.js";
43
+ import "./chunk-SYHGGDNW.js";
44
+ import "./chunk-G2WHYEL7.js";
45
45
  import {
46
46
  isDev
47
47
  } from "./chunk-NYNYSPK7.js";