@sit-onyx/headless 0.3.0-dev-20251027185548 → 0.3.0-dev-20251027213347

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.
@@ -0,0 +1,101 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {
2
+ slider: import('../../index.js', { with: { "resolution-mode": "import" } }).HeadlessComposable<{
3
+ root: import('vue').ComputedRef<{
4
+ ref: import('../../index.js', { with: { "resolution-mode": "import" } }).HeadlessElRef<HTMLElement>;
5
+ style: {
6
+ touchAction: "pan-x" | "pan-y";
7
+ };
8
+ onMousedown: (event: MouseEvent) => void;
9
+ onTouchstart: (event: TouchEvent) => void;
10
+ }>;
11
+ thumbContainer: import('vue').ComputedRef<(data: {
12
+ index: number;
13
+ value: number;
14
+ }) => {
15
+ "data-index": number;
16
+ style: {
17
+ [x: string]: string;
18
+ };
19
+ }>;
20
+ thumbInput: import('vue').ComputedRef<(data: {
21
+ index: number;
22
+ value: number;
23
+ }) => {
24
+ min: number;
25
+ max: number;
26
+ value: number;
27
+ role: string;
28
+ type: string;
29
+ "aria-label": string;
30
+ "aria-valuemin": number;
31
+ "aria-valuemax": number;
32
+ "aria-valuenow": number;
33
+ "aria-orientation": import('./createSlider.js', { with: { "resolution-mode": "import" } }).SliderOrientation;
34
+ "data-index": number;
35
+ tabIndex: number;
36
+ step: string | number;
37
+ disabled: boolean;
38
+ onChange: (event: Event) => void;
39
+ onFocus: (event: FocusEvent) => void;
40
+ onBlur: (event: FocusEvent) => void;
41
+ onKeydown: (event: KeyboardEvent) => void;
42
+ }>;
43
+ mark: import('vue').ComputedRef<(data: {
44
+ value: number;
45
+ label?: string;
46
+ }) => {
47
+ "data-value": number;
48
+ "aria-hidden": true;
49
+ style: {
50
+ [x: string]: string;
51
+ };
52
+ }>;
53
+ markLabel: import('vue').ComputedRef<(data: {
54
+ value: number;
55
+ }) => {
56
+ "data-value": number;
57
+ style: {
58
+ [x: string]: string;
59
+ };
60
+ "aria-hidden": true;
61
+ }>;
62
+ track: import('vue').ComputedRef<{
63
+ role: string;
64
+ "aria-hidden": true;
65
+ style: {
66
+ [x: string]: string;
67
+ };
68
+ }>;
69
+ rail: import('vue').ComputedRef<{
70
+ role: string;
71
+ "aria-hidden": true;
72
+ }>;
73
+ }, {
74
+ isDragging: import('vue').Ref<boolean, boolean>;
75
+ activeThumbIndex: import('vue').Ref<number, number>;
76
+ focusedThumbIndex: import('vue').Ref<number, number>;
77
+ isRange: import('vue').ComputedRef<boolean>;
78
+ trackOffset: import('vue').ComputedRef<number>;
79
+ trackLength: import('vue').ComputedRef<number>;
80
+ marksList: import('vue').ComputedRef<{
81
+ value: number;
82
+ label?: string;
83
+ }[]>;
84
+ }, {
85
+ valueToPercent: import('vue').ComputedRef<(value: number) => number>;
86
+ isMarkActive: import('vue').ComputedRef<(markValue: number) => boolean>;
87
+ clampValue: import('vue').ComputedRef<(value: number) => number>;
88
+ axis: import('vue').ComputedRef<{
89
+ position: "bottom";
90
+ size: "height";
91
+ cross: "width";
92
+ } | {
93
+ position: "left";
94
+ size: "width";
95
+ cross: "height";
96
+ }>;
97
+ roundToStep: import('vue').ComputedRef<(value: number) => number | undefined>;
98
+ normalizeValues: import('vue').ComputedRef<(values: number[]) => number[]>;
99
+ }>;
100
+ }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
101
+ export default _default;
@@ -0,0 +1,101 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {
2
+ slider: import('../../index.js', { with: { "resolution-mode": "import" } }).HeadlessComposable<{
3
+ root: import('vue').ComputedRef<{
4
+ ref: import('../../index.js', { with: { "resolution-mode": "import" } }).HeadlessElRef<HTMLElement>;
5
+ style: {
6
+ touchAction: "pan-x" | "pan-y";
7
+ };
8
+ onMousedown: (event: MouseEvent) => void;
9
+ onTouchstart: (event: TouchEvent) => void;
10
+ }>;
11
+ thumbContainer: import('vue').ComputedRef<(data: {
12
+ index: number;
13
+ value: number;
14
+ }) => {
15
+ "data-index": number;
16
+ style: {
17
+ [x: string]: string;
18
+ };
19
+ }>;
20
+ thumbInput: import('vue').ComputedRef<(data: {
21
+ index: number;
22
+ value: number;
23
+ }) => {
24
+ min: number;
25
+ max: number;
26
+ value: number;
27
+ role: string;
28
+ type: string;
29
+ "aria-label": string;
30
+ "aria-valuemin": number;
31
+ "aria-valuemax": number;
32
+ "aria-valuenow": number;
33
+ "aria-orientation": import('./createSlider.js', { with: { "resolution-mode": "import" } }).SliderOrientation;
34
+ "data-index": number;
35
+ tabIndex: number;
36
+ step: string | number;
37
+ disabled: boolean;
38
+ onChange: (event: Event) => void;
39
+ onFocus: (event: FocusEvent) => void;
40
+ onBlur: (event: FocusEvent) => void;
41
+ onKeydown: (event: KeyboardEvent) => void;
42
+ }>;
43
+ mark: import('vue').ComputedRef<(data: {
44
+ value: number;
45
+ label?: string;
46
+ }) => {
47
+ "data-value": number;
48
+ "aria-hidden": true;
49
+ style: {
50
+ [x: string]: string;
51
+ };
52
+ }>;
53
+ markLabel: import('vue').ComputedRef<(data: {
54
+ value: number;
55
+ }) => {
56
+ "data-value": number;
57
+ style: {
58
+ [x: string]: string;
59
+ };
60
+ "aria-hidden": true;
61
+ }>;
62
+ track: import('vue').ComputedRef<{
63
+ role: string;
64
+ "aria-hidden": true;
65
+ style: {
66
+ [x: string]: string;
67
+ };
68
+ }>;
69
+ rail: import('vue').ComputedRef<{
70
+ role: string;
71
+ "aria-hidden": true;
72
+ }>;
73
+ }, {
74
+ isDragging: import('vue').Ref<boolean, boolean>;
75
+ activeThumbIndex: import('vue').Ref<number, number>;
76
+ focusedThumbIndex: import('vue').Ref<number, number>;
77
+ isRange: import('vue').ComputedRef<boolean>;
78
+ trackOffset: import('vue').ComputedRef<number>;
79
+ trackLength: import('vue').ComputedRef<number>;
80
+ marksList: import('vue').ComputedRef<{
81
+ value: number;
82
+ label?: string;
83
+ }[]>;
84
+ }, {
85
+ valueToPercent: import('vue').ComputedRef<(value: number) => number>;
86
+ isMarkActive: import('vue').ComputedRef<(markValue: number) => boolean>;
87
+ clampValue: import('vue').ComputedRef<(value: number) => number>;
88
+ axis: import('vue').ComputedRef<{
89
+ position: "bottom";
90
+ size: "height";
91
+ cross: "width";
92
+ } | {
93
+ position: "left";
94
+ size: "width";
95
+ cross: "height";
96
+ }>;
97
+ roundToStep: import('vue').ComputedRef<(value: number) => number | undefined>;
98
+ normalizeValues: import('vue').ComputedRef<(values: number[]) => number[]>;
99
+ }>;
100
+ }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
101
+ export default _default;
@@ -0,0 +1,101 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {
2
+ slider: import('../../index.js', { with: { "resolution-mode": "import" } }).HeadlessComposable<{
3
+ root: import('vue').ComputedRef<{
4
+ ref: import('../../index.js', { with: { "resolution-mode": "import" } }).HeadlessElRef<HTMLElement>;
5
+ style: {
6
+ touchAction: "pan-x" | "pan-y";
7
+ };
8
+ onMousedown: (event: MouseEvent) => void;
9
+ onTouchstart: (event: TouchEvent) => void;
10
+ }>;
11
+ thumbContainer: import('vue').ComputedRef<(data: {
12
+ index: number;
13
+ value: number;
14
+ }) => {
15
+ "data-index": number;
16
+ style: {
17
+ [x: string]: string;
18
+ };
19
+ }>;
20
+ thumbInput: import('vue').ComputedRef<(data: {
21
+ index: number;
22
+ value: number;
23
+ }) => {
24
+ min: number;
25
+ max: number;
26
+ value: number;
27
+ role: string;
28
+ type: string;
29
+ "aria-label": string;
30
+ "aria-valuemin": number;
31
+ "aria-valuemax": number;
32
+ "aria-valuenow": number;
33
+ "aria-orientation": import('./createSlider.js', { with: { "resolution-mode": "import" } }).SliderOrientation;
34
+ "data-index": number;
35
+ tabIndex: number;
36
+ step: string | number;
37
+ disabled: boolean;
38
+ onChange: (event: Event) => void;
39
+ onFocus: (event: FocusEvent) => void;
40
+ onBlur: (event: FocusEvent) => void;
41
+ onKeydown: (event: KeyboardEvent) => void;
42
+ }>;
43
+ mark: import('vue').ComputedRef<(data: {
44
+ value: number;
45
+ label?: string;
46
+ }) => {
47
+ "data-value": number;
48
+ "aria-hidden": true;
49
+ style: {
50
+ [x: string]: string;
51
+ };
52
+ }>;
53
+ markLabel: import('vue').ComputedRef<(data: {
54
+ value: number;
55
+ }) => {
56
+ "data-value": number;
57
+ style: {
58
+ [x: string]: string;
59
+ };
60
+ "aria-hidden": true;
61
+ }>;
62
+ track: import('vue').ComputedRef<{
63
+ role: string;
64
+ "aria-hidden": true;
65
+ style: {
66
+ [x: string]: string;
67
+ };
68
+ }>;
69
+ rail: import('vue').ComputedRef<{
70
+ role: string;
71
+ "aria-hidden": true;
72
+ }>;
73
+ }, {
74
+ isDragging: import('vue').Ref<boolean, boolean>;
75
+ activeThumbIndex: import('vue').Ref<number, number>;
76
+ focusedThumbIndex: import('vue').Ref<number, number>;
77
+ isRange: import('vue').ComputedRef<boolean>;
78
+ trackOffset: import('vue').ComputedRef<number>;
79
+ trackLength: import('vue').ComputedRef<number>;
80
+ marksList: import('vue').ComputedRef<{
81
+ value: number;
82
+ label?: string;
83
+ }[]>;
84
+ }, {
85
+ valueToPercent: import('vue').ComputedRef<(value: number) => number>;
86
+ isMarkActive: import('vue').ComputedRef<(markValue: number) => boolean>;
87
+ clampValue: import('vue').ComputedRef<(value: number) => number>;
88
+ axis: import('vue').ComputedRef<{
89
+ position: "bottom";
90
+ size: "height";
91
+ cross: "width";
92
+ } | {
93
+ position: "left";
94
+ size: "width";
95
+ cross: "height";
96
+ }>;
97
+ roundToStep: import('vue').ComputedRef<(value: number) => number | undefined>;
98
+ normalizeValues: import('vue').ComputedRef<(values: number[]) => number[]>;
99
+ }>;
100
+ }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
101
+ export default _default;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,262 @@
1
+ import { MaybeRef, Ref } from 'vue';
2
+ import { Nullable } from '../../utils/types.js';
3
+ export type SliderMark = {
4
+ value: number;
5
+ label?: string;
6
+ } | number;
7
+ export type SliderOrientation = "horizontal" | "vertical";
8
+ export type CreateSliderOptions<TValue extends number | number[] = number> = {
9
+ /**
10
+ * Current value(s) of the slider.
11
+ * Each value should be between min and max.
12
+ *
13
+ * @default [min]
14
+ */
15
+ value: Ref<TValue>;
16
+ /**
17
+ * Minimum value of the slider.
18
+ *
19
+ * @default 0
20
+ */
21
+ min?: MaybeRef<number>;
22
+ /**
23
+ * Maximum value of the slider.
24
+ *
25
+ * @default 100
26
+ */
27
+ max?: MaybeRef<number>;
28
+ /**
29
+ * Step size for the slider.
30
+ *
31
+ * @default 1
32
+ */
33
+ step?: MaybeRef<number>;
34
+ /**
35
+ * Whether to render the slider in discrete mode.
36
+ *
37
+ * @default false
38
+ */
39
+ discrete?: MaybeRef<boolean>;
40
+ /**
41
+ * Step size when holding shift key or using Page Up/Page Down keys.
42
+ *
43
+ * Defaults to 10% of the total range (max - min) multiplied by the step size.
44
+ * This provides intuitive behavior that automatically scales with different slider ranges.
45
+ */
46
+ shiftStep?: MaybeRef<number>;
47
+ /**
48
+ * Whether the slider is disabled.
49
+ *
50
+ * @default false
51
+ */
52
+ disabled?: MaybeRef<boolean>;
53
+ /**
54
+ * Array of marks to display on the slider.
55
+ *
56
+ * @default false
57
+ */
58
+ marks?: MaybeRef<Nullable<SliderMark[] | boolean>>;
59
+ /**
60
+ * Aria label for the slider.
61
+ *
62
+ * @default undefined
63
+ */
64
+ label: MaybeRef<string>;
65
+ /**
66
+ * Orientation of the slider.
67
+ *
68
+ * @default "horizontal"
69
+ */
70
+ orientation?: MaybeRef<SliderOrientation>;
71
+ /**
72
+ * Callback when the value changes during interaction.
73
+ * Note: This is called during interaction (dragging, key press, etc.).
74
+ */
75
+ onChange?: (value: TValue) => void;
76
+ /**
77
+ * Callback when the value is committed (drag end, key press, etc.).
78
+ * Note: This is called when the user stops interacting with the slider.
79
+ */
80
+ onCommit?: (value: TValue) => void;
81
+ };
82
+ /**
83
+ * Composable for creating an accessibility-compliant slider.
84
+ * For supported keyboard shortcuts, see: https://www.w3.org/WAI/ARIA/apg/patterns/slider/
85
+ *
86
+ * * @experimental
87
+ * @deprecated This component is still under active development and its API might change in patch releases.
88
+ */
89
+ export declare const _unstableCreateSlider: <TValue extends number | number[] = number>(options: CreateSliderOptions<TValue>) => import('../../utils/builder.js').HeadlessComposable<{
90
+ /**
91
+ * Root slider container element
92
+ */
93
+ root: import('vue').ComputedRef<{
94
+ ref: import('../../utils/builder.js').HeadlessElRef<HTMLElement>;
95
+ style: {
96
+ touchAction: "pan-x" | "pan-y";
97
+ };
98
+ onMousedown: (event: MouseEvent) => void;
99
+ onTouchstart: (event: TouchEvent) => void;
100
+ }>;
101
+ /**
102
+ * Individual thumb elements for each value (span)
103
+ */
104
+ thumbContainer: import('vue').ComputedRef<(data: {
105
+ index: number;
106
+ value: number;
107
+ }) => {
108
+ "data-index": number;
109
+ style: {
110
+ [x: string]: string;
111
+ };
112
+ }>;
113
+ /**
114
+ * Visually hidden input inside each thumb for accessibility
115
+ */
116
+ thumbInput: import('vue').ComputedRef<(data: {
117
+ index: number;
118
+ value: number;
119
+ }) => {
120
+ min: number;
121
+ max: number;
122
+ value: number;
123
+ role: string;
124
+ type: string;
125
+ "aria-label": string;
126
+ "aria-valuemin": number;
127
+ "aria-valuemax": number;
128
+ "aria-valuenow": number;
129
+ "aria-orientation": SliderOrientation;
130
+ "data-index": number;
131
+ tabIndex: number;
132
+ step: string | number;
133
+ disabled: boolean;
134
+ onChange: (event: Event) => void;
135
+ onFocus: (event: FocusEvent) => void;
136
+ onBlur: (event: FocusEvent) => void;
137
+ onKeydown: (event: KeyboardEvent) => void;
138
+ }>;
139
+ /**
140
+ * Mark elements
141
+ */
142
+ mark: import('vue').ComputedRef<(data: {
143
+ value: number;
144
+ label?: string;
145
+ }) => {
146
+ "data-value": number;
147
+ "aria-hidden": true;
148
+ style: {
149
+ [x: string]: string;
150
+ };
151
+ }>;
152
+ /**
153
+ * Label for each mark
154
+ */
155
+ markLabel: import('vue').ComputedRef<(data: {
156
+ value: number;
157
+ }) => {
158
+ "data-value": number;
159
+ style: {
160
+ [x: string]: string;
161
+ };
162
+ "aria-hidden": true;
163
+ }>;
164
+ /**
165
+ * Track element representing the selected range
166
+ */
167
+ track: import('vue').ComputedRef<{
168
+ role: string;
169
+ "aria-hidden": true;
170
+ style: {
171
+ [x: string]: string;
172
+ };
173
+ }>;
174
+ /**
175
+ * Rail element representing the full slider range
176
+ */
177
+ rail: import('vue').ComputedRef<{
178
+ role: string;
179
+ "aria-hidden": true;
180
+ }>;
181
+ }, {
182
+ /**
183
+ * True if the slider is currently being dragged.
184
+ */
185
+ isDragging: Ref<boolean, boolean>;
186
+ /**
187
+ * Index of the currently active thumb.
188
+ * Thumb could be active even if not dragging (e.g. click on the rail to move the thumb there).
189
+ * `-1` if no thumb is active.
190
+ */
191
+ activeThumbIndex: Ref<number, number>;
192
+ /**
193
+ * Index of the thumb that is currently focused.
194
+ * `-1` if no thumb is focused.
195
+ */
196
+ focusedThumbIndex: Ref<number, number>;
197
+ /**
198
+ * `true` if the slider is a range slider (with two or more thumbs).
199
+ */
200
+ isRange: import('vue').ComputedRef<boolean>;
201
+ /**
202
+ * Offset of the track as a percentage (0-100).
203
+ */
204
+ trackOffset: import('vue').ComputedRef<number>;
205
+ /**
206
+ * Length of the track as a percentage (0-100).
207
+ */
208
+ trackLength: import('vue').ComputedRef<number>;
209
+ /**
210
+ * List of marks to display on the slider.
211
+ * - If marks option is `true`, marks are generated based on the step value.
212
+ * - If marks option is an array of numbers or objects, it is used as provided (filtered to be within range).
213
+ * - If marks option is `false`, no marks are shown.
214
+ */
215
+ marksList: import('vue').ComputedRef<{
216
+ value: number;
217
+ label?: string;
218
+ }[]>;
219
+ }, {
220
+ /**
221
+ * Converts a value from the slider's range to a percentage (0-100).
222
+ * @param value - value to convert
223
+ * @returns percentage representation of the value
224
+ */
225
+ valueToPercent: import('vue').ComputedRef<(value: number) => number>;
226
+ /**
227
+ * Checks if a given mark value is active (i.e., within the selected range).
228
+ * Use case: when rendering marks, to determine if a mark is covered by the selected range.
229
+ *
230
+ * @param markValue - value of the mark to check
231
+ * @returns `true` if the mark is active, `false` otherwise
232
+ */
233
+ isMarkActive: import('vue').ComputedRef<(markValue: number) => boolean>;
234
+ /**
235
+ * Clamps a value to the slider's range.
236
+ * @param value - value to clamp
237
+ * @returns clamped value
238
+ */
239
+ clampValue: import('vue').ComputedRef<(value: number) => number>;
240
+ /**
241
+ * Main axis properties based on orientation.
242
+ */
243
+ axis: import('vue').ComputedRef<{
244
+ position: "bottom";
245
+ size: "height";
246
+ cross: "width";
247
+ } | {
248
+ position: "left";
249
+ size: "width";
250
+ cross: "height";
251
+ }>;
252
+ /**
253
+ * Rounds a value to the nearest valid step.
254
+ * @param value - value to round
255
+ * @returns rounded value
256
+ */
257
+ roundToStep: import('vue').ComputedRef<(value: number) => number | undefined>;
258
+ /**
259
+ * Normalizes an array of values to ensure they are within min/max bounds,
260
+ */
261
+ normalizeValues: import('vue').ComputedRef<(values: number[]) => number[]>;
262
+ }>;
@@ -0,0 +1,49 @@
1
+ import { Locator, Page } from '@playwright/test';
2
+ export type SliderTestingOptions = {
3
+ /**
4
+ * Playwright page.
5
+ */
6
+ page: Page;
7
+ /**
8
+ * Locator for the slider element(s).
9
+ */
10
+ slider: Locator;
11
+ /**
12
+ * Optional: Locator for the slider container/root.
13
+ */
14
+ container?: Locator;
15
+ /**
16
+ * Expected initial value(s) for the slider.
17
+ */
18
+ initialValues?: number[];
19
+ /**
20
+ * Expected min value.
21
+ */
22
+ min?: number;
23
+ /**
24
+ * Expected max value.
25
+ */
26
+ max?: number;
27
+ /**
28
+ * Expected step value.
29
+ */
30
+ step?: number;
31
+ /**
32
+ * Expected shift step value.
33
+ */
34
+ shiftStep?: number;
35
+ };
36
+ /**
37
+ * Comprehensive testing for single-thumb slider implementation.
38
+ * Tests basic accessibility, keyboard navigation, and interaction patterns.
39
+ */
40
+ export declare const singleThumbSliderTesting: ({ page, slider, container, initialValues, min, max, step, shiftStep, }: SliderTestingOptions) => Promise<void>;
41
+ /**
42
+ * Comprehensive testing for multi-thumb (range) slider implementation.
43
+ * Tests range-specific behaviors, thumb independence, and collision handling.
44
+ */
45
+ export declare const multiThumbSliderTesting: ({ page, slider, container, initialValues, min, max, step, }: SliderTestingOptions) => Promise<void>;
46
+ /**
47
+ * Test slider with marks (discrete mode).
48
+ */
49
+ export declare const discreteSliderTesting: ({ slider }: SliderTestingOptions) => Promise<void>;
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from './composables/helpers/useOutsideClick.js';
5
5
  export * from './composables/listbox/createListbox.js';
6
6
  export * from './composables/menuButton/createMenuButton.js';
7
7
  export * from './composables/navigationMenu/createMenu.js';
8
+ export * from './composables/slider/createSlider.js';
8
9
  export * from './composables/tabs/createTabs.js';
9
10
  export * from './composables/tooltip/createToggletip.js';
10
11
  export * from './composables/tooltip/createTooltip.js';
package/dist/index.js CHANGED
@@ -843,7 +843,34 @@ const MathUtils = {
843
843
  /**
844
844
  * Ensures that a given `number` is or is between a given `min` and `max`.
845
845
  */
846
- clamp: (number, min, max) => Math.max(Math.min(number, max), min)
846
+ clamp: (number, min, max) => Math.max(Math.min(number, max), min),
847
+ /**
848
+ * Returns the count of decimal places in a number.
849
+ * @param number - The number to check.
850
+ * @returns The count of decimal places.
851
+ *
852
+ * decimals(1.23); // 2
853
+ * decimals(10); // 0
854
+ */
855
+ decimalsCount: (number) => String(number).split(".")[1]?.length ?? 0,
856
+ /**
857
+ * Converts a value within a range to a percentage (0-100).
858
+ *
859
+ * @param value - The value to convert.
860
+ * @param min - The minimum allowed value.
861
+ * @param max - The maximum allowed value.
862
+ * @returns The percentage representation of the value.
863
+ */
864
+ valueToPercent: (value, min, max) => (value - min) * 100 / (max - min),
865
+ /**
866
+ * Converts a percentage (0-100) to a value within a range.
867
+ *
868
+ * @param percent - The percentage to convert.
869
+ * @param min - The minimum allowed value.
870
+ * @param max - The maximum allowed value.
871
+ * @returns The value representation of the percentage.
872
+ */
873
+ percentToValue: (percent, min, max) => (max - min) * percent + min
847
874
  };
848
875
  const createNavigationMenu = createBuilder(({ navigationName }) => {
849
876
  const navId = useId();
@@ -882,6 +909,594 @@ const createNavigationMenu = createBuilder(({ navigationName }) => {
882
909
  }
883
910
  };
884
911
  });
912
+ const areArraysEqual = (arrayA, arrayB, comparer = (a, b) => a === b) => arrayA.length === arrayB.length && arrayA.every((value, index) => comparer(value, arrayB[index]));
913
+ const isFocusVisible = (element) => {
914
+ try {
915
+ return element.matches(":focus-visible");
916
+ } catch {
917
+ return false;
918
+ }
919
+ };
920
+ const isTouchEvent = (event) => "touches" in event || "changedTouches" in event || "targetTouches" in event;
921
+ const DRAG_MOVE_THRESHOLD = 2;
922
+ const KEY = {
923
+ Up: "ArrowUp",
924
+ Down: "ArrowDown",
925
+ Left: "ArrowLeft",
926
+ Right: "ArrowRight",
927
+ PageUp: "PageUp",
928
+ PageDown: "PageDown",
929
+ Home: "Home",
930
+ End: "End"
931
+ };
932
+ const NAVIGATION_KEYS = /* @__PURE__ */ new Set([
933
+ KEY.Up,
934
+ KEY.Down,
935
+ KEY.Left,
936
+ KEY.Right,
937
+ KEY.PageUp,
938
+ KEY.PageDown,
939
+ KEY.Home,
940
+ KEY.End
941
+ ]);
942
+ const INCREMENT_KEYS = /* @__PURE__ */ new Set([KEY.Right, KEY.Up, KEY.PageUp]);
943
+ const DECREMENT_KEYS = /* @__PURE__ */ new Set([KEY.Left, KEY.Down, KEY.PageDown]);
944
+ const TRACK_CALCULATION_STRATEGIES = {
945
+ horizontal: (rect, coords) => MathUtils.clamp((coords.x - rect.left) / rect.width, 0, 1),
946
+ vertical: (rect, coords) => MathUtils.clamp((rect.bottom - coords.y) / rect.height, 0, 1)
947
+ };
948
+ const readThumbIndex = (event) => Number(event.currentTarget?.dataset.index ?? -1);
949
+ const roundToStep = (value, step, min) => Number((Math.round((value - min) / step) * step + min).toFixed(MathUtils.decimalsCount(step)));
950
+ const normalizeValues = (values, min, max, step) => {
951
+ if (!values.length) return [min];
952
+ return values.map((value) => {
953
+ const clamped = MathUtils.clamp(value, min, max);
954
+ return roundToStep(clamped, step, min);
955
+ }).sort((a, b) => a - b);
956
+ };
957
+ const findClosestIndex = (values, currentValue) => {
958
+ const result = values.reduce((acc, value, index) => {
959
+ const distance = Math.abs(currentValue - value);
960
+ if (!acc || distance <= acc.closestDistance) {
961
+ return {
962
+ closestIndex: index,
963
+ closestDistance: distance
964
+ };
965
+ }
966
+ return acc;
967
+ }, null);
968
+ return result.closestIndex;
969
+ };
970
+ const adjustValueByIndex = ({
971
+ values,
972
+ newValue,
973
+ index
974
+ }) => values.map((value, i) => i === index ? newValue : value).sort((a, b) => a - b);
975
+ const asc = (a, b) => a - b;
976
+ const valueToArray = (value) => Array.isArray(value) ? value : [value];
977
+ const _unstableCreateSlider = createBuilder(
978
+ (options) => {
979
+ const sliderRef = createElRef();
980
+ const min = computed(() => unref(options.min) ?? 0);
981
+ const max = computed(() => unref(options.max) ?? 100);
982
+ const step = computed(() => unref(options.step) ?? 1);
983
+ const values = computed(() => {
984
+ const rawValues = unref(options.value);
985
+ if (Array.isArray(rawValues)) {
986
+ if (!rawValues?.length) {
987
+ return [min.value];
988
+ }
989
+ return normalizeValues(rawValues, min.value, max.value, step.value);
990
+ } else {
991
+ if (typeof rawValues !== "number") {
992
+ return [min.value];
993
+ }
994
+ return normalizeValues([rawValues], min.value, max.value, step.value);
995
+ }
996
+ });
997
+ const shiftStep = computed(() => {
998
+ const shiftStep2 = unref(options.shiftStep);
999
+ if (typeof shiftStep2 !== "undefined") {
1000
+ return shiftStep2;
1001
+ }
1002
+ const stepMultiple = Math.max(1, Math.round((max.value - min.value) * 0.1 / step.value));
1003
+ return stepMultiple * step.value;
1004
+ });
1005
+ const isDisabled = computed(() => unref(options.disabled) ?? false);
1006
+ const marks = computed(() => unref(options.marks) ?? false);
1007
+ const label = computed(() => unref(options.label));
1008
+ const orientation = computed(() => unref(options.orientation) ?? "horizontal");
1009
+ const isDiscrete = computed(() => unref(options.discrete) ?? false);
1010
+ let touchId = null;
1011
+ let movesSinceStart = 0;
1012
+ let lastChangedValue = null;
1013
+ let previousActiveIndex = null;
1014
+ const isDragging = ref(false);
1015
+ const activeThumbIndex = ref(-1);
1016
+ const focusedThumbIndex = ref(-1);
1017
+ const isRange = computed(() => {
1018
+ const unrefValues = unref(options.value);
1019
+ return Array.isArray(unrefValues) && unrefValues.length > 1;
1020
+ });
1021
+ const marksList = computed(() => {
1022
+ if (marks.value === false) return [];
1023
+ if (marks.value && Array.isArray(marks.value)) {
1024
+ return marks.value.map((mark) => typeof mark === "number" ? { value: mark } : mark).filter((mark) => mark.value >= min.value && mark.value <= max.value).sort((a, b) => asc(a.value, b.value));
1025
+ }
1026
+ if (step.value && step.value > 0) {
1027
+ return [...Array(Math.floor((max.value - min.value) / step.value + 1))].map((_, index) => ({
1028
+ value: min.value + step.value * index
1029
+ }));
1030
+ }
1031
+ return [];
1032
+ });
1033
+ const axis = computed(
1034
+ () => orientation.value === "vertical" ? { position: "bottom", size: "height", cross: "width" } : { position: "left", size: "width", cross: "height" }
1035
+ );
1036
+ const marksValues = computed(() => marksList.value.map((mark) => mark.value));
1037
+ const emitChange = (next) => {
1038
+ if (!areArraysEqual(values.value, next)) {
1039
+ const nextValue = isRange.value ? next : next[0];
1040
+ if (typeof nextValue !== "undefined") {
1041
+ options.onChange?.(nextValue);
1042
+ }
1043
+ }
1044
+ lastChangedValue = next;
1045
+ };
1046
+ const emitCommit = (fallback) => {
1047
+ const valueWithFallback = lastChangedValue ?? fallback;
1048
+ const nextValue = isRange.value ? valueWithFallback : valueWithFallback[0];
1049
+ if (typeof nextValue !== "undefined") {
1050
+ options.onCommit?.(nextValue);
1051
+ }
1052
+ };
1053
+ const ensureFocusOnThumb = (options2) => {
1054
+ const { index, shouldSetActive = false } = options2;
1055
+ const slider = sliderRef.value;
1056
+ if (!slider) return;
1057
+ if (slider.contains(document.activeElement) && Number(document.activeElement?.getAttribute("data-index")) !== index) {
1058
+ slider.querySelector(`[type="range"][data-index="${index}"]`)?.focus();
1059
+ }
1060
+ if (shouldSetActive) {
1061
+ activeThumbIndex.value = index;
1062
+ }
1063
+ };
1064
+ const eventToCoords = (event, touchId2) => {
1065
+ if (touchId2 !== void 0 && isTouchEvent(event)) {
1066
+ for (let i = 0; i < event.changedTouches.length; i += 1) {
1067
+ const touch = event.changedTouches[i];
1068
+ if (touch && touch.identifier === touchId2) {
1069
+ return {
1070
+ x: touch.clientX,
1071
+ y: touch.clientY
1072
+ };
1073
+ }
1074
+ }
1075
+ return false;
1076
+ }
1077
+ const mouseEvent = event;
1078
+ return {
1079
+ x: mouseEvent.clientX,
1080
+ y: mouseEvent.clientY
1081
+ };
1082
+ };
1083
+ const getNextFromCoords = (opts) => {
1084
+ const { coords, isMoving = false } = opts;
1085
+ const slider = sliderRef.value;
1086
+ if (!slider) return null;
1087
+ const rect = slider.getBoundingClientRect();
1088
+ const mainSize = orientation.value === "vertical" ? rect.height : rect.width;
1089
+ if (mainSize <= 0) return null;
1090
+ const percent = TRACK_CALCULATION_STRATEGIES[orientation.value](rect, coords);
1091
+ const raw = MathUtils.percentToValue(percent, min.value, max.value);
1092
+ const snapped = !isDiscrete.value ? roundToStep(raw, step.value, min.value) : marksValues.value[findClosestIndex(marksValues.value, raw)];
1093
+ if (typeof snapped !== "number") return null;
1094
+ const candidate = MathUtils.clamp(snapped, min.value, max.value);
1095
+ if (!isRange.value) {
1096
+ return { newValue: candidate, activeIndex: 0 };
1097
+ }
1098
+ const closestIndex = findClosestIndex(values.value, candidate);
1099
+ const index = isMoving && previousActiveIndex != null ? previousActiveIndex : closestIndex;
1100
+ const adjustedValues = adjustValueByIndex({
1101
+ values: values.value,
1102
+ newValue: candidate,
1103
+ index
1104
+ });
1105
+ const adjustedIndex = findClosestIndex(adjustedValues, candidate);
1106
+ previousActiveIndex = adjustedIndex;
1107
+ return { newValue: adjustedValues, activeIndex: adjustedIndex };
1108
+ };
1109
+ const commitValueFromEvent = (event, input) => {
1110
+ const index = readThumbIndex(event);
1111
+ const current = values.value[index];
1112
+ if (typeof current !== "number") {
1113
+ return;
1114
+ }
1115
+ const useMarks = isDiscrete.value && marksList.value.length > 0;
1116
+ const snapByMarks = (candidate) => {
1117
+ const list = marksList.value;
1118
+ const first = list[0];
1119
+ const last = list.at(-1);
1120
+ if (!first || !last) {
1121
+ return current;
1122
+ }
1123
+ if (candidate <= first.value) {
1124
+ return first.value;
1125
+ }
1126
+ if (candidate >= last.value) {
1127
+ return last.value;
1128
+ }
1129
+ const pos = marksValues.value.indexOf(current);
1130
+ const neighbor = candidate < current ? list[pos - 1] : list[pos + 1];
1131
+ return neighbor?.value ?? current;
1132
+ };
1133
+ const scalar = MathUtils.clamp(useMarks ? snapByMarks(input) : input, min.value, max.value);
1134
+ const nextValues = isRange.value ? adjustValueByIndex({ values: values.value, newValue: scalar, index }) : [scalar];
1135
+ if (isRange.value) {
1136
+ const activeIndex = nextValues.indexOf(scalar);
1137
+ ensureFocusOnThumb({ index: activeIndex, shouldSetActive: true });
1138
+ }
1139
+ focusedThumbIndex.value = index;
1140
+ if (!areArraysEqual(values.value, nextValues)) {
1141
+ emitChange(nextValues);
1142
+ }
1143
+ emitCommit(nextValues);
1144
+ };
1145
+ const handlePointerEnd = (event) => {
1146
+ const coords = eventToCoords(event, touchId);
1147
+ isDragging.value = false;
1148
+ if (!coords) {
1149
+ return;
1150
+ }
1151
+ const next = getNextFromCoords({ coords, isMoving: true });
1152
+ if (!next) {
1153
+ return;
1154
+ }
1155
+ const { newValue } = next;
1156
+ activeThumbIndex.value = -1;
1157
+ emitCommit(valueToArray(newValue));
1158
+ movesSinceStart = 0;
1159
+ touchId = null;
1160
+ stopPointerListening();
1161
+ };
1162
+ const handlePointerMove = (event) => {
1163
+ const coords = eventToCoords(event, touchId);
1164
+ if (!coords) return;
1165
+ movesSinceStart += 1;
1166
+ if (event.type === "mousemove" && event.buttons === 0) {
1167
+ handlePointerEnd(event);
1168
+ return;
1169
+ }
1170
+ const nextState = getNextFromCoords({ coords, isMoving: true });
1171
+ if (!nextState) {
1172
+ handlePointerEnd(event);
1173
+ return;
1174
+ }
1175
+ const { newValue, activeIndex } = nextState;
1176
+ if (!isDragging.value && movesSinceStart > DRAG_MOVE_THRESHOLD) {
1177
+ isDragging.value = true;
1178
+ }
1179
+ ensureFocusOnThumb({ index: activeIndex, shouldSetActive: true });
1180
+ emitChange(valueToArray(newValue));
1181
+ isDragging.value = true;
1182
+ };
1183
+ const handlePointerStart = (event) => {
1184
+ if (isDisabled.value) {
1185
+ return;
1186
+ }
1187
+ const touch = event.changedTouches[0];
1188
+ if (touch !== null && touch !== void 0) {
1189
+ touchId = touch.identifier;
1190
+ }
1191
+ const coords = eventToCoords(event, touchId);
1192
+ if (coords) {
1193
+ const nextState = getNextFromCoords({ coords, isMoving: false });
1194
+ if (nextState) {
1195
+ const { newValue, activeIndex } = nextState;
1196
+ ensureFocusOnThumb({ index: activeIndex, shouldSetActive: true });
1197
+ emitChange(valueToArray(newValue));
1198
+ }
1199
+ }
1200
+ movesSinceStart = 0;
1201
+ document.addEventListener("touchmove", handlePointerMove, {
1202
+ passive: true
1203
+ });
1204
+ document.addEventListener("touchend", handlePointerEnd);
1205
+ };
1206
+ const stopPointerListening = () => {
1207
+ document.removeEventListener("mousemove", handlePointerMove);
1208
+ document.removeEventListener("mouseup", handlePointerEnd);
1209
+ document.removeEventListener("touchmove", handlePointerMove);
1210
+ document.removeEventListener("touchend", handlePointerEnd);
1211
+ };
1212
+ const handleRootMousedown = (event) => {
1213
+ if (isDisabled.value) {
1214
+ return;
1215
+ }
1216
+ if (event.button !== 0) {
1217
+ return;
1218
+ }
1219
+ if (event.defaultPrevented) {
1220
+ return;
1221
+ }
1222
+ event.preventDefault();
1223
+ const coords = eventToCoords(event, touchId);
1224
+ if (coords) {
1225
+ const nextState = getNextFromCoords({ coords });
1226
+ if (nextState) {
1227
+ const { newValue, activeIndex } = nextState;
1228
+ ensureFocusOnThumb({ index: activeIndex, shouldSetActive: true });
1229
+ emitChange(valueToArray(newValue));
1230
+ }
1231
+ }
1232
+ movesSinceStart = 0;
1233
+ document.addEventListener("mousemove", handlePointerMove, {
1234
+ passive: true
1235
+ });
1236
+ document.addEventListener("mouseup", handlePointerEnd);
1237
+ };
1238
+ const handleHiddenInputChange = (event) => {
1239
+ if (isDisabled.value) {
1240
+ return;
1241
+ }
1242
+ const value = event.target.valueAsNumber;
1243
+ commitValueFromEvent(event, value);
1244
+ };
1245
+ const handleHiddenInputFocus = (event) => {
1246
+ const index = readThumbIndex(event);
1247
+ if (isFocusVisible(event.target)) {
1248
+ focusedThumbIndex.value = index;
1249
+ activeThumbIndex.value = index;
1250
+ }
1251
+ };
1252
+ const handleHiddenInputBlur = (event) => {
1253
+ if (!isFocusVisible(event.target)) {
1254
+ focusedThumbIndex.value = -1;
1255
+ activeThumbIndex.value = -1;
1256
+ }
1257
+ };
1258
+ const handleHiddenInputKeydown = (event) => {
1259
+ if (!NAVIGATION_KEYS.has(event.key)) return;
1260
+ event.preventDefault();
1261
+ const index = readThumbIndex(event);
1262
+ const value = values.value[index];
1263
+ if (typeof value !== "number") {
1264
+ return;
1265
+ }
1266
+ if (!isDiscrete.value) {
1267
+ const stepSize = event.shiftKey ? shiftStep.value : step.value;
1268
+ if (event.key === "Home") return commitValueFromEvent(event, min.value);
1269
+ if (event.key === "End") return commitValueFromEvent(event, max.value);
1270
+ if (INCREMENT_KEYS.has(event.key)) {
1271
+ const next = MathUtils.clamp(value + stepSize, min.value, max.value);
1272
+ if (next !== value) commitValueFromEvent(event, next);
1273
+ return;
1274
+ }
1275
+ if (DECREMENT_KEYS.has(event.key)) {
1276
+ const next = MathUtils.clamp(value - stepSize, min.value, max.value);
1277
+ if (next !== value) commitValueFromEvent(event, next);
1278
+ return;
1279
+ }
1280
+ return;
1281
+ } else {
1282
+ const values2 = marksValues.value;
1283
+ const lastIndex = values2.length - 1;
1284
+ const currentIndex = values2.indexOf(value);
1285
+ const first = values2[0];
1286
+ const last = values2[lastIndex];
1287
+ if (event.key === "Home" && typeof first === "number")
1288
+ return commitValueFromEvent(event, first);
1289
+ if (event.key === "End" && typeof last === "number")
1290
+ return commitValueFromEvent(event, last);
1291
+ if (INCREMENT_KEYS.has(event.key)) {
1292
+ const nextIdx = currentIndex < 0 ? 0 : Math.min(lastIndex, currentIndex + 1);
1293
+ const next = values2[nextIdx];
1294
+ if (next !== value && typeof next === "number") commitValueFromEvent(event, next);
1295
+ return;
1296
+ }
1297
+ if (DECREMENT_KEYS.has(event.key)) {
1298
+ const nextIdx = currentIndex < 0 ? 0 : Math.max(0, currentIndex - 1);
1299
+ const next = values2[nextIdx];
1300
+ if (next !== value && typeof next === "number") commitValueFromEvent(event, next);
1301
+ return;
1302
+ }
1303
+ }
1304
+ };
1305
+ const trackOffset = computed(
1306
+ () => MathUtils.valueToPercent(
1307
+ isRange.value && values.value[0] ? values.value[0] : min.value,
1308
+ min.value,
1309
+ max.value
1310
+ )
1311
+ );
1312
+ const trackLength = computed(
1313
+ () => MathUtils.valueToPercent(values.value.at(-1) ?? 0, min.value, max.value) - trackOffset.value
1314
+ );
1315
+ const trackStyle = computed(() => ({
1316
+ [axis.value.position]: `${trackOffset.value}%`,
1317
+ [axis.value.size]: `${trackLength.value}%`
1318
+ }));
1319
+ const isMarkActive = computed(() => (markValue) => {
1320
+ if (isRange.value) {
1321
+ const minValue = Math.min(...values.value);
1322
+ const maxValue = Math.max(...values.value);
1323
+ return markValue >= minValue && markValue <= maxValue;
1324
+ }
1325
+ const currentValue = values.value[0];
1326
+ return markValue <= currentValue;
1327
+ });
1328
+ onBeforeUnmount(stopPointerListening);
1329
+ watch(
1330
+ () => isDisabled.value,
1331
+ () => {
1332
+ if (isDisabled.value) {
1333
+ isDragging.value = false;
1334
+ activeThumbIndex.value = -1;
1335
+ focusedThumbIndex.value = -1;
1336
+ stopPointerListening();
1337
+ }
1338
+ }
1339
+ );
1340
+ return {
1341
+ elements: {
1342
+ /**
1343
+ * Root slider container element
1344
+ */
1345
+ root: computed(() => ({
1346
+ ref: sliderRef,
1347
+ style: { touchAction: orientation.value === "vertical" ? "pan-x" : "pan-y" },
1348
+ onMousedown: handleRootMousedown,
1349
+ onTouchstart: handlePointerStart
1350
+ })),
1351
+ /**
1352
+ * Individual thumb elements for each value (span)
1353
+ */
1354
+ thumbContainer: computed(() => (data) => ({
1355
+ "data-index": data.index,
1356
+ style: {
1357
+ [axis.value.position]: `${MathUtils.valueToPercent(data.value, min.value, max.value)}%`
1358
+ }
1359
+ })),
1360
+ /**
1361
+ * Visually hidden input inside each thumb for accessibility
1362
+ */
1363
+ thumbInput: computed(() => (data) => ({
1364
+ min: min.value,
1365
+ max: max.value,
1366
+ value: data.value,
1367
+ role: "slider",
1368
+ type: "range",
1369
+ "aria-label": label.value,
1370
+ "aria-valuemin": min.value,
1371
+ "aria-valuemax": max.value,
1372
+ "aria-valuenow": data.value,
1373
+ "aria-orientation": orientation.value,
1374
+ "data-index": data.index,
1375
+ tabIndex: isDisabled.value ? -1 : 0,
1376
+ step: isDiscrete.value && marks.value ? "any" : step.value ?? void 0,
1377
+ disabled: typeof isDisabled.value === "boolean" ? isDisabled.value : false,
1378
+ onChange: handleHiddenInputChange,
1379
+ onFocus: handleHiddenInputFocus,
1380
+ onBlur: handleHiddenInputBlur,
1381
+ onKeydown: handleHiddenInputKeydown
1382
+ })),
1383
+ /**
1384
+ * Mark elements
1385
+ */
1386
+ mark: computed(() => (data) => ({
1387
+ "data-value": data.value,
1388
+ "aria-hidden": true,
1389
+ style: {
1390
+ [axis.value.position]: `${MathUtils.clamp(MathUtils.valueToPercent(data.value, min.value, max.value), 0, 100)}%`
1391
+ }
1392
+ })),
1393
+ /**
1394
+ * Label for each mark
1395
+ */
1396
+ markLabel: computed(() => (data) => ({
1397
+ "data-value": data.value,
1398
+ style: {
1399
+ [axis.value.position]: `${MathUtils.valueToPercent(data.value, min.value, max.value)}%`
1400
+ },
1401
+ "aria-hidden": true
1402
+ })),
1403
+ /**
1404
+ * Track element representing the selected range
1405
+ */
1406
+ track: computed(() => ({
1407
+ role: "presentation",
1408
+ "aria-hidden": true,
1409
+ style: trackStyle.value
1410
+ })),
1411
+ /**
1412
+ * Rail element representing the full slider range
1413
+ */
1414
+ rail: computed(() => ({
1415
+ role: "presentation",
1416
+ "aria-hidden": true
1417
+ }))
1418
+ },
1419
+ state: {
1420
+ /**
1421
+ * True if the slider is currently being dragged.
1422
+ */
1423
+ isDragging,
1424
+ /**
1425
+ * Index of the currently active thumb.
1426
+ * Thumb could be active even if not dragging (e.g. click on the rail to move the thumb there).
1427
+ * `-1` if no thumb is active.
1428
+ */
1429
+ activeThumbIndex,
1430
+ /**
1431
+ * Index of the thumb that is currently focused.
1432
+ * `-1` if no thumb is focused.
1433
+ */
1434
+ focusedThumbIndex,
1435
+ /**
1436
+ * `true` if the slider is a range slider (with two or more thumbs).
1437
+ */
1438
+ isRange,
1439
+ /**
1440
+ * Offset of the track as a percentage (0-100).
1441
+ */
1442
+ trackOffset,
1443
+ /**
1444
+ * Length of the track as a percentage (0-100).
1445
+ */
1446
+ trackLength,
1447
+ /**
1448
+ * List of marks to display on the slider.
1449
+ * - If marks option is `true`, marks are generated based on the step value.
1450
+ * - If marks option is an array of numbers or objects, it is used as provided (filtered to be within range).
1451
+ * - If marks option is `false`, no marks are shown.
1452
+ */
1453
+ marksList
1454
+ },
1455
+ internals: {
1456
+ /**
1457
+ * Converts a value from the slider's range to a percentage (0-100).
1458
+ * @param value - value to convert
1459
+ * @returns percentage representation of the value
1460
+ */
1461
+ valueToPercent: computed(
1462
+ () => (value) => MathUtils.valueToPercent(value, min.value, max.value)
1463
+ ),
1464
+ /**
1465
+ * Checks if a given mark value is active (i.e., within the selected range).
1466
+ * Use case: when rendering marks, to determine if a mark is covered by the selected range.
1467
+ *
1468
+ * @param markValue - value of the mark to check
1469
+ * @returns `true` if the mark is active, `false` otherwise
1470
+ */
1471
+ isMarkActive,
1472
+ /**
1473
+ * Clamps a value to the slider's range.
1474
+ * @param value - value to clamp
1475
+ * @returns clamped value
1476
+ */
1477
+ clampValue: computed(() => (value) => MathUtils.clamp(value, min.value, max.value)),
1478
+ /**
1479
+ * Main axis properties based on orientation.
1480
+ */
1481
+ axis,
1482
+ /**
1483
+ * Rounds a value to the nearest valid step.
1484
+ * @param value - value to round
1485
+ * @returns rounded value
1486
+ */
1487
+ roundToStep: computed(
1488
+ () => (value) => !isDiscrete.value ? roundToStep(value, step.value, min.value) : marksValues.value[findClosestIndex(marksValues.value, value)]
1489
+ ),
1490
+ /**
1491
+ * Normalizes an array of values to ensure they are within min/max bounds,
1492
+ */
1493
+ normalizeValues: computed(
1494
+ () => (values2) => normalizeValues(values2, min.value, max.value, step.value)
1495
+ )
1496
+ }
1497
+ };
1498
+ }
1499
+ );
885
1500
  const createTabs = createBuilder((options) => {
886
1501
  const idMap = /* @__PURE__ */ new Map();
887
1502
  const getId = (value) => {
@@ -1066,6 +1681,7 @@ export {
1066
1681
  CLOSING_KEYS,
1067
1682
  OPENING_KEYS,
1068
1683
  _unstableCreateCalendar,
1684
+ _unstableCreateSlider,
1069
1685
  createBuilder,
1070
1686
  createComboBox,
1071
1687
  createElRef,
@@ -0,0 +1,4 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {
2
+ buttonRef: HTMLButtonElement;
3
+ }, HTMLDivElement>;
4
+ export default _default;
@@ -0,0 +1,4 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {
2
+ buttonRef: HTMLButtonElement;
3
+ }, HTMLDivElement>;
4
+ export default _default;
@@ -0,0 +1,3 @@
1
+ type Comparer<T> = (a: T, b: T) => boolean;
2
+ export declare const areArraysEqual: <T>(arrayA: ReadonlyArray<T>, arrayB: ReadonlyArray<T>, comparer?: Comparer<T>) => boolean;
3
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Checks if an element has focus-visible state (keyboard focus).
3
+ * Falls back to false if :focus-visible is not supported.
4
+ */
5
+ export declare const isFocusVisible: (element: Element) => boolean;
6
+ /**
7
+ * Determines whether a given event is a `TouchEvent`.
8
+ *
9
+ * This function uses property-based detection instead of `instanceof TouchEvent`,
10
+ * because Safari and other WebKit-based browsers may not expose the global
11
+ * `TouchEvent` constructor, causing `instanceof` checks to fail or throw errors.
12
+ *
13
+ * @param event - The event object to check.
14
+ * @returns `true` if the event is a touch event, otherwise `false`.
15
+ */
16
+ export declare const isTouchEvent: (event: Event) => event is TouchEvent;
@@ -3,4 +3,31 @@ export declare const MathUtils: {
3
3
  * Ensures that a given `number` is or is between a given `min` and `max`.
4
4
  */
5
5
  clamp: (number: number, min: number, max: number) => number;
6
+ /**
7
+ * Returns the count of decimal places in a number.
8
+ * @param number - The number to check.
9
+ * @returns The count of decimal places.
10
+ *
11
+ * decimals(1.23); // 2
12
+ * decimals(10); // 0
13
+ */
14
+ decimalsCount: (number: number) => number;
15
+ /**
16
+ * Converts a value within a range to a percentage (0-100).
17
+ *
18
+ * @param value - The value to convert.
19
+ * @param min - The minimum allowed value.
20
+ * @param max - The maximum allowed value.
21
+ * @returns The percentage representation of the value.
22
+ */
23
+ valueToPercent: (value: number, min: number, max: number) => number;
24
+ /**
25
+ * Converts a percentage (0-100) to a value within a range.
26
+ *
27
+ * @param percent - The percentage to convert.
28
+ * @param min - The minimum allowed value.
29
+ * @param max - The maximum allowed value.
30
+ * @returns The value representation of the percentage.
31
+ */
32
+ percentToValue: (percent: number, min: number, max: number) => number;
6
33
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sit-onyx/headless",
3
3
  "description": "Headless composables for Vue",
4
- "version": "0.3.0-dev-20251027185548",
4
+ "version": "0.3.0-dev-20251027213347",
5
5
  "type": "module",
6
6
  "author": "Schwarz IT KG",
7
7
  "license": "Apache-2.0",