@typed/ui 1.0.0-beta.0 → 1.0.0-beta.10
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/LICENSE +21 -0
- package/README.md +78 -26
- package/dist/Alert.d.ts +72 -0
- package/dist/Alert.d.ts.map +1 -0
- package/dist/Alert.js +40 -0
- package/dist/Button.d.ts +100 -0
- package/dist/Button.d.ts.map +1 -0
- package/dist/Button.js +42 -0
- package/dist/Carousel.d.ts +346 -0
- package/dist/Carousel.d.ts.map +1 -0
- package/dist/Carousel.js +264 -0
- package/dist/Checkbox.d.ts +168 -0
- package/dist/Checkbox.d.ts.map +1 -0
- package/dist/Checkbox.js +146 -0
- package/dist/Collection.d.ts +254 -0
- package/dist/Collection.d.ts.map +1 -0
- package/dist/Collection.js +218 -0
- package/dist/Combobox.d.ts +367 -0
- package/dist/Combobox.d.ts.map +1 -0
- package/dist/Combobox.js +300 -0
- package/dist/Composite.d.ts +823 -0
- package/dist/Composite.d.ts.map +1 -0
- package/dist/Composite.js +615 -0
- package/dist/Dialog.d.ts +544 -0
- package/dist/Dialog.d.ts.map +1 -0
- package/dist/Dialog.js +357 -0
- package/dist/Disclosure.d.ts +219 -0
- package/dist/Disclosure.d.ts.map +1 -0
- package/dist/Disclosure.js +128 -0
- package/dist/Dom/Events.d.ts +122 -0
- package/dist/Dom/Events.d.ts.map +1 -0
- package/dist/Dom/Events.js +192 -0
- package/dist/Dom/Props.d.ts +161 -0
- package/dist/Dom/Props.d.ts.map +1 -0
- package/dist/Dom/Props.js +110 -0
- package/dist/Dom/Refs.d.ts +58 -0
- package/dist/Dom/Refs.d.ts.map +1 -0
- package/dist/Dom/Refs.js +61 -0
- package/dist/Dom/Render.d.ts +59 -0
- package/dist/Dom/Render.d.ts.map +1 -0
- package/dist/Dom/Render.js +71 -0
- package/dist/Dom/Types.d.ts +570 -0
- package/dist/Dom/Types.d.ts.map +1 -0
- package/dist/Dom/Types.js +1 -0
- package/dist/Dom/index.d.ts +20 -0
- package/dist/Dom/index.d.ts.map +1 -0
- package/dist/Dom/index.js +8 -0
- package/dist/Dom.d.ts +14 -0
- package/dist/Dom.d.ts.map +1 -0
- package/dist/Dom.js +13 -0
- package/dist/Focusable.d.ts +85 -0
- package/dist/Focusable.d.ts.map +1 -0
- package/dist/Focusable.js +35 -0
- package/dist/Form.d.ts +1727 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/Form.js +1142 -0
- package/dist/Grid.d.ts +388 -0
- package/dist/Grid.d.ts.map +1 -0
- package/dist/Grid.js +284 -0
- package/dist/Group.d.ts +128 -0
- package/dist/Group.d.ts.map +1 -0
- package/dist/Group.js +71 -0
- package/dist/Heading.d.ts +87 -0
- package/dist/Heading.d.ts.map +1 -0
- package/dist/Heading.js +58 -0
- package/dist/Hovercard.d.ts +297 -0
- package/dist/Hovercard.d.ts.map +1 -0
- package/dist/Hovercard.js +188 -0
- package/dist/HttpRouter.d.ts +129 -6
- package/dist/HttpRouter.d.ts.map +1 -1
- package/dist/HttpRouter.js +199 -56
- package/dist/Link.d.ts +63 -28
- package/dist/Link.d.ts.map +1 -1
- package/dist/Link.js +84 -37
- package/dist/Listbox.d.ts +305 -0
- package/dist/Listbox.d.ts.map +1 -0
- package/dist/Listbox.js +245 -0
- package/dist/Menu.d.ts +663 -0
- package/dist/Menu.d.ts.map +1 -0
- package/dist/Menu.js +569 -0
- package/dist/Menubar.d.ts +249 -0
- package/dist/Menubar.d.ts.map +1 -0
- package/dist/Menubar.js +207 -0
- package/dist/Meter.d.ts +157 -0
- package/dist/Meter.d.ts.map +1 -0
- package/dist/Meter.js +87 -0
- package/dist/NativeDetails.d.ts +41 -0
- package/dist/NativeDetails.d.ts.map +1 -0
- package/dist/NativeDetails.js +40 -0
- package/dist/NativeDialog.d.ts +66 -0
- package/dist/NativeDialog.d.ts.map +1 -0
- package/dist/NativeDialog.js +88 -0
- package/dist/NativePopover.d.ts +43 -0
- package/dist/NativePopover.d.ts.map +1 -0
- package/dist/NativePopover.js +84 -0
- package/dist/Popover.d.ts +240 -0
- package/dist/Popover.d.ts.map +1 -0
- package/dist/Popover.js +140 -0
- package/dist/RadioGroup.d.ts +330 -0
- package/dist/RadioGroup.d.ts.map +1 -0
- package/dist/RadioGroup.js +241 -0
- package/dist/Role.d.ts +64 -0
- package/dist/Role.d.ts.map +1 -0
- package/dist/Role.js +27 -0
- package/dist/Select.d.ts +418 -0
- package/dist/Select.d.ts.map +1 -0
- package/dist/Select.js +357 -0
- package/dist/Separator.d.ts +58 -0
- package/dist/Separator.d.ts.map +1 -0
- package/dist/Separator.js +32 -0
- package/dist/Slider.d.ts +141 -0
- package/dist/Slider.d.ts.map +1 -0
- package/dist/Slider.js +101 -0
- package/dist/SpinButton.d.ts +141 -0
- package/dist/SpinButton.d.ts.map +1 -0
- package/dist/SpinButton.js +101 -0
- package/dist/Storybook.d.ts +76 -0
- package/dist/Storybook.d.ts.map +1 -0
- package/dist/Storybook.js +102 -0
- package/dist/Switch.d.ts +148 -0
- package/dist/Switch.d.ts.map +1 -0
- package/dist/Switch.js +110 -0
- package/dist/Tab.d.ts +26 -0
- package/dist/Tab.d.ts.map +1 -0
- package/dist/Tab.js +25 -0
- package/dist/Tabs.d.ts +411 -0
- package/dist/Tabs.d.ts.map +1 -0
- package/dist/Tabs.js +262 -0
- package/dist/Toolbar.d.ts +248 -0
- package/dist/Toolbar.d.ts.map +1 -0
- package/dist/Toolbar.js +187 -0
- package/dist/Tooltip.d.ts +296 -0
- package/dist/Tooltip.d.ts.map +1 -0
- package/dist/Tooltip.js +172 -0
- package/dist/Tree.d.ts +405 -0
- package/dist/Tree.d.ts.map +1 -0
- package/dist/Tree.js +333 -0
- package/dist/TreeGrid.d.ts +426 -0
- package/dist/TreeGrid.d.ts.map +1 -0
- package/dist/TreeGrid.js +308 -0
- package/dist/VisuallyHidden.d.ts +68 -0
- package/dist/VisuallyHidden.d.ts.map +1 -0
- package/dist/VisuallyHidden.js +44 -0
- package/dist/WindowSplitter.d.ts +336 -0
- package/dist/WindowSplitter.d.ts.map +1 -0
- package/dist/WindowSplitter.js +305 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -0
- package/package.json +47 -14
- package/src/HttpRouter.test.ts +0 -294
- package/src/HttpRouter.ts +0 -168
- package/src/Link.test.ts +0 -85
- package/src/Link.ts +0 -107
- package/src/index.ts +0 -2
- package/tsconfig.json +0 -6
package/dist/Listbox.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Listbox separates active and selected values, registers options in a Collection, and combines
|
|
3
|
+
* vertical keyboard movement with buffered typeahead. It supports roving focus by default and uses
|
|
4
|
+
* real focus, keydown, and click events.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* The module keeps policy, state transitions, and DOM rendering separable so applications can use
|
|
8
|
+
* the state and pure operations without mounting UI, or supply custom hosts without replacing native
|
|
9
|
+
* events and browser-owned focus.
|
|
10
|
+
*
|
|
11
|
+
* Learn the interaction in the [Listbox guide](/explore/ui-listbox).
|
|
12
|
+
*
|
|
13
|
+
* @since 1.0.0
|
|
14
|
+
* @category modules
|
|
15
|
+
* @packageDocumentation
|
|
16
|
+
*/
|
|
17
|
+
import * as Effect from "effect/Effect";
|
|
18
|
+
import * as Schema from "effect/Schema";
|
|
19
|
+
import { RefSubject } from "@typed/fx";
|
|
20
|
+
import { EventHandler, html, } from "@typed/template";
|
|
21
|
+
import * as Collection from "./Collection.js";
|
|
22
|
+
import * as Composite from "./Composite.js";
|
|
23
|
+
import * as Dom from "./Dom.js";
|
|
24
|
+
/**
|
|
25
|
+
* Effect Schema used by makeState to encode, decode, and hydrate Listbox state.
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import * as Schema from "effect/Schema";
|
|
31
|
+
* import * as Listbox from "@typed/ui/Listbox";
|
|
32
|
+
*
|
|
33
|
+
* const decodeState = Schema.decodeUnknownEffect(Listbox.StateSchema);
|
|
34
|
+
* ```
|
|
35
|
+
* @since 1.0.0
|
|
36
|
+
* @category Selection and focus
|
|
37
|
+
*/
|
|
38
|
+
export const StateSchema = Schema.Struct({
|
|
39
|
+
value: Schema.NullOr(Schema.String),
|
|
40
|
+
activeId: Schema.NullOr(Schema.String),
|
|
41
|
+
orientation: Schema.Literals(["vertical"]),
|
|
42
|
+
loop: Schema.Boolean,
|
|
43
|
+
rtl: Schema.Boolean,
|
|
44
|
+
virtualFocus: Schema.Boolean,
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* Creates hydrated Listbox state. value and activeId default to null, loop defaults to true, and
|
|
48
|
+
* roving focus is used.
|
|
49
|
+
*
|
|
50
|
+
* @remarks
|
|
51
|
+
* The returned Effect creates the RefSubject when run. That state is renderer-independent;
|
|
52
|
+
* collection registrations belong to the separate Scope that runs register or ref, not to state
|
|
53
|
+
* creation.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import * as Effect from "effect/Effect";
|
|
58
|
+
* import * as Listbox from "@typed/ui/Listbox";
|
|
59
|
+
*
|
|
60
|
+
* const program = Effect.scoped(
|
|
61
|
+
* Effect.gen(function* () {
|
|
62
|
+
* const state = yield* Listbox.makeState({});
|
|
63
|
+
* const collection = yield* Listbox.makeCollection();
|
|
64
|
+
* return { state: yield* state, collection: yield* collection };
|
|
65
|
+
* }),
|
|
66
|
+
* );
|
|
67
|
+
* ```
|
|
68
|
+
* @since 1.0.0
|
|
69
|
+
* @category Selection and focus
|
|
70
|
+
*/
|
|
71
|
+
export function makeState(initial = {}) {
|
|
72
|
+
return RefSubject.hydrate(StateSchema, {
|
|
73
|
+
value: initial.value ?? null,
|
|
74
|
+
activeId: initial.activeId ?? null,
|
|
75
|
+
orientation: "vertical",
|
|
76
|
+
loop: initial.loop ?? true,
|
|
77
|
+
rtl: false,
|
|
78
|
+
virtualFocus: false,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Creates a scoped Collection for Listbox items.
|
|
83
|
+
*
|
|
84
|
+
* @remarks
|
|
85
|
+
* The returned Effect allocates the RefSubject in the caller's Scope. Each later registration is
|
|
86
|
+
* owned by the Scope that runs register, independently of this construction Effect.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* import * as Effect from "effect/Effect";
|
|
91
|
+
* import * as Listbox from "@typed/ui/Listbox";
|
|
92
|
+
*
|
|
93
|
+
* const program = Effect.scoped(
|
|
94
|
+
* Effect.gen(function* () {
|
|
95
|
+
* const collection = yield* Listbox.makeCollection();
|
|
96
|
+
* return yield* collection;
|
|
97
|
+
* }),
|
|
98
|
+
* );
|
|
99
|
+
* ```
|
|
100
|
+
* @since 1.0.0
|
|
101
|
+
* @category Option registration
|
|
102
|
+
*/
|
|
103
|
+
export const makeCollection = Collection.makeState;
|
|
104
|
+
/**
|
|
105
|
+
* Sets both activeId and selected value in one update.
|
|
106
|
+
*
|
|
107
|
+
* @remarks
|
|
108
|
+
* The operation exposes Listbox's transition directly so callers can compose it in Effect programs
|
|
109
|
+
* and native event handlers.
|
|
110
|
+
*
|
|
111
|
+
* @since 1.0.0
|
|
112
|
+
* @category Selection and focus
|
|
113
|
+
*/
|
|
114
|
+
export function select(state, id, value) {
|
|
115
|
+
return RefSubject.update(state, (current) => ({ ...current, activeId: id, value }));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Moves through the registered options, synchronizes value when the target has one, then focuses
|
|
119
|
+
* and scrolls the active option.
|
|
120
|
+
*
|
|
121
|
+
* @remarks
|
|
122
|
+
* The operation exposes Listbox's transition directly so callers can compose it in Effect programs
|
|
123
|
+
* and native event handlers.
|
|
124
|
+
*
|
|
125
|
+
* @since 1.0.0
|
|
126
|
+
* @category Selection and focus
|
|
127
|
+
*/
|
|
128
|
+
export function move(state, collection, direction) {
|
|
129
|
+
return Effect.gen(function* () {
|
|
130
|
+
const current = yield* state;
|
|
131
|
+
const items = yield* collection;
|
|
132
|
+
const activeId = Composite.moveActiveId(items, current, direction);
|
|
133
|
+
const item = activeId === null ? undefined : items.find((item) => item.id === activeId);
|
|
134
|
+
const next = yield* RefSubject.update(state, (value) => ({
|
|
135
|
+
...value,
|
|
136
|
+
activeId,
|
|
137
|
+
value: item?.value ?? value.value,
|
|
138
|
+
}));
|
|
139
|
+
yield* Composite.focusActive({ state, collection });
|
|
140
|
+
yield* Composite.scrollActive({ state, collection });
|
|
141
|
+
return next;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function rootProps(options) {
|
|
145
|
+
let typeahead = { value: "", updatedAt: 0 };
|
|
146
|
+
const onfocus = options.collection === undefined
|
|
147
|
+
? undefined
|
|
148
|
+
: Effect.gen(function* () {
|
|
149
|
+
const current = yield* options.state;
|
|
150
|
+
if (current.activeId !== null)
|
|
151
|
+
return;
|
|
152
|
+
const item = Composite.moveActiveItem(yield* options.collection, current, "first");
|
|
153
|
+
if (item?.value === undefined)
|
|
154
|
+
return;
|
|
155
|
+
yield* select(options.state, item.id, item.value);
|
|
156
|
+
yield* Composite.focusActive({ state: options.state, collection: options.collection });
|
|
157
|
+
yield* Composite.scrollActive({ state: options.state, collection: options.collection });
|
|
158
|
+
});
|
|
159
|
+
const onkeydown = options.collection === undefined
|
|
160
|
+
? undefined
|
|
161
|
+
: EventHandler.make(Effect.fn(function* (event) {
|
|
162
|
+
const key = Composite.typeaheadKey(event);
|
|
163
|
+
if (key !== null) {
|
|
164
|
+
typeahead = Composite.updateTypeaheadBuffer(typeahead, key, Date.now());
|
|
165
|
+
const id = Composite.typeaheadFrom(yield* options.collection, typeahead.value, (yield* options.state).activeId);
|
|
166
|
+
if (id !== null) {
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
const item = (yield* options.collection).find((item) => item.id === id);
|
|
169
|
+
if (item?.value !== undefined)
|
|
170
|
+
yield* select(options.state, item.id, item.value);
|
|
171
|
+
yield* Composite.focusActive({
|
|
172
|
+
state: options.state,
|
|
173
|
+
collection: options.collection,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const direction = Composite.keyMove(event, { orientation: "vertical" });
|
|
179
|
+
if (direction !== undefined) {
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
yield* move(options.state, options.collection, direction);
|
|
182
|
+
}
|
|
183
|
+
}));
|
|
184
|
+
return ({ property }) => ({
|
|
185
|
+
role: "listbox",
|
|
186
|
+
"aria-label": property("label", undefined),
|
|
187
|
+
"aria-activedescendant": Composite.activeDescendant(options.state),
|
|
188
|
+
tabindex: Composite.rootTabIndex(options.state),
|
|
189
|
+
onfocus,
|
|
190
|
+
onkeydown,
|
|
191
|
+
ref: options.state,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Renders the listbox root, initializes selection on focus, and handles vertical movement and
|
|
196
|
+
* typeahead in DOM order.
|
|
197
|
+
*
|
|
198
|
+
* @remarks
|
|
199
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
200
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
201
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
202
|
+
*
|
|
203
|
+
* @since 1.0.0
|
|
204
|
+
* @category Listbox surface
|
|
205
|
+
*/
|
|
206
|
+
export function Root(options, host) {
|
|
207
|
+
return Dom.renderHost()(options, host, rootProps(options), options.content, (props, content) => {
|
|
208
|
+
return html `<div ...${props}>${content}</div>`;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function optionProps(options) {
|
|
212
|
+
const selected = RefSubject.map(options.state, (state) => state.value === options.value);
|
|
213
|
+
const register = options.collection === undefined
|
|
214
|
+
? undefined
|
|
215
|
+
: Collection.ref(options.collection, {
|
|
216
|
+
id: options.id,
|
|
217
|
+
value: options.value,
|
|
218
|
+
textValue: options.textValue ?? options.value,
|
|
219
|
+
disabled: options.disabled,
|
|
220
|
+
});
|
|
221
|
+
return () => ({
|
|
222
|
+
id: options.id,
|
|
223
|
+
role: "option",
|
|
224
|
+
"aria-selected": selected,
|
|
225
|
+
"aria-disabled": options.disabled ?? false,
|
|
226
|
+
tabindex: Composite.tabIndex(options.state, options.id),
|
|
227
|
+
onclick: options.disabled === true ? Effect.void : select(options.state, options.id, options.value),
|
|
228
|
+
onfocus: options.disabled === true ? Effect.void : select(options.state, options.id, options.value),
|
|
229
|
+
ref: Dom.composeRefs(register, options.ref),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Renders and optionally registers an option; focus or click selects it unless disabled.
|
|
234
|
+
*
|
|
235
|
+
* @remarks
|
|
236
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
237
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
238
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
239
|
+
*
|
|
240
|
+
* @since 1.0.0
|
|
241
|
+
* @category Selectable options
|
|
242
|
+
*/
|
|
243
|
+
export function Option(options, host) {
|
|
244
|
+
return Dom.renderHost()(options, host, optionProps(options), options.content, (props, content) => html `<div ...${props}>${content}</div>`);
|
|
245
|
+
}
|