@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.
Files changed (156) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -26
  3. package/dist/Alert.d.ts +72 -0
  4. package/dist/Alert.d.ts.map +1 -0
  5. package/dist/Alert.js +40 -0
  6. package/dist/Button.d.ts +100 -0
  7. package/dist/Button.d.ts.map +1 -0
  8. package/dist/Button.js +42 -0
  9. package/dist/Carousel.d.ts +346 -0
  10. package/dist/Carousel.d.ts.map +1 -0
  11. package/dist/Carousel.js +264 -0
  12. package/dist/Checkbox.d.ts +168 -0
  13. package/dist/Checkbox.d.ts.map +1 -0
  14. package/dist/Checkbox.js +146 -0
  15. package/dist/Collection.d.ts +254 -0
  16. package/dist/Collection.d.ts.map +1 -0
  17. package/dist/Collection.js +218 -0
  18. package/dist/Combobox.d.ts +367 -0
  19. package/dist/Combobox.d.ts.map +1 -0
  20. package/dist/Combobox.js +300 -0
  21. package/dist/Composite.d.ts +823 -0
  22. package/dist/Composite.d.ts.map +1 -0
  23. package/dist/Composite.js +615 -0
  24. package/dist/Dialog.d.ts +544 -0
  25. package/dist/Dialog.d.ts.map +1 -0
  26. package/dist/Dialog.js +357 -0
  27. package/dist/Disclosure.d.ts +219 -0
  28. package/dist/Disclosure.d.ts.map +1 -0
  29. package/dist/Disclosure.js +128 -0
  30. package/dist/Dom/Events.d.ts +122 -0
  31. package/dist/Dom/Events.d.ts.map +1 -0
  32. package/dist/Dom/Events.js +192 -0
  33. package/dist/Dom/Props.d.ts +161 -0
  34. package/dist/Dom/Props.d.ts.map +1 -0
  35. package/dist/Dom/Props.js +110 -0
  36. package/dist/Dom/Refs.d.ts +58 -0
  37. package/dist/Dom/Refs.d.ts.map +1 -0
  38. package/dist/Dom/Refs.js +61 -0
  39. package/dist/Dom/Render.d.ts +59 -0
  40. package/dist/Dom/Render.d.ts.map +1 -0
  41. package/dist/Dom/Render.js +71 -0
  42. package/dist/Dom/Types.d.ts +570 -0
  43. package/dist/Dom/Types.d.ts.map +1 -0
  44. package/dist/Dom/Types.js +1 -0
  45. package/dist/Dom/index.d.ts +20 -0
  46. package/dist/Dom/index.d.ts.map +1 -0
  47. package/dist/Dom/index.js +8 -0
  48. package/dist/Dom.d.ts +14 -0
  49. package/dist/Dom.d.ts.map +1 -0
  50. package/dist/Dom.js +13 -0
  51. package/dist/Focusable.d.ts +85 -0
  52. package/dist/Focusable.d.ts.map +1 -0
  53. package/dist/Focusable.js +35 -0
  54. package/dist/Form.d.ts +1727 -0
  55. package/dist/Form.d.ts.map +1 -0
  56. package/dist/Form.js +1142 -0
  57. package/dist/Grid.d.ts +388 -0
  58. package/dist/Grid.d.ts.map +1 -0
  59. package/dist/Grid.js +284 -0
  60. package/dist/Group.d.ts +128 -0
  61. package/dist/Group.d.ts.map +1 -0
  62. package/dist/Group.js +71 -0
  63. package/dist/Heading.d.ts +87 -0
  64. package/dist/Heading.d.ts.map +1 -0
  65. package/dist/Heading.js +58 -0
  66. package/dist/Hovercard.d.ts +297 -0
  67. package/dist/Hovercard.d.ts.map +1 -0
  68. package/dist/Hovercard.js +188 -0
  69. package/dist/HttpRouter.d.ts +129 -6
  70. package/dist/HttpRouter.d.ts.map +1 -1
  71. package/dist/HttpRouter.js +199 -56
  72. package/dist/Link.d.ts +63 -28
  73. package/dist/Link.d.ts.map +1 -1
  74. package/dist/Link.js +84 -37
  75. package/dist/Listbox.d.ts +305 -0
  76. package/dist/Listbox.d.ts.map +1 -0
  77. package/dist/Listbox.js +245 -0
  78. package/dist/Menu.d.ts +663 -0
  79. package/dist/Menu.d.ts.map +1 -0
  80. package/dist/Menu.js +569 -0
  81. package/dist/Menubar.d.ts +249 -0
  82. package/dist/Menubar.d.ts.map +1 -0
  83. package/dist/Menubar.js +207 -0
  84. package/dist/Meter.d.ts +157 -0
  85. package/dist/Meter.d.ts.map +1 -0
  86. package/dist/Meter.js +87 -0
  87. package/dist/NativeDetails.d.ts +41 -0
  88. package/dist/NativeDetails.d.ts.map +1 -0
  89. package/dist/NativeDetails.js +40 -0
  90. package/dist/NativeDialog.d.ts +66 -0
  91. package/dist/NativeDialog.d.ts.map +1 -0
  92. package/dist/NativeDialog.js +88 -0
  93. package/dist/NativePopover.d.ts +43 -0
  94. package/dist/NativePopover.d.ts.map +1 -0
  95. package/dist/NativePopover.js +84 -0
  96. package/dist/Popover.d.ts +240 -0
  97. package/dist/Popover.d.ts.map +1 -0
  98. package/dist/Popover.js +140 -0
  99. package/dist/RadioGroup.d.ts +330 -0
  100. package/dist/RadioGroup.d.ts.map +1 -0
  101. package/dist/RadioGroup.js +241 -0
  102. package/dist/Role.d.ts +64 -0
  103. package/dist/Role.d.ts.map +1 -0
  104. package/dist/Role.js +27 -0
  105. package/dist/Select.d.ts +418 -0
  106. package/dist/Select.d.ts.map +1 -0
  107. package/dist/Select.js +357 -0
  108. package/dist/Separator.d.ts +58 -0
  109. package/dist/Separator.d.ts.map +1 -0
  110. package/dist/Separator.js +32 -0
  111. package/dist/Slider.d.ts +141 -0
  112. package/dist/Slider.d.ts.map +1 -0
  113. package/dist/Slider.js +101 -0
  114. package/dist/SpinButton.d.ts +141 -0
  115. package/dist/SpinButton.d.ts.map +1 -0
  116. package/dist/SpinButton.js +101 -0
  117. package/dist/Storybook.d.ts +76 -0
  118. package/dist/Storybook.d.ts.map +1 -0
  119. package/dist/Storybook.js +102 -0
  120. package/dist/Switch.d.ts +148 -0
  121. package/dist/Switch.d.ts.map +1 -0
  122. package/dist/Switch.js +110 -0
  123. package/dist/Tab.d.ts +26 -0
  124. package/dist/Tab.d.ts.map +1 -0
  125. package/dist/Tab.js +25 -0
  126. package/dist/Tabs.d.ts +411 -0
  127. package/dist/Tabs.d.ts.map +1 -0
  128. package/dist/Tabs.js +262 -0
  129. package/dist/Toolbar.d.ts +248 -0
  130. package/dist/Toolbar.d.ts.map +1 -0
  131. package/dist/Toolbar.js +187 -0
  132. package/dist/Tooltip.d.ts +296 -0
  133. package/dist/Tooltip.d.ts.map +1 -0
  134. package/dist/Tooltip.js +172 -0
  135. package/dist/Tree.d.ts +405 -0
  136. package/dist/Tree.d.ts.map +1 -0
  137. package/dist/Tree.js +333 -0
  138. package/dist/TreeGrid.d.ts +426 -0
  139. package/dist/TreeGrid.d.ts.map +1 -0
  140. package/dist/TreeGrid.js +308 -0
  141. package/dist/VisuallyHidden.d.ts +68 -0
  142. package/dist/VisuallyHidden.d.ts.map +1 -0
  143. package/dist/VisuallyHidden.js +44 -0
  144. package/dist/WindowSplitter.d.ts +336 -0
  145. package/dist/WindowSplitter.d.ts.map +1 -0
  146. package/dist/WindowSplitter.js +305 -0
  147. package/dist/index.d.ts +48 -0
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +48 -0
  150. package/package.json +47 -14
  151. package/src/HttpRouter.test.ts +0 -294
  152. package/src/HttpRouter.ts +0 -168
  153. package/src/Link.test.ts +0 -85
  154. package/src/Link.ts +0 -107
  155. package/src/index.ts +0 -2
  156. 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":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EACL,YAAY,EAEZ,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAC;AAC9C,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,WAAW,KAAK;IACpB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW;;EAA4D,CAAC;AAErF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,YAAiB;;0DAEnD;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,cAAc,wKAAqC,CAAC;AAEjE;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAC3B,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,EACzC,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAE5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAY,SAAQ,GAAG,CAAC,WAAW,CAAC,cAAc,CAAC;IAClE;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IACzE;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;IAC5E;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAC1D;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACvE;AAED,iBAAS,iBAAiB,CAAC,KAAK,CAAC,OAAO,SAAS,WAAW,EAAE,OAAO,EAAE,OAAO,kBAetD,GAAG,CAAC,oBAAoB,CAAC,OAAO,CAAC;mBAE7C,MAAM;uBACF,CAAC;;;;;;;EAWhB;AACD,KAAK,iBAAiB,CAAC,OAAO,SAAS,WAAW,IAAI,UAAU,CAC9D,UAAU,CAAC,OAAO,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAC9C,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,IAAI,CAAC,KAAK,CAAC,OAAO,SAAS,WAAW,EAAE,KAAK,CAAC,IAAI,SAAS,UAAU,GAAG,KAAK,EAC3F,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,GAAG,CAAC,YAAY,CACrB,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC,EACxD,OAAO,CAAC,SAAS,CAAC,EAClB,IAAI,CACL,GACA,EAAE,CACH,WAAW,EACX,UAAU,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,EAChC,UAAU,CAAC,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,GAAG,cAAc,CACnE,CAUA;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAW,SAAQ,GAAG,CAAC,WAAW,CAAC,cAAc,CAAC;IACjE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CAC/D;AAED,iBAAS,gBAAgB,CAAC,KAAK,CAAC,OAAO,SAAS,UAAU,EAAE,EAC1D,QAAQ,GACT,EAAE,GAAG,CAAC,oBAAoB,CAAC,OAAO,CAAC;aACzB,IAAI,EAAE,KAAK;aAAE,eAAe;EACtC;AACD,KAAK,gBAAgB,CAAC,OAAO,SAAS,UAAU,IAAI,UAAU,CAAC,OAAO,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;AAEjG;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,GAAG,CAAC,KAAK,CAAC,OAAO,SAAS,UAAU,EAAE,KAAK,CAAC,IAAI,SAAS,UAAU,GAAG,KAAK,EACzF,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,GAAG,CAAC,YAAY,CACrB,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC,EACvD,OAAO,CAAC,SAAS,CAAC,EAClB,IAAI,CACL,GACA,EAAE,CACH,WAAW,EACX,UAAU,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,EAChC,UAAU,CAAC,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,GAAG,cAAc,CACnE,CAcA;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAY,SAAQ,GAAG,CAAC,WAAW,CAAC,cAAc,CAAC;IAClE;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IACzE;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;IAC5E;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC;CAClC;AAED,iBAAS,iBAAiB,CAAC,KAAK,CAAC,OAAO,SAAS,WAAW,EAC1D,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,WAAW,kBAWzB,GAAG,CAAC,oBAAoB,CAAC,OAAO,CAAC;;;;;;;;EAUxD;AACD,KAAK,iBAAiB,CAAC,OAAO,SAAS,WAAW,IAAI,UAAU,CAC9D,UAAU,CAAC,OAAO,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAC9C,CAAC;AAwBF;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAAC,KAAK,CAAC,OAAO,SAAS,WAAW,EAAE,KAAK,CAAC,IAAI,SAAS,UAAU,GAAG,KAAK,EAC3F,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,GAAG,CAAC,YAAY,CACrB,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC,EACxD,OAAO,CAAC,SAAS,CAAC,EAClB,IAAI,CACL,0DAGF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,KAAK,CAAC,OAAO,SAAS,WAAW,EACjC,KAAK,CAAC,IAAI,SAAS,UAAU,GAAG,KAAK,EAErC,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,GAAG,CAAC,YAAY,CACrB,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC,EACxD,OAAO,CAAC,SAAS,CAAC,EAClB,IAAI,CACL,0DAGF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,KAAK,CAAC,OAAO,SAAS,WAAW,EAAE,KAAK,CAAC,IAAI,SAAS,UAAU,GAAG,KAAK,EAChG,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,GAAG,CAAC,YAAY,CACrB,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC,EACxD,OAAO,CAAC,SAAS,CAAC,EAClB,IAAI,CACL,0DAGF;AAgBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,EAC/C,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,GAAG,SAAS,CAAC,GAC5C,MAAM,GAAG,SAAS,CAiBpB"}
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
+ }