@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.
Files changed (64) hide show
  1. package/dist/index.cjs +1203 -1036
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +176 -159
  4. package/dist/index.d.cts +241 -246
  5. package/dist/index.d.cts.map +1 -1
  6. package/dist/index.d.ts +241 -246
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1187 -1021
  9. package/dist/index.js.map +1 -1
  10. package/package.json +17 -19
  11. package/src/components/AnimationCanvas/index.tsx +0 -7
  12. package/src/components/DragObserver/index.tsx +9 -7
  13. package/src/components/Knob/ActiveLine.tsx +29 -0
  14. package/src/components/Knob/InactiveLine.tsx +53 -0
  15. package/src/components/Knob/SVGRoot.tsx +39 -0
  16. package/src/components/Knob/Thumb.tsx +64 -0
  17. package/src/components/Knob/context.tsx +120 -0
  18. package/src/components/Knob/index.css +5 -1
  19. package/src/components/Knob/index.tsx +110 -164
  20. package/src/components/NumberInput/DecrementStepper.tsx +0 -2
  21. package/src/components/NumberInput/IncrementStepper.tsx +0 -2
  22. package/src/components/NumberInput/InternalInput.tsx +3 -1
  23. package/src/components/NumberInput/Stepper.tsx +0 -2
  24. package/src/components/NumberInput/context.tsx +0 -1
  25. package/src/components/NumberInput/index.tsx +6 -8
  26. package/src/components/Piano/KeyLabel.tsx +0 -2
  27. package/src/components/Piano/context.tsx +0 -2
  28. package/src/components/Piano/index.tsx +7 -11
  29. package/src/components/Piano/key.tsx +0 -10
  30. package/src/components/Piano/keyboardShortcuts.ts +0 -2
  31. package/src/components/PointsEditor/Background.tsx +17 -0
  32. package/src/components/PointsEditor/Container.tsx +26 -0
  33. package/src/components/PointsEditor/Point.tsx +137 -0
  34. package/src/components/PointsEditor/context.tsx +91 -0
  35. package/src/components/PointsEditor/index.css +30 -0
  36. package/src/components/PointsEditor/index.tsx +129 -0
  37. package/src/components/Slider/Scale.tsx +0 -2
  38. package/src/components/Slider/ScaleOption.tsx +0 -2
  39. package/src/components/Slider/Thumb.tsx +0 -4
  40. package/src/components/Slider/Track.tsx +0 -2
  41. package/src/components/Slider/context.tsx +0 -1
  42. package/src/components/Slider/index.tsx +42 -14
  43. package/src/components/WheelObserver/index.tsx +17 -19
  44. package/src/components/XYPad/Area.tsx +0 -2
  45. package/src/components/XYPad/Thumb.tsx +0 -3
  46. package/src/components/XYPad/index.tsx +52 -25
  47. package/src/components/_util/composeRefs.tsx +63 -0
  48. package/src/components/_util/index.ts +23 -6
  49. package/src/components/_util/type.ts +1 -0
  50. package/src/hooks/useAnimationFrame.ts +0 -3
  51. package/src/hooks/useCallbackRef.ts +2 -2
  52. package/src/hooks/useDrag.ts +12 -11
  53. package/src/hooks/useDragWithElement.ts +12 -17
  54. package/src/hooks/useEventListener.ts +6 -9
  55. package/src/hooks/useInterval.ts +0 -3
  56. package/src/hooks/useLongPress.ts +0 -3
  57. package/src/hooks/useMIDIAccess.ts +0 -1
  58. package/src/hooks/useMIDIInput.ts +0 -1
  59. package/src/hooks/useMIDIMessage.ts +0 -1
  60. package/src/hooks/usePianoDrag.ts +75 -0
  61. package/src/index.ts +8 -5
  62. package/src/styles/global.css +17 -1
  63. package/dist/index.css.map +0 -1
  64. 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 { addNoSelect, removeNoSelect } from '../_util'
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
- /** Whether to apply `{use-select: none}` when dragging */
50
- bodyNoSelect?: boolean
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<'svg'>, keyof KnobProps>
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 = 50,
119
- bodyNoSelect = true,
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
- classes,
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<HTMLOrSVGElement>(null)
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<SVGElement>({
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 (bodyNoSelect) addNoSelect()
184
+ if (externalStyles.userSelectNone) addUserSelectNone()
185
+ if (externalStyles.cursor) setCursorStyle(externalStyles.cursor)
192
186
  },
193
187
  onDragEnd: () => {
194
- if (bodyNoSelect) removeNoSelect()
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
- <svg
244
- ref={(div) => {
245
- elmRef.current = div
246
- wheelRefCallback(div)
247
- touchMoveRefCallback(div)
248
- }}
249
- className={clsx('tremolo-knob', className)}
250
- viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}
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
- {/* lines */}
277
- {startValue > min && (
278
- <path
279
- className={clsx(
280
- 'tremolo-knob-inactive-line',
281
- classes?.inactiveLine,
282
- )}
283
- d={`M ${x1} ${y1} A ${center} ${center} -135 ${r2 - r1 > 180 ? 1 : 0} 1 ${x2}, ${y2}`}
284
- fill="none"
285
- stroke={inactiveLine || 'currentColor'}
286
- strokeWidth={lineWeight}
287
- />
288
- )}
289
- {startValue < max && (
290
- <path
291
- className={clsx(
292
- 'tremolo-knob-inactive-line',
293
- classes?.inactiveLine,
294
- )}
295
- d={`M ${x3} ${y3} A ${center} ${center} -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4}, ${y4}`}
296
- fill="none"
297
- stroke={inactiveLine || 'currentColor'}
298
- strokeWidth={lineWeight}
299
- />
300
- )}
301
- <path
302
- className={clsx('tremolo-knob-active-line', classes?.activeLine)}
303
- d={`M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3}, ${y3}`}
304
- fill="none"
305
- stroke={activeLine || 'currentColor'}
306
- strokeWidth={lineWeight}
307
- />
308
- {/* thumb */}
309
- <svg className={clsx('tremolo-knob-thumb', classes?.thumb)}>
310
- <circle
311
- cx="50%"
312
- cy="50%"
313
- r={`${thumbSize / 2}%`}
314
- fill={thumb || 'currentColor'}
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('required parameter: min, max') // TODO
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,
@@ -153,7 +153,6 @@ export function NumberInputProvider({
153
153
  )
154
154
  }
155
155
 
156
- /** @category NumberInput */
157
156
  export function useNumberInputContext<T>(
158
157
  selector: (state: State & Action) => T,
159
158
  ): T {
@@ -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 NumberInputImpl = forwardRef<NumberInputMethods, Props>(
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 = Object.assign(NumberInputImpl, {
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 PianoImpl = forwardRef<PianoMethods, Props>(
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
- const [touchMoveRefCallback, pointerDownHandler] =
244
- useDragWithElement<HTMLDivElement>({
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 = Object.assign(PianoImpl, { WhiteKey, BlackKey, KeyLabel })
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
  {
@@ -1,4 +1,3 @@
1
- /** @category Piano */
2
1
  export type KeyboardShortcuts = {
3
2
  keys: string[]
4
3
  // TODO
@@ -7,7 +6,6 @@ export type KeyboardShortcuts = {
7
6
  }
8
7
  }
9
8
 
10
- /** @category Piano */
11
9
  export const SHORTCUTS = {
12
10
  HOME_ROW: {
13
11
  keys: [
@@ -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
+ }