@tremolo-ui/react 0.5.0 → 0.6.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/dist/compose-refs.cjs +72 -0
- package/dist/compose-refs.cjs.map +1 -0
- package/dist/compose-refs.d.cts +43 -0
- package/dist/compose-refs.d.cts.map +1 -0
- package/dist/compose-refs.d.ts +43 -0
- package/dist/compose-refs.d.ts.map +1 -0
- package/dist/compose-refs.js +70 -0
- package/dist/compose-refs.js.map +1 -0
- package/dist/index.cjs +1468 -534
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1103 -435
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1103 -435
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1457 -534
- package/dist/index.js.map +1 -1
- package/package.json +28 -26
- package/src/components/AnimationCanvas/index.tsx +115 -76
- package/src/components/Knob/ActiveLine.tsx +7 -6
- package/src/components/Knob/InactiveLine.tsx +9 -10
- package/src/components/Knob/SVGRoot.tsx +5 -3
- package/src/components/Knob/Thumb.tsx +33 -11
- package/src/components/Knob/context.tsx +17 -1
- package/src/components/Knob/index.tsx +148 -55
- package/src/components/NumberInput/DecrementStepper.tsx +8 -4
- package/src/components/NumberInput/IncrementStepper.tsx +9 -4
- package/src/components/NumberInput/InputField.tsx +188 -105
- package/src/components/NumberInput/Stepper.tsx +123 -72
- package/src/components/NumberInput/{stepperButton.tsx → StepperButton.tsx} +27 -22
- package/src/components/NumberInput/context.tsx +35 -8
- package/src/components/NumberInput/index.tsx +272 -75
- package/src/components/Piano/index.tsx +354 -188
- package/src/components/PointsEditor/Background.tsx +11 -2
- package/src/components/PointsEditor/Container.tsx +87 -27
- package/src/components/PointsEditor/Point.tsx +239 -79
- package/src/components/PointsEditor/SelectionBox.tsx +54 -0
- package/src/components/PointsEditor/context.tsx +78 -10
- package/src/components/PointsEditor/index.tsx +440 -39
- package/src/components/Slider/Marks.tsx +48 -16
- package/src/components/Slider/MarksOption.tsx +57 -43
- package/src/components/Slider/Thumb.tsx +108 -50
- package/src/components/Slider/Track.tsx +69 -58
- package/src/components/Slider/context.tsx +4 -2
- package/src/components/Slider/index.tsx +131 -65
- package/src/components/Slider/type.ts +26 -8
- package/src/components/XYPad/Area.tsx +45 -35
- package/src/components/XYPad/Thumb.tsx +135 -62
- package/src/components/XYPad/context.tsx +4 -1
- package/src/components/XYPad/index.tsx +180 -48
- package/src/components/_util/Placement.tsx +68 -0
- package/src/components/_util/VisuallyHiddenRangeInput.tsx +24 -0
- package/src/components/_util/css-length.ts +13 -0
- package/src/{components/_util/composeRefs.tsx → compose-refs/index.tsx} +33 -5
- package/src/hooks/_internal/useCallbackRef.ts +3 -1
- package/src/hooks/_internal/useCheckSteps.ts +170 -0
- package/src/hooks/useAnimationFrame.ts +17 -7
- package/src/hooks/useDrag.ts +38 -8
- package/src/hooks/useDragValue.ts +54 -9
- package/src/hooks/useEventListener.ts +10 -6
- package/src/hooks/useLongPress.ts +42 -8
- package/src/hooks/useMIDIAccess.ts +82 -11
- package/src/hooks/useMIDIInput.ts +45 -13
- package/src/hooks/useMIDIMessage.ts +23 -5
- package/src/index.ts +38 -21
- package/src/input-event.ts +32 -0
- package/dist/index.css +0 -501
- package/src/Globals.d.ts +0 -3
- package/src/components/Knob/index.css +0 -81
- package/src/components/NumberInput/index.css +0 -121
- package/src/components/Piano/index.css +0 -90
- package/src/components/PointsEditor/index.css +0 -35
- package/src/components/Slider/index.css +0 -125
- package/src/components/XYPad/index.css +0 -60
- package/src/components/_util/index.ts +0 -28
- package/src/styles/global.css +0 -19
- /package/src/components/Piano/{keyboardShortcuts.ts → keyboard-shortcuts.ts} +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import clsx from 'clsx'
|
|
2
1
|
import React, {
|
|
3
2
|
ComponentPropsWithoutRef,
|
|
4
3
|
CSSProperties,
|
|
@@ -15,15 +14,22 @@ import {
|
|
|
15
14
|
applyDelta,
|
|
16
15
|
linearScale,
|
|
17
16
|
toFixed,
|
|
18
|
-
InputEventOption,
|
|
17
|
+
type InputEventOption,
|
|
18
|
+
type ModifierValue,
|
|
19
|
+
selectModifier,
|
|
19
20
|
xor,
|
|
20
21
|
type Scale,
|
|
21
22
|
} from '@tremolo-ui/functions'
|
|
22
23
|
|
|
24
|
+
import { useComposedRefs } from '../../compose-refs'
|
|
25
|
+
import { useCheckSteps } from '../../hooks/_internal/useCheckSteps'
|
|
23
26
|
import { useDragValue } from '../../hooks/useDragValue'
|
|
24
27
|
import { useWheel } from '../../hooks/useWheel'
|
|
25
|
-
import {
|
|
26
|
-
|
|
28
|
+
import {
|
|
29
|
+
DEFAULT_DRAG_SENSITIVITY,
|
|
30
|
+
DEFAULT_KEYBOARD_OPTIONS,
|
|
31
|
+
DEFAULT_WHEEL_OPTIONS,
|
|
32
|
+
} from '../../input-event'
|
|
27
33
|
|
|
28
34
|
import { SliderProvider } from './context'
|
|
29
35
|
import { Marks } from './Marks'
|
|
@@ -32,17 +38,23 @@ import { Thumb, SliderThumbMethods } from './Thumb'
|
|
|
32
38
|
import { Track } from './Track'
|
|
33
39
|
|
|
34
40
|
const defaultExternalStyles: SliderProps['externalStyles'] = {
|
|
35
|
-
userSelectNone: true,
|
|
36
41
|
cursor: 'pointer',
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
export interface SliderProps {
|
|
40
|
-
|
|
45
|
+
/** The current value. The slider shows only this, so update it from `onChange`. */
|
|
41
46
|
value: number
|
|
47
|
+
/** The value at the start of the travel. */
|
|
42
48
|
min: number
|
|
49
|
+
/** The value at the end of the travel. */
|
|
43
50
|
max: number
|
|
44
51
|
|
|
45
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Granularity of the value. A drag, the wheel and the arrow keys snap it to
|
|
54
|
+
* multiples of `step`.
|
|
55
|
+
*
|
|
56
|
+
* @default 1
|
|
57
|
+
*/
|
|
46
58
|
step?: number
|
|
47
59
|
/**
|
|
48
60
|
* How the value is distributed across the travel.
|
|
@@ -55,43 +67,93 @@ export interface SliderProps {
|
|
|
55
67
|
*/
|
|
56
68
|
scale?: Scale
|
|
57
69
|
/**
|
|
58
|
-
* slider
|
|
59
|
-
* aria-orientation
|
|
70
|
+
* Run the slider vertically, with the value growing upwards. The range input
|
|
71
|
+
* inside the thumb takes its `aria-orientation` from this.
|
|
72
|
+
*
|
|
73
|
+
* @default false
|
|
60
74
|
*/
|
|
61
75
|
vertical?: boolean
|
|
76
|
+
/**
|
|
77
|
+
* Grow the value the other way: leftwards, or downwards when `vertical`.
|
|
78
|
+
* The arrow keys follow the direction on screen.
|
|
79
|
+
*
|
|
80
|
+
* @default false
|
|
81
|
+
*/
|
|
62
82
|
reverse?: boolean
|
|
63
83
|
|
|
64
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* The cursor to show while dragging. It is set on the dragged element, so it
|
|
86
|
+
* stays while the pointer is outside the slider.
|
|
87
|
+
*
|
|
88
|
+
* @default { cursor: 'pointer' }
|
|
89
|
+
*/
|
|
65
90
|
externalStyles?: {
|
|
66
|
-
|
|
67
|
-
cursor?: Cursor
|
|
91
|
+
cursor?: CSSProperties['cursor']
|
|
68
92
|
}
|
|
69
93
|
/**
|
|
70
|
-
* wheel
|
|
71
|
-
*
|
|
94
|
+
* How much one notch of the wheel moves the value. It only acts while the
|
|
95
|
+
* focus is inside, so that scrolling the page past the slider leaves it
|
|
96
|
+
* alone.
|
|
97
|
+
*
|
|
98
|
+
* `['raw', n]` moves the value by `n`, and `['normalized', n]` by `n` of the
|
|
99
|
+
* range between `min` and `max`. The result is snapped to `step`, except for
|
|
100
|
+
* an amount set on a modifier key (`{ default: …, shift: … }`). `null` turns
|
|
101
|
+
* the wheel off.
|
|
102
|
+
*
|
|
103
|
+
* @default ['raw', 1]
|
|
72
104
|
*/
|
|
73
|
-
wheel?: InputEventOption | null
|
|
105
|
+
wheel?: ModifierValue<InputEventOption> | null
|
|
74
106
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
107
|
+
* How much a drag moves the value, per modifier key.
|
|
108
|
+
*
|
|
109
|
+
* `1` is the pointer position itself, which is what a drag normally is here.
|
|
110
|
+
* **Anything else turns the drag relative**: `0.1` makes the same movement
|
|
111
|
+
* cover a tenth of the travel, so the value stops following the pointer and
|
|
112
|
+
* starts moving a tenth as fast. Shift is bound to `0.1` by default, to
|
|
113
|
+
* match what it does on the arrow keys.
|
|
114
|
+
*
|
|
115
|
+
* Pressing or releasing the key mid-drag does not disturb the value: the
|
|
116
|
+
* travel so far is kept and the new sensitivity applies from there. **The
|
|
117
|
+
* pointer and the value stay apart for the rest of the drag** — snapping
|
|
118
|
+
* them back together on release would move the value nobody asked to move.
|
|
119
|
+
*
|
|
120
|
+
* @default { default: 1, shift: 0.1 }
|
|
77
121
|
*/
|
|
78
|
-
|
|
122
|
+
dragSensitivity?: ModifierValue<number>
|
|
79
123
|
|
|
80
124
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
125
|
+
* How much one arrow key press moves the value.
|
|
126
|
+
*
|
|
127
|
+
* `['raw', n]` moves the value by `n`, and `['normalized', n]` by `n` of the
|
|
128
|
+
* range between `min` and `max`. The result is snapped to `step`, except for
|
|
129
|
+
* an amount set on a modifier key, which is what lets shift move off the
|
|
130
|
+
* grid. `null` turns the arrow keys off.
|
|
131
|
+
*
|
|
132
|
+
* The default moves by 1, and by 0.1 with shift. With a `step` above 1, raise
|
|
133
|
+
* the amount to match: 1 would round straight back to where it started, and
|
|
134
|
+
* a development build warns about it.
|
|
135
|
+
*
|
|
136
|
+
* @default { default: ['raw', 1], shift: ['raw', 0.1] }
|
|
137
|
+
*/
|
|
138
|
+
keyboard?: ModifierValue<InputEventOption> | null
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Make the slider unchangeable and remove it from the tab order.
|
|
142
|
+
* The parts carry `data-disabled` while it is set.
|
|
84
143
|
*/
|
|
85
144
|
disabled?: boolean
|
|
86
145
|
/**
|
|
87
146
|
* Make the value unchangeable.
|
|
88
|
-
*
|
|
147
|
+
* The parts carry `data-readonly` while it is set.
|
|
89
148
|
*/
|
|
90
149
|
readonly?: boolean
|
|
91
150
|
className?: string
|
|
92
151
|
style?: CSSProperties
|
|
152
|
+
/** Called with the new value when a drag, the wheel or an arrow key moves it. */
|
|
93
153
|
onChange?: (value: number) => void
|
|
154
|
+
/** Called when a drag starts, with the value where the track was pressed. */
|
|
94
155
|
onDragStart?: (value: number) => void
|
|
156
|
+
/** Called when the drag ends, with the value it ended on. */
|
|
95
157
|
onDragEnd?: (value: number) => void
|
|
96
158
|
/**
|
|
97
159
|
* The slider renders exactly what you compose here; there is no default
|
|
@@ -100,7 +162,7 @@ export interface SliderProps {
|
|
|
100
162
|
* @example
|
|
101
163
|
* <Slider.Root value={value} min={0} max={100} onChange={setValue}>
|
|
102
164
|
* <Slider.Track>
|
|
103
|
-
* <Slider.Thumb />
|
|
165
|
+
* <Slider.Thumb aria-label="Level" />
|
|
104
166
|
* </Slider.Track>
|
|
105
167
|
* </Slider.Root>
|
|
106
168
|
*/
|
|
@@ -121,7 +183,7 @@ type Props = SliderProps &
|
|
|
121
183
|
*/
|
|
122
184
|
const WHEEL_OPTIONS = { requireFocus: true }
|
|
123
185
|
|
|
124
|
-
export const Root = forwardRef<SliderMethods, Props>(
|
|
186
|
+
export const Root = /* @__PURE__ */ forwardRef<SliderMethods, Props>(
|
|
125
187
|
(
|
|
126
188
|
{
|
|
127
189
|
value,
|
|
@@ -132,8 +194,9 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
132
194
|
vertical = false,
|
|
133
195
|
reverse = false,
|
|
134
196
|
externalStyles: _externalStyles,
|
|
135
|
-
wheel =
|
|
136
|
-
keyboard =
|
|
197
|
+
wheel = DEFAULT_WHEEL_OPTIONS,
|
|
198
|
+
keyboard = DEFAULT_KEYBOARD_OPTIONS,
|
|
199
|
+
dragSensitivity = DEFAULT_DRAG_SENSITIVITY,
|
|
137
200
|
disabled = false,
|
|
138
201
|
readonly = false,
|
|
139
202
|
onChange,
|
|
@@ -153,9 +216,9 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
153
216
|
// -- state and ref ---
|
|
154
217
|
const trackRef = useRef<HTMLDivElement>(null)
|
|
155
218
|
const thumbRef = useRef<SliderThumbMethods>(null)
|
|
156
|
-
|
|
157
219
|
// --- interpret props ---
|
|
158
220
|
const externalStyles = { ...defaultExternalStyles, ..._externalStyles }
|
|
221
|
+
const inactive = disabled || readonly
|
|
159
222
|
|
|
160
223
|
const p = toFixed(scale.normalize(value, min, max) * 100)
|
|
161
224
|
const rev = toFixed(100 - p)
|
|
@@ -179,18 +242,20 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
179
242
|
[min, max, step, scale, displayReversed],
|
|
180
243
|
)
|
|
181
244
|
|
|
245
|
+
useCheckSteps({ component: 'Slider', range: axis, keyboard, wheel })
|
|
246
|
+
|
|
182
247
|
const handleKeyDown = useCallback(
|
|
183
248
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
184
|
-
if (!keyboard || !onChange || readonly) return
|
|
185
249
|
const key = event.key
|
|
186
|
-
if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(key))
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
250
|
+
if (!['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(key))
|
|
251
|
+
return
|
|
252
|
+
event.preventDefault()
|
|
253
|
+
if (!keyboard || !onChange || inactive) return
|
|
254
|
+
let direction = key === 'ArrowRight' || key === 'ArrowUp' ? 1 : -1
|
|
255
|
+
if (reverse) direction *= -1
|
|
256
|
+
onChange(applyDelta(value, direction, keyboard, axis, event))
|
|
192
257
|
},
|
|
193
|
-
[keyboard, onChange,
|
|
258
|
+
[keyboard, onChange, inactive, reverse, value, axis],
|
|
194
259
|
)
|
|
195
260
|
|
|
196
261
|
// --- hooks ---
|
|
@@ -199,40 +264,38 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
199
264
|
const { refCallback: dragRefCallback } = useDragValue<HTMLDivElement>({
|
|
200
265
|
axis,
|
|
201
266
|
baseElementRef: trackRef,
|
|
202
|
-
cursor:
|
|
267
|
+
cursor: inactive ? undefined : externalStyles.cursor,
|
|
268
|
+
shouldStart: () => !inactive,
|
|
269
|
+
sensitivity: (state) =>
|
|
270
|
+
selectModifier(dragSensitivity, state.event).value,
|
|
203
271
|
updateOnPointerDown: true,
|
|
204
272
|
onChange: (v) => {
|
|
205
|
-
if (
|
|
273
|
+
if (inactive) return
|
|
206
274
|
onChange?.(valueOf(v))
|
|
207
275
|
},
|
|
208
276
|
onDragStart: (v) => {
|
|
209
|
-
if (
|
|
210
|
-
if (externalStyles.userSelectNone) addUserSelectNone()
|
|
211
|
-
|
|
277
|
+
if (inactive) return
|
|
212
278
|
thumbRef.current?.focus()
|
|
213
279
|
onDragStart?.(valueOf(v))
|
|
214
280
|
},
|
|
215
281
|
onDragEnd: (v) => {
|
|
216
|
-
if (
|
|
217
|
-
|
|
218
|
-
if (externalStyles.userSelectNone) removeUserSelectNone()
|
|
219
|
-
|
|
282
|
+
if (inactive) return
|
|
220
283
|
onDragEnd?.(valueOf(v))
|
|
221
284
|
},
|
|
222
285
|
})
|
|
223
286
|
|
|
224
287
|
const wheelRefCallback = useWheel<HTMLDivElement>((event) => {
|
|
225
|
-
if (!wheel || !onChange ||
|
|
288
|
+
if (!wheel || !onChange || inactive) return
|
|
226
289
|
event.preventDefault()
|
|
227
290
|
let direction
|
|
228
|
-
if (!vertical && event.deltaX
|
|
291
|
+
if (!vertical && event.deltaX !== 0) {
|
|
229
292
|
direction = event.deltaX > 0 ? 1 : -1
|
|
230
293
|
} else {
|
|
231
|
-
if (event.deltaY
|
|
294
|
+
if (event.deltaY === 0) return
|
|
232
295
|
direction = event.deltaY > 0 ? -1 : 1
|
|
233
296
|
}
|
|
234
|
-
if (
|
|
235
|
-
onChange(applyDelta(value, direction, wheel, axis))
|
|
297
|
+
if (reverse) direction *= -1
|
|
298
|
+
onChange(applyDelta(value, direction, wheel, axis, event))
|
|
236
299
|
}, WHEEL_OPTIONS)
|
|
237
300
|
|
|
238
301
|
// Composed once, so React attaches the refs a single time instead of
|
|
@@ -253,6 +316,7 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
253
316
|
reverse,
|
|
254
317
|
disabled,
|
|
255
318
|
readonly,
|
|
319
|
+
onChange,
|
|
256
320
|
percent,
|
|
257
321
|
trackRef,
|
|
258
322
|
thumbRef,
|
|
@@ -267,6 +331,7 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
267
331
|
reverse,
|
|
268
332
|
disabled,
|
|
269
333
|
readonly,
|
|
334
|
+
onChange,
|
|
270
335
|
percent,
|
|
271
336
|
],
|
|
272
337
|
)
|
|
@@ -274,28 +339,29 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
274
339
|
useImperativeHandle(forwardedRef, () => {
|
|
275
340
|
return {
|
|
276
341
|
focus() {
|
|
277
|
-
thumbRef.current?.focus()
|
|
342
|
+
if (!disabled) thumbRef.current?.focus()
|
|
278
343
|
},
|
|
279
344
|
blur() {
|
|
280
345
|
thumbRef.current?.blur()
|
|
281
346
|
},
|
|
282
347
|
}
|
|
283
|
-
}, [])
|
|
348
|
+
}, [disabled])
|
|
284
349
|
|
|
285
350
|
return (
|
|
286
351
|
<SliderProvider value={context}>
|
|
352
|
+
{/* oxlint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- the group is the pointer and keyboard event area, while the nested range input carries its control semantics */}
|
|
287
353
|
<div
|
|
288
|
-
className={
|
|
354
|
+
className={className}
|
|
289
355
|
ref={rootRefCallback}
|
|
356
|
+
role="group"
|
|
357
|
+
// A press lands on the track or the thumb, neither of which can hold
|
|
358
|
+
// focus, and the browser answers that by clearing the focus to the
|
|
359
|
+
// body — undoing the focus the drag just gave the input. Taking the
|
|
360
|
+
// focus here keeps it inside, and `onFocus` passes it to the input.
|
|
290
361
|
tabIndex={-1}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
aria-valuemax={max}
|
|
295
|
-
aria-orientation={vertical ? 'vertical' : 'horizontal'}
|
|
296
|
-
aria-disabled={disabled}
|
|
297
|
-
aria-readonly={readonly}
|
|
298
|
-
data-vertical={vertical}
|
|
362
|
+
data-orientation={vertical ? 'vertical' : 'horizontal'}
|
|
363
|
+
data-disabled={disabled ? '' : undefined}
|
|
364
|
+
data-readonly={readonly ? '' : undefined}
|
|
299
365
|
style={style}
|
|
300
366
|
onPointerDown={onPointerDown}
|
|
301
367
|
onKeyDown={(event) => {
|
|
@@ -303,7 +369,7 @@ export const Root = forwardRef<SliderMethods, Props>(
|
|
|
303
369
|
onKeyDown?.(event)
|
|
304
370
|
}}
|
|
305
371
|
onFocus={(event) => {
|
|
306
|
-
thumbRef.current?.focus()
|
|
372
|
+
if (!disabled) thumbRef.current?.focus()
|
|
307
373
|
onFocus?.(event)
|
|
308
374
|
}}
|
|
309
375
|
onBlur={(event) => {
|
|
@@ -330,9 +396,9 @@ export const Slider = {
|
|
|
330
396
|
MarksOption,
|
|
331
397
|
}
|
|
332
398
|
|
|
333
|
-
export { useSliderContext } from './context'
|
|
399
|
+
export { useSliderContext, type SliderContextValue } from './context'
|
|
334
400
|
export { type SliderThumbMethods, type SliderThumbProps } from './Thumb'
|
|
335
401
|
export { type SliderTrackProps } from './Track'
|
|
336
|
-
export { type
|
|
337
|
-
export { type
|
|
338
|
-
export { type MarksOptions
|
|
402
|
+
export { type SliderMarksProps } from './Marks'
|
|
403
|
+
export { type SliderMarksOptionProps } from './MarksOption'
|
|
404
|
+
export { type MarksOptions } from './type'
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { decimalPart, toFixed } from '@tremolo-ui/functions'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* How `Slider.Marks` fills itself in when it is given no children: one option
|
|
5
|
+
* every `per`, or every `step` of the slider. The object form turns off the
|
|
6
|
+
* mark or the label for the whole set; a single option is customized by
|
|
7
|
+
* writing `Slider.MarksOption` out instead.
|
|
8
|
+
*/
|
|
9
|
+
export type MarksOptions =
|
|
10
|
+
| 'step'
|
|
11
|
+
| number
|
|
12
|
+
| {
|
|
13
|
+
per: 'step' | number
|
|
14
|
+
mark?: boolean
|
|
15
|
+
label?: boolean
|
|
16
|
+
}
|
|
5
17
|
|
|
6
18
|
export function generateOptionsList(
|
|
7
19
|
options: MarksOptions,
|
|
@@ -9,18 +21,24 @@ export function generateOptionsList(
|
|
|
9
21
|
max: number,
|
|
10
22
|
step: number,
|
|
11
23
|
) {
|
|
24
|
+
const {
|
|
25
|
+
per,
|
|
26
|
+
mark = true,
|
|
27
|
+
label = true,
|
|
28
|
+
} = typeof options === 'object' ? options : { per: options }
|
|
12
29
|
const optionsList: {
|
|
13
30
|
value: number
|
|
14
|
-
|
|
31
|
+
mark: boolean
|
|
32
|
+
label: boolean
|
|
15
33
|
}[] = []
|
|
16
|
-
const
|
|
17
|
-
const count = Math.floor(max /
|
|
34
|
+
const interval = per === 'step' ? step : per
|
|
35
|
+
const count = Math.floor(max / interval) - Math.ceil(min / interval) + 1
|
|
18
36
|
for (let i = 0; i < count; i++) {
|
|
19
37
|
const value = toFixed(
|
|
20
|
-
|
|
21
|
-
decimalPart(
|
|
38
|
+
interval * (Math.ceil(min / interval) + i),
|
|
39
|
+
decimalPart(interval)?.length,
|
|
22
40
|
)
|
|
23
|
-
optionsList.push({ value: value,
|
|
41
|
+
optionsList.push({ value: value, mark: mark, label: label })
|
|
24
42
|
}
|
|
25
43
|
return optionsList
|
|
26
44
|
}
|
|
@@ -1,51 +1,61 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
ComponentPropsWithoutRef,
|
|
3
|
+
CSSProperties,
|
|
4
|
+
forwardRef,
|
|
5
|
+
ReactNode,
|
|
6
|
+
} from 'react'
|
|
3
7
|
|
|
4
|
-
import { useComposedRefs } from '
|
|
8
|
+
import { useComposedRefs } from '../../compose-refs'
|
|
9
|
+
import { cssLength } from '../_util/css-length'
|
|
10
|
+
import { Placement } from '../_util/Placement'
|
|
5
11
|
|
|
6
12
|
import { useXYPadContext } from './context'
|
|
7
13
|
|
|
8
14
|
export interface XYPadAreaProps {
|
|
15
|
+
/** Sets `--width`; the size the theme gives it stands when omitted. */
|
|
9
16
|
width?: number | string
|
|
17
|
+
/** Sets `--height`; the size the theme gives it stands when omitted. */
|
|
10
18
|
height?: number | string
|
|
19
|
+
/** Sets `--color`, for the theme to colour the area with. */
|
|
11
20
|
color?: string
|
|
12
21
|
className?: string
|
|
13
22
|
style?: CSSProperties
|
|
14
23
|
/** `<XYPad.Thumb />` goes here. */
|
|
15
24
|
children?: ReactNode
|
|
16
|
-
ref?: Ref<HTMLDivElement>
|
|
17
25
|
}
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
height = 120,
|
|
22
|
-
color,
|
|
23
|
-
children,
|
|
24
|
-
className,
|
|
25
|
-
style,
|
|
26
|
-
ref,
|
|
27
|
-
...props
|
|
28
|
-
}: XYPadAreaProps &
|
|
29
|
-
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadAreaProps>) {
|
|
30
|
-
const { areaRef } = useXYPadContext()
|
|
27
|
+
type Props = XYPadAreaProps &
|
|
28
|
+
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadAreaProps>
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
export const Area = /* @__PURE__ */ forwardRef<HTMLDivElement, Props>(
|
|
31
|
+
function Area(
|
|
32
|
+
{ width, height, color, children, className, style, ...props },
|
|
33
|
+
forwardedRef,
|
|
34
|
+
) {
|
|
35
|
+
const { areaRef } = useXYPadContext()
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
37
|
+
// The area is what the pointer position is normalized against, so the
|
|
38
|
+
// context ref is composed with any ref the caller passed.
|
|
39
|
+
const composedRef = useComposedRefs<HTMLDivElement>(forwardedRef, areaRef)
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
ref={composedRef}
|
|
44
|
+
className={className}
|
|
45
|
+
style={
|
|
46
|
+
{
|
|
47
|
+
'--color': color,
|
|
48
|
+
'--width': cssLength(width),
|
|
49
|
+
'--height': cssLength(height),
|
|
50
|
+
// The thumb inside is placed against this box.
|
|
51
|
+
position: 'relative',
|
|
52
|
+
...style,
|
|
53
|
+
} as CSSProperties
|
|
54
|
+
}
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
<Placement name="XYPad.Area">{children}</Placement>
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
},
|
|
61
|
+
)
|