@typed/ui 1.0.0-beta.4 → 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.
- package/README.md +53 -9
- package/dist/Alert.d.ts +78 -0
- package/dist/Alert.d.ts.map +1 -0
- package/dist/Alert.js +37 -0
- package/dist/Button.d.ts +144 -0
- package/dist/Button.d.ts.map +1 -0
- package/dist/Button.js +38 -0
- package/dist/Carousel.d.ts +547 -0
- package/dist/Carousel.d.ts.map +1 -0
- package/dist/Carousel.js +387 -0
- package/dist/Checkbox.d.ts +203 -0
- package/dist/Checkbox.d.ts.map +1 -0
- package/dist/Checkbox.js +140 -0
- package/dist/Collection.d.ts +254 -0
- package/dist/Collection.d.ts.map +1 -0
- package/dist/Collection.js +218 -0
- package/dist/Combobox.d.ts +516 -0
- package/dist/Combobox.d.ts.map +1 -0
- package/dist/Combobox.js +371 -0
- package/dist/Component.d.ts +127 -0
- package/dist/Component.d.ts.map +1 -0
- package/dist/Component.js +60 -0
- package/dist/Composite.d.ts +823 -0
- package/dist/Composite.d.ts.map +1 -0
- package/dist/Composite.js +615 -0
- package/dist/Dialog.d.ts +544 -0
- package/dist/Dialog.d.ts.map +1 -0
- package/dist/Dialog.js +355 -0
- package/dist/Disclosure.d.ts +219 -0
- package/dist/Disclosure.d.ts.map +1 -0
- package/dist/Disclosure.js +128 -0
- package/dist/Dom/Events.d.ts +122 -0
- package/dist/Dom/Events.d.ts.map +1 -0
- package/dist/Dom/Events.js +192 -0
- package/dist/Dom/Props.d.ts +161 -0
- package/dist/Dom/Props.d.ts.map +1 -0
- package/dist/Dom/Props.js +110 -0
- package/dist/Dom/Refs.d.ts +58 -0
- package/dist/Dom/Refs.d.ts.map +1 -0
- package/dist/Dom/Refs.js +61 -0
- package/dist/Dom/Render.d.ts +59 -0
- package/dist/Dom/Render.d.ts.map +1 -0
- package/dist/Dom/Render.js +71 -0
- package/dist/Dom/Types.d.ts +570 -0
- package/dist/Dom/Types.d.ts.map +1 -0
- package/dist/Dom/Types.js +1 -0
- package/dist/Dom/index.d.ts +20 -0
- package/dist/Dom/index.d.ts.map +1 -0
- package/dist/Dom/index.js +8 -0
- package/dist/Dom.d.ts +14 -0
- package/dist/Dom.d.ts.map +1 -0
- package/dist/Dom.js +13 -0
- package/dist/Focusable.d.ts +85 -0
- package/dist/Focusable.d.ts.map +1 -0
- package/dist/Focusable.js +35 -0
- package/dist/Form.d.ts +1695 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/Form.js +987 -0
- package/dist/Grid.d.ts +569 -0
- package/dist/Grid.d.ts.map +1 -0
- package/dist/Grid.js +379 -0
- package/dist/Group.d.ts +147 -0
- package/dist/Group.d.ts.map +1 -0
- package/dist/Group.js +63 -0
- package/dist/Heading.d.ts +86 -0
- package/dist/Heading.d.ts.map +1 -0
- package/dist/Heading.js +48 -0
- package/dist/Hovercard.d.ts +297 -0
- package/dist/Hovercard.d.ts.map +1 -0
- package/dist/Hovercard.js +188 -0
- package/dist/HttpRouter.d.ts +129 -6
- package/dist/HttpRouter.d.ts.map +1 -1
- package/dist/HttpRouter.js +196 -53
- package/dist/Link.d.ts +67 -28
- package/dist/Link.d.ts.map +1 -1
- package/dist/Link.js +91 -37
- package/dist/Listbox.d.ts +437 -0
- package/dist/Listbox.d.ts.map +1 -0
- package/dist/Listbox.js +316 -0
- package/dist/Menu.d.ts +972 -0
- package/dist/Menu.d.ts.map +1 -0
- package/dist/Menu.js +731 -0
- package/dist/Menubar.d.ts +367 -0
- package/dist/Menubar.d.ts.map +1 -0
- package/dist/Menubar.js +265 -0
- package/dist/Meter.d.ts +217 -0
- package/dist/Meter.d.ts.map +1 -0
- package/dist/Meter.js +94 -0
- package/dist/NativeDetails.d.ts +41 -0
- package/dist/NativeDetails.d.ts.map +1 -0
- package/dist/NativeDetails.js +40 -0
- package/dist/NativeDialog.d.ts +64 -0
- package/dist/NativeDialog.d.ts.map +1 -0
- package/dist/NativeDialog.js +50 -0
- package/dist/NativePopover.d.ts +41 -0
- package/dist/NativePopover.d.ts.map +1 -0
- package/dist/NativePopover.js +46 -0
- package/dist/Popover.d.ts +241 -0
- package/dist/Popover.d.ts.map +1 -0
- package/dist/Popover.js +141 -0
- package/dist/RadioGroup.d.ts +432 -0
- package/dist/RadioGroup.d.ts.map +1 -0
- package/dist/RadioGroup.js +291 -0
- package/dist/Role.d.ts +64 -0
- package/dist/Role.d.ts.map +1 -0
- package/dist/Role.js +27 -0
- package/dist/Select.d.ts +529 -0
- package/dist/Select.d.ts.map +1 -0
- package/dist/Select.js +407 -0
- package/dist/Separator.d.ts +54 -0
- package/dist/Separator.d.ts.map +1 -0
- package/dist/Separator.js +26 -0
- package/dist/Slider.d.ts +188 -0
- package/dist/Slider.d.ts.map +1 -0
- package/dist/Slider.js +96 -0
- package/dist/SpinButton.d.ts +188 -0
- package/dist/SpinButton.d.ts.map +1 -0
- package/dist/SpinButton.js +96 -0
- package/dist/Storybook.d.ts +76 -0
- package/dist/Storybook.d.ts.map +1 -0
- package/dist/Storybook.js +102 -0
- package/dist/Switch.d.ts +187 -0
- package/dist/Switch.d.ts.map +1 -0
- package/dist/Switch.js +116 -0
- package/dist/Tab.d.ts +26 -0
- package/dist/Tab.d.ts.map +1 -0
- package/dist/Tab.js +25 -0
- package/dist/Tabs.d.ts +591 -0
- package/dist/Tabs.d.ts.map +1 -0
- package/dist/Tabs.js +346 -0
- package/dist/Toolbar.d.ts +366 -0
- package/dist/Toolbar.d.ts.map +1 -0
- package/dist/Toolbar.js +245 -0
- package/dist/Tooltip.d.ts +296 -0
- package/dist/Tooltip.d.ts.map +1 -0
- package/dist/Tooltip.js +172 -0
- package/dist/Tree.d.ts +591 -0
- package/dist/Tree.d.ts.map +1 -0
- package/dist/Tree.js +434 -0
- package/dist/TreeGrid.d.ts +645 -0
- package/dist/TreeGrid.d.ts.map +1 -0
- package/dist/TreeGrid.js +420 -0
- package/dist/VisuallyHidden.d.ts +54 -0
- package/dist/VisuallyHidden.d.ts.map +1 -0
- package/dist/VisuallyHidden.js +27 -0
- package/dist/WindowSplitter.d.ts +376 -0
- package/dist/WindowSplitter.d.ts.map +1 -0
- package/dist/WindowSplitter.js +222 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -0
- package/package.json +45 -18
- package/src/HttpRouter.test.ts +0 -256
- package/src/HttpRouter.ts +0 -165
- package/src/Link.test.ts +0 -84
- package/src/Link.ts +0 -107
- 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
|
+
}
|