@react-fabric/form 1.0.0-beta.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.
Files changed (187) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -0
  3. package/dist/cjs/builder/FileField.js +131 -0
  4. package/dist/cjs/builder/FormField.js +119 -0
  5. package/dist/cjs/form/Controller.js +64 -0
  6. package/dist/cjs/form/Form.js +128 -0
  7. package/dist/cjs/hooks/useFileUploader.js +204 -0
  8. package/dist/cjs/hooks/useFormBuilder.js +87 -0
  9. package/dist/cjs/i18n/ar.json.js +37 -0
  10. package/dist/cjs/i18n/en.json.js +38 -0
  11. package/dist/cjs/i18n/index.js +33 -0
  12. package/dist/cjs/index.js +56 -0
  13. package/dist/cjs/input/ArrayInput.js +189 -0
  14. package/dist/cjs/input/ArrayInput.module.css +1 -0
  15. package/dist/cjs/input/AvatarInput.js +141 -0
  16. package/dist/cjs/input/Checkbox.js +83 -0
  17. package/dist/cjs/input/ColorInput.js +100 -0
  18. package/dist/cjs/input/ColorPicker.js +122 -0
  19. package/dist/cjs/input/ColorPicker.module.css +1 -0
  20. package/dist/cjs/input/Field.js +31 -0
  21. package/dist/cjs/input/File.js +165 -0
  22. package/dist/cjs/input/File.module.css +1 -0
  23. package/dist/cjs/input/Hidden.js +54 -0
  24. package/dist/cjs/input/Input.js +79 -0
  25. package/dist/cjs/input/Number.js +80 -0
  26. package/dist/cjs/input/Password.js +104 -0
  27. package/dist/cjs/input/Radio.js +70 -0
  28. package/dist/cjs/input/RadioGroup.js +31 -0
  29. package/dist/cjs/input/Range.js +169 -0
  30. package/dist/cjs/input/Search.js +109 -0
  31. package/dist/cjs/input/Slider.js +172 -0
  32. package/dist/cjs/input/Switch.js +79 -0
  33. package/dist/cjs/input/Textarea.js +87 -0
  34. package/dist/cjs/internal/ErrorIcon.js +32 -0
  35. package/dist/cjs/internal/Field.module.css +1 -0
  36. package/dist/cjs/internal/FieldWrapper.js +78 -0
  37. package/dist/cjs/internal/InputWrapper.js +113 -0
  38. package/dist/cjs/ranger/index.js +352 -0
  39. package/dist/cjs/ranger/utils.js +49 -0
  40. package/dist/cjs/select/AutoComplete.js +268 -0
  41. package/dist/cjs/select/DualList.js +238 -0
  42. package/dist/cjs/select/List.js +241 -0
  43. package/dist/cjs/select/Options.js +76 -0
  44. package/dist/cjs/select/Select.js +283 -0
  45. package/dist/cjs/select/helpers.js +41 -0
  46. package/dist/cjs/select/useSelect.js +337 -0
  47. package/dist/cjs/utils/eventHandlers.js +50 -0
  48. package/dist/cjs/utils/getColor.js +40 -0
  49. package/dist/esm/builder/FileField.js +131 -0
  50. package/dist/esm/builder/FormField.js +119 -0
  51. package/dist/esm/form/Controller.js +64 -0
  52. package/dist/esm/form/Form.js +128 -0
  53. package/dist/esm/hooks/useFileUploader.js +204 -0
  54. package/dist/esm/hooks/useFormBuilder.js +87 -0
  55. package/dist/esm/i18n/ar.json.js +31 -0
  56. package/dist/esm/i18n/en.json.js +32 -0
  57. package/dist/esm/i18n/index.js +33 -0
  58. package/dist/esm/index.js +28 -0
  59. package/dist/esm/input/ArrayInput.js +189 -0
  60. package/dist/esm/input/ArrayInput.module.css +1 -0
  61. package/dist/esm/input/AvatarInput.js +141 -0
  62. package/dist/esm/input/Checkbox.js +83 -0
  63. package/dist/esm/input/ColorInput.js +100 -0
  64. package/dist/esm/input/ColorPicker.js +122 -0
  65. package/dist/esm/input/ColorPicker.module.css +1 -0
  66. package/dist/esm/input/Field.js +31 -0
  67. package/dist/esm/input/File.js +165 -0
  68. package/dist/esm/input/File.module.css +1 -0
  69. package/dist/esm/input/Hidden.js +54 -0
  70. package/dist/esm/input/Input.js +79 -0
  71. package/dist/esm/input/Number.js +80 -0
  72. package/dist/esm/input/Password.js +104 -0
  73. package/dist/esm/input/Radio.js +70 -0
  74. package/dist/esm/input/RadioGroup.js +31 -0
  75. package/dist/esm/input/Range.js +169 -0
  76. package/dist/esm/input/Search.js +109 -0
  77. package/dist/esm/input/Slider.js +172 -0
  78. package/dist/esm/input/Switch.js +79 -0
  79. package/dist/esm/input/Textarea.js +87 -0
  80. package/dist/esm/internal/ErrorIcon.js +32 -0
  81. package/dist/esm/internal/Field.module.css +1 -0
  82. package/dist/esm/internal/FieldWrapper.js +78 -0
  83. package/dist/esm/internal/InputWrapper.js +113 -0
  84. package/dist/esm/ranger/index.js +352 -0
  85. package/dist/esm/ranger/utils.js +47 -0
  86. package/dist/esm/select/AutoComplete.js +268 -0
  87. package/dist/esm/select/DualList.js +238 -0
  88. package/dist/esm/select/List.js +241 -0
  89. package/dist/esm/select/Options.js +76 -0
  90. package/dist/esm/select/Select.js +283 -0
  91. package/dist/esm/select/helpers.js +40 -0
  92. package/dist/esm/select/useSelect.js +337 -0
  93. package/dist/esm/utils/eventHandlers.js +50 -0
  94. package/dist/esm/utils/getColor.js +39 -0
  95. package/dist/types/builder/FileField.d.ts +8 -0
  96. package/dist/types/builder/FormField.d.ts +9 -0
  97. package/dist/types/form/Controller.d.ts +20 -0
  98. package/dist/types/form/Form.d.ts +33 -0
  99. package/dist/types/hooks/useFileUploader.d.ts +23 -0
  100. package/dist/types/hooks/useFormBuilder.d.ts +16 -0
  101. package/dist/types/i18n/index.d.ts +1 -0
  102. package/dist/types/index.d.ts +27 -0
  103. package/dist/types/input/ArrayInput.d.ts +64 -0
  104. package/dist/types/input/AvatarInput.d.ts +31 -0
  105. package/dist/types/input/Checkbox.d.ts +46 -0
  106. package/dist/types/input/ColorInput.d.ts +10 -0
  107. package/dist/types/input/ColorPicker.d.ts +24 -0
  108. package/dist/types/input/Field.d.ts +41 -0
  109. package/dist/types/input/File.d.ts +17 -0
  110. package/dist/types/input/Hidden.d.ts +16 -0
  111. package/dist/types/input/Input.d.ts +9 -0
  112. package/dist/types/input/Number.d.ts +17 -0
  113. package/dist/types/input/Password.d.ts +13 -0
  114. package/dist/types/input/Radio.d.ts +42 -0
  115. package/dist/types/input/RadioGroup.d.ts +30 -0
  116. package/dist/types/input/Range.d.ts +59 -0
  117. package/dist/types/input/Search.d.ts +17 -0
  118. package/dist/types/input/Slider.d.ts +63 -0
  119. package/dist/types/input/Switch.d.ts +42 -0
  120. package/dist/types/input/Textarea.d.ts +17 -0
  121. package/dist/types/internal/ErrorIcon.d.ts +1 -0
  122. package/dist/types/internal/FieldWrapper.d.ts +1 -0
  123. package/dist/types/internal/InputWrapper.d.ts +1 -0
  124. package/dist/types/ranger/index.d.ts +70 -0
  125. package/dist/types/ranger/utils.d.ts +25 -0
  126. package/dist/types/select/AutoComplete.d.ts +17 -0
  127. package/dist/types/select/DualList.d.ts +38 -0
  128. package/dist/types/select/List.d.ts +4 -0
  129. package/dist/types/select/Options.d.ts +14 -0
  130. package/dist/types/select/Select.d.ts +2 -0
  131. package/dist/types/select/helpers.d.ts +2 -0
  132. package/dist/types/select/useSelect.d.ts +50 -0
  133. package/dist/types/types/index.d.ts +169 -0
  134. package/dist/types/types/schema.d.ts +59 -0
  135. package/dist/types/utils/eventHandlers.d.ts +11 -0
  136. package/dist/types/utils/getColor.d.ts +4 -0
  137. package/dist/types/utils/index.d.ts +2 -0
  138. package/package.json +64 -0
  139. package/src/builder/FileField.tsx +122 -0
  140. package/src/builder/FormField.tsx +161 -0
  141. package/src/form/Controller.tsx +103 -0
  142. package/src/form/Form.tsx +160 -0
  143. package/src/hooks/useFileUploader.ts +234 -0
  144. package/src/hooks/useFormBuilder.tsx +128 -0
  145. package/src/i18n/ar.json +23 -0
  146. package/src/i18n/en.json +24 -0
  147. package/src/i18n/index.ts +28 -0
  148. package/src/index.ts +54 -0
  149. package/src/input/ArrayInput.module.css +41 -0
  150. package/src/input/ArrayInput.tsx +318 -0
  151. package/src/input/AvatarInput.tsx +150 -0
  152. package/src/input/Checkbox.tsx +165 -0
  153. package/src/input/ColorInput.tsx +157 -0
  154. package/src/input/ColorPicker.module.css +51 -0
  155. package/src/input/ColorPicker.tsx +210 -0
  156. package/src/input/Field.tsx +96 -0
  157. package/src/input/File.module.css +42 -0
  158. package/src/input/File.tsx +224 -0
  159. package/src/input/Hidden.tsx +65 -0
  160. package/src/input/Input.tsx +114 -0
  161. package/src/input/Number.tsx +126 -0
  162. package/src/input/Password.tsx +150 -0
  163. package/src/input/Radio.tsx +146 -0
  164. package/src/input/RadioGroup.tsx +75 -0
  165. package/src/input/Range.tsx +276 -0
  166. package/src/input/Search.tsx +160 -0
  167. package/src/input/Slider.tsx +282 -0
  168. package/src/input/Switch.tsx +168 -0
  169. package/src/input/Textarea.tsx +127 -0
  170. package/src/internal/ErrorIcon.tsx +61 -0
  171. package/src/internal/Field.module.css +139 -0
  172. package/src/internal/FieldWrapper.tsx +84 -0
  173. package/src/internal/InputWrapper.tsx +187 -0
  174. package/src/ranger/index.tsx +514 -0
  175. package/src/ranger/utils.ts +48 -0
  176. package/src/select/AutoComplete.tsx +354 -0
  177. package/src/select/DualList.tsx +286 -0
  178. package/src/select/List.tsx +288 -0
  179. package/src/select/Options.tsx +121 -0
  180. package/src/select/Select.tsx +350 -0
  181. package/src/select/helpers.ts +36 -0
  182. package/src/select/useSelect.tsx +413 -0
  183. package/src/types/index.ts +203 -0
  184. package/src/types/schema.ts +100 -0
  185. package/src/utils/eventHandlers.ts +72 -0
  186. package/src/utils/getColor.ts +47 -0
  187. package/src/utils/index.ts +25 -0
@@ -0,0 +1,514 @@
1
+ /**
2
+ * react-ranger
3
+ * https://github.com/TanStack/ranger
4
+ */
5
+ import { useIsRtl } from "@react-fabric/core";
6
+ import {
7
+ type KeyboardEvent as KE,
8
+ type MouseEvent as ME,
9
+ type TouchEvent as TE,
10
+ useCallback,
11
+ useEffect,
12
+ useMemo,
13
+ useRef,
14
+ useState,
15
+ } from "react";
16
+ import {
17
+ getBoundingClientRect,
18
+ linearInterpolator,
19
+ sortNumList,
20
+ } from "./utils";
21
+
22
+ type RangerChangeEvent = (newValue: readonly number[]) => void;
23
+
24
+ type RangerConfig = {
25
+ values: readonly number[];
26
+
27
+ min: number;
28
+ max: number;
29
+
30
+ tickSize: number;
31
+ ticks?: number[];
32
+
33
+ vertical?: boolean;
34
+
35
+ onChange: RangerChangeEvent;
36
+ onDrag?: RangerChangeEvent;
37
+ onDragStart?: () => void;
38
+ onDragEnd?: () => void;
39
+
40
+ debug?: boolean;
41
+ } & ({ stepSize: number } | { steps: number[] });
42
+
43
+ const useGetLatest = (val: AnyObject) => {
44
+ const ref = useRef(val);
45
+ ref.current = val;
46
+ return useCallback(() => {
47
+ return ref.current;
48
+ }, []);
49
+ };
50
+
51
+ export const useRanger = (opts: RangerConfig) => {
52
+ const tickSize = opts.tickSize ?? 10;
53
+ const values = opts.values;
54
+ const min = opts.min;
55
+ const max = opts.max;
56
+ const isVertical = opts.vertical;
57
+ const controlledTicks = opts.ticks;
58
+ const steps = "steps" in opts ? opts.steps : undefined;
59
+ const onChange = opts.onChange;
60
+ const onDrag = opts.onDrag;
61
+ const onDragStart = opts.onDragStart;
62
+ const onDragEnd = opts.onDragEnd;
63
+ const stepSize = "stepSize" in opts ? opts.stepSize : 1;
64
+
65
+ const [activeHandleIndex, setActiveHandleIndex] = useState<number>();
66
+ const [segmentStart, setSegmentStart] = useState<[number, number]>();
67
+ const [tempValues, setTempValues] = useState<number[]>();
68
+ const isRtl = useIsRtl();
69
+
70
+ const positionProp = useMemo(
71
+ () => (isVertical ? "bottom" : isRtl ? "right" : "left"),
72
+ [isVertical, isRtl],
73
+ );
74
+ const marginProp = useMemo(
75
+ () => (isVertical ? "marginBottom" : isRtl ? "marginRight" : "marginLeft"),
76
+ [isVertical, isRtl],
77
+ );
78
+
79
+ const getLatest = useGetLatest({
80
+ activeHandleIndex,
81
+ segmentStart,
82
+ onChange,
83
+ onDrag,
84
+ onDragStart,
85
+ onDragEnd,
86
+ values,
87
+ tempValues,
88
+ });
89
+ const trackElRef = useRef<HTMLDivElement>(null);
90
+ const segmentElRef = useRef<HTMLDivElement>(null);
91
+ const getValueForClientX = useCallback(
92
+ (clientX: number) => {
93
+ const trackDims = getBoundingClientRect(trackElRef.current);
94
+ return linearInterpolator.getValueForClientX(
95
+ clientX,
96
+ trackDims,
97
+ min,
98
+ max,
99
+ isRtl,
100
+ );
101
+ },
102
+ [max, min, isRtl],
103
+ );
104
+ const getValueForClientY = useCallback(
105
+ (clientY: number) => {
106
+ const trackDims = getBoundingClientRect(trackElRef.current);
107
+ return linearInterpolator.getValueForClientY(
108
+ clientY,
109
+ trackDims,
110
+ min,
111
+ max,
112
+ );
113
+ },
114
+ [max, min],
115
+ );
116
+ const getNextStep = useCallback(
117
+ (val: number, direction: number) => {
118
+ if (steps) {
119
+ const currIndex = steps.indexOf(val);
120
+ const nextIndex = currIndex + direction;
121
+
122
+ if (nextIndex >= 0 && nextIndex < steps.length) {
123
+ return steps[nextIndex];
124
+ } else {
125
+ return val;
126
+ }
127
+ } else {
128
+ if (process.env.NODE_ENV !== "production") {
129
+ if (typeof stepSize === "undefined") {
130
+ throw new Error(
131
+ "Warning: The option `stepSize` is expected in `useRanger`, but its value is `undefined`",
132
+ );
133
+ }
134
+ }
135
+
136
+ const nextVal = val + stepSize * direction;
137
+
138
+ if (nextVal >= min && nextVal <= max) {
139
+ return nextVal;
140
+ } else {
141
+ return val;
142
+ }
143
+ }
144
+ },
145
+ [max, min, stepSize, steps],
146
+ );
147
+ const roundToStep = useCallback(
148
+ (val: number) => {
149
+ let left = min;
150
+ let right = max;
151
+
152
+ if (steps) {
153
+ steps.forEach((step) => {
154
+ if (step <= val && step > left) {
155
+ left = step;
156
+ }
157
+
158
+ if (step >= val && step < right) {
159
+ right = step;
160
+ }
161
+ });
162
+ } else {
163
+ if (process.env.NODE_ENV !== "production") {
164
+ if (typeof stepSize === "undefined") {
165
+ throw new Error(
166
+ "Warning: The option `stepSize` is expected in `useRanger`, but its value is `undefined`",
167
+ );
168
+ }
169
+ }
170
+
171
+ while (left < val && left + stepSize < val) {
172
+ left += stepSize;
173
+ }
174
+
175
+ right = Math.min(left + stepSize, max);
176
+ }
177
+
178
+ if (val - left < right - val) {
179
+ return +left.toFixed(3);
180
+ }
181
+
182
+ return +right.toFixed(3);
183
+ },
184
+ [max, min, stepSize, steps],
185
+ );
186
+ const handleDrag = useCallback(
187
+ (e: MouseEvent | TouchEvent) => {
188
+ const { activeHandleIndex, onDrag } = getLatest();
189
+
190
+ const clientX =
191
+ // @ts-expect-error ignore
192
+ e.type === "touchmove" ? e.changedTouches[0].clientX : e.clientX;
193
+ const clientY =
194
+ // @ts-expect-error ignore
195
+ e.type === "touchmove" ? e.changedTouches[0].clientY : e.clientY;
196
+ const newValue = isVertical
197
+ ? getValueForClientY(clientY)
198
+ : getValueForClientX(clientX);
199
+ const newRoundedValue = roundToStep(newValue);
200
+ const newValues = [
201
+ ...values.slice(0, activeHandleIndex),
202
+ ...[newRoundedValue],
203
+ ...values.slice(+activeHandleIndex + 1),
204
+ ];
205
+ onDrag?.(newValues);
206
+ setTempValues(newValues);
207
+ },
208
+ [
209
+ getLatest,
210
+ getValueForClientX,
211
+ getValueForClientY,
212
+ roundToStep,
213
+ values,
214
+ isVertical,
215
+ ],
216
+ );
217
+ const handleSegmentDrag = useCallback(
218
+ (e: MouseEvent | TouchEvent) => {
219
+ const {
220
+ segmentStart: [segX, segY],
221
+ tempValues,
222
+ onDrag,
223
+ } = getLatest();
224
+ const { left, bottom } = getBoundingClientRect(segmentElRef.current);
225
+
226
+ const clientX =
227
+ // @ts-expect-error ignore
228
+ e.type === "touchmove" ? e.changedTouches[0].clientX : e.clientX;
229
+ const clientY =
230
+ // @ts-expect-error ignore
231
+ e.type === "touchmove" ? e.changedTouches[0].clientY : e.clientY;
232
+
233
+ const start = isVertical
234
+ ? getValueForClientX(bottom + (clientY - segY))
235
+ : getValueForClientX(left + (clientX - segX));
236
+ let diff = roundToStep(start) - tempValues[0];
237
+ if (+tempValues[1] + diff > max) diff = 0;
238
+ const newValues = [+tempValues[0] + diff, +tempValues[1] + diff];
239
+ if (tempValues[0] !== newValues[0] || tempValues[1] !== newValues[1]) {
240
+ onDrag?.(newValues);
241
+ setTempValues(newValues);
242
+ setSegmentStart([clientX, clientY]);
243
+ }
244
+ },
245
+ [
246
+ getLatest,
247
+ getValueForClientX,
248
+ getValueForClientY,
249
+ roundToStep,
250
+ values,
251
+ isVertical,
252
+ max,
253
+ ],
254
+ );
255
+ const handleKeyDown = useCallback(
256
+ (e: KE, i: number) => {
257
+ const { values, onChange } = getLatest();
258
+
259
+ if (["ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown"].includes(e.key)) {
260
+ setActiveHandleIndex(i);
261
+ const direction = ["ArrowLeft", "ArrowDown"].includes(e.key) ? -1 : 1;
262
+ const newValue = +getNextStep(values[i], direction).toFixed(3);
263
+ const newValues = [
264
+ ...values.slice(0, i),
265
+ ...[newValue],
266
+ ...values.slice(i + 1),
267
+ ];
268
+ const sortedValues = sortNumList(newValues);
269
+ onChange?.(sortedValues);
270
+ e.stopPropagation();
271
+ e.preventDefault();
272
+ }
273
+ },
274
+ [getLatest, getNextStep],
275
+ );
276
+ const handlePress = useCallback(
277
+ (e: ME | TE, i: number) => {
278
+ const { onDragStart, values } = getLatest();
279
+ if (i === -1) {
280
+ const clientX =
281
+ e.type === "touchmove"
282
+ ? // @ts-expect-error ignore
283
+ e.nativeEvent.changedTouches[0].clientX
284
+ : // @ts-expect-error ignore
285
+ e.nativeEvent.clientX;
286
+ const clientY =
287
+ e.type === "touchmove"
288
+ ? // @ts-expect-error ignore
289
+ e.nativeEvent.changedTouches[0].clientY
290
+ : // @ts-expect-error ignore
291
+ e.nativeEvent.clientY;
292
+
293
+ setTempValues(values);
294
+ setSegmentStart([clientX, clientY]);
295
+ } else {
296
+ setActiveHandleIndex(i);
297
+ }
298
+ const handleRelease = (e: MouseEvent | TouchEvent) => {
299
+ const { tempValues, onChange, onDragEnd } = getLatest();
300
+
301
+ document.removeEventListener(
302
+ "mousemove",
303
+ i === -1 ? handleSegmentDrag : handleDrag,
304
+ );
305
+ document.removeEventListener(
306
+ "touchmove",
307
+ i === -1 ? handleSegmentDrag : handleDrag,
308
+ );
309
+ document.removeEventListener("mouseup", handleRelease, {
310
+ capture: true,
311
+ });
312
+ document.removeEventListener("touchend", handleRelease, {
313
+ capture: true,
314
+ });
315
+ const sortedValues = sortNumList(tempValues || values);
316
+ onDragEnd?.();
317
+ onChange?.(sortedValues);
318
+ setActiveHandleIndex(undefined);
319
+ setSegmentStart(undefined);
320
+ setTempValues(undefined);
321
+ };
322
+
323
+ onDragStart?.();
324
+ document.addEventListener(
325
+ "mousemove",
326
+ i === -1 ? handleSegmentDrag : handleDrag,
327
+ );
328
+ document.addEventListener(
329
+ "touchmove",
330
+ i === -1 ? handleSegmentDrag : handleDrag,
331
+ );
332
+ document.addEventListener("mouseup", handleRelease, { capture: true });
333
+ document.addEventListener("touchend", handleRelease, { capture: true });
334
+ },
335
+ [getLatest, handleDrag],
336
+ );
337
+ const getPercentageForValue = useCallback(
338
+ (val: number) => {
339
+ return linearInterpolator.getPercentageForValue(val, min, max);
340
+ },
341
+ [max, min],
342
+ );
343
+ const trackClicked = useCallback(
344
+ (e: MouseEvent | TouchEvent) => {
345
+ const { onChange, values } = getLatest();
346
+ const clientX =
347
+ e.type === "touchmove"
348
+ ? // @ts-expect-error ignore
349
+ e.changedTouches[0].clientX
350
+ : // @ts-expect-error ignore
351
+ e.clientX;
352
+ const clientY =
353
+ e.type === "touchmove"
354
+ ? // @ts-expect-error ignore
355
+ e.changedTouches[0].clientY
356
+ : // @ts-expect-error ignore
357
+ e.clientY;
358
+
359
+ const newValue = isVertical
360
+ ? getValueForClientY(clientY)
361
+ : getValueForClientX(clientX);
362
+ const newRoundedValue = roundToStep(newValue);
363
+ let newValues = [newRoundedValue];
364
+ if (values.length === 2) {
365
+ if (values[0] > newRoundedValue)
366
+ newValues = [newRoundedValue, values[1]];
367
+ else if (values[1] < newRoundedValue)
368
+ newValues = [values[0], newRoundedValue];
369
+ else {
370
+ const diff = (values[1] - values[0]) / 2;
371
+ if (values[0] + diff >= newRoundedValue)
372
+ newValues = [newRoundedValue, values[1]];
373
+ if (values[1] - diff <= newRoundedValue)
374
+ newValues = [values[0], newRoundedValue];
375
+ }
376
+ }
377
+ onChange(newValues);
378
+ },
379
+ [getLatest, getValueForClientX, getValueForClientY, isVertical],
380
+ );
381
+ useEffect(() => {
382
+ trackElRef.current?.addEventListener("mousedown", trackClicked);
383
+ }, [trackClicked]);
384
+ // Build the ticks
385
+ const ticks = useMemo(() => {
386
+ let ticks = controlledTicks ?? steps;
387
+
388
+ if (!ticks) {
389
+ ticks = [min];
390
+ const count = tickSize - (tickSize % stepSize);
391
+ while (ticks[ticks.length - 1] < max - count) {
392
+ ticks.push(ticks[ticks.length - 1] + count);
393
+ }
394
+
395
+ ticks.push(max);
396
+ }
397
+
398
+ return ticks.map((value, i) => {
399
+ return {
400
+ key: i,
401
+ value,
402
+ percentage: getPercentageForValue(value),
403
+ styles: {
404
+ [positionProp]: `${getPercentageForValue(value)}%`,
405
+ [marginProp]: "-0.125rem",
406
+ },
407
+ };
408
+ });
409
+ }, [
410
+ controlledTicks,
411
+ getPercentageForValue,
412
+ max,
413
+ min,
414
+ steps,
415
+ tickSize,
416
+ positionProp,
417
+ marginProp,
418
+ ]);
419
+ const segments = useMemo(() => {
420
+ const sortedValues = sortNumList(tempValues ?? values);
421
+ return [...sortedValues, max].map((value, i) => {
422
+ const active = i === sortedValues.length - 1;
423
+ return {
424
+ key: i,
425
+ value,
426
+ active,
427
+ props: active
428
+ ? {
429
+ ref: segmentElRef,
430
+ onMouseDownCapture: (e: ME) => {
431
+ e.stopPropagation();
432
+ e.persist();
433
+ handlePress(e, -1);
434
+ },
435
+ onTouchStartCapture: (e: TE) => {
436
+ e.stopPropagation();
437
+ e.persist();
438
+ handlePress(e, -1);
439
+ },
440
+ }
441
+ : {},
442
+ styles: (() => {
443
+ const left = getPercentageForValue(
444
+ sortedValues[i - 1] ? sortedValues[i - 1] : min,
445
+ );
446
+ const width: number = getPercentageForValue(value) - left;
447
+ return {
448
+ [positionProp]: `${left}%`,
449
+ [isVertical ? "height" : "width"]: `${width}%`,
450
+ };
451
+ })(),
452
+ };
453
+ });
454
+ }, [
455
+ getPercentageForValue,
456
+ max,
457
+ min,
458
+ tempValues,
459
+ values,
460
+ positionProp,
461
+ marginProp,
462
+ ]);
463
+ const handles = useMemo(() => {
464
+ return (tempValues ?? values).map((value, i) => {
465
+ return {
466
+ key: i,
467
+ value,
468
+ percentage: getPercentageForValue(value),
469
+ active: i === activeHandleIndex,
470
+ props: {
471
+ role: "slider",
472
+ "aria-valuemin": min,
473
+ "aria-valuemax": max,
474
+ "aria-valuenow": value,
475
+ onKeyDown: (e: KE) => {
476
+ e.stopPropagation();
477
+ e.persist();
478
+ handleKeyDown(e, i);
479
+ },
480
+ onMouseDownCapture: (e: ME) => {
481
+ e.stopPropagation();
482
+ e.persist();
483
+ handlePress(e, i);
484
+ },
485
+ onTouchStartCapture: (e: TE) => {
486
+ e.stopPropagation();
487
+ e.persist();
488
+ handlePress(e, i);
489
+ },
490
+ },
491
+ styles: {
492
+ [positionProp]: `${getPercentageForValue(value)}%`,
493
+ [marginProp]: "-0.375rem",
494
+ },
495
+ valueStyles: {
496
+ [positionProp]: `${getPercentageForValue(value)}%`,
497
+ },
498
+ };
499
+ });
500
+ }, [
501
+ activeHandleIndex,
502
+ getPercentageForValue,
503
+ handleKeyDown,
504
+ handlePress,
505
+ min,
506
+ max,
507
+ tempValues,
508
+ values,
509
+ positionProp,
510
+ marginProp,
511
+ ]);
512
+
513
+ return { trackElRef, activeHandleIndex, ticks, segments, handles };
514
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * react-ranger
3
+ * https://github.com/TanStack/ranger
4
+ */
5
+
6
+ export const getBoundingClientRect = (element: any) => {
7
+ const rect = element.getBoundingClientRect();
8
+ return {
9
+ top: Math.ceil(rect.top),
10
+ left: Math.ceil(rect.left),
11
+ right: Math.ceil(rect.right),
12
+ width: Math.ceil(rect.width),
13
+ bottom: Math.ceil(rect.bottom),
14
+ height: Math.ceil(rect.height),
15
+ };
16
+ };
17
+
18
+ export const sortNumList = (arr: ReadonlyArray<number | string>) =>
19
+ [...arr].map(Number).sort((a, b) => a - b);
20
+
21
+ export const linearInterpolator = {
22
+ getPercentageForValue: (val: number, min: number, max: number) => {
23
+ return Math.max(0, Math.min(100, ((val - min) / (max - min)) * 100));
24
+ },
25
+ getValueForClientX: (
26
+ clientX: number,
27
+ trackDims: { width: number; left: number; right: number },
28
+ min: number,
29
+ max: number,
30
+ isRtl: boolean
31
+ ) => {
32
+ const { left, right, width } = trackDims;
33
+ const percentageValue = (isRtl ? right - clientX : clientX - left) / width;
34
+ const value = (max - min) * percentageValue;
35
+ return value + min;
36
+ },
37
+ getValueForClientY: (
38
+ clientY: number,
39
+ trackDims: { height: number; bottom: number },
40
+ min: number,
41
+ max: number
42
+ ) => {
43
+ const { height, bottom } = trackDims;
44
+ const percentageValue = (bottom - clientY) / height;
45
+ const value = (max - min) * percentageValue;
46
+ return value + min;
47
+ },
48
+ };