sibujs 1.2.0 → 1.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 (89) hide show
  1. package/README.md +29 -25
  2. package/dist/browser.cjs +804 -2
  3. package/dist/browser.d.cts +591 -1
  4. package/dist/browser.d.ts +591 -1
  5. package/dist/browser.js +50 -8
  6. package/dist/build.cjs +654 -144
  7. package/dist/build.js +14 -12
  8. package/dist/cdn.global.js +188 -7
  9. package/dist/chunk-2BYQDGN3.js +742 -0
  10. package/dist/chunk-32DY64NT.js +282 -0
  11. package/dist/chunk-3AIRKM3B.js +1263 -0
  12. package/dist/chunk-3X2YG6YM.js +505 -0
  13. package/dist/chunk-5X6PP2UK.js +28 -0
  14. package/dist/chunk-77L6NL3X.js +1097 -0
  15. package/dist/chunk-BGN5ZMP4.js +26 -0
  16. package/dist/chunk-BTU3TJDS.js +365 -0
  17. package/dist/chunk-CHF5OHIA.js +61 -0
  18. package/dist/chunk-CMBFNA7L.js +27 -0
  19. package/dist/chunk-DAHRH4ON.js +331 -0
  20. package/dist/chunk-EBGIRKQY.js +616 -0
  21. package/dist/chunk-EUZND3CB.js +27 -0
  22. package/dist/chunk-F3FA4F32.js +292 -0
  23. package/dist/chunk-JAKHTMQU.js +1000 -0
  24. package/dist/chunk-JCI5M6U6.js +956 -0
  25. package/dist/chunk-KQPDEVVS.js +398 -0
  26. package/dist/chunk-NEKUBFPT.js +60 -0
  27. package/dist/chunk-NYVAC6P5.js +37 -0
  28. package/dist/chunk-PTQJDMRT.js +146 -0
  29. package/dist/chunk-QWZG56ET.js +2744 -0
  30. package/dist/chunk-TSOKIX5Z.js +654 -0
  31. package/dist/chunk-VRW3FULF.js +725 -0
  32. package/dist/chunk-WZSPOOER.js +84 -0
  33. package/dist/chunk-YT6HQ6AM.js +14 -0
  34. package/dist/chunk-ZD6OAMTH.js +277 -0
  35. package/dist/contracts-DDrwxvJ-.d.cts +245 -0
  36. package/dist/contracts-DDrwxvJ-.d.ts +245 -0
  37. package/dist/data.cjs +35 -2
  38. package/dist/data.d.cts +7 -0
  39. package/dist/data.d.ts +7 -0
  40. package/dist/data.js +9 -8
  41. package/dist/devtools.cjs +122 -0
  42. package/dist/devtools.d.cts +69 -461
  43. package/dist/devtools.d.ts +69 -461
  44. package/dist/devtools.js +127 -6
  45. package/dist/ecosystem.cjs +23 -6
  46. package/dist/ecosystem.d.cts +1 -1
  47. package/dist/ecosystem.d.ts +1 -1
  48. package/dist/ecosystem.js +10 -9
  49. package/dist/extras.cjs +1207 -65
  50. package/dist/extras.d.cts +5 -5
  51. package/dist/extras.d.ts +5 -5
  52. package/dist/extras.js +69 -24
  53. package/dist/index.cjs +663 -144
  54. package/dist/index.d.cts +397 -17
  55. package/dist/index.d.ts +397 -17
  56. package/dist/index.js +39 -17
  57. package/dist/introspect-BumjnBKr.d.cts +477 -0
  58. package/dist/introspect-CZrlcaYy.d.ts +477 -0
  59. package/dist/introspect-Cb0zgpi2.d.cts +477 -0
  60. package/dist/introspect-Y2xNXGSf.d.ts +477 -0
  61. package/dist/motion.js +4 -4
  62. package/dist/patterns.cjs +51 -2
  63. package/dist/patterns.d.cts +18 -8
  64. package/dist/patterns.d.ts +18 -8
  65. package/dist/patterns.js +7 -7
  66. package/dist/performance.js +4 -4
  67. package/dist/plugins.cjs +428 -81
  68. package/dist/plugins.d.cts +27 -4
  69. package/dist/plugins.d.ts +27 -4
  70. package/dist/plugins.js +156 -37
  71. package/dist/ssr-4PBXAOO3.js +40 -0
  72. package/dist/ssr-Do_SiVoL.d.cts +201 -0
  73. package/dist/ssr-Do_SiVoL.d.ts +201 -0
  74. package/dist/ssr.cjs +312 -60
  75. package/dist/ssr.d.cts +10 -1
  76. package/dist/ssr.d.ts +10 -1
  77. package/dist/ssr.js +13 -10
  78. package/dist/tagFactory-DaJ0YWX6.d.cts +47 -0
  79. package/dist/tagFactory-DaJ0YWX6.d.ts +47 -0
  80. package/dist/testing.cjs +233 -2
  81. package/dist/testing.d.cts +42 -1
  82. package/dist/testing.d.ts +42 -1
  83. package/dist/testing.js +129 -2
  84. package/dist/ui.cjs +374 -3
  85. package/dist/ui.d.cts +252 -2
  86. package/dist/ui.d.ts +252 -2
  87. package/dist/ui.js +328 -8
  88. package/dist/widgets.js +7 -7
  89. package/package.json +1 -1
package/dist/ui.d.cts CHANGED
@@ -1,5 +1,153 @@
1
1
  export { B as BoundFieldProps, C as CustomElementOptions, F as FieldConfig, a as FocusTrap, b as FormConfig, c as FormField, d as FormReturn, I as IntersectionResult, M as MaskOptions, T as Toast, e as ToastInstance, V as ValidatorFn, f as VirtualList, g as VirtualListProps, h as announce, i as aria, j as bindAttrs, k as bindBoolAttr, l as bindData, m as bindField, n as creditCardMask, o as custom, p as dateMask, q as defineElement, r as dialog, s as email, t as eventBus, u as focus, v as form, w as hotkey, x as infiniteScroll, y as inputMask, z as intersection, A as lazyLoad, D as matchesPattern, E as max, G as maxLength, H as min, J as minLength, K as pagination, L as phoneMask, N as removeScopedStyle, O as required, P as scopedStyle, Q as ssnMask, R as svgElement, S as timeMask, U as toast, W as withScopedStyle, X as zipMask } from './customElement-D2DJp_xn.cjs';
2
- export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V as Validator, b as assertType, c as composable, d as compose, e as createGuard, f as createSlots, g as defineComponent, h as defineSlottedComponent, i as defineStrictComponent, v as validateProps, j as validators, w as withBoundary, k as withDefaults, l as withProps, m as withWrapper } from './contracts-DOrhwbke.cjs';
2
+ export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V as Validator, b as assertType, c as composable, d as compose, e as createGuard, f as createSlots, g as defineComponent, h as defineSlottedComponent, i as defineStrictComponent, v as validateProps, j as validators, w as withBoundary, k as withDefaults, l as withProps, m as withWrapper } from './contracts-DDrwxvJ-.cjs';
3
+
4
+ interface FormActionHandle<TArgs extends unknown[], TResult> {
5
+ /** Invoke the action. Rejections become `error()`, resolutions `result()`. */
6
+ run: (...args: TArgs) => Promise<void>;
7
+ /** True while the underlying promise is unresolved. */
8
+ pending: () => boolean;
9
+ /** Last caught error, or `null`. */
10
+ error: () => unknown;
11
+ /** Last resolved value, or `null`. */
12
+ result: () => TResult | null;
13
+ /** Clear result and error without affecting an in-flight call. */
14
+ reset: () => void;
15
+ /**
16
+ * A ready-to-attach submit handler for `<form>` elements. Calls
17
+ * `e.preventDefault()`, builds a `FormData`, and passes it to the
18
+ * underlying action. Only available when `TArgs = [FormData]`.
19
+ */
20
+ onSubmit: (e: Event) => void;
21
+ }
22
+ /**
23
+ * Wrap an async function into a reactive form-action handle.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const save = formAction(async (data: FormData) => {
28
+ * const res = await fetch("/api/save", { method: "POST", body: data });
29
+ * if (!res.ok) throw new Error("Save failed");
30
+ * return res.json();
31
+ * });
32
+ *
33
+ * form({
34
+ * on: { submit: save.onSubmit },
35
+ * nodes: [
36
+ * input({ name: "title" }),
37
+ * button({ disabled: save.pending, nodes: () => (save.pending() ? "Saving..." : "Save") }),
38
+ * when(() => save.error() != null, () => div({ class: "error", nodes: () => String(save.error()) })),
39
+ * ],
40
+ * });
41
+ * ```
42
+ */
43
+ declare function formAction<TArgs extends unknown[], TResult>(fn: (...args: TArgs) => Promise<TResult>): FormActionHandle<TArgs, TResult>;
44
+
45
+ interface FocusManagerOptions {
46
+ /** CSS selector for focusable descendants. Default matches common form/link controls. */
47
+ selector?: string;
48
+ /** Wrap focus from last→first and first→last. Default `true`. */
49
+ loop?: boolean;
50
+ }
51
+ interface FocusManagerHandle {
52
+ /** Move focus to the first focusable descendant. */
53
+ focusFirst: () => void;
54
+ /** Move focus to the last focusable descendant. */
55
+ focusLast: () => void;
56
+ /** Move focus to the next focusable descendant relative to `document.activeElement`. */
57
+ focusNext: () => void;
58
+ /** Move focus to the previous focusable descendant relative to `document.activeElement`. */
59
+ focusPrev: () => void;
60
+ /** Return the currently focusable descendants, in order. */
61
+ items: () => HTMLElement[];
62
+ }
63
+ /**
64
+ * Build a focus manager scoped to a container. The manager offers
65
+ * `focusFirst` / `focusLast` / `focusNext` / `focusPrev` helpers that
66
+ * walk the focusable descendants in DOM order. It is the building
67
+ * block for custom listboxes, toolbars, and menus.
68
+ *
69
+ * This is a DOM-read utility — the item list is fetched fresh on every
70
+ * call so dynamic content is handled automatically.
71
+ */
72
+ declare function createFocusManager(container: HTMLElement, options?: FocusManagerOptions): FocusManagerHandle;
73
+ interface ListboxOptions {
74
+ /** Whether the listbox is multi-select. Default `false`. */
75
+ multiple?: boolean;
76
+ /** CSS selector for option elements. Default `[role="option"]`. */
77
+ optionSelector?: string;
78
+ /** Called when the user commits a selection. */
79
+ onSelect?: (value: string) => void;
80
+ }
81
+ interface ListboxHandle {
82
+ /** Reactive value: the currently-active (highlighted) option value. */
83
+ activeValue: () => string | null;
84
+ /** Reactive value: the currently-selected option value (single-select) or CSV (multiple). */
85
+ selectedValue: () => string | null;
86
+ /** Stable id that can be used as `aria-activedescendant` on the trigger. */
87
+ activeDescendantId: () => string | null;
88
+ /** Cleanup: removes listeners. */
89
+ dispose: () => void;
90
+ }
91
+ /**
92
+ * Build an ARIA listbox on top of an existing container element. The
93
+ * listbox wires:
94
+ * - `role="listbox"` + `aria-multiselectable` on the container
95
+ * - keyboard navigation (Arrow keys, Home, End, Enter, Space)
96
+ * - `aria-activedescendant` tracking
97
+ * - option highlight via `data-highlighted`
98
+ *
99
+ * Each option must expose a `data-value` attribute. Options get a
100
+ * stable `id` on mount so the container's `aria-activedescendant`
101
+ * can point at the active one.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * const container = ul({ nodes: [
106
+ * li({ role: "option", "data-value": "a", nodes: "Apple" }),
107
+ * li({ role: "option", "data-value": "b", nodes: "Banana" }),
108
+ * ]}) as HTMLElement;
109
+ *
110
+ * const lb = createListbox(container, { onSelect: v => console.log(v) });
111
+ * ```
112
+ */
113
+ declare function createListbox(container: HTMLElement, options?: ListboxOptions): ListboxHandle;
114
+ interface DialogAriaOptions {
115
+ /** Labelled-by id — the dialog title's id. */
116
+ labelledBy?: string;
117
+ /** Described-by id — the dialog description's id. */
118
+ describedBy?: string;
119
+ /** Modal dialog (true) or alertdialog (false for "alert"). Default `true`. */
120
+ modal?: boolean;
121
+ /** Use `role="alertdialog"` instead of `role="dialog"`. */
122
+ alert?: boolean;
123
+ }
124
+ interface DialogAriaHandle {
125
+ /** Auto-generated id that should be put on the title element. */
126
+ titleId: string;
127
+ /** Auto-generated id that should be put on the description element. */
128
+ descriptionId: string;
129
+ }
130
+ /**
131
+ * Apply the ARIA attributes needed for an accessible dialog to an
132
+ * existing element. Returns stable ids that the caller can pass to the
133
+ * title and description children so `aria-labelledby` / `aria-describedby`
134
+ * resolve correctly.
135
+ *
136
+ * Does NOT handle focus trapping — use `FocusTrap` or `createFocusManager`
137
+ * for that. Does NOT handle escape-to-close — wire that in the caller.
138
+ * The primitive is intentionally tight: it only owns the ARIA surface.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * const dlg = document.createElement("div");
143
+ * const aria = createDialogAria(dlg, { alert: false });
144
+ * dlg.append(
145
+ * h2({ id: aria.titleId, nodes: "Delete?" }),
146
+ * p({ id: aria.descriptionId, nodes: "This cannot be undone." }),
147
+ * );
148
+ * ```
149
+ */
150
+ declare function createDialogAria(element: HTMLElement, options?: DialogAriaOptions): DialogAriaHandle;
3
151
 
4
152
  /**
5
153
  * lazyEffect creates an effect that only activates when the target element
@@ -25,4 +173,106 @@ export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V
25
173
  */
26
174
  declare function lazyEffect(element: HTMLElement, effectFn: () => void, options?: IntersectionObserverInit): () => void;
27
175
 
28
- export { lazyEffect };
176
+ /**
177
+ * Declarative, disposable timers.
178
+ *
179
+ * The native `setInterval` / `setTimeout` are easy to leak if the owning
180
+ * component is destroyed before they fire. These helpers return a handle
181
+ * with a `stop()` function plus (for interval) `pause()` / `resume()`, and
182
+ * optionally integrate with the sibujs disposal lifecycle through the
183
+ * returned cleanup.
184
+ *
185
+ * Neither helper depends on any reactive context — they're pure JS with
186
+ * nicer ergonomics for UIs that need to start and stop timers safely.
187
+ */
188
+ interface IntervalHandle {
189
+ /** Stop the interval. Safe to call multiple times. */
190
+ stop: () => void;
191
+ /** Pause (preserving remaining ticks until `resume()`). */
192
+ pause: () => void;
193
+ /** Resume a paused interval. */
194
+ resume: () => void;
195
+ /** Whether the interval is currently running. */
196
+ isRunning: () => boolean;
197
+ }
198
+ /**
199
+ * Like `setInterval(fn, ms)` but returns a handle that can be stopped,
200
+ * paused, and resumed without leaking closures.
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * const tick = interval(() => setCount(c => c + 1), 1000);
205
+ * // later
206
+ * tick.pause();
207
+ * tick.resume();
208
+ * tick.stop();
209
+ * ```
210
+ */
211
+ declare function interval(fn: () => void, ms: number): IntervalHandle;
212
+ interface TimeoutHandle {
213
+ /** Cancel the pending timeout. No-op if already fired. */
214
+ cancel: () => void;
215
+ /** Whether the callback has run or been cancelled. */
216
+ isPending: () => boolean;
217
+ }
218
+ /**
219
+ * Like `setTimeout(fn, ms)` but returns a handle with an explicit `cancel()`.
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * const t = timeout(() => setVisible(false), 3000);
224
+ * // cancel on user interaction
225
+ * input({ on: { focus: () => t.cancel() } });
226
+ * ```
227
+ */
228
+ declare function timeout(fn: () => void, ms: number): TimeoutHandle;
229
+
230
+ /**
231
+ * hover attaches reactive hover tracking to an element. Uses `pointerenter`
232
+ * and `pointerleave` so it works on touch devices where a sustained press
233
+ * triggers a hover state.
234
+ *
235
+ * @param target Element to track
236
+ * @returns `{ hovered, dispose }` — reactive boolean plus cleanup
237
+ *
238
+ * @example
239
+ * ```ts
240
+ * const el = div({ class: "card" });
241
+ * const h = hover(el);
242
+ * effect(() => { el.classList.toggle("lifted", h.hovered()); });
243
+ * ```
244
+ */
245
+ declare function hover(target: HTMLElement): {
246
+ hovered: () => boolean;
247
+ dispose: () => void;
248
+ };
249
+
250
+ /**
251
+ * scrollLock stacks body scroll locks — useful when a modal / drawer / sheet
252
+ * stack is open and background scroll must be suppressed.
253
+ *
254
+ * Each `lock()` call increments an internal counter and applies
255
+ * `overflow: hidden` + preserves the scrollbar-width padding to prevent
256
+ * layout shift. Calling `unlock()` decrements; when the counter hits zero
257
+ * the previous body style is restored.
258
+ *
259
+ * Safe to call from multiple concurrent overlays — the last one to unlock
260
+ * releases the lock.
261
+ *
262
+ * @example
263
+ * ```ts
264
+ * const lock = scrollLock();
265
+ * lock.lock();
266
+ * // ... modal open
267
+ * lock.unlock();
268
+ * ```
269
+ */
270
+ interface ScrollLockHandle {
271
+ /** Activate a lock. Idempotent per-handle if called twice. */
272
+ lock: () => void;
273
+ /** Release this handle's lock. Idempotent. */
274
+ unlock: () => void;
275
+ }
276
+ declare function scrollLock(): ScrollLockHandle;
277
+
278
+ 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,5 +1,153 @@
1
1
  export { B as BoundFieldProps, C as CustomElementOptions, F as FieldConfig, a as FocusTrap, b as FormConfig, c as FormField, d as FormReturn, I as IntersectionResult, M as MaskOptions, T as Toast, e as ToastInstance, V as ValidatorFn, f as VirtualList, g as VirtualListProps, h as announce, i as aria, j as bindAttrs, k as bindBoolAttr, l as bindData, m as bindField, n as creditCardMask, o as custom, p as dateMask, q as defineElement, r as dialog, s as email, t as eventBus, u as focus, v as form, w as hotkey, x as infiniteScroll, y as inputMask, z as intersection, A as lazyLoad, D as matchesPattern, E as max, G as maxLength, H as min, J as minLength, K as pagination, L as phoneMask, N as removeScopedStyle, O as required, P as scopedStyle, Q as ssnMask, R as svgElement, S as timeMask, U as toast, W as withScopedStyle, X as zipMask } from './customElement-D2DJp_xn.js';
2
- export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V as Validator, b as assertType, c as composable, d as compose, e as createGuard, f as createSlots, g as defineComponent, h as defineSlottedComponent, i as defineStrictComponent, v as validateProps, j as validators, w as withBoundary, k as withDefaults, l as withProps, m as withWrapper } from './contracts-DOrhwbke.js';
2
+ export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V as Validator, b as assertType, c as composable, d as compose, e as createGuard, f as createSlots, g as defineComponent, h as defineSlottedComponent, i as defineStrictComponent, v as validateProps, j as validators, w as withBoundary, k as withDefaults, l as withProps, m as withWrapper } from './contracts-DDrwxvJ-.js';
3
+
4
+ interface FormActionHandle<TArgs extends unknown[], TResult> {
5
+ /** Invoke the action. Rejections become `error()`, resolutions `result()`. */
6
+ run: (...args: TArgs) => Promise<void>;
7
+ /** True while the underlying promise is unresolved. */
8
+ pending: () => boolean;
9
+ /** Last caught error, or `null`. */
10
+ error: () => unknown;
11
+ /** Last resolved value, or `null`. */
12
+ result: () => TResult | null;
13
+ /** Clear result and error without affecting an in-flight call. */
14
+ reset: () => void;
15
+ /**
16
+ * A ready-to-attach submit handler for `<form>` elements. Calls
17
+ * `e.preventDefault()`, builds a `FormData`, and passes it to the
18
+ * underlying action. Only available when `TArgs = [FormData]`.
19
+ */
20
+ onSubmit: (e: Event) => void;
21
+ }
22
+ /**
23
+ * Wrap an async function into a reactive form-action handle.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const save = formAction(async (data: FormData) => {
28
+ * const res = await fetch("/api/save", { method: "POST", body: data });
29
+ * if (!res.ok) throw new Error("Save failed");
30
+ * return res.json();
31
+ * });
32
+ *
33
+ * form({
34
+ * on: { submit: save.onSubmit },
35
+ * nodes: [
36
+ * input({ name: "title" }),
37
+ * button({ disabled: save.pending, nodes: () => (save.pending() ? "Saving..." : "Save") }),
38
+ * when(() => save.error() != null, () => div({ class: "error", nodes: () => String(save.error()) })),
39
+ * ],
40
+ * });
41
+ * ```
42
+ */
43
+ declare function formAction<TArgs extends unknown[], TResult>(fn: (...args: TArgs) => Promise<TResult>): FormActionHandle<TArgs, TResult>;
44
+
45
+ interface FocusManagerOptions {
46
+ /** CSS selector for focusable descendants. Default matches common form/link controls. */
47
+ selector?: string;
48
+ /** Wrap focus from last→first and first→last. Default `true`. */
49
+ loop?: boolean;
50
+ }
51
+ interface FocusManagerHandle {
52
+ /** Move focus to the first focusable descendant. */
53
+ focusFirst: () => void;
54
+ /** Move focus to the last focusable descendant. */
55
+ focusLast: () => void;
56
+ /** Move focus to the next focusable descendant relative to `document.activeElement`. */
57
+ focusNext: () => void;
58
+ /** Move focus to the previous focusable descendant relative to `document.activeElement`. */
59
+ focusPrev: () => void;
60
+ /** Return the currently focusable descendants, in order. */
61
+ items: () => HTMLElement[];
62
+ }
63
+ /**
64
+ * Build a focus manager scoped to a container. The manager offers
65
+ * `focusFirst` / `focusLast` / `focusNext` / `focusPrev` helpers that
66
+ * walk the focusable descendants in DOM order. It is the building
67
+ * block for custom listboxes, toolbars, and menus.
68
+ *
69
+ * This is a DOM-read utility — the item list is fetched fresh on every
70
+ * call so dynamic content is handled automatically.
71
+ */
72
+ declare function createFocusManager(container: HTMLElement, options?: FocusManagerOptions): FocusManagerHandle;
73
+ interface ListboxOptions {
74
+ /** Whether the listbox is multi-select. Default `false`. */
75
+ multiple?: boolean;
76
+ /** CSS selector for option elements. Default `[role="option"]`. */
77
+ optionSelector?: string;
78
+ /** Called when the user commits a selection. */
79
+ onSelect?: (value: string) => void;
80
+ }
81
+ interface ListboxHandle {
82
+ /** Reactive value: the currently-active (highlighted) option value. */
83
+ activeValue: () => string | null;
84
+ /** Reactive value: the currently-selected option value (single-select) or CSV (multiple). */
85
+ selectedValue: () => string | null;
86
+ /** Stable id that can be used as `aria-activedescendant` on the trigger. */
87
+ activeDescendantId: () => string | null;
88
+ /** Cleanup: removes listeners. */
89
+ dispose: () => void;
90
+ }
91
+ /**
92
+ * Build an ARIA listbox on top of an existing container element. The
93
+ * listbox wires:
94
+ * - `role="listbox"` + `aria-multiselectable` on the container
95
+ * - keyboard navigation (Arrow keys, Home, End, Enter, Space)
96
+ * - `aria-activedescendant` tracking
97
+ * - option highlight via `data-highlighted`
98
+ *
99
+ * Each option must expose a `data-value` attribute. Options get a
100
+ * stable `id` on mount so the container's `aria-activedescendant`
101
+ * can point at the active one.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * const container = ul({ nodes: [
106
+ * li({ role: "option", "data-value": "a", nodes: "Apple" }),
107
+ * li({ role: "option", "data-value": "b", nodes: "Banana" }),
108
+ * ]}) as HTMLElement;
109
+ *
110
+ * const lb = createListbox(container, { onSelect: v => console.log(v) });
111
+ * ```
112
+ */
113
+ declare function createListbox(container: HTMLElement, options?: ListboxOptions): ListboxHandle;
114
+ interface DialogAriaOptions {
115
+ /** Labelled-by id — the dialog title's id. */
116
+ labelledBy?: string;
117
+ /** Described-by id — the dialog description's id. */
118
+ describedBy?: string;
119
+ /** Modal dialog (true) or alertdialog (false for "alert"). Default `true`. */
120
+ modal?: boolean;
121
+ /** Use `role="alertdialog"` instead of `role="dialog"`. */
122
+ alert?: boolean;
123
+ }
124
+ interface DialogAriaHandle {
125
+ /** Auto-generated id that should be put on the title element. */
126
+ titleId: string;
127
+ /** Auto-generated id that should be put on the description element. */
128
+ descriptionId: string;
129
+ }
130
+ /**
131
+ * Apply the ARIA attributes needed for an accessible dialog to an
132
+ * existing element. Returns stable ids that the caller can pass to the
133
+ * title and description children so `aria-labelledby` / `aria-describedby`
134
+ * resolve correctly.
135
+ *
136
+ * Does NOT handle focus trapping — use `FocusTrap` or `createFocusManager`
137
+ * for that. Does NOT handle escape-to-close — wire that in the caller.
138
+ * The primitive is intentionally tight: it only owns the ARIA surface.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * const dlg = document.createElement("div");
143
+ * const aria = createDialogAria(dlg, { alert: false });
144
+ * dlg.append(
145
+ * h2({ id: aria.titleId, nodes: "Delete?" }),
146
+ * p({ id: aria.descriptionId, nodes: "This cannot be undone." }),
147
+ * );
148
+ * ```
149
+ */
150
+ declare function createDialogAria(element: HTMLElement, options?: DialogAriaOptions): DialogAriaHandle;
3
151
 
4
152
  /**
5
153
  * lazyEffect creates an effect that only activates when the target element
@@ -25,4 +173,106 @@ export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V
25
173
  */
26
174
  declare function lazyEffect(element: HTMLElement, effectFn: () => void, options?: IntersectionObserverInit): () => void;
27
175
 
28
- export { lazyEffect };
176
+ /**
177
+ * Declarative, disposable timers.
178
+ *
179
+ * The native `setInterval` / `setTimeout` are easy to leak if the owning
180
+ * component is destroyed before they fire. These helpers return a handle
181
+ * with a `stop()` function plus (for interval) `pause()` / `resume()`, and
182
+ * optionally integrate with the sibujs disposal lifecycle through the
183
+ * returned cleanup.
184
+ *
185
+ * Neither helper depends on any reactive context — they're pure JS with
186
+ * nicer ergonomics for UIs that need to start and stop timers safely.
187
+ */
188
+ interface IntervalHandle {
189
+ /** Stop the interval. Safe to call multiple times. */
190
+ stop: () => void;
191
+ /** Pause (preserving remaining ticks until `resume()`). */
192
+ pause: () => void;
193
+ /** Resume a paused interval. */
194
+ resume: () => void;
195
+ /** Whether the interval is currently running. */
196
+ isRunning: () => boolean;
197
+ }
198
+ /**
199
+ * Like `setInterval(fn, ms)` but returns a handle that can be stopped,
200
+ * paused, and resumed without leaking closures.
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * const tick = interval(() => setCount(c => c + 1), 1000);
205
+ * // later
206
+ * tick.pause();
207
+ * tick.resume();
208
+ * tick.stop();
209
+ * ```
210
+ */
211
+ declare function interval(fn: () => void, ms: number): IntervalHandle;
212
+ interface TimeoutHandle {
213
+ /** Cancel the pending timeout. No-op if already fired. */
214
+ cancel: () => void;
215
+ /** Whether the callback has run or been cancelled. */
216
+ isPending: () => boolean;
217
+ }
218
+ /**
219
+ * Like `setTimeout(fn, ms)` but returns a handle with an explicit `cancel()`.
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * const t = timeout(() => setVisible(false), 3000);
224
+ * // cancel on user interaction
225
+ * input({ on: { focus: () => t.cancel() } });
226
+ * ```
227
+ */
228
+ declare function timeout(fn: () => void, ms: number): TimeoutHandle;
229
+
230
+ /**
231
+ * hover attaches reactive hover tracking to an element. Uses `pointerenter`
232
+ * and `pointerleave` so it works on touch devices where a sustained press
233
+ * triggers a hover state.
234
+ *
235
+ * @param target Element to track
236
+ * @returns `{ hovered, dispose }` — reactive boolean plus cleanup
237
+ *
238
+ * @example
239
+ * ```ts
240
+ * const el = div({ class: "card" });
241
+ * const h = hover(el);
242
+ * effect(() => { el.classList.toggle("lifted", h.hovered()); });
243
+ * ```
244
+ */
245
+ declare function hover(target: HTMLElement): {
246
+ hovered: () => boolean;
247
+ dispose: () => void;
248
+ };
249
+
250
+ /**
251
+ * scrollLock stacks body scroll locks — useful when a modal / drawer / sheet
252
+ * stack is open and background scroll must be suppressed.
253
+ *
254
+ * Each `lock()` call increments an internal counter and applies
255
+ * `overflow: hidden` + preserves the scrollbar-width padding to prevent
256
+ * layout shift. Calling `unlock()` decrements; when the counter hits zero
257
+ * the previous body style is restored.
258
+ *
259
+ * Safe to call from multiple concurrent overlays — the last one to unlock
260
+ * releases the lock.
261
+ *
262
+ * @example
263
+ * ```ts
264
+ * const lock = scrollLock();
265
+ * lock.lock();
266
+ * // ... modal open
267
+ * lock.unlock();
268
+ * ```
269
+ */
270
+ interface ScrollLockHandle {
271
+ /** Activate a lock. Idempotent per-handle if called twice. */
272
+ lock: () => void;
273
+ /** Release this handle's lock. Idempotent. */
274
+ unlock: () => void;
275
+ }
276
+ declare function scrollLock(): ScrollLockHandle;
277
+
278
+ 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 };