@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/Select.js ADDED
@@ -0,0 +1,407 @@
1
+ /**
2
+ * Select keeps popover visibility, active option, and selected value separate. A native button
3
+ * targets manual-popover listbox content; keyboard movement and typeahead update active state
4
+ * before selection closes the popover.
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 Scope from "effect/Scope";
17
+ import * as Schema from "effect/Schema";
18
+ import { RefSubject } from "@typed/fx";
19
+ import { EventHandler, html, } from "@typed/template";
20
+ import * as Collection from "./Collection.js";
21
+ import * as Composite from "./Composite.js";
22
+ import * as Dom from "./Dom.js";
23
+ import * as NativePopover from "./NativePopover.js";
24
+ const invokers = new WeakMap();
25
+ /**
26
+ * Effect Schema used by makeState to encode, decode, and hydrate Select state.
27
+ *
28
+ * @remarks
29
+ * ## Why
30
+ *
31
+ * A public schema makes hydration and serialized state use the same runtime validation as direct
32
+ * construction.
33
+ *
34
+ * ## Ownership and lifetime
35
+ *
36
+ * This declaration is data or schema metadata and acquires no resources.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import * as Schema from "effect/Schema";
41
+ * import * as Select from "@typed/ui/Select";
42
+ *
43
+ * const decodeState = Schema.decodeUnknownEffect(Select.StateSchema);
44
+ * ```
45
+ * @since 1.0.0
46
+ * @category schemas
47
+ */
48
+ export const StateSchema = Schema.Struct({
49
+ id: Schema.String,
50
+ value: Schema.NullOr(Schema.String),
51
+ open: Schema.Boolean,
52
+ activeId: Schema.NullOr(Schema.String),
53
+ orientation: Schema.Literals(["vertical"]),
54
+ loop: Schema.Boolean,
55
+ rtl: Schema.Boolean,
56
+ virtualFocus: Schema.Boolean,
57
+ });
58
+ /**
59
+ * Creates hydrated Select state. The caller supplies id; value and activeId default null, open
60
+ * false, and loop true.
61
+ *
62
+ * @remarks
63
+ * ## Why
64
+ *
65
+ * State and collection ownership can be composed and tested independently from any renderer.
66
+ *
67
+ * ## Ownership and lifetime
68
+ *
69
+ * The returned Effect creates the RefSubject when run. That state is renderer-independent;
70
+ * collection registrations belong to the separate Scope that runs register or ref, not to state
71
+ * creation.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * import * as Effect from "effect/Effect";
76
+ * import * as Select from "@typed/ui/Select";
77
+ *
78
+ * const program = Effect.scoped(
79
+ * Effect.gen(function* () {
80
+ * const state = yield* Select.makeState({ id: "timezone" });
81
+ * const collection = yield* Select.makeCollection();
82
+ * return { state: yield* state, collection: yield* collection };
83
+ * }),
84
+ * );
85
+ * ```
86
+ * @since 1.0.0
87
+ * @category constructors
88
+ */
89
+ export function makeState(initial) {
90
+ return RefSubject.hydrate(StateSchema, {
91
+ id: initial.id,
92
+ value: initial.value ?? null,
93
+ open: initial.open ?? false,
94
+ activeId: initial.activeId ?? null,
95
+ orientation: "vertical",
96
+ loop: initial.loop ?? true,
97
+ rtl: false,
98
+ virtualFocus: false,
99
+ });
100
+ }
101
+ /**
102
+ * Creates a scoped Collection for Select items.
103
+ *
104
+ * @remarks
105
+ * ## Why
106
+ *
107
+ * State and collection ownership can be composed and tested independently from any renderer.
108
+ *
109
+ * ## Ownership and lifetime
110
+ *
111
+ * The returned Effect allocates the RefSubject in the caller's Scope. Each later registration is
112
+ * owned by the Scope that runs register, independently of this construction Effect.
113
+ *
114
+ * @example
115
+ * ```ts
116
+ * import * as Effect from "effect/Effect";
117
+ * import * as Select from "@typed/ui/Select";
118
+ *
119
+ * const program = Effect.scoped(
120
+ * Effect.gen(function* () {
121
+ * const collection = yield* Select.makeCollection();
122
+ * return yield* collection;
123
+ * }),
124
+ * );
125
+ * ```
126
+ * @since 1.0.0
127
+ * @category constructors
128
+ */
129
+ export const makeCollection = Collection.makeState;
130
+ /**
131
+ * Sets selected value and active id, then closes the popover.
132
+ *
133
+ * @remarks
134
+ * ## Why
135
+ *
136
+ * The operation exposes Select's transition directly so callers can compose it in Effect programs
137
+ * and native event handlers.
138
+ *
139
+ * ## Ownership and lifetime
140
+ *
141
+ * The returned Effect performs the update or DOM side effect only when run, preserves the declared
142
+ * error and service channels, and retains no resources after completion.
143
+ *
144
+ * ## Example
145
+ *
146
+ * Import with `import { select } from "@typed/ui/Select";` Extend the [Select.makeState runnable
147
+ * setup](/reference/%40typed%2Fui%2FSelect%23makeState). Inside the linked Effect program invoke
148
+ * `yield* select(state, "utc", "UTC")`, then read state to observe selection and active focus
149
+ * update together while the popup closes.
150
+ * @since 1.0.0
151
+ * @category combinators
152
+ */
153
+ export function select(state, id, value) {
154
+ return RefSubject.update(state, (current) => ({ ...current, activeId: id, value, open: false }));
155
+ }
156
+ function move(state, collection, direction) {
157
+ return Composite.moveAndFocus({ state, collection }, direction);
158
+ }
159
+ function selectActive(state, collection) {
160
+ return Effect.gen(function* () {
161
+ const activeId = (yield* state).activeId;
162
+ const item = activeId === null ? undefined : (yield* collection).find((item) => item.id === activeId);
163
+ return item?.value === undefined ? yield* state : yield* select(state, item.id, item.value);
164
+ });
165
+ }
166
+ function triggerProps(options) {
167
+ const id = RefSubject.map(options.state, (state) => state.id);
168
+ const triggerId = RefSubject.map(options.state, (state) => `${state.id}-trigger`);
169
+ const open = RefSubject.map(options.state, (state) => state.open);
170
+ return () => ({
171
+ id: triggerId,
172
+ type: "button",
173
+ popovertarget: id,
174
+ popovertargetaction: "toggle",
175
+ "aria-haspopup": "listbox",
176
+ "aria-expanded": open,
177
+ onkeydown: EventHandler.make((event) => {
178
+ if (event.key !== "ArrowDown")
179
+ return;
180
+ event.preventDefault();
181
+ Dom.currentTarget(event).click();
182
+ }),
183
+ ref: Dom.composeRefs(options.state, invokerRef(options.state)),
184
+ });
185
+ }
186
+ function invokerRef(state) {
187
+ return Effect.fn(function* (element) {
188
+ invokers.set(state, element);
189
+ const scope = yield* Effect.scope;
190
+ yield* Scope.addFinalizer(scope, Effect.sync(() => {
191
+ if (invokers.get(state) === element)
192
+ invokers.delete(state);
193
+ }));
194
+ });
195
+ }
196
+ /**
197
+ * Renders the native button that targets listbox popover content and opens it on ArrowDown.
198
+ *
199
+ * @remarks
200
+ * ## Why
201
+ *
202
+ * The component applies the family behavior while leaving callers free to supply a custom host
203
+ * through the shared DOM boundary.
204
+ *
205
+ * ## Ownership and lifetime
206
+ *
207
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
208
+ * collection registrations only when rendered. The rendering Scope removes those resources;
209
+ * unrelated nodes and attributes remain caller-owned.
210
+ *
211
+ * ## Example
212
+ *
213
+ * Import with `import { Trigger } from "@typed/ui/Select";` Extend the [Select.makeState runnable
214
+ * setup](/reference/%40typed%2Fui%2FSelect%23makeState). Replace the linked program's final
215
+ * snapshot read with `Trigger({ state, content: "Choose timezone" })`; render that Fx before the
216
+ * same Scope closes.
217
+ * @since 1.0.0
218
+ * @category components
219
+ */
220
+ export function Trigger(options, host) {
221
+ return Dom.renderHost()(options, host, triggerProps(options), options.content, (props, content) => html `<button ...${props}>${content}</button>`);
222
+ }
223
+ /**
224
+ * Consumer-facing alias of the canonical Select component with identical behavior and lifetime.
225
+ *
226
+ * @remarks
227
+ * ## Why
228
+ *
229
+ * The component applies the family behavior while leaving callers free to supply a custom host
230
+ * through the shared DOM boundary.
231
+ *
232
+ * ## Ownership and lifetime
233
+ *
234
+ * The alias acquires nothing. Rendering it has exactly the canonical component's Scope and DOM
235
+ * ownership contract.
236
+ *
237
+ * ## Example
238
+ *
239
+ * Import with `import { Select } from "@typed/ui/Select";` Extend the [Select.makeState runnable
240
+ * setup](/reference/%40typed%2Fui%2FSelect%23makeState). Replace the linked program's final
241
+ * snapshot read with `Select({ state, content: "Choose timezone" })`; render that Fx before the
242
+ * same Scope closes.
243
+ * @since 1.0.0
244
+ * @category components
245
+ */
246
+ export const Select = Trigger;
247
+ function contentProps(options) {
248
+ const id = RefSubject.map(options.state, (state) => state.id);
249
+ const triggerId = RefSubject.map(options.state, (state) => `${state.id}-trigger`);
250
+ let typeahead = { value: "", updatedAt: 0 };
251
+ let restoreInvokerFocus = false;
252
+ const restoreFocus = Effect.gen(function* () {
253
+ if (!restoreInvokerFocus)
254
+ return;
255
+ restoreInvokerFocus = false;
256
+ yield* Composite.focusElement(invokers.get(options.state));
257
+ });
258
+ const onkeydown = options.collection === undefined
259
+ ? undefined
260
+ : EventHandler.make(Effect.fn(function* (event) {
261
+ const direction = Composite.keyMove(event, { orientation: "vertical" });
262
+ if (direction !== undefined) {
263
+ event.preventDefault();
264
+ yield* move(options.state, options.collection, direction);
265
+ return;
266
+ }
267
+ if (event.key === "Enter" || event.key === " ") {
268
+ event.preventDefault();
269
+ yield* selectActive(options.state, options.collection);
270
+ return;
271
+ }
272
+ if (event.key === "Escape") {
273
+ event.preventDefault();
274
+ restoreInvokerFocus = true;
275
+ yield* setOpen(options.state, false);
276
+ return;
277
+ }
278
+ const key = Composite.typeaheadKey(event);
279
+ if (key === null)
280
+ return;
281
+ typeahead = Composite.updateTypeaheadBuffer(typeahead, key, Date.now());
282
+ const activeId = Composite.typeaheadFrom(yield* options.collection, typeahead.value, (yield* options.state).activeId);
283
+ if (activeId === null)
284
+ return;
285
+ event.preventDefault();
286
+ yield* RefSubject.update(options.state, (state) => ({ ...state, activeId }));
287
+ yield* Composite.focusActive({ state: options.state, collection: options.collection });
288
+ }));
289
+ return () => ({
290
+ id,
291
+ role: "listbox",
292
+ "aria-labelledby": triggerId,
293
+ popover: "manual",
294
+ "aria-activedescendant": Composite.activeDescendant(options.state),
295
+ onkeydown,
296
+ ontoggle: EventHandler.make(Effect.fn(function* (event) {
297
+ const open = Dom.toggleState(event) === "open";
298
+ const current = yield* options.state;
299
+ if (current.open === open) {
300
+ if (!open)
301
+ yield* restoreFocus;
302
+ return current;
303
+ }
304
+ const next = yield* RefSubject.update(options.state, (state) => ({ ...state, open }));
305
+ if (!open) {
306
+ yield* restoreFocus;
307
+ return next;
308
+ }
309
+ if (options.collection === undefined)
310
+ return next;
311
+ const item = (yield* options.collection).find((item) => item.value === next.value);
312
+ if (item === undefined)
313
+ return next;
314
+ const selected = yield* RefSubject.update(options.state, (state) => ({
315
+ ...state,
316
+ activeId: item.id,
317
+ }));
318
+ yield* Composite.focusActive({ state: options.state, collection: options.collection });
319
+ yield* Composite.scrollActive({ state: options.state, collection: options.collection });
320
+ return selected;
321
+ })),
322
+ ref: Dom.composeRefs(options.state, NativePopover.ref(options.state)),
323
+ });
324
+ }
325
+ /**
326
+ * Renders manual-popover listbox content and handles Arrow keys, Enter, Escape, and typeahead.
327
+ *
328
+ * @remarks
329
+ * ## Why
330
+ *
331
+ * The component applies the family behavior while leaving callers free to supply a custom host
332
+ * through the shared DOM boundary.
333
+ *
334
+ * ## Ownership and lifetime
335
+ *
336
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
337
+ * collection registrations only when rendered. The rendering Scope removes those resources;
338
+ * unrelated nodes and attributes remain caller-owned.
339
+ *
340
+ * ## Example
341
+ *
342
+ * Import with `import { Content } from "@typed/ui/Select";` Extend the [Select.makeState runnable
343
+ * setup](/reference/%40typed%2Fui%2FSelect%23makeState). Replace the linked program's final
344
+ * snapshot read with `Content({ state, content: "Timezones" })`; render that Fx before the same
345
+ * Scope closes.
346
+ * @since 1.0.0
347
+ * @category components
348
+ */
349
+ export function Content(options, host) {
350
+ return Dom.renderHost()(options, host, contentProps(options), options.content, (props, content) => {
351
+ return html `<div ...${props}>${content}</div>`;
352
+ });
353
+ }
354
+ function optionProps(options) {
355
+ const selected = RefSubject.map(options.state, (state) => state.value === options.value);
356
+ const register = options.collection === undefined
357
+ ? undefined
358
+ : Collection.ref(options.collection, {
359
+ id: options.id,
360
+ value: options.value,
361
+ textValue: options.textValue ?? options.value,
362
+ disabled: options.disabled,
363
+ });
364
+ return () => ({
365
+ id: options.id,
366
+ role: "option",
367
+ "aria-selected": selected,
368
+ "aria-disabled": options.disabled ?? false,
369
+ tabindex: Composite.tabIndex(options.state, options.id),
370
+ onclick: options.disabled === true ? Effect.void : select(options.state, options.id, options.value),
371
+ onfocus: options.disabled === true
372
+ ? Effect.void
373
+ : RefSubject.update(options.state, (state) => ({ ...state, activeId: options.id })),
374
+ ref: Dom.composeRefs(register, options.ref),
375
+ });
376
+ }
377
+ /**
378
+ * Renders and optionally registers a selectable option; focus updates activeId and click selects
379
+ * unless disabled.
380
+ *
381
+ * @remarks
382
+ * ## Why
383
+ *
384
+ * The component applies the family behavior while leaving callers free to supply a custom host
385
+ * through the shared DOM boundary.
386
+ *
387
+ * ## Ownership and lifetime
388
+ *
389
+ * The returned Fx installs DOM refs, native listeners, state subscriptions, and optional
390
+ * collection registrations only when rendered. The rendering Scope removes those resources;
391
+ * unrelated nodes and attributes remain caller-owned.
392
+ *
393
+ * ## Example
394
+ *
395
+ * Import with `import { Option } from "@typed/ui/Select";` Extend the [Select.makeState runnable
396
+ * setup](/reference/%40typed%2Fui%2FSelect%23makeState). Replace the linked program's final
397
+ * snapshot read with `Option({ state, id: "utc", value: "UTC", content: "UTC" })`; render that Fx
398
+ * before the same Scope closes.
399
+ * @since 1.0.0
400
+ * @category components
401
+ */
402
+ export function Option(options, host) {
403
+ return Dom.renderHost()(options, host, optionProps(options), options.content, (props, content) => html `<div ...${props}>${content}</div>`);
404
+ }
405
+ function setOpen(state, open) {
406
+ return RefSubject.update(state, (current) => ({ ...current, open }));
407
+ }
@@ -0,0 +1,54 @@
1
+ import type * as Scope from "effect/Scope";
2
+ import type { Fx } from "@typed/fx/Fx";
3
+ import { type Renderable, type RenderEvent, type RenderTemplate } from "@typed/template";
4
+ import * as Dom from "./Dom.js";
5
+ import type { HostResult } from "./Dom/Types.js";
6
+ /**
7
+ * Options for a non-interactive ARIA separator.
8
+ * @remarks
9
+ * ## Why
10
+ * Orientation communicates whether the visual division is horizontal or
11
+ * vertical without implying splitter interaction.
12
+ * ## Ownership and lifetime
13
+ * The model is inert; dynamic orientation follows the rendered Scope.
14
+ * @since 1.0.0
15
+ * @category models
16
+ */
17
+ export interface SeparatorOptions extends Dom.HostOptions<HTMLDivElement> {
18
+ /** Separator orientation, defaulting to horizontal.
19
+ * @remarks
20
+ * ## Why
21
+ * Assistive technology uses orientation to interpret the division.
22
+ * ## Ownership and lifetime
23
+ * The dynamic attribute subscription ends with the component Scope.
24
+ * @since 1.0.0
25
+ * @category accessibility
26
+ */
27
+ readonly orientation?: Renderable.Any<"horizontal" | "vertical" | null | undefined>;
28
+ }
29
+ declare function internalProps<const Options extends SeparatorOptions>({ property, }: Dom.InternalPropsHelpers<Options>): {
30
+ role: string;
31
+ "aria-orientation": "horizontal" | Exclude<Dom.Property<Options, "orientation">, Dom.Nullish>;
32
+ };
33
+ type SeparatorInternalProps<Options extends SeparatorOptions> = ReturnType<typeof internalProps<Options>>;
34
+ /**
35
+ * Renders a non-focusable element with `role="separator"`.
36
+ * @remarks
37
+ * ## Why
38
+ * This component expresses a semantic division. Use `WindowSplitter` when the
39
+ * separator changes pane sizes and therefore requires keyboard interaction.
40
+ * ## Ownership and lifetime
41
+ * Running the Fx owns its reactive attribute until the Effect Scope closes. A
42
+ * custom host must preserve the role and orientation.
43
+ * @example
44
+ * ```ts
45
+ * import { Separator } from "@typed/ui/Separator"
46
+ *
47
+ * const divider = Separator({ orientation: "vertical" })
48
+ * ```
49
+ * @since 1.0.0
50
+ * @category components
51
+ */
52
+ export declare function Separator<const Options extends SeparatorOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, SeparatorInternalProps<Options>>, "", Host>): Fx<RenderEvent, Renderable.Error<Options | Host>, Renderable.Services<Options | Host> | Scope.Scope | RenderTemplate>;
53
+ export {};
54
+ //# sourceMappingURL=Separator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Separator.d.ts","sourceRoot":"","sources":["../src/Separator.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,gBAAiB,SAAQ,GAAG,CAAC,WAAW,CAAC,cAAc,CAAC;IACvE;;;;;;;;OAQG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,YAAY,GAAG,UAAU,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACrF;AAED,iBAAS,aAAa,CAAC,KAAK,CAAC,OAAO,SAAS,gBAAgB,EAAE,EAC7D,QAAQ,GACT,EAAE,GAAG,CAAC,oBAAoB,CAAC,OAAO,CAAC;IACzB,IAAI;IAAe,kBAAkB;EAC/C;AAED,KAAK,sBAAsB,CAAC,OAAO,SAAS,gBAAgB,IAAI,UAAU,CACxE,OAAO,aAAa,CAAC,OAAO,CAAC,CAC9B,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,SAAS,CACvB,KAAK,CAAC,OAAO,SAAS,gBAAgB,EACtC,KAAK,CAAC,IAAI,SAAS,UAAU,GAAG,KAAK,EAErC,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,sBAAsB,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,GAC/F,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,26 @@
1
+ import { html } from "@typed/template";
2
+ import * as Dom from "./Dom.js";
3
+ function internalProps({ property, }) {
4
+ return { role: "separator", "aria-orientation": property("orientation", "horizontal") };
5
+ }
6
+ /**
7
+ * Renders a non-focusable element with `role="separator"`.
8
+ * @remarks
9
+ * ## Why
10
+ * This component expresses a semantic division. Use `WindowSplitter` when the
11
+ * separator changes pane sizes and therefore requires keyboard interaction.
12
+ * ## Ownership and lifetime
13
+ * Running the Fx owns its reactive attribute until the Effect Scope closes. A
14
+ * custom host must preserve the role and orientation.
15
+ * @example
16
+ * ```ts
17
+ * import { Separator } from "@typed/ui/Separator"
18
+ *
19
+ * const divider = Separator({ orientation: "vertical" })
20
+ * ```
21
+ * @since 1.0.0
22
+ * @category components
23
+ */
24
+ export function Separator(options, host) {
25
+ return Dom.renderHost()(options, host, internalProps, "", (props) => html `<div ...${props}></div>`);
26
+ }
@@ -0,0 +1,188 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ import { RefSubject } from "@typed/fx";
4
+ import { EventHandler, type Renderable } from "@typed/template";
5
+ import * as Dom from "./Dom.js";
6
+ import type { HostResult } from "./Dom/Types.js";
7
+ /** Current renderer-independent slider value.
8
+ * @remarks
9
+ * ## Why
10
+ * Value logic remains testable without mounting a range input.
11
+ * ## Ownership and lifetime
12
+ * Plain state is resource-free; RefSubject ownership is Scope-based.
13
+ * @since 1.0.0
14
+ * @category state
15
+ */
16
+ export interface State {
17
+ /** Finite value reflected by the native input.
18
+ * @remarks
19
+ * ## Why
20
+ * A single numeric source prevents state and DOM drift.
21
+ * ## Ownership and lifetime
22
+ * Plain data acquires no resources.
23
+ * @since 1.0.0
24
+ * @category state
25
+ */
26
+ readonly value: number;
27
+ }
28
+ /** Initial slider value.
29
+ * @remarks
30
+ * ## Why
31
+ * Explicit state gives SSR and hydration the same numeric snapshot.
32
+ * ## Ownership and lifetime
33
+ * Configuration is inert.
34
+ * @since 1.0.0
35
+ * @category state
36
+ */
37
+ export interface InitialState {
38
+ /** Finite initial value.
39
+ * @remarks
40
+ * ## Why
41
+ * It seeds the synchronized input state.
42
+ * ## Ownership and lifetime
43
+ * Plain data retains no resources.
44
+ * @since 1.0.0
45
+ * @category state
46
+ */
47
+ readonly value: number;
48
+ }
49
+ /** Schema for slider hydration state.
50
+ * @remarks
51
+ * ## Why
52
+ * Finite validation prevents invalid serialized range values.
53
+ * ## Ownership and lifetime
54
+ * The immutable schema acquires no resources.
55
+ * @since 1.0.0
56
+ * @category schemas
57
+ */
58
+ export declare const StateSchema: Schema.Struct<{
59
+ readonly value: Schema.Finite;
60
+ }>;
61
+ /** Creates hydrated slider state.
62
+ * @remarks
63
+ * ## Why
64
+ * State can be composed and tested independently of the renderer.
65
+ * ## Ownership and lifetime
66
+ * The calling Effect Scope owns the returned RefSubject.
67
+ * @example
68
+ * ```ts
69
+ * import { Effect } from "effect"
70
+ * import * as Slider from "@typed/ui/Slider"
71
+ *
72
+ * const program = Effect.gen(function* () {
73
+ * return yield* Slider.makeState({ value: 50 })
74
+ * })
75
+ * ```
76
+ * @since 1.0.0
77
+ * @category constructors
78
+ */
79
+ export declare function makeState(initial: InitialState): Effect.Effect<RefSubject.HydratedRefSubject<{
80
+ readonly value: number;
81
+ }, Schema.SchemaError, never, never>, never, import("effect/Scope").Scope>;
82
+ /** Updates the synchronized slider value.
83
+ * @remarks
84
+ * ## Why
85
+ * An explicit Effect transition preserves RefSubject failures and services.
86
+ * ## Ownership and lifetime
87
+ * It uses the existing state lifetime and acquires no resource.
88
+ * @example
89
+ * ```ts
90
+ * import { Effect } from "effect"
91
+ * import * as Slider from "@typed/ui/Slider"
92
+ *
93
+ * const program = Effect.gen(function* () {
94
+ * const state = yield* Slider.makeState({ value: 25 })
95
+ * yield* Slider.setValue(state, 50)
96
+ * })
97
+ * ```
98
+ * @since 1.0.0
99
+ * @category state
100
+ */
101
+ export declare function setValue<E, R>(state: RefSubject.RefSubject<State, E, R>, value: number): Effect.Effect<State, E, R>;
102
+ /** Options for a native range input.
103
+ * @remarks
104
+ * ## Why
105
+ * Native range semantics provide keyboard and accessibility behavior while
106
+ * Typed synchronizes renderer-independent state.
107
+ * ## Ownership and lifetime
108
+ * Options are inert; the mounted component owns subscriptions by Scope.
109
+ * @since 1.0.0
110
+ * @category models
111
+ */
112
+ export interface SliderOptions extends Dom.HostOptions<HTMLInputElement> {
113
+ /** Hydrated state synchronized to the input.
114
+ * @remarks
115
+ * ## Why
116
+ * One source serves SSR, UI, and state-only tests.
117
+ * ## Ownership and lifetime
118
+ * The component borrows state and subscribes only while mounted.
119
+ * @since 1.0.0
120
+ * @category state
121
+ */
122
+ readonly state: RefSubject.HydratedRefSubject<State, Schema.SchemaError>;
123
+ /** Native minimum value.
124
+ * @remarks
125
+ * ## Why
126
+ * The platform uses this bound for keyboard and constraint behavior.
127
+ * ## Ownership and lifetime
128
+ * Dynamic attributes follow the component Scope.
129
+ * @since 1.0.0
130
+ * @category attributes
131
+ */
132
+ readonly min?: Renderable.Any<number | null | undefined>;
133
+ /** Native maximum value.
134
+ * @remarks
135
+ * ## Why
136
+ * The platform uses this bound for keyboard and constraint behavior.
137
+ * ## Ownership and lifetime
138
+ * Dynamic attributes follow the component Scope.
139
+ * @since 1.0.0
140
+ * @category attributes
141
+ */
142
+ readonly max?: Renderable.Any<number | null | undefined>;
143
+ /** Native step value or `"any"`.
144
+ * @remarks
145
+ * ## Why
146
+ * Step semantics remain delegated to the input element.
147
+ * ## Ownership and lifetime
148
+ * Dynamic attributes follow the component Scope.
149
+ * @since 1.0.0
150
+ * @category attributes
151
+ */
152
+ readonly step?: Renderable.Any<number | "any" | null | undefined>;
153
+ }
154
+ declare function internalProps<const Options extends SliderOptions>(options: Options): ({ property }: Dom.InternalPropsHelpers<Options>) => {
155
+ readonly type: "range";
156
+ readonly value: RefSubject.Computed<number, Schema.SchemaError, never>;
157
+ readonly ".value": RefSubject.Computed<string, Schema.SchemaError, never>;
158
+ readonly min: Exclude<Dom.Property<Options, "min">, Dom.Nullish> | undefined;
159
+ readonly max: Exclude<Dom.Property<Options, "max">, Dom.Nullish> | undefined;
160
+ readonly step: Exclude<Dom.Property<Options, "step">, Dom.Nullish> | undefined;
161
+ readonly oninput: EventHandler.EventHandler<Event, Schema.SchemaError, never>;
162
+ readonly ref: RefSubject.HydratedRefSubject<State, Schema.SchemaError, never, never>;
163
+ };
164
+ type SliderInternalProps<Options extends SliderOptions> = ReturnType<ReturnType<typeof internalProps<Options>>>;
165
+ /** Renders a native range input synchronized with hydrated state.
166
+ * @remarks
167
+ * ## Why
168
+ * Browser pointer, keyboard, constraints, and accessibility remain intact;
169
+ * input events update the same RefSubject consumed elsewhere.
170
+ * ## Ownership and lifetime
171
+ * Running the Fx owns native listeners and subscriptions in its Scope. A
172
+ * custom host must preserve type, value, range props, and the hydration ref.
173
+ * @example
174
+ * ```ts
175
+ * import { Effect } from "effect"
176
+ * import * as Slider from "@typed/ui/Slider"
177
+ *
178
+ * const program = Effect.gen(function* () {
179
+ * const state = yield* Slider.makeState({ value: 50 })
180
+ * return Slider.Slider({ state, min: 0, max: 100 })
181
+ * })
182
+ * ```
183
+ * @since 1.0.0
184
+ * @category components
185
+ */
186
+ export declare function Slider<const Options extends SliderOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, SliderInternalProps<Options>>, "", Host>): import("./Dom/Types.js").HostComponent<Host | Options>;
187
+ export {};
188
+ //# sourceMappingURL=Slider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Slider.d.ts","sourceRoot":"","sources":["../src/Slider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,YAAY,EAAQ,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;;;;GAQG;AACH,MAAM,WAAW,KAAK;IACpB;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW;;EAA0C,CAAC;AAEnE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY;;2EAE9C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAC3B,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,EACzC,KAAK,EAAE,MAAM,GACZ,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAE5B;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAc,SAAQ,GAAG,CAAC,WAAW,CAAC,gBAAgB,CAAC;IACtE;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IACzE;;;;;;;;OAQG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACzD;;;;;;;;OAQG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACzD;;;;;;;;OAQG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,KAAK,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACnE;AAED,iBAAS,aAAa,CAAC,KAAK,CAAC,OAAO,SAAS,aAAa,EAAE,OAAO,EAAE,OAAO,kBACpD,GAAG,CAAC,oBAAoB,CAAC,OAAO,CAAC;aAEnD,IAAI,EAAE,OAAO;aACb,KAAK;aACL,QAAQ;aACR,GAAG;aACH,GAAG;aACH,IAAI;aACJ,OAAO;aAKP,GAAG;EAER;AACD,KAAK,mBAAmB,CAAC,OAAO,SAAS,aAAa,IAAI,UAAU,CAClE,UAAU,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC,CAC1C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,MAAM,CAAC,KAAK,CAAC,OAAO,SAAS,aAAa,EAAE,KAAK,CAAC,IAAI,SAAS,UAAU,GAAG,KAAK,EAC/F,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,0DAW9F"}