@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/Grid.d.ts
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid is a virtual-focus ARIA grid. The root retains DOM focus while the active cell id is
|
|
3
|
+
* exposed through aria-activedescendant; CellPosition supplies row and column coordinates for
|
|
4
|
+
* two-dimensional movement.
|
|
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 [Grid guide](/explore/ui-grid).
|
|
12
|
+
*
|
|
13
|
+
* @since 1.0.0
|
|
14
|
+
* @category modules
|
|
15
|
+
* @packageDocumentation
|
|
16
|
+
*/
|
|
17
|
+
import * as Effect from "effect/Effect";
|
|
18
|
+
import type * as Scope from "effect/Scope";
|
|
19
|
+
import * as Schema from "effect/Schema";
|
|
20
|
+
import type { Fx } from "@typed/fx/Fx";
|
|
21
|
+
import { RefSubject } from "@typed/fx";
|
|
22
|
+
import { EventHandler, type Renderable, type RenderEvent, type RenderTemplate } from "@typed/template";
|
|
23
|
+
import * as Collection from "./Collection.js";
|
|
24
|
+
import * as Dom from "./Dom.js";
|
|
25
|
+
import type { HostResult } from "./Dom/Types.js";
|
|
26
|
+
/**
|
|
27
|
+
* The cell ID exposed by the root through `aria-activedescendant`.
|
|
28
|
+
* Native focus stays on the root; selected cells and editing state are not stored here.
|
|
29
|
+
*
|
|
30
|
+
* @since 1.0.0
|
|
31
|
+
* @category Cell focus
|
|
32
|
+
*/
|
|
33
|
+
export interface State {
|
|
34
|
+
/**
|
|
35
|
+
* Id currently active for keyboard navigation; null means no active item.
|
|
36
|
+
* @since 1.0.0
|
|
37
|
+
* @category Keyboard focus
|
|
38
|
+
*/
|
|
39
|
+
readonly activeId: string | null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Initial Grid values. activeId defaults to null.
|
|
43
|
+
*
|
|
44
|
+
* @since 1.0.0
|
|
45
|
+
* @category Cell focus
|
|
46
|
+
*/
|
|
47
|
+
export interface InitialState {
|
|
48
|
+
/**
|
|
49
|
+
* Id currently active for keyboard navigation; null means no active item.
|
|
50
|
+
* @since 1.0.0
|
|
51
|
+
* @category Keyboard focus
|
|
52
|
+
*/
|
|
53
|
+
readonly activeId?: string | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Effect Schema used by makeState to encode, decode, and hydrate Grid state.
|
|
57
|
+
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* import * as Schema from "effect/Schema";
|
|
62
|
+
* import * as Grid from "@typed/ui/Grid";
|
|
63
|
+
*
|
|
64
|
+
* const decodeState = Schema.decodeUnknownEffect(Grid.StateSchema);
|
|
65
|
+
* ```
|
|
66
|
+
* @since 1.0.0
|
|
67
|
+
* @category Cell focus
|
|
68
|
+
*/
|
|
69
|
+
export declare const StateSchema: Schema.Struct<{
|
|
70
|
+
readonly activeId: Schema.NullOr<Schema.String>;
|
|
71
|
+
}>;
|
|
72
|
+
/**
|
|
73
|
+
* Creates hydrated Grid state. activeId defaults to null.
|
|
74
|
+
*
|
|
75
|
+
* @remarks
|
|
76
|
+
* The returned Effect creates the RefSubject when run. That state is renderer-independent;
|
|
77
|
+
* collection registrations belong to the separate Scope that runs register or ref, not to state
|
|
78
|
+
* creation.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* import * as Effect from "effect/Effect";
|
|
83
|
+
* import * as Grid from "@typed/ui/Grid";
|
|
84
|
+
*
|
|
85
|
+
* const program = Effect.scoped(
|
|
86
|
+
* Effect.gen(function* () {
|
|
87
|
+
* const state = yield* Grid.makeState({});
|
|
88
|
+
* const collection = yield* Grid.makeCollection();
|
|
89
|
+
* return { state: yield* state, collection: yield* collection };
|
|
90
|
+
* }),
|
|
91
|
+
* );
|
|
92
|
+
* ```
|
|
93
|
+
* @since 1.0.0
|
|
94
|
+
* @category Cell focus
|
|
95
|
+
*/
|
|
96
|
+
export declare function makeState(initial?: InitialState): Effect.Effect<RefSubject.HydratedRefSubject<{
|
|
97
|
+
readonly activeId: string | null;
|
|
98
|
+
}, Schema.SchemaError, never, never>, never, Scope.Scope>;
|
|
99
|
+
/**
|
|
100
|
+
* Logical coordinates stored with each registered Grid cell.
|
|
101
|
+
*
|
|
102
|
+
* @remarks
|
|
103
|
+
* The public model lets custom composites reuse Grid's deterministic policy without copying an
|
|
104
|
+
* internal shape.
|
|
105
|
+
*
|
|
106
|
+
* @since 1.0.0
|
|
107
|
+
* @category Cell registration
|
|
108
|
+
*/
|
|
109
|
+
export interface CellPosition {
|
|
110
|
+
/**
|
|
111
|
+
* Stable logical row identity used by vertical grid movement.
|
|
112
|
+
* @since 1.0.0
|
|
113
|
+
* @category Row identity
|
|
114
|
+
*/
|
|
115
|
+
readonly rowId: string;
|
|
116
|
+
/**
|
|
117
|
+
* Caller-supplied column index used for movement and emitted unchanged as aria-colindex; ARIA
|
|
118
|
+
* indexes are one-based.
|
|
119
|
+
* @since 1.0.0
|
|
120
|
+
* @category Cell position
|
|
121
|
+
*/
|
|
122
|
+
readonly columnIndex: number;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Creates a scoped Collection for Grid items.
|
|
126
|
+
*
|
|
127
|
+
* @remarks
|
|
128
|
+
* The returned Effect allocates the RefSubject in the caller's Scope. Each later registration is
|
|
129
|
+
* owned by the Scope that runs register, independently of this construction Effect.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* import * as Effect from "effect/Effect";
|
|
134
|
+
* import * as Grid from "@typed/ui/Grid";
|
|
135
|
+
*
|
|
136
|
+
* const program = Effect.scoped(
|
|
137
|
+
* Effect.gen(function* () {
|
|
138
|
+
* const collection = yield* Grid.makeCollection();
|
|
139
|
+
* return yield* collection;
|
|
140
|
+
* }),
|
|
141
|
+
* );
|
|
142
|
+
* ```
|
|
143
|
+
* @since 1.0.0
|
|
144
|
+
* @category Cell registration
|
|
145
|
+
*/
|
|
146
|
+
export declare const makeCollection: (initial?: Collection.State<CellPosition, Element>) => Effect.Effect<RefSubject.RefSubject<Collection.State<CellPosition, Element>, never, never>, never, Scope.Scope>;
|
|
147
|
+
/**
|
|
148
|
+
* Sets activeId, including null to clear virtual focus.
|
|
149
|
+
*
|
|
150
|
+
* @remarks
|
|
151
|
+
* The operation exposes Grid's transition directly so callers can compose it in Effect programs
|
|
152
|
+
* and native event handlers.
|
|
153
|
+
*
|
|
154
|
+
* @since 1.0.0
|
|
155
|
+
* @category Cell focus
|
|
156
|
+
*/
|
|
157
|
+
export declare function activate<E, R>(state: RefSubject.RefSubject<State, E, R>, activeId: string | null): Effect.Effect<State, E, R>;
|
|
158
|
+
/**
|
|
159
|
+
* Inputs accepted by Grid.Root in addition to the shared DOM host options.
|
|
160
|
+
*
|
|
161
|
+
* @since 1.0.0
|
|
162
|
+
* @category Grid surface
|
|
163
|
+
*/
|
|
164
|
+
export interface RootOptions extends Dom.HostOptions<HTMLDivElement> {
|
|
165
|
+
/**
|
|
166
|
+
* Renderer-independent RefSubject state consumed by this component or operation.
|
|
167
|
+
* @since 1.0.0
|
|
168
|
+
* @category State connection
|
|
169
|
+
*/
|
|
170
|
+
readonly state: RefSubject.HydratedRefSubject<State, Schema.SchemaError>;
|
|
171
|
+
/**
|
|
172
|
+
* Item registry used for collection-driven keyboard behavior and mounted ordering.
|
|
173
|
+
* @since 1.0.0
|
|
174
|
+
* @category Item registration
|
|
175
|
+
*/
|
|
176
|
+
readonly collection?: RefSubject.RefSubject<Collection.State<CellPosition>>;
|
|
177
|
+
/**
|
|
178
|
+
* Accessible label rendered through aria-label.
|
|
179
|
+
* @since 1.0.0
|
|
180
|
+
* @category Accessible naming
|
|
181
|
+
*/
|
|
182
|
+
readonly label: Renderable.Any<string | null | undefined>;
|
|
183
|
+
/**
|
|
184
|
+
* Renderable child content for the component host.
|
|
185
|
+
* @since 1.0.0
|
|
186
|
+
* @category Rendered content
|
|
187
|
+
*/
|
|
188
|
+
readonly content: Renderable.Any;
|
|
189
|
+
/**
|
|
190
|
+
* Whether aria-multiselectable is announced on the grid.
|
|
191
|
+
* @since 1.0.0
|
|
192
|
+
* @category Selection state
|
|
193
|
+
*/
|
|
194
|
+
readonly multiselectable?: Renderable.Any<boolean | null | undefined>;
|
|
195
|
+
}
|
|
196
|
+
declare function rootInternalProps<const Options extends RootOptions>(options: Options): ({ property }: Dom.InternalPropsHelpers<Options>) => {
|
|
197
|
+
readonly role: "grid";
|
|
198
|
+
readonly tabindex: 0;
|
|
199
|
+
readonly "aria-label": Exclude<Dom.Property<Options, "label">, Dom.Nullish> | undefined;
|
|
200
|
+
readonly "aria-activedescendant": RefSubject.Computed<string | undefined, Schema.SchemaError, never>;
|
|
201
|
+
readonly "aria-multiselectable": false | Exclude<Dom.Property<Options, "multiselectable">, Dom.Nullish>;
|
|
202
|
+
readonly onfocus: Effect.Effect<void, Schema.SchemaError, never> | undefined;
|
|
203
|
+
readonly onkeydown: EventHandler.EventHandler<KeyboardEvent, Schema.SchemaError, never> | undefined;
|
|
204
|
+
readonly ref: RefSubject.HydratedRefSubject<State, Schema.SchemaError, never, never>;
|
|
205
|
+
};
|
|
206
|
+
type RootInternalProps<Options extends RootOptions> = ReturnType<ReturnType<typeof rootInternalProps<Options>>>;
|
|
207
|
+
/**
|
|
208
|
+
* Renders the focus-owning grid root and initializes the active cell from DOM order on first
|
|
209
|
+
* focus.
|
|
210
|
+
*
|
|
211
|
+
* @remarks
|
|
212
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
213
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
214
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
215
|
+
*
|
|
216
|
+
* @since 1.0.0
|
|
217
|
+
* @category Grid surface
|
|
218
|
+
*/
|
|
219
|
+
export declare function Root<const Options extends RootOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, RootInternalProps<Options>>, Options["content"], Host>): Fx<RenderEvent, Renderable.Error<Options | Host>, Renderable.Services<Options | Host> | Scope.Scope | RenderTemplate>;
|
|
220
|
+
/**
|
|
221
|
+
* Inputs accepted by Grid.Row in addition to the shared DOM host options.
|
|
222
|
+
*
|
|
223
|
+
* @since 1.0.0
|
|
224
|
+
* @category Rows
|
|
225
|
+
*/
|
|
226
|
+
export interface RowOptions extends Dom.HostOptions<HTMLDivElement> {
|
|
227
|
+
/**
|
|
228
|
+
* Renderable child content for the component host.
|
|
229
|
+
* @since 1.0.0
|
|
230
|
+
* @category Rendered content
|
|
231
|
+
*/
|
|
232
|
+
readonly content: Renderable.Any;
|
|
233
|
+
/**
|
|
234
|
+
* Optional one-based aria-rowindex value supplied by the caller.
|
|
235
|
+
* @since 1.0.0
|
|
236
|
+
* @category Cell position
|
|
237
|
+
*/
|
|
238
|
+
readonly rowIndex?: Renderable.Any<number | null | undefined>;
|
|
239
|
+
}
|
|
240
|
+
declare function rowInternalProps<const Options extends RowOptions>({ property, }: Dom.InternalPropsHelpers<Options>): {
|
|
241
|
+
readonly role: "row";
|
|
242
|
+
readonly "aria-rowindex": Exclude<Dom.Property<Options, "rowIndex">, Dom.Nullish> | undefined;
|
|
243
|
+
};
|
|
244
|
+
type RowInternalProps<Options extends RowOptions> = ReturnType<typeof rowInternalProps<Options>>;
|
|
245
|
+
/**
|
|
246
|
+
* Renders an ARIA row and forwards an optional one-based aria-rowindex.
|
|
247
|
+
*
|
|
248
|
+
* @remarks
|
|
249
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
250
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
251
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* ```ts
|
|
255
|
+
* import * as Grid from "@typed/ui/Grid";
|
|
256
|
+
*
|
|
257
|
+
* const view = Grid.Row({ rowIndex: 1, content: "Cells" });
|
|
258
|
+
* ```
|
|
259
|
+
* @since 1.0.0
|
|
260
|
+
* @category Rows
|
|
261
|
+
*/
|
|
262
|
+
export declare function Row<const Options extends RowOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, RowInternalProps<Options>>, Options["content"], Host>): Fx<RenderEvent, Renderable.Error<Options | Host>, Renderable.Services<Options | Host> | Scope.Scope | RenderTemplate>;
|
|
263
|
+
/**
|
|
264
|
+
* Inputs accepted by Grid.Cell in addition to the shared DOM host options.
|
|
265
|
+
*
|
|
266
|
+
* @since 1.0.0
|
|
267
|
+
* @category Cells and headers
|
|
268
|
+
*/
|
|
269
|
+
export interface CellOptions extends Dom.HostOptions<HTMLDivElement> {
|
|
270
|
+
/**
|
|
271
|
+
* Renderer-independent RefSubject state consumed by this component or operation.
|
|
272
|
+
* @since 1.0.0
|
|
273
|
+
* @category State connection
|
|
274
|
+
*/
|
|
275
|
+
readonly state: RefSubject.HydratedRefSubject<State, Schema.SchemaError>;
|
|
276
|
+
/**
|
|
277
|
+
* Item registry used for collection-driven keyboard behavior and mounted ordering.
|
|
278
|
+
* @since 1.0.0
|
|
279
|
+
* @category Item registration
|
|
280
|
+
*/
|
|
281
|
+
readonly collection?: RefSubject.RefSubject<Collection.State<CellPosition>>;
|
|
282
|
+
/**
|
|
283
|
+
* Stable id used for collection identity and ARIA relationships.
|
|
284
|
+
* @since 1.0.0
|
|
285
|
+
* @category Identity and relationships
|
|
286
|
+
*/
|
|
287
|
+
readonly id: string;
|
|
288
|
+
/**
|
|
289
|
+
* Stable logical row identity used by vertical grid movement.
|
|
290
|
+
* @since 1.0.0
|
|
291
|
+
* @category Row identity
|
|
292
|
+
*/
|
|
293
|
+
readonly rowId: string;
|
|
294
|
+
/**
|
|
295
|
+
* Caller-supplied column index used for movement and emitted unchanged as aria-colindex; ARIA
|
|
296
|
+
* indexes are one-based.
|
|
297
|
+
* @since 1.0.0
|
|
298
|
+
* @category Cell position
|
|
299
|
+
*/
|
|
300
|
+
readonly columnIndex: number;
|
|
301
|
+
/**
|
|
302
|
+
* Optional one-based aria-rowindex value supplied by the caller.
|
|
303
|
+
* @since 1.0.0
|
|
304
|
+
* @category Cell position
|
|
305
|
+
*/
|
|
306
|
+
readonly rowIndex?: number;
|
|
307
|
+
/**
|
|
308
|
+
* Optional selected state exposed through aria-selected.
|
|
309
|
+
* @since 1.0.0
|
|
310
|
+
* @category Selection state
|
|
311
|
+
*/
|
|
312
|
+
readonly selected?: Renderable.Any<boolean | null | undefined>;
|
|
313
|
+
/**
|
|
314
|
+
* Renderable child content for the component host.
|
|
315
|
+
* @since 1.0.0
|
|
316
|
+
* @category Rendered content
|
|
317
|
+
*/
|
|
318
|
+
readonly content: Renderable.Any;
|
|
319
|
+
}
|
|
320
|
+
declare function cellInternalProps<const Options extends CellOptions>(options: Options, role: "gridcell" | "columnheader" | "rowheader"): ({ property }: Dom.InternalPropsHelpers<Options>) => {
|
|
321
|
+
readonly id: string;
|
|
322
|
+
readonly role: "columnheader" | "gridcell" | "rowheader";
|
|
323
|
+
readonly "aria-colindex": number;
|
|
324
|
+
readonly "aria-rowindex": Exclude<Dom.Property<Options, "rowIndex">, Dom.Nullish> | undefined;
|
|
325
|
+
readonly "aria-selected": Exclude<Dom.Property<Options, "selected">, Dom.Nullish> | undefined;
|
|
326
|
+
readonly "?data-active": RefSubject.Computed<boolean, Schema.SchemaError, never>;
|
|
327
|
+
readonly ref: ((element: Element) => Effect.Effect<void, never, Scope.Scope>) | undefined;
|
|
328
|
+
};
|
|
329
|
+
type CellInternalProps<Options extends CellOptions> = ReturnType<ReturnType<typeof cellInternalProps<Options>>>;
|
|
330
|
+
/**
|
|
331
|
+
* Renders and optionally registers a gridcell with row and column coordinates.
|
|
332
|
+
*
|
|
333
|
+
* @remarks
|
|
334
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
335
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
336
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
337
|
+
*
|
|
338
|
+
* @since 1.0.0
|
|
339
|
+
* @category Cells and headers
|
|
340
|
+
*/
|
|
341
|
+
export declare function Cell<const Options extends CellOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, CellInternalProps<Options>>, Options["content"], Host>): import("./Dom/Types.js").HostComponent<Host | Options>;
|
|
342
|
+
/**
|
|
343
|
+
* Renders the Cell contract with the columnheader role.
|
|
344
|
+
*
|
|
345
|
+
* @remarks
|
|
346
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
347
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
348
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
349
|
+
*
|
|
350
|
+
* @since 1.0.0
|
|
351
|
+
* @category Cells and headers
|
|
352
|
+
*/
|
|
353
|
+
export declare function ColumnHeader<const Options extends CellOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, CellInternalProps<Options>>, Options["content"], Host>): import("./Dom/Types.js").HostComponent<Host | Options>;
|
|
354
|
+
/**
|
|
355
|
+
* Renders the Cell contract with the rowheader role.
|
|
356
|
+
*
|
|
357
|
+
* @remarks
|
|
358
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
359
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
360
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
361
|
+
*
|
|
362
|
+
* @since 1.0.0
|
|
363
|
+
* @category Cells and headers
|
|
364
|
+
*/
|
|
365
|
+
export declare function RowHeader<const Options extends CellOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, CellInternalProps<Options>>, Options["content"], Host>): import("./Dom/Types.js").HostComponent<Host | Options>;
|
|
366
|
+
/**
|
|
367
|
+
* Maps grid keys to a cell id: horizontal movement stays in-row, vertical movement stays
|
|
368
|
+
* in-column, and Ctrl+Home/End reaches grid endpoints.
|
|
369
|
+
*
|
|
370
|
+
* @remarks
|
|
371
|
+
* Separating this deterministic policy from event wiring lets applications test it directly and
|
|
372
|
+
* reuse it in custom composites.
|
|
373
|
+
*
|
|
374
|
+
* This is a synchronous calculation. It acquires no resources and does not mutate the input array,
|
|
375
|
+
* state, event, or DOM.
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* ```ts
|
|
379
|
+
* import * as Grid from "@typed/ui/Grid";
|
|
380
|
+
*
|
|
381
|
+
* const nextId = Grid.moveActiveId([{ id: "a", value: { rowId: "r1", columnIndex: 1 } }], "a", { key: "ArrowRight", ctrlKey: false });
|
|
382
|
+
* ```
|
|
383
|
+
* @since 1.0.0
|
|
384
|
+
* @category Spatial navigation
|
|
385
|
+
*/
|
|
386
|
+
export declare function moveActiveId(items: readonly Collection.Item<CellPosition>[], activeId: string | null, event: Pick<KeyboardEvent, "key" | "ctrlKey">): string | undefined;
|
|
387
|
+
export {};
|
|
388
|
+
//# sourceMappingURL=Grid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Grid.d.ts","sourceRoot":"","sources":["../src/Grid.ts"],"names":[],"mappings":"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"}
|
package/dist/Grid.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid is a virtual-focus ARIA grid. The root retains DOM focus while the active cell id is
|
|
3
|
+
* exposed through aria-activedescendant; CellPosition supplies row and column coordinates for
|
|
4
|
+
* two-dimensional movement.
|
|
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 [Grid guide](/explore/ui-grid).
|
|
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 Dom from "./Dom.js";
|
|
23
|
+
/**
|
|
24
|
+
* Effect Schema used by makeState to encode, decode, and hydrate Grid state.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* import * as Schema from "effect/Schema";
|
|
30
|
+
* import * as Grid from "@typed/ui/Grid";
|
|
31
|
+
*
|
|
32
|
+
* const decodeState = Schema.decodeUnknownEffect(Grid.StateSchema);
|
|
33
|
+
* ```
|
|
34
|
+
* @since 1.0.0
|
|
35
|
+
* @category Cell focus
|
|
36
|
+
*/
|
|
37
|
+
export const StateSchema = Schema.Struct({ activeId: Schema.NullOr(Schema.String) });
|
|
38
|
+
/**
|
|
39
|
+
* Creates hydrated Grid state. activeId defaults to null.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* The returned Effect creates the RefSubject when run. That state is renderer-independent;
|
|
43
|
+
* collection registrations belong to the separate Scope that runs register or ref, not to state
|
|
44
|
+
* creation.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import * as Effect from "effect/Effect";
|
|
49
|
+
* import * as Grid from "@typed/ui/Grid";
|
|
50
|
+
*
|
|
51
|
+
* const program = Effect.scoped(
|
|
52
|
+
* Effect.gen(function* () {
|
|
53
|
+
* const state = yield* Grid.makeState({});
|
|
54
|
+
* const collection = yield* Grid.makeCollection();
|
|
55
|
+
* return { state: yield* state, collection: yield* collection };
|
|
56
|
+
* }),
|
|
57
|
+
* );
|
|
58
|
+
* ```
|
|
59
|
+
* @since 1.0.0
|
|
60
|
+
* @category Cell focus
|
|
61
|
+
*/
|
|
62
|
+
export function makeState(initial = {}) {
|
|
63
|
+
return RefSubject.hydrate(StateSchema, { activeId: initial.activeId ?? null });
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Creates a scoped Collection for Grid items.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* The returned Effect allocates the RefSubject in the caller's Scope. Each later registration is
|
|
70
|
+
* owned by the Scope that runs register, independently of this construction Effect.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* import * as Effect from "effect/Effect";
|
|
75
|
+
* import * as Grid from "@typed/ui/Grid";
|
|
76
|
+
*
|
|
77
|
+
* const program = Effect.scoped(
|
|
78
|
+
* Effect.gen(function* () {
|
|
79
|
+
* const collection = yield* Grid.makeCollection();
|
|
80
|
+
* return yield* collection;
|
|
81
|
+
* }),
|
|
82
|
+
* );
|
|
83
|
+
* ```
|
|
84
|
+
* @since 1.0.0
|
|
85
|
+
* @category Cell registration
|
|
86
|
+
*/
|
|
87
|
+
export const makeCollection = Collection.makeState;
|
|
88
|
+
/**
|
|
89
|
+
* Sets activeId, including null to clear virtual focus.
|
|
90
|
+
*
|
|
91
|
+
* @remarks
|
|
92
|
+
* The operation exposes Grid's transition directly so callers can compose it in Effect programs
|
|
93
|
+
* and native event handlers.
|
|
94
|
+
*
|
|
95
|
+
* @since 1.0.0
|
|
96
|
+
* @category Cell focus
|
|
97
|
+
*/
|
|
98
|
+
export function activate(state, activeId) {
|
|
99
|
+
return RefSubject.update(state, (current) => ({ ...current, activeId }));
|
|
100
|
+
}
|
|
101
|
+
function rootInternalProps(options) {
|
|
102
|
+
const onfocus = options.collection === undefined
|
|
103
|
+
? undefined
|
|
104
|
+
: Effect.gen(function* () {
|
|
105
|
+
if ((yield* options.state).activeId !== null)
|
|
106
|
+
return;
|
|
107
|
+
const first = Collection.byDomOrder(yield* options.collection)[0];
|
|
108
|
+
if (first !== undefined)
|
|
109
|
+
yield* activate(options.state, first.id);
|
|
110
|
+
});
|
|
111
|
+
const onkeydown = options.collection === undefined
|
|
112
|
+
? undefined
|
|
113
|
+
: EventHandler.make(Effect.fn((event) => onKeyDown(options.state, options.collection, event)));
|
|
114
|
+
return ({ property }) => ({
|
|
115
|
+
role: "grid",
|
|
116
|
+
tabindex: 0,
|
|
117
|
+
"aria-label": property("label", undefined),
|
|
118
|
+
"aria-activedescendant": RefSubject.map(options.state, (state) => state.activeId ?? undefined),
|
|
119
|
+
"aria-multiselectable": property("multiselectable", false),
|
|
120
|
+
onfocus,
|
|
121
|
+
onkeydown,
|
|
122
|
+
ref: options.state,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Renders the focus-owning grid root and initializes the active cell from DOM order on first
|
|
127
|
+
* focus.
|
|
128
|
+
*
|
|
129
|
+
* @remarks
|
|
130
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
131
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
132
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
133
|
+
*
|
|
134
|
+
* @since 1.0.0
|
|
135
|
+
* @category Grid surface
|
|
136
|
+
*/
|
|
137
|
+
export function Root(options, host) {
|
|
138
|
+
return Dom.renderHost()(options, host, rootInternalProps(options), options.content, (props, content) => {
|
|
139
|
+
return html `<div ...${props}>${content}</div>`;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function rowInternalProps({ property, }) {
|
|
143
|
+
return { role: "row", "aria-rowindex": property("rowIndex", undefined) };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Renders an ARIA row and forwards an optional one-based aria-rowindex.
|
|
147
|
+
*
|
|
148
|
+
* @remarks
|
|
149
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
150
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
151
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* import * as Grid from "@typed/ui/Grid";
|
|
156
|
+
*
|
|
157
|
+
* const view = Grid.Row({ rowIndex: 1, content: "Cells" });
|
|
158
|
+
* ```
|
|
159
|
+
* @since 1.0.0
|
|
160
|
+
* @category Rows
|
|
161
|
+
*/
|
|
162
|
+
export function Row(options, host) {
|
|
163
|
+
return Dom.renderHost()(options, host, rowInternalProps, options.content, (props, content) => html `<div ...${props}>${content}</div>`);
|
|
164
|
+
}
|
|
165
|
+
function cellInternalProps(options, role) {
|
|
166
|
+
const active = RefSubject.map(options.state, (state) => state.activeId === options.id);
|
|
167
|
+
const register = options.collection === undefined
|
|
168
|
+
? undefined
|
|
169
|
+
: Collection.ref(options.collection, {
|
|
170
|
+
id: options.id,
|
|
171
|
+
value: { rowId: options.rowId, columnIndex: options.columnIndex },
|
|
172
|
+
textValue: options.id,
|
|
173
|
+
});
|
|
174
|
+
return ({ property }) => ({
|
|
175
|
+
id: options.id,
|
|
176
|
+
role,
|
|
177
|
+
"aria-colindex": options.columnIndex,
|
|
178
|
+
"aria-rowindex": property("rowIndex", undefined),
|
|
179
|
+
"aria-selected": property("selected", undefined),
|
|
180
|
+
"?data-active": active,
|
|
181
|
+
ref: register,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function cell(options, host, role) {
|
|
185
|
+
return Dom.renderHost()(options, host, cellInternalProps(options, role), options.content, (props, content) => {
|
|
186
|
+
return html `<div ...${props}>${content}</div>`;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Renders and optionally registers a gridcell with row and column coordinates.
|
|
191
|
+
*
|
|
192
|
+
* @remarks
|
|
193
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
194
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
195
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
196
|
+
*
|
|
197
|
+
* @since 1.0.0
|
|
198
|
+
* @category Cells and headers
|
|
199
|
+
*/
|
|
200
|
+
export function Cell(options, host) {
|
|
201
|
+
return cell(options, host, "gridcell");
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Renders the Cell contract with the columnheader role.
|
|
205
|
+
*
|
|
206
|
+
* @remarks
|
|
207
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
208
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
209
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
210
|
+
*
|
|
211
|
+
* @since 1.0.0
|
|
212
|
+
* @category Cells and headers
|
|
213
|
+
*/
|
|
214
|
+
export function ColumnHeader(options, host) {
|
|
215
|
+
return cell(options, host, "columnheader");
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Renders the Cell contract with the rowheader role.
|
|
219
|
+
*
|
|
220
|
+
* @remarks
|
|
221
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
222
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
223
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
224
|
+
*
|
|
225
|
+
* @since 1.0.0
|
|
226
|
+
* @category Cells and headers
|
|
227
|
+
*/
|
|
228
|
+
export function RowHeader(options, host) {
|
|
229
|
+
return cell(options, host, "rowheader");
|
|
230
|
+
}
|
|
231
|
+
function onKeyDown(state, collection, event) {
|
|
232
|
+
return Effect.gen(function* () {
|
|
233
|
+
const items = Collection.byDomOrder(yield* collection);
|
|
234
|
+
const next = moveActiveId(items, (yield* state).activeId, event);
|
|
235
|
+
if (next === undefined)
|
|
236
|
+
return yield* state;
|
|
237
|
+
event.preventDefault();
|
|
238
|
+
return yield* activate(state, next);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Maps grid keys to a cell id: horizontal movement stays in-row, vertical movement stays
|
|
243
|
+
* in-column, and Ctrl+Home/End reaches grid endpoints.
|
|
244
|
+
*
|
|
245
|
+
* @remarks
|
|
246
|
+
* Separating this deterministic policy from event wiring lets applications test it directly and
|
|
247
|
+
* reuse it in custom composites.
|
|
248
|
+
*
|
|
249
|
+
* This is a synchronous calculation. It acquires no resources and does not mutate the input array,
|
|
250
|
+
* state, event, or DOM.
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* ```ts
|
|
254
|
+
* import * as Grid from "@typed/ui/Grid";
|
|
255
|
+
*
|
|
256
|
+
* const nextId = Grid.moveActiveId([{ id: "a", value: { rowId: "r1", columnIndex: 1 } }], "a", { key: "ArrowRight", ctrlKey: false });
|
|
257
|
+
* ```
|
|
258
|
+
* @since 1.0.0
|
|
259
|
+
* @category Spatial navigation
|
|
260
|
+
*/
|
|
261
|
+
export function moveActiveId(items, activeId, event) {
|
|
262
|
+
if (items.length === 0)
|
|
263
|
+
return undefined;
|
|
264
|
+
if (event.ctrlKey && event.key === "Home")
|
|
265
|
+
return items[0]?.id;
|
|
266
|
+
if (event.ctrlKey && event.key === "End")
|
|
267
|
+
return items.at(-1)?.id;
|
|
268
|
+
const active = activeId === null ? undefined : items.find((item) => item.id === activeId);
|
|
269
|
+
if (active === undefined || active.value === undefined)
|
|
270
|
+
return event.key === "Home" ? items[0]?.id : undefined;
|
|
271
|
+
const row = items.filter((item) => item.value?.rowId === active.value.rowId);
|
|
272
|
+
if (event.key === "ArrowLeft")
|
|
273
|
+
return row.at(Math.max(0, row.indexOf(active) - 1))?.id;
|
|
274
|
+
if (event.key === "ArrowRight")
|
|
275
|
+
return row.at(Math.min(row.length - 1, row.indexOf(active) + 1))?.id;
|
|
276
|
+
if (event.key === "Home")
|
|
277
|
+
return row[0]?.id;
|
|
278
|
+
if (event.key === "End")
|
|
279
|
+
return row.at(-1)?.id;
|
|
280
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown")
|
|
281
|
+
return undefined;
|
|
282
|
+
const column = items.filter((item) => item.value?.columnIndex === active.value.columnIndex);
|
|
283
|
+
return column.at(column.indexOf(active) + (event.key === "ArrowUp" ? -1 : 1))?.id ?? active.id;
|
|
284
|
+
}
|