@tremolo-ui/react 0.1.6 → 0.2.1
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 +1329 -1152
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +238 -183
- package/dist/index.d.cts +367 -366
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +367 -366
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1313 -1125
- package/dist/index.js.map +1 -1
- package/package.json +17 -19
- package/src/components/AnimationCanvas/index.tsx +0 -7
- package/src/components/DragObserver/index.tsx +9 -7
- package/src/components/Knob/ActiveLine.tsx +29 -0
- package/src/components/Knob/InactiveLine.tsx +53 -0
- package/src/components/Knob/SVGRoot.tsx +39 -0
- package/src/components/Knob/Thumb.tsx +64 -0
- package/src/components/Knob/context.tsx +120 -0
- package/src/components/Knob/index.css +25 -27
- package/src/components/Knob/index.tsx +110 -164
- package/src/components/NumberInput/DecrementStepper.tsx +0 -2
- package/src/components/NumberInput/IncrementStepper.tsx +0 -2
- package/src/components/NumberInput/InternalInput.tsx +3 -1
- package/src/components/NumberInput/Stepper.tsx +0 -2
- package/src/components/NumberInput/context.tsx +0 -1
- package/src/components/NumberInput/index.css +31 -15
- package/src/components/NumberInput/index.tsx +21 -14
- package/src/components/Piano/KeyLabel.tsx +0 -2
- package/src/components/Piano/context.tsx +0 -2
- package/src/components/Piano/index.css +7 -14
- package/src/components/Piano/index.tsx +14 -15
- package/src/components/Piano/key.tsx +0 -10
- package/src/components/Piano/keyboardShortcuts.ts +0 -2
- package/src/components/PointsEditor/Background.tsx +17 -0
- package/src/components/PointsEditor/Container.tsx +26 -0
- package/src/components/PointsEditor/Point.tsx +137 -0
- package/src/components/PointsEditor/context.tsx +91 -0
- package/src/components/PointsEditor/index.css +30 -0
- package/src/components/PointsEditor/index.tsx +129 -0
- package/src/components/Slider/Scale.tsx +0 -2
- package/src/components/Slider/ScaleOption.tsx +0 -2
- package/src/components/Slider/Thumb.tsx +1 -5
- package/src/components/Slider/Track.tsx +1 -3
- package/src/components/Slider/context.tsx +0 -1
- package/src/components/Slider/index.css +33 -8
- package/src/components/Slider/index.tsx +61 -33
- package/src/components/WheelObserver/index.tsx +17 -19
- package/src/components/XYPad/Area.tsx +4 -5
- package/src/components/XYPad/Thumb.tsx +8 -7
- package/src/components/XYPad/index.css +18 -4
- package/src/components/XYPad/index.tsx +71 -38
- package/src/components/_util/composeRefs.tsx +63 -0
- package/src/components/_util/index.ts +23 -6
- package/src/components/_util/type.ts +1 -0
- package/src/hooks/useAnimationFrame.ts +0 -3
- package/src/hooks/useCallbackRef.ts +2 -2
- package/src/hooks/useDrag.ts +12 -11
- package/src/hooks/useDragWithElement.ts +12 -17
- package/src/hooks/useEventListener.ts +6 -9
- package/src/hooks/useInterval.ts +0 -3
- package/src/hooks/useLongPress.ts +0 -3
- package/src/hooks/useMIDIAccess.ts +0 -1
- package/src/hooks/useMIDIInput.ts +0 -1
- package/src/hooks/useMIDIMessage.ts +0 -1
- package/src/hooks/usePianoDrag.ts +75 -0
- package/src/index.ts +21 -30
- package/src/styles/global.css +16 -4
- package/dist/index.css.map +0 -1
- package/src/components/AnimationKnob/index.tsx +0 -314
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
.tremolo-slider-thumb {
|
|
19
|
-
--color:
|
|
19
|
+
--color: oklch(59.5% 0.17548 266.373);
|
|
20
20
|
|
|
21
21
|
background: var(--color);
|
|
22
22
|
border-radius: 50%;
|
|
23
23
|
outline: none;
|
|
24
24
|
|
|
25
25
|
&[aria-disabled='true'] {
|
|
26
|
-
background:
|
|
26
|
+
background: oklch(50.462% 0.03307 270.021);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
&[aria-readonly='false']:focus {
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
.tremolo-slider-track {
|
|
36
|
-
--active:
|
|
37
|
-
--inactive:
|
|
36
|
+
--active: oklch(69.212% 0.12903 267.633);
|
|
37
|
+
--inactive: oklch(95% 0 0);
|
|
38
38
|
|
|
39
39
|
position: relative;
|
|
40
40
|
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
&:hover {
|
|
50
|
-
--active:
|
|
50
|
+
--active: oklch(64.276% 0.15244 266.932);
|
|
51
51
|
--inactive: #e0e0e0;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
&[aria-disabled='true'] {
|
|
55
|
-
--active:
|
|
56
|
-
--inactive:
|
|
55
|
+
--active: oklch(75.327% 0.01237 264.547);
|
|
56
|
+
--inactive: oklch(89.755% 0.0001 271.152);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
|
|
65
65
|
.tremolo-slider-scale-option {
|
|
66
66
|
display: flex;
|
|
67
|
-
color:
|
|
67
|
+
color: oklch(25.197% 0.00003 271.152);
|
|
68
68
|
flex-direction: column;
|
|
69
69
|
justify-content: center;
|
|
70
70
|
align-items: center;
|
|
@@ -85,3 +85,28 @@
|
|
|
85
85
|
.tremolo-slider-scale-option-label {
|
|
86
86
|
text-align: right;
|
|
87
87
|
}
|
|
88
|
+
|
|
89
|
+
:where(.dark, [data-theme='dark']) {
|
|
90
|
+
.tremolo-slider-thumb {
|
|
91
|
+
--color: oklch(64.5% 0.17548 266.373);
|
|
92
|
+
|
|
93
|
+
&:hover {
|
|
94
|
+
--color: oklch(67.5% 0.17548 266.373);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.tremolo-slider-track {
|
|
99
|
+
--active: oklch(59.5% 0.17548 266.373);
|
|
100
|
+
--inactive: oklch(41.7% 0 0);
|
|
101
|
+
|
|
102
|
+
&:hover {
|
|
103
|
+
--active: oklch(62.5% 0.17548 266.373);
|
|
104
|
+
--inactive: oklch(44.7% 0 0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
&[aria-disabled='true'] {
|
|
108
|
+
--active: oklch(75.327% 0.01237 264.547);
|
|
109
|
+
--inactive: oklch(89.755% 0.0001 271.152);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -23,14 +23,25 @@ import {
|
|
|
23
23
|
|
|
24
24
|
import { useDragWithElement } from '../../hooks/useDragWithElement'
|
|
25
25
|
import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
addUserSelectNone,
|
|
28
|
+
Cursor,
|
|
29
|
+
removeUserSelectNone,
|
|
30
|
+
resetCursorStyle,
|
|
31
|
+
setCursorStyle,
|
|
32
|
+
} from '../_util'
|
|
27
33
|
|
|
28
34
|
import { SliderProvider } from './context'
|
|
29
35
|
import { Scale } from './Scale'
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
36
|
+
import { ScaleOption } from './ScaleOption'
|
|
37
|
+
import { Thumb, SliderThumbMethods, SliderThumbProps } from './Thumb'
|
|
38
|
+
import { Track, SliderTrackProps } from './Track'
|
|
39
|
+
|
|
40
|
+
const defaultExternalStyles: SliderProps['externalStyles'] = {
|
|
41
|
+
userSelectNone: true,
|
|
42
|
+
cursor: 'pointer',
|
|
43
|
+
}
|
|
32
44
|
|
|
33
|
-
/** @category Slider */
|
|
34
45
|
export interface SliderProps {
|
|
35
46
|
// required
|
|
36
47
|
value: number
|
|
@@ -40,24 +51,38 @@ export interface SliderProps {
|
|
|
40
51
|
// optional
|
|
41
52
|
step?: number
|
|
42
53
|
skew?: number // TODO | SkewFunction
|
|
54
|
+
/**
|
|
55
|
+
* slider orientation
|
|
56
|
+
* aria-orientation property is also applied.
|
|
57
|
+
*/
|
|
43
58
|
vertical?: boolean
|
|
44
59
|
reverse?: boolean
|
|
45
|
-
|
|
60
|
+
|
|
61
|
+
/** Global style to apply when dragged */
|
|
62
|
+
externalStyles?: {
|
|
63
|
+
userSelectNone?: boolean
|
|
64
|
+
cursor?: Cursor
|
|
65
|
+
}
|
|
46
66
|
/**
|
|
47
67
|
* wheel control option
|
|
68
|
+
* If null, no event will be triggered
|
|
48
69
|
*/
|
|
49
70
|
wheel?: InputEventOption | null
|
|
50
71
|
/**
|
|
51
72
|
* keyboard control option
|
|
73
|
+
* If null, no event will be triggered
|
|
52
74
|
*/
|
|
53
75
|
keyboard?: InputEventOption | null
|
|
76
|
+
|
|
54
77
|
/**
|
|
55
78
|
* Only the appearance will change.
|
|
56
79
|
* Please consider using with readonly.
|
|
80
|
+
* aria-disabled property is also applied.
|
|
57
81
|
*/
|
|
58
82
|
disabled?: boolean
|
|
59
83
|
/**
|
|
60
84
|
* Make the value unchangeable.
|
|
85
|
+
* aria-readonly property is also applied.
|
|
61
86
|
*/
|
|
62
87
|
readonly?: boolean
|
|
63
88
|
className?: string
|
|
@@ -70,9 +95,6 @@ export interface SliderProps {
|
|
|
70
95
|
// ReactElement<typeof SliderThumb, typeof SliderTrack>[]
|
|
71
96
|
}
|
|
72
97
|
|
|
73
|
-
/**
|
|
74
|
-
* @category Slider
|
|
75
|
-
*/
|
|
76
98
|
export interface SliderMethods {
|
|
77
99
|
focus: () => void
|
|
78
100
|
blur: () => void
|
|
@@ -81,11 +103,7 @@ export interface SliderMethods {
|
|
|
81
103
|
type Props = SliderProps &
|
|
82
104
|
Omit<ComponentPropsWithoutRef<'div'>, keyof SliderProps>
|
|
83
105
|
|
|
84
|
-
|
|
85
|
-
* Customizable slider
|
|
86
|
-
* @category Slider
|
|
87
|
-
*/
|
|
88
|
-
export const Slider = forwardRef<SliderMethods, Props>(
|
|
106
|
+
const Root = forwardRef<SliderMethods, Props>(
|
|
89
107
|
(
|
|
90
108
|
{
|
|
91
109
|
value,
|
|
@@ -95,7 +113,7 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
95
113
|
skew = 1,
|
|
96
114
|
vertical = false,
|
|
97
115
|
reverse = false,
|
|
98
|
-
|
|
116
|
+
externalStyles: _externalStyles,
|
|
99
117
|
wheel = ['raw', 1],
|
|
100
118
|
keyboard = ['raw', 1],
|
|
101
119
|
disabled = false,
|
|
@@ -119,6 +137,8 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
119
137
|
const thumbRef = useRef<SliderThumbMethods>(null)
|
|
120
138
|
|
|
121
139
|
// --- interpret props ---
|
|
140
|
+
const externalStyles = { ...defaultExternalStyles, ..._externalStyles }
|
|
141
|
+
|
|
122
142
|
const p = toFixed(normalizeValue(value, min, max, skew) * 100)
|
|
123
143
|
const rev = toFixed(100 - p)
|
|
124
144
|
// NOTE
|
|
@@ -138,9 +158,9 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
138
158
|
if (children != undefined) {
|
|
139
159
|
React.Children.forEach(children, (child) => {
|
|
140
160
|
if (React.isValidElement(child)) {
|
|
141
|
-
if (child.type ==
|
|
161
|
+
if (child.type == Thumb) {
|
|
142
162
|
thumbProps = child.props as SliderThumbProps
|
|
143
|
-
} else if (child.type ==
|
|
163
|
+
} else if (child.type == Track) {
|
|
144
164
|
trackProps = child.props as SliderTrackProps
|
|
145
165
|
} else if (child.type == Scale) {
|
|
146
166
|
scaleComponent = child
|
|
@@ -195,13 +215,15 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
195
215
|
)
|
|
196
216
|
|
|
197
217
|
// --- hooks ---
|
|
198
|
-
const
|
|
218
|
+
const { refHandler: touchMoveRefCallback, pointerDownHandler } =
|
|
199
219
|
useDragWithElement<HTMLDivElement>({
|
|
200
220
|
baseElementRef: trackElementRef,
|
|
201
221
|
onDrag: onDrag,
|
|
202
222
|
onDragStart: (nx, ny) => {
|
|
203
223
|
if (readonly) return
|
|
204
|
-
if (
|
|
224
|
+
if (externalStyles.userSelectNone) addUserSelectNone()
|
|
225
|
+
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor)
|
|
226
|
+
|
|
205
227
|
thumbRef.current?.focus()
|
|
206
228
|
onDragStart?.(
|
|
207
229
|
clamp(
|
|
@@ -213,7 +235,10 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
213
235
|
},
|
|
214
236
|
onDragEnd: (nx, ny) => {
|
|
215
237
|
if (readonly) return
|
|
216
|
-
|
|
238
|
+
|
|
239
|
+
if (externalStyles.userSelectNone) removeUserSelectNone()
|
|
240
|
+
if (externalStyles.cursor) resetCursorStyle()
|
|
241
|
+
|
|
217
242
|
onDragEnd?.(
|
|
218
243
|
clamp(
|
|
219
244
|
stepValue(rawValue(vertical ? ny : nx, min, max, skew), step),
|
|
@@ -312,14 +337,10 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
312
337
|
}}
|
|
313
338
|
>
|
|
314
339
|
<div className="tremolo-slider-track-wrapper" ref={trackElementRef}>
|
|
315
|
-
<
|
|
340
|
+
<Track
|
|
316
341
|
__percent={percent}
|
|
317
342
|
__thumb={
|
|
318
|
-
<
|
|
319
|
-
ref={thumbRef}
|
|
320
|
-
__percent={percent}
|
|
321
|
-
{...thumbProps}
|
|
322
|
-
/>
|
|
343
|
+
<Thumb ref={thumbRef} __percent={percent} {...thumbProps} />
|
|
323
344
|
}
|
|
324
345
|
{...trackProps}
|
|
325
346
|
/>
|
|
@@ -332,13 +353,20 @@ export const Slider = forwardRef<SliderMethods, Props>(
|
|
|
332
353
|
},
|
|
333
354
|
)
|
|
334
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Customizable slider
|
|
358
|
+
*/
|
|
359
|
+
export const Slider = {
|
|
360
|
+
Root,
|
|
361
|
+
Thumb,
|
|
362
|
+
Track,
|
|
363
|
+
Scale,
|
|
364
|
+
ScaleOption,
|
|
365
|
+
}
|
|
366
|
+
|
|
335
367
|
export { useSliderContext } from './context'
|
|
336
|
-
export {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
} from './Thumb'
|
|
341
|
-
export { SliderTrack, type SliderTrackProps } from './Track'
|
|
342
|
-
export { Scale, type ScaleProps } from './Scale'
|
|
343
|
-
export { ScaleOption, type ScaleOptionProps } from './ScaleOption'
|
|
368
|
+
export { type SliderThumbMethods, type SliderThumbProps } from './Thumb'
|
|
369
|
+
export { type SliderTrackProps } from './Track'
|
|
370
|
+
export { type ScaleProps } from './Scale'
|
|
371
|
+
export { type ScaleOptionProps } from './ScaleOption'
|
|
344
372
|
export { type ScaleOptions, type ScaleType } from './type'
|
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ComponentProps, ElementType, ReactNode } from 'react'
|
|
2
2
|
|
|
3
3
|
import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
|
|
4
|
+
import { composeRefs } from '../_util/composeRefs'
|
|
5
|
+
import { Override } from '../_util/type'
|
|
4
6
|
|
|
5
|
-
/**
|
|
6
|
-
* @example
|
|
7
|
-
* <WheelObserver
|
|
8
|
-
* onWheel={(event) => {
|
|
9
|
-
* event.preventDefault()
|
|
10
|
-
* console.log(event.deltaY)
|
|
11
|
-
* }
|
|
12
|
-
* >
|
|
13
|
-
* <div>Wheel here</div>
|
|
14
|
-
* </WheelObserver>
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/** @category WheelObserver */
|
|
18
7
|
export interface WheelObserverProps<T extends ElementType> {
|
|
19
8
|
/**
|
|
20
9
|
* React.ElementType
|
|
@@ -26,12 +15,21 @@ export interface WheelObserverProps<T extends ElementType> {
|
|
|
26
15
|
onWheel?: (event: WheelEvent) => void
|
|
27
16
|
}
|
|
28
17
|
|
|
29
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* @example
|
|
20
|
+
* <WheelObserver
|
|
21
|
+
* onWheel={(event) => {
|
|
22
|
+
* event.preventDefault()
|
|
23
|
+
* console.log(event.deltaY)
|
|
24
|
+
* }
|
|
25
|
+
* >
|
|
26
|
+
* <div>Wheel here</div>
|
|
27
|
+
* </WheelObserver>
|
|
28
|
+
*/
|
|
30
29
|
export function WheelObserver<T extends ElementType = 'div'>(
|
|
31
|
-
props: WheelObserverProps<T
|
|
32
|
-
Omit<ComponentPropsWithoutRef<T>, keyof WheelObserverProps<T>>,
|
|
30
|
+
props: Override<WheelObserverProps<T>, ComponentProps<T>>,
|
|
33
31
|
) {
|
|
34
|
-
const {
|
|
32
|
+
const { as: Component = 'div', children, onWheel, ref, ...attributes } = props
|
|
35
33
|
|
|
36
34
|
const wheelRefCallback = useRefCallbackEvent(
|
|
37
35
|
'wheel',
|
|
@@ -43,7 +41,7 @@ export function WheelObserver<T extends ElementType = 'div'>(
|
|
|
43
41
|
)
|
|
44
42
|
|
|
45
43
|
return (
|
|
46
|
-
<Component ref={wheelRefCallback} {...attributes}>
|
|
44
|
+
<Component ref={composeRefs(ref, wheelRefCallback)} {...attributes}>
|
|
47
45
|
{children}
|
|
48
46
|
</Component>
|
|
49
47
|
)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
2
|
import { ComponentPropsWithoutRef, CSSProperties, ReactElement } from 'react'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
export interface AreaProps {
|
|
4
|
+
export interface XYPadAreaProps {
|
|
6
5
|
width?: number | string
|
|
7
6
|
height?: number | string
|
|
8
7
|
color?: string
|
|
@@ -14,8 +13,7 @@ export interface AreaProps {
|
|
|
14
13
|
__thumb?: ReactElement
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
export function XYPadArea({
|
|
16
|
+
export function Area({
|
|
19
17
|
width = 120,
|
|
20
18
|
height = 120,
|
|
21
19
|
color,
|
|
@@ -24,7 +22,8 @@ export function XYPadArea({
|
|
|
24
22
|
style,
|
|
25
23
|
__thumb,
|
|
26
24
|
...props
|
|
27
|
-
}:
|
|
25
|
+
}: XYPadAreaProps &
|
|
26
|
+
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadAreaProps>) {
|
|
28
27
|
return (
|
|
29
28
|
<div
|
|
30
29
|
className={clsx('tremolo-xy-pad-area', className)}
|
|
@@ -8,8 +8,7 @@ import {
|
|
|
8
8
|
useRef,
|
|
9
9
|
} from 'react'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
export interface ThumbProps {
|
|
11
|
+
export interface XYPadThumbProps {
|
|
13
12
|
size?: number | string
|
|
14
13
|
width?: number | string
|
|
15
14
|
height?: number | string
|
|
@@ -24,20 +23,20 @@ export interface ThumbProps {
|
|
|
24
23
|
/** @internal */
|
|
25
24
|
__disabled?: boolean
|
|
26
25
|
/** @internal */
|
|
26
|
+
__readonly?: boolean
|
|
27
|
+
/** @internal */
|
|
27
28
|
__css?: CSSProperties
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
/** @category XYPad */
|
|
31
31
|
export interface XYPadThumbMethods {
|
|
32
32
|
focus: () => void
|
|
33
33
|
blur: () => void
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
type Props =
|
|
37
|
-
Omit<ComponentPropsWithoutRef<'div'>, keyof
|
|
36
|
+
type Props = XYPadThumbProps &
|
|
37
|
+
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadThumbProps>
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
export const XYPadThumb = forwardRef<XYPadThumbMethods, Props>(
|
|
39
|
+
export const Thumb = forwardRef<XYPadThumbMethods, Props>(
|
|
41
40
|
(
|
|
42
41
|
{
|
|
43
42
|
size,
|
|
@@ -50,6 +49,7 @@ export const XYPadThumb = forwardRef<XYPadThumbMethods, Props>(
|
|
|
50
49
|
className,
|
|
51
50
|
style,
|
|
52
51
|
__disabled,
|
|
52
|
+
__readonly,
|
|
53
53
|
__css,
|
|
54
54
|
...props
|
|
55
55
|
}: Props,
|
|
@@ -87,6 +87,7 @@ export const XYPadThumb = forwardRef<XYPadThumbMethods, Props>(
|
|
|
87
87
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
88
88
|
tabIndex={0}
|
|
89
89
|
aria-disabled={__disabled}
|
|
90
|
+
aria-readonly={__readonly}
|
|
90
91
|
style={{
|
|
91
92
|
...{ '--color': color },
|
|
92
93
|
width: size ?? width,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
.tremolo-xy-pad-area {
|
|
7
|
-
--color:
|
|
7
|
+
--color: oklch(95% 0 0);
|
|
8
8
|
|
|
9
9
|
position: relative;
|
|
10
10
|
background-color: var(--color);
|
|
@@ -17,18 +17,32 @@
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
.tremolo-xy-pad-thumb {
|
|
20
|
-
--color:
|
|
20
|
+
--color: oklch(59.5% 0.17548 266.373);
|
|
21
21
|
|
|
22
22
|
border-radius: 50%;
|
|
23
23
|
outline: none;
|
|
24
24
|
background-color: var(--color);
|
|
25
25
|
|
|
26
26
|
&[aria-disabled='true'] {
|
|
27
|
-
background-color:
|
|
27
|
+
background-color: oklch(50.462% 0.03307 270.021);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
&[aria-readonly='false']:focus {
|
|
31
31
|
box-shadow: 0px 0px 0px 3px
|
|
32
32
|
color-mix(in srgb, var(--color) 20%, transparent);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
:where(.dark, [data-theme='dark']) {
|
|
37
|
+
.tremolo-xy-pad-area {
|
|
38
|
+
--color: oklch(41.7% 0 0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.tremolo-xy-pad-thumb {
|
|
42
|
+
--color: oklch(64.5% 0.17548 266.373);
|
|
43
|
+
|
|
44
|
+
&:hover {
|
|
45
|
+
--color: oklch(67.5% 0.17548 266.373);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -3,6 +3,7 @@ import React, {
|
|
|
3
3
|
ComponentPropsWithoutRef,
|
|
4
4
|
forwardRef,
|
|
5
5
|
ReactElement,
|
|
6
|
+
Ref,
|
|
6
7
|
useCallback,
|
|
7
8
|
useImperativeHandle,
|
|
8
9
|
useMemo,
|
|
@@ -21,14 +22,33 @@ import {
|
|
|
21
22
|
|
|
22
23
|
import { useDragWithElement } from '../../hooks/useDragWithElement'
|
|
23
24
|
import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
|
|
24
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
addUserSelectNone,
|
|
27
|
+
Cursor,
|
|
28
|
+
removeUserSelectNone,
|
|
29
|
+
resetCursorStyle,
|
|
30
|
+
setCursorStyle,
|
|
31
|
+
} from '../_util'
|
|
32
|
+
import { composeRefs } from '../_util/composeRefs'
|
|
33
|
+
|
|
34
|
+
import { Area, XYPadAreaProps } from './Area'
|
|
35
|
+
import { Thumb, XYPadThumbMethods, XYPadThumbProps } from './Thumb'
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
const defaultValueOptions = {
|
|
38
|
+
skew: 1,
|
|
39
|
+
step: 1,
|
|
40
|
+
reverse: false,
|
|
41
|
+
wheel: ['raw', 1] as InputEventOption,
|
|
42
|
+
keyboard: ['raw', 1] as InputEventOption,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const defaultExternalStyles: XYPadProps['externalStyles'] = {
|
|
46
|
+
userSelectNone: true,
|
|
47
|
+
cursor: 'pointer',
|
|
48
|
+
}
|
|
28
49
|
|
|
29
50
|
/**
|
|
30
51
|
* Two-dimensional slider component.
|
|
31
|
-
* @category XYPad
|
|
32
52
|
*/
|
|
33
53
|
export interface ValueOptions {
|
|
34
54
|
// required
|
|
@@ -50,14 +70,18 @@ export interface ValueOptions {
|
|
|
50
70
|
keyboard?: InputEventOption | null
|
|
51
71
|
}
|
|
52
72
|
|
|
53
|
-
/** @category XYPad */
|
|
54
73
|
export interface XYPadProps {
|
|
55
74
|
x: ValueOptions
|
|
56
75
|
y: ValueOptions
|
|
57
76
|
|
|
58
|
-
|
|
77
|
+
externalStyles?: {
|
|
78
|
+
userSelectNone?: boolean
|
|
79
|
+
cursor?: Cursor
|
|
80
|
+
}
|
|
81
|
+
|
|
59
82
|
disabled?: boolean
|
|
60
83
|
readonly?: boolean
|
|
84
|
+
|
|
61
85
|
onChange?: (valueX: number, valueY: number) => void
|
|
62
86
|
onDragStart?: (valueX: number, valueY: number) => void
|
|
63
87
|
onDragEnd?: (valueX: number, valueY: number) => void
|
|
@@ -65,35 +89,23 @@ export interface XYPadProps {
|
|
|
65
89
|
children?: ReactElement | ReactElement[]
|
|
66
90
|
}
|
|
67
91
|
|
|
68
|
-
/** @category XYPad */
|
|
69
92
|
export interface XYPadMethods {
|
|
70
93
|
focus: () => void
|
|
71
94
|
blur: () => void
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const defaultValueOptions = {
|
|
75
|
-
skew: 1,
|
|
76
|
-
step: 1,
|
|
77
|
-
reverse: false,
|
|
78
|
-
wheel: ['raw', 1] as InputEventOption,
|
|
79
|
-
keyboard: ['raw', 1] as InputEventOption,
|
|
95
|
+
original: Ref<HTMLDivElement>
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
type Props = XYPadProps &
|
|
83
99
|
Omit<ComponentPropsWithoutRef<'div'>, keyof XYPadProps>
|
|
84
100
|
|
|
85
|
-
|
|
86
|
-
* Simple XYPad
|
|
87
|
-
* @category XYPad
|
|
88
|
-
*/
|
|
89
|
-
export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
101
|
+
export const Root = forwardRef<XYPadMethods, Props>(
|
|
90
102
|
(
|
|
91
103
|
{
|
|
92
104
|
x: _x,
|
|
93
105
|
y: _y,
|
|
94
106
|
className,
|
|
95
107
|
style,
|
|
96
|
-
|
|
108
|
+
externalStyles: _externalStyles,
|
|
97
109
|
disabled = false,
|
|
98
110
|
readonly = false,
|
|
99
111
|
onChange,
|
|
@@ -116,24 +128,27 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
116
128
|
}, [_y])
|
|
117
129
|
|
|
118
130
|
// -- state and ref ---
|
|
131
|
+
const rootRef = useRef<HTMLDivElement>(null)
|
|
119
132
|
const areaElementRef = useRef<HTMLDivElement>(null)
|
|
120
133
|
const thumbRef = useRef<XYPadThumbMethods>(null)
|
|
121
134
|
|
|
122
135
|
// --- interpret props ---
|
|
136
|
+
const externalStyles = { ...defaultExternalStyles, ..._externalStyles }
|
|
137
|
+
|
|
123
138
|
const nx = normalizeValue(x.value, x.min, x.max, x.skew)
|
|
124
139
|
const ny = normalizeValue(y.value, y.min, y.max, y.skew)
|
|
125
140
|
const percentX = toFixed((x.reverse ? 1 - nx : nx) * 100)
|
|
126
141
|
const percentY = toFixed((y.reverse ? 1 - ny : ny) * 100)
|
|
127
142
|
|
|
128
|
-
let areaProps:
|
|
129
|
-
let thumbProps:
|
|
143
|
+
let areaProps: XYPadAreaProps = {}
|
|
144
|
+
let thumbProps: XYPadThumbProps = {}
|
|
130
145
|
if (children != undefined) {
|
|
131
146
|
React.Children.forEach(children, (child) => {
|
|
132
147
|
if (React.isValidElement(child)) {
|
|
133
|
-
if (child.type ==
|
|
134
|
-
thumbProps = child.props as
|
|
135
|
-
} else if (child.type ==
|
|
136
|
-
areaProps = child.props as
|
|
148
|
+
if (child.type == Thumb) {
|
|
149
|
+
thumbProps = child.props as XYPadThumbProps
|
|
150
|
+
} else if (child.type == Area) {
|
|
151
|
+
areaProps = child.props as XYPadAreaProps
|
|
137
152
|
} else {
|
|
138
153
|
throw new Error('only <XYPadThumb> or <XYPadArea>')
|
|
139
154
|
}
|
|
@@ -216,14 +231,18 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
216
231
|
)
|
|
217
232
|
|
|
218
233
|
// --- hooks ---
|
|
219
|
-
const
|
|
234
|
+
const { refHandler: touchMoveRefCallback, pointerDownHandler } =
|
|
220
235
|
useDragWithElement<HTMLDivElement>({
|
|
221
236
|
baseElementRef: areaElementRef,
|
|
222
237
|
onDrag: onDrag,
|
|
223
238
|
onDragStart: (nx, ny) => {
|
|
224
239
|
if (readonly) return
|
|
225
|
-
|
|
240
|
+
|
|
241
|
+
if (externalStyles.userSelectNone) addUserSelectNone()
|
|
242
|
+
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor)
|
|
243
|
+
|
|
226
244
|
thumbRef.current?.focus()
|
|
245
|
+
|
|
227
246
|
const valueX = clamp(
|
|
228
247
|
stepValue(rawValue(nx, x.min, x.max, x.skew), x.step),
|
|
229
248
|
x.min,
|
|
@@ -234,11 +253,17 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
234
253
|
y.min,
|
|
235
254
|
y.max,
|
|
236
255
|
)
|
|
256
|
+
|
|
257
|
+
// TODO: ドラッグせず、pointer downだけの場合でも、onChange を発火させるべき
|
|
258
|
+
// onChange()
|
|
237
259
|
onDragStart?.(valueX, valueY)
|
|
238
260
|
},
|
|
239
261
|
onDragEnd: (nx, ny) => {
|
|
240
262
|
if (readonly) return
|
|
241
|
-
|
|
263
|
+
|
|
264
|
+
if (externalStyles.userSelectNone) removeUserSelectNone()
|
|
265
|
+
if (externalStyles.cursor) resetCursorStyle()
|
|
266
|
+
|
|
242
267
|
const valueX = clamp(
|
|
243
268
|
stepValue(rawValue(nx, x.min, x.max, x.skew), x.step),
|
|
244
269
|
x.min,
|
|
@@ -284,6 +309,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
284
309
|
blur() {
|
|
285
310
|
thumbRef.current?.blur()
|
|
286
311
|
},
|
|
312
|
+
original: rootRef,
|
|
287
313
|
}
|
|
288
314
|
}, [])
|
|
289
315
|
|
|
@@ -291,10 +317,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
291
317
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
292
318
|
<div
|
|
293
319
|
className={clsx('tremolo-xy-pad', className)}
|
|
294
|
-
ref={(
|
|
295
|
-
wheelRefCallback(div)
|
|
296
|
-
touchMoveRefCallback(div)
|
|
297
|
-
}}
|
|
320
|
+
ref={composeRefs(rootRef, wheelRefCallback, touchMoveRefCallback)}
|
|
298
321
|
tabIndex={-1}
|
|
299
322
|
aria-disabled={disabled}
|
|
300
323
|
aria-readonly={readonly}
|
|
@@ -323,11 +346,12 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
323
346
|
{...props}
|
|
324
347
|
>
|
|
325
348
|
<div className="tremolo-xy-pad-area-wrapper" ref={areaElementRef}>
|
|
326
|
-
<
|
|
349
|
+
<Area
|
|
327
350
|
__thumb={
|
|
328
|
-
<
|
|
351
|
+
<Thumb
|
|
329
352
|
ref={thumbRef}
|
|
330
353
|
__disabled={disabled}
|
|
354
|
+
__readonly={readonly}
|
|
331
355
|
__css={{
|
|
332
356
|
top: `${percentY}%`,
|
|
333
357
|
left: `${percentX}%`,
|
|
@@ -343,5 +367,14 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
|
|
|
343
367
|
},
|
|
344
368
|
)
|
|
345
369
|
|
|
346
|
-
|
|
347
|
-
|
|
370
|
+
/**
|
|
371
|
+
* Simple XYPad
|
|
372
|
+
*/
|
|
373
|
+
export const XYPad = {
|
|
374
|
+
Root,
|
|
375
|
+
Thumb,
|
|
376
|
+
Area,
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export { type XYPadThumbProps, type XYPadThumbMethods } from './Thumb'
|
|
380
|
+
export { type XYPadAreaProps } from './Area'
|