@rhi-zone/rainbow-ui 0.2.0-alpha.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.
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @rhi-zone/rainbow-ui/keybinds
3
+ *
4
+ * Reactive integration between the `keybinds` library and rainbow signals.
5
+ *
6
+ * `keybindsContext` wires a `Signal<S>` into a keybinds registration so that
7
+ * context is always evaluated from current signal state on each keypress.
8
+ *
9
+ * `bindingsStoreSignal` wraps a `BindingsStore` (from keybinds) in a
10
+ * `ReadonlySignal` so that user-customized bindings are reactive.
11
+ */
12
+ import type { Signal, ReadonlySignal } from "@rhi-zone/rainbow";
13
+ /** Minimal shape of a keybinds Command object. */
14
+ export interface Command {
15
+ id: string;
16
+ label: string;
17
+ description?: string;
18
+ category?: string;
19
+ keys?: string[];
20
+ mouse?: string[];
21
+ when?: (ctx: Record<string, unknown>) => boolean;
22
+ execute: (ctx: Record<string, unknown>, event?: Event) => unknown;
23
+ hidden?: boolean;
24
+ captureInput?: boolean;
25
+ menu?: string | string[];
26
+ }
27
+ /** Options accepted by the keybinds() function. */
28
+ export interface KeybindsOptions {
29
+ target?: EventTarget;
30
+ onExecute?: (cmd: Command, ctx: Record<string, unknown>) => void;
31
+ }
32
+ /**
33
+ * The `keybinds` function signature from the keybinds library.
34
+ * Accept this as a parameter so callers supply the concrete import —
35
+ * no hard dependency on the `keybinds` package from `@rhi-zone/rainbow-ui`.
36
+ */
37
+ export type KeybindsFn = (commands: Command[], getContext: () => Record<string, unknown>, options?: KeybindsOptions) => () => void;
38
+ /** The BindingsStore class shape from keybinds (extends EventTarget). */
39
+ export interface BindingsStore<T = Record<string, unknown>> extends EventTarget {
40
+ schema: T;
41
+ storageKey: string;
42
+ overrides: Record<string, unknown>;
43
+ bindings: T;
44
+ get(): T;
45
+ getOverrides(): Record<string, unknown>;
46
+ save(newOverrides: Record<string, unknown>): void;
47
+ }
48
+ /**
49
+ * Register keybindings whose context is derived from a reactive signal.
50
+ *
51
+ * The `getContext` callback is invoked on each keypress, reading current
52
+ * signal state at that moment. No re-registration is needed when state
53
+ * changes — the closure is inherently fresh.
54
+ *
55
+ * The subscription to `state` is established so that callers can extend
56
+ * this helper without forking it (e.g. to rebuild command lists reactively).
57
+ * It also serves as a documented contract: this integration owns the signal.
58
+ *
59
+ * Pass the `keybinds` function imported from the `keybinds` package as the
60
+ * first argument. This avoids a hard package dependency on `keybinds` from
61
+ * `@rhi-zone/rainbow-ui` — callers bring their own import.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * import { keybinds } from "keybinds"
66
+ * import { keybindsContext } from "@rhi-zone/rainbow-ui"
67
+ *
68
+ * const dispose = keybindsContext(keybinds, commands, appState, s => ({
69
+ * isAdmin: s.role === "admin",
70
+ * hasSelection: s.selection.length > 0,
71
+ * }))
72
+ * ```
73
+ *
74
+ * @param keybindsFn - The `keybinds` function from the keybinds library.
75
+ * @param commands - Array of command definitions.
76
+ * @param state - Signal whose value supplies the keybind context.
77
+ * @param buildContext - Pure function mapping signal state to a context object.
78
+ * @param options - Optional keybinds options (target element, onExecute hook).
79
+ * @returns A dispose function that removes event listeners and unsubscribes
80
+ * from the signal. Call it when the component or scope unmounts.
81
+ */
82
+ export declare function keybindsContext<S>(keybindsFn: KeybindsFn, commands: Command[], state: Signal<S> | ReadonlySignal<S>, buildContext: (s: S) => Record<string, unknown>, options?: KeybindsOptions): () => void;
83
+ /**
84
+ * Wrap a `BindingsStore` from the keybinds library in a `ReadonlySignal<T>`
85
+ * so that user-customized bindings propagate reactively.
86
+ *
87
+ * The signal holds the current merged bindings (schema + overrides). When the
88
+ * user saves new overrides via `BindingsStore.save()`, the store dispatches a
89
+ * `'change'` CustomEvent and this signal updates automatically.
90
+ *
91
+ * The returned dispose function removes the event listener. Call it when the
92
+ * component or scope unmounts to prevent memory leaks.
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * const store = new BindingsStore(schema, "app-bindings")
97
+ * const [bindingsSignal, disposeBindings] = bindingsStoreSignal(store)
98
+ *
99
+ * // bindingsSignal.get() always returns the latest merged bindings
100
+ * // bindingsSignal.subscribe(...) fires whenever the user customizes a key
101
+ * ```
102
+ */
103
+ export declare function bindingsStoreSignal<T>(store: BindingsStore<T>): [ReadonlySignal<T>, () => void];
@@ -0,0 +1,420 @@
1
+ /**
2
+ * @rhi-zone/rainbow-ui/widget
3
+ *
4
+ * Algebraic widget combinators for rainbow signals.
5
+ *
6
+ * Widget<T, E> = (signal: Signal<T>) => E
7
+ *
8
+ * A widget is a pure function from a reactive signal to a typed DOM node.
9
+ * The second type parameter E (defaults to FlowContent) tracks what kind of
10
+ * element the widget produces, so the HTML content model flows through
11
+ * composition and invalid nesting is a type error.
12
+ *
13
+ * The seven combinators:
14
+ * focus — zoom into a product field via a Lens
15
+ * narrow — zoom into a sum variant via a Prism; renders nothing when unmatched
16
+ * each — render a list; re-renders on length change, item signals handle diffs
17
+ * beside — pair two widgets side by side; state is the product [A, B]
18
+ * above — pair two widgets vertically; state is the product [A, B]
19
+ * dynamic — pair local state S with an external signal A via stateful()
20
+ * map — reinterpret the signal type via a total Prism (iso)
21
+ * show — boolean gate; renders nothing when predicate is false
22
+ * concat — combine two list widgets over the same signal
23
+ *
24
+ * Lifecycle / cleanup:
25
+ * All subscriptions created inside a widget call are tracked via a
26
+ * thread-local (synchronous) context. mount() collects them and returns
27
+ * a single cleanup function that unsubscribes everything.
28
+ *
29
+ * Combinators that conditionally render (narrow, show) manage inner cleanup
30
+ * themselves: they tear down child subscriptions when the condition becomes
31
+ * false and rebuild them when it becomes true again.
32
+ */
33
+ import { type Signal, type ReadonlySignal, type Lens, type Prism, type AsyncData } from "@rhi-zone/rainbow";
34
+ import type { AnyEl, El, FlowContent, DivEl, InputEl, InputAttrs, TextareaEl, TextareaAttrs, SelectEl, SelectAttrs } from "./html.js";
35
+ /**
36
+ * A widget is a pure function from a reactive signal to a typed DOM node.
37
+ * Calling a widget subscribes it to the signal; the returned node is updated
38
+ * in place whenever the signal changes.
39
+ *
40
+ * @typeParam T - The signal value type
41
+ * @typeParam E - The element type produced (defaults to FlowContent)
42
+ */
43
+ export type Widget<T, E extends AnyEl = FlowContent> = (signal: Signal<T>) => E;
44
+ /**
45
+ * Run `fn` in an explicit cleanup scope. Returns the result and a dispose
46
+ * function. Use at the top level (bootstrap, app root) where there is no
47
+ * enclosing widget context for `register()` calls to land in.
48
+ *
49
+ * @example
50
+ * const [, disposeApp] = withScope(() => {
51
+ * register(disposeInit)
52
+ * renderContactList(sidebar)
53
+ * renderDetailPanel(detail)
54
+ * })
55
+ */
56
+ export declare function withScope<T>(fn: () => T): [T, () => void];
57
+ /** Minimal interface satisfied by both Signal<T> and ReadonlySignal<T>. */
58
+ type Subscribable<T> = Pick<Signal<T> | ReadonlySignal<T>, "subscribe">;
59
+ /**
60
+ * Subscribe to a signal (or readonly signal) and register the unsubscribe in
61
+ * the current context. Must be called during a widget call (directly or via a
62
+ * combinator).
63
+ */
64
+ export declare function subscribe<T>(s: Subscribable<T>, fn: (value: T) => void): void;
65
+ /**
66
+ * Register an arbitrary cleanup function in the current widget call context.
67
+ * Called on unmount alongside subscription cleanups. Use for event listeners,
68
+ * timers, or `fromAsync` dispose functions.
69
+ *
70
+ * @example
71
+ * const [data, dispose] = fromAsync(querySignal, fetch)
72
+ * register(dispose)
73
+ */
74
+ export declare function register(fn: () => void): void;
75
+ /**
76
+ * Render a widget into a container and return a cleanup function.
77
+ * The cleanup removes the rendered node and unsubscribes all signals.
78
+ *
79
+ * @example
80
+ * const cleanup = mount(counterWidget, countSignal, document.getElementById('root')!)
81
+ * // later:
82
+ * cleanup()
83
+ */
84
+ export declare function mount<T, E extends AnyEl>(widget: Widget<T, E>, signal: Signal<T> | ReadonlySignal<T>, container: HTMLElement): () => void;
85
+ /**
86
+ * Zoom into a product field. The child widget operates on `B`; the parent
87
+ * signal carries `A`. Reads and writes pass through the lens.
88
+ *
89
+ * @example
90
+ * // Widget<CompareExpr> that only sees the 'op' field
91
+ * focus(opPicker, field('op'))
92
+ */
93
+ export declare function focus<A, B, E extends AnyEl>(w: Widget<B, E>, l: Lens<A, B>): Widget<A, E>;
94
+ /**
95
+ * Zoom into a sum variant. The child widget renders when the prism matches;
96
+ * renders an empty container when it doesn't. Container switches on match
97
+ * status changes; in-variant updates are handled by the child's own signal.
98
+ *
99
+ * @example
100
+ * narrow(compareWidget, comparePrism) // Widget<Expr>: visible only for compare nodes
101
+ */
102
+ export declare function narrow<A, B>(w: Widget<B, FlowContent>, prism: Prism<A, B>): Widget<A, DivEl>;
103
+ /**
104
+ * Render a list. Each item gets a focused signal via an index lens. Fully
105
+ * re-renders on length change; per-item updates are handled by each item
106
+ * signal (no explicit keying yet — that is a future optimisation).
107
+ *
108
+ * @example
109
+ * each(rowWidget) // Widget<Row[]>
110
+ */
111
+ export declare function each<A>(w: Widget<A, FlowContent>): Widget<A[], DivEl>;
112
+ /**
113
+ * Render two widgets side by side. The combined signal is the product [A, B].
114
+ * Layout (flex/grid) is the caller's responsibility via CSS.
115
+ *
116
+ * @example
117
+ * beside(focus(opPicker, field('op')), focus(exprWidget, field('left')))
118
+ */
119
+ export declare function beside<A, B>(wa: Widget<A, FlowContent>, wb: Widget<B, FlowContent>): Widget<[A, B], DivEl>;
120
+ /**
121
+ * Render two widgets stacked vertically. The combined signal is the product [A, B].
122
+ *
123
+ * @example
124
+ * above(labelWidget, inputWidget)
125
+ */
126
+ export declare function above<A, B>(wa: Widget<A, FlowContent>, wb: Widget<B, FlowContent>): Widget<[A, B], DivEl>;
127
+ /**
128
+ * Pair local state `S` with an external signal `A`. The child widget sees the
129
+ * product `[S, A]`. Local state changes do not propagate to the parent.
130
+ *
131
+ * Implemented via rainbow's `stateful(init, outer)`.
132
+ *
133
+ * @example
134
+ * // Combobox with open/closed state independent of the value signal
135
+ * dynamic(false, comboboxWidget) // Widget<Value>
136
+ */
137
+ export declare function dynamic<S, A, E extends AnyEl>(init: S, w: Widget<[S, A], E>): Widget<A, E>;
138
+ /**
139
+ * Reinterpret the widget's value type via a total prism (isomorphism).
140
+ * The prism must be a bijection: `match` must always return a value.
141
+ *
142
+ * Useful for display transforms such as number ↔ string for text inputs.
143
+ *
144
+ * @example
145
+ * const numToStr = iso((n: number) => String(n), (s) => Number(s))
146
+ * map(numberWidget, numToStr) // Widget<string>
147
+ */
148
+ export declare function map<A, B, E extends AnyEl>(w: Widget<A, E>, isoP: Prism<B, A>): Widget<B, E>;
149
+ /**
150
+ * Boolean gate. Renders the child widget when `predicate` holds; renders an
151
+ * empty container otherwise. Simpler than `narrow` when there is no Prism —
152
+ * just a boolean condition. SolidJS `<Show>` is the prior art.
153
+ *
154
+ * @example
155
+ * show(detailsWidget, (form) => form.advanced)
156
+ */
157
+ /**
158
+ * Boolean gate. Renders the child widget when `predicate` holds; hides it
159
+ * (via `display:none`) otherwise. Simpler than `narrow` when there is no
160
+ * Prism — just a boolean condition. SolidJS `<Show>` is the prior art.
161
+ *
162
+ * The child is rendered **eagerly** and kept alive in the DOM. Toggling
163
+ * visibility is a single style-property write — no DOM teardown/rebuild, no
164
+ * GC pressure, and subscriptions remain active so the child stays current
165
+ * while hidden (showing is instant, no catch-up render required).
166
+ *
167
+ * @example
168
+ * show(detailsWidget, (form) => form.advanced)
169
+ */
170
+ export declare function show<A>(w: Widget<A, FlowContent>, predicate: (a: A) => boolean): Widget<A, DivEl>;
171
+ /**
172
+ * Append two list widgets over the same signal. Both widgets see the full
173
+ * list; use `show`/`narrow`/filtering inside each to render different subsets.
174
+ * Corresponds to unicorn's concat combinator.
175
+ *
176
+ * @example
177
+ * // Active rows, then archived rows, in the same <tbody>
178
+ * concat(
179
+ * each(show(rowWidget, r => r.active)),
180
+ * each(show(rowWidget, r => !r.active))
181
+ * )
182
+ */
183
+ export declare function concat<A>(wa: Widget<A[], DivEl>, wb: Widget<A[], DivEl>): Widget<A[], DivEl>;
184
+ /**
185
+ * Render N widgets all receiving the same signal, stacked into one container.
186
+ * The same-signal variant of `above` — no product type required.
187
+ *
188
+ * Primary use case: form fields that all operate on `FormState<T>`.
189
+ *
190
+ * @example
191
+ * stack(formField("name", inputWidget()), formField("email", inputWidget()))
192
+ */
193
+ export declare function stack<A>(...widgets: Widget<A, FlowContent>[]): Widget<A, DivEl>;
194
+ /**
195
+ * Maps a `refs` dict `{ refName: tagName }` to a typed El map:
196
+ * `{ refName: El<tagName, HTMLElementTagNameMap[tagName]> }`
197
+ */
198
+ export type RefsMap<R extends Record<string, keyof HTMLElementTagNameMap>> = {
199
+ readonly [K in keyof R]: El<R[K] & string, HTMLElementTagNameMap[R[K]]>;
200
+ };
201
+ /**
202
+ * Template combinator. Parses `innerHTML` once (at definition time), then for
203
+ * each widget instantiation clones the template, queries out typed DOM refs via
204
+ * `data-ref` attributes, and calls `bind` with the signal and those refs.
205
+ *
206
+ * Refs are queried as `tagName[data-ref="refName"]`, so `{ name: "span" }`
207
+ * expects `<span data-ref="name">` in the template. Using `data-ref` rather
208
+ * than `id` means the same template can be cloned many times (e.g. inside
209
+ * `eachKeyed`) without duplicate-ID issues.
210
+ *
211
+ * `bind` is called inside the widget call context, so any `subscribe` calls
212
+ * inside it are tracked and cleaned up by `mount` / parent combinators.
213
+ *
214
+ * @throws if a declared ref is absent from the cloned template.
215
+ *
216
+ * @example
217
+ * const cardWidget = template(
218
+ * `<div class="card"><span data-ref="name"></span><b data-ref="score"></b></div>`,
219
+ * { name: "span", score: "b" } as const,
220
+ * (s, { name, score }) => {
221
+ * name.node.textContent = s.get().label
222
+ * score.node.textContent = String(s.get().value)
223
+ * subscribe(s, v => {
224
+ * name.node.textContent = v.label
225
+ * score.node.textContent = String(v.value)
226
+ * })
227
+ * }
228
+ * )
229
+ */
230
+ export declare function template<const R extends Record<string, keyof HTMLElementTagNameMap>, T>(innerHTML: string, refs: R, bind: (signal: Signal<T>, refs: RefsMap<R>) => void): Widget<T, DivEl>;
231
+ /**
232
+ * Render a keyed list. Each item gets a stable Signal<T> for its lifetime —
233
+ * only added/removed items trigger mount/unmount. Reordering and in-place
234
+ * updates are handled without remounting.
235
+ *
236
+ * Write-back: if an item widget sets its signal (e.g. an editable field),
237
+ * the change propagates back to the parent list signal at the item's current
238
+ * key position — but only when `s` is a writable Signal (has a `set` method).
239
+ * A flag prevents the resulting list update from cycling back.
240
+ *
241
+ * GC note: O(n) `Object.is` comparisons per list update (to sync item
242
+ * signals), but O(new keys) DOM/signal allocations. Stable lists are cheap.
243
+ *
244
+ * @param s - Signal carrying the full array
245
+ * @param getKey - Stable key function (like React's `key` prop)
246
+ * @param widget - Widget factory receiving a per-item Signal<T>
247
+ * @param options - Optional container tag (default "div"); pass a specific
248
+ * HTML tag to fix CSS grid/flex layout issues that arise
249
+ * from an extra wrapper div.
250
+ *
251
+ * @example
252
+ * eachKeyed(todosSignal, t => t.id, todoWidget)
253
+ * eachKeyed(rowsSignal, r => r.id, rowWidget, { container: "ul" })
254
+ */
255
+ export declare function eachKeyed<T>(s: Signal<T[]> | ReadonlySignal<T[]>, getKey: (item: T) => string, widget: (itemSignal: Signal<T>) => AnyEl, options?: {
256
+ container?: keyof HTMLElementTagNameMap;
257
+ }): AnyEl;
258
+ /**
259
+ * Attach a typed event listener and register its removal as cleanup.
260
+ * Must be called during a widget call context (directly or via a combinator).
261
+ *
262
+ * @example
263
+ * on(buttonEl.node, "click", () => s.set(s.get() + 1))
264
+ */
265
+ export declare function on<K extends keyof HTMLElementEventMap>(el: EventTarget, event: K, fn: (e: HTMLElementEventMap[K]) => void): void;
266
+ /**
267
+ * Two-way bind a text `<input>` or `<textarea>` to a `Signal<string>`.
268
+ *
269
+ * Sets `el.value` immediately from the signal, then:
270
+ * - DOM → signal: `input` event calls `s.set(el.value)`
271
+ * - signal → DOM: updates `el.value` only when the new value differs
272
+ * (guards against cursor-position jumps on mid-type updates)
273
+ *
274
+ * When called inside a widget call context, cleanup is registered
275
+ * automatically. When called outside (e.g. imperative bootstrap), use the
276
+ * returned cleanup function.
277
+ *
278
+ * @returns A cleanup function that unsubscribes and removes the event listener.
279
+ */
280
+ export declare function bindInput(el: HTMLInputElement | HTMLTextAreaElement, s: Signal<string>): () => void;
281
+ /**
282
+ * Two-way bind a `<select>` to a `Signal<string>`.
283
+ * DOM → signal on `change` event; signal → DOM when value differs.
284
+ *
285
+ * Must be called during a widget call context.
286
+ */
287
+ export declare function bindSelect(el: HTMLSelectElement, s: Signal<string>): void;
288
+ /**
289
+ * Two-way bind a checkbox `<input>` to a `Signal<boolean>`.
290
+ * DOM → signal on `change` event; signal → DOM when checked state differs.
291
+ *
292
+ * Must be called during a widget call context.
293
+ */
294
+ export declare function bindCheckbox(el: HTMLInputElement, s: Signal<boolean>): void;
295
+ /**
296
+ * Run `fn` immediately with the current signal value, then subscribe to future
297
+ * changes. Registers the subscription cleanup in the current widget context.
298
+ *
299
+ * Eliminates the `fn(s.get()); subscribe(s, fn)` boilerplate in template bind
300
+ * callbacks.
301
+ *
302
+ * Must be called during a widget call context.
303
+ */
304
+ export declare function subscribeNow<T>(s: Signal<T> | ReadonlySignal<T>, fn: (v: T) => void): void;
305
+ /**
306
+ * Reactively set `el.textContent` to the value of `s`. Sets the initial value
307
+ * synchronously, then subscribes for future changes.
308
+ *
309
+ * Must be called during a widget call context.
310
+ *
311
+ * @example
312
+ * bindText(nameSpan.node, nameSignal)
313
+ */
314
+ export declare function bindText(el: {
315
+ textContent: string | null;
316
+ }, s: Signal<string> | ReadonlySignal<string>): void;
317
+ /**
318
+ * Reactively set an attribute on `el` to the value of `s`. Sets the initial
319
+ * value synchronously, then subscribes for future changes.
320
+ *
321
+ * Must be called during a widget call context.
322
+ *
323
+ * @example
324
+ * bindAttr(imgEl.node, "src", urlSignal)
325
+ */
326
+ export declare function bindAttr(el: Element, attr: string, s: Signal<string> | ReadonlySignal<string>): void;
327
+ /**
328
+ * Reactively toggle a CSS class on `el` based on the boolean value of `s`.
329
+ * Sets the initial state synchronously, then subscribes for future changes.
330
+ *
331
+ * Must be called during a widget call context.
332
+ *
333
+ * @example
334
+ * bindClass(rowEl.node, "selected", isSelectedSignal)
335
+ */
336
+ export declare function bindClass(el: Element, className: string, s: Signal<boolean> | ReadonlySignal<boolean>): void;
337
+ /**
338
+ * Subscribe to multiple signals and run `fn` whenever any of them changes.
339
+ * Calls `fn()` immediately on invocation, then re-runs on each change.
340
+ * Returns a cleanup function that unsubscribes from all signals.
341
+ *
342
+ * Replaces the repetitive `subscribe(a, fn); subscribe(b, fn); fn()` pattern
343
+ * when a single side-effectful function depends on N signals.
344
+ *
345
+ * @example
346
+ * const stop = watchAll([contacts, panel, searchQuery, sortBy], renderList)
347
+ * // later:
348
+ * stop()
349
+ */
350
+ export declare function watchAll(signals: ReadonlyArray<ReadonlySignal<unknown>>, fn: () => void): () => void;
351
+ /**
352
+ * Show or hide `el` (via `style.display`) based on the boolean signal `s`.
353
+ * Sets `display` to `""` when `s` is true, `"none"` when false.
354
+ * Applies the initial value synchronously on call.
355
+ * Returns a cleanup function that unsubscribes the signal.
356
+ *
357
+ * Use `signal.map(...)` to derive the boolean from a richer signal:
358
+ *
359
+ * @example
360
+ * const stop = bindShow(viewPanel, panel.map(p => p.mode === 'viewing'))
361
+ * // later:
362
+ * stop()
363
+ */
364
+ export declare function bindShow(el: HTMLElement, s: ReadonlySignal<boolean>): () => void;
365
+ /**
366
+ * Reactively render one of four states of an `AsyncData` signal into a
367
+ * container div. Each state maps to an optional render function; missing cases
368
+ * render an empty div. Replaces the container's single child on every state
369
+ * change.
370
+ *
371
+ * Modelled after `narrow` — manages inner cleanup when swapping states.
372
+ * Must be called during a widget call context.
373
+ *
374
+ * @example
375
+ * const container = foldWidget(asyncDataSignal, {
376
+ * notAsked: () => text("—"),
377
+ * loading: () => text("Loading…"),
378
+ * failure: (err) => text(String(err)),
379
+ * success: (data) => renderData(data),
380
+ * })
381
+ */
382
+ export declare function foldWidget<T, E>(s: Signal<AsyncData<T, E>> | ReadonlySignal<AsyncData<T, E>>, cases: {
383
+ notAsked?: () => AnyEl;
384
+ loading?: () => AnyEl;
385
+ failure?: (err: E) => AnyEl;
386
+ success?: (value: T) => AnyEl;
387
+ }): DivEl;
388
+ /**
389
+ * Text input widget. The `value` attr is omitted from `attrs` — the signal
390
+ * owns the value. Use `focus` to connect to a field on a larger signal.
391
+ *
392
+ * @example
393
+ * focus(inputWidget({ placeholder: "Name" }), field("name"))
394
+ */
395
+ export declare function inputWidget(attrs?: Omit<InputAttrs, "value">): Widget<string, InputEl>;
396
+ /**
397
+ * Textarea widget. Signal owns the value.
398
+ */
399
+ export declare function textareaWidget(attrs?: TextareaAttrs): Widget<string, TextareaEl>;
400
+ /**
401
+ * Checkbox widget. Signal owns the checked state.
402
+ */
403
+ export declare function checkboxWidget(attrs?: Omit<InputAttrs, "type" | "checked">): Widget<boolean, InputEl>;
404
+ /**
405
+ * Number input widget. Signal owns the numeric value. NaN inputs are ignored
406
+ * (the signal is only updated when the parsed value is a valid number).
407
+ */
408
+ export declare function numberInputWidget(attrs?: Omit<InputAttrs, "value" | "type">): Widget<number, InputEl>;
409
+ /**
410
+ * Select widget with a static options list. Signal owns the selected value.
411
+ * For dynamic options, compose `each` + `focus` over a list signal instead.
412
+ *
413
+ * @example
414
+ * selectWidget([{ value: "au", label: "Australia" }, { value: "nz", label: "New Zealand" }])
415
+ */
416
+ export declare function selectWidget(options: {
417
+ value: string;
418
+ label: string;
419
+ }[], attrs?: SelectAttrs): Widget<string, SelectEl>;
420
+ export {};