sibujs 1.0.7 → 1.0.8

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.
@@ -0,0 +1,313 @@
1
+ type ValidatorFn<T = unknown> = (value: T) => string | null;
2
+ interface FieldConfig<T = unknown> {
3
+ initial: T;
4
+ validators?: ValidatorFn<T>[];
5
+ }
6
+ type FormConfig<T extends Record<string, unknown>> = {
7
+ [K in keyof T]: FieldConfig<T[K]>;
8
+ };
9
+ interface FormField<T = unknown> {
10
+ value: () => T;
11
+ set: (v: T) => void;
12
+ error: () => string | null;
13
+ touched: () => boolean;
14
+ touch: () => void;
15
+ reset: () => void;
16
+ }
17
+ interface FormReturn<T extends Record<string, unknown>> {
18
+ fields: {
19
+ [K in keyof T]: FormField<T[K]>;
20
+ };
21
+ errors: () => Partial<Record<keyof T, string | null>>;
22
+ isValid: () => boolean;
23
+ isDirty: () => boolean;
24
+ touched: () => Partial<Record<keyof T, boolean>>;
25
+ values: () => T;
26
+ handleSubmit: (onSubmit: (values: T) => void | Promise<void>) => (e?: Event) => void;
27
+ reset: () => void;
28
+ setError: (field: keyof T, message: string) => void;
29
+ }
30
+ declare function required(message?: string): ValidatorFn<unknown>;
31
+ declare function minLength(min: number, message?: string): ValidatorFn<string>;
32
+ declare function maxLength(max: number, message?: string): ValidatorFn<string>;
33
+ declare function matchesPattern(regex: RegExp, message?: string): ValidatorFn<string>;
34
+ declare function email(message?: string): ValidatorFn<string>;
35
+ declare function min(minVal: number, message?: string): ValidatorFn<number>;
36
+ declare function max(maxVal: number, message?: string): ValidatorFn<number>;
37
+ declare function custom<T>(fn: (value: T) => boolean, message: string): ValidatorFn<T>;
38
+ /**
39
+ * Props returned by bindField, ready to spread into an input tag factory.
40
+ */
41
+ interface BoundFieldProps {
42
+ value: () => unknown;
43
+ on: {
44
+ input: (e: Event) => void;
45
+ change: (e: Event) => void;
46
+ blur: () => void;
47
+ };
48
+ [attr: string]: unknown;
49
+ }
50
+ /**
51
+ * Bind a FormField to an input element, eliminating the value/input/blur boilerplate.
52
+ *
53
+ * Works with text inputs (`input` event) and selects/checkboxes (`change` event).
54
+ *
55
+ * @param field A FormField from form().fields
56
+ * @param extras Additional props to merge (placeholder, class, disabled, etc.)
57
+ * @returns Props object ready to pass directly to a tag factory
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * const f = form({ email: { initial: "", validators: [required(), email()] } });
62
+ *
63
+ * // Before — verbose
64
+ * input({ value: f.fields.email.value(), on: { input: e => f.fields.email.set(e.target.value), blur: () => f.fields.email.touch() } })
65
+ *
66
+ * // After — one-liner
67
+ * input(bindField(f.fields.email, { type: "email", placeholder: "Email" }))
68
+ * ```
69
+ */
70
+ declare function bindField<T>(field: FormField<T>, extras?: Record<string, unknown>): BoundFieldProps;
71
+ declare function form<T extends Record<string, unknown>>(config: FormConfig<T>): FormReturn<T>;
72
+
73
+ interface VirtualListProps<T> {
74
+ items: () => T[];
75
+ itemHeight: number;
76
+ containerHeight: number;
77
+ overscan?: number;
78
+ renderItem: (item: T, index: number) => HTMLElement;
79
+ class?: string;
80
+ }
81
+ /**
82
+ * VirtualList renders only visible items for efficient large-list rendering.
83
+ */
84
+ declare function VirtualList<T>(props: VirtualListProps<T>): HTMLElement;
85
+
86
+ interface IntersectionResult {
87
+ isIntersecting: () => boolean;
88
+ intersectionRatio: () => number;
89
+ observe: (element: HTMLElement) => void;
90
+ unobserve: () => void;
91
+ }
92
+ /**
93
+ * intersection provides reactive intersection observer state.
94
+ */
95
+ declare function intersection(options?: IntersectionObserverInit): IntersectionResult;
96
+ /**
97
+ * Lazy-load utility using IntersectionObserver.
98
+ * Calls the loader function when element becomes visible.
99
+ */
100
+ declare function lazyLoad(element: HTMLElement, loader: () => void, options?: IntersectionObserverInit): () => void;
101
+
102
+ interface MaskOptions {
103
+ /** Pattern: 9 = digit, A = letter, * = any, other chars are literals */
104
+ pattern: string;
105
+ /** Placeholder character for unfilled positions */
106
+ placeholder?: string;
107
+ }
108
+ /**
109
+ * inputMask applies a mask to an input element.
110
+ * Returns reactive value and ref binding.
111
+ */
112
+ declare function inputMask(options: MaskOptions): {
113
+ value: () => string;
114
+ rawValue: () => string;
115
+ bind: (input: HTMLInputElement) => void;
116
+ };
117
+ /** Phone number mask: (999) 999-9999 */
118
+ declare function phoneMask(): MaskOptions;
119
+ /** Date mask: 99/99/9999 */
120
+ declare function dateMask(): MaskOptions;
121
+ /** Credit card mask: 9999 9999 9999 9999 */
122
+ declare function creditCardMask(): MaskOptions;
123
+ /** Time mask: 99:99 */
124
+ declare function timeMask(): MaskOptions;
125
+ /** SSN mask: 999-99-9999 */
126
+ declare function ssnMask(): MaskOptions;
127
+ /** ZIP code mask: 99999 */
128
+ declare function zipMask(): MaskOptions;
129
+
130
+ /**
131
+ * aria applies reactive ARIA attributes to an element.
132
+ */
133
+ declare function aria(element: HTMLElement, attrs: Record<string, string | boolean | (() => string | boolean)>): void;
134
+ /**
135
+ * focus manages focus state for an element.
136
+ */
137
+ declare function focus(): {
138
+ isFocused: () => boolean;
139
+ focus: () => void;
140
+ blur: () => void;
141
+ bind: (element: HTMLElement) => void;
142
+ };
143
+ /**
144
+ * FocusTrap traps focus within a container element.
145
+ * Tab cycling stays inside the container.
146
+ */
147
+ declare function FocusTrap(nodes: HTMLElement, options?: {
148
+ autoFocus?: boolean;
149
+ restoreFocus?: boolean;
150
+ }): HTMLElement;
151
+ /**
152
+ * hotkey registers a keyboard shortcut handler.
153
+ * Returns a cleanup function.
154
+ *
155
+ * Supports two calling styles:
156
+ * - String combo: hotkey("ctrl+shift+z", handler)
157
+ * - Explicit flags: hotkey("z", handler, { ctrl: true, shift: true })
158
+ */
159
+ declare function hotkey(combo: string, handler: (e: KeyboardEvent) => void, options?: {
160
+ ctrl?: boolean;
161
+ shift?: boolean;
162
+ alt?: boolean;
163
+ meta?: boolean;
164
+ global?: boolean;
165
+ preventDefault?: boolean;
166
+ }): () => void;
167
+ /**
168
+ * announce creates a screen reader announcement using ARIA live regions.
169
+ */
170
+ declare function announce(message: string, priority?: "polite" | "assertive"): void;
171
+
172
+ /**
173
+ * scopedStyle creates component-scoped CSS by generating a unique scope ID
174
+ * and prefixing all selectors.
175
+ * Returns the scope attribute name and injects the CSS into the document.
176
+ *
177
+ * CSS is sanitized to remove dangerous patterns (`url()`, `@import`,
178
+ * `expression()`, `-moz-binding`, `behavior`). If you need `url()` for
179
+ * background images, use inline styles via the `style` prop instead.
180
+ */
181
+ declare function scopedStyle(css: string): {
182
+ scope: string;
183
+ attr: string;
184
+ };
185
+ /**
186
+ * withScopedStyle wraps a component function to auto-apply scoped styles.
187
+ * The component and all its children get the scope attribute.
188
+ */
189
+ declare function withScopedStyle<P>(css: string, component: (props: P) => HTMLElement): (props: P) => HTMLElement;
190
+ /**
191
+ * Removes a scoped style by its scope ID.
192
+ */
193
+ declare function removeScopedStyle(scopeId: string): void;
194
+
195
+ /**
196
+ * Bind multiple reactive attributes to an element.
197
+ * Each attribute value can be a static value or a reactive getter.
198
+ * Returns a single teardown function that stops all bindings.
199
+ */
200
+ declare function bindAttrs(el: HTMLElement, attrs: Record<string, string | number | boolean | (() => string | number | boolean)>): () => void;
201
+ /**
202
+ * Reactively toggle a boolean attribute (like disabled, readonly, hidden).
203
+ * When the value is truthy the attribute is present (set to ""),
204
+ * when falsy the attribute is removed entirely.
205
+ * Returns a teardown function to stop reactive tracking.
206
+ */
207
+ declare function bindBoolAttr(el: HTMLElement, attr: string, getter: boolean | (() => boolean)): () => void;
208
+ /**
209
+ * Bind a data-* attribute reactively.
210
+ * Shorthand for `bindAttribute(el, "data-<key>", getter)`.
211
+ * Returns a teardown function to stop reactive tracking.
212
+ */
213
+ declare function bindData(el: HTMLElement, key: string, getter: string | (() => string)): () => void;
214
+
215
+ /**
216
+ * dialog provides reactive dialog state management with escape-to-close support.
217
+ */
218
+ declare function dialog(): {
219
+ open: () => void;
220
+ close: () => void;
221
+ isOpen: () => boolean;
222
+ toggle: () => void;
223
+ };
224
+
225
+ /**
226
+ * Toast notification system with auto-dismiss and max toast limits.
227
+ */
228
+ interface Toast {
229
+ id: string;
230
+ message: string;
231
+ type?: "info" | "success" | "error" | "warning";
232
+ }
233
+ interface ToastInstance {
234
+ toasts: () => Toast[];
235
+ show: (message: string, type?: Toast["type"]) => string;
236
+ /** Show an info toast. */
237
+ info: (message: string) => string;
238
+ /** Show a success toast. */
239
+ success: (message: string) => string;
240
+ /** Show an error toast. */
241
+ error: (message: string) => string;
242
+ /** Show a warning toast. */
243
+ warning: (message: string) => string;
244
+ dismiss: (id: string) => void;
245
+ dismissAll: () => void;
246
+ }
247
+ declare function toast(options?: {
248
+ duration?: number;
249
+ maxToasts?: number;
250
+ }): ToastInstance;
251
+
252
+ /**
253
+ * infiniteScroll combines IntersectionObserver with a data-fetching trigger
254
+ * to implement infinite scroll behavior.
255
+ */
256
+ declare function infiniteScroll(options: {
257
+ onLoadMore: () => Promise<void>;
258
+ hasMore: () => boolean;
259
+ threshold?: number;
260
+ }): {
261
+ sentinelRef: {
262
+ current: HTMLElement | null;
263
+ };
264
+ loading: () => boolean;
265
+ dispose: () => void;
266
+ };
267
+
268
+ /**
269
+ * pagination provides reactive pagination state and controls.
270
+ */
271
+ declare function pagination(options: {
272
+ totalItems: () => number;
273
+ pageSize?: number;
274
+ initialPage?: number;
275
+ }): {
276
+ page: () => number;
277
+ pageSize: () => number;
278
+ totalPages: () => number;
279
+ next: () => void;
280
+ prev: () => void;
281
+ goTo: (page: number) => void;
282
+ startIndex: () => number;
283
+ endIndex: () => number;
284
+ };
285
+
286
+ /**
287
+ * eventBus creates a typed publish/subscribe event system.
288
+ * No reactive state needed -- pure event dispatching.
289
+ */
290
+ declare function eventBus<T extends Record<string, unknown>>(): {
291
+ on: <K extends keyof T>(event: K, handler: (data: T[K]) => void) => () => void;
292
+ emit: <K extends keyof T>(event: K, data: T[K]) => void;
293
+ off: <K extends keyof T>(event: K, handler: (data: T[K]) => void) => void;
294
+ clear: () => void;
295
+ };
296
+
297
+ interface CustomElementOptions {
298
+ shadow?: boolean;
299
+ mode?: "open" | "closed";
300
+ styles?: string;
301
+ observedAttributes?: string[];
302
+ extends?: string;
303
+ }
304
+ /**
305
+ * defineElement creates a Web Component wrapping a SibuJS component function.
306
+ */
307
+ declare function defineElement(name: string, component: (props: Record<string, unknown>, element: HTMLElement) => HTMLElement, options?: CustomElementOptions): void;
308
+ /**
309
+ * Creates an SVG element with proper namespace.
310
+ */
311
+ declare function svgElement(tag: string, props?: Record<string, unknown>, ...nodes: (SVGElement | string)[]): SVGElement;
312
+
313
+ export { lazyLoad as A, type BoundFieldProps as B, type CustomElementOptions as C, matchesPattern as D, max as E, type FieldConfig as F, maxLength as G, min as H, type IntersectionResult as I, minLength as J, pagination as K, phoneMask as L, type MaskOptions as M, removeScopedStyle as N, required as O, scopedStyle as P, ssnMask as Q, svgElement as R, timeMask as S, type Toast as T, toast as U, type ValidatorFn as V, withScopedStyle as W, zipMask as X, FocusTrap as a, type FormConfig as b, type FormField as c, type FormReturn as d, type ToastInstance as e, VirtualList as f, type VirtualListProps as g, announce as h, aria as i, bindAttrs as j, bindBoolAttr as k, bindData as l, bindField as m, creditCardMask as n, custom as o, dateMask as p, defineElement as q, dialog as r, email as s, eventBus as t, focus as u, form as v, hotkey as w, infiniteScroll as x, inputMask as y, intersection as z };
@@ -0,0 +1,313 @@
1
+ type ValidatorFn<T = unknown> = (value: T) => string | null;
2
+ interface FieldConfig<T = unknown> {
3
+ initial: T;
4
+ validators?: ValidatorFn<T>[];
5
+ }
6
+ type FormConfig<T extends Record<string, unknown>> = {
7
+ [K in keyof T]: FieldConfig<T[K]>;
8
+ };
9
+ interface FormField<T = unknown> {
10
+ value: () => T;
11
+ set: (v: T) => void;
12
+ error: () => string | null;
13
+ touched: () => boolean;
14
+ touch: () => void;
15
+ reset: () => void;
16
+ }
17
+ interface FormReturn<T extends Record<string, unknown>> {
18
+ fields: {
19
+ [K in keyof T]: FormField<T[K]>;
20
+ };
21
+ errors: () => Partial<Record<keyof T, string | null>>;
22
+ isValid: () => boolean;
23
+ isDirty: () => boolean;
24
+ touched: () => Partial<Record<keyof T, boolean>>;
25
+ values: () => T;
26
+ handleSubmit: (onSubmit: (values: T) => void | Promise<void>) => (e?: Event) => void;
27
+ reset: () => void;
28
+ setError: (field: keyof T, message: string) => void;
29
+ }
30
+ declare function required(message?: string): ValidatorFn<unknown>;
31
+ declare function minLength(min: number, message?: string): ValidatorFn<string>;
32
+ declare function maxLength(max: number, message?: string): ValidatorFn<string>;
33
+ declare function matchesPattern(regex: RegExp, message?: string): ValidatorFn<string>;
34
+ declare function email(message?: string): ValidatorFn<string>;
35
+ declare function min(minVal: number, message?: string): ValidatorFn<number>;
36
+ declare function max(maxVal: number, message?: string): ValidatorFn<number>;
37
+ declare function custom<T>(fn: (value: T) => boolean, message: string): ValidatorFn<T>;
38
+ /**
39
+ * Props returned by bindField, ready to spread into an input tag factory.
40
+ */
41
+ interface BoundFieldProps {
42
+ value: () => unknown;
43
+ on: {
44
+ input: (e: Event) => void;
45
+ change: (e: Event) => void;
46
+ blur: () => void;
47
+ };
48
+ [attr: string]: unknown;
49
+ }
50
+ /**
51
+ * Bind a FormField to an input element, eliminating the value/input/blur boilerplate.
52
+ *
53
+ * Works with text inputs (`input` event) and selects/checkboxes (`change` event).
54
+ *
55
+ * @param field A FormField from form().fields
56
+ * @param extras Additional props to merge (placeholder, class, disabled, etc.)
57
+ * @returns Props object ready to pass directly to a tag factory
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * const f = form({ email: { initial: "", validators: [required(), email()] } });
62
+ *
63
+ * // Before — verbose
64
+ * input({ value: f.fields.email.value(), on: { input: e => f.fields.email.set(e.target.value), blur: () => f.fields.email.touch() } })
65
+ *
66
+ * // After — one-liner
67
+ * input(bindField(f.fields.email, { type: "email", placeholder: "Email" }))
68
+ * ```
69
+ */
70
+ declare function bindField<T>(field: FormField<T>, extras?: Record<string, unknown>): BoundFieldProps;
71
+ declare function form<T extends Record<string, unknown>>(config: FormConfig<T>): FormReturn<T>;
72
+
73
+ interface VirtualListProps<T> {
74
+ items: () => T[];
75
+ itemHeight: number;
76
+ containerHeight: number;
77
+ overscan?: number;
78
+ renderItem: (item: T, index: number) => HTMLElement;
79
+ class?: string;
80
+ }
81
+ /**
82
+ * VirtualList renders only visible items for efficient large-list rendering.
83
+ */
84
+ declare function VirtualList<T>(props: VirtualListProps<T>): HTMLElement;
85
+
86
+ interface IntersectionResult {
87
+ isIntersecting: () => boolean;
88
+ intersectionRatio: () => number;
89
+ observe: (element: HTMLElement) => void;
90
+ unobserve: () => void;
91
+ }
92
+ /**
93
+ * intersection provides reactive intersection observer state.
94
+ */
95
+ declare function intersection(options?: IntersectionObserverInit): IntersectionResult;
96
+ /**
97
+ * Lazy-load utility using IntersectionObserver.
98
+ * Calls the loader function when element becomes visible.
99
+ */
100
+ declare function lazyLoad(element: HTMLElement, loader: () => void, options?: IntersectionObserverInit): () => void;
101
+
102
+ interface MaskOptions {
103
+ /** Pattern: 9 = digit, A = letter, * = any, other chars are literals */
104
+ pattern: string;
105
+ /** Placeholder character for unfilled positions */
106
+ placeholder?: string;
107
+ }
108
+ /**
109
+ * inputMask applies a mask to an input element.
110
+ * Returns reactive value and ref binding.
111
+ */
112
+ declare function inputMask(options: MaskOptions): {
113
+ value: () => string;
114
+ rawValue: () => string;
115
+ bind: (input: HTMLInputElement) => void;
116
+ };
117
+ /** Phone number mask: (999) 999-9999 */
118
+ declare function phoneMask(): MaskOptions;
119
+ /** Date mask: 99/99/9999 */
120
+ declare function dateMask(): MaskOptions;
121
+ /** Credit card mask: 9999 9999 9999 9999 */
122
+ declare function creditCardMask(): MaskOptions;
123
+ /** Time mask: 99:99 */
124
+ declare function timeMask(): MaskOptions;
125
+ /** SSN mask: 999-99-9999 */
126
+ declare function ssnMask(): MaskOptions;
127
+ /** ZIP code mask: 99999 */
128
+ declare function zipMask(): MaskOptions;
129
+
130
+ /**
131
+ * aria applies reactive ARIA attributes to an element.
132
+ */
133
+ declare function aria(element: HTMLElement, attrs: Record<string, string | boolean | (() => string | boolean)>): void;
134
+ /**
135
+ * focus manages focus state for an element.
136
+ */
137
+ declare function focus(): {
138
+ isFocused: () => boolean;
139
+ focus: () => void;
140
+ blur: () => void;
141
+ bind: (element: HTMLElement) => void;
142
+ };
143
+ /**
144
+ * FocusTrap traps focus within a container element.
145
+ * Tab cycling stays inside the container.
146
+ */
147
+ declare function FocusTrap(nodes: HTMLElement, options?: {
148
+ autoFocus?: boolean;
149
+ restoreFocus?: boolean;
150
+ }): HTMLElement;
151
+ /**
152
+ * hotkey registers a keyboard shortcut handler.
153
+ * Returns a cleanup function.
154
+ *
155
+ * Supports two calling styles:
156
+ * - String combo: hotkey("ctrl+shift+z", handler)
157
+ * - Explicit flags: hotkey("z", handler, { ctrl: true, shift: true })
158
+ */
159
+ declare function hotkey(combo: string, handler: (e: KeyboardEvent) => void, options?: {
160
+ ctrl?: boolean;
161
+ shift?: boolean;
162
+ alt?: boolean;
163
+ meta?: boolean;
164
+ global?: boolean;
165
+ preventDefault?: boolean;
166
+ }): () => void;
167
+ /**
168
+ * announce creates a screen reader announcement using ARIA live regions.
169
+ */
170
+ declare function announce(message: string, priority?: "polite" | "assertive"): void;
171
+
172
+ /**
173
+ * scopedStyle creates component-scoped CSS by generating a unique scope ID
174
+ * and prefixing all selectors.
175
+ * Returns the scope attribute name and injects the CSS into the document.
176
+ *
177
+ * CSS is sanitized to remove dangerous patterns (`url()`, `@import`,
178
+ * `expression()`, `-moz-binding`, `behavior`). If you need `url()` for
179
+ * background images, use inline styles via the `style` prop instead.
180
+ */
181
+ declare function scopedStyle(css: string): {
182
+ scope: string;
183
+ attr: string;
184
+ };
185
+ /**
186
+ * withScopedStyle wraps a component function to auto-apply scoped styles.
187
+ * The component and all its children get the scope attribute.
188
+ */
189
+ declare function withScopedStyle<P>(css: string, component: (props: P) => HTMLElement): (props: P) => HTMLElement;
190
+ /**
191
+ * Removes a scoped style by its scope ID.
192
+ */
193
+ declare function removeScopedStyle(scopeId: string): void;
194
+
195
+ /**
196
+ * Bind multiple reactive attributes to an element.
197
+ * Each attribute value can be a static value or a reactive getter.
198
+ * Returns a single teardown function that stops all bindings.
199
+ */
200
+ declare function bindAttrs(el: HTMLElement, attrs: Record<string, string | number | boolean | (() => string | number | boolean)>): () => void;
201
+ /**
202
+ * Reactively toggle a boolean attribute (like disabled, readonly, hidden).
203
+ * When the value is truthy the attribute is present (set to ""),
204
+ * when falsy the attribute is removed entirely.
205
+ * Returns a teardown function to stop reactive tracking.
206
+ */
207
+ declare function bindBoolAttr(el: HTMLElement, attr: string, getter: boolean | (() => boolean)): () => void;
208
+ /**
209
+ * Bind a data-* attribute reactively.
210
+ * Shorthand for `bindAttribute(el, "data-<key>", getter)`.
211
+ * Returns a teardown function to stop reactive tracking.
212
+ */
213
+ declare function bindData(el: HTMLElement, key: string, getter: string | (() => string)): () => void;
214
+
215
+ /**
216
+ * dialog provides reactive dialog state management with escape-to-close support.
217
+ */
218
+ declare function dialog(): {
219
+ open: () => void;
220
+ close: () => void;
221
+ isOpen: () => boolean;
222
+ toggle: () => void;
223
+ };
224
+
225
+ /**
226
+ * Toast notification system with auto-dismiss and max toast limits.
227
+ */
228
+ interface Toast {
229
+ id: string;
230
+ message: string;
231
+ type?: "info" | "success" | "error" | "warning";
232
+ }
233
+ interface ToastInstance {
234
+ toasts: () => Toast[];
235
+ show: (message: string, type?: Toast["type"]) => string;
236
+ /** Show an info toast. */
237
+ info: (message: string) => string;
238
+ /** Show a success toast. */
239
+ success: (message: string) => string;
240
+ /** Show an error toast. */
241
+ error: (message: string) => string;
242
+ /** Show a warning toast. */
243
+ warning: (message: string) => string;
244
+ dismiss: (id: string) => void;
245
+ dismissAll: () => void;
246
+ }
247
+ declare function toast(options?: {
248
+ duration?: number;
249
+ maxToasts?: number;
250
+ }): ToastInstance;
251
+
252
+ /**
253
+ * infiniteScroll combines IntersectionObserver with a data-fetching trigger
254
+ * to implement infinite scroll behavior.
255
+ */
256
+ declare function infiniteScroll(options: {
257
+ onLoadMore: () => Promise<void>;
258
+ hasMore: () => boolean;
259
+ threshold?: number;
260
+ }): {
261
+ sentinelRef: {
262
+ current: HTMLElement | null;
263
+ };
264
+ loading: () => boolean;
265
+ dispose: () => void;
266
+ };
267
+
268
+ /**
269
+ * pagination provides reactive pagination state and controls.
270
+ */
271
+ declare function pagination(options: {
272
+ totalItems: () => number;
273
+ pageSize?: number;
274
+ initialPage?: number;
275
+ }): {
276
+ page: () => number;
277
+ pageSize: () => number;
278
+ totalPages: () => number;
279
+ next: () => void;
280
+ prev: () => void;
281
+ goTo: (page: number) => void;
282
+ startIndex: () => number;
283
+ endIndex: () => number;
284
+ };
285
+
286
+ /**
287
+ * eventBus creates a typed publish/subscribe event system.
288
+ * No reactive state needed -- pure event dispatching.
289
+ */
290
+ declare function eventBus<T extends Record<string, unknown>>(): {
291
+ on: <K extends keyof T>(event: K, handler: (data: T[K]) => void) => () => void;
292
+ emit: <K extends keyof T>(event: K, data: T[K]) => void;
293
+ off: <K extends keyof T>(event: K, handler: (data: T[K]) => void) => void;
294
+ clear: () => void;
295
+ };
296
+
297
+ interface CustomElementOptions {
298
+ shadow?: boolean;
299
+ mode?: "open" | "closed";
300
+ styles?: string;
301
+ observedAttributes?: string[];
302
+ extends?: string;
303
+ }
304
+ /**
305
+ * defineElement creates a Web Component wrapping a SibuJS component function.
306
+ */
307
+ declare function defineElement(name: string, component: (props: Record<string, unknown>, element: HTMLElement) => HTMLElement, options?: CustomElementOptions): void;
308
+ /**
309
+ * Creates an SVG element with proper namespace.
310
+ */
311
+ declare function svgElement(tag: string, props?: Record<string, unknown>, ...nodes: (SVGElement | string)[]): SVGElement;
312
+
313
+ export { lazyLoad as A, type BoundFieldProps as B, type CustomElementOptions as C, matchesPattern as D, max as E, type FieldConfig as F, maxLength as G, min as H, type IntersectionResult as I, minLength as J, pagination as K, phoneMask as L, type MaskOptions as M, removeScopedStyle as N, required as O, scopedStyle as P, ssnMask as Q, svgElement as R, timeMask as S, type Toast as T, toast as U, type ValidatorFn as V, withScopedStyle as W, zipMask as X, FocusTrap as a, type FormConfig as b, type FormField as c, type FormReturn as d, type ToastInstance as e, VirtualList as f, type VirtualListProps as g, announce as h, aria as i, bindAttrs as j, bindBoolAttr as k, bindData as l, bindField as m, creditCardMask as n, custom as o, dateMask as p, defineElement as q, dialog as r, email as s, eventBus as t, focus as u, form as v, hotkey as w, infiniteScroll as x, inputMask as y, intersection as z };
package/dist/extras.cjs CHANGED
@@ -3320,18 +3320,31 @@ function FocusTrap(nodes, options = {}) {
3320
3320
  }
3321
3321
  return container;
3322
3322
  }
3323
- function hotkey(key, handler, options = {}) {
3323
+ function hotkey(combo, handler, options = {}) {
3324
+ let key = combo;
3325
+ let needCtrl = options.ctrl ?? false;
3326
+ let needShift = options.shift ?? false;
3327
+ let needAlt = options.alt ?? false;
3328
+ let needMeta = options.meta ?? false;
3329
+ if (combo.includes("+")) {
3330
+ const parts = combo.toLowerCase().split("+");
3331
+ key = parts[parts.length - 1];
3332
+ for (let i2 = 0; i2 < parts.length - 1; i2++) {
3333
+ const mod = parts[i2];
3334
+ if (mod === "ctrl" || mod === "control") needCtrl = true;
3335
+ else if (mod === "shift") needShift = true;
3336
+ else if (mod === "alt") needAlt = true;
3337
+ else if (mod === "meta" || mod === "cmd" || mod === "command") needMeta = true;
3338
+ }
3339
+ }
3324
3340
  const listener = (e) => {
3325
3341
  const ke = e;
3326
3342
  if (ke.key.toLowerCase() !== key.toLowerCase()) return;
3327
- if (options.ctrl && !ke.ctrlKey) return;
3328
- if (!options.ctrl && ke.ctrlKey) return;
3329
- if (options.shift && !ke.shiftKey) return;
3330
- if (!options.shift && ke.shiftKey) return;
3331
- if (options.alt && !ke.altKey) return;
3332
- if (!options.alt && ke.altKey) return;
3333
- if (options.meta && !ke.metaKey) return;
3334
- if (!options.meta && ke.metaKey) return;
3343
+ if (needCtrl !== ke.ctrlKey) return;
3344
+ if (needShift !== ke.shiftKey) return;
3345
+ if (needAlt !== ke.altKey) return;
3346
+ if (needMeta !== ke.metaKey) return;
3347
+ if (options.preventDefault) ke.preventDefault();
3335
3348
  handler(ke);
3336
3349
  };
3337
3350
  document.addEventListener("keydown", listener);
package/dist/extras.d.cts CHANGED
@@ -3,7 +3,7 @@ export { battery, clipboard, colorScheme, draggable, dropZone, formatCurrency, f
3
3
  export { GlobalStore, MachineConfig, MachineReturn, Middleware, OptimisticAction, PersistOptions, Selector, TimeTravelReturn, createEffect, createMemo, createSignal, globalStore, machine, optimistic, optimisticList, persisted, timeline } from './patterns.cjs';
4
4
  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';
5
5
  export { AnimationPreset, PresetOptions, SlideDirection, SpringOptions, TransitionGroup, TransitionGroupOptions, TransitionOptions, animate, bounceIn, bounceOut, fadeIn, fadeOut, flipIn, pulse, reducedMotion, scaleDown, scaleUp, sequence, shake, slideIn, slideOut, spring, springSignal, stagger, transition, viewTransition } from './motion.cjs';
6
- 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-yz8uyk-0.cjs';
6
+ 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';
7
7
  export { ChunkConfig, DOMPool, Features, NormalizeResult, NormalizedEntities, NormalizedSchema, NormalizedState, NormalizedStoreActions, Priority, PriorityLevel, block, cloneTemplate, conditional, createChunkRegistry, deferredValue, denormalize, devOnly, domPool, flushScheduler, hoistable, lazyChunk, noSideEffect, normalize, normalizedStore, pendingTasks, precompile, prefetch, preloadImage, preloadModule, preloadModules, preloadResource, processInChunks, pure, resetIdCounter, scheduleUpdate, setIdPrefix, startTransition, staticTemplate, transitionState, uniqueId, yieldToMain } from './performance.cjs';
8
8
  export { ActionFn, ActionResult, Head, ISROptions, MicroApp, MicroAppConfig, MiddlewareFn, SSGOptions, SSGResult, ScrollRestorationOptions, ServiceWorkerState, SharedScope, UseWorkerFnReturn, UseWorkerReturn, WasmConfig, WasmModuleState, WorkerPool, clearWasmCache, composeMiddleware, createAction, createISR, createMicroApp, createMiddlewareChain, createSharedScope, createWasmBridge, createWorkerPool, defineRemoteComponent, generateStaticSite, isWasmCached, loadRemoteModule, loadWasmModule, preloadWasm, scrollRestoration, serviceWorker, setCanonical, setStructuredData, wasm, worker, workerFn } from './ssr.cjs';
9
9
  export { T as TrustedHTML, c as collectStream, d as deserializeState, h as hydrate, a as hydrateIslands, b as hydrateProgressively, i as island, r as renderToDocument, e as renderToReadableStream, f as renderToStream, g as renderToString, j as renderToSuspenseStream, k as resetSSRState, s as serializeState, l as ssrSuspense, m as suspenseSwapScript, t as trustHTML } from './ssr-BA6sxxUd.cjs';