@tremolo-ui/react 0.3.0 → 0.5.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/index.cjs +1162 -1554
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +58 -93
- package/dist/index.d.cts +592 -278
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +592 -278
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1163 -1555
- package/dist/index.js.map +1 -1
- package/package.json +12 -13
- package/src/components/AnimationCanvas/index.tsx +84 -155
- package/src/components/Knob/ActiveLine.tsx +6 -6
- package/src/components/Knob/InactiveLine.tsx +9 -11
- package/src/components/Knob/SVGRoot.tsx +9 -18
- package/src/components/Knob/context.tsx +57 -89
- package/src/components/Knob/index.css +6 -2
- package/src/components/Knob/index.tsx +95 -83
- package/src/components/NumberInput/DecrementStepper.tsx +17 -50
- package/src/components/NumberInput/IncrementStepper.tsx +17 -50
- package/src/components/NumberInput/InputField.tsx +123 -0
- package/src/components/NumberInput/Stepper.tsx +90 -12
- package/src/components/NumberInput/context.tsx +74 -146
- package/src/components/NumberInput/index.css +38 -102
- package/src/components/NumberInput/index.tsx +252 -98
- package/src/components/NumberInput/stepperButton.tsx +90 -0
- package/src/components/Piano/index.tsx +272 -288
- package/src/components/Piano/keyboardShortcuts.ts +34 -4
- package/src/components/PointsEditor/Background.tsx +8 -2
- package/src/components/PointsEditor/Container.tsx +19 -9
- package/src/components/PointsEditor/Point.tsx +117 -52
- package/src/components/PointsEditor/context.tsx +28 -74
- package/src/components/PointsEditor/index.css +5 -0
- package/src/components/PointsEditor/index.tsx +77 -43
- package/src/components/Slider/{Scale.tsx → Marks.tsx} +8 -8
- package/src/components/Slider/{ScaleOption.tsx → MarksOption.tsx} +16 -18
- package/src/components/Slider/Thumb.tsx +55 -68
- package/src/components/Slider/Track.tsx +15 -15
- package/src/components/Slider/context.tsx +36 -51
- package/src/components/Slider/index.css +18 -5
- package/src/components/Slider/index.tsx +136 -170
- package/src/components/Slider/type.ts +4 -4
- package/src/components/XYPad/Area.tsx +16 -7
- package/src/components/XYPad/Thumb.tsx +62 -74
- package/src/components/XYPad/context.tsx +46 -0
- package/src/components/XYPad/index.css +12 -0
- package/src/components/XYPad/index.tsx +193 -250
- package/src/hooks/useDrag.ts +41 -76
- package/src/hooks/useDragValue.ts +149 -0
- package/src/hooks/useEventListener.ts +1 -1
- package/src/hooks/useWheel.ts +51 -0
- package/src/index.ts +15 -13
- package/src/components/AnimationCanvas/canvas.ts +0 -46
- package/src/components/DragObserver/index.tsx +0 -58
- package/src/components/NumberInput/InternalInput.tsx +0 -221
- package/src/components/NumberInput/type.ts +0 -58
- package/src/components/Piano/KeyLabel.tsx +0 -51
- package/src/components/Piano/context.tsx +0 -73
- package/src/components/Piano/key.tsx +0 -205
- package/src/components/WheelObserver/index.tsx +0 -48
- package/src/components/_util/type.ts +0 -1
- package/src/hooks/useDragWithElement.ts +0 -72
- package/src/hooks/usePianoDrag.ts +0 -75
- package/src/hooks/useRefCallbackEvent.ts +0 -34
- /package/src/hooks/{useCallbackRef.ts → _internal/useCallbackRef.ts} +0 -0
|
@@ -3,39 +3,33 @@ import React, {
|
|
|
3
3
|
ComponentPropsWithoutRef,
|
|
4
4
|
CSSProperties,
|
|
5
5
|
forwardRef,
|
|
6
|
-
|
|
6
|
+
ReactNode,
|
|
7
7
|
useCallback,
|
|
8
8
|
useImperativeHandle,
|
|
9
9
|
useMemo,
|
|
10
10
|
useRef,
|
|
11
11
|
} from 'react'
|
|
12
12
|
|
|
13
|
+
import type { AxisOptions, XY } from '@tremolo-ui/dom'
|
|
13
14
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
rawValue,
|
|
17
|
-
stepValue,
|
|
15
|
+
applyDelta,
|
|
16
|
+
linearScale,
|
|
18
17
|
toFixed,
|
|
19
18
|
InputEventOption,
|
|
20
|
-
styleHelper,
|
|
21
19
|
xor,
|
|
20
|
+
type Scale,
|
|
22
21
|
} from '@tremolo-ui/functions'
|
|
23
22
|
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
Cursor,
|
|
29
|
-
removeUserSelectNone,
|
|
30
|
-
resetCursorStyle,
|
|
31
|
-
setCursorStyle,
|
|
32
|
-
} from '../_util'
|
|
23
|
+
import { useDragValue } from '../../hooks/useDragValue'
|
|
24
|
+
import { useWheel } from '../../hooks/useWheel'
|
|
25
|
+
import { addUserSelectNone, Cursor, removeUserSelectNone } from '../_util'
|
|
26
|
+
import { useComposedRefs } from '../_util/composeRefs'
|
|
33
27
|
|
|
34
28
|
import { SliderProvider } from './context'
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import { Thumb, SliderThumbMethods
|
|
38
|
-
import { Track
|
|
29
|
+
import { Marks } from './Marks'
|
|
30
|
+
import { MarksOption } from './MarksOption'
|
|
31
|
+
import { Thumb, SliderThumbMethods } from './Thumb'
|
|
32
|
+
import { Track } from './Track'
|
|
39
33
|
|
|
40
34
|
const defaultExternalStyles: SliderProps['externalStyles'] = {
|
|
41
35
|
userSelectNone: true,
|
|
@@ -50,7 +44,16 @@ export interface SliderProps {
|
|
|
50
44
|
|
|
51
45
|
// optional
|
|
52
46
|
step?: number
|
|
53
|
-
|
|
47
|
+
/**
|
|
48
|
+
* How the value is distributed across the travel.
|
|
49
|
+
*
|
|
50
|
+
* Pick one of the scales from `@tremolo-ui/functions`: `linearScale`,
|
|
51
|
+
* `exponentialScale`, `curveScale(n)`, `symmetricSkewScale(n)`, or
|
|
52
|
+
* `skewScale(n)` for a value that has to match a JUCE parameter.
|
|
53
|
+
*
|
|
54
|
+
* @default linearScale
|
|
55
|
+
*/
|
|
56
|
+
scale?: Scale
|
|
54
57
|
/**
|
|
55
58
|
* slider orientation
|
|
56
59
|
* aria-orientation property is also applied.
|
|
@@ -90,9 +93,18 @@ export interface SliderProps {
|
|
|
90
93
|
onChange?: (value: number) => void
|
|
91
94
|
onDragStart?: (value: number) => void
|
|
92
95
|
onDragEnd?: (value: number) => void
|
|
93
|
-
/**
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
/**
|
|
97
|
+
* The slider renders exactly what you compose here; there is no default
|
|
98
|
+
* markup to fall back to.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* <Slider.Root value={value} min={0} max={100} onChange={setValue}>
|
|
102
|
+
* <Slider.Track>
|
|
103
|
+
* <Slider.Thumb />
|
|
104
|
+
* </Slider.Track>
|
|
105
|
+
* </Slider.Root>
|
|
106
|
+
*/
|
|
107
|
+
children: ReactNode
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
export interface SliderMethods {
|
|
@@ -103,14 +115,20 @@ export interface SliderMethods {
|
|
|
103
115
|
type Props = SliderProps &
|
|
104
116
|
Omit<ComponentPropsWithoutRef<'div'>, keyof SliderProps>
|
|
105
117
|
|
|
106
|
-
|
|
118
|
+
/**
|
|
119
|
+
* The wheel only acts while the focus is inside, so that scrolling a page past
|
|
120
|
+
* the control does not change its value.
|
|
121
|
+
*/
|
|
122
|
+
const WHEEL_OPTIONS = { requireFocus: true }
|
|
123
|
+
|
|
124
|
+
export const Root = forwardRef<SliderMethods, Props>(
|
|
107
125
|
(
|
|
108
126
|
{
|
|
109
127
|
value,
|
|
110
128
|
min,
|
|
111
129
|
max,
|
|
112
130
|
step = 1,
|
|
113
|
-
|
|
131
|
+
scale = linearScale,
|
|
114
132
|
vertical = false,
|
|
115
133
|
reverse = false,
|
|
116
134
|
externalStyles: _externalStyles,
|
|
@@ -133,13 +151,13 @@ const Root = forwardRef<SliderMethods, Props>(
|
|
|
133
151
|
forwardedRef,
|
|
134
152
|
) => {
|
|
135
153
|
// -- state and ref ---
|
|
136
|
-
const
|
|
154
|
+
const trackRef = useRef<HTMLDivElement>(null)
|
|
137
155
|
const thumbRef = useRef<SliderThumbMethods>(null)
|
|
138
156
|
|
|
139
157
|
// --- interpret props ---
|
|
140
158
|
const externalStyles = { ...defaultExternalStyles, ..._externalStyles }
|
|
141
159
|
|
|
142
|
-
const p = toFixed(
|
|
160
|
+
const p = toFixed(scale.normalize(value, min, max) * 100)
|
|
143
161
|
const rev = toFixed(100 - p)
|
|
144
162
|
// NOTE
|
|
145
163
|
// normal -> normal (right)
|
|
@@ -152,51 +170,13 @@ const Root = forwardRef<SliderMethods, Props>(
|
|
|
152
170
|
)
|
|
153
171
|
const percent = displayReversed ? rev : p
|
|
154
172
|
|
|
155
|
-
let trackProps: SliderTrackProps = {}
|
|
156
|
-
let thumbProps: SliderThumbProps = {}
|
|
157
|
-
let scaleComponent: ReactElement | undefined = undefined
|
|
158
|
-
if (children != undefined) {
|
|
159
|
-
React.Children.forEach(children, (child) => {
|
|
160
|
-
if (React.isValidElement(child)) {
|
|
161
|
-
if (child.type == Thumb) {
|
|
162
|
-
thumbProps = child.props as SliderThumbProps
|
|
163
|
-
} else if (child.type == Track) {
|
|
164
|
-
trackProps = child.props as SliderTrackProps
|
|
165
|
-
} else if (child.type == Scale) {
|
|
166
|
-
scaleComponent = child
|
|
167
|
-
} else {
|
|
168
|
-
throw new Error('only <SliderThumb> or <SliderTrack>')
|
|
169
|
-
}
|
|
170
|
-
} else {
|
|
171
|
-
throw new Error('children is an invalid element.')
|
|
172
|
-
}
|
|
173
|
-
})
|
|
174
|
-
}
|
|
175
|
-
|
|
176
173
|
// --- internal functions ---
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
onChange(v2)
|
|
184
|
-
},
|
|
185
|
-
[displayReversed, max, min, onChange, readonly, skew, step, vertical],
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
const updateValueByEvent = useCallback(
|
|
189
|
-
(eventType: InputEventOption[0], x: number) => {
|
|
190
|
-
let newValue
|
|
191
|
-
if (eventType == 'normalized') {
|
|
192
|
-
const n = normalizeValue(value, min, max, skew)
|
|
193
|
-
newValue = rawValue(n + x, min, max, skew)
|
|
194
|
-
} else {
|
|
195
|
-
newValue = value + x
|
|
196
|
-
}
|
|
197
|
-
return clamp(stepValue(newValue, step), min, max)
|
|
198
|
-
},
|
|
199
|
-
[max, min, skew, step, value],
|
|
174
|
+
// The pointer is normalized against the track on both axes; only the one
|
|
175
|
+
// the slider runs along is read back. `AxisOptions` extends `ValueRange`,
|
|
176
|
+
// so the same object also describes the scaling for `applyDelta`.
|
|
177
|
+
const axis: AxisOptions = useMemo(
|
|
178
|
+
() => ({ min, max, step, scale, reverse: displayReversed }),
|
|
179
|
+
[min, max, step, scale, displayReversed],
|
|
200
180
|
)
|
|
201
181
|
|
|
202
182
|
const handleKeyDown = useCallback(
|
|
@@ -205,69 +185,90 @@ const Root = forwardRef<SliderMethods, Props>(
|
|
|
205
185
|
const key = event.key
|
|
206
186
|
if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(key)) {
|
|
207
187
|
event.preventDefault()
|
|
208
|
-
let
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
onChange(updateValueByEvent(keyboard[0], x))
|
|
188
|
+
let direction = key == 'ArrowRight' || key == 'ArrowUp' ? 1 : -1
|
|
189
|
+
if (reverse) direction *= -1
|
|
190
|
+
onChange(applyDelta(value, direction, keyboard, axis))
|
|
212
191
|
}
|
|
213
192
|
},
|
|
214
|
-
[keyboard, onChange, readonly, reverse,
|
|
193
|
+
[keyboard, onChange, readonly, reverse, value, axis],
|
|
215
194
|
)
|
|
216
195
|
|
|
217
196
|
// --- hooks ---
|
|
218
|
-
const
|
|
219
|
-
useDragWithElement<HTMLDivElement>({
|
|
220
|
-
baseElementRef: trackElementRef,
|
|
221
|
-
onDrag: onDrag,
|
|
222
|
-
onDragStart: (nx, ny) => {
|
|
223
|
-
if (readonly) return
|
|
224
|
-
if (externalStyles.userSelectNone) addUserSelectNone()
|
|
225
|
-
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor)
|
|
197
|
+
const valueOf = (v: XY<number>) => v[vertical ? 1 : 0]
|
|
226
198
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
199
|
+
const { refCallback: dragRefCallback } = useDragValue<HTMLDivElement>({
|
|
200
|
+
axis,
|
|
201
|
+
baseElementRef: trackRef,
|
|
202
|
+
cursor: readonly ? undefined : externalStyles.cursor,
|
|
203
|
+
updateOnPointerDown: true,
|
|
204
|
+
onChange: (v) => {
|
|
205
|
+
if (readonly) return
|
|
206
|
+
onChange?.(valueOf(v))
|
|
207
|
+
},
|
|
208
|
+
onDragStart: (v) => {
|
|
209
|
+
if (readonly) return
|
|
210
|
+
if (externalStyles.userSelectNone) addUserSelectNone()
|
|
238
211
|
|
|
239
|
-
|
|
240
|
-
|
|
212
|
+
thumbRef.current?.focus()
|
|
213
|
+
onDragStart?.(valueOf(v))
|
|
214
|
+
},
|
|
215
|
+
onDragEnd: (v) => {
|
|
216
|
+
if (readonly) return
|
|
241
217
|
|
|
242
|
-
|
|
243
|
-
clamp(
|
|
244
|
-
stepValue(rawValue(vertical ? ny : nx, min, max, skew), step),
|
|
245
|
-
min,
|
|
246
|
-
max,
|
|
247
|
-
),
|
|
248
|
-
)
|
|
249
|
-
},
|
|
250
|
-
})
|
|
218
|
+
if (externalStyles.userSelectNone) removeUserSelectNone()
|
|
251
219
|
|
|
252
|
-
|
|
253
|
-
'wheel',
|
|
254
|
-
(event) => {
|
|
255
|
-
if (!wheel || !onChange || readonly) return
|
|
256
|
-
event.preventDefault()
|
|
257
|
-
let x
|
|
258
|
-
if (!vertical && event.deltaX != 0) {
|
|
259
|
-
x = event.deltaX > 0 ? wheel[1] : -wheel[1]
|
|
260
|
-
} else {
|
|
261
|
-
if (event.deltaY == 0) return
|
|
262
|
-
x = event.deltaY > 0 ? -wheel[1] : wheel[1]
|
|
263
|
-
}
|
|
264
|
-
if (vertical && reverse) x *= -1
|
|
265
|
-
onChange(updateValueByEvent(wheel[0], x))
|
|
220
|
+
onDragEnd?.(valueOf(v))
|
|
266
221
|
},
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
const wheelRefCallback = useWheel<HTMLDivElement>((event) => {
|
|
225
|
+
if (!wheel || !onChange || readonly) return
|
|
226
|
+
event.preventDefault()
|
|
227
|
+
let direction
|
|
228
|
+
if (!vertical && event.deltaX != 0) {
|
|
229
|
+
direction = event.deltaX > 0 ? 1 : -1
|
|
230
|
+
} else {
|
|
231
|
+
if (event.deltaY == 0) return
|
|
232
|
+
direction = event.deltaY > 0 ? -1 : 1
|
|
233
|
+
}
|
|
234
|
+
if (vertical && reverse) direction *= -1
|
|
235
|
+
onChange(applyDelta(value, direction, wheel, axis))
|
|
236
|
+
}, WHEEL_OPTIONS)
|
|
237
|
+
|
|
238
|
+
// Composed once, so React attaches the refs a single time instead of
|
|
239
|
+
// detaching and re-attaching on every render.
|
|
240
|
+
const rootRefCallback = useComposedRefs<HTMLDivElement>(
|
|
241
|
+
dragRefCallback,
|
|
242
|
+
wheelRefCallback,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
const context = useMemo(
|
|
246
|
+
() => ({
|
|
247
|
+
value,
|
|
248
|
+
min,
|
|
249
|
+
max,
|
|
250
|
+
step,
|
|
251
|
+
scale,
|
|
252
|
+
vertical,
|
|
253
|
+
reverse,
|
|
254
|
+
disabled,
|
|
255
|
+
readonly,
|
|
256
|
+
percent,
|
|
257
|
+
trackRef,
|
|
258
|
+
thumbRef,
|
|
259
|
+
}),
|
|
260
|
+
[
|
|
261
|
+
value,
|
|
262
|
+
min,
|
|
263
|
+
max,
|
|
264
|
+
step,
|
|
265
|
+
scale,
|
|
266
|
+
vertical,
|
|
267
|
+
reverse,
|
|
268
|
+
disabled,
|
|
269
|
+
readonly,
|
|
270
|
+
percent,
|
|
271
|
+
],
|
|
271
272
|
)
|
|
272
273
|
|
|
273
274
|
useImperativeHandle(forwardedRef, () => {
|
|
@@ -282,22 +283,10 @@ const Root = forwardRef<SliderMethods, Props>(
|
|
|
282
283
|
}, [])
|
|
283
284
|
|
|
284
285
|
return (
|
|
285
|
-
<SliderProvider
|
|
286
|
-
min={min}
|
|
287
|
-
max={max}
|
|
288
|
-
step={step}
|
|
289
|
-
skew={skew}
|
|
290
|
-
vertical={vertical}
|
|
291
|
-
reverse={reverse}
|
|
292
|
-
disabled={disabled}
|
|
293
|
-
readonly={readonly}
|
|
294
|
-
>
|
|
286
|
+
<SliderProvider value={context}>
|
|
295
287
|
<div
|
|
296
288
|
className={clsx('tremolo-slider', className)}
|
|
297
|
-
ref={
|
|
298
|
-
wheelRefCallback(div)
|
|
299
|
-
touchMoveRefCallback(div)
|
|
300
|
-
}}
|
|
289
|
+
ref={rootRefCallback}
|
|
301
290
|
tabIndex={-1}
|
|
302
291
|
role="slider"
|
|
303
292
|
aria-valuenow={value}
|
|
@@ -306,14 +295,9 @@ const Root = forwardRef<SliderMethods, Props>(
|
|
|
306
295
|
aria-orientation={vertical ? 'vertical' : 'horizontal'}
|
|
307
296
|
aria-disabled={disabled}
|
|
308
297
|
aria-readonly={readonly}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}}
|
|
313
|
-
onPointerDown={(event) => {
|
|
314
|
-
pointerDownHandler(event)
|
|
315
|
-
onPointerDown?.(event)
|
|
316
|
-
}}
|
|
298
|
+
data-vertical={vertical}
|
|
299
|
+
style={style}
|
|
300
|
+
onPointerDown={onPointerDown}
|
|
317
301
|
onKeyDown={(event) => {
|
|
318
302
|
handleKeyDown(event)
|
|
319
303
|
onKeyDown?.(event)
|
|
@@ -328,25 +312,7 @@ const Root = forwardRef<SliderMethods, Props>(
|
|
|
328
312
|
}}
|
|
329
313
|
{...props}
|
|
330
314
|
>
|
|
331
|
-
|
|
332
|
-
style={{
|
|
333
|
-
display: 'flex',
|
|
334
|
-
flexDirection: vertical ? 'row' : 'column',
|
|
335
|
-
width: !vertical ? '100%' : undefined,
|
|
336
|
-
height: vertical ? '100%' : undefined,
|
|
337
|
-
}}
|
|
338
|
-
>
|
|
339
|
-
<div className="tremolo-slider-track-wrapper" ref={trackElementRef}>
|
|
340
|
-
<Track
|
|
341
|
-
__percent={percent}
|
|
342
|
-
__thumb={
|
|
343
|
-
<Thumb ref={thumbRef} __percent={percent} {...thumbProps} />
|
|
344
|
-
}
|
|
345
|
-
{...trackProps}
|
|
346
|
-
/>
|
|
347
|
-
</div>
|
|
348
|
-
{scaleComponent}
|
|
349
|
-
</div>
|
|
315
|
+
{children}
|
|
350
316
|
</div>
|
|
351
317
|
</SliderProvider>
|
|
352
318
|
)
|
|
@@ -360,13 +326,13 @@ export const Slider = {
|
|
|
360
326
|
Root,
|
|
361
327
|
Thumb,
|
|
362
328
|
Track,
|
|
363
|
-
|
|
364
|
-
|
|
329
|
+
Marks,
|
|
330
|
+
MarksOption,
|
|
365
331
|
}
|
|
366
332
|
|
|
367
333
|
export { useSliderContext } from './context'
|
|
368
334
|
export { type SliderThumbMethods, type SliderThumbProps } from './Thumb'
|
|
369
335
|
export { type SliderTrackProps } from './Track'
|
|
370
|
-
export { type
|
|
371
|
-
export { type
|
|
372
|
-
export { type
|
|
336
|
+
export { type MarksProps } from './Marks'
|
|
337
|
+
export { type MarksOptionProps } from './MarksOption'
|
|
338
|
+
export { type MarksOptions, type MarksType } from './type'
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { decimalPart, toFixed } from '@tremolo-ui/functions'
|
|
2
2
|
|
|
3
|
-
export type
|
|
4
|
-
export type
|
|
3
|
+
export type MarksType = 'mark' | 'mark-number' | 'number'
|
|
4
|
+
export type MarksOptions = ['step', MarksType] | [number, MarksType]
|
|
5
5
|
|
|
6
6
|
export function generateOptionsList(
|
|
7
|
-
options:
|
|
7
|
+
options: MarksOptions,
|
|
8
8
|
min: number,
|
|
9
9
|
max: number,
|
|
10
10
|
step: number,
|
|
11
11
|
) {
|
|
12
12
|
const optionsList: {
|
|
13
13
|
value: number
|
|
14
|
-
type:
|
|
14
|
+
type: MarksType
|
|
15
15
|
}[] = []
|
|
16
16
|
const per = options[0] == 'step' ? step : options[0]
|
|
17
17
|
const count = Math.floor(max / per) - Math.ceil(min / per) + 1
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
|
-
import { ComponentPropsWithoutRef, CSSProperties,
|
|
2
|
+
import { ComponentPropsWithoutRef, CSSProperties, ReactNode, Ref } from 'react'
|
|
3
|
+
|
|
4
|
+
import { useComposedRefs } from '../_util/composeRefs'
|
|
5
|
+
|
|
6
|
+
import { useXYPadContext } from './context'
|
|
3
7
|
|
|
4
8
|
export interface XYPadAreaProps {
|
|
5
9
|
width?: number | string
|
|
@@ -7,10 +11,9 @@ export interface XYPadAreaProps {
|
|
|
7
11
|
color?: string
|
|
8
12
|
className?: string
|
|
9
13
|
style?: CSSProperties
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
__thumb?: ReactElement
|
|
14
|
+
/** `<XYPad.Thumb />` goes here. */
|
|
15
|
+
children?: ReactNode
|
|
16
|
+
ref?: Ref<HTMLDivElement>
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
export function Area({
|
|
@@ -20,12 +23,19 @@ export function Area({
|
|
|
20
23
|
children,
|
|
21
24
|
className,
|
|
22
25
|
style,
|
|
23
|
-
|
|
26
|
+
ref,
|
|
24
27
|
...props
|
|
25
28
|
}: XYPadAreaProps &
|
|
26
29
|
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadAreaProps>) {
|
|
30
|
+
const { areaRef } = useXYPadContext()
|
|
31
|
+
|
|
32
|
+
// The area is what the pointer position is normalized against, so the
|
|
33
|
+
// context ref is composed with any ref the caller passed.
|
|
34
|
+
const composedRef = useComposedRefs<HTMLDivElement>(ref, areaRef)
|
|
35
|
+
|
|
27
36
|
return (
|
|
28
37
|
<div
|
|
38
|
+
ref={composedRef}
|
|
29
39
|
className={clsx('tremolo-xy-pad-area', className)}
|
|
30
40
|
style={{
|
|
31
41
|
...{ '--color': color },
|
|
@@ -36,7 +46,6 @@ export function Area({
|
|
|
36
46
|
{...props}
|
|
37
47
|
>
|
|
38
48
|
{children}
|
|
39
|
-
{__thumb}
|
|
40
49
|
</div>
|
|
41
50
|
)
|
|
42
51
|
}
|
|
@@ -2,30 +2,26 @@ import clsx from 'clsx'
|
|
|
2
2
|
import {
|
|
3
3
|
ComponentPropsWithoutRef,
|
|
4
4
|
CSSProperties,
|
|
5
|
-
forwardRef,
|
|
6
5
|
ReactNode,
|
|
6
|
+
Ref,
|
|
7
7
|
useImperativeHandle,
|
|
8
8
|
useRef,
|
|
9
9
|
} from 'react'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
size?: number | string
|
|
13
|
-
width?: number | string
|
|
14
|
-
height?: number | string
|
|
11
|
+
import { useXYPadContext } from './context'
|
|
15
12
|
|
|
13
|
+
export interface XYPadThumbProps {
|
|
14
|
+
/**
|
|
15
|
+
* Size comes from the `--thumb-size` CSS variable on `XYPad.Root`, so that
|
|
16
|
+
* the root can reserve the matching amount of space around the area.
|
|
17
|
+
*/
|
|
16
18
|
color?: string
|
|
17
19
|
|
|
18
20
|
wrapperClassName?: string
|
|
19
21
|
wrapperStyle?: CSSProperties
|
|
20
22
|
|
|
21
23
|
children?: ReactNode
|
|
22
|
-
|
|
23
|
-
/** @internal */
|
|
24
|
-
__disabled?: boolean
|
|
25
|
-
/** @internal */
|
|
26
|
-
__readonly?: boolean
|
|
27
|
-
/** @internal */
|
|
28
|
-
__css?: CSSProperties
|
|
24
|
+
ref?: Ref<XYPadThumbMethods>
|
|
29
25
|
}
|
|
30
26
|
|
|
31
27
|
export interface XYPadThumbMethods {
|
|
@@ -36,67 +32,59 @@ export interface XYPadThumbMethods {
|
|
|
36
32
|
type Props = XYPadThumbProps &
|
|
37
33
|
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadThumbProps>
|
|
38
34
|
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
__disabled,
|
|
52
|
-
__readonly,
|
|
53
|
-
__css,
|
|
54
|
-
...props
|
|
55
|
-
}: Props,
|
|
56
|
-
ref,
|
|
57
|
-
) => {
|
|
58
|
-
const wrapperRef = useRef<HTMLDivElement>(null)
|
|
35
|
+
export function Thumb({
|
|
36
|
+
color,
|
|
37
|
+
children,
|
|
38
|
+
wrapperClassName,
|
|
39
|
+
wrapperStyle,
|
|
40
|
+
className,
|
|
41
|
+
style,
|
|
42
|
+
ref,
|
|
43
|
+
...props
|
|
44
|
+
}: Props) {
|
|
45
|
+
const elementRef = useRef<HTMLDivElement>(null)
|
|
46
|
+
const { disabled, readonly, percent, thumbRef } = useXYPadContext()
|
|
59
47
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
}, [])
|
|
48
|
+
const methods = () => ({
|
|
49
|
+
focus() {
|
|
50
|
+
elementRef.current?.focus()
|
|
51
|
+
},
|
|
52
|
+
blur() {
|
|
53
|
+
elementRef.current?.blur()
|
|
54
|
+
},
|
|
55
|
+
})
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
57
|
+
useImperativeHandle(ref, methods, [])
|
|
58
|
+
// Root focuses the thumb when a drag starts, wherever the user placed it.
|
|
59
|
+
useImperativeHandle(thumbRef, methods, [])
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
className={clsx('tremolo-xy-pad-thumb-wrapper', wrapperClassName)}
|
|
64
|
+
style={{
|
|
65
|
+
left: `${percent[0]}%`,
|
|
66
|
+
top: `${percent[1]}%`,
|
|
67
|
+
...wrapperStyle,
|
|
68
|
+
}}
|
|
69
|
+
{...props}
|
|
70
|
+
>
|
|
71
|
+
{children ? (
|
|
72
|
+
children
|
|
73
|
+
) : (
|
|
74
|
+
// default thumb
|
|
75
|
+
<div
|
|
76
|
+
ref={elementRef}
|
|
77
|
+
className={clsx('tremolo-xy-pad-thumb', className)}
|
|
78
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
79
|
+
tabIndex={0}
|
|
80
|
+
aria-disabled={disabled}
|
|
81
|
+
aria-readonly={readonly}
|
|
82
|
+
style={{
|
|
83
|
+
...{ '--color': color },
|
|
84
|
+
...style,
|
|
85
|
+
}}
|
|
86
|
+
></div>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
)
|
|
90
|
+
}
|