@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
package/dist/Select.js ADDED
@@ -0,0 +1,357 @@
1
+ /**
2
+ * A native popover-backed listbox with separate selection, focus, and visibility.
3
+ * Unlike Form.Select, these options are divs rather than native select options.
4
+ * Pass one collection to Content and its Options for registered keyboard navigation.
5
+ *
6
+ * Read the [Select guide](/explore/ui-select) for a complete example.
7
+ *
8
+ * [APG interaction reference](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/).
9
+ * @since 1.0.0
10
+ * @category Overview
11
+ * @packageDocumentation
12
+ */
13
+ import * as Effect from "effect/Effect";
14
+ import * as Scope from "effect/Scope";
15
+ import * as Schema from "effect/Schema";
16
+ import { RefSubject } from "@typed/fx";
17
+ import { EventHandler, html, } from "@typed/template";
18
+ import * as Collection from "./Collection.js";
19
+ import * as Composite from "./Composite.js";
20
+ import * as Dom from "./Dom.js";
21
+ import * as NativePopover from "./NativePopover.js";
22
+ const invokers = new WeakMap();
23
+ /**
24
+ * Effect Schema used by makeState to encode, decode, and hydrate Select state.
25
+ *
26
+ * @remarks
27
+ * A public schema makes hydration and serialized state use the same runtime validation as direct
28
+ * construction.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import * as Schema from "effect/Schema";
33
+ * import * as Select from "@typed/ui/Select";
34
+ *
35
+ * const decodeState = Schema.decodeUnknownEffect(Select.StateSchema);
36
+ * ```
37
+ * @since 1.0.0
38
+ * @category Hydration schemas
39
+ */
40
+ export const StateSchema = Schema.Struct({
41
+ id: Schema.String,
42
+ value: Schema.NullOr(Schema.String),
43
+ open: Schema.Boolean,
44
+ activeId: Schema.NullOr(Schema.String),
45
+ orientation: Schema.Literals(["vertical"]),
46
+ loop: Schema.Boolean,
47
+ rtl: Schema.Boolean,
48
+ virtualFocus: Schema.Boolean,
49
+ });
50
+ /**
51
+ * Creates hydrated Select state. The caller supplies id; value and activeId default null, open
52
+ * false, and loop true.
53
+ *
54
+ * @remarks
55
+ * State and collection ownership can be composed and tested independently from any renderer.
56
+ *
57
+ * The returned Effect creates the RefSubject when run. That state is renderer-independent;
58
+ * collection registrations belong to the separate Scope that runs register or ref, not to state
59
+ * creation.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * import * as Effect from "effect/Effect";
64
+ * import * as Select from "@typed/ui/Select";
65
+ *
66
+ * const program = Effect.scoped(
67
+ * Effect.gen(function* () {
68
+ * const state = yield* Select.makeState({ id: "timezone" });
69
+ * const collection = yield* Select.makeCollection();
70
+ * return { state: yield* state, collection: yield* collection };
71
+ * }),
72
+ * );
73
+ * ```
74
+ * @since 1.0.0
75
+ * @category State construction
76
+ */
77
+ export function makeState(initial) {
78
+ return RefSubject.hydrate(StateSchema, {
79
+ id: initial.id,
80
+ value: initial.value ?? null,
81
+ open: initial.open ?? false,
82
+ activeId: initial.activeId ?? null,
83
+ orientation: "vertical",
84
+ loop: initial.loop ?? true,
85
+ rtl: false,
86
+ virtualFocus: false,
87
+ });
88
+ }
89
+ /**
90
+ * Creates a scoped Collection for Select items.
91
+ *
92
+ * @remarks
93
+ * State and collection ownership can be composed and tested independently from any renderer.
94
+ *
95
+ * The returned Effect allocates the RefSubject in the caller's Scope. Each later registration is
96
+ * owned by the Scope that runs register, independently of this construction Effect.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * import * as Effect from "effect/Effect";
101
+ * import * as Select from "@typed/ui/Select";
102
+ *
103
+ * const program = Effect.scoped(
104
+ * Effect.gen(function* () {
105
+ * const collection = yield* Select.makeCollection();
106
+ * return yield* collection;
107
+ * }),
108
+ * );
109
+ * ```
110
+ * @since 1.0.0
111
+ * @category State construction
112
+ */
113
+ export const makeCollection = Collection.makeState;
114
+ /**
115
+ * Commits an option ID and value, then marks the popup closed.
116
+ *
117
+ * @remarks
118
+ * This Effect changes state only. It does not check collection membership or move focus itself;
119
+ * the mounted popover observes the visibility change. Keep id and value consistent with the
120
+ * option being selected.
121
+ * @since 1.0.0
122
+ * @category State transitions
123
+ */
124
+ export function select(state, id, value) {
125
+ return RefSubject.update(state, (current) => ({ ...current, activeId: id, value, open: false }));
126
+ }
127
+ function move(state, collection, direction) {
128
+ return Composite.moveAndFocus({ state, collection }, direction);
129
+ }
130
+ function selectActive(state, collection) {
131
+ return Effect.gen(function* () {
132
+ const activeId = (yield* state).activeId;
133
+ const item = activeId === null ? undefined : (yield* collection).find((item) => item.id === activeId);
134
+ return item?.value === undefined ? yield* state : yield* select(state, item.id, item.value);
135
+ });
136
+ }
137
+ function triggerProps(options) {
138
+ const id = RefSubject.map(options.state, (state) => state.id);
139
+ const triggerId = RefSubject.map(options.state, (state) => `${state.id}-trigger`);
140
+ const open = RefSubject.map(options.state, (state) => state.open);
141
+ return () => ({
142
+ id: triggerId,
143
+ type: "button",
144
+ popovertarget: id,
145
+ popovertargetaction: "toggle",
146
+ "aria-haspopup": "listbox",
147
+ "aria-expanded": open,
148
+ onkeydown: EventHandler.make((event) => {
149
+ if (event.key !== "ArrowDown")
150
+ return;
151
+ event.preventDefault();
152
+ Dom.currentTarget(event).click();
153
+ }),
154
+ ref: Dom.composeRefs(options.state, invokerRef(options.state)),
155
+ });
156
+ }
157
+ function invokerRef(state) {
158
+ return Effect.fn(function* (element) {
159
+ invokers.set(state, element);
160
+ const scope = yield* Effect.scope;
161
+ yield* Scope.addFinalizer(scope, Effect.sync(() => {
162
+ if (invokers.get(state) === element)
163
+ invokers.delete(state);
164
+ }));
165
+ });
166
+ }
167
+ /**
168
+ * Renders the native button that toggles the select listbox popover.
169
+ *
170
+ * @remarks
171
+ * The generated button ID labels Content; popovertarget points to state.id. ArrowDown invokes
172
+ * the native button click. Content synchronizes native toggle events with open state. Render a
173
+ * useful name containing the current choice; the trigger does not format selected values for
174
+ * you.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * import { RefSubject } from "@typed/fx";
179
+ * import { html } from "@typed/template";
180
+ * import { component } from "@typed/template";
181
+ * import * as Select from "@typed/ui/Select";
182
+ *
183
+ * export const DensityPicker = component(function* () {
184
+ * const state = yield* Select.makeState({ id: "density-options", value: "comfortable" });
185
+ * const collection = yield* Select.makeCollection();
186
+ * const label = RefSubject.map(state, ({ value }) =>
187
+ * value === "compact" ? "Density: compact" : "Density: comfortable",
188
+ * );
189
+ * return html`<div class="density-picker">
190
+ * ${Select.Trigger({ state, content: label })}
191
+ * ${Select.Content({
192
+ * state, collection,
193
+ * content: [
194
+ * Select.Option({
195
+ * state, collection, id: "density-comfortable", value: "comfortable",
196
+ * textValue: "Comfortable", content: "Comfortable",
197
+ * }),
198
+ * Select.Option({
199
+ * state, collection, id: "density-compact", value: "compact",
200
+ * textValue: "Compact", content: "Compact",
201
+ * }),
202
+ * ],
203
+ * })}
204
+ * </div>`;
205
+ * });
206
+ * ```
207
+ * @since 1.0.0
208
+ * @category Controls
209
+ */
210
+ export function Trigger(options, host) {
211
+ return Dom.renderHost()(options, host, triggerProps(options), options.content, (props, content) => html `<button ...${props}>${content}</button>`);
212
+ }
213
+ /**
214
+ * Alias of Trigger.
215
+ *
216
+ * @remarks
217
+ * Calling Select alone creates only the invoker button. Compose Content and Option for the popup
218
+ * choice surface; use Form.Select for a native select element.
219
+ * @since 1.0.0
220
+ * @category Controls
221
+ */
222
+ export const Select = Trigger;
223
+ function contentProps(options) {
224
+ const id = RefSubject.map(options.state, (state) => state.id);
225
+ const triggerId = RefSubject.map(options.state, (state) => `${state.id}-trigger`);
226
+ let typeahead = { value: "", updatedAt: 0 };
227
+ let restoreInvokerFocus = false;
228
+ const restoreFocus = Effect.gen(function* () {
229
+ if (!restoreInvokerFocus)
230
+ return;
231
+ restoreInvokerFocus = false;
232
+ yield* Composite.focusElement(invokers.get(options.state));
233
+ });
234
+ const onkeydown = options.collection === undefined
235
+ ? undefined
236
+ : EventHandler.make(Effect.fn(function* (event) {
237
+ const direction = Composite.keyMove(event, { orientation: "vertical" });
238
+ if (direction !== undefined) {
239
+ event.preventDefault();
240
+ yield* move(options.state, options.collection, direction);
241
+ return;
242
+ }
243
+ if (event.key === "Enter" || event.key === " ") {
244
+ event.preventDefault();
245
+ yield* selectActive(options.state, options.collection);
246
+ return;
247
+ }
248
+ if (event.key === "Escape") {
249
+ event.preventDefault();
250
+ restoreInvokerFocus = true;
251
+ yield* setOpen(options.state, false);
252
+ return;
253
+ }
254
+ const key = Composite.typeaheadKey(event);
255
+ if (key === null)
256
+ return;
257
+ typeahead = Composite.updateTypeaheadBuffer(typeahead, key, Date.now());
258
+ const activeId = Composite.typeaheadFrom(yield* options.collection, typeahead.value, (yield* options.state).activeId);
259
+ if (activeId === null)
260
+ return;
261
+ event.preventDefault();
262
+ yield* RefSubject.update(options.state, (state) => ({ ...state, activeId }));
263
+ yield* Composite.focusActive({ state: options.state, collection: options.collection });
264
+ }));
265
+ return () => ({
266
+ id,
267
+ role: "listbox",
268
+ "aria-labelledby": triggerId,
269
+ popover: "manual",
270
+ "aria-activedescendant": Composite.activeDescendant(options.state),
271
+ onkeydown,
272
+ ontoggle: EventHandler.make(Effect.fn(function* (event) {
273
+ const open = Dom.toggleState(event) === "open";
274
+ const current = yield* options.state;
275
+ if (current.open === open) {
276
+ if (!open)
277
+ yield* restoreFocus;
278
+ return current;
279
+ }
280
+ const next = yield* RefSubject.update(options.state, (state) => ({ ...state, open }));
281
+ if (!open) {
282
+ yield* restoreFocus;
283
+ return next;
284
+ }
285
+ if (options.collection === undefined)
286
+ return next;
287
+ const item = (yield* options.collection).find((item) => item.value === next.value);
288
+ if (item === undefined)
289
+ return next;
290
+ const selected = yield* RefSubject.update(options.state, (state) => ({
291
+ ...state,
292
+ activeId: item.id,
293
+ }));
294
+ yield* Composite.focusActive({ state: options.state, collection: options.collection });
295
+ yield* Composite.scrollActive({ state: options.state, collection: options.collection });
296
+ return selected;
297
+ })),
298
+ ref: Dom.composeRefs(options.state, NativePopover.ref(options.state)),
299
+ });
300
+ }
301
+ /**
302
+ * Renders the manual-popover listbox and coordinates registered keyboard movement.
303
+ *
304
+ * @remarks
305
+ * With a collection, arrows and Home/End move focus, buffered typeahead locates options,
306
+ * Enter/Space commits the active choice, and Escape closes with invoker focus restoration.
307
+ * Opening focuses a registered option matching the selected value; an unmatched or null value
308
+ * does not choose the first option. Manual popover mode does not imply auto-mode outside-click
309
+ * dismissal.
310
+ * @since 1.0.0
311
+ * @category Controls
312
+ */
313
+ export function Content(options, host) {
314
+ return Dom.renderHost()(options, host, contentProps(options), options.content, (props, content) => {
315
+ return html `<div ...${props}>${content}</div>`;
316
+ });
317
+ }
318
+ function optionProps(options) {
319
+ const selected = RefSubject.map(options.state, (state) => state.value === options.value);
320
+ const register = options.collection === undefined
321
+ ? undefined
322
+ : Collection.ref(options.collection, {
323
+ id: options.id,
324
+ value: options.value,
325
+ textValue: options.textValue ?? options.value,
326
+ disabled: options.disabled,
327
+ });
328
+ return () => ({
329
+ id: options.id,
330
+ role: "option",
331
+ "aria-selected": selected,
332
+ "aria-disabled": options.disabled ?? false,
333
+ tabindex: Composite.tabIndex(options.state, options.id),
334
+ onclick: options.disabled === true ? Effect.void : select(options.state, options.id, options.value),
335
+ onfocus: options.disabled === true
336
+ ? Effect.void
337
+ : RefSubject.update(options.state, (state) => ({ ...state, activeId: options.id })),
338
+ ref: Dom.composeRefs(register, options.ref),
339
+ });
340
+ }
341
+ /**
342
+ * Renders a selectable listbox option with distinct active and selected state.
343
+ *
344
+ * @remarks
345
+ * Click commits value and closes the popup; focus updates activeId without committing a new
346
+ * value. Register every option with the same collection used by Content for keyboard navigation.
347
+ * Supply textValue for readable typeahead when value is an internal code. This div is not a
348
+ * native form field.
349
+ * @since 1.0.0
350
+ * @category Controls
351
+ */
352
+ export function Option(options, host) {
353
+ return Dom.renderHost()(options, host, optionProps(options), options.content, (props, content) => html `<div ...${props}>${content}</div>`);
354
+ }
355
+ function setOpen(state, open) {
356
+ return RefSubject.update(state, (current) => ({ ...current, open }));
357
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * A non-focusable semantic boundary with an orientation attribute.
3
+ * CSS draws the line; WindowSplitter supplies the separate adjustable-pane interaction.
4
+ *
5
+ * Read the [Separator guide](/explore/ui-separator) for a complete example.
6
+ *
7
+ * [Platform reference](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/separator_role).
8
+ * @since 1.0.0
9
+ * @category Overview
10
+ * @packageDocumentation
11
+ */
12
+ import type * as Scope from "effect/Scope";
13
+ import type { Fx } from "@typed/fx/Fx";
14
+ import { type Renderable, type RenderEvent, type RenderTemplate } from "@typed/template";
15
+ import * as Dom from "./Dom.js";
16
+ import type { HostResult } from "./Dom/Types.js";
17
+ /**
18
+ */
19
+ export interface SeparatorOptions extends Dom.HostOptions<HTMLDivElement> {
20
+ /**
21
+ * Separator orientation, defaulting to horizontal.
22
+ * @since 1.0.0
23
+ * @category Keyboard navigation
24
+ */
25
+ readonly orientation?: Renderable.Any<"horizontal" | "vertical" | null | undefined>;
26
+ }
27
+ declare function internalProps<const Options extends SeparatorOptions>({ property, }: Dom.InternalPropsHelpers<Options>): {
28
+ role: string;
29
+ "aria-orientation": "horizontal" | Exclude<Dom.Property<Options, "orientation">, Dom.Nullish>;
30
+ };
31
+ type SeparatorInternalProps<Options extends SeparatorOptions> = ReturnType<typeof internalProps<Options>>;
32
+ /**
33
+ * Renders a non-interactive semantic division.
34
+ *
35
+ * @remarks
36
+ * orientation defaults to horizontal and changes aria-orientation, not layout or dimensions.
37
+ * Supply the visible line through CSS. Do not make this host focusable to imitate a resizer;
38
+ * WindowSplitter owns that separate interaction.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { html } from "@typed/template";
43
+ * import { Heading } from "@typed/ui/Heading";
44
+ * import { Separator } from "@typed/ui/Separator";
45
+ *
46
+ * export const AccountSummary = html`<section>
47
+ * ${Heading({ level: 2, content: "Account summary" })}
48
+ * <p>Personal account</p>
49
+ * ${Separator({ props: { class: "summary-divider" } })}
50
+ * <p>Next renewal: September 30</p>
51
+ * </section>`;
52
+ * ```
53
+ * @since 1.0.0
54
+ * @category Structure and naming
55
+ */
56
+ 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>;
57
+ export {};
58
+ //# sourceMappingURL=Separator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Separator.d.ts","sourceRoot":"","sources":["../src/Separator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAQ,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC/F,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD;GACG;AACH,MAAM,WAAW,gBAAiB,SAAQ,GAAG,CAAC,WAAW,CAAC,cAAc,CAAC;IACvE;;;;OAIG;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;;;;;;;;;;;;;;;;;;;;;;;GAuBG;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,32 @@
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-interactive semantic division.
8
+ *
9
+ * @remarks
10
+ * orientation defaults to horizontal and changes aria-orientation, not layout or dimensions.
11
+ * Supply the visible line through CSS. Do not make this host focusable to imitate a resizer;
12
+ * WindowSplitter owns that separate interaction.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { html } from "@typed/template";
17
+ * import { Heading } from "@typed/ui/Heading";
18
+ * import { Separator } from "@typed/ui/Separator";
19
+ *
20
+ * export const AccountSummary = html`<section>
21
+ * ${Heading({ level: 2, content: "Account summary" })}
22
+ * <p>Personal account</p>
23
+ * ${Separator({ props: { class: "summary-divider" } })}
24
+ * <p>Next renewal: September 30</p>
25
+ * </section>`;
26
+ * ```
27
+ * @since 1.0.0
28
+ * @category Structure and naming
29
+ */
30
+ export function Separator(options, host) {
31
+ return Dom.renderHost()(options, host, internalProps, "", (props) => html `<div ...${props}></div>`);
32
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Native range input publishing numeric updates during input.
3
+ * Keep programmatic values coherent with bounds and control expensive downstream work separately.
4
+ *
5
+ * Read the [Slider guide](/explore/ui-slider) for a complete example.
6
+ *
7
+ * [APG interaction reference](https://www.w3.org/WAI/ARIA/apg/patterns/slider/).
8
+ * @since 1.0.0
9
+ * @category Overview
10
+ * @packageDocumentation
11
+ */
12
+ import * as Effect from "effect/Effect";
13
+ import * as Schema from "effect/Schema";
14
+ import { RefSubject } from "@typed/fx";
15
+ import { EventHandler, type Renderable } from "@typed/template";
16
+ import * as Dom from "./Dom.js";
17
+ import type { HostResult } from "./Dom/Types.js";
18
+ /**
19
+ */
20
+ export interface State {
21
+ /**
22
+ */
23
+ readonly value: number;
24
+ }
25
+ /**
26
+ */
27
+ export interface InitialState {
28
+ /**
29
+ */
30
+ readonly value: number;
31
+ }
32
+ /**
33
+ */
34
+ export declare const StateSchema: Schema.Struct<{
35
+ readonly value: Schema.Finite;
36
+ }>;
37
+ /**
38
+ * Creates hydrated slider state.
39
+ * @remarks
40
+ * State can be composed and tested independently of the renderer.
41
+ * The calling Effect Scope owns the returned RefSubject.
42
+ * @example
43
+ * ```ts
44
+ * import { Effect } from "effect"
45
+ * import * as Slider from "@typed/ui/Slider"
46
+ *
47
+ * const program = Effect.gen(function* () {
48
+ * return yield* Slider.makeState({ value: 50 })
49
+ * })
50
+ * ```
51
+ * @since 1.0.0
52
+ * @category State construction
53
+ */
54
+ export declare function makeState(initial: InitialState): Effect.Effect<RefSubject.HydratedRefSubject<{
55
+ readonly value: number;
56
+ }, Schema.SchemaError, never, never>, never, import("effect/Scope").Scope>;
57
+ /**
58
+ * Assigns a range value without clamping or snapping it.
59
+ *
60
+ * @remarks
61
+ * min, max, and step belong to the native input options and are not consulted here. Validate or
62
+ * normalize programmatic values before assignment so the subject and browser-sanitized range
63
+ * value stay consistent.
64
+ * @since 1.0.0
65
+ * @category State transitions
66
+ */
67
+ export declare function setValue<E, R>(state: RefSubject.RefSubject<State, E, R>, value: number): Effect.Effect<State, E, R>;
68
+ /**
69
+ */
70
+ export interface SliderOptions extends Dom.HostOptions<HTMLInputElement> {
71
+ /**
72
+ * Hydrated state synchronized to the input.
73
+ * @since 1.0.0
74
+ * @category State connection
75
+ */
76
+ readonly state: RefSubject.HydratedRefSubject<State, Schema.SchemaError>;
77
+ /**
78
+ * Native minimum value.
79
+ * @since 1.0.0
80
+ * @category Numeric bounds
81
+ */
82
+ readonly min?: Renderable.Any<number | null | undefined>;
83
+ /**
84
+ * Native maximum value.
85
+ * @since 1.0.0
86
+ * @category Numeric bounds
87
+ */
88
+ readonly max?: Renderable.Any<number | null | undefined>;
89
+ /**
90
+ * Native step value or `"any"`.
91
+ * @since 1.0.0
92
+ * @category Numeric bounds
93
+ */
94
+ readonly step?: Renderable.Any<number | "any" | null | undefined>;
95
+ }
96
+ declare function internalProps<const Options extends SliderOptions>(options: Options): ({ property }: Dom.InternalPropsHelpers<Options>) => {
97
+ readonly type: "range";
98
+ readonly value: RefSubject.Computed<number, Schema.SchemaError, never>;
99
+ readonly ".value": RefSubject.Computed<string, Schema.SchemaError, never>;
100
+ readonly min: Exclude<Dom.Property<Options, "min">, Dom.Nullish> | undefined;
101
+ readonly max: Exclude<Dom.Property<Options, "max">, Dom.Nullish> | undefined;
102
+ readonly step: Exclude<Dom.Property<Options, "step">, Dom.Nullish> | undefined;
103
+ readonly oninput: EventHandler.EventHandler<Event, Schema.SchemaError, never>;
104
+ readonly ref: RefSubject.HydratedRefSubject<State, Schema.SchemaError, never, never>;
105
+ };
106
+ type SliderInternalProps<Options extends SliderOptions> = ReturnType<ReturnType<typeof internalProps<Options>>>;
107
+ /**
108
+ * Renders a native range input whose input events update numeric state.
109
+ *
110
+ * @remarks
111
+ * Dragging publishes through input, so expensive downstream work should control its own update
112
+ * rate. Native min/max/step, keyboard interaction, and pointer behavior stay with the browser.
113
+ * Supply a label and a unit-bearing readout; a custom non-input host must implement the
114
+ * interaction itself.
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * import { RefSubject } from "@typed/fx";
119
+ * import { html } from "@typed/template";
120
+ * import { component } from "@typed/template";
121
+ * import * as Slider from "@typed/ui/Slider";
122
+ *
123
+ * export const ZoomControl = component(function* () {
124
+ * const state = yield* Slider.makeState({ value: 100 });
125
+ * const percentage = RefSubject.map(state, ({ value }) => `${value}%`);
126
+ * return html`<div class="zoom-control">
127
+ * <label for="preview-zoom">Preview zoom</label>
128
+ * ${Slider.Slider({
129
+ * state, min: 50, max: 200, step: 10,
130
+ * props: { id: "preview-zoom", name: "zoom", "aria-valuetext": percentage },
131
+ * })}
132
+ * <output for="preview-zoom">${percentage}</output>
133
+ * </div>`;
134
+ * });
135
+ * ```
136
+ * @since 1.0.0
137
+ * @category Native controls
138
+ */
139
+ 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>;
140
+ export {};
141
+ //# sourceMappingURL=Slider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Slider.d.ts","sourceRoot":"","sources":["../src/Slider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,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;GACG;AACH,MAAM,WAAW,KAAK;IACpB;OACG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;GACG;AACH,MAAM,WAAW,YAAY;IAC3B;OACG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;GACG;AACH,eAAO,MAAM,WAAW;;EAA0C,CAAC;AAEnE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY;;2EAE9C;AAED;;;;;;;;;GASG;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;GACG;AACH,MAAM,WAAW,aAAc,SAAQ,GAAG,CAAC,WAAW,CAAC,gBAAgB,CAAC;IACtE;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IACzE;;;;OAIG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACzD;;;;OAIG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACzD;;;;OAIG;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;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"}