sibujs 4.1.0 → 4.3.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 +24 -0
  2. package/dist/browser.cjs +13 -21
  3. package/dist/browser.js +6 -6
  4. package/dist/build.cjs +307 -111
  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-ANGSYNPX.js → chunk-2LSY4WDM.js} +86 -44
  9. package/dist/{chunk-VPP2FONR.js → chunk-2WLZ6757.js} +17 -10
  10. package/dist/{chunk-DCDS2PQ6.js → chunk-3E5YNK2F.js} +2 -2
  11. package/dist/{chunk-TIRZCERI.js → chunk-5DXA2J44.js} +1 -1
  12. package/dist/{chunk-PFTDGEPZ.js → chunk-7BJLKDBI.js} +3 -3
  13. package/dist/{chunk-XSZRJW76.js → chunk-7BPN4MXR.js} +6 -6
  14. package/dist/{chunk-BMSN6NWQ.js → chunk-7GQIRV27.js} +6 -6
  15. package/dist/{chunk-SHDVWATN.js → chunk-7UBG6LDP.js} +7 -8
  16. package/dist/{chunk-MRAMKDHQ.js → chunk-7UK6BSAT.js} +1 -1
  17. package/dist/{chunk-LOFZHP6V.js → chunk-AAHT6AKT.js} +7 -8
  18. package/dist/{chunk-FMHIQNMP.js → chunk-G7GPNB6N.js} +5 -5
  19. package/dist/{chunk-XIHMS2Y6.js → chunk-GZYQNIPS.js} +8 -8
  20. package/dist/{chunk-5INI7D2L.js → chunk-IXKSNWV5.js} +74 -11
  21. package/dist/{chunk-KSOEJGFO.js → chunk-IYHM4FWD.js} +3 -3
  22. package/dist/{chunk-AW6HPJ3L.js → chunk-KKLW7YWL.js} +1 -1
  23. package/dist/{chunk-5GJ5RTHA.js → chunk-L4B3Y3OM.js} +7 -8
  24. package/dist/{chunk-UFSQEO7P.js → chunk-MYA5HEK5.js} +7 -8
  25. package/dist/{chunk-HYCCIYNS.js → chunk-NIOYEGBQ.js} +13 -14
  26. package/dist/{chunk-DHDZ7IVN.js → chunk-QKRPLZ2V.js} +7 -8
  27. package/dist/{chunk-FB7KQXZI.js → chunk-R4EF7V3K.js} +4 -4
  28. package/dist/{chunk-IM7N5WR4.js → chunk-RE6KLGMV.js} +2 -2
  29. package/dist/{chunk-D3SZ4LVS.js → chunk-TSL64O5L.js} +5 -6
  30. package/dist/{chunk-DAKLOMKS.js → chunk-UD6YE2GW.js} +3 -3
  31. package/dist/{chunk-4U7KD3RY.js → chunk-USS5ENJ2.js} +3 -3
  32. package/dist/{chunk-I3VI34DP.js → chunk-XGCZ45NH.js} +149 -33
  33. package/dist/{chunk-4RH6D7GR.js → chunk-ZVYNIUOG.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 +15 -24
  37. package/dist/data.js +9 -9
  38. package/dist/devtools.cjs +18 -22
  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 +222 -67
  44. package/dist/ecosystem.js +11 -11
  45. package/dist/extras.cjs +243 -86
  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 +301 -105
  50. package/dist/index.d.cts +190 -37
  51. package/dist/index.d.ts +190 -37
  52. package/dist/index.js +15 -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 +13 -22
  56. package/dist/motion.js +5 -5
  57. package/dist/patterns.cjs +16 -24
  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 +16 -27
  62. package/dist/performance.js +9 -9
  63. package/dist/plugins.cjs +263 -88
  64. package/dist/plugins.d.cts +102 -2
  65. package/dist/plugins.d.ts +102 -2
  66. package/dist/plugins.js +21 -19
  67. package/dist/{ssr-JFP5535P.js → ssr-Y7XOEPEN.js} +5 -5
  68. package/dist/ssr.cjs +233 -79
  69. package/dist/ssr.js +12 -12
  70. package/dist/testing.cjs +11 -19
  71. package/dist/testing.js +5 -5
  72. package/dist/ui.cjs +98 -43
  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 +13 -21
  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 +5 -2
@@ -1,27 +1,121 @@
1
1
  import {
2
2
  bindAttribute,
3
3
  setSafeAttribute
4
- } from "./chunk-SHDVWATN.js";
4
+ } from "./chunk-7UBG6LDP.js";
5
5
  import {
6
6
  isEventHandlerAttr,
7
7
  sanitizeCSSValue,
8
8
  sanitizeStyleAttribute
9
- } from "./chunk-5INI7D2L.js";
9
+ } from "./chunk-IXKSNWV5.js";
10
10
  import {
11
11
  dispose,
12
12
  registerDisposer
13
- } from "./chunk-DHDZ7IVN.js";
13
+ } from "./chunk-QKRPLZ2V.js";
14
14
  import {
15
15
  reactiveBinding
16
- } from "./chunk-D3SZ4LVS.js";
16
+ } from "./chunk-TSL64O5L.js";
17
17
  import {
18
+ DEV,
18
19
  devWarn,
19
- isDev,
20
+ devWarnLazy,
20
21
  reportError
21
- } from "./chunk-VPP2FONR.js";
22
+ } from "./chunk-2WLZ6757.js";
23
+
24
+ // src/core/rendering/focusPreservation.ts
25
+ var IDENTITY_ATTRS = ["data-focus-key", "id", "name"];
26
+ var SELECTABLE_TYPES = /* @__PURE__ */ new Set(["text", "search", "url", "tel", "password", ""]);
27
+ function selectionOf(el) {
28
+ const tag = el.tagName;
29
+ const selectable = tag === "TEXTAREA" || tag === "INPUT" && SELECTABLE_TYPES.has(el.type.toLowerCase());
30
+ if (!selectable) return { start: null, end: null, direction: null };
31
+ const field = el;
32
+ return { start: field.selectionStart, end: field.selectionEnd, direction: field.selectionDirection };
33
+ }
34
+ function captureFocusWithin(nodes) {
35
+ if (nodes.length === 0 || typeof document === "undefined") return null;
36
+ const active = document.activeElement;
37
+ if (!active || active === document.body) return null;
38
+ let inside = false;
39
+ for (let i = 0; i < nodes.length; i++) {
40
+ const n = nodes[i];
41
+ if (n === active || n.nodeType === 1 && n.contains(active)) {
42
+ inside = true;
43
+ break;
44
+ }
45
+ }
46
+ if (!inside) return null;
47
+ let attr = null;
48
+ let value = null;
49
+ for (let i = 0; i < IDENTITY_ATTRS.length; i++) {
50
+ const candidate = active.getAttribute(IDENTITY_ATTRS[i]);
51
+ if (candidate) {
52
+ attr = IDENTITY_ATTRS[i];
53
+ value = candidate;
54
+ break;
55
+ }
56
+ }
57
+ return { el: active, attr, value, tag: active.tagName, ...selectionOf(active) };
58
+ }
59
+ function quote(value) {
60
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
61
+ }
62
+ function findUniqueByIdentity(nodes, selector) {
63
+ let el = null;
64
+ let count = 0;
65
+ for (let i = 0; i < nodes.length; i++) {
66
+ const n = nodes[i];
67
+ if (n.nodeType !== 1) continue;
68
+ const root = n;
69
+ if (root.matches(selector)) {
70
+ count++;
71
+ el ?? (el = root);
72
+ }
73
+ const inner = root.querySelectorAll(selector);
74
+ for (let j = 0; j < inner.length; j++) {
75
+ count++;
76
+ el ?? (el = inner[j]);
77
+ }
78
+ if (count > 1) return { el: null, count };
79
+ }
80
+ return count === 1 ? { el, count } : { el: null, count };
81
+ }
82
+ function refocus(el, snapshot) {
83
+ el.focus();
84
+ if (snapshot.start !== null && typeof el.setSelectionRange === "function") {
85
+ const field = el;
86
+ const max = field.value.length;
87
+ const start = Math.min(snapshot.start, max);
88
+ const end = Math.min(snapshot.end ?? start, max);
89
+ field.setSelectionRange(start, end, snapshot.direction ?? "none");
90
+ }
91
+ }
92
+ function restoreFocusWithin(snapshot, nodes, where) {
93
+ if (!snapshot) return;
94
+ if (typeof document === "undefined") return;
95
+ const active = document.activeElement;
96
+ if (active === snapshot.el) return;
97
+ if (active && active !== document.body && active !== document.documentElement) return;
98
+ if (snapshot.el.isConnected) {
99
+ refocus(snapshot.el, snapshot);
100
+ return;
101
+ }
102
+ let ambiguous = false;
103
+ if (snapshot.attr && snapshot.value) {
104
+ const { el: replacement, count } = findUniqueByIdentity(nodes, `[${snapshot.attr}=${quote(snapshot.value)}]`);
105
+ if (replacement && replacement.tagName === snapshot.tag) {
106
+ refocus(replacement, snapshot);
107
+ return;
108
+ }
109
+ ambiguous = count > 1;
110
+ }
111
+ devWarnLazy(() => {
112
+ const id = snapshot.attr ? ` (${snapshot.attr}="${snapshot.value}")` : "";
113
+ const why = ambiguous ? `several elements in the rebuilt subtree share ${snapshot.attr}="${snapshot.value}", so there is no way to tell which one the user was in \u2014 a shared name (a radio group) identifies a GROUP, not a control. Add a unique id or data-focus-key to each` : "no element in the rebuilt subtree carries the same identity. Give it a stable id, name, or data-focus-key that the rebuild reproduces";
114
+ return `${where}: a reactive rebuild discarded the focused element <${snapshot.tag.toLowerCase()}>${id} and could not re-establish it, so focus fell back to <body>. An in-progress edit ends here, and an in-progress IME composition is lost outright \u2014 a composition belongs to the DOM node, so it cannot survive the node being destroyed. ${why}, or stop the rebuild from happening: key the subtree with match(() => shapeKey(), { \u2026 }) so it is re-created only when the shape actually changes, and drive the rest with reactive attributes and text children, which mutate nodes in place instead of replacing them.`;
115
+ });
116
+ }
22
117
 
23
118
  // src/reactivity/bindChildNode.ts
24
- var _isDev = isDev();
25
119
  function bindChildNode(placeholder, getter) {
26
120
  let lastNodes = [];
27
121
  function commit() {
@@ -32,6 +126,7 @@ function bindChildNode(placeholder, getter) {
32
126
  reportError(err, { phase: "binding", name: "bindChildNode", node: placeholder });
33
127
  return;
34
128
  }
129
+ const focused = captureFocusWithin(lastNodes);
35
130
  if (result == null || typeof result === "boolean") {
36
131
  for (let i = 0; i < lastNodes.length; i++) {
37
132
  const node = lastNodes[i];
@@ -39,6 +134,7 @@ function bindChildNode(placeholder, getter) {
39
134
  if (node.parentNode) node.parentNode.removeChild(node);
40
135
  }
41
136
  lastNodes.length = 0;
137
+ restoreFocusWithin(focused, lastNodes, "bindChildNode");
42
138
  return;
43
139
  }
44
140
  const parent = placeholder.parentNode;
@@ -55,8 +151,7 @@ function bindChildNode(placeholder, getter) {
55
151
  if (item == null || typeof item === "boolean") continue;
56
152
  const node = item instanceof Node ? item : document.createTextNode(String(item));
57
153
  if (seen.has(node)) {
58
- if (_isDev)
59
- devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
154
+ if (DEV) devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
60
155
  continue;
61
156
  }
62
157
  seen.add(node);
@@ -89,13 +184,13 @@ function bindChildNode(placeholder, getter) {
89
184
  prev = node;
90
185
  }
91
186
  lastNodes = newNodes;
187
+ restoreFocusWithin(focused, newNodes, "bindChildNode");
92
188
  }
93
189
  return reactiveBinding(commit, placeholder);
94
190
  }
95
191
 
96
192
  // src/core/rendering/tagFactory.ts
97
193
  var SVG_NS = "http://www.w3.org/2000/svg";
98
- var _isDev2 = isDev();
99
194
  var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
100
195
  function isBlockedTag(tag) {
101
196
  return BLOCKED_TAGS.has(tag.toLowerCase());
@@ -113,18 +208,9 @@ var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
113
208
  "self",
114
209
  "frames"
115
210
  ]);
116
- function looksLikeClassList(s) {
117
- const t = s.trim();
118
- if (!t) return false;
119
- const tokens = t.split(/\s+/);
120
- let sawClassish = false;
121
- for (let i = 0; i < tokens.length; i++) {
122
- const tok = tokens[i];
123
- if (!/^-?[A-Za-z_][A-Za-z0-9_:/.-]*$/.test(tok)) return false;
124
- if (/[-:/0-9]/.test(tok)) sawClassish = true;
125
- }
126
- return sawClassish;
127
- }
211
+ var warnedLoneStrings = /* @__PURE__ */ new Set();
212
+ var MAX_WARNED_LONE_STRINGS = 100;
213
+ var loneStringCapAnnounced = false;
128
214
  var kebabCache = /* @__PURE__ */ new Map();
129
215
  function toKebab(prop) {
130
216
  let cached = kebabCache.get(prop);
@@ -136,13 +222,13 @@ function toKebab(prop) {
136
222
  function applyStyle(el, style) {
137
223
  if (typeof style === "function") {
138
224
  const teardown = reactiveBinding(() => {
139
- el.setAttribute("style", sanitizeStyleAttribute(String(style())));
225
+ el.setAttribute("style", sanitizeStyleAttribute(String(style()), { element: el }));
140
226
  }, el);
141
227
  registerDisposer(el, teardown);
142
228
  return;
143
229
  }
144
230
  if (typeof style === "string") {
145
- el.setAttribute("style", sanitizeStyleAttribute(style));
231
+ el.setAttribute("style", sanitizeStyleAttribute(style, { element: el }));
146
232
  return;
147
233
  }
148
234
  const htmlEl = el;
@@ -152,14 +238,26 @@ function applyStyle(el, style) {
152
238
  if (typeof val === "function") {
153
239
  const getter = val;
154
240
  const teardown = reactiveBinding(() => {
155
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter())));
241
+ htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter()), { property: name, element: el }));
156
242
  }, el);
157
243
  registerDisposer(el, teardown);
158
244
  } else {
159
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(val)));
245
+ htmlEl.style.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
160
246
  }
161
247
  }
162
248
  }
249
+ function resolveClassValue(cls) {
250
+ if (typeof cls === "string") return cls;
251
+ if (typeof cls === "function") return cls();
252
+ if (!cls) return "";
253
+ let out = "";
254
+ for (const name in cls) {
255
+ const val = cls[name];
256
+ const active = typeof val === "function" ? val() : val;
257
+ if (active) out = out ? `${out} ${name}` : name;
258
+ }
259
+ return out;
260
+ }
163
261
  function applyClass(el, cls) {
164
262
  if (typeof cls === "string") {
165
263
  el.setAttribute("class", cls);
@@ -263,11 +361,26 @@ var tagFactory = (tag, ns) => {
263
361
  appendChildren(el, second);
264
362
  return el;
265
363
  }
266
- if (_isDev2 && looksLikeClassList(first)) {
267
- devWarn(
268
- `tagFactory: lone string "${first}" looks like a class list but is being rendered as TEXT. For a class, use ${tag}({ class: "${first}" }) \u2014 or ${tag}("${first}", children) to set the class AND add children.`
269
- );
270
- }
364
+ devWarnLazy(() => {
365
+ const tokens = first.trim().split(/\s+/);
366
+ if (tokens.length < 2) return "";
367
+ let utilityTokens = 0;
368
+ for (let i = 0; i < tokens.length; i++) {
369
+ const tok = tokens[i];
370
+ if (!/^-?[A-Za-z_][A-Za-z0-9_:/.-]*$/.test(tok)) return "";
371
+ if (/[-:/0-9]/.test(tok)) utilityTokens++;
372
+ }
373
+ if (utilityTokens < 2) return "";
374
+ const key = `${tag}|${first}`;
375
+ if (warnedLoneStrings.has(key)) return "";
376
+ if (warnedLoneStrings.size >= MAX_WARNED_LONE_STRINGS) {
377
+ if (loneStringCapAnnounced) return "";
378
+ loneStringCapAnnounced = true;
379
+ return `tagFactory: ${MAX_WARNED_LONE_STRINGS} distinct lone-string class warnings have been reported; suppressing further ones for the rest of this session so they cannot flood the console. Fix the reported ones and reload to see any that remain.`;
380
+ }
381
+ warnedLoneStrings.add(key);
382
+ return `tagFactory: lone string "${first}" looks like a class list but is being rendered as TEXT. For a class, use ${tag}({ class: "${first}" }) \u2014 or ${tag}("${first}", children) to set the class AND add children.`;
383
+ });
271
384
  el.textContent = first;
272
385
  return el;
273
386
  }
@@ -284,7 +397,7 @@ var tagFactory = (tag, ns) => {
284
397
  if (pClass != null) applyClass(el, pClass);
285
398
  const pId = props.id;
286
399
  if (pId != null) {
287
- if (_isDev2 && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
400
+ if (DEV && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
288
401
  devWarn(
289
402
  `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
290
403
  );
@@ -299,7 +412,7 @@ var tagFactory = (tag, ns) => {
299
412
  const handler = pOn[ev];
300
413
  if (typeof handler === "function") {
301
414
  el.addEventListener(ev, handler);
302
- } else if (_isDev2) {
415
+ } else if (DEV) {
303
416
  devWarn(
304
417
  `tagFactory: on.${ev} handler is not a function (got ${typeof handler}). Event listener was not attached.`
305
418
  );
@@ -341,7 +454,10 @@ var tagFactory = (tag, ns) => {
341
454
  };
342
455
 
343
456
  export {
457
+ captureFocusWithin,
458
+ restoreFocusWithin,
344
459
  bindChildNode,
345
460
  SVG_NS,
461
+ resolveClassValue,
346
462
  tagFactory
347
463
  };
@@ -1,26 +1,26 @@
1
1
  import {
2
2
  stripHtml
3
- } from "./chunk-5INI7D2L.js";
3
+ } from "./chunk-IXKSNWV5.js";
4
4
  import {
5
5
  globalSingleton
6
6
  } from "./chunk-7ZHH77QA.js";
7
7
  import {
8
8
  effect
9
- } from "./chunk-PFTDGEPZ.js";
9
+ } from "./chunk-7BJLKDBI.js";
10
10
  import {
11
11
  dispose
12
- } from "./chunk-DHDZ7IVN.js";
12
+ } from "./chunk-QKRPLZ2V.js";
13
13
  import {
14
14
  signal
15
- } from "./chunk-UFSQEO7P.js";
15
+ } from "./chunk-MYA5HEK5.js";
16
16
  import {
17
17
  forEachSubscriber,
18
18
  getSubscriberCount,
19
19
  getSubscriberDeps
20
- } from "./chunk-D3SZ4LVS.js";
20
+ } from "./chunk-TSL64O5L.js";
21
21
  import {
22
22
  isDev
23
- } from "./chunk-VPP2FONR.js";
23
+ } from "./chunk-2WLZ6757.js";
24
24
 
25
25
  // src/devtools/debug.ts
26
26
  var debugEnabled = false;
@@ -29,13 +29,69 @@ interface FormReturn<T extends object> {
29
29
  reset: () => void;
30
30
  setError: (field: keyof T, message: string) => void;
31
31
  }
32
+ /**
33
+ * Require a non-empty value. Rejects `null`, `undefined`, `""` and `[]`.
34
+ *
35
+ * @param message Overrides the default message.
36
+ * @returns A validator returning the message on failure, `null` on success.
37
+ */
32
38
  declare function required(message?: string): ValidatorFn<unknown>;
39
+ /**
40
+ * Require a minimum string length. An empty value passes — pair with
41
+ * {@link required} when the field is mandatory.
42
+ *
43
+ * @param min Minimum number of characters.
44
+ * @param message Overrides the default message.
45
+ * @returns A validator returning the message on failure, `null` on success.
46
+ */
33
47
  declare function minLength(min: number, message?: string): ValidatorFn<string>;
48
+ /**
49
+ * Require a maximum string length. An empty value passes.
50
+ *
51
+ * @param max Maximum number of characters.
52
+ * @param message Overrides the default message.
53
+ * @returns A validator returning the message on failure, `null` on success.
54
+ */
34
55
  declare function maxLength(max: number, message?: string): ValidatorFn<string>;
56
+ /**
57
+ * Require the value to match a regular expression. An empty value passes.
58
+ *
59
+ * @param regex Pattern the value must satisfy.
60
+ * @param message Message used on failure.
61
+ * @returns A validator returning the message on failure, `null` on success.
62
+ */
35
63
  declare function matchesPattern(regex: RegExp, message?: string): ValidatorFn<string>;
64
+ /**
65
+ * Require a syntactically plausible email address. Deliberately permissive —
66
+ * the only authoritative check is sending mail to it. An empty value passes.
67
+ *
68
+ * @param message Message used on failure.
69
+ * @returns A validator returning the message on failure, `null` on success.
70
+ */
36
71
  declare function email(message?: string): ValidatorFn<string>;
72
+ /**
73
+ * Require a numeric value to be at least `minVal`. `null`/`undefined` passes.
74
+ *
75
+ * @param minVal Inclusive lower bound.
76
+ * @param message Overrides the default message.
77
+ * @returns A validator returning the message on failure, `null` on success.
78
+ */
37
79
  declare function min(minVal: number, message?: string): ValidatorFn<number>;
80
+ /**
81
+ * Require a numeric value to be at most `maxVal`. `null`/`undefined` passes.
82
+ *
83
+ * @param maxVal Inclusive upper bound.
84
+ * @param message Overrides the default message.
85
+ * @returns A validator returning the message on failure, `null` on success.
86
+ */
38
87
  declare function max(maxVal: number, message?: string): ValidatorFn<number>;
88
+ /**
89
+ * Build a validator from an arbitrary predicate.
90
+ *
91
+ * @param fn Returns `true` when the value is acceptable.
92
+ * @param message Message used when `fn` returns `false`.
93
+ * @returns A validator returning the message on failure, `null` on success.
94
+ */
39
95
  declare function custom<T>(fn: (value: T) => boolean, message: string): ValidatorFn<T>;
40
96
  /**
41
97
  * Props returned by bindField, ready to spread into an input tag factory.
@@ -180,6 +236,13 @@ declare function hotkey(combo: string, handler: (e: KeyboardEvent) => void, opti
180
236
  meta?: boolean;
181
237
  preventDefault?: boolean;
182
238
  }): () => void;
239
+ /**
240
+ * Announce a message to screen readers through a shared live region.
241
+ *
242
+ * @param message Text to announce.
243
+ * @param priority `"polite"` waits for a pause; `"assertive"` interrupts.
244
+ * @returns Nothing. A no-op without a DOM, so it is safe under SSR.
245
+ */
183
246
  declare function announce(message: string, priority?: "polite" | "assertive"): void;
184
247
 
185
248
  /**
@@ -266,6 +329,12 @@ declare function bindData(el: HTMLElement, key: string, getter: AttributeValue |
266
329
  * @internal
267
330
  */
268
331
  declare function __resetDialogStack(): void;
332
+ /**
333
+ * Dialog open/close state with focus trapping and scroll locking wired in.
334
+ *
335
+ * @returns A handle exposing `open`, `close`, `toggle` and the reactive
336
+ * `isOpen` accessor.
337
+ */
269
338
  declare function dialog(): {
270
339
  open: () => void;
271
340
  close: () => void;
@@ -296,6 +365,14 @@ interface ToastInstance {
296
365
  dismiss: (id: string) => void;
297
366
  dismissAll: () => void;
298
367
  }
368
+ /**
369
+ * Create a toast queue.
370
+ *
371
+ * @param options `duration` in ms before auto-dismiss; `maxToasts` caps how
372
+ * many are shown at once.
373
+ * @returns A {@link ToastInstance} with `info`, `success`, `warning`, `error`,
374
+ * plus the reactive list to render.
375
+ */
299
376
  declare function toast(options?: {
300
377
  duration?: number;
301
378
  maxToasts?: number;
@@ -29,13 +29,69 @@ interface FormReturn<T extends object> {
29
29
  reset: () => void;
30
30
  setError: (field: keyof T, message: string) => void;
31
31
  }
32
+ /**
33
+ * Require a non-empty value. Rejects `null`, `undefined`, `""` and `[]`.
34
+ *
35
+ * @param message Overrides the default message.
36
+ * @returns A validator returning the message on failure, `null` on success.
37
+ */
32
38
  declare function required(message?: string): ValidatorFn<unknown>;
39
+ /**
40
+ * Require a minimum string length. An empty value passes — pair with
41
+ * {@link required} when the field is mandatory.
42
+ *
43
+ * @param min Minimum number of characters.
44
+ * @param message Overrides the default message.
45
+ * @returns A validator returning the message on failure, `null` on success.
46
+ */
33
47
  declare function minLength(min: number, message?: string): ValidatorFn<string>;
48
+ /**
49
+ * Require a maximum string length. An empty value passes.
50
+ *
51
+ * @param max Maximum number of characters.
52
+ * @param message Overrides the default message.
53
+ * @returns A validator returning the message on failure, `null` on success.
54
+ */
34
55
  declare function maxLength(max: number, message?: string): ValidatorFn<string>;
56
+ /**
57
+ * Require the value to match a regular expression. An empty value passes.
58
+ *
59
+ * @param regex Pattern the value must satisfy.
60
+ * @param message Message used on failure.
61
+ * @returns A validator returning the message on failure, `null` on success.
62
+ */
35
63
  declare function matchesPattern(regex: RegExp, message?: string): ValidatorFn<string>;
64
+ /**
65
+ * Require a syntactically plausible email address. Deliberately permissive —
66
+ * the only authoritative check is sending mail to it. An empty value passes.
67
+ *
68
+ * @param message Message used on failure.
69
+ * @returns A validator returning the message on failure, `null` on success.
70
+ */
36
71
  declare function email(message?: string): ValidatorFn<string>;
72
+ /**
73
+ * Require a numeric value to be at least `minVal`. `null`/`undefined` passes.
74
+ *
75
+ * @param minVal Inclusive lower bound.
76
+ * @param message Overrides the default message.
77
+ * @returns A validator returning the message on failure, `null` on success.
78
+ */
37
79
  declare function min(minVal: number, message?: string): ValidatorFn<number>;
80
+ /**
81
+ * Require a numeric value to be at most `maxVal`. `null`/`undefined` passes.
82
+ *
83
+ * @param maxVal Inclusive upper bound.
84
+ * @param message Overrides the default message.
85
+ * @returns A validator returning the message on failure, `null` on success.
86
+ */
38
87
  declare function max(maxVal: number, message?: string): ValidatorFn<number>;
88
+ /**
89
+ * Build a validator from an arbitrary predicate.
90
+ *
91
+ * @param fn Returns `true` when the value is acceptable.
92
+ * @param message Message used when `fn` returns `false`.
93
+ * @returns A validator returning the message on failure, `null` on success.
94
+ */
39
95
  declare function custom<T>(fn: (value: T) => boolean, message: string): ValidatorFn<T>;
40
96
  /**
41
97
  * Props returned by bindField, ready to spread into an input tag factory.
@@ -180,6 +236,13 @@ declare function hotkey(combo: string, handler: (e: KeyboardEvent) => void, opti
180
236
  meta?: boolean;
181
237
  preventDefault?: boolean;
182
238
  }): () => void;
239
+ /**
240
+ * Announce a message to screen readers through a shared live region.
241
+ *
242
+ * @param message Text to announce.
243
+ * @param priority `"polite"` waits for a pause; `"assertive"` interrupts.
244
+ * @returns Nothing. A no-op without a DOM, so it is safe under SSR.
245
+ */
183
246
  declare function announce(message: string, priority?: "polite" | "assertive"): void;
184
247
 
185
248
  /**
@@ -266,6 +329,12 @@ declare function bindData(el: HTMLElement, key: string, getter: AttributeValue |
266
329
  * @internal
267
330
  */
268
331
  declare function __resetDialogStack(): void;
332
+ /**
333
+ * Dialog open/close state with focus trapping and scroll locking wired in.
334
+ *
335
+ * @returns A handle exposing `open`, `close`, `toggle` and the reactive
336
+ * `isOpen` accessor.
337
+ */
269
338
  declare function dialog(): {
270
339
  open: () => void;
271
340
  close: () => void;
@@ -296,6 +365,14 @@ interface ToastInstance {
296
365
  dismiss: (id: string) => void;
297
366
  dismissAll: () => void;
298
367
  }
368
+ /**
369
+ * Create a toast queue.
370
+ *
371
+ * @param options `duration` in ms before auto-dismiss; `maxToasts` caps how
372
+ * many are shown at once.
373
+ * @returns A {@link ToastInstance} with `info`, `success`, `warning`, `error`,
374
+ * plus the reactive list to render.
375
+ */
299
376
  declare function toast(options?: {
300
377
  duration?: number;
301
378
  maxToasts?: number;
package/dist/data.cjs CHANGED
@@ -45,23 +45,17 @@ __export(data_exports, {
45
45
  module.exports = __toCommonJS(data_exports);
46
46
 
47
47
  // src/core/dev.ts
48
- function isDev() {
49
- return typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
50
- // The bare `__SIBU_DEV__` is a bundler define that only exists in
51
- // production builds; under the test runner it is always undefined, so
52
- // this branch is unreachable here.
53
- /* v8 ignore next 2 */
54
- typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : typeof process !== "undefined" && process.env?.NODE_ENV !== "production"
55
- );
56
- }
57
- var _isDev = isDev();
48
+ var DEV = typeof __SIBU_DEV__ !== "undefined" ? !!__SIBU_DEV__ : typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : (
49
+ // safe default: off in browser, on in test/dev Node
50
+ typeof process !== "undefined" && process.env?.NODE_ENV !== "production"
51
+ );
58
52
  function devAssert(condition, message) {
59
- if (_isDev && !condition) {
53
+ if ((typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) && !condition) {
60
54
  throw new Error(`[SibuJS] ${message}`);
61
55
  }
62
56
  }
63
57
  function devWarn(message) {
64
- if (_isDev) {
58
+ if (typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : DEV) {
65
59
  console.warn(`[SibuJS] ${message}`);
66
60
  }
67
61
  }
@@ -610,17 +604,16 @@ function forEachSubscriber(signal2, visit) {
610
604
  }
611
605
 
612
606
  // src/reactivity/track.ts
613
- var _isDev2 = isDev();
614
- var _runtimeVersion = true ? "4.1.0" : "dev";
607
+ var _runtimeVersion = true ? "4.3.0" : "dev";
615
608
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
616
609
  function resolveReactiveApi() {
617
610
  const g = globalThis;
618
611
  const existing = g[REGISTRY_KEY];
619
612
  if (existing) {
620
- if (_isDev2 && !existing.__dupWarned) {
613
+ if (DEV && !existing.__dupWarned) {
621
614
  existing.__dupWarned = true;
622
615
  devWarn(
623
- `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']).`
616
+ `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.`
624
617
  );
625
618
  }
626
619
  return existing;
@@ -966,7 +959,6 @@ var isBatching = API2.isBatching;
966
959
 
967
960
  // src/core/signals/signal.ts
968
961
  var _g2 = globalThis;
969
- var _isDev3 = isDev();
970
962
  function signal(initial, options) {
971
963
  const state2 = {
972
964
  value: initial,
@@ -979,7 +971,7 @@ function signal(initial, options) {
979
971
  _d: false,
980
972
  _validate: void 0
981
973
  };
982
- const debugName = _isDev3 ? options?.name : void 0;
974
+ const debugName = DEV ? options?.name : void 0;
983
975
  const equalsFn = options?.equals;
984
976
  if (debugName) state2.__name = debugName;
985
977
  function get() {
@@ -996,7 +988,7 @@ function signal(initial, options) {
996
988
  if (equalsFn(prev, newValue)) return;
997
989
  state2.value = newValue;
998
990
  state2.__v++;
999
- if (_isDev3) {
991
+ if (DEV) {
1000
992
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1001
993
  if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1002
994
  }
@@ -1004,7 +996,7 @@ function signal(initial, options) {
1004
996
  notifySubscribers2(state2);
1005
997
  }
1006
998
  };
1007
- } else if (_isDev3) {
999
+ } else if (DEV) {
1008
1000
  set = (next) => {
1009
1001
  const prev = state2.value;
1010
1002
  const newValue = typeof next === "function" ? next(prev) : next;
@@ -1029,7 +1021,7 @@ function signal(initial, options) {
1029
1021
  }
1030
1022
  };
1031
1023
  }
1032
- if (_isDev3) {
1024
+ if (DEV) {
1033
1025
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1034
1026
  if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
1035
1027
  }
@@ -2166,11 +2158,10 @@ function syncAdapter(config) {
2166
2158
  }
2167
2159
 
2168
2160
  // src/core/rendering/context.ts
2169
- var _isDev4 = isDev();
2170
2161
  function context(defaultValue) {
2171
2162
  const [getValue, setValue] = signal(defaultValue);
2172
2163
  const warnIfSSR = (method) => {
2173
- if (!_isDev4 || !isSSR()) return;
2164
+ if (!DEV || !isSSR()) return;
2174
2165
  devWarn(
2175
2166
  `context.${method}() called during SSR. SibuJS context() is application-global \u2014 it is NOT isolated per request, so a concurrent request can observe this value. Do not use context() for request-specific SSR data; pass it explicitly or use request-scoped storage instead.`
2176
2167
  );
@@ -2198,7 +2189,7 @@ function context(defaultValue) {
2198
2189
  setValue(value);
2199
2190
  try {
2200
2191
  const result = fn();
2201
- if (_isDev4 && result !== null && typeof result?.then === "function") {
2192
+ if (DEV && result !== null && typeof result?.then === "function") {
2202
2193
  devWarn(
2203
2194
  "context.withContext() received an async callback. Scoping is synchronous only \u2014 the previous value is restored as soon as the callback returns its promise, so anything after an `await` runs outside the scope."
2204
2195
  );