@typed/ui 1.0.0-beta.4 → 1.0.0-beta.6

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 (157) hide show
  1. package/README.md +53 -9
  2. package/dist/Alert.d.ts +78 -0
  3. package/dist/Alert.d.ts.map +1 -0
  4. package/dist/Alert.js +37 -0
  5. package/dist/Button.d.ts +144 -0
  6. package/dist/Button.d.ts.map +1 -0
  7. package/dist/Button.js +38 -0
  8. package/dist/Carousel.d.ts +547 -0
  9. package/dist/Carousel.d.ts.map +1 -0
  10. package/dist/Carousel.js +387 -0
  11. package/dist/Checkbox.d.ts +203 -0
  12. package/dist/Checkbox.d.ts.map +1 -0
  13. package/dist/Checkbox.js +140 -0
  14. package/dist/Collection.d.ts +254 -0
  15. package/dist/Collection.d.ts.map +1 -0
  16. package/dist/Collection.js +218 -0
  17. package/dist/Combobox.d.ts +516 -0
  18. package/dist/Combobox.d.ts.map +1 -0
  19. package/dist/Combobox.js +371 -0
  20. package/dist/Component.d.ts +127 -0
  21. package/dist/Component.d.ts.map +1 -0
  22. package/dist/Component.js +60 -0
  23. package/dist/Composite.d.ts +823 -0
  24. package/dist/Composite.d.ts.map +1 -0
  25. package/dist/Composite.js +615 -0
  26. package/dist/Dialog.d.ts +544 -0
  27. package/dist/Dialog.d.ts.map +1 -0
  28. package/dist/Dialog.js +355 -0
  29. package/dist/Disclosure.d.ts +219 -0
  30. package/dist/Disclosure.d.ts.map +1 -0
  31. package/dist/Disclosure.js +128 -0
  32. package/dist/Dom/Events.d.ts +122 -0
  33. package/dist/Dom/Events.d.ts.map +1 -0
  34. package/dist/Dom/Events.js +192 -0
  35. package/dist/Dom/Props.d.ts +161 -0
  36. package/dist/Dom/Props.d.ts.map +1 -0
  37. package/dist/Dom/Props.js +110 -0
  38. package/dist/Dom/Refs.d.ts +58 -0
  39. package/dist/Dom/Refs.d.ts.map +1 -0
  40. package/dist/Dom/Refs.js +61 -0
  41. package/dist/Dom/Render.d.ts +59 -0
  42. package/dist/Dom/Render.d.ts.map +1 -0
  43. package/dist/Dom/Render.js +71 -0
  44. package/dist/Dom/Types.d.ts +570 -0
  45. package/dist/Dom/Types.d.ts.map +1 -0
  46. package/dist/Dom/Types.js +1 -0
  47. package/dist/Dom/index.d.ts +20 -0
  48. package/dist/Dom/index.d.ts.map +1 -0
  49. package/dist/Dom/index.js +8 -0
  50. package/dist/Dom.d.ts +14 -0
  51. package/dist/Dom.d.ts.map +1 -0
  52. package/dist/Dom.js +13 -0
  53. package/dist/Focusable.d.ts +85 -0
  54. package/dist/Focusable.d.ts.map +1 -0
  55. package/dist/Focusable.js +35 -0
  56. package/dist/Form.d.ts +1695 -0
  57. package/dist/Form.d.ts.map +1 -0
  58. package/dist/Form.js +987 -0
  59. package/dist/Grid.d.ts +569 -0
  60. package/dist/Grid.d.ts.map +1 -0
  61. package/dist/Grid.js +379 -0
  62. package/dist/Group.d.ts +147 -0
  63. package/dist/Group.d.ts.map +1 -0
  64. package/dist/Group.js +63 -0
  65. package/dist/Heading.d.ts +86 -0
  66. package/dist/Heading.d.ts.map +1 -0
  67. package/dist/Heading.js +48 -0
  68. package/dist/Hovercard.d.ts +297 -0
  69. package/dist/Hovercard.d.ts.map +1 -0
  70. package/dist/Hovercard.js +188 -0
  71. package/dist/HttpRouter.d.ts +129 -6
  72. package/dist/HttpRouter.d.ts.map +1 -1
  73. package/dist/HttpRouter.js +196 -53
  74. package/dist/Link.d.ts +67 -28
  75. package/dist/Link.d.ts.map +1 -1
  76. package/dist/Link.js +91 -37
  77. package/dist/Listbox.d.ts +437 -0
  78. package/dist/Listbox.d.ts.map +1 -0
  79. package/dist/Listbox.js +316 -0
  80. package/dist/Menu.d.ts +972 -0
  81. package/dist/Menu.d.ts.map +1 -0
  82. package/dist/Menu.js +731 -0
  83. package/dist/Menubar.d.ts +367 -0
  84. package/dist/Menubar.d.ts.map +1 -0
  85. package/dist/Menubar.js +265 -0
  86. package/dist/Meter.d.ts +217 -0
  87. package/dist/Meter.d.ts.map +1 -0
  88. package/dist/Meter.js +94 -0
  89. package/dist/NativeDetails.d.ts +41 -0
  90. package/dist/NativeDetails.d.ts.map +1 -0
  91. package/dist/NativeDetails.js +40 -0
  92. package/dist/NativeDialog.d.ts +64 -0
  93. package/dist/NativeDialog.d.ts.map +1 -0
  94. package/dist/NativeDialog.js +50 -0
  95. package/dist/NativePopover.d.ts +41 -0
  96. package/dist/NativePopover.d.ts.map +1 -0
  97. package/dist/NativePopover.js +46 -0
  98. package/dist/Popover.d.ts +241 -0
  99. package/dist/Popover.d.ts.map +1 -0
  100. package/dist/Popover.js +141 -0
  101. package/dist/RadioGroup.d.ts +432 -0
  102. package/dist/RadioGroup.d.ts.map +1 -0
  103. package/dist/RadioGroup.js +291 -0
  104. package/dist/Role.d.ts +64 -0
  105. package/dist/Role.d.ts.map +1 -0
  106. package/dist/Role.js +27 -0
  107. package/dist/Select.d.ts +529 -0
  108. package/dist/Select.d.ts.map +1 -0
  109. package/dist/Select.js +407 -0
  110. package/dist/Separator.d.ts +54 -0
  111. package/dist/Separator.d.ts.map +1 -0
  112. package/dist/Separator.js +26 -0
  113. package/dist/Slider.d.ts +188 -0
  114. package/dist/Slider.d.ts.map +1 -0
  115. package/dist/Slider.js +96 -0
  116. package/dist/SpinButton.d.ts +188 -0
  117. package/dist/SpinButton.d.ts.map +1 -0
  118. package/dist/SpinButton.js +96 -0
  119. package/dist/Storybook.d.ts +76 -0
  120. package/dist/Storybook.d.ts.map +1 -0
  121. package/dist/Storybook.js +102 -0
  122. package/dist/Switch.d.ts +187 -0
  123. package/dist/Switch.d.ts.map +1 -0
  124. package/dist/Switch.js +116 -0
  125. package/dist/Tab.d.ts +26 -0
  126. package/dist/Tab.d.ts.map +1 -0
  127. package/dist/Tab.js +25 -0
  128. package/dist/Tabs.d.ts +591 -0
  129. package/dist/Tabs.d.ts.map +1 -0
  130. package/dist/Tabs.js +346 -0
  131. package/dist/Toolbar.d.ts +366 -0
  132. package/dist/Toolbar.d.ts.map +1 -0
  133. package/dist/Toolbar.js +245 -0
  134. package/dist/Tooltip.d.ts +296 -0
  135. package/dist/Tooltip.d.ts.map +1 -0
  136. package/dist/Tooltip.js +172 -0
  137. package/dist/Tree.d.ts +591 -0
  138. package/dist/Tree.d.ts.map +1 -0
  139. package/dist/Tree.js +434 -0
  140. package/dist/TreeGrid.d.ts +645 -0
  141. package/dist/TreeGrid.d.ts.map +1 -0
  142. package/dist/TreeGrid.js +420 -0
  143. package/dist/VisuallyHidden.d.ts +54 -0
  144. package/dist/VisuallyHidden.d.ts.map +1 -0
  145. package/dist/VisuallyHidden.js +27 -0
  146. package/dist/WindowSplitter.d.ts +376 -0
  147. package/dist/WindowSplitter.d.ts.map +1 -0
  148. package/dist/WindowSplitter.js +222 -0
  149. package/dist/index.d.ts +49 -0
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/index.js +49 -0
  152. package/package.json +45 -18
  153. package/src/HttpRouter.test.ts +0 -256
  154. package/src/HttpRouter.ts +0 -165
  155. package/src/Link.test.ts +0 -84
  156. package/src/Link.ts +0 -107
  157. package/src/index.ts +0 -2
@@ -0,0 +1,420 @@
1
+ /**
2
+ * TreeGrid combines tree expansion with virtual-focus grid movement. Rows carry hierarchy and
3
+ * expansion, cells carry column positions, and the root retains focus while aria-activedescendant
4
+ * identifies the active cell.
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
+ * @since 1.0.0
12
+ * @category modules
13
+ * @packageDocumentation
14
+ */
15
+ import * as Effect from "effect/Effect";
16
+ import { RefSubject } from "@typed/fx";
17
+ import { EventHandler, html, } from "@typed/template";
18
+ import * as Collection from "./Collection.js";
19
+ import * as Dom from "./Dom.js";
20
+ import * as Grid from "./Grid.js";
21
+ import * as Tree from "./Tree.js";
22
+ /**
23
+ * Effect Schema used by makeState to encode, decode, and hydrate TreeGrid state.
24
+ *
25
+ * @remarks
26
+ * ## Why
27
+ *
28
+ * A public schema makes hydration and serialized state use the same runtime validation as direct
29
+ * construction.
30
+ *
31
+ * ## Ownership and lifetime
32
+ *
33
+ * This declaration is data or schema metadata and acquires no resources.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import * as Schema from "effect/Schema";
38
+ * import * as TreeGrid from "@typed/ui/TreeGrid";
39
+ *
40
+ * const decodeState = Schema.decodeUnknownEffect(TreeGrid.StateSchema);
41
+ * ```
42
+ * @since 1.0.0
43
+ * @category schemas
44
+ */
45
+ export const StateSchema = Tree.StateSchema;
46
+ /**
47
+ * Creates hydrated TreeGrid state. Uses Tree state and defaults.
48
+ *
49
+ * @remarks
50
+ * ## Why
51
+ *
52
+ * State and collection ownership can be composed and tested independently from any renderer.
53
+ *
54
+ * ## Ownership and lifetime
55
+ *
56
+ * The returned Effect creates the RefSubject when run. That state is renderer-independent;
57
+ * collection registrations belong to the separate Scope that runs register or ref, not to state
58
+ * creation.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * import * as Effect from "effect/Effect";
63
+ * import * as TreeGrid from "@typed/ui/TreeGrid";
64
+ *
65
+ * const program = Effect.scoped(
66
+ * Effect.gen(function* () {
67
+ * const state = yield* TreeGrid.makeState({});
68
+ * const collection = yield* TreeGrid.makeCollection();
69
+ * return { state: yield* state, collection: yield* collection };
70
+ * }),
71
+ * );
72
+ * ```
73
+ * @since 1.0.0
74
+ * @category constructors
75
+ */
76
+ export const makeState = Tree.makeState;
77
+ /**
78
+ * Tests membership in expandedIds without touching the DOM; this is the Tree helper exposed from
79
+ * the TreeGrid entrypoint.
80
+ *
81
+ * @remarks
82
+ * ## Why
83
+ *
84
+ * Separating this deterministic policy from event wiring lets applications test it directly and
85
+ * reuse it in custom composites.
86
+ *
87
+ * ## Ownership and lifetime
88
+ *
89
+ * This is a synchronous calculation. It acquires no resources and does not mutate the input array,
90
+ * state, event, or DOM.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * import * as TreeGrid from "@typed/ui/TreeGrid";
95
+ *
96
+ * const expanded = TreeGrid.isExpanded({ activeId: null, expandedIds: ["parent"], loop: true }, "parent");
97
+ * ```
98
+ * @since 1.0.0
99
+ * @category combinators
100
+ */
101
+ export const isExpanded = Tree.isExpanded;
102
+ /**
103
+ * Adds a row id once to expandedIds using the Tree state transition.
104
+ *
105
+ * @remarks
106
+ * ## Why
107
+ *
108
+ * The operation exposes TreeGrid's transition directly so callers can compose it in Effect
109
+ * programs and native event handlers.
110
+ *
111
+ * ## Ownership and lifetime
112
+ *
113
+ * The returned Effect performs the update or DOM side effect only when run, preserves the declared
114
+ * error and service channels, and retains no resources after completion.
115
+ *
116
+ * ## Example
117
+ *
118
+ * Import with `import { expand } from "@typed/ui/TreeGrid";` Extend the [TreeGrid.makeState
119
+ * runnable setup](/reference/%40typed%2Fui%2FTreeGrid%23makeState). Inside the linked Effect
120
+ * program run `yield* expand(state, "parent")`, then read state to observe the id in expandedIds.
121
+ * @since 1.0.0
122
+ * @category combinators
123
+ */
124
+ export const expand = Tree.expand;
125
+ /**
126
+ * Removes a row id from expandedIds using the Tree state transition.
127
+ *
128
+ * @remarks
129
+ * ## Why
130
+ *
131
+ * The operation exposes TreeGrid's transition directly so callers can compose it in Effect
132
+ * programs and native event handlers.
133
+ *
134
+ * ## Ownership and lifetime
135
+ *
136
+ * The returned Effect performs the update or DOM side effect only when run, preserves the declared
137
+ * error and service channels, and retains no resources after completion.
138
+ *
139
+ * ## Example
140
+ *
141
+ * Import with `import { collapse } from "@typed/ui/TreeGrid";` Extend the [TreeGrid.makeState
142
+ * runnable setup](/reference/%40typed%2Fui%2FTreeGrid%23makeState). Inside the linked Effect
143
+ * program run `yield* collapse(state, "parent")`, then read state to observe the id removed from
144
+ * expandedIds.
145
+ * @since 1.0.0
146
+ * @category combinators
147
+ */
148
+ export const collapse = Tree.collapse;
149
+ /**
150
+ * Sets the active TreeGrid row or cell id without changing expansion.
151
+ *
152
+ * @remarks
153
+ * ## Why
154
+ *
155
+ * The operation exposes TreeGrid's transition directly so callers can compose it in Effect
156
+ * programs and native event handlers.
157
+ *
158
+ * ## Ownership and lifetime
159
+ *
160
+ * The returned Effect performs the update or DOM side effect only when run, preserves the declared
161
+ * error and service channels, and retains no resources after completion.
162
+ *
163
+ * ## Example
164
+ *
165
+ * Import with `import { activate } from "@typed/ui/TreeGrid";` Extend the [TreeGrid.makeState
166
+ * runnable setup](/reference/%40typed%2Fui%2FTreeGrid%23makeState). Inside the linked Effect
167
+ * program run `yield* activate(state, "cell-1")`, then read state to observe activeId.
168
+ * @since 1.0.0
169
+ * @category combinators
170
+ */
171
+ export const activate = Tree.activate;
172
+ /**
173
+ * Creates a scoped Collection for TreeGrid items.
174
+ *
175
+ * @remarks
176
+ * ## Why
177
+ *
178
+ * State and collection ownership can be composed and tested independently from any renderer.
179
+ *
180
+ * ## Ownership and lifetime
181
+ *
182
+ * The returned Effect allocates the RefSubject in the caller's Scope. Each later registration is
183
+ * owned by the Scope that runs register, independently of this construction Effect.
184
+ *
185
+ * @example
186
+ * ```ts
187
+ * import * as Effect from "effect/Effect";
188
+ * import * as TreeGrid from "@typed/ui/TreeGrid";
189
+ *
190
+ * const program = Effect.scoped(
191
+ * Effect.gen(function* () {
192
+ * const collection = yield* TreeGrid.makeCollection();
193
+ * return yield* collection;
194
+ * }),
195
+ * );
196
+ * ```
197
+ * @since 1.0.0
198
+ * @category constructors
199
+ */
200
+ export const makeCollection = Collection.makeState;
201
+ function rootInternalProps(options) {
202
+ const onfocus = options.collection === undefined
203
+ ? undefined
204
+ : Effect.gen(function* () {
205
+ const current = yield* options.state;
206
+ if (current.activeId !== null)
207
+ return;
208
+ const first = visibleItems(yield* options.collection, current)[0];
209
+ if (first !== undefined)
210
+ yield* activate(options.state, first.id);
211
+ });
212
+ const onkeydown = options.collection === undefined
213
+ ? undefined
214
+ : EventHandler.make(Effect.fn((event) => onKeyDown(options.state, options.collection, event)));
215
+ return ({ property }) => ({
216
+ role: "treegrid",
217
+ tabindex: 0,
218
+ "aria-label": property("label", undefined),
219
+ "aria-activedescendant": RefSubject.map(options.state, (state) => state.activeId ?? undefined),
220
+ onfocus,
221
+ onkeydown,
222
+ ref: options.state,
223
+ });
224
+ }
225
+ /**
226
+ * Renders the focus-owning treegrid root and applies combined grid and expansion keys.
227
+ *
228
+ * @remarks
229
+ * ## Why
230
+ *
231
+ * The component applies the family behavior while leaving callers free to supply a custom host
232
+ * through the shared DOM boundary.
233
+ *
234
+ * ## Ownership and lifetime
235
+ *
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
+ * ## Example
241
+ *
242
+ * Import with `import { Root } from "@typed/ui/TreeGrid";` Extend the [TreeGrid.makeState runnable
243
+ * setup](/reference/%40typed%2Fui%2FTreeGrid%23makeState). Replace the linked program's final
244
+ * snapshot read with `Root({ state, label: "Files", content: "Rows" })`; render that Fx before the
245
+ * same Scope closes.
246
+ * @since 1.0.0
247
+ * @category components
248
+ */
249
+ export function Root(options, host) {
250
+ return Dom.renderHost()(options, host, rootInternalProps(options), options.content, (props, content) => {
251
+ return html `<div ...${props}>${content}</div>`;
252
+ });
253
+ }
254
+ function rowInternalProps(options) {
255
+ const hasChildren = options.hasChildren ?? false;
256
+ return ({ property }) => ({
257
+ id: options.rowId,
258
+ role: "row",
259
+ "aria-level": property("level", 1),
260
+ "aria-expanded": hasChildren
261
+ ? RefSubject.map(options.state, (state) => isExpanded(state, options.rowId))
262
+ : undefined,
263
+ });
264
+ }
265
+ /**
266
+ * Renders a hierarchical row with level and optional expanded state.
267
+ *
268
+ * @remarks
269
+ * ## Why
270
+ *
271
+ * The component applies the family behavior while leaving callers free to supply a custom host
272
+ * through the shared DOM boundary.
273
+ *
274
+ * ## Ownership and lifetime
275
+ *
276
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
277
+ * collection registrations only when rendered. The rendering Scope removes those resources;
278
+ * unrelated nodes and attributes remain caller-owned.
279
+ *
280
+ * ## Example
281
+ *
282
+ * Import with `import { Row } from "@typed/ui/TreeGrid";` Extend the [TreeGrid.makeState runnable
283
+ * setup](/reference/%40typed%2Fui%2FTreeGrid%23makeState). Replace the linked program's final
284
+ * snapshot read with `Row({ state, rowId: "src", content: "Cells" })`; render that Fx before the
285
+ * same Scope closes.
286
+ * @since 1.0.0
287
+ * @category components
288
+ */
289
+ export function Row(options, host) {
290
+ return Dom.renderHost()(options, host, rowInternalProps(options), options.content, (props, content) => html `<div ...${props}>${content}</div>`);
291
+ }
292
+ function cellInternalProps(options) {
293
+ const active = RefSubject.map(options.state, (state) => state.activeId === options.id);
294
+ const register = options.collection === undefined
295
+ ? undefined
296
+ : Collection.ref(options.collection, {
297
+ id: options.id,
298
+ value: {
299
+ rowId: options.rowId,
300
+ columnIndex: options.columnIndex,
301
+ parentId: options.parentId,
302
+ hasChildren: options.hasChildren ?? false,
303
+ },
304
+ textValue: options.id,
305
+ });
306
+ return () => ({
307
+ id: options.id,
308
+ role: "gridcell",
309
+ "aria-colindex": options.columnIndex,
310
+ "?data-active": active,
311
+ ref: register,
312
+ });
313
+ }
314
+ /**
315
+ * Renders and optionally registers one gridcell with row and column coordinates.
316
+ *
317
+ * @remarks
318
+ * ## Why
319
+ *
320
+ * The component applies the family behavior while leaving callers free to supply a custom host
321
+ * through the shared DOM boundary.
322
+ *
323
+ * ## Ownership and lifetime
324
+ *
325
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
326
+ * collection registrations only when rendered. The rendering Scope removes those resources;
327
+ * unrelated nodes and attributes remain caller-owned.
328
+ *
329
+ * ## Example
330
+ *
331
+ * Import with `import { Cell } from "@typed/ui/TreeGrid";` Extend the [TreeGrid.makeState runnable
332
+ * setup](/reference/%40typed%2Fui%2FTreeGrid%23makeState). Replace the linked program's final
333
+ * snapshot read with `Cell({ state, id: "src-name", rowId: "src", columnIndex: 1, content: "src"
334
+ * })`; render that Fx before the same Scope closes.
335
+ * @since 1.0.0
336
+ * @category components
337
+ */
338
+ export function Cell(options, host) {
339
+ return Dom.renderHost()(options, host, cellInternalProps(options), options.content, (props, content) => {
340
+ return html `<div ...${props}>${content}</div>`;
341
+ });
342
+ }
343
+ function groupInternalProps(options) {
344
+ return () => ({
345
+ role: "rowgroup",
346
+ "?hidden": RefSubject.map(options.state, (state) => !isExpanded(state, options.parentId)),
347
+ });
348
+ }
349
+ /**
350
+ * Renders a rowgroup and hides it while its parent row is collapsed.
351
+ *
352
+ * @remarks
353
+ * ## Why
354
+ *
355
+ * The component applies the family behavior while leaving callers free to supply a custom host
356
+ * through the shared DOM boundary.
357
+ *
358
+ * ## Ownership and lifetime
359
+ *
360
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
361
+ * collection registrations only when rendered. The rendering Scope removes those resources;
362
+ * unrelated nodes and attributes remain caller-owned.
363
+ *
364
+ * ## Example
365
+ *
366
+ * Import with `import { Group } from "@typed/ui/TreeGrid";` Extend the [TreeGrid.makeState runnable
367
+ * setup](/reference/%40typed%2Fui%2FTreeGrid%23makeState). Replace the linked program's final
368
+ * snapshot read with `Group({ state, parentId: "src", content: "Rows" })`; render that Fx before
369
+ * the same Scope closes.
370
+ * @since 1.0.0
371
+ * @category components
372
+ */
373
+ export function Group(options, host) {
374
+ return Dom.renderHost()(options, host, groupInternalProps(options), options.content, (props, content) => html `<div ...${props}>${content}</div>`);
375
+ }
376
+ function onKeyDown(state, collection, event) {
377
+ return Effect.gen(function* () {
378
+ const current = yield* state;
379
+ const all = yield* collection;
380
+ const visible = visibleItems(all, current);
381
+ const active = current.activeId === null ? undefined : all.find((item) => item.id === current.activeId);
382
+ const activeValue = active?.value;
383
+ if (activeValue?.columnIndex === 1 &&
384
+ (event.key === "ArrowRight" || event.key === "ArrowLeft")) {
385
+ if (event.key === "ArrowRight" && activeValue.hasChildren) {
386
+ event.preventDefault();
387
+ if (!isExpanded(current, activeValue.rowId))
388
+ return yield* expand(state, activeValue.rowId);
389
+ const child = visible.find((item) => item.value?.parentId === activeValue.rowId && item.value?.columnIndex === 1);
390
+ return child === undefined ? current : yield* activate(state, child.id);
391
+ }
392
+ if (event.key === "ArrowLeft") {
393
+ event.preventDefault();
394
+ if (activeValue.hasChildren && isExpanded(current, activeValue.rowId))
395
+ return yield* collapse(state, activeValue.rowId);
396
+ const parent = activeValue.parentId === undefined
397
+ ? undefined
398
+ : visible.find((item) => item.value?.rowId === activeValue.parentId && item.value?.columnIndex === 1);
399
+ return parent === undefined ? current : yield* activate(state, parent.id);
400
+ }
401
+ }
402
+ const next = Grid.moveActiveId(visible, current.activeId, event);
403
+ if (next === undefined)
404
+ return current;
405
+ event.preventDefault();
406
+ return yield* activate(state, next);
407
+ });
408
+ }
409
+ function visibleItems(items, state) {
410
+ const ordered = Collection.byDomOrder(items);
411
+ return ordered.filter((item) => {
412
+ let parentId = item.value?.parentId;
413
+ while (parentId !== undefined) {
414
+ if (!isExpanded(state, parentId))
415
+ return false;
416
+ parentId = ordered.find((candidate) => candidate.value?.rowId === parentId)?.value?.parentId;
417
+ }
418
+ return true;
419
+ });
420
+ }
@@ -0,0 +1,54 @@
1
+ import type * as Scope from "effect/Scope";
2
+ import type { Fx } from "@typed/fx/Fx";
3
+ import { type Renderable, type RenderEvent, type RenderTemplate } from "@typed/template";
4
+ import * as Dom from "./Dom.js";
5
+ import type { HostResult } from "./Dom/Types.js";
6
+ /**
7
+ * Options for content hidden visually but retained for assistive technology.
8
+ * @remarks
9
+ * ## Why
10
+ * Some controls need an accessible name or instruction that should not alter
11
+ * the visual layout.
12
+ * ## Ownership and lifetime
13
+ * The options are inert; dynamic content follows the rendered Scope.
14
+ * @since 1.0.0
15
+ * @category models
16
+ */
17
+ export interface VisuallyHiddenOptions extends Dom.HostOptions<HTMLSpanElement> {
18
+ /** Content retained in the document and accessibility tree.
19
+ * @remarks
20
+ * ## Why
21
+ * Unlike `hidden` or `display: none`, clipped content remains perceivable to
22
+ * screen readers.
23
+ * ## Ownership and lifetime
24
+ * Dynamic content is released when the component Scope closes.
25
+ * @since 1.0.0
26
+ * @category content
27
+ */
28
+ readonly content: Renderable.Any;
29
+ }
30
+ declare function internalProps(): {
31
+ style: string;
32
+ };
33
+ type VisuallyHiddenInternalProps = ReturnType<typeof internalProps>;
34
+ /**
35
+ * Renders screen-reader-accessible content with standard clipping styles.
36
+ * @remarks
37
+ * ## Why
38
+ * The primitive supplies a tested visual-hiding recipe without removing the
39
+ * node from semantic layout or the accessibility tree.
40
+ * ## Ownership and lifetime
41
+ * Running the Fx owns the rendered span and dynamic content in its Effect
42
+ * Scope. A custom host must preserve the supplied clipping styles.
43
+ * @example
44
+ * ```ts
45
+ * import { VisuallyHidden } from "@typed/ui/VisuallyHidden"
46
+ *
47
+ * const label = VisuallyHidden({ content: "Open navigation" })
48
+ * ```
49
+ * @since 1.0.0
50
+ * @category components
51
+ */
52
+ export declare function VisuallyHidden<const Options extends VisuallyHiddenOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, VisuallyHiddenInternalProps>, Options["content"], Host>): Fx<RenderEvent, Renderable.Error<Options | Host>, Renderable.Services<Options | Host> | Scope.Scope | RenderTemplate>;
53
+ export {};
54
+ //# sourceMappingURL=VisuallyHidden.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VisuallyHidden.d.ts","sourceRoot":"","sources":["../src/VisuallyHidden.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAQ,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC/F,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAKjD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,qBAAsB,SAAQ,GAAG,CAAC,WAAW,CAAC,eAAe,CAAC;IAC7E;;;;;;;;;OASG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC;CAClC;AAED,iBAAS,aAAa;;EAErB;AAED,KAAK,2BAA2B,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC5B,KAAK,CAAC,OAAO,SAAS,qBAAqB,EAC3C,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,2BAA2B,CAAC,EACzD,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,CAQA"}
@@ -0,0 +1,27 @@
1
+ import { html } from "@typed/template";
2
+ import * as Dom from "./Dom.js";
3
+ const style = "border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px";
4
+ function internalProps() {
5
+ return { style };
6
+ }
7
+ /**
8
+ * Renders screen-reader-accessible content with standard clipping styles.
9
+ * @remarks
10
+ * ## Why
11
+ * The primitive supplies a tested visual-hiding recipe without removing the
12
+ * node from semantic layout or the accessibility tree.
13
+ * ## Ownership and lifetime
14
+ * Running the Fx owns the rendered span and dynamic content in its Effect
15
+ * Scope. A custom host must preserve the supplied clipping styles.
16
+ * @example
17
+ * ```ts
18
+ * import { VisuallyHidden } from "@typed/ui/VisuallyHidden"
19
+ *
20
+ * const label = VisuallyHidden({ content: "Open navigation" })
21
+ * ```
22
+ * @since 1.0.0
23
+ * @category components
24
+ */
25
+ export function VisuallyHidden(options, host) {
26
+ return Dom.renderHost()(options, host, internalProps, options.content, (props, content) => html `<span ...${props}>${content}</span>`);
27
+ }