@typed/ui 1.0.0-beta.0 → 1.0.0-beta.10

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 (156) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -26
  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 +199 -56
  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 +47 -14
  151. package/src/HttpRouter.test.ts +0 -294
  152. package/src/HttpRouter.ts +0 -168
  153. package/src/Link.test.ts +0 -85
  154. package/src/Link.ts +0 -107
  155. package/src/index.ts +0 -2
  156. package/tsconfig.json +0 -6
@@ -0,0 +1,336 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Scope from "effect/Scope";
3
+ import * as Schema from "effect/Schema";
4
+ import { Fx, RefSubject } from "@typed/fx";
5
+ import { EventHandler, type Renderable } from "@typed/template";
6
+ import * as Dom from "./Dom.js";
7
+ import type { ComposedRef } from "./Dom/Refs.js";
8
+ import type { HostResult } from "./Dom/Types.js";
9
+ /** Visual orientation of the separator between panes.
10
+ * @remarks
11
+ * A vertical separator adjusts with Left/Right; a horizontal separator adjusts
12
+ * with Up/Down, matching the axis perpendicular to the dividing line.
13
+ * @since 1.0.0
14
+ * @category Resize axis
15
+ */
16
+ export type Orientation = "horizontal" | "vertical";
17
+ /** Current value and keyboard policy for a window splitter.
18
+ * @remarks
19
+ * All range and collapse information remains renderer-independent and directly
20
+ * testable.
21
+ * Plain state retains no resources; its hydrated RefSubject is Scope-owned.
22
+ * @since 1.0.0
23
+ * @category Pane sizing state
24
+ */
25
+ export interface State {
26
+ /** Current pane size value.
27
+ * @remarks
28
+ * It drives `aria-valuenow` and keyboard adjustments.
29
+ * @since 1.0.0
30
+ * @category Current value
31
+ */
32
+ readonly value: number;
33
+ /** Restore value used by `toggleCollapsed`.
34
+ * @remarks
35
+ * It starts at the initial clamped value. `toggleCollapsed` overwrites it only
36
+ * when collapsing from a non-minimum value, then reads it when restoring.
37
+ * Ordinary `setValue` and `adjust` calls do not update it.
38
+ * @since 1.0.0
39
+ * @category Collapse and restore
40
+ */
41
+ readonly previousValue: number;
42
+ /** Inclusive minimum pane value.
43
+ * @remarks
44
+ * Updates clamp to this bound and Home jumps to it.
45
+ * @since 1.0.0
46
+ * @category Range constraints
47
+ */
48
+ readonly min: number;
49
+ /** Inclusive maximum pane value.
50
+ * @remarks
51
+ * Updates clamp to this bound and End jumps to it.
52
+ * @since 1.0.0
53
+ * @category Range constraints
54
+ */
55
+ readonly max: number;
56
+ /** Keyboard adjustment increment.
57
+ * @remarks
58
+ * Arrow keys require a deterministic application-defined change size. The
59
+ * state constructor does not enforce positivity; callers must provide a
60
+ * positive value for conventional keyboard direction.
61
+ * @since 1.0.0
62
+ * @category Keyboard adjustment
63
+ */
64
+ readonly step: number;
65
+ /** Separator orientation and keyboard axis.
66
+ * @remarks
67
+ * The value drives both `aria-orientation` and valid arrow keys.
68
+ * @since 1.0.0
69
+ * @category Keyboard navigation
70
+ */
71
+ readonly orientation: Orientation;
72
+ }
73
+ /** Initial splitter range and keyboard configuration.
74
+ * @remarks
75
+ * Defaults give the widget a complete deterministic state before rendering.
76
+ * Callers remain responsible for `min <= max` and `step > 0`.
77
+ * @since 1.0.0
78
+ * @category Pane sizing state
79
+ */
80
+ export interface InitialState {
81
+ /** Initial pane size, clamped to min/max.
82
+ * @remarks
83
+ * Invalid out-of-range starts become a valid accessible value.
84
+ * @since 1.0.0
85
+ * @category Current value
86
+ */
87
+ readonly value: number;
88
+ /** Minimum value, defaulting to zero.
89
+ * @remarks
90
+ * It defines the collapsed boundary and Home result. Callers must ensure it is
91
+ * less than or equal to `max`; neither the schema nor constructor compares them.
92
+ * @since 1.0.0
93
+ * @category Range constraints
94
+ */
95
+ readonly min?: number;
96
+ /** Maximum value, defaulting to one hundred.
97
+ * @remarks
98
+ * It defines the upper keyboard and ARIA bound. Callers must ensure it is
99
+ * greater than or equal to `min`; neither the schema nor constructor compares them.
100
+ * @since 1.0.0
101
+ * @category Range constraints
102
+ */
103
+ readonly max?: number;
104
+ /** Arrow-key increment, defaulting to ten.
105
+ * @remarks
106
+ * The widget needs an explicit adjustment granularity. Callers must provide a
107
+ * positive value; `Schema.Finite` rejects infinities and NaN but accepts zero
108
+ * and negative numbers.
109
+ * @since 1.0.0
110
+ * @category Keyboard adjustment
111
+ */
112
+ readonly step?: number;
113
+ /** Separator orientation, defaulting to vertical.
114
+ * @remarks
115
+ * Orientation determines ARIA output and arrow-key mapping.
116
+ * @since 1.0.0
117
+ * @category Keyboard navigation
118
+ */
119
+ readonly orientation?: Orientation;
120
+ }
121
+ /** Schema for the structural splitter hydration state.
122
+ * @remarks
123
+ * It validates finite numeric fields and the orientation union. It does not
124
+ * enforce `min <= max`, a positive `step`, or any relationship among fields;
125
+ * callers must satisfy those semantic preconditions.
126
+ * @since 1.0.0
127
+ * @category Pane sizing state
128
+ */
129
+ export declare const StateSchema: Schema.Struct<{
130
+ readonly value: Schema.Finite;
131
+ readonly previousValue: Schema.Finite;
132
+ readonly min: Schema.Finite;
133
+ readonly max: Schema.Finite;
134
+ readonly step: Schema.Finite;
135
+ readonly orientation: Schema.Literals<readonly ["horizontal", "vertical"]>;
136
+ }>;
137
+ /** Creates hydrated splitter state and clamps the initial value.
138
+ * @remarks
139
+ * Pane sizing behavior can be composed and tested without mounting a separator.
140
+ * Callers must supply `min <= max` and `step > 0`; the constructor defaults
141
+ * omitted bounds/step but does not validate those relationships.
142
+ * The calling Effect Scope owns the returned RefSubject.
143
+ * @example
144
+ * ```ts
145
+ * import { Effect } from "effect"
146
+ * import * as WindowSplitter from "@typed/ui/WindowSplitter"
147
+ *
148
+ * const program = Effect.gen(function* () {
149
+ * return yield* WindowSplitter.makeState({ value: 320, min: 180, max: 640 })
150
+ * })
151
+ * ```
152
+ * @since 1.0.0
153
+ * @category Pane sizing state
154
+ */
155
+ export declare function makeState(initial: InitialState): Effect.Effect<RefSubject.HydratedRefSubject<{
156
+ readonly value: number;
157
+ readonly previousValue: number;
158
+ readonly min: number;
159
+ readonly max: number;
160
+ readonly step: number;
161
+ readonly orientation: "horizontal" | "vertical";
162
+ }, Schema.SchemaError, never, never>, never, Scope.Scope>;
163
+ /** Sets and clamps the current splitter value.
164
+ * @remarks
165
+ * All callers share one transition bounded by the state's `min` and `max`.
166
+ * Correct range ordering remains a caller precondition. This operation does not
167
+ * change `previousValue`.
168
+ * The Effect uses the existing RefSubject lifetime and acquires no resource.
169
+ * @example
170
+ * ```ts
171
+ * import { Effect } from "effect"
172
+ * import * as WindowSplitter from "@typed/ui/WindowSplitter"
173
+ *
174
+ * const program = Effect.gen(function* () {
175
+ * const state = yield* WindowSplitter.makeState({ value: 320 })
176
+ * yield* WindowSplitter.setValue(state, 400)
177
+ * })
178
+ * ```
179
+ * @since 1.0.0
180
+ * @category Bounded resizing
181
+ */
182
+ export declare function setValue<E, R>(state: RefSubject.RefSubject<State, E, R>, value: number): Effect.Effect<State, E, R>;
183
+ /** Adds a delta and clamps the resulting splitter value.
184
+ * @remarks
185
+ * Arrow-key behavior can be tested independently of DOM events. Correct range
186
+ * ordering remains a caller precondition, and this operation does not change
187
+ * `previousValue`.
188
+ * The Effect uses the existing RefSubject lifetime and acquires no resource.
189
+ * @example
190
+ * ```ts
191
+ * import { Effect } from "effect"
192
+ * import * as WindowSplitter from "@typed/ui/WindowSplitter"
193
+ *
194
+ * const program = Effect.gen(function* () {
195
+ * const state = yield* WindowSplitter.makeState({ value: 320, step: 24 })
196
+ * yield* WindowSplitter.adjust(state, 24)
197
+ * })
198
+ * ```
199
+ * @since 1.0.0
200
+ * @category Bounded resizing
201
+ */
202
+ export declare function adjust<E, R>(state: RefSubject.RefSubject<State, E, R>, delta: number): Effect.Effect<State, E, R>;
203
+ /** Collapses to min or restores the recorded collapse value.
204
+ * @remarks
205
+ * When the current value is not `min`, the operation records that exact value in
206
+ * `previousValue` and collapses. At `min`, it restores the stored value after
207
+ * clamping it to the current range. Other updates do not maintain a history.
208
+ * The Effect updates existing state atomically and acquires no resource.
209
+ * @example
210
+ * ```ts
211
+ * import { Effect } from "effect"
212
+ * import * as WindowSplitter from "@typed/ui/WindowSplitter"
213
+ *
214
+ * const program = Effect.gen(function* () {
215
+ * const state = yield* WindowSplitter.makeState({ value: 320 })
216
+ * yield* WindowSplitter.toggleCollapsed(state)
217
+ * })
218
+ * ```
219
+ * @since 1.0.0
220
+ * @category Collapse and restore
221
+ */
222
+ export declare function toggleCollapsed<E, R>(state: RefSubject.RefSubject<State, E, R>): Effect.Effect<State, E, R>;
223
+ /** Options for the focusable ARIA window splitter.
224
+ * @remarks
225
+ * The model connects renderer-independent sizing to its controlled pane and
226
+ * accessible range metadata. Its state must already satisfy `min <= max` and a
227
+ * positive `step`; the component does not repair invalid state.
228
+ * Options are inert; rendering owns subscriptions/listeners by Scope.
229
+ * @since 1.0.0
230
+ * @category Resize separator
231
+ */
232
+ export interface WindowSplitterOptions extends Dom.HostOptions<HTMLDivElement> {
233
+ /** Hydrated sizing state.
234
+ * @remarks
235
+ * One source drives keyboard updates and every ARIA range attribute.
236
+ * The splitter borrows state; its original Scope owns it.
237
+ * @since 1.0.0
238
+ * @category State connection
239
+ */
240
+ readonly state: RefSubject.HydratedRefSubject<State, Schema.SchemaError>;
241
+ /** Id of the pane controlled by the splitter.
242
+ * @remarks
243
+ * `aria-controls` makes the resizing relationship explicit.
244
+ * Dynamic values follow the component Scope.
245
+ * @since 1.0.0
246
+ * @category Identity and relationships
247
+ */
248
+ readonly primaryPaneId: Renderable.Any<string | null | undefined>;
249
+ /** Accessible name when no external label is supplied.
250
+ * @remarks
251
+ * A focusable separator needs a discoverable purpose.
252
+ * Dynamic values follow the component Scope.
253
+ * @since 1.0.0
254
+ * @category Accessible naming
255
+ */
256
+ readonly label?: Renderable.Any<string | null | undefined>;
257
+ /** Human-readable value text supplementing the number.
258
+ * @remarks
259
+ * Pane sizes such as percentages or labels may be clearer than raw values.
260
+ * Dynamic values follow the component Scope.
261
+ * @since 1.0.0
262
+ * @category Accessible value text
263
+ */
264
+ readonly valueText?: Renderable.Any<string | null | undefined>;
265
+ /** State-value units represented by one CSS pixel of pointer movement.
266
+ * @remarks
267
+ * By default, the usable parent width (vertical separator) or height
268
+ * (horizontal separator), minus the separator thickness, represents 100
269
+ * units. This matches percentage pane sizes. Set `1` for CSS-pixel values.
270
+ * Positive axis-aligned CSS scaling is accounted for; pointer coordinates
271
+ * are converted from viewport pixels to the parent's CSS-pixel scale.
272
+ * Supply a positive finite number when using another unit system.
273
+ * Keyboard step and min/max remain independent of pointer resolution.
274
+ * @since 1.0.0
275
+ * @category Pointer resizing
276
+ */
277
+ readonly valuePerPixel?: number;
278
+ /** Whether keyboard and pointer adjustments are disabled.
279
+ * @remarks
280
+ * ARIA disabled state and handler behavior must agree.
281
+ * Dynamic values follow the component Scope.
282
+ * @since 1.0.0
283
+ * @category Availability
284
+ */
285
+ readonly disabled?: Renderable.Any<boolean | null | undefined>;
286
+ }
287
+ type WindowSplitterInternalProps<Options extends WindowSplitterOptions> = {
288
+ readonly role: "separator";
289
+ readonly style: Fx.Fx<string | Partial<CSSStyleDeclaration>, Renderable.Error<Options>, Renderable.Services<Options>>;
290
+ readonly tabindex: 0;
291
+ readonly "aria-orientation": RefSubject.Computed<Orientation, Schema.SchemaError>;
292
+ readonly "aria-valuenow": RefSubject.Computed<number, Schema.SchemaError>;
293
+ readonly "aria-valuemin": RefSubject.Computed<number, Schema.SchemaError>;
294
+ readonly "aria-valuemax": RefSubject.Computed<number, Schema.SchemaError>;
295
+ readonly "aria-controls": Dom.NonNullish<Dom.Property<Options, "primaryPaneId">> | undefined;
296
+ readonly "aria-label": Dom.NonNullish<Dom.Property<Options, "label">> | undefined;
297
+ readonly "aria-valuetext": Dom.NonNullish<Dom.Property<Options, "valueText">> | undefined;
298
+ readonly "aria-disabled": Dom.NonNullish<Dom.Property<Options, "disabled">> | false;
299
+ readonly onkeydown: EventHandler.EventHandler<KeyboardEvent, Schema.SchemaError>;
300
+ readonly onpointerdown: EventHandler.EventHandler<PointerEvent>;
301
+ readonly onpointermove: EventHandler.EventHandler<PointerEvent, Schema.SchemaError>;
302
+ readonly onpointerup: EventHandler.EventHandler<PointerEvent>;
303
+ readonly onpointercancel: EventHandler.EventHandler<PointerEvent>;
304
+ readonly onlostpointercapture: EventHandler.EventHandler<PointerEvent>;
305
+ readonly ref: ComposedRef<RefSubject.HydratedRefSubject<State, Schema.SchemaError>, (element: HTMLDivElement) => Effect.Effect<void, never, Scope.Scope>> | undefined;
306
+ };
307
+ /** Renders a focusable ARIA separator for resizing a pane.
308
+ * @remarks
309
+ * The component exposes native DOM focus/events and the ARIA window-splitter
310
+ * range contract: arrows adjust by step, Home/End reach bounds, and Enter
311
+ * collapses/restores. Pointer dragging adjusts the same clamped state, using
312
+ * percentage-of-parent movement by default or the supplied `valuePerPixel`.
313
+ * Native pointer capture keeps movement attached outside the separator;
314
+ * release, cancellation, lost capture, and Scope teardown end the drag.
315
+ * The host composes caller styles with `touch-action: none` so touch gestures
316
+ * remain resize interactions. The caller binds state to actual pane layout. Disabled state
317
+ * leaves keyboard and pointer activation inert.
318
+ * Running the returned Fx owns real key listeners, reactive ARIA attributes,
319
+ * and the hydration ref in its Effect Scope. A custom host must preserve role,
320
+ * tab index, range/relationship attributes, handler, and one hydration owner.
321
+ * @example
322
+ * ```ts
323
+ * import { Effect } from "effect"
324
+ * import * as WindowSplitter from "@typed/ui/WindowSplitter"
325
+ *
326
+ * const program = Effect.gen(function* () {
327
+ * const state = yield* WindowSplitter.makeState({ value: 320, min: 180, max: 640 })
328
+ * return WindowSplitter.WindowSplitter({ state, primaryPaneId: "navigation" })
329
+ * })
330
+ * ```
331
+ * @since 1.0.0
332
+ * @category Resize separator
333
+ */
334
+ export declare function WindowSplitter<const Options extends WindowSplitterOptions, const Host extends HostResult = never>(options: Options, host?: Dom.HostOverride<Dom.RenderHostProps<Options, WindowSplitterInternalProps<Options>>, "", Host>): import("./Dom/Types.js").HostComponent<Host | Options>;
335
+ export {};
336
+ //# sourceMappingURL=WindowSplitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WindowSplitter.d.ts","sourceRoot":"","sources":["../src/WindowSplitter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,305 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Scope from "effect/Scope";
3
+ import * as Schema from "effect/Schema";
4
+ import { Fx, RefSubject } from "@typed/fx";
5
+ import { EventHandler, html, liftRenderableToFx } from "@typed/template";
6
+ import * as Dom from "./Dom.js";
7
+ /** Schema for the structural splitter hydration state.
8
+ * @remarks
9
+ * It validates finite numeric fields and the orientation union. It does not
10
+ * enforce `min <= max`, a positive `step`, or any relationship among fields;
11
+ * callers must satisfy those semantic preconditions.
12
+ * @since 1.0.0
13
+ * @category Pane sizing state
14
+ */
15
+ export const StateSchema = Schema.Struct({
16
+ value: Schema.Finite,
17
+ previousValue: Schema.Finite,
18
+ min: Schema.Finite,
19
+ max: Schema.Finite,
20
+ step: Schema.Finite,
21
+ orientation: Schema.Literals(["horizontal", "vertical"]),
22
+ });
23
+ /** Creates hydrated splitter state and clamps the initial value.
24
+ * @remarks
25
+ * Pane sizing behavior can be composed and tested without mounting a separator.
26
+ * Callers must supply `min <= max` and `step > 0`; the constructor defaults
27
+ * omitted bounds/step but does not validate those relationships.
28
+ * The calling Effect Scope owns the returned RefSubject.
29
+ * @example
30
+ * ```ts
31
+ * import { Effect } from "effect"
32
+ * import * as WindowSplitter from "@typed/ui/WindowSplitter"
33
+ *
34
+ * const program = Effect.gen(function* () {
35
+ * return yield* WindowSplitter.makeState({ value: 320, min: 180, max: 640 })
36
+ * })
37
+ * ```
38
+ * @since 1.0.0
39
+ * @category Pane sizing state
40
+ */
41
+ export function makeState(initial) {
42
+ const min = initial.min ?? 0;
43
+ const max = initial.max ?? 100;
44
+ const value = clamp(initial.value, min, max);
45
+ return RefSubject.hydrate(StateSchema, {
46
+ value,
47
+ previousValue: value,
48
+ min,
49
+ max,
50
+ step: initial.step ?? 10,
51
+ orientation: initial.orientation ?? "vertical",
52
+ });
53
+ }
54
+ /** Sets and clamps the current splitter value.
55
+ * @remarks
56
+ * All callers share one transition bounded by the state's `min` and `max`.
57
+ * Correct range ordering remains a caller precondition. This operation does not
58
+ * change `previousValue`.
59
+ * The Effect uses the existing RefSubject lifetime and acquires no resource.
60
+ * @example
61
+ * ```ts
62
+ * import { Effect } from "effect"
63
+ * import * as WindowSplitter from "@typed/ui/WindowSplitter"
64
+ *
65
+ * const program = Effect.gen(function* () {
66
+ * const state = yield* WindowSplitter.makeState({ value: 320 })
67
+ * yield* WindowSplitter.setValue(state, 400)
68
+ * })
69
+ * ```
70
+ * @since 1.0.0
71
+ * @category Bounded resizing
72
+ */
73
+ export function setValue(state, value) {
74
+ return RefSubject.update(state, (current) => ({
75
+ ...current,
76
+ value: clamp(value, current.min, current.max),
77
+ }));
78
+ }
79
+ /** Adds a delta and clamps the resulting splitter value.
80
+ * @remarks
81
+ * Arrow-key behavior can be tested independently of DOM events. Correct range
82
+ * ordering remains a caller precondition, and this operation does not change
83
+ * `previousValue`.
84
+ * The Effect uses the existing RefSubject lifetime and acquires no resource.
85
+ * @example
86
+ * ```ts
87
+ * import { Effect } from "effect"
88
+ * import * as WindowSplitter from "@typed/ui/WindowSplitter"
89
+ *
90
+ * const program = Effect.gen(function* () {
91
+ * const state = yield* WindowSplitter.makeState({ value: 320, step: 24 })
92
+ * yield* WindowSplitter.adjust(state, 24)
93
+ * })
94
+ * ```
95
+ * @since 1.0.0
96
+ * @category Bounded resizing
97
+ */
98
+ export function adjust(state, delta) {
99
+ return RefSubject.update(state, (current) => ({
100
+ ...current,
101
+ value: clamp(current.value + delta, current.min, current.max),
102
+ }));
103
+ }
104
+ /** Collapses to min or restores the recorded collapse value.
105
+ * @remarks
106
+ * When the current value is not `min`, the operation records that exact value in
107
+ * `previousValue` and collapses. At `min`, it restores the stored value after
108
+ * clamping it to the current range. Other updates do not maintain a history.
109
+ * The Effect updates existing state atomically and acquires no resource.
110
+ * @example
111
+ * ```ts
112
+ * import { Effect } from "effect"
113
+ * import * as WindowSplitter from "@typed/ui/WindowSplitter"
114
+ *
115
+ * const program = Effect.gen(function* () {
116
+ * const state = yield* WindowSplitter.makeState({ value: 320 })
117
+ * yield* WindowSplitter.toggleCollapsed(state)
118
+ * })
119
+ * ```
120
+ * @since 1.0.0
121
+ * @category Collapse and restore
122
+ */
123
+ export function toggleCollapsed(state) {
124
+ return RefSubject.update(state, (current) => current.value === current.min
125
+ ? { ...current, value: clamp(current.previousValue, current.min, current.max) }
126
+ : { ...current, previousValue: current.value, value: current.min });
127
+ }
128
+ function internalProps(options) {
129
+ const drags = new WeakMap();
130
+ const release = (element) => {
131
+ const drag = drags.get(element);
132
+ drags.delete(element);
133
+ if (drag !== undefined && element.hasPointerCapture(drag.pointerId)) {
134
+ element.releasePointerCapture(drag.pointerId);
135
+ }
136
+ };
137
+ const pointerRef = Effect.fn(function* (element) {
138
+ yield* Scope.addFinalizer(yield* Effect.scope, Effect.sync(() => {
139
+ release(element);
140
+ }));
141
+ });
142
+ const pointerStyle = liftRenderableToFx(options.props?.style).pipe(Fx.map((style) => {
143
+ if (typeof style === "string")
144
+ return `${style};touch-action:none;`;
145
+ if (typeof style?.cssText === "string")
146
+ return `${style.cssText};touch-action:none;`;
147
+ return { ...style, touchAction: "none" };
148
+ }));
149
+ const onpointerdown = EventHandler.make((event) => Effect.sync(() => {
150
+ const element = Dom.currentTarget(event);
151
+ if (event.button !== 0 || !event.isPrimary || element.getAttribute("aria-disabled") === "true")
152
+ return;
153
+ if (drags.has(element))
154
+ return;
155
+ // The visible ARIA snapshot starts the native gesture without yielding to
156
+ // state acquisition before preventDefault and pointer capture.
157
+ const valueAttribute = element.getAttribute("aria-valuenow");
158
+ const value = Number(valueAttribute);
159
+ const orientation = element.getAttribute("aria-orientation");
160
+ if (valueAttribute === null || !Number.isFinite(value))
161
+ return;
162
+ const parent = element.parentElement;
163
+ if (parent === null)
164
+ return;
165
+ const vertical = orientation === "vertical";
166
+ const parentRect = parent.getBoundingClientRect();
167
+ const separatorRect = element.getBoundingClientRect();
168
+ // Pointer coordinates and DOMRects use viewport pixels. Convert the
169
+ // parent's client extent to that same unit, excluding its border/scrollbar.
170
+ const axisScale = vertical
171
+ ? parentRect.width / parent.offsetWidth
172
+ : parentRect.height / parent.offsetHeight;
173
+ if (!Number.isFinite(axisScale) || axisScale <= 0)
174
+ return;
175
+ const extent = vertical
176
+ ? parent.clientWidth * axisScale - separatorRect.width
177
+ : parent.clientHeight * axisScale - separatorRect.height;
178
+ const scale = options.valuePerPixel === undefined
179
+ ? 100 / extent
180
+ : options.valuePerPixel / axisScale;
181
+ if (!Number.isFinite(scale) || scale <= 0)
182
+ return;
183
+ event.preventDefault();
184
+ element.focus();
185
+ element.setPointerCapture(event.pointerId);
186
+ drags.set(element, {
187
+ pointerId: event.pointerId,
188
+ coordinate: vertical ? event.clientX : event.clientY,
189
+ value,
190
+ orientation: vertical ? "vertical" : "horizontal",
191
+ scale,
192
+ });
193
+ }));
194
+ const onpointermove = EventHandler.make(Effect.fn(function* (event) {
195
+ const element = Dom.currentTarget(event);
196
+ const drag = drags.get(element);
197
+ if (drag === undefined || drag.pointerId !== event.pointerId)
198
+ return;
199
+ if (element.getAttribute("aria-disabled") === "true" || (event.buttons & 1) === 0) {
200
+ release(element);
201
+ return;
202
+ }
203
+ event.preventDefault();
204
+ const coordinate = drag.orientation === "vertical" ? event.clientX : event.clientY;
205
+ yield* setValue(options.state, drag.value + (coordinate - drag.coordinate) * drag.scale);
206
+ }));
207
+ const endDrag = EventHandler.make((event) => Effect.sync(() => {
208
+ const element = Dom.currentTarget(event);
209
+ if (drags.get(element)?.pointerId === event.pointerId)
210
+ release(element);
211
+ }));
212
+ const onkeydown = EventHandler.make(Effect.fn(function* (event) {
213
+ const current = yield* options.state;
214
+ if (Dom.currentTarget(event).getAttribute("aria-disabled") === "true") {
215
+ return current;
216
+ }
217
+ const delta = keyDelta(event.key, current);
218
+ if (delta !== undefined) {
219
+ event.preventDefault();
220
+ return yield* adjust(options.state, delta);
221
+ }
222
+ if (event.key === "Home") {
223
+ event.preventDefault();
224
+ return yield* setValue(options.state, current.min);
225
+ }
226
+ if (event.key === "End") {
227
+ event.preventDefault();
228
+ return yield* setValue(options.state, current.max);
229
+ }
230
+ if (event.key === "Enter") {
231
+ event.preventDefault();
232
+ return yield* toggleCollapsed(options.state);
233
+ }
234
+ return current;
235
+ }));
236
+ return ({ property }) => ({
237
+ role: "separator",
238
+ style: pointerStyle,
239
+ tabindex: 0,
240
+ "aria-orientation": RefSubject.map(options.state, (state) => state.orientation),
241
+ "aria-valuenow": RefSubject.map(options.state, (state) => state.value),
242
+ "aria-valuemin": RefSubject.map(options.state, (state) => state.min),
243
+ "aria-valuemax": RefSubject.map(options.state, (state) => state.max),
244
+ "aria-controls": property("primaryPaneId", undefined),
245
+ "aria-label": property("label", undefined),
246
+ "aria-valuetext": property("valueText", undefined),
247
+ "aria-disabled": property("disabled", false),
248
+ onkeydown,
249
+ onpointerdown,
250
+ onpointermove,
251
+ onpointerup: endDrag,
252
+ onpointercancel: endDrag,
253
+ onlostpointercapture: endDrag,
254
+ ref: Dom.composeRefs(options.state, pointerRef),
255
+ });
256
+ }
257
+ /** Renders a focusable ARIA separator for resizing a pane.
258
+ * @remarks
259
+ * The component exposes native DOM focus/events and the ARIA window-splitter
260
+ * range contract: arrows adjust by step, Home/End reach bounds, and Enter
261
+ * collapses/restores. Pointer dragging adjusts the same clamped state, using
262
+ * percentage-of-parent movement by default or the supplied `valuePerPixel`.
263
+ * Native pointer capture keeps movement attached outside the separator;
264
+ * release, cancellation, lost capture, and Scope teardown end the drag.
265
+ * The host composes caller styles with `touch-action: none` so touch gestures
266
+ * remain resize interactions. The caller binds state to actual pane layout. Disabled state
267
+ * leaves keyboard and pointer activation inert.
268
+ * Running the returned Fx owns real key listeners, reactive ARIA attributes,
269
+ * and the hydration ref in its Effect Scope. A custom host must preserve role,
270
+ * tab index, range/relationship attributes, handler, and one hydration owner.
271
+ * @example
272
+ * ```ts
273
+ * import { Effect } from "effect"
274
+ * import * as WindowSplitter from "@typed/ui/WindowSplitter"
275
+ *
276
+ * const program = Effect.gen(function* () {
277
+ * const state = yield* WindowSplitter.makeState({ value: 320, min: 180, max: 640 })
278
+ * return WindowSplitter.WindowSplitter({ state, primaryPaneId: "navigation" })
279
+ * })
280
+ * ```
281
+ * @since 1.0.0
282
+ * @category Resize separator
283
+ */
284
+ export function WindowSplitter(options, host) {
285
+ return Dom.renderHost()(options, host, internalProps(options), "", (props) => {
286
+ return html `<div ...${props}></div>`;
287
+ });
288
+ }
289
+ function keyDelta(key, state) {
290
+ if (state.orientation === "vertical") {
291
+ if (key === "ArrowLeft")
292
+ return -state.step;
293
+ if (key === "ArrowRight")
294
+ return state.step;
295
+ return undefined;
296
+ }
297
+ if (key === "ArrowUp")
298
+ return -state.step;
299
+ if (key === "ArrowDown")
300
+ return state.step;
301
+ return undefined;
302
+ }
303
+ function clamp(value, min, max) {
304
+ return Math.min(Math.max(value, min), max);
305
+ }