@zag-js/slider 0.24.0 → 0.25.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.
@@ -6,7 +6,12 @@ import type { CommonProperties, Context, DirectionProperty, PropTypes, RequiredB
6
6
  * -----------------------------------------------------------------------------*/
7
7
 
8
8
  export interface ValueChangeDetails {
9
- value: number
9
+ value: number[]
10
+ }
11
+
12
+ export interface FocusChangeDetails {
13
+ focusedIndex: number
14
+ value: number[]
10
15
  }
11
16
 
12
17
  /* -----------------------------------------------------------------------------
@@ -15,32 +20,40 @@ export interface ValueChangeDetails {
15
20
 
16
21
  type ElementIds = Partial<{
17
22
  root: string
18
- thumb: string
23
+ thumb(index: number): string
19
24
  control: string
20
25
  track: string
21
26
  range: string
22
27
  label: string
23
28
  output: string
24
- hiddenInput: string
29
+ marker(index: number): string
25
30
  }>
26
31
 
27
32
  interface PublicContext extends DirectionProperty, CommonProperties {
28
33
  /**
29
- * The ids of the elements in the slider. Useful for composition.
34
+ * The ids of the elements in the range slider. Useful for composition.
30
35
  */
31
36
  ids?: ElementIds
32
37
  /**
33
- * The value of the slider
38
+ * The aria-label of each slider thumb. Useful for providing an accessible name to the slider
34
39
  */
35
- value: number
40
+ "aria-label"?: string[]
41
+ /**
42
+ * The `id` of the elements that labels each slider thumb. Useful for providing an accessible name to the slider
43
+ */
44
+ "aria-labelledby"?: string[]
36
45
  /**
37
- * The name associated with the slider (when used in a form)
46
+ * The name associated with each slider thumb (when used in a form)
38
47
  */
39
48
  name?: string
40
49
  /**
41
50
  * The associate form of the underlying input element.
42
51
  */
43
52
  form?: string
53
+ /**
54
+ * The value of the range slider
55
+ */
56
+ value: number[]
44
57
  /**
45
58
  * Whether the slider is disabled
46
59
  */
@@ -50,58 +63,50 @@ interface PublicContext extends DirectionProperty, CommonProperties {
50
63
  */
51
64
  readOnly?: boolean
52
65
  /**
53
- * Whether the slider value is invalid
66
+ * Whether the slider is invalid
54
67
  */
55
68
  invalid?: boolean
56
69
  /**
57
- * The minimum value of the slider
58
- */
59
- min: number
60
- /**
61
- * The maximum value of the slider
62
- */
63
- max: number
64
- /**
65
- * The step value of the slider
70
+ * Function invoked when the value of the slider changes
66
71
  */
67
- step: number
72
+ onValueChange?(details: ValueChangeDetails): void
68
73
  /**
69
- * The orientation of the slider
74
+ * Function invoked when the slider value change is done
70
75
  */
71
- orientation?: "vertical" | "horizontal"
76
+ onValueChangeEnd?(details: ValueChangeDetails): void
72
77
  /**
73
- * - "start": Useful when the value represents an absolute value
74
- * - "center": Useful when the value represents an offset (relative)
78
+ * Function invoked when the slider's focused index changes
75
79
  */
76
- origin?: "start" | "center"
80
+ onFocusChange?(details: FocusChangeDetails): void
77
81
  /**
78
- * The aria-label of the slider. Useful for providing an accessible name to the slider
82
+ * Function that returns a human readable value for the slider thumb
79
83
  */
80
- "aria-label"?: string
84
+ getAriaValueText?(value: number, index: number): string
81
85
  /**
82
- * The `id` of the element that labels the slider. Useful for providing an accessible name to the slider
86
+ * The minimum value of the slider
83
87
  */
84
- "aria-labelledby"?: string
88
+ min: number
85
89
  /**
86
- * Whether to focus the slider thumb after interaction (scrub and keyboard)
90
+ * The maximum value of the slider
87
91
  */
88
- focusThumbOnChange?: boolean
92
+ max: number
89
93
  /**
90
- * Function that returns a human readable value for the slider
94
+ * The step value of the slider
91
95
  */
92
- getAriaValueText?(value: number): string
96
+ step: number
93
97
  /**
94
- * Function invoked when the value of the slider changes
98
+ * The minimum permitted steps between multiple thumbs.
95
99
  */
96
- onValueChange?(details: ValueChangeDetails): void
100
+ minStepsBetweenThumbs: number
97
101
  /**
98
- * Function invoked when the slider value change is done
102
+ * The orientation of the slider
99
103
  */
100
- onValueChangeEnd?(details: ValueChangeDetails): void
104
+ orientation: "vertical" | "horizontal"
101
105
  /**
102
- * Function invoked when the slider value change is started
106
+ * - "start": Useful when the value represents an absolute value
107
+ * - "center": Useful when the value represents an offset (relative)
103
108
  */
104
- onValueChangeStart?(details: ValueChangeDetails): void
109
+ origin?: "start" | "center"
105
110
  /**
106
111
  * The alignment of the slider thumb relative to the track
107
112
  * - `center`: the thumb will extend beyond the bounds of the slider track.
@@ -109,9 +114,9 @@ interface PublicContext extends DirectionProperty, CommonProperties {
109
114
  */
110
115
  thumbAlignment?: "contain" | "center"
111
116
  /**
112
- * The slider thumb dimensions.If not provided, the thumb size will be measured automatically.
117
+ * The slider thumbs dimensions
113
118
  */
114
- thumbSize: Size | null
119
+ thumbSize: { width: number; height: number } | null
115
120
  }
116
121
 
117
122
  export type UserDefinedContext = RequiredBy<PublicContext, "id">
@@ -119,24 +124,29 @@ export type UserDefinedContext = RequiredBy<PublicContext, "id">
119
124
  type ComputedContext = Readonly<{
120
125
  /**
121
126
  * @computed
122
- * Whether the slider is interactive
127
+ * Whether the slider thumb has been measured
123
128
  */
124
- isInteractive: boolean
129
+ hasMeasuredThumbSize: boolean
125
130
  /**
126
131
  * @computed
127
- * Whether the thumb size has been measured
132
+ * Whether the slider is interactive
128
133
  */
129
- hasMeasuredThumbSize: boolean
134
+ isInteractive: boolean
130
135
  /**
131
136
  * @computed
132
- * Whether the slider is horizontal
137
+ * The raw value of the space between each thumb
133
138
  */
134
- isHorizontal: boolean
139
+ spacing: number
135
140
  /**
136
141
  * @computed
137
142
  * Whether the slider is vertical
138
143
  */
139
144
  isVertical: boolean
145
+ /**
146
+ * @computed
147
+ * Whether the slider is horizontal
148
+ */
149
+ isHorizontal: boolean
140
150
  /**
141
151
  * @computed
142
152
  * Whether the slider is in RTL mode
@@ -144,9 +154,9 @@ type ComputedContext = Readonly<{
144
154
  isRtl: boolean
145
155
  /**
146
156
  * @computed
147
- * The value of the slider as a percentage
157
+ * The percentage of the slider value relative to the slider min/max
148
158
  */
149
- valuePercent: number
159
+ valuePercent: number[]
150
160
  /**
151
161
  * @computed
152
162
  * Whether the slider is disabled
@@ -157,9 +167,10 @@ type ComputedContext = Readonly<{
157
167
  type PrivateContext = Context<{
158
168
  /**
159
169
  * @internal
160
- * The move threshold of the slider thumb before it is considered to be moved
170
+ * The active index of the range slider. This represents
171
+ * the currently dragged/focused thumb.
161
172
  */
162
- threshold: number
173
+ focusedIndex: number
163
174
  /**
164
175
  * @internal
165
176
  * Whether the slider fieldset is disabled
@@ -181,68 +192,86 @@ export type Send = S.Send<S.AnyEventObject>
181
192
  * Component API
182
193
  * -----------------------------------------------------------------------------*/
183
194
 
184
- export interface Point {
185
- x: number
186
- y: number
187
- }
188
-
189
195
  interface Size {
190
196
  width: number
191
197
  height: number
192
198
  }
193
199
 
194
- interface MarkerProps {
200
+ export interface MarkerProps {
195
201
  value: number
196
202
  }
197
203
 
204
+ export interface ThumbProps {
205
+ index: number
206
+ }
207
+
198
208
  export interface MachineApi<T extends PropTypes = PropTypes> {
199
209
  /**
200
- * Whether the slider is focused.
210
+ * The value of the slider.
201
211
  */
202
- isFocused: boolean
212
+ value: number[]
203
213
  /**
204
214
  * Whether the slider is being dragged.
205
215
  */
206
216
  isDragging: boolean
207
217
  /**
208
- * The value of the slider.
218
+ * Whether the slider is focused.
209
219
  */
210
- value: number
220
+ isFocused: boolean
211
221
  /**
212
- * The value of the slider as a percent.
222
+ * Function to set the value of the slider.
213
223
  */
214
- percent: number
224
+ setValue(value: number[]): void
215
225
  /**
216
- * Function to set the value of the slider.
226
+ * Returns the value of the thumb at the given index.
217
227
  */
218
- setValue(value: number): void
228
+ getThumbValue(index: number): number
219
229
  /**
220
- * Returns the value of the slider at the given percent.
230
+ * Sets the value of the thumb at the given index.
221
231
  */
222
- getPercentValue: (percent: number) => number
232
+ setThumbValue(index: number, value: number): void
223
233
  /**
224
- * Returns the percent of the slider at the given value.
234
+ * Returns the percent of the thumb at the given index.
225
235
  */
226
236
  getValuePercent: (value: number) => number
227
237
  /**
228
- * Function to focus the slider.
238
+ * Returns the value of the thumb at the given percent.
229
239
  */
230
- focus(): void
240
+ getPercentValue: (percent: number) => number
231
241
  /**
232
- * Function to increment the value of the slider by the step.
242
+ * Returns the percent of the thumb at the given index.
233
243
  */
234
- increment(): void
244
+ getThumbPercent(index: number): number
235
245
  /**
236
- * Function to decrement the value of the slider by the step.
246
+ * Sets the percent of the thumb at the given index.
237
247
  */
238
- decrement(): void
239
-
240
- rootProps: T["element"]
248
+ setThumbPercent(index: number, percent: number): void
249
+ /**
250
+ * Returns the min value of the thumb at the given index.
251
+ */
252
+ getThumbMin(index: number): number
253
+ /**
254
+ * Returns the max value of the thumb at the given index.
255
+ */
256
+ getThumbMax(index: number): number
257
+ /**
258
+ * Function to increment the value of the slider at the given index.
259
+ */
260
+ increment(index: number): void
261
+ /**
262
+ * Function to decrement the value of the slider at the given index.
263
+ */
264
+ decrement(index: number): void
265
+ /**
266
+ * Function to focus the slider. This focuses the first thumb.
267
+ */
268
+ focus(): void
241
269
  labelProps: T["label"]
242
- thumbProps: T["element"]
243
- hiddenInputProps: T["input"]
270
+ rootProps: T["element"]
244
271
  outputProps: T["output"]
245
272
  trackProps: T["element"]
273
+ getThumbProps(props: ThumbProps): T["element"]
274
+ getHiddenInputProps(props: ThumbProps): T["input"]
246
275
  rangeProps: T["element"]
247
276
  controlProps: T["element"]
248
277
  markerGroupProps: T["element"]
@@ -1,31 +1,64 @@
1
- import { clampValue, getNextStepValue, getPreviousStepValue, snapValueToStep } from "@zag-js/numeric-range"
1
+ import {
2
+ clampValue,
3
+ getClosestValueIndex,
4
+ getNextStepValue,
5
+ getPreviousStepValue,
6
+ getValueRanges,
7
+ snapValueToStep,
8
+ } from "@zag-js/numeric-range"
2
9
  import type { MachineContext as Ctx } from "./slider.types"
3
10
 
11
+ export function normalizeValues(ctx: Ctx, nextValues: number[]) {
12
+ return nextValues.map((value, index, values) => {
13
+ return constrainValue({ ...ctx, value: values }, value, index)
14
+ })
15
+ }
16
+
4
17
  export function clampPercent(percent: number) {
5
18
  return clampValue(percent, 0, 1)
6
19
  }
7
20
 
8
- export function constrainValue(ctx: Ctx, value: number) {
21
+ export function getRangeAtIndex(ctx: Ctx, index: number) {
22
+ return getValueRanges(ctx.value, ctx.min, ctx.max, ctx.minStepsBetweenThumbs)[index]
23
+ }
24
+
25
+ export function constrainValue(ctx: Ctx, value: number, index: number) {
26
+ const range = getRangeAtIndex(ctx, index)
9
27
  const snapValue = snapValueToStep(value, ctx.min, ctx.max, ctx.step)
10
- return clampValue(snapValue, ctx.min, ctx.max)
28
+ return clampValue(snapValue, range.min, range.max)
11
29
  }
12
30
 
13
- export function decrement(ctx: Ctx, step?: number) {
14
- const index = 0
15
- const values = getPreviousStepValue(index, {
16
- ...ctx,
31
+ export function decrement(ctx: Ctx, index?: number, step?: number) {
32
+ const idx = index ?? ctx.focusedIndex
33
+ const range = getRangeAtIndex(ctx, idx)
34
+ const nextValues = getPreviousStepValue(idx, {
35
+ ...range,
17
36
  step: step ?? ctx.step,
18
- values: [ctx.value],
37
+ values: ctx.value,
19
38
  })
20
- return values[index]
39
+ nextValues[idx] = clampValue(nextValues[idx], range.min, range.max)
40
+ return nextValues
21
41
  }
22
42
 
23
- export function increment(ctx: Ctx, step?: number) {
24
- const index = 0
25
- const values = getNextStepValue(index, {
26
- ...ctx,
43
+ export function increment(ctx: Ctx, index?: number, step?: number) {
44
+ const idx = index ?? ctx.focusedIndex
45
+ const range = getRangeAtIndex(ctx, idx)
46
+ const nextValues = getNextStepValue(idx, {
47
+ ...range,
27
48
  step: step ?? ctx.step,
28
- values: [ctx.value],
49
+ values: ctx.value,
29
50
  })
30
- return values[index]
51
+ nextValues[idx] = clampValue(nextValues[idx], range.min, range.max)
52
+ return nextValues
53
+ }
54
+
55
+ export function getClosestIndex(ctx: Ctx, pointValue: number) {
56
+ return getClosestValueIndex(ctx.value, pointValue)
57
+ }
58
+
59
+ export function assignArray(current: number[], next: number[]) {
60
+ for (let i = 0; i < next.length; i++) {
61
+ const value = next[i]
62
+ current[i] = value
63
+ }
31
64
  }