@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.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -10
  3. package/dist/Alert.d.ts +72 -0
  4. package/dist/Alert.d.ts.map +1 -0
  5. package/dist/Alert.js +40 -0
  6. package/dist/Button.d.ts +100 -0
  7. package/dist/Button.d.ts.map +1 -0
  8. package/dist/Button.js +42 -0
  9. package/dist/Carousel.d.ts +346 -0
  10. package/dist/Carousel.d.ts.map +1 -0
  11. package/dist/Carousel.js +264 -0
  12. package/dist/Checkbox.d.ts +168 -0
  13. package/dist/Checkbox.d.ts.map +1 -0
  14. package/dist/Checkbox.js +146 -0
  15. package/dist/Collection.d.ts +254 -0
  16. package/dist/Collection.d.ts.map +1 -0
  17. package/dist/Collection.js +218 -0
  18. package/dist/Combobox.d.ts +367 -0
  19. package/dist/Combobox.d.ts.map +1 -0
  20. package/dist/Combobox.js +300 -0
  21. package/dist/Composite.d.ts +823 -0
  22. package/dist/Composite.d.ts.map +1 -0
  23. package/dist/Composite.js +615 -0
  24. package/dist/Dialog.d.ts +544 -0
  25. package/dist/Dialog.d.ts.map +1 -0
  26. package/dist/Dialog.js +357 -0
  27. package/dist/Disclosure.d.ts +219 -0
  28. package/dist/Disclosure.d.ts.map +1 -0
  29. package/dist/Disclosure.js +128 -0
  30. package/dist/Dom/Events.d.ts +122 -0
  31. package/dist/Dom/Events.d.ts.map +1 -0
  32. package/dist/Dom/Events.js +192 -0
  33. package/dist/Dom/Props.d.ts +161 -0
  34. package/dist/Dom/Props.d.ts.map +1 -0
  35. package/dist/Dom/Props.js +110 -0
  36. package/dist/Dom/Refs.d.ts +58 -0
  37. package/dist/Dom/Refs.d.ts.map +1 -0
  38. package/dist/Dom/Refs.js +61 -0
  39. package/dist/Dom/Render.d.ts +59 -0
  40. package/dist/Dom/Render.d.ts.map +1 -0
  41. package/dist/Dom/Render.js +71 -0
  42. package/dist/Dom/Types.d.ts +570 -0
  43. package/dist/Dom/Types.d.ts.map +1 -0
  44. package/dist/Dom/Types.js +1 -0
  45. package/dist/Dom/index.d.ts +20 -0
  46. package/dist/Dom/index.d.ts.map +1 -0
  47. package/dist/Dom/index.js +8 -0
  48. package/dist/Dom.d.ts +14 -0
  49. package/dist/Dom.d.ts.map +1 -0
  50. package/dist/Dom.js +13 -0
  51. package/dist/Focusable.d.ts +85 -0
  52. package/dist/Focusable.d.ts.map +1 -0
  53. package/dist/Focusable.js +35 -0
  54. package/dist/Form.d.ts +1727 -0
  55. package/dist/Form.d.ts.map +1 -0
  56. package/dist/Form.js +1142 -0
  57. package/dist/Grid.d.ts +388 -0
  58. package/dist/Grid.d.ts.map +1 -0
  59. package/dist/Grid.js +284 -0
  60. package/dist/Group.d.ts +128 -0
  61. package/dist/Group.d.ts.map +1 -0
  62. package/dist/Group.js +71 -0
  63. package/dist/Heading.d.ts +87 -0
  64. package/dist/Heading.d.ts.map +1 -0
  65. package/dist/Heading.js +58 -0
  66. package/dist/Hovercard.d.ts +297 -0
  67. package/dist/Hovercard.d.ts.map +1 -0
  68. package/dist/Hovercard.js +188 -0
  69. package/dist/HttpRouter.d.ts +129 -6
  70. package/dist/HttpRouter.d.ts.map +1 -1
  71. package/dist/HttpRouter.js +198 -55
  72. package/dist/Link.d.ts +63 -28
  73. package/dist/Link.d.ts.map +1 -1
  74. package/dist/Link.js +84 -37
  75. package/dist/Listbox.d.ts +305 -0
  76. package/dist/Listbox.d.ts.map +1 -0
  77. package/dist/Listbox.js +245 -0
  78. package/dist/Menu.d.ts +663 -0
  79. package/dist/Menu.d.ts.map +1 -0
  80. package/dist/Menu.js +569 -0
  81. package/dist/Menubar.d.ts +249 -0
  82. package/dist/Menubar.d.ts.map +1 -0
  83. package/dist/Menubar.js +207 -0
  84. package/dist/Meter.d.ts +157 -0
  85. package/dist/Meter.d.ts.map +1 -0
  86. package/dist/Meter.js +87 -0
  87. package/dist/NativeDetails.d.ts +41 -0
  88. package/dist/NativeDetails.d.ts.map +1 -0
  89. package/dist/NativeDetails.js +40 -0
  90. package/dist/NativeDialog.d.ts +66 -0
  91. package/dist/NativeDialog.d.ts.map +1 -0
  92. package/dist/NativeDialog.js +88 -0
  93. package/dist/NativePopover.d.ts +43 -0
  94. package/dist/NativePopover.d.ts.map +1 -0
  95. package/dist/NativePopover.js +84 -0
  96. package/dist/Popover.d.ts +240 -0
  97. package/dist/Popover.d.ts.map +1 -0
  98. package/dist/Popover.js +140 -0
  99. package/dist/RadioGroup.d.ts +330 -0
  100. package/dist/RadioGroup.d.ts.map +1 -0
  101. package/dist/RadioGroup.js +241 -0
  102. package/dist/Role.d.ts +64 -0
  103. package/dist/Role.d.ts.map +1 -0
  104. package/dist/Role.js +27 -0
  105. package/dist/Select.d.ts +418 -0
  106. package/dist/Select.d.ts.map +1 -0
  107. package/dist/Select.js +357 -0
  108. package/dist/Separator.d.ts +58 -0
  109. package/dist/Separator.d.ts.map +1 -0
  110. package/dist/Separator.js +32 -0
  111. package/dist/Slider.d.ts +141 -0
  112. package/dist/Slider.d.ts.map +1 -0
  113. package/dist/Slider.js +101 -0
  114. package/dist/SpinButton.d.ts +141 -0
  115. package/dist/SpinButton.d.ts.map +1 -0
  116. package/dist/SpinButton.js +101 -0
  117. package/dist/Storybook.d.ts +76 -0
  118. package/dist/Storybook.d.ts.map +1 -0
  119. package/dist/Storybook.js +102 -0
  120. package/dist/Switch.d.ts +148 -0
  121. package/dist/Switch.d.ts.map +1 -0
  122. package/dist/Switch.js +110 -0
  123. package/dist/Tab.d.ts +26 -0
  124. package/dist/Tab.d.ts.map +1 -0
  125. package/dist/Tab.js +25 -0
  126. package/dist/Tabs.d.ts +411 -0
  127. package/dist/Tabs.d.ts.map +1 -0
  128. package/dist/Tabs.js +262 -0
  129. package/dist/Toolbar.d.ts +248 -0
  130. package/dist/Toolbar.d.ts.map +1 -0
  131. package/dist/Toolbar.js +187 -0
  132. package/dist/Tooltip.d.ts +296 -0
  133. package/dist/Tooltip.d.ts.map +1 -0
  134. package/dist/Tooltip.js +172 -0
  135. package/dist/Tree.d.ts +405 -0
  136. package/dist/Tree.d.ts.map +1 -0
  137. package/dist/Tree.js +333 -0
  138. package/dist/TreeGrid.d.ts +426 -0
  139. package/dist/TreeGrid.d.ts.map +1 -0
  140. package/dist/TreeGrid.js +308 -0
  141. package/dist/VisuallyHidden.d.ts +68 -0
  142. package/dist/VisuallyHidden.d.ts.map +1 -0
  143. package/dist/VisuallyHidden.js +44 -0
  144. package/dist/WindowSplitter.d.ts +336 -0
  145. package/dist/WindowSplitter.d.ts.map +1 -0
  146. package/dist/WindowSplitter.js +305 -0
  147. package/dist/index.d.ts +48 -0
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +48 -0
  150. package/package.json +48 -20
  151. package/src/HttpRouter.test.ts +0 -294
  152. package/src/HttpRouter.ts +0 -168
  153. package/src/Link.test.ts +0 -84
  154. package/src/Link.ts +0 -107
  155. package/src/index.ts +0 -2
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Listbox separates active and selected values, registers options in a Collection, and combines
3
+ * vertical keyboard movement with buffered typeahead. It supports roving focus by default and uses
4
+ * real focus, keydown, and click events.
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 [Listbox guide](/explore/ui-listbox).
12
+ *
13
+ * @since 1.0.0
14
+ * @category modules
15
+ * @packageDocumentation
16
+ */
17
+ import * as Effect from "effect/Effect";
18
+ import * as Schema from "effect/Schema";
19
+ import { RefSubject } from "@typed/fx";
20
+ import { EventHandler, html, } from "@typed/template";
21
+ import * as Collection from "./Collection.js";
22
+ import * as Composite from "./Composite.js";
23
+ import * as Dom from "./Dom.js";
24
+ /**
25
+ * Effect Schema used by makeState to encode, decode, and hydrate Listbox state.
26
+ *
27
+ * @remarks
28
+ * @example
29
+ * ```ts
30
+ * import * as Schema from "effect/Schema";
31
+ * import * as Listbox from "@typed/ui/Listbox";
32
+ *
33
+ * const decodeState = Schema.decodeUnknownEffect(Listbox.StateSchema);
34
+ * ```
35
+ * @since 1.0.0
36
+ * @category Selection and focus
37
+ */
38
+ export const StateSchema = Schema.Struct({
39
+ value: Schema.NullOr(Schema.String),
40
+ activeId: Schema.NullOr(Schema.String),
41
+ orientation: Schema.Literals(["vertical"]),
42
+ loop: Schema.Boolean,
43
+ rtl: Schema.Boolean,
44
+ virtualFocus: Schema.Boolean,
45
+ });
46
+ /**
47
+ * Creates hydrated Listbox state. value and activeId default to null, loop defaults to true, and
48
+ * roving focus is used.
49
+ *
50
+ * @remarks
51
+ * The returned Effect creates the RefSubject when run. That state is renderer-independent;
52
+ * collection registrations belong to the separate Scope that runs register or ref, not to state
53
+ * creation.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * import * as Effect from "effect/Effect";
58
+ * import * as Listbox from "@typed/ui/Listbox";
59
+ *
60
+ * const program = Effect.scoped(
61
+ * Effect.gen(function* () {
62
+ * const state = yield* Listbox.makeState({});
63
+ * const collection = yield* Listbox.makeCollection();
64
+ * return { state: yield* state, collection: yield* collection };
65
+ * }),
66
+ * );
67
+ * ```
68
+ * @since 1.0.0
69
+ * @category Selection and focus
70
+ */
71
+ export function makeState(initial = {}) {
72
+ return RefSubject.hydrate(StateSchema, {
73
+ value: initial.value ?? null,
74
+ activeId: initial.activeId ?? null,
75
+ orientation: "vertical",
76
+ loop: initial.loop ?? true,
77
+ rtl: false,
78
+ virtualFocus: false,
79
+ });
80
+ }
81
+ /**
82
+ * Creates a scoped Collection for Listbox items.
83
+ *
84
+ * @remarks
85
+ * The returned Effect allocates the RefSubject in the caller's Scope. Each later registration is
86
+ * owned by the Scope that runs register, independently of this construction Effect.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * import * as Effect from "effect/Effect";
91
+ * import * as Listbox from "@typed/ui/Listbox";
92
+ *
93
+ * const program = Effect.scoped(
94
+ * Effect.gen(function* () {
95
+ * const collection = yield* Listbox.makeCollection();
96
+ * return yield* collection;
97
+ * }),
98
+ * );
99
+ * ```
100
+ * @since 1.0.0
101
+ * @category Option registration
102
+ */
103
+ export const makeCollection = Collection.makeState;
104
+ /**
105
+ * Sets both activeId and selected value in one update.
106
+ *
107
+ * @remarks
108
+ * The operation exposes Listbox's transition directly so callers can compose it in Effect programs
109
+ * and native event handlers.
110
+ *
111
+ * @since 1.0.0
112
+ * @category Selection and focus
113
+ */
114
+ export function select(state, id, value) {
115
+ return RefSubject.update(state, (current) => ({ ...current, activeId: id, value }));
116
+ }
117
+ /**
118
+ * Moves through the registered options, synchronizes value when the target has one, then focuses
119
+ * and scrolls the active option.
120
+ *
121
+ * @remarks
122
+ * The operation exposes Listbox's transition directly so callers can compose it in Effect programs
123
+ * and native event handlers.
124
+ *
125
+ * @since 1.0.0
126
+ * @category Selection and focus
127
+ */
128
+ export function move(state, collection, direction) {
129
+ return Effect.gen(function* () {
130
+ const current = yield* state;
131
+ const items = yield* collection;
132
+ const activeId = Composite.moveActiveId(items, current, direction);
133
+ const item = activeId === null ? undefined : items.find((item) => item.id === activeId);
134
+ const next = yield* RefSubject.update(state, (value) => ({
135
+ ...value,
136
+ activeId,
137
+ value: item?.value ?? value.value,
138
+ }));
139
+ yield* Composite.focusActive({ state, collection });
140
+ yield* Composite.scrollActive({ state, collection });
141
+ return next;
142
+ });
143
+ }
144
+ function rootProps(options) {
145
+ let typeahead = { value: "", updatedAt: 0 };
146
+ const onfocus = options.collection === undefined
147
+ ? undefined
148
+ : Effect.gen(function* () {
149
+ const current = yield* options.state;
150
+ if (current.activeId !== null)
151
+ return;
152
+ const item = Composite.moveActiveItem(yield* options.collection, current, "first");
153
+ if (item?.value === undefined)
154
+ return;
155
+ yield* select(options.state, item.id, item.value);
156
+ yield* Composite.focusActive({ state: options.state, collection: options.collection });
157
+ yield* Composite.scrollActive({ state: options.state, collection: options.collection });
158
+ });
159
+ const onkeydown = options.collection === undefined
160
+ ? undefined
161
+ : EventHandler.make(Effect.fn(function* (event) {
162
+ const key = Composite.typeaheadKey(event);
163
+ if (key !== null) {
164
+ typeahead = Composite.updateTypeaheadBuffer(typeahead, key, Date.now());
165
+ const id = Composite.typeaheadFrom(yield* options.collection, typeahead.value, (yield* options.state).activeId);
166
+ if (id !== null) {
167
+ event.preventDefault();
168
+ const item = (yield* options.collection).find((item) => item.id === id);
169
+ if (item?.value !== undefined)
170
+ yield* select(options.state, item.id, item.value);
171
+ yield* Composite.focusActive({
172
+ state: options.state,
173
+ collection: options.collection,
174
+ });
175
+ }
176
+ return;
177
+ }
178
+ const direction = Composite.keyMove(event, { orientation: "vertical" });
179
+ if (direction !== undefined) {
180
+ event.preventDefault();
181
+ yield* move(options.state, options.collection, direction);
182
+ }
183
+ }));
184
+ return ({ property }) => ({
185
+ role: "listbox",
186
+ "aria-label": property("label", undefined),
187
+ "aria-activedescendant": Composite.activeDescendant(options.state),
188
+ tabindex: Composite.rootTabIndex(options.state),
189
+ onfocus,
190
+ onkeydown,
191
+ ref: options.state,
192
+ });
193
+ }
194
+ /**
195
+ * Renders the listbox root, initializes selection on focus, and handles vertical movement and
196
+ * typeahead in DOM order.
197
+ *
198
+ * @remarks
199
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
200
+ * collection registrations only when rendered. The rendering Scope removes those resources;
201
+ * unrelated nodes and attributes remain caller-owned.
202
+ *
203
+ * @since 1.0.0
204
+ * @category Listbox surface
205
+ */
206
+ export function Root(options, host) {
207
+ return Dom.renderHost()(options, host, rootProps(options), options.content, (props, content) => {
208
+ return html `<div ...${props}>${content}</div>`;
209
+ });
210
+ }
211
+ function optionProps(options) {
212
+ const selected = RefSubject.map(options.state, (state) => state.value === options.value);
213
+ const register = options.collection === undefined
214
+ ? undefined
215
+ : Collection.ref(options.collection, {
216
+ id: options.id,
217
+ value: options.value,
218
+ textValue: options.textValue ?? options.value,
219
+ disabled: options.disabled,
220
+ });
221
+ return () => ({
222
+ id: options.id,
223
+ role: "option",
224
+ "aria-selected": selected,
225
+ "aria-disabled": options.disabled ?? false,
226
+ tabindex: Composite.tabIndex(options.state, options.id),
227
+ onclick: options.disabled === true ? Effect.void : select(options.state, options.id, options.value),
228
+ onfocus: options.disabled === true ? Effect.void : select(options.state, options.id, options.value),
229
+ ref: Dom.composeRefs(register, options.ref),
230
+ });
231
+ }
232
+ /**
233
+ * Renders and optionally registers an option; focus or click selects it unless disabled.
234
+ *
235
+ * @remarks
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
+ * @since 1.0.0
241
+ * @category Selectable options
242
+ */
243
+ export function Option(options, host) {
244
+ return Dom.renderHost()(options, host, optionProps(options), options.content, (props, content) => html `<div ...${props}>${content}</div>`);
245
+ }