@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
package/dist/Grid.js ADDED
@@ -0,0 +1,379 @@
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
+ * @since 1.0.0
12
+ * @category modules
13
+ * @packageDocumentation
14
+ */
15
+ import * as Effect from "effect/Effect";
16
+ import * as Schema from "effect/Schema";
17
+ import { RefSubject } from "@typed/fx";
18
+ import { EventHandler, html, } from "@typed/template";
19
+ import * as Collection from "./Collection.js";
20
+ import * as Dom from "./Dom.js";
21
+ /**
22
+ * Effect Schema used by makeState to encode, decode, and hydrate Grid state.
23
+ *
24
+ * @remarks
25
+ * ## Why
26
+ *
27
+ * A public schema makes hydration and serialized state use the same runtime validation as direct
28
+ * construction.
29
+ *
30
+ * ## Ownership and lifetime
31
+ *
32
+ * This declaration is data or schema metadata and acquires no resources.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * import * as Schema from "effect/Schema";
37
+ * import * as Grid from "@typed/ui/Grid";
38
+ *
39
+ * const decodeState = Schema.decodeUnknownEffect(Grid.StateSchema);
40
+ * ```
41
+ * @since 1.0.0
42
+ * @category schemas
43
+ */
44
+ export const StateSchema = Schema.Struct({ activeId: Schema.NullOr(Schema.String) });
45
+ /**
46
+ * Creates hydrated Grid state. activeId defaults to null.
47
+ *
48
+ * @remarks
49
+ * ## Why
50
+ *
51
+ * State and collection ownership can be composed and tested independently from any renderer.
52
+ *
53
+ * ## Ownership and lifetime
54
+ *
55
+ * The returned Effect creates the RefSubject when run. That state is renderer-independent;
56
+ * collection registrations belong to the separate Scope that runs register or ref, not to state
57
+ * creation.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * import * as Effect from "effect/Effect";
62
+ * import * as Grid from "@typed/ui/Grid";
63
+ *
64
+ * const program = Effect.scoped(
65
+ * Effect.gen(function* () {
66
+ * const state = yield* Grid.makeState({});
67
+ * const collection = yield* Grid.makeCollection();
68
+ * return { state: yield* state, collection: yield* collection };
69
+ * }),
70
+ * );
71
+ * ```
72
+ * @since 1.0.0
73
+ * @category constructors
74
+ */
75
+ export function makeState(initial = {}) {
76
+ return RefSubject.hydrate(StateSchema, { activeId: initial.activeId ?? null });
77
+ }
78
+ /**
79
+ * Creates a scoped Collection for Grid items.
80
+ *
81
+ * @remarks
82
+ * ## Why
83
+ *
84
+ * State and collection ownership can be composed and tested independently from any renderer.
85
+ *
86
+ * ## Ownership and lifetime
87
+ *
88
+ * The returned Effect allocates the RefSubject in the caller's Scope. Each later registration is
89
+ * owned by the Scope that runs register, independently of this construction Effect.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * import * as Effect from "effect/Effect";
94
+ * import * as Grid from "@typed/ui/Grid";
95
+ *
96
+ * const program = Effect.scoped(
97
+ * Effect.gen(function* () {
98
+ * const collection = yield* Grid.makeCollection();
99
+ * return yield* collection;
100
+ * }),
101
+ * );
102
+ * ```
103
+ * @since 1.0.0
104
+ * @category constructors
105
+ */
106
+ export const makeCollection = Collection.makeState;
107
+ /**
108
+ * Sets activeId, including null to clear virtual focus.
109
+ *
110
+ * @remarks
111
+ * ## Why
112
+ *
113
+ * The operation exposes Grid's transition directly so callers can compose it in Effect programs
114
+ * and native event handlers.
115
+ *
116
+ * ## Ownership and lifetime
117
+ *
118
+ * The returned Effect performs the update or DOM side effect only when run, preserves the declared
119
+ * error and service channels, and retains no resources after completion.
120
+ *
121
+ * ## Example
122
+ *
123
+ * Import with `import { activate } from "@typed/ui/Grid";` Extend the [Grid.makeState runnable
124
+ * setup](/reference/%40typed%2Fui%2FGrid%23makeState). Inside the linked Effect program invoke
125
+ * `yield* activate(state, "item-2")`, then read the state snapshot to observe the transition
126
+ * described above.
127
+ * @since 1.0.0
128
+ * @category combinators
129
+ */
130
+ export function activate(state, activeId) {
131
+ return RefSubject.update(state, (current) => ({ ...current, activeId }));
132
+ }
133
+ function rootInternalProps(options) {
134
+ const onfocus = options.collection === undefined
135
+ ? undefined
136
+ : Effect.gen(function* () {
137
+ if ((yield* options.state).activeId !== null)
138
+ return;
139
+ const first = Collection.byDomOrder(yield* options.collection)[0];
140
+ if (first !== undefined)
141
+ yield* activate(options.state, first.id);
142
+ });
143
+ const onkeydown = options.collection === undefined
144
+ ? undefined
145
+ : EventHandler.make(Effect.fn((event) => onKeyDown(options.state, options.collection, event)));
146
+ return ({ property }) => ({
147
+ role: "grid",
148
+ tabindex: 0,
149
+ "aria-label": property("label", undefined),
150
+ "aria-activedescendant": RefSubject.map(options.state, (state) => state.activeId ?? undefined),
151
+ "aria-multiselectable": property("multiselectable", false),
152
+ onfocus,
153
+ onkeydown,
154
+ ref: options.state,
155
+ });
156
+ }
157
+ /**
158
+ * Renders the focus-owning grid root and initializes the active cell from DOM order on first
159
+ * focus.
160
+ *
161
+ * @remarks
162
+ * ## Why
163
+ *
164
+ * The component applies the family behavior while leaving callers free to supply a custom host
165
+ * through the shared DOM boundary.
166
+ *
167
+ * ## Ownership and lifetime
168
+ *
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
+ * ## Example
174
+ *
175
+ * Import with `import { Root } from "@typed/ui/Grid";` Extend the [Grid.makeState runnable
176
+ * setup](/reference/%40typed%2Fui%2FGrid%23makeState). Replace the linked program's final snapshot
177
+ * read with `Root({ state, label: "Schedule", content: "Rows" })`; render that Fx before the same
178
+ * Scope closes.
179
+ * @since 1.0.0
180
+ * @category components
181
+ */
182
+ export function Root(options, host) {
183
+ return Dom.renderHost()(options, host, rootInternalProps(options), options.content, (props, content) => {
184
+ return html `<div ...${props}>${content}</div>`;
185
+ });
186
+ }
187
+ function rowInternalProps({ property, }) {
188
+ return { role: "row", "aria-rowindex": property("rowIndex", undefined) };
189
+ }
190
+ /**
191
+ * Renders an ARIA row and forwards an optional one-based aria-rowindex.
192
+ *
193
+ * @remarks
194
+ * ## Why
195
+ *
196
+ * The component applies the family behavior while leaving callers free to supply a custom host
197
+ * through the shared DOM boundary.
198
+ *
199
+ * ## Ownership and lifetime
200
+ *
201
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
202
+ * collection registrations only when rendered. The rendering Scope removes those resources;
203
+ * unrelated nodes and attributes remain caller-owned.
204
+ *
205
+ * @example
206
+ * ```ts
207
+ * import * as Grid from "@typed/ui/Grid";
208
+ *
209
+ * const view = Grid.Row({ rowIndex: 1, content: "Cells" });
210
+ * ```
211
+ * @since 1.0.0
212
+ * @category components
213
+ */
214
+ export function Row(options, host) {
215
+ return Dom.renderHost()(options, host, rowInternalProps, options.content, (props, content) => html `<div ...${props}>${content}</div>`);
216
+ }
217
+ function cellInternalProps(options, role) {
218
+ const active = RefSubject.map(options.state, (state) => state.activeId === options.id);
219
+ const register = options.collection === undefined
220
+ ? undefined
221
+ : Collection.ref(options.collection, {
222
+ id: options.id,
223
+ value: { rowId: options.rowId, columnIndex: options.columnIndex },
224
+ textValue: options.id,
225
+ });
226
+ return ({ property }) => ({
227
+ id: options.id,
228
+ role,
229
+ "aria-colindex": options.columnIndex,
230
+ "aria-rowindex": property("rowIndex", undefined),
231
+ "aria-selected": property("selected", undefined),
232
+ "?data-active": active,
233
+ ref: register,
234
+ });
235
+ }
236
+ function cell(options, host, role) {
237
+ return Dom.renderHost()(options, host, cellInternalProps(options, role), options.content, (props, content) => {
238
+ return html `<div ...${props}>${content}</div>`;
239
+ });
240
+ }
241
+ /**
242
+ * Renders and optionally registers a gridcell with row and column coordinates.
243
+ *
244
+ * @remarks
245
+ * ## Why
246
+ *
247
+ * The component applies the family behavior while leaving callers free to supply a custom host
248
+ * through the shared DOM boundary.
249
+ *
250
+ * ## Ownership and lifetime
251
+ *
252
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
253
+ * collection registrations only when rendered. The rendering Scope removes those resources;
254
+ * unrelated nodes and attributes remain caller-owned.
255
+ *
256
+ * ## Example
257
+ *
258
+ * Import with `import { Cell } from "@typed/ui/Grid";` Extend the [Grid.makeState runnable
259
+ * setup](/reference/%40typed%2Fui%2FGrid%23makeState). Replace the linked program's final snapshot
260
+ * read with `Cell({ state, id: "r1c1", rowId: "r1", columnIndex: 1, content: "Monday" })`; render
261
+ * that Fx before the same Scope closes.
262
+ * @since 1.0.0
263
+ * @category components
264
+ */
265
+ export function Cell(options, host) {
266
+ return cell(options, host, "gridcell");
267
+ }
268
+ /**
269
+ * Renders the Cell contract with the columnheader role.
270
+ *
271
+ * @remarks
272
+ * ## Why
273
+ *
274
+ * The component applies the family behavior while leaving callers free to supply a custom host
275
+ * through the shared DOM boundary.
276
+ *
277
+ * ## Ownership and lifetime
278
+ *
279
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
280
+ * collection registrations only when rendered. The rendering Scope removes those resources;
281
+ * unrelated nodes and attributes remain caller-owned.
282
+ *
283
+ * ## Example
284
+ *
285
+ * Import with `import { ColumnHeader } from "@typed/ui/Grid";` Extend the [Grid.makeState runnable
286
+ * setup](/reference/%40typed%2Fui%2FGrid%23makeState). Replace the linked program's final snapshot
287
+ * read with `ColumnHeader({ state, id: "day", rowId: "headers", columnIndex: 1, content: "Day" })`;
288
+ * render that Fx before the same Scope closes.
289
+ * @since 1.0.0
290
+ * @category components
291
+ */
292
+ export function ColumnHeader(options, host) {
293
+ return cell(options, host, "columnheader");
294
+ }
295
+ /**
296
+ * Renders the Cell contract with the rowheader role.
297
+ *
298
+ * @remarks
299
+ * ## Why
300
+ *
301
+ * The component applies the family behavior while leaving callers free to supply a custom host
302
+ * through the shared DOM boundary.
303
+ *
304
+ * ## Ownership and lifetime
305
+ *
306
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
307
+ * collection registrations only when rendered. The rendering Scope removes those resources;
308
+ * unrelated nodes and attributes remain caller-owned.
309
+ *
310
+ * ## Example
311
+ *
312
+ * Import with `import { RowHeader } from "@typed/ui/Grid";` Extend the [Grid.makeState runnable
313
+ * setup](/reference/%40typed%2Fui%2FGrid%23makeState). Replace the linked program's final snapshot
314
+ * read with `RowHeader({ state, id: "week-1", rowId: "week-1", columnIndex: 1, content: "Week 1"
315
+ * })`; render that Fx before the same Scope closes.
316
+ * @since 1.0.0
317
+ * @category components
318
+ */
319
+ export function RowHeader(options, host) {
320
+ return cell(options, host, "rowheader");
321
+ }
322
+ function onKeyDown(state, collection, event) {
323
+ return Effect.gen(function* () {
324
+ const items = Collection.byDomOrder(yield* collection);
325
+ const next = moveActiveId(items, (yield* state).activeId, event);
326
+ if (next === undefined)
327
+ return yield* state;
328
+ event.preventDefault();
329
+ return yield* activate(state, next);
330
+ });
331
+ }
332
+ /**
333
+ * Maps grid keys to a cell id: horizontal movement stays in-row, vertical movement stays
334
+ * in-column, and Ctrl+Home/End reaches grid endpoints.
335
+ *
336
+ * @remarks
337
+ * ## Why
338
+ *
339
+ * Separating this deterministic policy from event wiring lets applications test it directly and
340
+ * reuse it in custom composites.
341
+ *
342
+ * ## Ownership and lifetime
343
+ *
344
+ * This is a synchronous calculation. It acquires no resources and does not mutate the input array,
345
+ * state, event, or DOM.
346
+ *
347
+ * @example
348
+ * ```ts
349
+ * import * as Grid from "@typed/ui/Grid";
350
+ *
351
+ * const nextId = Grid.moveActiveId([{ id: "a", value: { rowId: "r1", columnIndex: 1 } }], "a", { key: "ArrowRight", ctrlKey: false });
352
+ * ```
353
+ * @since 1.0.0
354
+ * @category combinators
355
+ */
356
+ export function moveActiveId(items, activeId, event) {
357
+ if (items.length === 0)
358
+ return undefined;
359
+ if (event.ctrlKey && event.key === "Home")
360
+ return items[0]?.id;
361
+ if (event.ctrlKey && event.key === "End")
362
+ return items.at(-1)?.id;
363
+ const active = activeId === null ? undefined : items.find((item) => item.id === activeId);
364
+ if (active === undefined || active.value === undefined)
365
+ return event.key === "Home" ? items[0]?.id : undefined;
366
+ const row = items.filter((item) => item.value?.rowId === active.value.rowId);
367
+ if (event.key === "ArrowLeft")
368
+ return row.at(Math.max(0, row.indexOf(active) - 1))?.id;
369
+ if (event.key === "ArrowRight")
370
+ return row.at(Math.min(row.length - 1, row.indexOf(active) + 1))?.id;
371
+ if (event.key === "Home")
372
+ return row[0]?.id;
373
+ if (event.key === "End")
374
+ return row.at(-1)?.id;
375
+ if (event.key !== "ArrowUp" && event.key !== "ArrowDown")
376
+ return undefined;
377
+ const column = items.filter((item) => item.value?.columnIndex === active.value.columnIndex);
378
+ return column.at(column.indexOf(active) + (event.key === "ArrowUp" ? -1 : 1))?.id ?? active.id;
379
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Group supplies a neutral ARIA group host and an optional label host. Callers retain the normal
3
+ * Dom host-override contract and must provide either an accessible label or a labelled-by
4
+ * relationship where the surrounding widget requires one.
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 type * as Scope from "effect/Scope";
16
+ import type { Fx } from "@typed/fx/Fx";
17
+ import { type Renderable, type RenderEvent, type RenderTemplate } from "@typed/template";
18
+ import * as Dom from "./Dom.js";
19
+ import type { HostResult } from "./Dom/Types.js";
20
+ /**
21
+ * Inputs accepted by Group.Group in addition to the shared DOM host options.
22
+ *
23
+ * @remarks
24
+ * ## Why
25
+ *
26
+ * The options type makes required state, content, accessible relationships, and custom-host inputs
27
+ * visible before rendering.
28
+ *
29
+ * ## Ownership and lifetime
30
+ *
31
+ * This declaration is data or schema metadata and acquires no resources.
32
+ *
33
+ * ## Example
34
+ *
35
+ * Import with `import type { GroupOptions } from "@typed/ui/Group";` Extend the [Group.Group
36
+ * runnable setup](/reference/%40typed%2Fui%2FGroup%23Group). A labeled group host accepts
37
+ * `const options: GroupOptions = { label: "Formatting", content: "Controls" }`.
38
+ * @since 1.0.0
39
+ * @category models
40
+ */
41
+ export interface GroupOptions extends Dom.HostOptions<HTMLDivElement> {
42
+ /**
43
+ * Renderable child content for the component host.
44
+ * @since 1.0.0
45
+ * @category models
46
+ */
47
+ readonly content: Renderable.Any;
48
+ /**
49
+ * Accessible label rendered through aria-label.
50
+ * @since 1.0.0
51
+ * @category models
52
+ */
53
+ readonly label?: Renderable.Any<string | null | undefined>;
54
+ /**
55
+ * Id of the external element used through aria-labelledby.
56
+ * @since 1.0.0
57
+ * @category models
58
+ */
59
+ readonly labelledBy?: Renderable.Any<string | null | undefined>;
60
+ }
61
+ declare function internalProps<const Options extends GroupOptions>({ property }: Dom.InternalPropsHelpers<Options>): {
62
+ role: string;
63
+ "aria-label": Exclude<Dom.Property<Options, "label">, Dom.Nullish> | undefined;
64
+ "aria-labelledby": Exclude<Dom.Property<Options, "labelledBy">, Dom.Nullish> | undefined;
65
+ };
66
+ type GroupInternalProps<Options extends GroupOptions> = ReturnType<typeof internalProps<Options>>;
67
+ /**
68
+ * Renders a div host with role=group and caller-controlled aria-label or aria-labelledby.
69
+ *
70
+ * @remarks
71
+ * ## Why
72
+ *
73
+ * The component applies the family behavior while leaving callers free to supply a custom host
74
+ * through the shared DOM boundary.
75
+ *
76
+ * ## Ownership and lifetime
77
+ *
78
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
79
+ * collection registrations only when rendered. The rendering Scope removes those resources;
80
+ * unrelated nodes and attributes remain caller-owned.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * import * as Group from "@typed/ui/Group";
85
+ *
86
+ * const view = Group.Group({ label: "Formatting", content: "Controls" });
87
+ * ```
88
+ * @since 1.0.0
89
+ * @category components
90
+ */
91
+ export declare function Group<const Options extends GroupOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, GroupInternalProps<Options>>, Options["content"], Host>): Fx<RenderEvent, Renderable.Error<Options | Host>, Renderable.Services<Options | Host> | Scope.Scope | RenderTemplate>;
92
+ /**
93
+ * Inputs accepted by Group.Label in addition to the shared DOM host options.
94
+ *
95
+ * @remarks
96
+ * ## Why
97
+ *
98
+ * The options type makes required state, content, accessible relationships, and custom-host inputs
99
+ * visible before rendering.
100
+ *
101
+ * ## Ownership and lifetime
102
+ *
103
+ * This declaration is data or schema metadata and acquires no resources.
104
+ *
105
+ * ## Example
106
+ *
107
+ * Import with `import type { LabelOptions } from "@typed/ui/Group";` Extend the [Group.Group
108
+ * runnable setup](/reference/%40typed%2Fui%2FGroup%23Group). A visible group label accepts
109
+ * `const options: LabelOptions = { content: "Formatting" }`.
110
+ * @since 1.0.0
111
+ * @category models
112
+ */
113
+ export interface LabelOptions extends Dom.HostOptions<HTMLSpanElement> {
114
+ /**
115
+ * Renderable child content for the component host.
116
+ * @since 1.0.0
117
+ * @category models
118
+ */
119
+ readonly content: Renderable.Any;
120
+ }
121
+ /**
122
+ * Renders the group's visible label content without inventing an implicit id relationship.
123
+ *
124
+ * @remarks
125
+ * ## Why
126
+ *
127
+ * The component applies the family behavior while leaving callers free to supply a custom host
128
+ * through the shared DOM boundary.
129
+ *
130
+ * ## Ownership and lifetime
131
+ *
132
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
133
+ * collection registrations only when rendered. The rendering Scope removes those resources;
134
+ * unrelated nodes and attributes remain caller-owned.
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * import * as Group from "@typed/ui/Group";
139
+ *
140
+ * const view = Group.Label({ content: "Formatting" });
141
+ * ```
142
+ * @since 1.0.0
143
+ * @category components
144
+ */
145
+ export declare function Label<const Options extends LabelOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, Record<never, never>>, Options["content"], Host>): Fx<RenderEvent, Renderable.Error<Options | Host>, Renderable.Services<Options | Host> | Scope.Scope | RenderTemplate>;
146
+ export {};
147
+ //# sourceMappingURL=Group.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Group.d.ts","sourceRoot":"","sources":["../src/Group.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;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;AAEjD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,YAAa,SAAQ,GAAG,CAAC,WAAW,CAAC,cAAc,CAAC;IACnE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAC3D;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACjE;AAED,iBAAS,aAAa,CAAC,KAAK,CAAC,OAAO,SAAS,YAAY,EAAE,EAAE,QAAQ,EAAE,EAAE,GAAG,CAAC,oBAAoB,CAAC,OAAO,CAAC;IAEtG,IAAI;IACJ,YAAY;IACZ,iBAAiB;EAEpB;AAED,KAAK,kBAAkB,CAAC,OAAO,SAAS,YAAY,IAAI,UAAU,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;AAElG;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,KAAK,CAAC,KAAK,CAAC,OAAO,SAAS,YAAY,EAAE,KAAK,CAAC,IAAI,SAAS,UAAU,GAAG,KAAK,EAC7F,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,GAC3G,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;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,YAAa,SAAQ,GAAG,CAAC,WAAW,CAAC,eAAe,CAAC;IACpE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,KAAK,CAAC,KAAK,CAAC,OAAO,SAAS,YAAY,EAAE,KAAK,CAAC,IAAI,SAAS,UAAU,GAAG,KAAK,EAC7F,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,GACpG,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"}
package/dist/Group.js ADDED
@@ -0,0 +1,63 @@
1
+ import { html } from "@typed/template";
2
+ import * as Dom from "./Dom.js";
3
+ function internalProps({ property }) {
4
+ return {
5
+ role: "group",
6
+ "aria-label": property("label", undefined),
7
+ "aria-labelledby": property("labelledBy", undefined),
8
+ };
9
+ }
10
+ /**
11
+ * Renders a div host with role=group and caller-controlled aria-label or aria-labelledby.
12
+ *
13
+ * @remarks
14
+ * ## Why
15
+ *
16
+ * The component applies the family behavior while leaving callers free to supply a custom host
17
+ * through the shared DOM boundary.
18
+ *
19
+ * ## Ownership and lifetime
20
+ *
21
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
22
+ * collection registrations only when rendered. The rendering Scope removes those resources;
23
+ * unrelated nodes and attributes remain caller-owned.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import * as Group from "@typed/ui/Group";
28
+ *
29
+ * const view = Group.Group({ label: "Formatting", content: "Controls" });
30
+ * ```
31
+ * @since 1.0.0
32
+ * @category components
33
+ */
34
+ export function Group(options, host) {
35
+ return Dom.renderHost()(options, host, internalProps, options.content, (props, content) => html `<div ...${props}>${content}</div>`);
36
+ }
37
+ /**
38
+ * Renders the group's visible label content without inventing an implicit id relationship.
39
+ *
40
+ * @remarks
41
+ * ## Why
42
+ *
43
+ * The component applies the family behavior while leaving callers free to supply a custom host
44
+ * through the shared DOM boundary.
45
+ *
46
+ * ## Ownership and lifetime
47
+ *
48
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
49
+ * collection registrations only when rendered. The rendering Scope removes those resources;
50
+ * unrelated nodes and attributes remain caller-owned.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * import * as Group from "@typed/ui/Group";
55
+ *
56
+ * const view = Group.Label({ content: "Formatting" });
57
+ * ```
58
+ * @since 1.0.0
59
+ * @category components
60
+ */
61
+ export function Label(options, host) {
62
+ return Dom.renderHost()(options, host, () => ({}), options.content, (props, content) => html `<span ...${props}>${content}</span>`);
63
+ }
@@ -0,0 +1,86 @@
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 an ARIA heading whose level may be reactive.
8
+ * @remarks
9
+ * ## Why
10
+ * Separating heading semantics from a fixed tag supports reusable components
11
+ * whose outline level is selected by their context.
12
+ * ## Ownership and lifetime
13
+ * The options are inert; rendering owns dynamic values for its Scope.
14
+ * @since 1.0.0
15
+ * @category models
16
+ */
17
+ export interface HeadingOptions extends Dom.HostOptions<HTMLDivElement> {
18
+ /** Heading content and accessible name.
19
+ * @remarks
20
+ * ## Why
21
+ * Visible content participates directly in the accessibility tree.
22
+ * ## Ownership and lifetime
23
+ * Dynamic content follows the rendered Scope.
24
+ * @since 1.0.0
25
+ * @category content
26
+ */
27
+ readonly content: Renderable.Any;
28
+ /** ARIA heading level, defaulting to one.
29
+ * @remarks
30
+ * ## Why
31
+ * The level communicates document hierarchy even when the host tag is fixed.
32
+ * ## Ownership and lifetime
33
+ * The reflected value retains no resources.
34
+ * @since 1.0.0
35
+ * @category accessibility
36
+ */
37
+ readonly level?: Renderable.Any<number | null | undefined>;
38
+ }
39
+ declare function internalProps<const Options extends HeadingOptions>({ property, }: Dom.InternalPropsHelpers<Options>): {
40
+ role: string;
41
+ "aria-level": 1 | Exclude<Dom.Property<Options, "level">, Dom.Nullish>;
42
+ };
43
+ type HeadingInternalProps<Options extends HeadingOptions> = ReturnType<typeof internalProps<Options>>;
44
+ /**
45
+ * Renders a host with `role="heading"` and a reactive `aria-level`.
46
+ * @remarks
47
+ * ## Why
48
+ * The primitive lets a design system preserve document-outline semantics when
49
+ * its visual host cannot be selected statically.
50
+ * ## Ownership and lifetime
51
+ * Running the returned Fx owns its dynamic attributes and content in an Effect
52
+ * Scope. A custom host must preserve `role` and `aria-level`.
53
+ * @example
54
+ * ```ts
55
+ * import { Heading } from "@typed/ui/Heading"
56
+ *
57
+ * const title = Heading({ level: 3, content: "Details" })
58
+ * ```
59
+ * @since 1.0.0
60
+ * @category components
61
+ */
62
+ export declare function Heading<const Options extends HeadingOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, HeadingInternalProps<Options>>, Options["content"], Host>): Fx<RenderEvent, Renderable.Error<Options | Host>, Renderable.Services<Options | Host> | Scope.Scope | RenderTemplate>;
63
+ /**
64
+ * Alias for `Heading` retained for level-oriented imports.
65
+ * @remarks
66
+ * ## Why
67
+ * The alias names the role the component plays in a contextual heading system.
68
+ * ## Ownership and lifetime
69
+ * It has exactly the same Scope and host ownership as `Heading`.
70
+ * @since 1.0.0
71
+ * @category aliases
72
+ */
73
+ export declare const Level: typeof Heading;
74
+ /**
75
+ * Descriptive alias for `Heading`.
76
+ * @remarks
77
+ * ## Why
78
+ * The name remains available without duplicating an implementation contract.
79
+ * ## Ownership and lifetime
80
+ * It has exactly the same Scope and host ownership as `Heading`.
81
+ * @since 1.0.0
82
+ * @category aliases
83
+ */
84
+ export declare const HeadingLevel: typeof Heading;
85
+ export {};
86
+ //# sourceMappingURL=Heading.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Heading.d.ts","sourceRoot":"","sources":["../src/Heading.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;AAEjD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,cAAe,SAAQ,GAAG,CAAC,WAAW,CAAC,cAAc,CAAC;IACrE;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CAC5D;AAED,iBAAS,aAAa,CAAC,KAAK,CAAC,OAAO,SAAS,cAAc,EAAE,EAC3D,QAAQ,GACT,EAAE,GAAG,CAAC,oBAAoB,CAAC,OAAO,CAAC;IACzB,IAAI;IAAa,YAAY;EACvC;AAED,KAAK,oBAAoB,CAAC,OAAO,SAAS,cAAc,IAAI,UAAU,CACpE,OAAO,aAAa,CAAC,OAAO,CAAC,CAC9B,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CACrB,KAAK,CAAC,OAAO,SAAS,cAAc,EACpC,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,oBAAoB,CAAC,OAAO,CAAC,CAAC,EAC3D,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;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,KAAK,gBAAU,CAAC;AAC7B;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,gBAAU,CAAC"}