@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/Slider.js ADDED
@@ -0,0 +1,101 @@
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, html } from "@typed/template";
16
+ import * as Dom from "./Dom.js";
17
+ /**
18
+ */
19
+ export const StateSchema = Schema.Struct({ value: Schema.Finite });
20
+ /**
21
+ * Creates hydrated slider state.
22
+ * @remarks
23
+ * State can be composed and tested independently of the renderer.
24
+ * The calling Effect Scope owns the returned RefSubject.
25
+ * @example
26
+ * ```ts
27
+ * import { Effect } from "effect"
28
+ * import * as Slider from "@typed/ui/Slider"
29
+ *
30
+ * const program = Effect.gen(function* () {
31
+ * return yield* Slider.makeState({ value: 50 })
32
+ * })
33
+ * ```
34
+ * @since 1.0.0
35
+ * @category State construction
36
+ */
37
+ export function makeState(initial) {
38
+ return RefSubject.hydrate(StateSchema, initial);
39
+ }
40
+ /**
41
+ * Assigns a range value without clamping or snapping it.
42
+ *
43
+ * @remarks
44
+ * min, max, and step belong to the native input options and are not consulted here. Validate or
45
+ * normalize programmatic values before assignment so the subject and browser-sanitized range
46
+ * value stay consistent.
47
+ * @since 1.0.0
48
+ * @category State transitions
49
+ */
50
+ export function setValue(state, value) {
51
+ return RefSubject.update(state, (current) => ({ ...current, value }));
52
+ }
53
+ function internalProps(options) {
54
+ return ({ property }) => ({
55
+ type: "range",
56
+ value: RefSubject.map(options.state, (state) => state.value),
57
+ ".value": RefSubject.map(options.state, (state) => String(state.value)),
58
+ min: property("min", undefined),
59
+ max: property("max", undefined),
60
+ step: property("step", undefined),
61
+ oninput: EventHandler.make(Effect.fn((event) => setValue(options.state, Dom.currentTarget(event).valueAsNumber))),
62
+ ref: options.state,
63
+ });
64
+ }
65
+ /**
66
+ * Renders a native range input whose input events update numeric state.
67
+ *
68
+ * @remarks
69
+ * Dragging publishes through input, so expensive downstream work should control its own update
70
+ * rate. Native min/max/step, keyboard interaction, and pointer behavior stay with the browser.
71
+ * Supply a label and a unit-bearing readout; a custom non-input host must implement the
72
+ * interaction itself.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * import { RefSubject } from "@typed/fx";
77
+ * import { html } from "@typed/template";
78
+ * import { component } from "@typed/template";
79
+ * import * as Slider from "@typed/ui/Slider";
80
+ *
81
+ * export const ZoomControl = component(function* () {
82
+ * const state = yield* Slider.makeState({ value: 100 });
83
+ * const percentage = RefSubject.map(state, ({ value }) => `${value}%`);
84
+ * return html`<div class="zoom-control">
85
+ * <label for="preview-zoom">Preview zoom</label>
86
+ * ${Slider.Slider({
87
+ * state, min: 50, max: 200, step: 10,
88
+ * props: { id: "preview-zoom", name: "zoom", "aria-valuetext": percentage },
89
+ * })}
90
+ * <output for="preview-zoom">${percentage}</output>
91
+ * </div>`;
92
+ * });
93
+ * ```
94
+ * @since 1.0.0
95
+ * @category Native controls
96
+ */
97
+ export function Slider(options, host) {
98
+ return Dom.renderHost()(options, host, internalProps(options), "", (props) => {
99
+ return html `<input ...${props} />`;
100
+ });
101
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Native number entry publishing valueAsNumber at the change boundary.
3
+ * This thin primitive does not maintain draft strings or filter invalid numeric writes.
4
+ *
5
+ * Read the [SpinButton guide](/explore/ui-spin-button) for a complete example.
6
+ *
7
+ * [APG interaction reference](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/).
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 spin-button state.
39
+ * @remarks
40
+ * State remains renderer-independent and directly testable.
41
+ * The calling Effect Scope owns the returned RefSubject.
42
+ * @example
43
+ * ```ts
44
+ * import { Effect } from "effect"
45
+ * import * as SpinButton from "@typed/ui/SpinButton"
46
+ *
47
+ * const program = Effect.gen(function* () {
48
+ * return yield* SpinButton.makeState({ value: 3 })
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 numeric value without bounds checks or draft filtering.
59
+ *
60
+ * @remarks
61
+ * The helper does not consult min/max/step or reject NaN. The hydration schema describes finite
62
+ * snapshots, but this update is not a decoder. Choose Form.NumberInput or an explicit draft
63
+ * model when invalid text needs structured feedback.
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 SpinButtonOptions extends Dom.HostOptions<HTMLInputElement> {
71
+ /**
72
+ * Hydrated value state.
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 SpinButtonOptions>(options: Options): ({ property }: Dom.InternalPropsHelpers<Options>) => {
97
+ readonly type: "number";
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 onchange: EventHandler.EventHandler<Event, Schema.SchemaError, never>;
104
+ readonly ref: RefSubject.HydratedRefSubject<State, Schema.SchemaError, never, never>;
105
+ };
106
+ type SpinButtonInternalProps<Options extends SpinButtonOptions> = ReturnType<ReturnType<typeof internalProps<Options>>>;
107
+ /**
108
+ * Renders a native number input that writes valueAsNumber on change.
109
+ *
110
+ * @remarks
111
+ * State updates occur at the change boundary rather than every input event. Empty or invalid
112
+ * native text can yield NaN; the handler does not filter it before assignment. This thin
113
+ * primitive does not preserve a separate draft string, create field errors, or clamp
114
+ * programmatic writes.
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 SpinButton from "@typed/ui/SpinButton";
122
+ *
123
+ * export const CopyCount = component(function* () {
124
+ * const state = yield* SpinButton.makeState({ value: 1 });
125
+ * const summary = RefSubject.map(state, ({ value }) => `Copies requested: ${value}`);
126
+ * return html`<div class="copy-count">
127
+ * <label for="print-copies">Number of copies</label>
128
+ * ${SpinButton.SpinButton({
129
+ * state, min: 1, max: 100, step: 1,
130
+ * props: { id: "print-copies", name: "copies", required: true },
131
+ * })}
132
+ * <p>${summary}</p>
133
+ * </div>`;
134
+ * });
135
+ * ```
136
+ * @since 1.0.0
137
+ * @category Native controls
138
+ */
139
+ export declare function SpinButton<const Options extends SpinButtonOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, SpinButtonInternalProps<Options>>, "", Host>): import("./Dom/Types.js").HostComponent<Host | Options>;
140
+ export {};
141
+ //# sourceMappingURL=SpinButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpinButton.d.ts","sourceRoot":"","sources":["../src/SpinButton.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,iBAAkB,SAAQ,GAAG,CAAC,WAAW,CAAC,gBAAgB,CAAC;IAC1E;;;;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,iBAAiB,EAAE,OAAO,EAAE,OAAO,kBACxD,GAAG,CAAC,oBAAoB,CAAC,OAAO,CAAC;aAEnD,IAAI,EAAE,QAAQ;aACd,KAAK;aACL,QAAQ;aACR,GAAG;aACH,GAAG;aACH,IAAI;aACJ,QAAQ;aAKR,GAAG;EAER;AACD,KAAK,uBAAuB,CAAC,OAAO,SAAS,iBAAiB,IAAI,UAAU,CAC1E,UAAU,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC,CAC1C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,UAAU,CACxB,KAAK,CAAC,OAAO,SAAS,iBAAiB,EACvC,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,uBAAuB,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,0DAWlG"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Native number entry publishing valueAsNumber at the change boundary.
3
+ * This thin primitive does not maintain draft strings or filter invalid numeric writes.
4
+ *
5
+ * Read the [SpinButton guide](/explore/ui-spin-button) for a complete example.
6
+ *
7
+ * [APG interaction reference](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/).
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, html } from "@typed/template";
16
+ import * as Dom from "./Dom.js";
17
+ /**
18
+ */
19
+ export const StateSchema = Schema.Struct({ value: Schema.Finite });
20
+ /**
21
+ * Creates hydrated spin-button state.
22
+ * @remarks
23
+ * State remains renderer-independent and directly testable.
24
+ * The calling Effect Scope owns the returned RefSubject.
25
+ * @example
26
+ * ```ts
27
+ * import { Effect } from "effect"
28
+ * import * as SpinButton from "@typed/ui/SpinButton"
29
+ *
30
+ * const program = Effect.gen(function* () {
31
+ * return yield* SpinButton.makeState({ value: 3 })
32
+ * })
33
+ * ```
34
+ * @since 1.0.0
35
+ * @category State construction
36
+ */
37
+ export function makeState(initial) {
38
+ return RefSubject.hydrate(StateSchema, initial);
39
+ }
40
+ /**
41
+ * Assigns a numeric value without bounds checks or draft filtering.
42
+ *
43
+ * @remarks
44
+ * The helper does not consult min/max/step or reject NaN. The hydration schema describes finite
45
+ * snapshots, but this update is not a decoder. Choose Form.NumberInput or an explicit draft
46
+ * model when invalid text needs structured feedback.
47
+ * @since 1.0.0
48
+ * @category State transitions
49
+ */
50
+ export function setValue(state, value) {
51
+ return RefSubject.update(state, (current) => ({ ...current, value }));
52
+ }
53
+ function internalProps(options) {
54
+ return ({ property }) => ({
55
+ type: "number",
56
+ value: RefSubject.map(options.state, (state) => state.value),
57
+ ".value": RefSubject.map(options.state, (state) => String(state.value)),
58
+ min: property("min", undefined),
59
+ max: property("max", undefined),
60
+ step: property("step", undefined),
61
+ onchange: EventHandler.make(Effect.fn((event) => setValue(options.state, Dom.currentTarget(event).valueAsNumber))),
62
+ ref: options.state,
63
+ });
64
+ }
65
+ /**
66
+ * Renders a native number input that writes valueAsNumber on change.
67
+ *
68
+ * @remarks
69
+ * State updates occur at the change boundary rather than every input event. Empty or invalid
70
+ * native text can yield NaN; the handler does not filter it before assignment. This thin
71
+ * primitive does not preserve a separate draft string, create field errors, or clamp
72
+ * programmatic writes.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * import { RefSubject } from "@typed/fx";
77
+ * import { html } from "@typed/template";
78
+ * import { component } from "@typed/template";
79
+ * import * as SpinButton from "@typed/ui/SpinButton";
80
+ *
81
+ * export const CopyCount = component(function* () {
82
+ * const state = yield* SpinButton.makeState({ value: 1 });
83
+ * const summary = RefSubject.map(state, ({ value }) => `Copies requested: ${value}`);
84
+ * return html`<div class="copy-count">
85
+ * <label for="print-copies">Number of copies</label>
86
+ * ${SpinButton.SpinButton({
87
+ * state, min: 1, max: 100, step: 1,
88
+ * props: { id: "print-copies", name: "copies", required: true },
89
+ * })}
90
+ * <p>${summary}</p>
91
+ * </div>`;
92
+ * });
93
+ * ```
94
+ * @since 1.0.0
95
+ * @category Native controls
96
+ */
97
+ export function SpinButton(options, host) {
98
+ return Dom.renderHost()(options, host, internalProps(options), "", (props) => {
99
+ return html `<input ...${props} />`;
100
+ });
101
+ }
@@ -0,0 +1,76 @@
1
+ import * as Scope from "effect/Scope";
2
+ import { Fx } from "@typed/fx";
3
+ import { type RenderEvent, type RenderTemplate } from "@typed/template";
4
+ /**
5
+ * Live Storybook mount and its explicit asynchronous disposer.
6
+ *
7
+ * @remarks
8
+ * ## Why
9
+ * Stories need access to the actual canvas for assertions and deterministic
10
+ * teardown of a long-lived reactive render.
11
+ *
12
+ * ## Ownership and lifetime
13
+ * The mount owns its private Scope and render fiber. `dispose` interrupts the
14
+ * fiber and closes the Scope exactly once; callers own appending/removing the
15
+ * returned canvas. If the canvas is connected, disposal does not disconnect the
16
+ * removal observer; removing the canvas is the observer's cleanup boundary.
17
+ *
18
+ * @since 1.0.0
19
+ * @category Story lifetime
20
+ */
21
+ export interface MountedStory {
22
+ /** Detached div containing the story's real rendered DOM. */
23
+ readonly canvas: HTMLDivElement;
24
+ /** Idempotently closes rendering; remove a connected canvas to release its observer. */
25
+ readonly dispose: () => Promise<void>;
26
+ }
27
+ /**
28
+ * Mounts a Typed component in an isolated Storybook canvas.
29
+ *
30
+ * The mount stays alive after its first render so reactive attributes and event
31
+ * handlers remain attached. It is released explicitly or when Storybook
32
+ * removes the canvas from its document.
33
+ *
34
+ * @remarks
35
+ * ## Why
36
+ *
37
+ * Waiting only for the first `RenderEvent` and then ending the fiber would
38
+ * detach reactive attributes and event handlers. This helper keeps the Typed
39
+ * renderer alive while still giving tests an explicit disposal boundary.
40
+ *
41
+ * ## Ownership and lifetime
42
+ *
43
+ * `mount` creates an unsafe Scope dedicated to this story, starts the render
44
+ * fiber, and resolves only after the first event. `dispose` interrupts and
45
+ * closes it idempotently. After the canvas has once been connected, a
46
+ * document-scoped MutationObserver disposes it when removed and then disconnects.
47
+ * Calling `dispose` while the canvas is still connected does **not** disconnect
48
+ * that observer: it continues retaining the document, canvas, and disposal
49
+ * closure until the canvas is removed. Tests or hosts that keep a disposed
50
+ * canvas connected must remove it themselves to release that observer. If
51
+ * rendering fails before the first event, Scope cleanup runs before rejection
52
+ * and no removal observer is installed.
53
+ *
54
+ * ## Platform requirements
55
+ *
56
+ * This is a browser/testing API. The provided Document supplies element and
57
+ * MutationObserver implementations. The canvas is returned detached so
58
+ * Storybook or a test controls where it enters the DOM.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * import { mount } from "@typed/ui/Storybook"
63
+ * import { html } from "@typed/template"
64
+ *
65
+ * const story = await mount(html`<button type="button">Save</button>`)
66
+ * document.body.append(story.canvas)
67
+ * // Run interactions and assertions against story.canvas.
68
+ * await story.dispose()
69
+ * story.canvas.remove() // disconnects the removal observer
70
+ * ```
71
+ *
72
+ * @since 1.0.0
73
+ * @category Story mounting
74
+ */
75
+ export declare function mount<E>(content: Fx.Fx<RenderEvent, E, Scope.Scope | RenderTemplate>, document?: Document): Promise<MountedStory>;
76
+ //# sourceMappingURL=Storybook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Storybook.d.ts","sourceRoot":"","sources":["../src/Storybook.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAC/B,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,wFAAwF;IACxF,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAsB,KAAK,CAAC,CAAC,EAC3B,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,cAAc,CAAC,EAC5D,QAAQ,GAAE,QAA8B,GACvC,OAAO,CAAC,YAAY,CAAC,CAqCvB"}
@@ -0,0 +1,102 @@
1
+ import * as Deferred from "effect/Deferred";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Exit from "effect/Exit";
4
+ import * as Fiber from "effect/Fiber";
5
+ import * as Scope from "effect/Scope";
6
+ import { Fx } from "@typed/fx";
7
+ import { DomRenderTemplate, render as renderDom, } from "@typed/template";
8
+ /**
9
+ * Mounts a Typed component in an isolated Storybook canvas.
10
+ *
11
+ * The mount stays alive after its first render so reactive attributes and event
12
+ * handlers remain attached. It is released explicitly or when Storybook
13
+ * removes the canvas from its document.
14
+ *
15
+ * @remarks
16
+ * ## Why
17
+ *
18
+ * Waiting only for the first `RenderEvent` and then ending the fiber would
19
+ * detach reactive attributes and event handlers. This helper keeps the Typed
20
+ * renderer alive while still giving tests an explicit disposal boundary.
21
+ *
22
+ * ## Ownership and lifetime
23
+ *
24
+ * `mount` creates an unsafe Scope dedicated to this story, starts the render
25
+ * fiber, and resolves only after the first event. `dispose` interrupts and
26
+ * closes it idempotently. After the canvas has once been connected, a
27
+ * document-scoped MutationObserver disposes it when removed and then disconnects.
28
+ * Calling `dispose` while the canvas is still connected does **not** disconnect
29
+ * that observer: it continues retaining the document, canvas, and disposal
30
+ * closure until the canvas is removed. Tests or hosts that keep a disposed
31
+ * canvas connected must remove it themselves to release that observer. If
32
+ * rendering fails before the first event, Scope cleanup runs before rejection
33
+ * and no removal observer is installed.
34
+ *
35
+ * ## Platform requirements
36
+ *
37
+ * This is a browser/testing API. The provided Document supplies element and
38
+ * MutationObserver implementations. The canvas is returned detached so
39
+ * Storybook or a test controls where it enters the DOM.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * import { mount } from "@typed/ui/Storybook"
44
+ * import { html } from "@typed/template"
45
+ *
46
+ * const story = await mount(html`<button type="button">Save</button>`)
47
+ * document.body.append(story.canvas)
48
+ * // Run interactions and assertions against story.canvas.
49
+ * await story.dispose()
50
+ * story.canvas.remove() // disconnects the removal observer
51
+ * ```
52
+ *
53
+ * @since 1.0.0
54
+ * @category Story mounting
55
+ */
56
+ export async function mount(content, document = globalThis.document) {
57
+ const canvas = document.createElement("div");
58
+ const scope = Scope.makeUnsafe();
59
+ const mounted = Deferred.makeUnsafe();
60
+ let disposed = false;
61
+ let fiber;
62
+ const dispose = async () => {
63
+ if (disposed)
64
+ return;
65
+ disposed = true;
66
+ if (fiber !== undefined) {
67
+ await Effect.runPromise(Fiber.interrupt(fiber));
68
+ }
69
+ await Effect.runPromise(Scope.close(scope, Exit.void));
70
+ };
71
+ fiber = Effect.runFork(Scope.provide(Fx.drain(renderDom(content, canvas).pipe(Fx.tap(() => Deferred.succeed(mounted, undefined)))).pipe(Effect.provide(DomRenderTemplate.using(document))), scope));
72
+ try {
73
+ const endedBeforeMount = Effect.andThen(Fiber.join(fiber), Effect.die("Story completed before rendering any content"));
74
+ await Effect.runPromise(Effect.raceFirst(Deferred.await(mounted), endedBeforeMount));
75
+ }
76
+ catch (error) {
77
+ await dispose();
78
+ throw error;
79
+ }
80
+ disposeWhenRemoved(canvas, document, dispose);
81
+ return { canvas, dispose };
82
+ }
83
+ function disposeWhenRemoved(canvas, document, dispose) {
84
+ const MutationObserver = document.defaultView?.MutationObserver;
85
+ if (MutationObserver === undefined)
86
+ return;
87
+ let connected = canvas.isConnected;
88
+ const observer = new MutationObserver(() => {
89
+ if (canvas.isConnected) {
90
+ connected = true;
91
+ }
92
+ else if (connected) {
93
+ observer.disconnect();
94
+ void dispose();
95
+ }
96
+ });
97
+ observer.observe(document, { childList: true, subtree: true });
98
+ queueMicrotask(() => {
99
+ if (canvas.isConnected)
100
+ connected = true;
101
+ });
102
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * A button-backed on/off setting with a stable accessible name.
3
+ * The internal click toggles boolean state; persistence and form serialization remain explicit.
4
+ *
5
+ * Read the [Switch guide](/explore/ui-switch) for a complete example.
6
+ *
7
+ * [APG interaction reference](https://www.w3.org/WAI/ARIA/apg/patterns/switch/).
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 { 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 checked: boolean;
24
+ }
25
+ /**
26
+ */
27
+ export interface InitialState {
28
+ /**
29
+ */
30
+ readonly checked?: boolean;
31
+ }
32
+ /**
33
+ */
34
+ export declare const StateSchema: Schema.Struct<{
35
+ readonly checked: Schema.Boolean;
36
+ }>;
37
+ /**
38
+ * Creates hydrated switch state independent of rendering.
39
+ * @remarks
40
+ * Domain code and tests can own the state while any renderer consumes it.
41
+ * The caller's Effect Scope owns the returned RefSubject.
42
+ * @example
43
+ * ```ts
44
+ * import { Effect } from "effect"
45
+ * import * as Switch from "@typed/ui/Switch"
46
+ *
47
+ * const program = Effect.gen(function* () {
48
+ * return yield* Switch.makeState({ checked: true })
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 checked: boolean;
56
+ }, Schema.SchemaError, never, never>, never, import("effect/Scope").Scope>;
57
+ /**
58
+ * Sets the switch to the requested checked state.
59
+ * @example
60
+ * ```ts
61
+ * import { Effect } from "effect"
62
+ * import * as Switch from "@typed/ui/Switch"
63
+ *
64
+ * const program = Effect.gen(function* () {
65
+ * const state = yield* Switch.makeState()
66
+ * yield* Switch.setChecked(state, true)
67
+ * })
68
+ * ```
69
+ * @since 1.0.0
70
+ * @category State transitions
71
+ */
72
+ export declare function setChecked<E, R>(state: RefSubject.RefSubject<State, E, R>, checked: boolean): Effect.Effect<State, E, R>;
73
+ /**
74
+ * Toggles the switch between checked and unchecked.
75
+ * @example
76
+ * ```ts
77
+ * import { Effect } from "effect"
78
+ * import * as Switch from "@typed/ui/Switch"
79
+ *
80
+ * const program = Effect.gen(function* () {
81
+ * const state = yield* Switch.makeState({ checked: true })
82
+ * yield* Switch.toggle(state)
83
+ * })
84
+ * ```
85
+ * @since 1.0.0
86
+ * @category State transitions
87
+ */
88
+ export declare function toggle<E, R>(state: RefSubject.RefSubject<State, E, R>): Effect.Effect<State, E, R>;
89
+ /**
90
+ */
91
+ export interface SwitchOptions extends Dom.HostOptions<HTMLButtonElement> {
92
+ /**
93
+ * Hydrated state synchronized with `aria-checked`.
94
+ * @since 1.0.0
95
+ * @category State connection
96
+ */
97
+ readonly state: RefSubject.HydratedRefSubject<State, Schema.SchemaError>;
98
+ /**
99
+ * Visible content and accessible name.
100
+ * @since 1.0.0
101
+ * @category Rendered content
102
+ */
103
+ readonly content: Renderable.Any;
104
+ }
105
+ declare function internalProps<const Options extends SwitchOptions>(options: Options): ({ property }: Dom.InternalPropsHelpers<Options>) => {
106
+ readonly type: "button";
107
+ readonly role: "switch";
108
+ readonly "aria-checked": RefSubject.Computed<boolean, Schema.SchemaError, never>;
109
+ readonly "?disabled": false | Exclude<Dom.Property<Options, "disabled">, Dom.Nullish>;
110
+ readonly onclick: Effect.Effect<State, Schema.SchemaError, never>;
111
+ readonly ref: RefSubject.HydratedRefSubject<State, Schema.SchemaError, never, never>;
112
+ };
113
+ type SwitchInternalProps<Options extends SwitchOptions> = ReturnType<ReturnType<typeof internalProps<Options>>>;
114
+ /**
115
+ * Renders a button-backed ARIA switch.
116
+ * @remarks
117
+ * The component combines native button activation with a renderer-independent
118
+ * boolean state and real DOM click events.
119
+ * Running the Fx owns listener/state subscriptions in its Effect Scope. A
120
+ * custom host must preserve button type, role, `aria-checked`, click handler,
121
+ * and the single hydration ref owner.
122
+ * @example
123
+ * ```ts
124
+ * import { RefSubject } from "@typed/fx";
125
+ * import { html } from "@typed/template";
126
+ * import { component } from "@typed/template";
127
+ * import * as Switch from "@typed/ui/Switch";
128
+ *
129
+ * export const PreviewSetting = component(function* () {
130
+ * const state = yield* Switch.makeState({ checked: true });
131
+ * const status = RefSubject.map(state, ({ checked }) => checked ? "On" : "Off");
132
+ * return html`<section>
133
+ * ${Switch.Switch({
134
+ * state,
135
+ * content: "Show live preview",
136
+ * props: { class: "preview-switch", "aria-describedby": "preview-setting-help" },
137
+ * })}
138
+ * <span aria-hidden="true">${status}</span>
139
+ * <p id="preview-setting-help">Updates the preview while you edit.</p>
140
+ * </section>`;
141
+ * });
142
+ * ```
143
+ * @since 1.0.0
144
+ * @category Native controls
145
+ */
146
+ export declare function Switch<const Options extends SwitchOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, SwitchInternalProps<Options>>, Options["content"], Host>): import("./Dom/Types.js").HostComponent<Host | Options>;
147
+ export {};
148
+ //# sourceMappingURL=Switch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Switch.d.ts","sourceRoot":"","sources":["../src/Switch.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,EAAQ,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACxD,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,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;GACG;AACH,MAAM,WAAW,YAAY;IAC3B;OACG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;GACG;AACH,eAAO,MAAM,WAAW;;EAA6C,CAAC;AAEtE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,YAAiB;;2EAEnD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAC7B,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,EACzC,OAAO,EAAE,OAAO,GACf,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAE5B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,CAAC,EACzB,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,GACxC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAE5B;AAED;GACG;AACH,MAAM,WAAW,aAAc,SAAQ,GAAG,CAAC,WAAW,CAAC,iBAAiB,CAAC;IACvE;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IACzE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC;CAClC;AAED,iBAAS,aAAa,CAAC,KAAK,CAAC,OAAO,SAAS,aAAa,EAAE,OAAO,EAAE,OAAO,kBACpD,GAAG,CAAC,oBAAoB,CAAC,OAAO,CAAC;aAEnD,IAAI,EAAE,QAAQ;aACd,IAAI,EAAE,QAAQ;aACd,cAAc;aACd,WAAW;aACX,OAAO;aACP,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,CACrB,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC,EAC1D,OAAO,CAAC,SAAS,CAAC,EAClB,IAAI,CACL,0DAWF"}