@typed/ui 1.0.0-beta.1 → 1.0.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +54 -10
- package/dist/Alert.d.ts +72 -0
- package/dist/Alert.d.ts.map +1 -0
- package/dist/Alert.js +40 -0
- package/dist/Button.d.ts +100 -0
- package/dist/Button.d.ts.map +1 -0
- package/dist/Button.js +42 -0
- package/dist/Carousel.d.ts +346 -0
- package/dist/Carousel.d.ts.map +1 -0
- package/dist/Carousel.js +264 -0
- package/dist/Checkbox.d.ts +168 -0
- package/dist/Checkbox.d.ts.map +1 -0
- package/dist/Checkbox.js +146 -0
- package/dist/Collection.d.ts +254 -0
- package/dist/Collection.d.ts.map +1 -0
- package/dist/Collection.js +218 -0
- package/dist/Combobox.d.ts +367 -0
- package/dist/Combobox.d.ts.map +1 -0
- package/dist/Combobox.js +300 -0
- package/dist/Composite.d.ts +823 -0
- package/dist/Composite.d.ts.map +1 -0
- package/dist/Composite.js +615 -0
- package/dist/Dialog.d.ts +544 -0
- package/dist/Dialog.d.ts.map +1 -0
- package/dist/Dialog.js +357 -0
- package/dist/Disclosure.d.ts +219 -0
- package/dist/Disclosure.d.ts.map +1 -0
- package/dist/Disclosure.js +128 -0
- package/dist/Dom/Events.d.ts +122 -0
- package/dist/Dom/Events.d.ts.map +1 -0
- package/dist/Dom/Events.js +192 -0
- package/dist/Dom/Props.d.ts +161 -0
- package/dist/Dom/Props.d.ts.map +1 -0
- package/dist/Dom/Props.js +110 -0
- package/dist/Dom/Refs.d.ts +58 -0
- package/dist/Dom/Refs.d.ts.map +1 -0
- package/dist/Dom/Refs.js +61 -0
- package/dist/Dom/Render.d.ts +59 -0
- package/dist/Dom/Render.d.ts.map +1 -0
- package/dist/Dom/Render.js +71 -0
- package/dist/Dom/Types.d.ts +570 -0
- package/dist/Dom/Types.d.ts.map +1 -0
- package/dist/Dom/Types.js +1 -0
- package/dist/Dom/index.d.ts +20 -0
- package/dist/Dom/index.d.ts.map +1 -0
- package/dist/Dom/index.js +8 -0
- package/dist/Dom.d.ts +14 -0
- package/dist/Dom.d.ts.map +1 -0
- package/dist/Dom.js +13 -0
- package/dist/Focusable.d.ts +85 -0
- package/dist/Focusable.d.ts.map +1 -0
- package/dist/Focusable.js +35 -0
- package/dist/Form.d.ts +1727 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/Form.js +1142 -0
- package/dist/Grid.d.ts +388 -0
- package/dist/Grid.d.ts.map +1 -0
- package/dist/Grid.js +284 -0
- package/dist/Group.d.ts +128 -0
- package/dist/Group.d.ts.map +1 -0
- package/dist/Group.js +71 -0
- package/dist/Heading.d.ts +87 -0
- package/dist/Heading.d.ts.map +1 -0
- package/dist/Heading.js +58 -0
- package/dist/Hovercard.d.ts +297 -0
- package/dist/Hovercard.d.ts.map +1 -0
- package/dist/Hovercard.js +188 -0
- package/dist/HttpRouter.d.ts +129 -6
- package/dist/HttpRouter.d.ts.map +1 -1
- package/dist/HttpRouter.js +198 -55
- package/dist/Link.d.ts +63 -28
- package/dist/Link.d.ts.map +1 -1
- package/dist/Link.js +84 -37
- package/dist/Listbox.d.ts +305 -0
- package/dist/Listbox.d.ts.map +1 -0
- package/dist/Listbox.js +245 -0
- package/dist/Menu.d.ts +663 -0
- package/dist/Menu.d.ts.map +1 -0
- package/dist/Menu.js +569 -0
- package/dist/Menubar.d.ts +249 -0
- package/dist/Menubar.d.ts.map +1 -0
- package/dist/Menubar.js +207 -0
- package/dist/Meter.d.ts +157 -0
- package/dist/Meter.d.ts.map +1 -0
- package/dist/Meter.js +87 -0
- package/dist/NativeDetails.d.ts +41 -0
- package/dist/NativeDetails.d.ts.map +1 -0
- package/dist/NativeDetails.js +40 -0
- package/dist/NativeDialog.d.ts +66 -0
- package/dist/NativeDialog.d.ts.map +1 -0
- package/dist/NativeDialog.js +88 -0
- package/dist/NativePopover.d.ts +43 -0
- package/dist/NativePopover.d.ts.map +1 -0
- package/dist/NativePopover.js +84 -0
- package/dist/Popover.d.ts +240 -0
- package/dist/Popover.d.ts.map +1 -0
- package/dist/Popover.js +140 -0
- package/dist/RadioGroup.d.ts +330 -0
- package/dist/RadioGroup.d.ts.map +1 -0
- package/dist/RadioGroup.js +241 -0
- package/dist/Role.d.ts +64 -0
- package/dist/Role.d.ts.map +1 -0
- package/dist/Role.js +27 -0
- package/dist/Select.d.ts +418 -0
- package/dist/Select.d.ts.map +1 -0
- package/dist/Select.js +357 -0
- package/dist/Separator.d.ts +58 -0
- package/dist/Separator.d.ts.map +1 -0
- package/dist/Separator.js +32 -0
- package/dist/Slider.d.ts +141 -0
- package/dist/Slider.d.ts.map +1 -0
- package/dist/Slider.js +101 -0
- package/dist/SpinButton.d.ts +141 -0
- package/dist/SpinButton.d.ts.map +1 -0
- package/dist/SpinButton.js +101 -0
- package/dist/Storybook.d.ts +76 -0
- package/dist/Storybook.d.ts.map +1 -0
- package/dist/Storybook.js +102 -0
- package/dist/Switch.d.ts +148 -0
- package/dist/Switch.d.ts.map +1 -0
- package/dist/Switch.js +110 -0
- package/dist/Tab.d.ts +26 -0
- package/dist/Tab.d.ts.map +1 -0
- package/dist/Tab.js +25 -0
- package/dist/Tabs.d.ts +411 -0
- package/dist/Tabs.d.ts.map +1 -0
- package/dist/Tabs.js +262 -0
- package/dist/Toolbar.d.ts +248 -0
- package/dist/Toolbar.d.ts.map +1 -0
- package/dist/Toolbar.js +187 -0
- package/dist/Tooltip.d.ts +296 -0
- package/dist/Tooltip.d.ts.map +1 -0
- package/dist/Tooltip.js +172 -0
- package/dist/Tree.d.ts +405 -0
- package/dist/Tree.d.ts.map +1 -0
- package/dist/Tree.js +333 -0
- package/dist/TreeGrid.d.ts +426 -0
- package/dist/TreeGrid.d.ts.map +1 -0
- package/dist/TreeGrid.js +308 -0
- package/dist/VisuallyHidden.d.ts +68 -0
- package/dist/VisuallyHidden.d.ts.map +1 -0
- package/dist/VisuallyHidden.js +44 -0
- package/dist/WindowSplitter.d.ts +336 -0
- package/dist/WindowSplitter.d.ts.map +1 -0
- package/dist/WindowSplitter.js +305 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -0
- package/package.json +48 -20
- 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,308 @@
|
|
|
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
|
+
* Learn the interaction in the [TreeGrid guide](/explore/ui-tree-grid).
|
|
12
|
+
*
|
|
13
|
+
* @since 1.0.0
|
|
14
|
+
* @category modules
|
|
15
|
+
* @packageDocumentation
|
|
16
|
+
*/
|
|
17
|
+
import * as Effect from "effect/Effect";
|
|
18
|
+
import { RefSubject } from "@typed/fx";
|
|
19
|
+
import { EventHandler, html, } from "@typed/template";
|
|
20
|
+
import * as Collection from "./Collection.js";
|
|
21
|
+
import * as Dom from "./Dom.js";
|
|
22
|
+
import * as Grid from "./Grid.js";
|
|
23
|
+
import * as Tree from "./Tree.js";
|
|
24
|
+
/**
|
|
25
|
+
* Effect Schema used by makeState to encode, decode, and hydrate TreeGrid state.
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import * as Schema from "effect/Schema";
|
|
31
|
+
* import * as TreeGrid from "@typed/ui/TreeGrid";
|
|
32
|
+
*
|
|
33
|
+
* const decodeState = Schema.decodeUnknownEffect(TreeGrid.StateSchema);
|
|
34
|
+
* ```
|
|
35
|
+
* @since 1.0.0
|
|
36
|
+
* @category Hierarchy and cell focus
|
|
37
|
+
*/
|
|
38
|
+
export const StateSchema = Tree.StateSchema;
|
|
39
|
+
/**
|
|
40
|
+
* Creates hydrated TreeGrid state. Uses Tree state and defaults.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* The returned Effect creates the RefSubject when run. That state is renderer-independent;
|
|
44
|
+
* collection registrations belong to the separate Scope that runs register or ref, not to state
|
|
45
|
+
* creation.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* import * as Effect from "effect/Effect";
|
|
50
|
+
* import * as TreeGrid from "@typed/ui/TreeGrid";
|
|
51
|
+
*
|
|
52
|
+
* const program = Effect.scoped(
|
|
53
|
+
* Effect.gen(function* () {
|
|
54
|
+
* const state = yield* TreeGrid.makeState({});
|
|
55
|
+
* const collection = yield* TreeGrid.makeCollection();
|
|
56
|
+
* return { state: yield* state, collection: yield* collection };
|
|
57
|
+
* }),
|
|
58
|
+
* );
|
|
59
|
+
* ```
|
|
60
|
+
* @since 1.0.0
|
|
61
|
+
* @category Hierarchy and cell focus
|
|
62
|
+
*/
|
|
63
|
+
export const makeState = Tree.makeState;
|
|
64
|
+
/**
|
|
65
|
+
* Tests membership in expandedIds without touching the DOM; this is the Tree helper exposed from
|
|
66
|
+
* the TreeGrid entrypoint.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* Separating this deterministic policy from event wiring lets applications test it directly and
|
|
70
|
+
* reuse it in custom composites.
|
|
71
|
+
*
|
|
72
|
+
* This is a synchronous calculation. It acquires no resources and does not mutate the input array,
|
|
73
|
+
* state, event, or DOM.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* import * as TreeGrid from "@typed/ui/TreeGrid";
|
|
78
|
+
*
|
|
79
|
+
* const expanded = TreeGrid.isExpanded({ activeId: null, expandedIds: ["parent"], loop: true }, "parent");
|
|
80
|
+
* ```
|
|
81
|
+
* @since 1.0.0
|
|
82
|
+
* @category Row expansion
|
|
83
|
+
*/
|
|
84
|
+
export const isExpanded = Tree.isExpanded;
|
|
85
|
+
/**
|
|
86
|
+
* Adds a row id once to expandedIds using the Tree state transition.
|
|
87
|
+
*
|
|
88
|
+
* @remarks
|
|
89
|
+
* The operation exposes TreeGrid's transition directly so callers can compose it in Effect
|
|
90
|
+
* programs and native event handlers.
|
|
91
|
+
*
|
|
92
|
+
* @since 1.0.0
|
|
93
|
+
* @category Row expansion
|
|
94
|
+
*/
|
|
95
|
+
export const expand = Tree.expand;
|
|
96
|
+
/**
|
|
97
|
+
* Removes a row id from expandedIds using the Tree state transition.
|
|
98
|
+
*
|
|
99
|
+
* @remarks
|
|
100
|
+
* The operation exposes TreeGrid's transition directly so callers can compose it in Effect
|
|
101
|
+
* programs and native event handlers.
|
|
102
|
+
*
|
|
103
|
+
* @since 1.0.0
|
|
104
|
+
* @category Row expansion
|
|
105
|
+
*/
|
|
106
|
+
export const collapse = Tree.collapse;
|
|
107
|
+
/**
|
|
108
|
+
* Sets the active TreeGrid row or cell id without changing expansion.
|
|
109
|
+
*
|
|
110
|
+
* @remarks
|
|
111
|
+
* The operation exposes TreeGrid's transition directly so callers can compose it in Effect
|
|
112
|
+
* programs and native event handlers.
|
|
113
|
+
*
|
|
114
|
+
* @since 1.0.0
|
|
115
|
+
* @category Hierarchy and cell focus
|
|
116
|
+
*/
|
|
117
|
+
export const activate = Tree.activate;
|
|
118
|
+
/**
|
|
119
|
+
* Creates a scoped Collection for TreeGrid items.
|
|
120
|
+
*
|
|
121
|
+
* @remarks
|
|
122
|
+
* The returned Effect allocates the RefSubject in the caller's Scope. Each later registration is
|
|
123
|
+
* owned by the Scope that runs register, independently of this construction Effect.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* import * as Effect from "effect/Effect";
|
|
128
|
+
* import * as TreeGrid from "@typed/ui/TreeGrid";
|
|
129
|
+
*
|
|
130
|
+
* const program = Effect.scoped(
|
|
131
|
+
* Effect.gen(function* () {
|
|
132
|
+
* const collection = yield* TreeGrid.makeCollection();
|
|
133
|
+
* return yield* collection;
|
|
134
|
+
* }),
|
|
135
|
+
* );
|
|
136
|
+
* ```
|
|
137
|
+
* @since 1.0.0
|
|
138
|
+
* @category Cell registration
|
|
139
|
+
*/
|
|
140
|
+
export const makeCollection = Collection.makeState;
|
|
141
|
+
function rootInternalProps(options) {
|
|
142
|
+
const onfocus = options.collection === undefined
|
|
143
|
+
? undefined
|
|
144
|
+
: Effect.gen(function* () {
|
|
145
|
+
const current = yield* options.state;
|
|
146
|
+
if (current.activeId !== null)
|
|
147
|
+
return;
|
|
148
|
+
const first = visibleItems(yield* options.collection, current)[0];
|
|
149
|
+
if (first !== undefined)
|
|
150
|
+
yield* activate(options.state, first.id);
|
|
151
|
+
});
|
|
152
|
+
const onkeydown = options.collection === undefined
|
|
153
|
+
? undefined
|
|
154
|
+
: EventHandler.make(Effect.fn((event) => onKeyDown(options.state, options.collection, event)));
|
|
155
|
+
return ({ property }) => ({
|
|
156
|
+
role: "treegrid",
|
|
157
|
+
tabindex: 0,
|
|
158
|
+
"aria-label": property("label", undefined),
|
|
159
|
+
"aria-activedescendant": RefSubject.map(options.state, (state) => state.activeId ?? undefined),
|
|
160
|
+
onfocus,
|
|
161
|
+
onkeydown,
|
|
162
|
+
ref: options.state,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Renders the focus-owning treegrid root and applies combined grid and expansion keys.
|
|
167
|
+
*
|
|
168
|
+
* @remarks
|
|
169
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
170
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
171
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
172
|
+
*
|
|
173
|
+
* @since 1.0.0
|
|
174
|
+
* @category Treegrid surface
|
|
175
|
+
*/
|
|
176
|
+
export function Root(options, host) {
|
|
177
|
+
return Dom.renderHost()(options, host, rootInternalProps(options), options.content, (props, content) => {
|
|
178
|
+
return html `<div ...${props}>${content}</div>`;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function rowInternalProps(options) {
|
|
182
|
+
const hasChildren = options.hasChildren ?? false;
|
|
183
|
+
return ({ property }) => ({
|
|
184
|
+
id: options.rowId,
|
|
185
|
+
role: "row",
|
|
186
|
+
"aria-level": property("level", 1),
|
|
187
|
+
"aria-expanded": hasChildren
|
|
188
|
+
? RefSubject.map(options.state, (state) => isExpanded(state, options.rowId))
|
|
189
|
+
: undefined,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Renders a hierarchical row with level and optional expanded state.
|
|
194
|
+
*
|
|
195
|
+
* @remarks
|
|
196
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
197
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
198
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
199
|
+
*
|
|
200
|
+
* @since 1.0.0
|
|
201
|
+
* @category Hierarchical rows
|
|
202
|
+
*/
|
|
203
|
+
export function Row(options, host) {
|
|
204
|
+
return Dom.renderHost()(options, host, rowInternalProps(options), options.content, (props, content) => html `<div ...${props}>${content}</div>`);
|
|
205
|
+
}
|
|
206
|
+
function cellInternalProps(options) {
|
|
207
|
+
const active = RefSubject.map(options.state, (state) => state.activeId === options.id);
|
|
208
|
+
const register = options.collection === undefined
|
|
209
|
+
? undefined
|
|
210
|
+
: Collection.ref(options.collection, {
|
|
211
|
+
id: options.id,
|
|
212
|
+
value: {
|
|
213
|
+
rowId: options.rowId,
|
|
214
|
+
columnIndex: options.columnIndex,
|
|
215
|
+
parentId: options.parentId,
|
|
216
|
+
hasChildren: options.hasChildren ?? false,
|
|
217
|
+
},
|
|
218
|
+
textValue: options.id,
|
|
219
|
+
});
|
|
220
|
+
return () => ({
|
|
221
|
+
id: options.id,
|
|
222
|
+
role: "gridcell",
|
|
223
|
+
"aria-colindex": options.columnIndex,
|
|
224
|
+
"?data-active": active,
|
|
225
|
+
ref: register,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Renders and optionally registers one gridcell with row and column coordinates.
|
|
230
|
+
*
|
|
231
|
+
* @remarks
|
|
232
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
233
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
234
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
235
|
+
*
|
|
236
|
+
* @since 1.0.0
|
|
237
|
+
* @category Navigable cells
|
|
238
|
+
*/
|
|
239
|
+
export function Cell(options, host) {
|
|
240
|
+
return Dom.renderHost()(options, host, cellInternalProps(options), options.content, (props, content) => {
|
|
241
|
+
return html `<div ...${props}>${content}</div>`;
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
function groupInternalProps(options) {
|
|
245
|
+
return () => ({
|
|
246
|
+
role: "rowgroup",
|
|
247
|
+
"?hidden": RefSubject.map(options.state, (state) => !isExpanded(state, options.parentId)),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Renders a rowgroup and hides it while its parent row is collapsed.
|
|
252
|
+
*
|
|
253
|
+
* @remarks
|
|
254
|
+
* The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
|
|
255
|
+
* collection registrations only when rendered. The rendering Scope removes those resources;
|
|
256
|
+
* unrelated nodes and attributes remain caller-owned.
|
|
257
|
+
*
|
|
258
|
+
* @since 1.0.0
|
|
259
|
+
* @category Child row visibility
|
|
260
|
+
*/
|
|
261
|
+
export function Group(options, host) {
|
|
262
|
+
return Dom.renderHost()(options, host, groupInternalProps(options), options.content, (props, content) => html `<div ...${props}>${content}</div>`);
|
|
263
|
+
}
|
|
264
|
+
function onKeyDown(state, collection, event) {
|
|
265
|
+
return Effect.gen(function* () {
|
|
266
|
+
const current = yield* state;
|
|
267
|
+
const all = yield* collection;
|
|
268
|
+
const visible = visibleItems(all, current);
|
|
269
|
+
const active = current.activeId === null ? undefined : all.find((item) => item.id === current.activeId);
|
|
270
|
+
const activeValue = active?.value;
|
|
271
|
+
if (activeValue?.columnIndex === 1 &&
|
|
272
|
+
(event.key === "ArrowRight" || event.key === "ArrowLeft")) {
|
|
273
|
+
if (event.key === "ArrowRight" && activeValue.hasChildren) {
|
|
274
|
+
event.preventDefault();
|
|
275
|
+
if (!isExpanded(current, activeValue.rowId))
|
|
276
|
+
return yield* expand(state, activeValue.rowId);
|
|
277
|
+
const child = visible.find((item) => item.value?.parentId === activeValue.rowId && item.value?.columnIndex === 1);
|
|
278
|
+
return child === undefined ? current : yield* activate(state, child.id);
|
|
279
|
+
}
|
|
280
|
+
if (event.key === "ArrowLeft") {
|
|
281
|
+
event.preventDefault();
|
|
282
|
+
if (activeValue.hasChildren && isExpanded(current, activeValue.rowId))
|
|
283
|
+
return yield* collapse(state, activeValue.rowId);
|
|
284
|
+
const parent = activeValue.parentId === undefined
|
|
285
|
+
? undefined
|
|
286
|
+
: visible.find((item) => item.value?.rowId === activeValue.parentId && item.value?.columnIndex === 1);
|
|
287
|
+
return parent === undefined ? current : yield* activate(state, parent.id);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const next = Grid.moveActiveId(visible, current.activeId, event);
|
|
291
|
+
if (next === undefined)
|
|
292
|
+
return current;
|
|
293
|
+
event.preventDefault();
|
|
294
|
+
return yield* activate(state, next);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
function visibleItems(items, state) {
|
|
298
|
+
const ordered = Collection.byDomOrder(items);
|
|
299
|
+
return ordered.filter((item) => {
|
|
300
|
+
let parentId = item.value?.parentId;
|
|
301
|
+
while (parentId !== undefined) {
|
|
302
|
+
if (!isExpanded(state, parentId))
|
|
303
|
+
return false;
|
|
304
|
+
parentId = ordered.find((candidate) => candidate.value?.rowId === parentId)?.value?.parentId;
|
|
305
|
+
}
|
|
306
|
+
return true;
|
|
307
|
+
});
|
|
308
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text clipped visually but retained for accessible names and descriptions.
|
|
3
|
+
* The clipping style has no focus-reveal behavior and must not hide interactive descendants.
|
|
4
|
+
*
|
|
5
|
+
* Read the [VisuallyHidden guide](/explore/ui-visually-hidden) for a complete example.
|
|
6
|
+
*
|
|
7
|
+
* [Platform reference](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-hidden).
|
|
8
|
+
* @since 1.0.0
|
|
9
|
+
* @category Overview
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
import type * as Scope from "effect/Scope";
|
|
13
|
+
import type { Fx } from "@typed/fx/Fx";
|
|
14
|
+
import { type Renderable, type RenderEvent, type RenderTemplate } from "@typed/template";
|
|
15
|
+
import * as Dom from "./Dom.js";
|
|
16
|
+
import type { HostResult } from "./Dom/Types.js";
|
|
17
|
+
/**
|
|
18
|
+
*/
|
|
19
|
+
export interface VisuallyHiddenOptions extends Dom.HostOptions<HTMLSpanElement> {
|
|
20
|
+
/**
|
|
21
|
+
* Content retained in the document and accessibility tree.
|
|
22
|
+
* @since 1.0.0
|
|
23
|
+
* @category Rendered content
|
|
24
|
+
*/
|
|
25
|
+
readonly content: Renderable.Any;
|
|
26
|
+
}
|
|
27
|
+
declare function internalProps(): {
|
|
28
|
+
style: string;
|
|
29
|
+
};
|
|
30
|
+
type VisuallyHiddenInternalProps = ReturnType<typeof internalProps>;
|
|
31
|
+
/**
|
|
32
|
+
* Clips text visually while retaining it for accessible naming and reading.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* The span uses absolute positioning and a one-pixel clipping recipe. It has no focus-reveal
|
|
36
|
+
* behavior: do not hide interactive controls or use it alone as a skip link. An aria-hidden
|
|
37
|
+
* ancestor still removes its content from accessibility exposure. Custom hosts must preserve the
|
|
38
|
+
* supplied style.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* import { RefSubject } from "@typed/fx";
|
|
43
|
+
* import { html } from "@typed/template";
|
|
44
|
+
* import { Button } from "@typed/ui/Button";
|
|
45
|
+
* import { component } from "@typed/template";
|
|
46
|
+
* import { VisuallyHidden } from "@typed/ui/VisuallyHidden";
|
|
47
|
+
*
|
|
48
|
+
* export const ResetCounter = component(function* () {
|
|
49
|
+
* const count = yield* RefSubject.make(3);
|
|
50
|
+
* return html`<div>
|
|
51
|
+
* <p>Selected items: ${count}</p>
|
|
52
|
+
* ${Button({
|
|
53
|
+
* content: html`
|
|
54
|
+
* <span aria-hidden="true">×</span>
|
|
55
|
+
* ${VisuallyHidden({ content: "Clear selection" })}
|
|
56
|
+
* `,
|
|
57
|
+
* props: { class: "icon-action" },
|
|
58
|
+
* onclick: RefSubject.set(count, 0),
|
|
59
|
+
* })}
|
|
60
|
+
* </div>`;
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
* @since 1.0.0
|
|
64
|
+
* @category Structure and naming
|
|
65
|
+
*/
|
|
66
|
+
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>;
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=VisuallyHidden.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VisuallyHidden.d.ts","sourceRoot":"","sources":["../src/VisuallyHidden.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,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;GACG;AACH,MAAM,WAAW,qBAAsB,SAAQ,GAAG,CAAC,WAAW,CAAC,eAAe,CAAC;IAC7E;;;;OAIG;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;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,44 @@
|
|
|
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
|
+
* Clips text visually while retaining it for accessible naming and reading.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* The span uses absolute positioning and a one-pixel clipping recipe. It has no focus-reveal
|
|
12
|
+
* behavior: do not hide interactive controls or use it alone as a skip link. An aria-hidden
|
|
13
|
+
* ancestor still removes its content from accessibility exposure. Custom hosts must preserve the
|
|
14
|
+
* supplied style.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { RefSubject } from "@typed/fx";
|
|
19
|
+
* import { html } from "@typed/template";
|
|
20
|
+
* import { Button } from "@typed/ui/Button";
|
|
21
|
+
* import { component } from "@typed/template";
|
|
22
|
+
* import { VisuallyHidden } from "@typed/ui/VisuallyHidden";
|
|
23
|
+
*
|
|
24
|
+
* export const ResetCounter = component(function* () {
|
|
25
|
+
* const count = yield* RefSubject.make(3);
|
|
26
|
+
* return html`<div>
|
|
27
|
+
* <p>Selected items: ${count}</p>
|
|
28
|
+
* ${Button({
|
|
29
|
+
* content: html`
|
|
30
|
+
* <span aria-hidden="true">×</span>
|
|
31
|
+
* ${VisuallyHidden({ content: "Clear selection" })}
|
|
32
|
+
* `,
|
|
33
|
+
* props: { class: "icon-action" },
|
|
34
|
+
* onclick: RefSubject.set(count, 0),
|
|
35
|
+
* })}
|
|
36
|
+
* </div>`;
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
* @since 1.0.0
|
|
40
|
+
* @category Structure and naming
|
|
41
|
+
*/
|
|
42
|
+
export function VisuallyHidden(options, host) {
|
|
43
|
+
return Dom.renderHost()(options, host, internalProps, options.content, (props, content) => html `<span ...${props}>${content}</span>`);
|
|
44
|
+
}
|