@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.
- package/dist/elements.d.ts +95 -0
- package/dist/elements.js +118 -0
- package/dist/elements.test.d.ts +1 -0
- package/dist/form-state.d.ts +173 -0
- package/dist/form-state.js +98 -0
- package/dist/form-state.test.d.ts +1 -0
- package/dist/html-C8SnQjvU.js +238 -0
- package/dist/html.d.ts +499 -0
- package/dist/html.js +90 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +72 -0
- package/dist/keybinds.d.ts +103 -0
- package/dist/widget.d.ts +420 -0
- package/dist/widget.js +347 -0
- package/dist/widget.test.d.ts +7 -0
- package/package.json +44 -0
|
@@ -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];
|
package/dist/widget.d.ts
ADDED
|
@@ -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 {};
|