@foldkit/ui 0.112.0
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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/anchor.d.ts +38 -0
- package/dist/anchor.d.ts.map +1 -0
- package/dist/anchor.js +142 -0
- package/dist/animation/index.d.ts +49 -0
- package/dist/animation/index.d.ts.map +1 -0
- package/dist/animation/index.js +75 -0
- package/dist/animation/public.d.ts +3 -0
- package/dist/animation/public.d.ts.map +1 -0
- package/dist/animation/public.js +1 -0
- package/dist/animation/schema.d.ts +43 -0
- package/dist/animation/schema.d.ts.map +1 -0
- package/dist/animation/schema.js +41 -0
- package/dist/animation/update.d.ts +24 -0
- package/dist/animation/update.d.ts.map +1 -0
- package/dist/animation/update.js +67 -0
- package/dist/button/index.d.ts +17 -0
- package/dist/button/index.d.ts.map +1 -0
- package/dist/button/index.js +22 -0
- package/dist/button/public.d.ts +3 -0
- package/dist/button/public.d.ts.map +1 -0
- package/dist/button/public.js +1 -0
- package/dist/calendar/index.d.ts +462 -0
- package/dist/calendar/index.d.ts.map +1 -0
- package/dist/calendar/index.js +825 -0
- package/dist/calendar/public.d.ts +3 -0
- package/dist/calendar/public.d.ts.map +1 -0
- package/dist/calendar/public.js +1 -0
- package/dist/checkbox/index.d.ts +119 -0
- package/dist/checkbox/index.d.ts.map +1 -0
- package/dist/checkbox/index.js +111 -0
- package/dist/checkbox/public.d.ts +3 -0
- package/dist/checkbox/public.d.ts.map +1 -0
- package/dist/checkbox/public.js +1 -0
- package/dist/combobox/multi.d.ts +183 -0
- package/dist/combobox/multi.d.ts.map +1 -0
- package/dist/combobox/multi.js +81 -0
- package/dist/combobox/multiPublic.d.ts +3 -0
- package/dist/combobox/multiPublic.d.ts.map +1 -0
- package/dist/combobox/multiPublic.js +1 -0
- package/dist/combobox/public.d.ts +7 -0
- package/dist/combobox/public.d.ts.map +1 -0
- package/dist/combobox/public.js +3 -0
- package/dist/combobox/shared.d.ts +423 -0
- package/dist/combobox/shared.d.ts.map +1 -0
- package/dist/combobox/shared.js +708 -0
- package/dist/combobox/single.d.ts +198 -0
- package/dist/combobox/single.d.ts.map +1 -0
- package/dist/combobox/single.js +106 -0
- package/dist/datePicker/index.d.ts +457 -0
- package/dist/datePicker/index.d.ts.map +1 -0
- package/dist/datePicker/index.js +318 -0
- package/dist/datePicker/public.d.ts +3 -0
- package/dist/datePicker/public.d.ts.map +1 -0
- package/dist/datePicker/public.js +1 -0
- package/dist/dialog/index.d.ts +160 -0
- package/dist/dialog/index.d.ts.map +1 -0
- package/dist/dialog/index.js +211 -0
- package/dist/dialog/public.d.ts +3 -0
- package/dist/dialog/public.d.ts.map +1 -0
- package/dist/dialog/public.js +1 -0
- package/dist/disclosure/index.d.ts +110 -0
- package/dist/disclosure/index.d.ts.map +1 -0
- package/dist/disclosure/index.js +111 -0
- package/dist/disclosure/public.d.ts +3 -0
- package/dist/disclosure/public.d.ts.map +1 -0
- package/dist/disclosure/public.js +1 -0
- package/dist/dragAndDrop/index.d.ts +540 -0
- package/dist/dragAndDrop/index.d.ts.map +1 -0
- package/dist/dragAndDrop/index.js +535 -0
- package/dist/dragAndDrop/public.d.ts +3 -0
- package/dist/dragAndDrop/public.d.ts.map +1 -0
- package/dist/dragAndDrop/public.js +1 -0
- package/dist/fieldset/index.d.ts +21 -0
- package/dist/fieldset/index.d.ts.map +1 -0
- package/dist/fieldset/index.js +25 -0
- package/dist/fieldset/public.d.ts +3 -0
- package/dist/fieldset/public.d.ts.map +1 -0
- package/dist/fieldset/public.js +1 -0
- package/dist/fileDrop/index.d.ts +109 -0
- package/dist/fileDrop/index.d.ts.map +1 -0
- package/dist/fileDrop/index.js +127 -0
- package/dist/fileDrop/public.d.ts +3 -0
- package/dist/fileDrop/public.d.ts.map +1 -0
- package/dist/fileDrop/public.js +1 -0
- package/dist/group.d.ts +8 -0
- package/dist/group.d.ts.map +1 -0
- package/dist/group.js +13 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/input/index.d.ts +26 -0
- package/dist/input/index.d.ts.map +1 -0
- package/dist/input/index.js +43 -0
- package/dist/input/public.d.ts +3 -0
- package/dist/input/public.d.ts.map +1 -0
- package/dist/input/public.js +1 -0
- package/dist/internal/optionExtensions.d.ts +6 -0
- package/dist/internal/optionExtensions.d.ts.map +1 -0
- package/dist/internal/optionExtensions.js +2 -0
- package/dist/keyboard.d.ts +6 -0
- package/dist/keyboard.d.ts.map +1 -0
- package/dist/keyboard.js +9 -0
- package/dist/listbox/multi.d.ts +189 -0
- package/dist/listbox/multi.d.ts.map +1 -0
- package/dist/listbox/multi.js +65 -0
- package/dist/listbox/multiPublic.d.ts +3 -0
- package/dist/listbox/multiPublic.d.ts.map +1 -0
- package/dist/listbox/multiPublic.js +1 -0
- package/dist/listbox/public.d.ts +7 -0
- package/dist/listbox/public.d.ts.map +1 -0
- package/dist/listbox/public.js +3 -0
- package/dist/listbox/shared.d.ts +432 -0
- package/dist/listbox/shared.d.ts.map +1 -0
- package/dist/listbox/shared.js +670 -0
- package/dist/listbox/single.d.ts +207 -0
- package/dist/listbox/single.d.ts.map +1 -0
- package/dist/listbox/single.js +73 -0
- package/dist/menu/index.d.ts +368 -0
- package/dist/menu/index.d.ts.map +1 -0
- package/dist/menu/index.js +682 -0
- package/dist/menu/public.d.ts +4 -0
- package/dist/menu/public.d.ts.map +1 -0
- package/dist/menu/public.js +1 -0
- package/dist/popover/index.d.ts +267 -0
- package/dist/popover/index.d.ts.map +1 -0
- package/dist/popover/index.js +346 -0
- package/dist/popover/public.d.ts +4 -0
- package/dist/popover/public.d.ts.map +1 -0
- package/dist/popover/public.js +1 -0
- package/dist/radioGroup/index.d.ts +169 -0
- package/dist/radioGroup/index.d.ts.map +1 -0
- package/dist/radioGroup/index.js +197 -0
- package/dist/radioGroup/public.d.ts +3 -0
- package/dist/radioGroup/public.d.ts.map +1 -0
- package/dist/radioGroup/public.js +1 -0
- package/dist/select/index.d.ts +24 -0
- package/dist/select/index.d.ts.map +1 -0
- package/dist/select/index.js +40 -0
- package/dist/select/public.d.ts +3 -0
- package/dist/select/public.d.ts.map +1 -0
- package/dist/select/public.js +1 -0
- package/dist/slider/index.d.ts +318 -0
- package/dist/slider/index.d.ts.map +1 -0
- package/dist/slider/index.js +337 -0
- package/dist/slider/public.d.ts +3 -0
- package/dist/slider/public.d.ts.map +1 -0
- package/dist/slider/public.js +1 -0
- package/dist/switch/index.d.ts +99 -0
- package/dist/switch/index.d.ts.map +1 -0
- package/dist/switch/index.js +107 -0
- package/dist/switch/public.d.ts +3 -0
- package/dist/switch/public.d.ts.map +1 -0
- package/dist/switch/public.js +1 -0
- package/dist/tabs/index.d.ts +155 -0
- package/dist/tabs/index.d.ts.map +1 -0
- package/dist/tabs/index.js +185 -0
- package/dist/tabs/public.d.ts +3 -0
- package/dist/tabs/public.d.ts.map +1 -0
- package/dist/tabs/public.js +1 -0
- package/dist/test/apps/disabledButton.d.ts +38 -0
- package/dist/test/apps/disabledButton.d.ts.map +1 -0
- package/dist/test/apps/disabledButton.js +71 -0
- package/dist/textarea/index.d.ts +26 -0
- package/dist/textarea/index.d.ts.map +1 -0
- package/dist/textarea/index.js +44 -0
- package/dist/textarea/public.d.ts +3 -0
- package/dist/textarea/public.d.ts.map +1 -0
- package/dist/textarea/public.js +1 -0
- package/dist/toast/index.d.ts +608 -0
- package/dist/toast/index.d.ts.map +1 -0
- package/dist/toast/index.js +146 -0
- package/dist/toast/public.d.ts +4 -0
- package/dist/toast/public.d.ts.map +1 -0
- package/dist/toast/public.js +1 -0
- package/dist/toast/schema.d.ts +154 -0
- package/dist/toast/schema.d.ts.map +1 -0
- package/dist/toast/schema.js +93 -0
- package/dist/toast/update.d.ts +510 -0
- package/dist/toast/update.d.ts.map +1 -0
- package/dist/toast/update.js +225 -0
- package/dist/tooltip/index.d.ts +170 -0
- package/dist/tooltip/index.d.ts.map +1 -0
- package/dist/tooltip/index.js +253 -0
- package/dist/tooltip/public.d.ts +4 -0
- package/dist/tooltip/public.d.ts.map +1 -0
- package/dist/tooltip/public.js +1 -0
- package/dist/typeahead.d.ts +4 -0
- package/dist/typeahead.d.ts.map +1 -0
- package/dist/typeahead.js +14 -0
- package/dist/virtualList/index.d.ts +203 -0
- package/dist/virtualList/index.d.ts.map +1 -0
- package/dist/virtualList/index.js +392 -0
- package/dist/virtualList/public.d.ts +3 -0
- package/dist/virtualList/public.d.ts.map +1 -0
- package/dist/virtualList/public.js +1 -0
- package/dist/vitest-setup.d.ts +2 -0
- package/dist/vitest-setup.d.ts.map +1 -0
- package/dist/vitest-setup.js +2 -0
- package/package.json +161 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { Option, Schema as S } from 'effect';
|
|
2
|
+
import type { Command } from 'foldkit/command';
|
|
3
|
+
import { type ChildAttribute, type Html } from 'foldkit/html';
|
|
4
|
+
import { type Reflect } from 'foldkit/submodel';
|
|
5
|
+
import * as Subscription from 'foldkit/subscription';
|
|
6
|
+
/** Schema for the slider component's state. Tracks the current value, the
|
|
7
|
+
* range (min/max/step), and the active drag phase. */
|
|
8
|
+
export declare const Model: S.Struct<{
|
|
9
|
+
readonly id: S.String;
|
|
10
|
+
readonly value: S.Number;
|
|
11
|
+
readonly min: S.Number;
|
|
12
|
+
readonly max: S.Number;
|
|
13
|
+
readonly step: S.Number;
|
|
14
|
+
readonly dragState: S.Union<readonly [import("foldkit/schema").CallableTaggedStruct<"Idle", {}>, import("foldkit/schema").CallableTaggedStruct<"Dragging", {
|
|
15
|
+
originValue: S.Number;
|
|
16
|
+
}>]>;
|
|
17
|
+
}>;
|
|
18
|
+
export type Model = typeof Model.Type;
|
|
19
|
+
/** The user pressed the thumb. Starts a drag without changing the value. */
|
|
20
|
+
export declare const PressedThumb: import("foldkit/schema").CallableTaggedStruct<"PressedThumb", {}>;
|
|
21
|
+
/** The user pressed the track. Starts a drag and snaps the value to the
|
|
22
|
+
* cursor position. Ignored while already dragging, which absorbs the bubble
|
|
23
|
+
* from a thumb press so the value is not shifted. */
|
|
24
|
+
export declare const PressedPointer: import("foldkit/schema").CallableTaggedStruct<"PressedPointer", {
|
|
25
|
+
value: S.Number;
|
|
26
|
+
}>;
|
|
27
|
+
/** The pointer moved during a drag, producing a new snapped value from the
|
|
28
|
+
* cursor position within the track. */
|
|
29
|
+
export declare const MovedDragPointer: import("foldkit/schema").CallableTaggedStruct<"MovedDragPointer", {
|
|
30
|
+
value: S.Number;
|
|
31
|
+
}>;
|
|
32
|
+
/** The pointer was released during a drag. Commits the current value. */
|
|
33
|
+
export declare const ReleasedDragPointer: import("foldkit/schema").CallableTaggedStruct<"ReleasedDragPointer", {}>;
|
|
34
|
+
/** Escape was pressed during a drag. Restores the value from the drag origin. */
|
|
35
|
+
export declare const CancelledDrag: import("foldkit/schema").CallableTaggedStruct<"CancelledDrag", {}>;
|
|
36
|
+
/** The user pressed a keyboard navigation key on the focused thumb. */
|
|
37
|
+
export declare const PressedKeyboardNavigation: import("foldkit/schema").CallableTaggedStruct<"PressedKeyboardNavigation", {
|
|
38
|
+
direction: S.Literals<readonly ["StepDecrement", "StepIncrement", "PageDecrement", "PageIncrement", "Min", "Max"]>;
|
|
39
|
+
}>;
|
|
40
|
+
/** Union of all messages the slider component can produce. */
|
|
41
|
+
export declare const Message: S.Union<[
|
|
42
|
+
typeof PressedThumb,
|
|
43
|
+
typeof PressedPointer,
|
|
44
|
+
typeof MovedDragPointer,
|
|
45
|
+
typeof ReleasedDragPointer,
|
|
46
|
+
typeof CancelledDrag,
|
|
47
|
+
typeof PressedKeyboardNavigation
|
|
48
|
+
]>;
|
|
49
|
+
export type Message = typeof Message.Type;
|
|
50
|
+
export type PressedThumb = typeof PressedThumb.Type;
|
|
51
|
+
export type PressedPointer = typeof PressedPointer.Type;
|
|
52
|
+
export type MovedDragPointer = typeof MovedDragPointer.Type;
|
|
53
|
+
export type ReleasedDragPointer = typeof ReleasedDragPointer.Type;
|
|
54
|
+
export type CancelledDrag = typeof CancelledDrag.Type;
|
|
55
|
+
export type PressedKeyboardNavigation = typeof PressedKeyboardNavigation.Type;
|
|
56
|
+
/** Emitted when the slider value changes. The parent can handle this to
|
|
57
|
+
* update its own state or dispatch its own Commands, for example to run
|
|
58
|
+
* validation or trigger a downstream Command. */
|
|
59
|
+
export declare const ChangedValue: import("foldkit/schema").CallableTaggedStruct<"ChangedValue", {
|
|
60
|
+
value: S.Number;
|
|
61
|
+
}>;
|
|
62
|
+
/** Union of all out-messages the slider component can emit to its parent. */
|
|
63
|
+
export declare const OutMessage: S.Union<readonly [import("foldkit/schema").CallableTaggedStruct<"ChangedValue", {
|
|
64
|
+
value: S.Number;
|
|
65
|
+
}>]>;
|
|
66
|
+
export type OutMessage = typeof OutMessage.Type;
|
|
67
|
+
/** Configuration for creating a slider model with `init`. */
|
|
68
|
+
export type InitConfig = Readonly<{
|
|
69
|
+
id: string;
|
|
70
|
+
min: number;
|
|
71
|
+
max: number;
|
|
72
|
+
step: number;
|
|
73
|
+
initialValue: number;
|
|
74
|
+
}>;
|
|
75
|
+
/** Creates an initial slider model from a config. The initial value is
|
|
76
|
+
* snapped to the step and clamped into range. */
|
|
77
|
+
export declare const init: (config: InitConfig) => Model;
|
|
78
|
+
/** Computes the fraction (0–1) of a value between min and max. Returns 0 when
|
|
79
|
+
* the range has zero width. */
|
|
80
|
+
export declare const fractionOfValue: (model: Model) => number;
|
|
81
|
+
type UpdateReturn = readonly [
|
|
82
|
+
Model,
|
|
83
|
+
ReadonlyArray<Command<Message>>,
|
|
84
|
+
Option.Option<OutMessage>
|
|
85
|
+
];
|
|
86
|
+
/** Processes a slider message and returns the next model, commands, and an
|
|
87
|
+
* optional out-message for the parent. */
|
|
88
|
+
export declare const update: (model: Model, message: Message) => UpdateReturn;
|
|
89
|
+
/** Reflects an externally-driven range onto the slider. Snaps and clamps the
|
|
90
|
+
* current value into the new range. Use this when min/max derive from
|
|
91
|
+
* external state (e.g. a bounded buffer whose first/last index shifts over
|
|
92
|
+
* time). Unlike `reflectValue`, this runs even while the user is Dragging: a
|
|
93
|
+
* structural range change cannot leave the value out of bounds. */
|
|
94
|
+
export declare const reflectRange: Reflect<Model, Readonly<{
|
|
95
|
+
min: number;
|
|
96
|
+
max: number;
|
|
97
|
+
}>>;
|
|
98
|
+
/** Reflects an externally-driven value onto the slider, snapped and clamped
|
|
99
|
+
* into range; a no-op while the user is dragging, since drag state owns the
|
|
100
|
+
* value. Does not emit `ChangedValue`; use when the value is being driven by
|
|
101
|
+
* external state rather than user input. */
|
|
102
|
+
export declare const reflectValue: Reflect<Model, number>;
|
|
103
|
+
/** Builds slider drag subscriptions, looking up the track
|
|
104
|
+
* element through the supplied root resolver. Use this when the slider is
|
|
105
|
+
* rendered inside a Shadow DOM. The root is read lazily so consumers can
|
|
106
|
+
* resolve it at subscription time. */
|
|
107
|
+
export declare const subscriptionsForRoot: (getTrackRoot: () => Document | ShadowRoot) => {
|
|
108
|
+
readonly dragPointer: Subscription.EntryWithoutKeepAlive<{
|
|
109
|
+
readonly id: string;
|
|
110
|
+
readonly value: number;
|
|
111
|
+
readonly min: number;
|
|
112
|
+
readonly max: number;
|
|
113
|
+
readonly step: number;
|
|
114
|
+
readonly dragState: {
|
|
115
|
+
readonly _tag: "Idle";
|
|
116
|
+
} | {
|
|
117
|
+
readonly _tag: "Dragging";
|
|
118
|
+
readonly originValue: number;
|
|
119
|
+
};
|
|
120
|
+
}, {
|
|
121
|
+
readonly _tag: "CancelledDrag";
|
|
122
|
+
} | {
|
|
123
|
+
readonly _tag: "PressedThumb";
|
|
124
|
+
} | {
|
|
125
|
+
readonly _tag: "PressedPointer";
|
|
126
|
+
readonly value: number;
|
|
127
|
+
} | {
|
|
128
|
+
readonly _tag: "MovedDragPointer";
|
|
129
|
+
readonly value: number;
|
|
130
|
+
} | {
|
|
131
|
+
readonly _tag: "ReleasedDragPointer";
|
|
132
|
+
} | {
|
|
133
|
+
readonly _tag: "PressedKeyboardNavigation";
|
|
134
|
+
readonly direction: "Max" | "Min" | "StepDecrement" | "StepIncrement" | "PageDecrement" | "PageIncrement";
|
|
135
|
+
}, {
|
|
136
|
+
readonly dragActivity: "Idle" | "Active";
|
|
137
|
+
readonly id: string;
|
|
138
|
+
readonly min: number;
|
|
139
|
+
readonly max: number;
|
|
140
|
+
}, never> & {
|
|
141
|
+
readonly __subscription: never;
|
|
142
|
+
};
|
|
143
|
+
readonly dragEscape: Subscription.EntryWithoutKeepAlive<{
|
|
144
|
+
readonly id: string;
|
|
145
|
+
readonly value: number;
|
|
146
|
+
readonly min: number;
|
|
147
|
+
readonly max: number;
|
|
148
|
+
readonly step: number;
|
|
149
|
+
readonly dragState: {
|
|
150
|
+
readonly _tag: "Idle";
|
|
151
|
+
} | {
|
|
152
|
+
readonly _tag: "Dragging";
|
|
153
|
+
readonly originValue: number;
|
|
154
|
+
};
|
|
155
|
+
}, {
|
|
156
|
+
readonly _tag: "CancelledDrag";
|
|
157
|
+
} | {
|
|
158
|
+
readonly _tag: "PressedThumb";
|
|
159
|
+
} | {
|
|
160
|
+
readonly _tag: "PressedPointer";
|
|
161
|
+
readonly value: number;
|
|
162
|
+
} | {
|
|
163
|
+
readonly _tag: "MovedDragPointer";
|
|
164
|
+
readonly value: number;
|
|
165
|
+
} | {
|
|
166
|
+
readonly _tag: "ReleasedDragPointer";
|
|
167
|
+
} | {
|
|
168
|
+
readonly _tag: "PressedKeyboardNavigation";
|
|
169
|
+
readonly direction: "Max" | "Min" | "StepDecrement" | "StepIncrement" | "PageDecrement" | "PageIncrement";
|
|
170
|
+
}, {
|
|
171
|
+
readonly dragActivity: "Idle" | "Active";
|
|
172
|
+
}, never> & {
|
|
173
|
+
readonly __subscription: never;
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
/** Default drag subscriptions, with the track looked up via `document`. */
|
|
177
|
+
export declare const subscriptions: {
|
|
178
|
+
readonly dragPointer: Subscription.EntryWithoutKeepAlive<{
|
|
179
|
+
readonly id: string;
|
|
180
|
+
readonly value: number;
|
|
181
|
+
readonly min: number;
|
|
182
|
+
readonly max: number;
|
|
183
|
+
readonly step: number;
|
|
184
|
+
readonly dragState: {
|
|
185
|
+
readonly _tag: "Idle";
|
|
186
|
+
} | {
|
|
187
|
+
readonly _tag: "Dragging";
|
|
188
|
+
readonly originValue: number;
|
|
189
|
+
};
|
|
190
|
+
}, {
|
|
191
|
+
readonly _tag: "CancelledDrag";
|
|
192
|
+
} | {
|
|
193
|
+
readonly _tag: "PressedThumb";
|
|
194
|
+
} | {
|
|
195
|
+
readonly _tag: "PressedPointer";
|
|
196
|
+
readonly value: number;
|
|
197
|
+
} | {
|
|
198
|
+
readonly _tag: "MovedDragPointer";
|
|
199
|
+
readonly value: number;
|
|
200
|
+
} | {
|
|
201
|
+
readonly _tag: "ReleasedDragPointer";
|
|
202
|
+
} | {
|
|
203
|
+
readonly _tag: "PressedKeyboardNavigation";
|
|
204
|
+
readonly direction: "Max" | "Min" | "StepDecrement" | "StepIncrement" | "PageDecrement" | "PageIncrement";
|
|
205
|
+
}, {
|
|
206
|
+
readonly dragActivity: "Idle" | "Active";
|
|
207
|
+
readonly id: string;
|
|
208
|
+
readonly min: number;
|
|
209
|
+
readonly max: number;
|
|
210
|
+
}, never> & {
|
|
211
|
+
readonly __subscription: never;
|
|
212
|
+
};
|
|
213
|
+
readonly dragEscape: Subscription.EntryWithoutKeepAlive<{
|
|
214
|
+
readonly id: string;
|
|
215
|
+
readonly value: number;
|
|
216
|
+
readonly min: number;
|
|
217
|
+
readonly max: number;
|
|
218
|
+
readonly step: number;
|
|
219
|
+
readonly dragState: {
|
|
220
|
+
readonly _tag: "Idle";
|
|
221
|
+
} | {
|
|
222
|
+
readonly _tag: "Dragging";
|
|
223
|
+
readonly originValue: number;
|
|
224
|
+
};
|
|
225
|
+
}, {
|
|
226
|
+
readonly _tag: "CancelledDrag";
|
|
227
|
+
} | {
|
|
228
|
+
readonly _tag: "PressedThumb";
|
|
229
|
+
} | {
|
|
230
|
+
readonly _tag: "PressedPointer";
|
|
231
|
+
readonly value: number;
|
|
232
|
+
} | {
|
|
233
|
+
readonly _tag: "MovedDragPointer";
|
|
234
|
+
readonly value: number;
|
|
235
|
+
} | {
|
|
236
|
+
readonly _tag: "ReleasedDragPointer";
|
|
237
|
+
} | {
|
|
238
|
+
readonly _tag: "PressedKeyboardNavigation";
|
|
239
|
+
readonly direction: "Max" | "Min" | "StepDecrement" | "StepIncrement" | "PageDecrement" | "PageIncrement";
|
|
240
|
+
}, {
|
|
241
|
+
readonly dragActivity: "Idle" | "Active";
|
|
242
|
+
}, never> & {
|
|
243
|
+
readonly __subscription: never;
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
/** Attribute groups the slider component provides to the consumer's `toView`
|
|
247
|
+
* callback. Each bundle carries the boundary's captured dispatch, so the
|
|
248
|
+
* consumer can spread it directly into element attributes without manual
|
|
249
|
+
* Message wrapping. */
|
|
250
|
+
export type SliderAttributes = Readonly<{
|
|
251
|
+
root: ReadonlyArray<ChildAttribute>;
|
|
252
|
+
track: ReadonlyArray<ChildAttribute>;
|
|
253
|
+
filledTrack: ReadonlyArray<ChildAttribute>;
|
|
254
|
+
thumb: ReadonlyArray<ChildAttribute>;
|
|
255
|
+
label: ReadonlyArray<ChildAttribute>;
|
|
256
|
+
hiddenInput: ReadonlyArray<ChildAttribute>;
|
|
257
|
+
}>;
|
|
258
|
+
/** Per-render view inputs passed to `view` via `h.submodel`'s `viewInputs` field. */
|
|
259
|
+
export type ViewInputs = Readonly<{
|
|
260
|
+
toView: (attributes: SliderAttributes) => Html;
|
|
261
|
+
ariaLabel?: string;
|
|
262
|
+
ariaLabelledBy?: string;
|
|
263
|
+
formatValue?: (value: number) => string;
|
|
264
|
+
isDisabled?: boolean;
|
|
265
|
+
name?: string;
|
|
266
|
+
/** Resolves the root that holds the slider track when looking it up by its
|
|
267
|
+
* `data-slider-track-id` attribute. Defaults to `document`. Provide a
|
|
268
|
+
* ShadowRoot when rendering the slider inside a shadow tree so pointer
|
|
269
|
+
* events on the track can map clientX into a value. */
|
|
270
|
+
getTrackRoot?: () => Document | ShadowRoot;
|
|
271
|
+
}>;
|
|
272
|
+
/** Renders an accessible slider by building ARIA attribute groups and
|
|
273
|
+
* delegating layout to the consumer's `toView` callback. Follows the
|
|
274
|
+
* WAI-ARIA slider pattern: role="slider" on the thumb, aria-valuemin /
|
|
275
|
+
* aria-valuemax / aria-valuenow, keyboard navigation by step / page / home /
|
|
276
|
+
* end. Pointer drag is handled by the component's drag subscriptions. */
|
|
277
|
+
export declare const view: import("foldkit/submodel").View<{
|
|
278
|
+
readonly id: string;
|
|
279
|
+
readonly value: number;
|
|
280
|
+
readonly min: number;
|
|
281
|
+
readonly max: number;
|
|
282
|
+
readonly step: number;
|
|
283
|
+
readonly dragState: {
|
|
284
|
+
readonly _tag: "Idle";
|
|
285
|
+
} | {
|
|
286
|
+
readonly _tag: "Dragging";
|
|
287
|
+
readonly originValue: number;
|
|
288
|
+
};
|
|
289
|
+
}, {
|
|
290
|
+
readonly _tag: "CancelledDrag";
|
|
291
|
+
} | {
|
|
292
|
+
readonly _tag: "PressedThumb";
|
|
293
|
+
} | {
|
|
294
|
+
readonly _tag: "PressedPointer";
|
|
295
|
+
readonly value: number;
|
|
296
|
+
} | {
|
|
297
|
+
readonly _tag: "MovedDragPointer";
|
|
298
|
+
readonly value: number;
|
|
299
|
+
} | {
|
|
300
|
+
readonly _tag: "ReleasedDragPointer";
|
|
301
|
+
} | {
|
|
302
|
+
readonly _tag: "PressedKeyboardNavigation";
|
|
303
|
+
readonly direction: "Max" | "Min" | "StepDecrement" | "StepIncrement" | "PageDecrement" | "PageIncrement";
|
|
304
|
+
}, Readonly<{
|
|
305
|
+
toView: (attributes: SliderAttributes) => Html;
|
|
306
|
+
ariaLabel?: string;
|
|
307
|
+
ariaLabelledBy?: string;
|
|
308
|
+
formatValue?: (value: number) => string;
|
|
309
|
+
isDisabled?: boolean;
|
|
310
|
+
name?: string;
|
|
311
|
+
/** Resolves the root that holds the slider track when looking it up by its
|
|
312
|
+
* `data-slider-track-id` attribute. Defaults to `document`. Provide a
|
|
313
|
+
* ShadowRoot when rendering the slider inside a shadow tree so pointer
|
|
314
|
+
* events on the track can map clientX into a value. */
|
|
315
|
+
getTrackRoot?: () => Document | ShadowRoot;
|
|
316
|
+
}>>;
|
|
317
|
+
export {};
|
|
318
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/slider/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { Effect, Equal, Function, Match as M, Option, Schema as S, Stream, String as String_, pipe, } from 'effect';
|
|
2
|
+
import { childAttributes, html, } from 'foldkit/html';
|
|
3
|
+
import { m } from 'foldkit/message';
|
|
4
|
+
import { ts } from 'foldkit/schema';
|
|
5
|
+
import { evo } from 'foldkit/struct';
|
|
6
|
+
import { defineView } from 'foldkit/submodel';
|
|
7
|
+
import * as Subscription from 'foldkit/subscription';
|
|
8
|
+
// MODEL
|
|
9
|
+
const Idle = ts('Idle');
|
|
10
|
+
const Dragging = ts('Dragging', { originValue: S.Number });
|
|
11
|
+
const DragState = S.Union([Idle, Dragging]);
|
|
12
|
+
/** Schema for the slider component's state. Tracks the current value, the
|
|
13
|
+
* range (min/max/step), and the active drag phase. */
|
|
14
|
+
export const Model = S.Struct({
|
|
15
|
+
id: S.String,
|
|
16
|
+
value: S.Number,
|
|
17
|
+
min: S.Number,
|
|
18
|
+
max: S.Number,
|
|
19
|
+
step: S.Number,
|
|
20
|
+
dragState: DragState,
|
|
21
|
+
});
|
|
22
|
+
// MESSAGE
|
|
23
|
+
/** The user pressed the thumb. Starts a drag without changing the value. */
|
|
24
|
+
export const PressedThumb = m('PressedThumb');
|
|
25
|
+
/** The user pressed the track. Starts a drag and snaps the value to the
|
|
26
|
+
* cursor position. Ignored while already dragging, which absorbs the bubble
|
|
27
|
+
* from a thumb press so the value is not shifted. */
|
|
28
|
+
export const PressedPointer = m('PressedPointer', { value: S.Number });
|
|
29
|
+
/** The pointer moved during a drag, producing a new snapped value from the
|
|
30
|
+
* cursor position within the track. */
|
|
31
|
+
export const MovedDragPointer = m('MovedDragPointer', { value: S.Number });
|
|
32
|
+
/** The pointer was released during a drag. Commits the current value. */
|
|
33
|
+
export const ReleasedDragPointer = m('ReleasedDragPointer');
|
|
34
|
+
/** Escape was pressed during a drag. Restores the value from the drag origin. */
|
|
35
|
+
export const CancelledDrag = m('CancelledDrag');
|
|
36
|
+
/** The user pressed a keyboard navigation key on the focused thumb. */
|
|
37
|
+
export const PressedKeyboardNavigation = m('PressedKeyboardNavigation', {
|
|
38
|
+
direction: S.Literals([
|
|
39
|
+
'StepDecrement',
|
|
40
|
+
'StepIncrement',
|
|
41
|
+
'PageDecrement',
|
|
42
|
+
'PageIncrement',
|
|
43
|
+
'Min',
|
|
44
|
+
'Max',
|
|
45
|
+
]),
|
|
46
|
+
});
|
|
47
|
+
/** Union of all messages the slider component can produce. */
|
|
48
|
+
export const Message = S.Union([
|
|
49
|
+
PressedThumb,
|
|
50
|
+
PressedPointer,
|
|
51
|
+
MovedDragPointer,
|
|
52
|
+
ReleasedDragPointer,
|
|
53
|
+
CancelledDrag,
|
|
54
|
+
PressedKeyboardNavigation,
|
|
55
|
+
]);
|
|
56
|
+
// OUT MESSAGE
|
|
57
|
+
/** Emitted when the slider value changes. The parent can handle this to
|
|
58
|
+
* update its own state or dispatch its own Commands, for example to run
|
|
59
|
+
* validation or trigger a downstream Command. */
|
|
60
|
+
export const ChangedValue = m('ChangedValue', { value: S.Number });
|
|
61
|
+
/** Union of all out-messages the slider component can emit to its parent. */
|
|
62
|
+
export const OutMessage = S.Union([ChangedValue]);
|
|
63
|
+
/** Creates an initial slider model from a config. The initial value is
|
|
64
|
+
* snapped to the step and clamped into range. */
|
|
65
|
+
export const init = (config) => ({
|
|
66
|
+
id: config.id,
|
|
67
|
+
value: snapAndClamp(config.initialValue, config.min, config.max, config.step),
|
|
68
|
+
min: config.min,
|
|
69
|
+
max: config.max,
|
|
70
|
+
step: config.step,
|
|
71
|
+
dragState: Idle(),
|
|
72
|
+
});
|
|
73
|
+
// HELPERS
|
|
74
|
+
const stepDecimals = (step) => {
|
|
75
|
+
const text = step.toString();
|
|
76
|
+
return pipe(text, String_.indexOf('.'), Option.match({
|
|
77
|
+
onNone: () => 0,
|
|
78
|
+
onSome: dotIndex => text.length - dotIndex - 1,
|
|
79
|
+
}));
|
|
80
|
+
};
|
|
81
|
+
const roundToStepPrecision = (value, step) => {
|
|
82
|
+
const decimals = stepDecimals(step);
|
|
83
|
+
return Number(value.toFixed(decimals));
|
|
84
|
+
};
|
|
85
|
+
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
86
|
+
const snapAndClamp = (value, min, max, step) => {
|
|
87
|
+
const snapped = min + Math.round((value - min) / step) * step;
|
|
88
|
+
return roundToStepPrecision(clamp(snapped, min, max), step);
|
|
89
|
+
};
|
|
90
|
+
/** Computes the fraction (0–1) of a value between min and max. Returns 0 when
|
|
91
|
+
* the range has zero width. */
|
|
92
|
+
export const fractionOfValue = (model) => {
|
|
93
|
+
const range = model.max - model.min;
|
|
94
|
+
if (range <= 0) {
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
return clamp((model.value - model.min) / range, 0, 1);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const PAGE_STEP_MULTIPLIER = 10;
|
|
102
|
+
const nextValueForDirection = (model, direction) => M.value(direction).pipe(M.withReturnType(), M.when('StepIncrement', () => snapAndClamp(model.value + model.step, model.min, model.max, model.step)), M.when('StepDecrement', () => snapAndClamp(model.value - model.step, model.min, model.max, model.step)), M.when('PageIncrement', () => snapAndClamp(model.value + model.step * PAGE_STEP_MULTIPLIER, model.min, model.max, model.step)), M.when('PageDecrement', () => snapAndClamp(model.value - model.step * PAGE_STEP_MULTIPLIER, model.min, model.max, model.step)), M.when('Min', () => model.min), M.when('Max', () => model.max), M.exhaustive);
|
|
103
|
+
const withUpdateReturn = M.withReturnType();
|
|
104
|
+
const withValue = (model, nextValue, commands) => {
|
|
105
|
+
if (nextValue === model.value) {
|
|
106
|
+
return [model, commands, Option.none()];
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
return [
|
|
110
|
+
evo(model, { value: () => nextValue }),
|
|
111
|
+
commands,
|
|
112
|
+
Option.some(ChangedValue({ value: nextValue })),
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
/** Processes a slider message and returns the next model, commands, and an
|
|
117
|
+
* optional out-message for the parent. */
|
|
118
|
+
export const update = (model, message) => M.value(message).pipe(withUpdateReturn, M.tagsExhaustive({
|
|
119
|
+
PressedThumb: () => M.value(model.dragState).pipe(withUpdateReturn, M.tag('Dragging', () => [model, [], Option.none()]), M.orElse(() => [
|
|
120
|
+
evo(model, {
|
|
121
|
+
dragState: () => Dragging({ originValue: model.value }),
|
|
122
|
+
}),
|
|
123
|
+
[],
|
|
124
|
+
Option.none(),
|
|
125
|
+
])),
|
|
126
|
+
// NOTE: the pointerdown event on the thumb bubbles to the track, so a
|
|
127
|
+
// thumb press also dispatches PressedPointer. Short-circuit when already
|
|
128
|
+
// Dragging so the bubbled track handler cannot shift the value away
|
|
129
|
+
// from the thumb's current position. Fine-grained sliders (e.g. step
|
|
130
|
+
// 0.05) see a visible jump without this guard, because the cursor sits
|
|
131
|
+
// off-center on a non-zero-width thumb.
|
|
132
|
+
PressedPointer: ({ value }) => M.value(model.dragState).pipe(withUpdateReturn, M.tag('Dragging', () => [model, [], Option.none()]), M.orElse(() => {
|
|
133
|
+
const snapped = snapAndClamp(value, model.min, model.max, model.step);
|
|
134
|
+
const [modelWithValue, commands, maybeOutMessage] = withValue(model, snapped, []);
|
|
135
|
+
return [
|
|
136
|
+
evo(modelWithValue, {
|
|
137
|
+
dragState: () => Dragging({ originValue: model.value }),
|
|
138
|
+
}),
|
|
139
|
+
commands,
|
|
140
|
+
maybeOutMessage,
|
|
141
|
+
];
|
|
142
|
+
})),
|
|
143
|
+
MovedDragPointer: ({ value }) => M.value(model.dragState).pipe(withUpdateReturn, M.tag('Dragging', () => withValue(model, snapAndClamp(value, model.min, model.max, model.step), [])), M.orElse(() => [model, [], Option.none()])),
|
|
144
|
+
ReleasedDragPointer: () => M.value(model.dragState).pipe(withUpdateReturn, M.tag('Dragging', () => [
|
|
145
|
+
evo(model, { dragState: () => Idle() }),
|
|
146
|
+
[],
|
|
147
|
+
Option.none(),
|
|
148
|
+
]), M.orElse(() => [model, [], Option.none()])),
|
|
149
|
+
CancelledDrag: () => M.value(model.dragState).pipe(withUpdateReturn, M.tag('Dragging', ({ originValue }) => {
|
|
150
|
+
const restored = evo(model, {
|
|
151
|
+
dragState: () => Idle(),
|
|
152
|
+
});
|
|
153
|
+
return withValue(restored, originValue, []);
|
|
154
|
+
}), M.orElse(() => [model, [], Option.none()])),
|
|
155
|
+
PressedKeyboardNavigation: ({ direction }) => withValue(model, nextValueForDirection(model, direction), []),
|
|
156
|
+
}));
|
|
157
|
+
/** Reflects an externally-driven range onto the slider. Snaps and clamps the
|
|
158
|
+
* current value into the new range. Use this when min/max derive from
|
|
159
|
+
* external state (e.g. a bounded buffer whose first/last index shifts over
|
|
160
|
+
* time). Unlike `reflectValue`, this runs even while the user is Dragging: a
|
|
161
|
+
* structural range change cannot leave the value out of bounds. */
|
|
162
|
+
export const reflectRange = Function.dual(2, (model, range) => evo(model, {
|
|
163
|
+
min: () => range.min,
|
|
164
|
+
max: () => range.max,
|
|
165
|
+
value: current => snapAndClamp(current, range.min, range.max, model.step),
|
|
166
|
+
}));
|
|
167
|
+
/** Reflects an externally-driven value onto the slider, snapped and clamped
|
|
168
|
+
* into range; a no-op while the user is dragging, since drag state owns the
|
|
169
|
+
* value. Does not emit `ChangedValue`; use when the value is being driven by
|
|
170
|
+
* external state rather than user input. */
|
|
171
|
+
export const reflectValue = Function.dual(2, (model, value) => M.value(model.dragState).pipe(M.withReturnType(), M.tag('Dragging', () => model), M.orElse(() => evo(model, {
|
|
172
|
+
value: () => snapAndClamp(value, model.min, model.max, model.step),
|
|
173
|
+
}))));
|
|
174
|
+
// SUBSCRIPTION
|
|
175
|
+
const DragActivity = S.Literals(['Idle', 'Active']);
|
|
176
|
+
const dragActivityFromModel = (model) => M.value(model.dragState).pipe(M.withReturnType(), M.tag('Dragging', () => 'Active'), M.orElse(() => 'Idle'));
|
|
177
|
+
const trackElement = (id, root) => Option.fromNullishOr(root.querySelector(`[data-slider-track-id="${id}"]`));
|
|
178
|
+
const valueFromClientX = (clientX, trackElement_, min, max) => {
|
|
179
|
+
const rect = trackElement_.getBoundingClientRect();
|
|
180
|
+
if (rect.width === 0) {
|
|
181
|
+
return min;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
const fraction = clamp((clientX - rect.left) / rect.width, 0, 1);
|
|
185
|
+
return min + fraction * (max - min);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
/** Builds slider drag subscriptions, looking up the track
|
|
189
|
+
* element through the supplied root resolver. Use this when the slider is
|
|
190
|
+
* rendered inside a Shadow DOM. The root is read lazily so consumers can
|
|
191
|
+
* resolve it at subscription time. */
|
|
192
|
+
export const subscriptionsForRoot = (getTrackRoot) => Subscription.make()(entry => ({
|
|
193
|
+
dragPointer: entry({
|
|
194
|
+
dragActivity: DragActivity,
|
|
195
|
+
id: S.String,
|
|
196
|
+
min: S.Number,
|
|
197
|
+
max: S.Number,
|
|
198
|
+
}, {
|
|
199
|
+
modelToDependencies: model => ({
|
|
200
|
+
dragActivity: dragActivityFromModel(model),
|
|
201
|
+
id: model.id,
|
|
202
|
+
min: model.min,
|
|
203
|
+
max: model.max,
|
|
204
|
+
}),
|
|
205
|
+
dependenciesToStream: ({ dragActivity, id, min, max }) => {
|
|
206
|
+
const pointerEvents = Stream.merge(Stream.fromEventListener(document, 'pointermove').pipe(Stream.mapEffect(event => Effect.sync(() => Option.map(trackElement(id, getTrackRoot()), element => MovedDragPointer({
|
|
207
|
+
value: valueFromClientX(event.clientX, element, min, max),
|
|
208
|
+
})))), Stream.filter(Option.isSome), Stream.map(option => option.value)), Stream.fromEventListener(document, 'pointerup').pipe(Stream.map(() => ReleasedDragPointer())));
|
|
209
|
+
// NOTE: prevents text selection and locks cursor to grabbing while the
|
|
210
|
+
// user drags the thumb. Matches the approach used in drag-and-drop.
|
|
211
|
+
const documentDragStyles = Stream.callback(() => Effect.acquireRelease(Effect.sync(() => {
|
|
212
|
+
document.documentElement.style.setProperty('user-select', 'none');
|
|
213
|
+
document.documentElement.style.setProperty('-webkit-user-select', 'none');
|
|
214
|
+
const cursorStyle = document.createElement('style');
|
|
215
|
+
cursorStyle.textContent = '* { cursor: grabbing !important; }';
|
|
216
|
+
document.head.appendChild(cursorStyle);
|
|
217
|
+
return cursorStyle;
|
|
218
|
+
}), cursorStyle => Effect.sync(() => {
|
|
219
|
+
document.documentElement.style.removeProperty('user-select');
|
|
220
|
+
document.documentElement.style.removeProperty('-webkit-user-select');
|
|
221
|
+
cursorStyle.remove();
|
|
222
|
+
})).pipe(Effect.flatMap(() => Effect.never)));
|
|
223
|
+
return Stream.when(Stream.merge(pointerEvents, documentDragStyles), Effect.sync(() => dragActivity === 'Active'));
|
|
224
|
+
},
|
|
225
|
+
}),
|
|
226
|
+
dragEscape: entry({ dragActivity: DragActivity }, {
|
|
227
|
+
modelToDependencies: model => ({
|
|
228
|
+
dragActivity: dragActivityFromModel(model),
|
|
229
|
+
}),
|
|
230
|
+
dependenciesToStream: ({ dragActivity }) => Stream.when(Stream.fromEventListener(document, 'keydown').pipe(Stream.filter(({ key }) => key === 'Escape'), Stream.map(() => CancelledDrag())), Effect.sync(() => dragActivity === 'Active')),
|
|
231
|
+
}),
|
|
232
|
+
}));
|
|
233
|
+
/** Default drag subscriptions, with the track looked up via `document`. */
|
|
234
|
+
export const subscriptions = subscriptionsForRoot(() => document);
|
|
235
|
+
// VIEW
|
|
236
|
+
const LEFT_MOUSE_BUTTON = 0;
|
|
237
|
+
const labelId = (id) => `${id}-label`;
|
|
238
|
+
const keyToDirection = (key) => M.value(key).pipe(M.withReturnType(), M.whenOr('ArrowRight', 'ArrowUp', () => 'StepIncrement'), M.whenOr('ArrowLeft', 'ArrowDown', () => 'StepDecrement'), M.when('PageUp', () => 'PageIncrement'), M.when('PageDown', () => 'PageDecrement'), M.when('Home', () => 'Min'), M.when('End', () => 'Max'), M.option);
|
|
239
|
+
const percentString = (fraction) => `${Math.round(fraction * 10000) / 100}%`;
|
|
240
|
+
/** Renders an accessible slider by building ARIA attribute groups and
|
|
241
|
+
* delegating layout to the consumer's `toView` callback. Follows the
|
|
242
|
+
* WAI-ARIA slider pattern: role="slider" on the thumb, aria-valuemin /
|
|
243
|
+
* aria-valuemax / aria-valuenow, keyboard navigation by step / page / home /
|
|
244
|
+
* end. Pointer drag is handled by the component's drag subscriptions. */
|
|
245
|
+
export const view = defineView((model, viewInputs) => {
|
|
246
|
+
const h = html();
|
|
247
|
+
const { formatValue, isDisabled = false, name, getTrackRoot = () => document, } = viewInputs;
|
|
248
|
+
const { id, value, min, max } = model;
|
|
249
|
+
const isDragging = model.dragState._tag === 'Dragging';
|
|
250
|
+
const fraction = fractionOfValue(model);
|
|
251
|
+
const handleKeyDown = (key) => Option.map(keyToDirection(key), direction => PressedKeyboardNavigation({ direction }));
|
|
252
|
+
const pointerAtClientX = (clientX) => Option.map(trackElement(id, getTrackRoot()), element => PressedPointer({
|
|
253
|
+
value: valueFromClientX(clientX, element, min, max),
|
|
254
|
+
}));
|
|
255
|
+
const trackPointerHandler = (_pointerType, button, _screenX, _screenY, _timeStamp, clientX) => pipe(button, Option.liftPredicate(Equal.equals(LEFT_MOUSE_BUTTON)), Option.flatMap(() => pointerAtClientX(clientX)));
|
|
256
|
+
const thumbPointerHandler = (_pointerType, button) => pipe(button, Option.liftPredicate(Equal.equals(LEFT_MOUSE_BUTTON)), Option.map(() => PressedThumb()));
|
|
257
|
+
const stateAttributes = [
|
|
258
|
+
...(isDragging ? [h.DataAttribute('dragging', '')] : []),
|
|
259
|
+
...(isDisabled ? [h.DataAttribute('disabled', '')] : []),
|
|
260
|
+
];
|
|
261
|
+
const rootAttributes = [
|
|
262
|
+
h.DataAttribute('slider-id', id),
|
|
263
|
+
h.DataAttribute('orientation', 'horizontal'),
|
|
264
|
+
...stateAttributes,
|
|
265
|
+
];
|
|
266
|
+
const trackInteractionAttributes = isDisabled
|
|
267
|
+
? []
|
|
268
|
+
: [h.OnPointerDown(trackPointerHandler)];
|
|
269
|
+
const trackAttributes = [
|
|
270
|
+
h.DataAttribute('slider-track-id', id),
|
|
271
|
+
h.Style({ position: 'relative', 'touch-action': 'none' }),
|
|
272
|
+
...stateAttributes,
|
|
273
|
+
...trackInteractionAttributes,
|
|
274
|
+
];
|
|
275
|
+
const filledTrackAttributes = [
|
|
276
|
+
h.Style({
|
|
277
|
+
position: 'absolute',
|
|
278
|
+
left: '0',
|
|
279
|
+
top: '0',
|
|
280
|
+
bottom: '0',
|
|
281
|
+
width: percentString(fraction),
|
|
282
|
+
'pointer-events': 'none',
|
|
283
|
+
}),
|
|
284
|
+
...stateAttributes,
|
|
285
|
+
];
|
|
286
|
+
const resolveThumbLabel = () => {
|
|
287
|
+
if (viewInputs.ariaLabel !== undefined) {
|
|
288
|
+
return [h.AriaLabel(viewInputs.ariaLabel)];
|
|
289
|
+
}
|
|
290
|
+
else if (viewInputs.ariaLabelledBy !== undefined) {
|
|
291
|
+
return [h.AriaLabelledBy(viewInputs.ariaLabelledBy)];
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
return [h.AriaLabelledBy(labelId(id))];
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
const thumbLabelAttributes = resolveThumbLabel();
|
|
298
|
+
const maybeAriaValuetext = formatValue !== undefined ? [h.AriaValuetext(formatValue(value))] : [];
|
|
299
|
+
const thumbInteractionAttributes = isDisabled
|
|
300
|
+
? []
|
|
301
|
+
: [
|
|
302
|
+
h.OnPointerDown(thumbPointerHandler),
|
|
303
|
+
h.OnKeyDownPreventDefault(handleKeyDown),
|
|
304
|
+
];
|
|
305
|
+
const thumbAttributes = [
|
|
306
|
+
h.Id(`${id}-thumb`),
|
|
307
|
+
h.Role('slider'),
|
|
308
|
+
h.Tabindex(0),
|
|
309
|
+
h.AriaOrientation('horizontal'),
|
|
310
|
+
h.AriaValuemin(min),
|
|
311
|
+
h.AriaValuemax(max),
|
|
312
|
+
h.AriaValuenow(value),
|
|
313
|
+
...maybeAriaValuetext,
|
|
314
|
+
...thumbLabelAttributes,
|
|
315
|
+
...(isDisabled ? [h.AriaDisabled(true)] : []),
|
|
316
|
+
h.Style({
|
|
317
|
+
position: 'absolute',
|
|
318
|
+
left: percentString(fraction),
|
|
319
|
+
transform: 'translateX(-50%)',
|
|
320
|
+
'touch-action': 'none',
|
|
321
|
+
}),
|
|
322
|
+
...stateAttributes,
|
|
323
|
+
...thumbInteractionAttributes,
|
|
324
|
+
];
|
|
325
|
+
const labelAttributes = [h.Id(labelId(id))];
|
|
326
|
+
const hiddenInputAttributes = name !== undefined
|
|
327
|
+
? [h.Type('hidden'), h.Name(name), h.Value(value.toString())]
|
|
328
|
+
: [];
|
|
329
|
+
return viewInputs.toView({
|
|
330
|
+
root: childAttributes(rootAttributes),
|
|
331
|
+
track: childAttributes(trackAttributes),
|
|
332
|
+
filledTrack: childAttributes(filledTrackAttributes),
|
|
333
|
+
thumb: childAttributes(thumbAttributes),
|
|
334
|
+
label: childAttributes(labelAttributes),
|
|
335
|
+
hiddenInput: childAttributes(hiddenInputAttributes),
|
|
336
|
+
});
|
|
337
|
+
});
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { init, update, reflectRange, reflectValue, view, subscriptions, subscriptionsForRoot, fractionOfValue, Model, Message, OutMessage, } from './index.js';
|
|
2
|
+
export type { InitConfig, ViewInputs, SliderAttributes, PressedThumb, PressedPointer, MovedDragPointer, ReleasedDragPointer, CancelledDrag, PressedKeyboardNavigation, } from './index.js';
|
|
3
|
+
//# sourceMappingURL=public.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/slider/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,YAAY,EACZ,YAAY,EACZ,IAAI,EACJ,aAAa,EACb,oBAAoB,EACpB,eAAe,EACf,KAAK,EACL,OAAO,EACP,UAAU,GACX,MAAM,YAAY,CAAA;AAEnB,YAAY,EACV,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,yBAAyB,GAC1B,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { init, update, reflectRange, reflectValue, view, subscriptions, subscriptionsForRoot, fractionOfValue, Model, Message, OutMessage, } from './index.js';
|