@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/Slider.js ADDED
@@ -0,0 +1,96 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ import { RefSubject } from "@typed/fx";
4
+ import { EventHandler, html } from "@typed/template";
5
+ import * as Dom from "./Dom.js";
6
+ /** Schema for slider hydration state.
7
+ * @remarks
8
+ * ## Why
9
+ * Finite validation prevents invalid serialized range values.
10
+ * ## Ownership and lifetime
11
+ * The immutable schema acquires no resources.
12
+ * @since 1.0.0
13
+ * @category schemas
14
+ */
15
+ export const StateSchema = Schema.Struct({ value: Schema.Finite });
16
+ /** Creates hydrated slider state.
17
+ * @remarks
18
+ * ## Why
19
+ * State can be composed and tested independently of the renderer.
20
+ * ## Ownership and lifetime
21
+ * The calling Effect Scope owns the returned RefSubject.
22
+ * @example
23
+ * ```ts
24
+ * import { Effect } from "effect"
25
+ * import * as Slider from "@typed/ui/Slider"
26
+ *
27
+ * const program = Effect.gen(function* () {
28
+ * return yield* Slider.makeState({ value: 50 })
29
+ * })
30
+ * ```
31
+ * @since 1.0.0
32
+ * @category constructors
33
+ */
34
+ export function makeState(initial) {
35
+ return RefSubject.hydrate(StateSchema, initial);
36
+ }
37
+ /** Updates the synchronized slider value.
38
+ * @remarks
39
+ * ## Why
40
+ * An explicit Effect transition preserves RefSubject failures and services.
41
+ * ## Ownership and lifetime
42
+ * It uses the existing state lifetime and acquires no resource.
43
+ * @example
44
+ * ```ts
45
+ * import { Effect } from "effect"
46
+ * import * as Slider from "@typed/ui/Slider"
47
+ *
48
+ * const program = Effect.gen(function* () {
49
+ * const state = yield* Slider.makeState({ value: 25 })
50
+ * yield* Slider.setValue(state, 50)
51
+ * })
52
+ * ```
53
+ * @since 1.0.0
54
+ * @category state
55
+ */
56
+ export function setValue(state, value) {
57
+ return RefSubject.update(state, (current) => ({ ...current, value }));
58
+ }
59
+ function internalProps(options) {
60
+ return ({ property }) => ({
61
+ type: "range",
62
+ value: RefSubject.map(options.state, (state) => state.value),
63
+ ".value": RefSubject.map(options.state, (state) => String(state.value)),
64
+ min: property("min", undefined),
65
+ max: property("max", undefined),
66
+ step: property("step", undefined),
67
+ oninput: EventHandler.make(Effect.fn((event) => setValue(options.state, Dom.currentTarget(event).valueAsNumber))),
68
+ ref: options.state,
69
+ });
70
+ }
71
+ /** Renders a native range input synchronized with hydrated state.
72
+ * @remarks
73
+ * ## Why
74
+ * Browser pointer, keyboard, constraints, and accessibility remain intact;
75
+ * input events update the same RefSubject consumed elsewhere.
76
+ * ## Ownership and lifetime
77
+ * Running the Fx owns native listeners and subscriptions in its Scope. A
78
+ * custom host must preserve type, value, range props, and the hydration ref.
79
+ * @example
80
+ * ```ts
81
+ * import { Effect } from "effect"
82
+ * import * as Slider from "@typed/ui/Slider"
83
+ *
84
+ * const program = Effect.gen(function* () {
85
+ * const state = yield* Slider.makeState({ value: 50 })
86
+ * return Slider.Slider({ state, min: 0, max: 100 })
87
+ * })
88
+ * ```
89
+ * @since 1.0.0
90
+ * @category components
91
+ */
92
+ export function Slider(options, host) {
93
+ return Dom.renderHost()(options, host, internalProps(options), "", (props) => {
94
+ return html `<input ...${props} />`;
95
+ });
96
+ }
@@ -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 spin-button value.
8
+ * @remarks
9
+ * ## Why
10
+ * Numeric state can be transformed and tested without an input element.
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 number input.
18
+ * @remarks
19
+ * ## Why
20
+ * One source keeps form control and application state aligned.
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 spin-button 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 synchronized state before the element mounts.
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 spin-button hydration state.
50
+ * @remarks
51
+ * ## Why
52
+ * Finite validation prevents invalid serialized number 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 spin-button state.
62
+ * @remarks
63
+ * ## Why
64
+ * State remains renderer-independent and directly testable.
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 SpinButton from "@typed/ui/SpinButton"
71
+ *
72
+ * const program = Effect.gen(function* () {
73
+ * return yield* SpinButton.makeState({ value: 3 })
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 numeric value.
83
+ * @remarks
84
+ * ## Why
85
+ * An explicit Effect transition preserves RefSubject errors 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 SpinButton from "@typed/ui/SpinButton"
92
+ *
93
+ * const program = Effect.gen(function* () {
94
+ * const state = yield* SpinButton.makeState({ value: 1 })
95
+ * yield* SpinButton.setValue(state, 2)
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 number input.
103
+ * @remarks
104
+ * ## Why
105
+ * Native spin-button behavior supplies editing, stepping, forms, and
106
+ * accessibility while Typed synchronizes 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 SpinButtonOptions extends Dom.HostOptions<HTMLInputElement> {
113
+ /** Hydrated value state.
114
+ * @remarks
115
+ * ## Why
116
+ * One source serves SSR, UI, and renderer-free state logic.
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 applies this bound to stepping and validation.
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 applies this bound to stepping and validation.
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
+ * Increment semantics remain delegated to the number input.
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 SpinButtonOptions>(options: Options): ({ property }: Dom.InternalPropsHelpers<Options>) => {
155
+ readonly type: "number";
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 onchange: EventHandler.EventHandler<Event, Schema.SchemaError, never>;
162
+ readonly ref: RefSubject.HydratedRefSubject<State, Schema.SchemaError, never, never>;
163
+ };
164
+ type SpinButtonInternalProps<Options extends SpinButtonOptions> = ReturnType<ReturnType<typeof internalProps<Options>>>;
165
+ /** Renders a native number input synchronized with hydrated state.
166
+ * @remarks
167
+ * ## Why
168
+ * Browser editing, stepping, validation, and form behavior remain intact while
169
+ * change events update renderer-independent state.
170
+ * ## Ownership and lifetime
171
+ * Running the Fx owns listener/state subscriptions in its Scope. A custom host
172
+ * must preserve type, value, constraints, and the single hydration ref owner.
173
+ * @example
174
+ * ```ts
175
+ * import { Effect } from "effect"
176
+ * import * as SpinButton from "@typed/ui/SpinButton"
177
+ *
178
+ * const program = Effect.gen(function* () {
179
+ * const state = yield* SpinButton.makeState({ value: 1 })
180
+ * return SpinButton.SpinButton({ state, min: 0 })
181
+ * })
182
+ * ```
183
+ * @since 1.0.0
184
+ * @category components
185
+ */
186
+ 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>;
187
+ export {};
188
+ //# sourceMappingURL=SpinButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpinButton.d.ts","sourceRoot":"","sources":["../src/SpinButton.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,iBAAkB,SAAQ,GAAG,CAAC,WAAW,CAAC,gBAAgB,CAAC;IAC1E;;;;;;;;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,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;;;;;;;;;;;;;;;;;;;;GAoBG;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,96 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ import { RefSubject } from "@typed/fx";
4
+ import { EventHandler, html } from "@typed/template";
5
+ import * as Dom from "./Dom.js";
6
+ /** Schema for spin-button hydration state.
7
+ * @remarks
8
+ * ## Why
9
+ * Finite validation prevents invalid serialized number values.
10
+ * ## Ownership and lifetime
11
+ * The immutable schema acquires no resources.
12
+ * @since 1.0.0
13
+ * @category schemas
14
+ */
15
+ export const StateSchema = Schema.Struct({ value: Schema.Finite });
16
+ /** Creates hydrated spin-button state.
17
+ * @remarks
18
+ * ## Why
19
+ * State remains renderer-independent and directly testable.
20
+ * ## Ownership and lifetime
21
+ * The calling Effect Scope owns the returned RefSubject.
22
+ * @example
23
+ * ```ts
24
+ * import { Effect } from "effect"
25
+ * import * as SpinButton from "@typed/ui/SpinButton"
26
+ *
27
+ * const program = Effect.gen(function* () {
28
+ * return yield* SpinButton.makeState({ value: 3 })
29
+ * })
30
+ * ```
31
+ * @since 1.0.0
32
+ * @category constructors
33
+ */
34
+ export function makeState(initial) {
35
+ return RefSubject.hydrate(StateSchema, initial);
36
+ }
37
+ /** Updates the synchronized numeric value.
38
+ * @remarks
39
+ * ## Why
40
+ * An explicit Effect transition preserves RefSubject errors and services.
41
+ * ## Ownership and lifetime
42
+ * It uses the existing state lifetime and acquires no resource.
43
+ * @example
44
+ * ```ts
45
+ * import { Effect } from "effect"
46
+ * import * as SpinButton from "@typed/ui/SpinButton"
47
+ *
48
+ * const program = Effect.gen(function* () {
49
+ * const state = yield* SpinButton.makeState({ value: 1 })
50
+ * yield* SpinButton.setValue(state, 2)
51
+ * })
52
+ * ```
53
+ * @since 1.0.0
54
+ * @category state
55
+ */
56
+ export function setValue(state, value) {
57
+ return RefSubject.update(state, (current) => ({ ...current, value }));
58
+ }
59
+ function internalProps(options) {
60
+ return ({ property }) => ({
61
+ type: "number",
62
+ value: RefSubject.map(options.state, (state) => state.value),
63
+ ".value": RefSubject.map(options.state, (state) => String(state.value)),
64
+ min: property("min", undefined),
65
+ max: property("max", undefined),
66
+ step: property("step", undefined),
67
+ onchange: EventHandler.make(Effect.fn((event) => setValue(options.state, Dom.currentTarget(event).valueAsNumber))),
68
+ ref: options.state,
69
+ });
70
+ }
71
+ /** Renders a native number input synchronized with hydrated state.
72
+ * @remarks
73
+ * ## Why
74
+ * Browser editing, stepping, validation, and form behavior remain intact while
75
+ * change events update renderer-independent state.
76
+ * ## Ownership and lifetime
77
+ * Running the Fx owns listener/state subscriptions in its Scope. A custom host
78
+ * must preserve type, value, constraints, and the single hydration ref owner.
79
+ * @example
80
+ * ```ts
81
+ * import { Effect } from "effect"
82
+ * import * as SpinButton from "@typed/ui/SpinButton"
83
+ *
84
+ * const program = Effect.gen(function* () {
85
+ * const state = yield* SpinButton.makeState({ value: 1 })
86
+ * return SpinButton.SpinButton({ state, min: 0 })
87
+ * })
88
+ * ```
89
+ * @since 1.0.0
90
+ * @category components
91
+ */
92
+ export function SpinButton(options, host) {
93
+ return Dom.renderHost()(options, host, internalProps(options), "", (props) => {
94
+ return html `<input ...${props} />`;
95
+ });
96
+ }
@@ -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 testing
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 testing
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 testing
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
+ }