@typed/ui 1.0.0-beta.3 → 1.0.0-beta.5
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/README.md +53 -9
- package/dist/Alert.d.ts +78 -0
- package/dist/Alert.d.ts.map +1 -0
- package/dist/Alert.js +37 -0
- package/dist/Button.d.ts +144 -0
- package/dist/Button.d.ts.map +1 -0
- package/dist/Button.js +38 -0
- package/dist/Carousel.d.ts +547 -0
- package/dist/Carousel.d.ts.map +1 -0
- package/dist/Carousel.js +387 -0
- package/dist/Checkbox.d.ts +203 -0
- package/dist/Checkbox.d.ts.map +1 -0
- package/dist/Checkbox.js +140 -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 +516 -0
- package/dist/Combobox.d.ts.map +1 -0
- package/dist/Combobox.js +371 -0
- package/dist/Component.d.ts +127 -0
- package/dist/Component.d.ts.map +1 -0
- package/dist/Component.js +60 -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 +355 -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 +1695 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/Form.js +987 -0
- package/dist/Grid.d.ts +569 -0
- package/dist/Grid.d.ts.map +1 -0
- package/dist/Grid.js +379 -0
- package/dist/Group.d.ts +147 -0
- package/dist/Group.d.ts.map +1 -0
- package/dist/Group.js +63 -0
- package/dist/Heading.d.ts +86 -0
- package/dist/Heading.d.ts.map +1 -0
- package/dist/Heading.js +48 -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 +197 -54
- package/dist/Link.d.ts +67 -28
- package/dist/Link.d.ts.map +1 -1
- package/dist/Link.js +91 -37
- package/dist/Listbox.d.ts +437 -0
- package/dist/Listbox.d.ts.map +1 -0
- package/dist/Listbox.js +316 -0
- package/dist/Menu.d.ts +972 -0
- package/dist/Menu.d.ts.map +1 -0
- package/dist/Menu.js +731 -0
- package/dist/Menubar.d.ts +367 -0
- package/dist/Menubar.d.ts.map +1 -0
- package/dist/Menubar.js +265 -0
- package/dist/Meter.d.ts +217 -0
- package/dist/Meter.d.ts.map +1 -0
- package/dist/Meter.js +94 -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 +64 -0
- package/dist/NativeDialog.d.ts.map +1 -0
- package/dist/NativeDialog.js +50 -0
- package/dist/NativePopover.d.ts +41 -0
- package/dist/NativePopover.d.ts.map +1 -0
- package/dist/NativePopover.js +46 -0
- package/dist/Popover.d.ts +241 -0
- package/dist/Popover.d.ts.map +1 -0
- package/dist/Popover.js +141 -0
- package/dist/RadioGroup.d.ts +432 -0
- package/dist/RadioGroup.d.ts.map +1 -0
- package/dist/RadioGroup.js +291 -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 +529 -0
- package/dist/Select.d.ts.map +1 -0
- package/dist/Select.js +407 -0
- package/dist/Separator.d.ts +54 -0
- package/dist/Separator.d.ts.map +1 -0
- package/dist/Separator.js +26 -0
- package/dist/Slider.d.ts +188 -0
- package/dist/Slider.d.ts.map +1 -0
- package/dist/Slider.js +96 -0
- package/dist/SpinButton.d.ts +188 -0
- package/dist/SpinButton.d.ts.map +1 -0
- package/dist/SpinButton.js +96 -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 +187 -0
- package/dist/Switch.d.ts.map +1 -0
- package/dist/Switch.js +116 -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 +591 -0
- package/dist/Tabs.d.ts.map +1 -0
- package/dist/Tabs.js +346 -0
- package/dist/Toolbar.d.ts +366 -0
- package/dist/Toolbar.d.ts.map +1 -0
- package/dist/Toolbar.js +245 -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 +591 -0
- package/dist/Tree.d.ts.map +1 -0
- package/dist/Tree.js +434 -0
- package/dist/TreeGrid.d.ts +645 -0
- package/dist/TreeGrid.d.ts.map +1 -0
- package/dist/TreeGrid.js +420 -0
- package/dist/VisuallyHidden.d.ts +54 -0
- package/dist/VisuallyHidden.d.ts.map +1 -0
- package/dist/VisuallyHidden.js +27 -0
- package/dist/WindowSplitter.d.ts +376 -0
- package/dist/WindowSplitter.d.ts.map +1 -0
- package/dist/WindowSplitter.js +222 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -0
- package/package.json +42 -15
- package/src/HttpRouter.test.ts +0 -294
- package/src/HttpRouter.ts +0 -168
- package/src/Link.test.ts +0 -84
- package/src/Link.ts +0 -107
- package/src/index.ts +0 -2
package/dist/TreeGrid.js
ADDED
|
@@ -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
|
+
}
|