@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.
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 +197 -54
  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 +42 -15
  153. package/src/HttpRouter.test.ts +0 -294
  154. package/src/HttpRouter.ts +0 -168
  155. package/src/Link.test.ts +0 -84
  156. package/src/Link.ts +0 -107
  157. package/src/index.ts +0 -2
package/dist/Tabs.js ADDED
@@ -0,0 +1,346 @@
1
+ /**
2
+ * Tabs separates active focus from selected tab and supports automatic or manual activation. List
3
+ * handles orientation-aware movement; Tab publishes stable aria-controls state; Panel retains its
4
+ * node while hidden.
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 Composite from "./Composite.js";
21
+ import * as Dom from "./Dom.js";
22
+ /**
23
+ * Effect Schema used by makeState to encode, decode, and hydrate Tabs 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 Tabs from "@typed/ui/Tabs";
39
+ *
40
+ * const decodeState = Schema.decodeUnknownEffect(Tabs.StateSchema);
41
+ * ```
42
+ * @since 1.0.0
43
+ * @category schemas
44
+ */
45
+ export const StateSchema = Schema.Struct({
46
+ selectedId: Schema.String,
47
+ activeId: Schema.String,
48
+ activationMode: Schema.Literals(["automatic", "manual"]),
49
+ orientation: Schema.Literals(["horizontal", "vertical"]),
50
+ loop: Schema.Boolean,
51
+ rtl: Schema.Boolean,
52
+ virtualFocus: Schema.Boolean,
53
+ });
54
+ /**
55
+ * Creates hydrated Tabs state. The caller supplies value; activeId defaults to value, activation
56
+ * is automatic, orientation horizontal, and loop true.
57
+ *
58
+ * @remarks
59
+ * ## Why
60
+ *
61
+ * State and collection ownership can be composed and tested independently from any renderer.
62
+ *
63
+ * ## Ownership and lifetime
64
+ *
65
+ * The returned Effect creates the RefSubject when run. That state is renderer-independent;
66
+ * collection registrations belong to the separate Scope that runs register or ref, not to state
67
+ * creation.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import * as Effect from "effect/Effect";
72
+ * import * as Tabs from "@typed/ui/Tabs";
73
+ *
74
+ * const program = Effect.scoped(
75
+ * Effect.gen(function* () {
76
+ * const state = yield* Tabs.makeState({ selectedId: "overview" });
77
+ * const collection = yield* Tabs.makeCollection();
78
+ * return { state: yield* state, collection: yield* collection };
79
+ * }),
80
+ * );
81
+ * ```
82
+ * @since 1.0.0
83
+ * @category constructors
84
+ */
85
+ export function makeState(initial) {
86
+ return RefSubject.hydrate(StateSchema, {
87
+ selectedId: initial.selectedId,
88
+ activeId: initial.activeId ?? initial.selectedId,
89
+ activationMode: initial.activationMode ?? "automatic",
90
+ orientation: initial.orientation ?? "horizontal",
91
+ loop: initial.loop ?? true,
92
+ rtl: initial.rtl ?? false,
93
+ virtualFocus: false,
94
+ });
95
+ }
96
+ /**
97
+ * Creates a scoped Collection for Tabs items.
98
+ *
99
+ * @remarks
100
+ * ## Why
101
+ *
102
+ * State and collection ownership can be composed and tested independently from any renderer.
103
+ *
104
+ * ## Ownership and lifetime
105
+ *
106
+ * The returned Effect allocates the RefSubject in the caller's Scope. Each later registration is
107
+ * owned by the Scope that runs register, independently of this construction Effect.
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * import * as Effect from "effect/Effect";
112
+ * import * as Tabs from "@typed/ui/Tabs";
113
+ *
114
+ * const program = Effect.scoped(
115
+ * Effect.gen(function* () {
116
+ * const collection = yield* Tabs.makeCollection();
117
+ * return yield* collection;
118
+ * }),
119
+ * );
120
+ * ```
121
+ * @since 1.0.0
122
+ * @category constructors
123
+ */
124
+ export const makeCollection = Collection.makeState;
125
+ /**
126
+ * Selects a tab and makes it the active roving-focus item.
127
+ *
128
+ * @remarks
129
+ * ## Why
130
+ *
131
+ * The operation exposes Tabs's transition directly so callers can compose it in Effect programs
132
+ * 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 { select } from "@typed/ui/Tabs";` Extend the [Tabs.makeState runnable
142
+ * setup](/reference/%40typed%2Fui%2FTabs%23makeState). Inside the linked Effect program invoke
143
+ * `yield* select(state, "settings")`, then read state to observe both `selectedId` and `activeId`
144
+ * become `"settings"`.
145
+ * @since 1.0.0
146
+ * @category combinators
147
+ */
148
+ export function select(state, selectedId) {
149
+ return RefSubject.update(state, (current) => ({ ...current, activeId: selectedId, selectedId }));
150
+ }
151
+ /**
152
+ * Moves activeId through registered tabs without selecting under manual activation.
153
+ *
154
+ * @remarks
155
+ * ## Why
156
+ *
157
+ * The operation exposes Tabs's transition directly so callers can compose it in Effect programs
158
+ * and native event handlers.
159
+ *
160
+ * ## Ownership and lifetime
161
+ *
162
+ * The returned Effect performs the update or DOM side effect only when run, preserves the declared
163
+ * error and service channels, and retains no resources after completion.
164
+ *
165
+ * ## Example
166
+ *
167
+ * Import with `import { move } from "@typed/ui/Tabs";` Extend the [Tabs.makeState runnable
168
+ * setup](/reference/%40typed%2Fui%2FTabs%23makeState). Inside the linked Effect program invoke
169
+ * `yield* move(state, yield* collection, "next")`, then read state to observe focus move—and, in
170
+ * automatic mode, selection move—to the next enabled tab.
171
+ * @since 1.0.0
172
+ * @category combinators
173
+ */
174
+ export function move(state, items, direction) {
175
+ return Effect.gen(function* () {
176
+ const current = yield* state;
177
+ const activeId = Composite.moveActiveId(items, current, direction);
178
+ if (activeId === null)
179
+ return current;
180
+ return yield* RefSubject.update(state, (value) => ({
181
+ ...value,
182
+ activeId,
183
+ selectedId: value.activationMode === "automatic" ? activeId : value.selectedId,
184
+ }));
185
+ });
186
+ }
187
+ function moveAndFocus(state, collection, direction) {
188
+ return Effect.gen(function* () {
189
+ const next = yield* move(state, yield* collection, direction);
190
+ yield* Composite.focusActive({ state, collection });
191
+ yield* Composite.scrollActive({ state, collection });
192
+ return next;
193
+ });
194
+ }
195
+ function listInternalProps(options) {
196
+ const orientation = RefSubject.map(options.state, (state) => state.orientation);
197
+ const onkeydown = options.collection === undefined && options.items === undefined
198
+ ? undefined
199
+ : EventHandler.make(Effect.fn(function* (event) {
200
+ const direction = Composite.keyMove(event, yield* options.state);
201
+ if (direction !== undefined) {
202
+ event.preventDefault();
203
+ if (options.collection !== undefined) {
204
+ yield* moveAndFocus(options.state, options.collection, direction);
205
+ }
206
+ else {
207
+ yield* move(options.state, options.items, direction);
208
+ }
209
+ return;
210
+ }
211
+ if ((event.key === "Enter" || event.key === " ") &&
212
+ (yield* options.state).activationMode === "manual") {
213
+ event.preventDefault();
214
+ yield* select(options.state, (yield* options.state).activeId);
215
+ }
216
+ }));
217
+ return ({ property }) => ({
218
+ role: "tablist",
219
+ "aria-label": property("label", undefined),
220
+ "aria-orientation": orientation,
221
+ onkeydown,
222
+ ref: options.state,
223
+ });
224
+ }
225
+ /**
226
+ * Renders the tablist and applies orientation-aware movement plus Enter/Space activation in manual
227
+ * mode.
228
+ *
229
+ * @remarks
230
+ * ## Why
231
+ *
232
+ * The component applies the family behavior while leaving callers free to supply a custom host
233
+ * through the shared DOM boundary.
234
+ *
235
+ * ## Ownership and lifetime
236
+ *
237
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
238
+ * collection registrations only when rendered. The rendering Scope removes those resources;
239
+ * unrelated nodes and attributes remain caller-owned.
240
+ *
241
+ * ## Example
242
+ *
243
+ * Import with `import { List } from "@typed/ui/Tabs";` Extend the [Tabs.makeState runnable
244
+ * setup](/reference/%40typed%2Fui%2FTabs%23makeState). Replace the linked program's final snapshot
245
+ * read with `List({ state, label: "Account", content: "Tabs" })`; render that Fx before the same
246
+ * Scope closes.
247
+ * @since 1.0.0
248
+ * @category components
249
+ */
250
+ export function List(options, host) {
251
+ return Dom.renderHost()(options, host, listInternalProps(options), options.content, (props, content) => {
252
+ return html `<div ...${props}>${content}</div>`;
253
+ });
254
+ }
255
+ function tabInternalProps(options) {
256
+ const selected = RefSubject.map(options.state, (state) => state.selectedId === options.id);
257
+ const register = options.collection === undefined
258
+ ? undefined
259
+ : Collection.ref(options.collection, {
260
+ id: options.id,
261
+ value: options.id,
262
+ textValue: options.id,
263
+ disabled: options.disabled,
264
+ });
265
+ return () => ({
266
+ id: options.id,
267
+ type: "button",
268
+ role: "tab",
269
+ "aria-controls": options.panelId,
270
+ "aria-selected": selected,
271
+ "aria-disabled": options.disabled ?? false,
272
+ tabindex: Composite.tabIndex(options.state, options.id),
273
+ onclick: options.disabled === true ? Effect.void : select(options.state, options.id),
274
+ onfocus: options.disabled === true
275
+ ? Effect.void
276
+ : RefSubject.update(options.state, (state) => state.activationMode === "automatic"
277
+ ? { ...state, activeId: options.id, selectedId: options.id }
278
+ : { ...state, activeId: options.id }),
279
+ ref: Dom.composeRefs(register, options.ref),
280
+ });
281
+ }
282
+ /**
283
+ * Renders and optionally registers a tab with stable aria-controls, selected state, and roving
284
+ * tabindex.
285
+ *
286
+ * @remarks
287
+ * ## Why
288
+ *
289
+ * The component applies the family behavior while leaving callers free to supply a custom host
290
+ * through the shared DOM boundary.
291
+ *
292
+ * ## Ownership and lifetime
293
+ *
294
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
295
+ * collection registrations only when rendered. The rendering Scope removes those resources;
296
+ * unrelated nodes and attributes remain caller-owned.
297
+ *
298
+ * ## Example
299
+ *
300
+ * Import with `import { Tab } from "@typed/ui/Tabs";` Extend the [Tabs.makeState runnable
301
+ * setup](/reference/%40typed%2Fui%2FTabs%23makeState). Replace the linked program's final snapshot
302
+ * read with `Tab({ state, id: "overview", panelId: "overview-panel", content: "Overview" })`;
303
+ * render that Fx before the same Scope closes.
304
+ * @since 1.0.0
305
+ * @category components
306
+ */
307
+ export function Tab(options, host) {
308
+ return Dom.renderHost()(options, host, tabInternalProps(options), options.content, (props, content) => html `<button ...${props}>${content}</button>`);
309
+ }
310
+ function panelInternalProps(options) {
311
+ const selected = RefSubject.map(options.state, (state) => state.selectedId === options.tabId);
312
+ return () => ({
313
+ id: options.id,
314
+ role: "tabpanel",
315
+ "aria-labelledby": options.tabId,
316
+ tabindex: 0,
317
+ "?hidden": RefSubject.map(selected, (value) => !value),
318
+ });
319
+ }
320
+ /**
321
+ * Renders a labelled tabpanel and hides it reactively when its tab is not selected.
322
+ *
323
+ * @remarks
324
+ * ## Why
325
+ *
326
+ * The component applies the family behavior while leaving callers free to supply a custom host
327
+ * through the shared DOM boundary.
328
+ *
329
+ * ## Ownership and lifetime
330
+ *
331
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
332
+ * collection registrations only when rendered. The rendering Scope removes those resources;
333
+ * unrelated nodes and attributes remain caller-owned.
334
+ *
335
+ * ## Example
336
+ *
337
+ * Import with `import { Panel } from "@typed/ui/Tabs";` Extend the [Tabs.makeState runnable
338
+ * setup](/reference/%40typed%2Fui%2FTabs%23makeState). Replace the linked program's final snapshot
339
+ * read with `Panel({ state, id: "overview-panel", tabId: "overview", content: "Account overview"
340
+ * })`; render that Fx before the same Scope closes.
341
+ * @since 1.0.0
342
+ * @category components
343
+ */
344
+ export function Panel(options, host) {
345
+ return Dom.renderHost()(options, host, panelInternalProps(options), options.content, (props, content) => html `<div ...${props}>${content}</div>`);
346
+ }