@tremolo-ui/react 0.2.0 → 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 +1203 -1036
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +176 -159
- package/dist/index.d.cts +241 -246
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +241 -246
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1187 -1021
- 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 +5 -1
- 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.tsx +6 -8
- package/src/components/Piano/KeyLabel.tsx +0 -2
- package/src/components/Piano/context.tsx +0 -2
- package/src/components/Piano/index.tsx +7 -11
- 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 +0 -4
- package/src/components/Slider/Track.tsx +0 -2
- package/src/components/Slider/context.tsx +0 -1
- package/src/components/Slider/index.tsx +42 -14
- package/src/components/WheelObserver/index.tsx +17 -19
- package/src/components/XYPad/Area.tsx +0 -2
- package/src/components/XYPad/Thumb.tsx +0 -3
- package/src/components/XYPad/index.tsx +52 -25
- 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 +8 -5
- package/src/styles/global.css +17 -1
- package/dist/index.css.map +0 -1
- package/src/components/AnimationKnob/index.tsx +0 -314
|
@@ -5,22 +5,38 @@ import {
|
|
|
5
5
|
useCallback,
|
|
6
6
|
useImperativeHandle,
|
|
7
7
|
useRef,
|
|
8
|
+
useState,
|
|
8
9
|
} from 'react'
|
|
9
10
|
|
|
10
11
|
import {
|
|
11
12
|
clamp,
|
|
12
13
|
InputEventOption,
|
|
13
14
|
normalizeValue,
|
|
14
|
-
radian,
|
|
15
15
|
rawValue,
|
|
16
16
|
stepValue,
|
|
17
17
|
} from '@tremolo-ui/functions'
|
|
18
18
|
|
|
19
19
|
import { useDrag } from '../../hooks/useDrag'
|
|
20
20
|
import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
addUserSelectNone,
|
|
23
|
+
Cursor,
|
|
24
|
+
removeUserSelectNone,
|
|
25
|
+
resetCursorStyle,
|
|
26
|
+
setCursorStyle,
|
|
27
|
+
} from '../_util'
|
|
28
|
+
|
|
29
|
+
import { ActiveLine } from './ActiveLine'
|
|
30
|
+
import { KnobProvider } from './context'
|
|
31
|
+
import { InactiveLine } from './InactiveLine'
|
|
32
|
+
import { SVGRoot } from './SVGRoot'
|
|
33
|
+
import { Thumb } from './Thumb'
|
|
34
|
+
|
|
35
|
+
const defaultExternalStyles: KnobProps['externalStyles'] = {
|
|
36
|
+
userSelectNone: true,
|
|
37
|
+
cursor: 'grabbing',
|
|
38
|
+
}
|
|
22
39
|
|
|
23
|
-
/** @category Knob */
|
|
24
40
|
export interface KnobProps {
|
|
25
41
|
// required
|
|
26
42
|
value: number
|
|
@@ -29,9 +45,10 @@ export interface KnobProps {
|
|
|
29
45
|
|
|
30
46
|
// optional
|
|
31
47
|
step?: number
|
|
32
|
-
skew?: number // | SkewFunction
|
|
48
|
+
skew?: number // | SkewFunction // TODO
|
|
33
49
|
/**
|
|
34
50
|
* value set when double-clicking
|
|
51
|
+
* restriction: enableDoubleClickDefault = true
|
|
35
52
|
* @default min
|
|
36
53
|
* @see enableDoubleClickDefault
|
|
37
54
|
*/
|
|
@@ -46,14 +63,22 @@ export interface KnobProps {
|
|
|
46
63
|
/** width and height */
|
|
47
64
|
size?: number | string
|
|
48
65
|
|
|
49
|
-
/**
|
|
50
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Global style to apply when dragged
|
|
68
|
+
* @default defaultExternalStyles
|
|
69
|
+
*/
|
|
70
|
+
externalStyles?: {
|
|
71
|
+
userSelectNone?: boolean
|
|
72
|
+
cursor?: Cursor
|
|
73
|
+
}
|
|
51
74
|
/**
|
|
52
75
|
* wheel control option
|
|
76
|
+
* If null, no event will be triggered
|
|
53
77
|
*/
|
|
54
78
|
wheel?: InputEventOption | null
|
|
55
79
|
/**
|
|
56
80
|
* keyboard control option
|
|
81
|
+
* If null, no event will be triggered
|
|
57
82
|
*/
|
|
58
83
|
keyboard?: InputEventOption | null
|
|
59
84
|
enableDoubleClickDefault?: boolean
|
|
@@ -61,51 +86,20 @@ export interface KnobProps {
|
|
|
61
86
|
disabled?: boolean
|
|
62
87
|
readonly?: boolean
|
|
63
88
|
|
|
64
|
-
/** color */
|
|
65
|
-
activeLine?: string
|
|
66
|
-
/** color */
|
|
67
|
-
inactiveLine?: string
|
|
68
|
-
/** color */
|
|
69
|
-
thumb?: string
|
|
70
|
-
/** color */
|
|
71
|
-
thumbLine?: string
|
|
72
|
-
/** percent (0-100) */
|
|
73
|
-
thumbSize?: number
|
|
74
|
-
/** percent (0-100) */
|
|
75
|
-
thumbLineWeight?: number
|
|
76
|
-
/** percent (0-100) */
|
|
77
|
-
thumbLineLength?: number
|
|
78
|
-
/** line weight [px] */
|
|
79
|
-
lineWeight?: number
|
|
80
89
|
/** angle range [degree] */
|
|
81
90
|
angleRange?: number
|
|
82
91
|
|
|
83
|
-
classes?: {
|
|
84
|
-
activeLine?: string
|
|
85
|
-
inactiveLine?: string
|
|
86
|
-
thumb?: string
|
|
87
|
-
thumbLine?: string
|
|
88
|
-
}
|
|
89
|
-
|
|
90
92
|
onChange?: (value: number) => void
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
/**
|
|
94
|
-
* @category Knob
|
|
95
|
-
*/
|
|
96
95
|
export interface KnobMethods {
|
|
97
96
|
focus: () => void
|
|
98
97
|
blur: () => void
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
type Props = KnobProps & Omit<ComponentPropsWithoutRef<'
|
|
100
|
+
type Props = KnobProps & Omit<ComponentPropsWithoutRef<'div'>, keyof KnobProps>
|
|
102
101
|
|
|
103
|
-
|
|
104
|
-
* Interactive rotary knob component implemented in SVG.
|
|
105
|
-
*
|
|
106
|
-
* @category Knob
|
|
107
|
-
*/
|
|
108
|
-
export const Knob = forwardRef<KnobMethods, Props>(
|
|
102
|
+
const Root = forwardRef<KnobMethods, Props>(
|
|
109
103
|
(
|
|
110
104
|
{
|
|
111
105
|
value,
|
|
@@ -115,34 +109,30 @@ export const Knob = forwardRef<KnobMethods, Props>(
|
|
|
115
109
|
skew = 1,
|
|
116
110
|
defaultValue = min,
|
|
117
111
|
startValue = min,
|
|
118
|
-
size
|
|
119
|
-
|
|
112
|
+
size,
|
|
113
|
+
externalStyles: _externalStyles,
|
|
120
114
|
wheel = ['raw', 1],
|
|
121
115
|
keyboard = ['raw', 1],
|
|
122
116
|
enableDoubleClickDefault = true,
|
|
123
117
|
disabled = false,
|
|
124
118
|
readonly = false,
|
|
125
|
-
activeLine,
|
|
126
|
-
inactiveLine,
|
|
127
|
-
thumb,
|
|
128
|
-
thumbLine,
|
|
129
|
-
thumbSize = 84,
|
|
130
|
-
thumbLineWeight = 6,
|
|
131
|
-
thumbLineLength = 35,
|
|
132
|
-
lineWeight = 6,
|
|
133
119
|
angleRange = 270,
|
|
134
120
|
onChange,
|
|
135
121
|
onKeyDown,
|
|
136
122
|
onPointerDown,
|
|
137
123
|
onDoubleClick,
|
|
138
124
|
className,
|
|
139
|
-
|
|
125
|
+
style,
|
|
126
|
+
children,
|
|
140
127
|
...props
|
|
141
128
|
}: Props,
|
|
142
129
|
forwardedRef,
|
|
143
130
|
) => {
|
|
131
|
+
const [dragging, setDragging] = useState(false)
|
|
144
132
|
const valueRef = useRef(0)
|
|
145
|
-
const elmRef = useRef<
|
|
133
|
+
const elmRef = useRef<HTMLElement | SVGElement>(null)
|
|
134
|
+
|
|
135
|
+
const externalStyles = { ...defaultExternalStyles, ..._externalStyles }
|
|
146
136
|
|
|
147
137
|
// --- internal functions ---
|
|
148
138
|
const onDrag = useCallback(
|
|
@@ -184,14 +174,20 @@ export const Knob = forwardRef<KnobMethods, Props>(
|
|
|
184
174
|
)
|
|
185
175
|
|
|
186
176
|
// --- hooks ---
|
|
187
|
-
const [touchMoveRefCallback, pointerDownHandler] = useDrag<
|
|
177
|
+
const [touchMoveRefCallback, pointerDownHandler] = useDrag<
|
|
178
|
+
HTMLElement | SVGElement
|
|
179
|
+
>({
|
|
188
180
|
onDrag: onDrag,
|
|
189
181
|
onDragStart: () => {
|
|
182
|
+
setDragging(true)
|
|
190
183
|
valueRef.current = normalizeValue(value, min, max, skew)
|
|
191
|
-
if (
|
|
184
|
+
if (externalStyles.userSelectNone) addUserSelectNone()
|
|
185
|
+
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor)
|
|
192
186
|
},
|
|
193
187
|
onDragEnd: () => {
|
|
194
|
-
|
|
188
|
+
setDragging(false)
|
|
189
|
+
if (externalStyles.userSelectNone) removeUserSelectNone()
|
|
190
|
+
if (externalStyles.cursor) resetCursorStyle()
|
|
195
191
|
},
|
|
196
192
|
})
|
|
197
193
|
|
|
@@ -219,118 +215,68 @@ export const Knob = forwardRef<KnobMethods, Props>(
|
|
|
219
215
|
}
|
|
220
216
|
}, [])
|
|
221
217
|
|
|
222
|
-
const viewBoxSize = 100
|
|
223
|
-
const center = viewBoxSize / 2
|
|
224
|
-
const padding = (viewBoxSize - clamp(thumbSize, 0, 100)) / 2 // %
|
|
225
|
-
const p = normalizeValue(value, min, max, skew)
|
|
226
|
-
const s = normalizeValue(startValue, min, max, skew)
|
|
227
|
-
|
|
228
|
-
// 時計回りで描画していく
|
|
229
|
-
const r1 = -angleRange / 2 // ロータリー開始位置
|
|
230
|
-
const r2 = r1 + Math.min(p, s) * angleRange // activeLineの開始位置
|
|
231
|
-
const r3 = r1 + Math.max(p, s) * angleRange // activeLineの終了位置
|
|
232
|
-
const r4 = angleRange / 2 // ロータリー終了位置
|
|
233
|
-
const x1 = center + center * Math.cos(radian(r1 - 90))
|
|
234
|
-
const y1 = center + center * Math.sin(radian(r1 - 90))
|
|
235
|
-
const x2 = center + center * Math.cos(radian(r2 - 90))
|
|
236
|
-
const y2 = center + center * Math.sin(radian(r2 - 90))
|
|
237
|
-
const x3 = center + center * Math.cos(radian(r3 - 90))
|
|
238
|
-
const y3 = center + center * Math.sin(radian(r3 - 90))
|
|
239
|
-
const x4 = center + center * Math.cos(radian(r4 - 90))
|
|
240
|
-
const y4 = center + center * Math.sin(radian(r4 - 90))
|
|
241
|
-
|
|
242
218
|
return (
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
width={size}
|
|
252
|
-
height={size}
|
|
253
|
-
tabIndex={0}
|
|
254
|
-
role="slider"
|
|
255
|
-
aria-valuenow={value}
|
|
256
|
-
aria-valuemin={min}
|
|
257
|
-
aria-valuemax={max}
|
|
258
|
-
aria-disabled={disabled}
|
|
259
|
-
aria-readonly={readonly}
|
|
260
|
-
onPointerDown={(event) => {
|
|
261
|
-
pointerDownHandler(event)
|
|
262
|
-
onPointerDown?.(event)
|
|
263
|
-
}}
|
|
264
|
-
onDoubleClick={(event) => {
|
|
265
|
-
if (enableDoubleClickDefault && onChange) {
|
|
266
|
-
onChange(defaultValue)
|
|
267
|
-
}
|
|
268
|
-
onDoubleClick?.(event)
|
|
269
|
-
}}
|
|
270
|
-
onKeyDown={(event) => {
|
|
271
|
-
handleKeyDown(event)
|
|
272
|
-
onKeyDown?.(event)
|
|
273
|
-
}}
|
|
274
|
-
{...props}
|
|
219
|
+
<KnobProvider
|
|
220
|
+
value={value}
|
|
221
|
+
min={min}
|
|
222
|
+
max={max}
|
|
223
|
+
step={step}
|
|
224
|
+
skew={skew}
|
|
225
|
+
startValue={startValue}
|
|
226
|
+
angleRange={angleRange}
|
|
275
227
|
>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
/>
|
|
316
|
-
<line
|
|
317
|
-
className={clsx('tremolo-knob-thumb-line', classes?.thumbLine)}
|
|
318
|
-
x1="50%"
|
|
319
|
-
y1={`${padding}%`}
|
|
320
|
-
x2="50%"
|
|
321
|
-
y2={`${thumbLineLength}%`}
|
|
322
|
-
stroke={thumbLine || 'currentColor'}
|
|
323
|
-
strokeWidth={`${thumbLineWeight}%`}
|
|
324
|
-
// NOTE
|
|
325
|
-
// https://bugs.webkit.org/show_bug.cgi?id=201854
|
|
326
|
-
// https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/transform-origin#browser_compatibility
|
|
327
|
-
style={{
|
|
328
|
-
transform: `rotate(${r1 + p * angleRange}deg)`,
|
|
329
|
-
transformOrigin: '50% 50%',
|
|
330
|
-
}}
|
|
331
|
-
/>
|
|
332
|
-
</svg>
|
|
333
|
-
</svg>
|
|
228
|
+
<div
|
|
229
|
+
ref={(elm) => {
|
|
230
|
+
elmRef.current = elm
|
|
231
|
+
wheelRefCallback(elm)
|
|
232
|
+
touchMoveRefCallback(elm)
|
|
233
|
+
}}
|
|
234
|
+
className={clsx('tremolo-knob', className)}
|
|
235
|
+
tabIndex={0}
|
|
236
|
+
role="slider"
|
|
237
|
+
aria-valuenow={value}
|
|
238
|
+
aria-valuemin={min}
|
|
239
|
+
aria-valuemax={max}
|
|
240
|
+
aria-disabled={disabled}
|
|
241
|
+
aria-readonly={readonly}
|
|
242
|
+
data-dragging={dragging}
|
|
243
|
+
style={{
|
|
244
|
+
width: size,
|
|
245
|
+
height: size,
|
|
246
|
+
...style,
|
|
247
|
+
}}
|
|
248
|
+
onPointerDown={(event) => {
|
|
249
|
+
pointerDownHandler(event)
|
|
250
|
+
onPointerDown?.(event)
|
|
251
|
+
}}
|
|
252
|
+
onDoubleClick={(event) => {
|
|
253
|
+
if (enableDoubleClickDefault && onChange) {
|
|
254
|
+
onChange(defaultValue)
|
|
255
|
+
}
|
|
256
|
+
onDoubleClick?.(event)
|
|
257
|
+
}}
|
|
258
|
+
onKeyDown={(event) => {
|
|
259
|
+
handleKeyDown(event)
|
|
260
|
+
onKeyDown?.(event)
|
|
261
|
+
}}
|
|
262
|
+
{...props}
|
|
263
|
+
>
|
|
264
|
+
{children || <SVGRoot />}
|
|
265
|
+
</div>
|
|
266
|
+
</KnobProvider>
|
|
334
267
|
)
|
|
335
268
|
},
|
|
336
269
|
)
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Interactive rotary knob component implemented in SVG.
|
|
273
|
+
*/
|
|
274
|
+
export const Knob = {
|
|
275
|
+
Root,
|
|
276
|
+
SVGRoot,
|
|
277
|
+
InactiveLine,
|
|
278
|
+
ActiveLine,
|
|
279
|
+
Thumb,
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export { useKnobContext } from './context'
|
|
@@ -5,13 +5,11 @@ import { useLongPress } from '../../hooks/useLongPress'
|
|
|
5
5
|
|
|
6
6
|
import { useNumberInputContext } from './context'
|
|
7
7
|
|
|
8
|
-
/** @category NumberInput */
|
|
9
8
|
export interface DecrementStepperProps {
|
|
10
9
|
size?: number
|
|
11
10
|
children?: ReactNode
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
/** @category NumberInput */
|
|
15
13
|
export function DecrementStepper({
|
|
16
14
|
size = 12,
|
|
17
15
|
children,
|
|
@@ -5,13 +5,11 @@ import { useLongPress } from '../../hooks/useLongPress'
|
|
|
5
5
|
|
|
6
6
|
import { useNumberInputContext } from './context'
|
|
7
7
|
|
|
8
|
-
/** @category NumberInput */
|
|
9
8
|
export interface IncrementStepperProps {
|
|
10
9
|
size?: number
|
|
11
10
|
children?: ReactNode
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
/** @category NumberInput */
|
|
15
13
|
export function IncrementStepper({
|
|
16
14
|
size = 12,
|
|
17
15
|
children,
|
|
@@ -96,7 +96,9 @@ export const InternalInput = forwardRef<
|
|
|
96
96
|
let newValue
|
|
97
97
|
if (eventType == 'normalized') {
|
|
98
98
|
if (!min || !max) {
|
|
99
|
-
throw new Error(
|
|
99
|
+
throw new Error(
|
|
100
|
+
'[NumberInput] "min" and "max" are required when InputEventOption[0] is set to "normalized".',
|
|
101
|
+
)
|
|
100
102
|
}
|
|
101
103
|
const n = normalizeValue(valueAsNumber, min, max)
|
|
102
104
|
newValue = rawValue(n + x, min, max)
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
2
|
import { ComponentPropsWithoutRef, ReactNode } from 'react'
|
|
3
3
|
|
|
4
|
-
/** @category NumberInput */
|
|
5
4
|
export interface StepperProps {
|
|
6
5
|
/** Display only when hovering. */
|
|
7
6
|
dynamic?: boolean
|
|
8
7
|
children?: ReactNode
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
/** @category NumberInput */
|
|
12
10
|
export function Stepper({
|
|
13
11
|
dynamic = true,
|
|
14
12
|
children,
|
|
@@ -16,7 +16,6 @@ TODO
|
|
|
16
16
|
- skew
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
/** @category NumberInput */
|
|
20
19
|
export interface NumberInputProps {
|
|
21
20
|
value: number | string
|
|
22
21
|
|
|
@@ -60,10 +59,12 @@ export interface NumberInputProps {
|
|
|
60
59
|
|
|
61
60
|
/**
|
|
62
61
|
* wheel control option
|
|
62
|
+
* If null, no event will be triggered
|
|
63
63
|
*/
|
|
64
64
|
wheel?: InputEventOption | null
|
|
65
65
|
/**
|
|
66
66
|
* keyboard control option
|
|
67
|
+
* If null, no event will be triggered
|
|
67
68
|
*/
|
|
68
69
|
keyboard?: InputEventOption | null
|
|
69
70
|
|
|
@@ -86,9 +87,6 @@ export interface NumberInputProps {
|
|
|
86
87
|
children?: ReactNode
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
/**
|
|
90
|
-
* @category NumberInput
|
|
91
|
-
*/
|
|
92
90
|
export interface NumberInputMethods {
|
|
93
91
|
focus: () => void
|
|
94
92
|
blur: () => void
|
|
@@ -97,7 +95,7 @@ export interface NumberInputMethods {
|
|
|
97
95
|
type Props = NumberInputProps &
|
|
98
96
|
Omit<ComponentPropsWithoutRef<'input'>, keyof NumberInputProps | 'type'>
|
|
99
97
|
|
|
100
|
-
const
|
|
98
|
+
const Root = forwardRef<NumberInputMethods, Props>(
|
|
101
99
|
(
|
|
102
100
|
{
|
|
103
101
|
value,
|
|
@@ -175,13 +173,13 @@ const NumberInputImpl = forwardRef<NumberInputMethods, Props>(
|
|
|
175
173
|
|
|
176
174
|
/**
|
|
177
175
|
* Input with some useful functions for entering numerical values.
|
|
178
|
-
* @category NumberInput
|
|
179
176
|
*/
|
|
180
|
-
export const NumberInput =
|
|
177
|
+
export const NumberInput = {
|
|
178
|
+
Root,
|
|
181
179
|
Stepper,
|
|
182
180
|
IncrementStepper,
|
|
183
181
|
DecrementStepper,
|
|
184
|
-
}
|
|
182
|
+
}
|
|
185
183
|
|
|
186
184
|
export { type StepperProps } from './Stepper'
|
|
187
185
|
export { type IncrementStepperProps } from './IncrementStepper'
|
|
@@ -5,7 +5,6 @@ import { usePianoContext } from './context'
|
|
|
5
5
|
|
|
6
6
|
import { getNoteRangeArray } from '.'
|
|
7
7
|
|
|
8
|
-
/** @category Piano */
|
|
9
8
|
export interface KeyLabelProps {
|
|
10
9
|
/**
|
|
11
10
|
* override Piano.label
|
|
@@ -21,7 +20,6 @@ export interface KeyLabelProps {
|
|
|
21
20
|
__label?: (note: number, index: number) => ReactNode
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
/** @category Piano */
|
|
25
23
|
export function KeyLabel({
|
|
26
24
|
label,
|
|
27
25
|
className,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createContext, ReactNode, useContext, useEffect, useRef } from 'react'
|
|
2
2
|
import { createStore, useStore } from 'zustand'
|
|
3
3
|
|
|
4
|
-
/** @category Piano */
|
|
5
4
|
export type NoteRange = {
|
|
6
5
|
first: number
|
|
7
6
|
last: number
|
|
@@ -67,7 +66,6 @@ export function PianoProvider({ children, ...props }: PianoProviderProps) {
|
|
|
67
66
|
)
|
|
68
67
|
}
|
|
69
68
|
|
|
70
|
-
/** @category Piano */
|
|
71
69
|
export function usePianoContext<T>(selector: (state: State & Action) => T): T {
|
|
72
70
|
const store = useContext(PianoContext)
|
|
73
71
|
if (!store) throw new Error('Missing PianoContext.Provider in the tree')
|
|
@@ -23,8 +23,8 @@ import {
|
|
|
23
23
|
noteKeys,
|
|
24
24
|
} from '@tremolo-ui/functions'
|
|
25
25
|
|
|
26
|
-
import { useDragWithElement } from '../../hooks/useDragWithElement'
|
|
27
26
|
import { useEventListener } from '../../hooks/useEventListener'
|
|
27
|
+
import { usePianoDrag } from '../../hooks/usePianoDrag'
|
|
28
28
|
|
|
29
29
|
import { NoteRange, PianoProvider } from './context'
|
|
30
30
|
import {
|
|
@@ -39,7 +39,6 @@ import { KeyLabel } from './KeyLabel'
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* [noteRange.first, noteRange.first + 1 ..., noteRange.last]
|
|
42
|
-
* @category Piano
|
|
43
42
|
*/
|
|
44
43
|
export function getNoteRangeArray(noteRange: NoteRange) {
|
|
45
44
|
return Array.from(
|
|
@@ -48,7 +47,6 @@ export function getNoteRangeArray(noteRange: NoteRange) {
|
|
|
48
47
|
)
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
/** @category Piano */
|
|
52
50
|
export interface PianoProps {
|
|
53
51
|
noteRange: NoteRange
|
|
54
52
|
|
|
@@ -74,9 +72,6 @@ export interface PianoProps {
|
|
|
74
72
|
|
|
75
73
|
const blackPerWhiteWidth = defaultBlackKeyWidth / defaultWhiteKeyWidth // 0.65
|
|
76
74
|
|
|
77
|
-
/**
|
|
78
|
-
* @category Piano
|
|
79
|
-
*/
|
|
80
75
|
export interface PianoMethods {
|
|
81
76
|
playNote: (note: number, velocity?: number) => void
|
|
82
77
|
stopNote: (note: number) => void
|
|
@@ -85,7 +80,7 @@ export interface PianoMethods {
|
|
|
85
80
|
type Props = PianoProps &
|
|
86
81
|
Omit<ComponentPropsWithoutRef<'div'>, keyof PianoProps>
|
|
87
82
|
|
|
88
|
-
const
|
|
83
|
+
const Root = forwardRef<PianoMethods, Props>(
|
|
89
84
|
(
|
|
90
85
|
{
|
|
91
86
|
noteRange,
|
|
@@ -240,10 +235,12 @@ const PianoImpl = forwardRef<PianoMethods, Props>(
|
|
|
240
235
|
}
|
|
241
236
|
}, [fill, _whiteNoteWidth, whiteNoteCount])
|
|
242
237
|
|
|
243
|
-
|
|
244
|
-
|
|
238
|
+
// FIXME
|
|
239
|
+
const { refHandler: touchMoveRefCallback, pointerDownHandler } =
|
|
240
|
+
usePianoDrag<HTMLDivElement>({
|
|
245
241
|
baseElementRef: pianoRef,
|
|
246
242
|
onDrag: onDrag,
|
|
243
|
+
// onDragStart: onDrag,
|
|
247
244
|
onDragEnd: onDragEnd,
|
|
248
245
|
})
|
|
249
246
|
|
|
@@ -344,9 +341,8 @@ const PianoImpl = forwardRef<PianoMethods, Props>(
|
|
|
344
341
|
|
|
345
342
|
/**
|
|
346
343
|
* Customizable piano component.
|
|
347
|
-
* @category Piano
|
|
348
344
|
*/
|
|
349
|
-
export const Piano =
|
|
345
|
+
export const Piano = { Root, WhiteKey, BlackKey, KeyLabel }
|
|
350
346
|
|
|
351
347
|
export { type KeyboardShortcuts, SHORTCUTS } from './keyboardShortcuts'
|
|
352
348
|
export { type KeyProps, type KeyMethods } from './key'
|
|
@@ -11,9 +11,6 @@ import React, {
|
|
|
11
11
|
import { usePianoContext } from './context'
|
|
12
12
|
import { KeyLabel, KeyLabelProps } from './KeyLabel'
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* @category Piano
|
|
16
|
-
*/
|
|
17
14
|
export interface KeyProps {
|
|
18
15
|
noteNumber: number
|
|
19
16
|
|
|
@@ -36,9 +33,6 @@ export interface KeyProps {
|
|
|
36
33
|
__width?: number
|
|
37
34
|
}
|
|
38
35
|
|
|
39
|
-
/**
|
|
40
|
-
* @category Piano
|
|
41
|
-
*/
|
|
42
36
|
export interface KeyMethods {
|
|
43
37
|
play: (velocity?: number) => void
|
|
44
38
|
stop: () => void
|
|
@@ -48,9 +42,7 @@ export interface KeyMethods {
|
|
|
48
42
|
type Props = KeyProps & Omit<ComponentPropsWithoutRef<'div'>, keyof KeyProps>
|
|
49
43
|
type ImplProps = Props & { keyType: 'black' | 'white' }
|
|
50
44
|
|
|
51
|
-
/** @category Piano */
|
|
52
45
|
export const defaultWhiteKeyWidth = 40
|
|
53
|
-
/** @category Piano */
|
|
54
46
|
export const defaultBlackKeyWidth = 26
|
|
55
47
|
|
|
56
48
|
const KeyImpl = forwardRef<KeyMethods, ImplProps>(
|
|
@@ -166,7 +158,6 @@ const KeyImpl = forwardRef<KeyMethods, ImplProps>(
|
|
|
166
158
|
},
|
|
167
159
|
)
|
|
168
160
|
|
|
169
|
-
/** @category Piano */
|
|
170
161
|
export const WhiteKey = forwardRef<KeyMethods, Props>(
|
|
171
162
|
(
|
|
172
163
|
{
|
|
@@ -190,7 +181,6 @@ export const WhiteKey = forwardRef<KeyMethods, Props>(
|
|
|
190
181
|
},
|
|
191
182
|
)
|
|
192
183
|
|
|
193
|
-
/** @category Piano */
|
|
194
184
|
export const BlackKey = forwardRef<KeyMethods, Props>(
|
|
195
185
|
(
|
|
196
186
|
{
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
import { ComponentPropsWithoutRef } from 'react'
|
|
3
|
+
|
|
4
|
+
export function Background({
|
|
5
|
+
className,
|
|
6
|
+
children,
|
|
7
|
+
...props
|
|
8
|
+
}: ComponentPropsWithoutRef<'div'>) {
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
className={clsx('tremolo-points-editor-background', className)}
|
|
12
|
+
{...props}
|
|
13
|
+
>
|
|
14
|
+
{children}
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
}
|