@tremolo-ui/react 0.0.11 → 0.1.0-alpha.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.
Files changed (153) hide show
  1. package/dist/cjs/index.js +711 -395
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/AnimationCanvas/index.d.ts +2 -2
  4. package/dist/cjs/types/components/AnimationCanvas/index.d.ts.map +1 -1
  5. package/dist/cjs/types/components/AnimationKnob/index.d.ts +10 -3
  6. package/dist/cjs/types/components/AnimationKnob/index.d.ts.map +1 -1
  7. package/dist/cjs/types/components/Knob/index.d.ts +25 -3
  8. package/dist/cjs/types/components/Knob/index.d.ts.map +1 -1
  9. package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts +2 -0
  10. package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts.map +1 -1
  11. package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts +2 -0
  12. package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts.map +1 -1
  13. package/dist/cjs/types/components/NumberInput/InternalInput.d.ts +18 -0
  14. package/dist/cjs/types/components/NumberInput/InternalInput.d.ts.map +1 -0
  15. package/dist/cjs/types/components/NumberInput/Stepper.d.ts +2 -0
  16. package/dist/cjs/types/components/NumberInput/Stepper.d.ts.map +1 -1
  17. package/dist/cjs/types/components/NumberInput/context.d.ts +3 -2
  18. package/dist/cjs/types/components/NumberInput/context.d.ts.map +1 -1
  19. package/dist/cjs/types/components/NumberInput/index.d.ts +12 -3
  20. package/dist/cjs/types/components/NumberInput/index.d.ts.map +1 -1
  21. package/dist/cjs/types/components/Piano/KeyLabel.d.ts +1 -3
  22. package/dist/cjs/types/components/Piano/KeyLabel.d.ts.map +1 -1
  23. package/dist/cjs/types/components/Piano/context.d.ts +24 -0
  24. package/dist/cjs/types/components/Piano/context.d.ts.map +1 -0
  25. package/dist/cjs/types/components/Piano/index.d.ts +15 -12
  26. package/dist/cjs/types/components/Piano/index.d.ts.map +1 -1
  27. package/dist/cjs/types/components/Piano/key.d.ts +14 -19
  28. package/dist/cjs/types/components/Piano/key.d.ts.map +1 -1
  29. package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts +2 -2
  30. package/dist/cjs/types/components/Slider/Scale.d.ts +1 -3
  31. package/dist/cjs/types/components/Slider/Scale.d.ts.map +1 -1
  32. package/dist/cjs/types/components/Slider/Thumb.d.ts +1 -0
  33. package/dist/cjs/types/components/Slider/Thumb.d.ts.map +1 -1
  34. package/dist/cjs/types/components/Slider/Track.d.ts +2 -2
  35. package/dist/cjs/types/components/Slider/Track.d.ts.map +1 -1
  36. package/dist/cjs/types/components/Slider/index.d.ts +5 -1
  37. package/dist/cjs/types/components/Slider/index.d.ts.map +1 -1
  38. package/dist/cjs/types/components/WheelObserver/index.d.ts +3 -3
  39. package/dist/cjs/types/components/WheelObserver/index.d.ts.map +1 -1
  40. package/dist/cjs/types/components/XYPad/Area.d.ts +3 -3
  41. package/dist/cjs/types/components/XYPad/Area.d.ts.map +1 -1
  42. package/dist/cjs/types/components/XYPad/Thumb.d.ts +1 -0
  43. package/dist/cjs/types/components/XYPad/Thumb.d.ts.map +1 -1
  44. package/dist/cjs/types/components/XYPad/index.d.ts +6 -1
  45. package/dist/cjs/types/components/XYPad/index.d.ts.map +1 -1
  46. package/dist/cjs/types/hooks/useDrag.d.ts.map +1 -1
  47. package/dist/cjs/types/hooks/useDragWithElement.d.ts +16 -0
  48. package/dist/cjs/types/hooks/useDragWithElement.d.ts.map +1 -0
  49. package/dist/cjs/types/hooks/useInterval.d.ts +2 -0
  50. package/dist/cjs/types/hooks/useInterval.d.ts.map +1 -0
  51. package/dist/cjs/types/hooks/useLongPress.d.ts +2 -0
  52. package/dist/cjs/types/hooks/useLongPress.d.ts.map +1 -0
  53. package/dist/cjs/types/index.d.ts +5 -0
  54. package/dist/cjs/types/index.d.ts.map +1 -1
  55. package/dist/esm/index.js +706 -396
  56. package/dist/esm/index.js.map +1 -1
  57. package/dist/esm/types/components/AnimationCanvas/index.d.ts +2 -2
  58. package/dist/esm/types/components/AnimationCanvas/index.d.ts.map +1 -1
  59. package/dist/esm/types/components/AnimationKnob/index.d.ts +10 -3
  60. package/dist/esm/types/components/AnimationKnob/index.d.ts.map +1 -1
  61. package/dist/esm/types/components/Knob/index.d.ts +25 -3
  62. package/dist/esm/types/components/Knob/index.d.ts.map +1 -1
  63. package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts +2 -0
  64. package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts.map +1 -1
  65. package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts +2 -0
  66. package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts.map +1 -1
  67. package/dist/esm/types/components/NumberInput/InternalInput.d.ts +18 -0
  68. package/dist/esm/types/components/NumberInput/InternalInput.d.ts.map +1 -0
  69. package/dist/esm/types/components/NumberInput/Stepper.d.ts +2 -0
  70. package/dist/esm/types/components/NumberInput/Stepper.d.ts.map +1 -1
  71. package/dist/esm/types/components/NumberInput/context.d.ts +3 -2
  72. package/dist/esm/types/components/NumberInput/context.d.ts.map +1 -1
  73. package/dist/esm/types/components/NumberInput/index.d.ts +12 -3
  74. package/dist/esm/types/components/NumberInput/index.d.ts.map +1 -1
  75. package/dist/esm/types/components/Piano/KeyLabel.d.ts +1 -3
  76. package/dist/esm/types/components/Piano/KeyLabel.d.ts.map +1 -1
  77. package/dist/esm/types/components/Piano/context.d.ts +24 -0
  78. package/dist/esm/types/components/Piano/context.d.ts.map +1 -0
  79. package/dist/esm/types/components/Piano/index.d.ts +15 -12
  80. package/dist/esm/types/components/Piano/index.d.ts.map +1 -1
  81. package/dist/esm/types/components/Piano/key.d.ts +14 -19
  82. package/dist/esm/types/components/Piano/key.d.ts.map +1 -1
  83. package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts +2 -2
  84. package/dist/esm/types/components/Slider/Scale.d.ts +1 -3
  85. package/dist/esm/types/components/Slider/Scale.d.ts.map +1 -1
  86. package/dist/esm/types/components/Slider/Thumb.d.ts +1 -0
  87. package/dist/esm/types/components/Slider/Thumb.d.ts.map +1 -1
  88. package/dist/esm/types/components/Slider/Track.d.ts +2 -2
  89. package/dist/esm/types/components/Slider/Track.d.ts.map +1 -1
  90. package/dist/esm/types/components/Slider/index.d.ts +5 -1
  91. package/dist/esm/types/components/Slider/index.d.ts.map +1 -1
  92. package/dist/esm/types/components/WheelObserver/index.d.ts +3 -3
  93. package/dist/esm/types/components/WheelObserver/index.d.ts.map +1 -1
  94. package/dist/esm/types/components/XYPad/Area.d.ts +3 -3
  95. package/dist/esm/types/components/XYPad/Area.d.ts.map +1 -1
  96. package/dist/esm/types/components/XYPad/Thumb.d.ts +1 -0
  97. package/dist/esm/types/components/XYPad/Thumb.d.ts.map +1 -1
  98. package/dist/esm/types/components/XYPad/index.d.ts +6 -1
  99. package/dist/esm/types/components/XYPad/index.d.ts.map +1 -1
  100. package/dist/esm/types/hooks/useDrag.d.ts.map +1 -1
  101. package/dist/esm/types/hooks/useDragWithElement.d.ts +16 -0
  102. package/dist/esm/types/hooks/useDragWithElement.d.ts.map +1 -0
  103. package/dist/esm/types/hooks/useInterval.d.ts +2 -0
  104. package/dist/esm/types/hooks/useInterval.d.ts.map +1 -0
  105. package/dist/esm/types/hooks/useLongPress.d.ts +2 -0
  106. package/dist/esm/types/hooks/useLongPress.d.ts.map +1 -0
  107. package/dist/esm/types/index.d.ts +5 -0
  108. package/dist/esm/types/index.d.ts.map +1 -1
  109. package/dist/styles/index.css +117 -33
  110. package/package.json +3 -2
  111. package/src/components/AnimationCanvas/index.tsx +14 -6
  112. package/src/components/AnimationKnob/index.tsx +216 -187
  113. package/src/components/Knob/index.tsx +244 -171
  114. package/src/components/NumberInput/DecrementStepper.tsx +9 -2
  115. package/src/components/NumberInput/IncrementStepper.tsx +9 -2
  116. package/src/components/NumberInput/InternalInput.tsx +219 -0
  117. package/src/components/NumberInput/Stepper.tsx +2 -0
  118. package/src/components/NumberInput/context.tsx +20 -8
  119. package/src/components/NumberInput/index.css +76 -22
  120. package/src/components/NumberInput/index.tsx +94 -162
  121. package/src/components/Piano/KeyLabel.tsx +9 -5
  122. package/src/components/Piano/context.tsx +108 -0
  123. package/src/components/Piano/index.css +24 -6
  124. package/src/components/Piano/index.tsx +192 -122
  125. package/src/components/Piano/key.tsx +215 -0
  126. package/src/components/Piano/keyboardShortcuts.ts +2 -2
  127. package/src/components/Slider/Scale.tsx +1 -4
  128. package/src/components/Slider/Thumb.tsx +4 -0
  129. package/src/components/Slider/Track.tsx +5 -2
  130. package/src/components/Slider/index.css +5 -2
  131. package/src/components/Slider/index.tsx +84 -71
  132. package/src/components/WheelObserver/index.tsx +4 -2
  133. package/src/components/XYPad/Area.tsx +7 -6
  134. package/src/components/XYPad/Thumb.tsx +6 -0
  135. package/src/components/XYPad/index.css +12 -2
  136. package/src/components/XYPad/index.tsx +116 -90
  137. package/src/hooks/useDrag.ts +6 -7
  138. package/src/hooks/useDragWithElement.ts +76 -0
  139. package/src/hooks/useInterval.ts +27 -0
  140. package/src/hooks/useLongPress.ts +31 -0
  141. package/src/index.ts +6 -0
  142. package/src/styles/index.css +0 -1
  143. package/dist/cjs/types/components/Piano/BlackKey.d.ts +0 -5
  144. package/dist/cjs/types/components/Piano/BlackKey.d.ts.map +0 -1
  145. package/dist/cjs/types/components/Piano/WhiteKey.d.ts +0 -7
  146. package/dist/cjs/types/components/Piano/WhiteKey.d.ts.map +0 -1
  147. package/dist/esm/types/components/Piano/BlackKey.d.ts +0 -5
  148. package/dist/esm/types/components/Piano/BlackKey.d.ts.map +0 -1
  149. package/dist/esm/types/components/Piano/WhiteKey.d.ts +0 -7
  150. package/dist/esm/types/components/Piano/WhiteKey.d.ts.map +0 -1
  151. package/src/components/Piano/BlackKey.tsx +0 -118
  152. package/src/components/Piano/WhiteKey.tsx +0 -120
  153. package/src/components/Piano/key.ts +0 -46
@@ -19,14 +19,17 @@ import {
19
19
  styleHelper,
20
20
  } from '@tremolo-ui/functions'
21
21
 
22
- import { useEventListener } from '../../hooks/useEventListener'
22
+ import { useDragWithElement } from '../../hooks/useDragWithElement'
23
23
  import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
24
24
  import { addNoSelect, removeNoSelect } from '../_util'
25
25
 
26
26
  import { XYPadArea, AreaProps } from './Area'
27
27
  import { XYPadThumb, XYPadThumbMethods, ThumbProps } from './Thumb'
28
28
 
29
- /** @category XYPad */
29
+ /**
30
+ * Two-dimensional slider component.
31
+ * @category XYPad
32
+ */
30
33
  export interface ValueOptions {
31
34
  // required
32
35
  value: number
@@ -56,6 +59,8 @@ export interface XYPadProps {
56
59
  disabled?: boolean
57
60
  readonly?: boolean
58
61
  onChange?: (valueX: number, valueY: number) => void
62
+ onDragStart?: (valueX: number, valueY: number) => void
63
+ onDragEnd?: (valueX: number, valueY: number) => void
59
64
  /** \<XYPadThumb /> | \<XYPadArea /> */
60
65
  children?: ReactElement | ReactElement[]
61
66
  }
@@ -92,6 +97,12 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
92
97
  disabled = false,
93
98
  readonly = false,
94
99
  onChange,
100
+ onDragStart,
101
+ onDragEnd,
102
+ onPointerDown,
103
+ onKeyDown,
104
+ onFocus,
105
+ onBlur,
95
106
  children,
96
107
  ...props
97
108
  }: Props,
@@ -107,7 +118,6 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
107
118
  // -- state and ref ---
108
119
  const areaElementRef = useRef<HTMLDivElement>(null)
109
120
  const thumbRef = useRef<XYPadThumbMethods>(null)
110
- const thumbDragged = useRef(false)
111
121
 
112
122
  // --- interpret props ---
113
123
  const nx = normalizeValue(x.value, x.min, x.max, x.skew)
@@ -115,8 +125,8 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
115
125
  const percentX = toFixed((x.reverse ? 1 - nx : nx) * 100)
116
126
  const percentY = toFixed((y.reverse ? 1 - ny : ny) * 100)
117
127
 
118
- let areaProps: AreaProps = {},
119
- thumbProps: ThumbProps = {}
128
+ let areaProps: AreaProps = {}
129
+ let thumbProps: ThumbProps = {}
120
130
  if (children != undefined) {
121
131
  React.Children.forEach(children, (child) => {
122
132
  if (React.isValidElement(child)) {
@@ -134,35 +144,53 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
134
144
  }
135
145
 
136
146
  // --- internal functions ---
137
- const handleValue = (
138
- event: MouseEvent | React.PointerEvent<HTMLDivElement> | TouchEvent,
139
- ) => {
140
- if (
141
- !areaElementRef.current ||
142
- !thumbDragged.current ||
143
- !onChange ||
144
- readonly
145
- )
146
- return
147
- const isTouch = event instanceof TouchEvent
148
- if (isTouch && event.cancelable) event.preventDefault()
149
- if (bodyNoSelect) addNoSelect()
150
- const {
151
- left: x1,
152
- top: y1,
153
- right: x2,
154
- bottom: y2,
155
- } = areaElementRef.current.getBoundingClientRect()
156
- const mouseX = isTouch ? event.touches[0].clientX : event.clientX
157
- const mouseY = isTouch ? event.touches[0].clientY : event.clientY
158
- const nx = normalizeValue(mouseX, x1, x2)
159
- const ny = normalizeValue(mouseY, y1, y2)
160
- const vx = rawValue(x.reverse ? 1 - nx : nx, x.min, x.max, x.skew)
161
- const vy = rawValue(y.reverse ? 1 - ny : ny, y.min, y.max, y.skew)
162
- const newX = clamp(stepValue(vx, x.step), x.min, x.max)
163
- const newY = clamp(stepValue(vy, y.step), y.min, y.max)
164
- onChange(newX, newY)
165
- }
147
+ const onDrag = useCallback(
148
+ (nx: number, ny: number) => {
149
+ if (!onChange || readonly) return
150
+ const vx = rawValue(x.reverse ? 1 - nx : nx, x.min, x.max, x.skew)
151
+ const vy = rawValue(y.reverse ? 1 - ny : ny, y.min, y.max, y.skew)
152
+ const newX = clamp(stepValue(vx, x.step), x.min, x.max)
153
+ const newY = clamp(stepValue(vy, y.step), y.min, y.max)
154
+ onChange(newX, newY)
155
+ },
156
+ [
157
+ onChange,
158
+ readonly,
159
+ x.max,
160
+ x.min,
161
+ x.reverse,
162
+ x.skew,
163
+ x.step,
164
+ y.max,
165
+ y.min,
166
+ y.reverse,
167
+ y.skew,
168
+ y.step,
169
+ ],
170
+ )
171
+
172
+ const updateValueByEvent = useCallback(
173
+ (
174
+ eventType: InputEventOption[0],
175
+ target: Required<ValueOptions>,
176
+ x: number,
177
+ ) => {
178
+ let v
179
+ if (eventType == 'normalized') {
180
+ const n = normalizeValue(
181
+ target.value,
182
+ target.min,
183
+ target.max,
184
+ target.skew,
185
+ )
186
+ v = rawValue(n + x, target.min, target.max, target.skew)
187
+ } else {
188
+ v = target.value + x
189
+ }
190
+ return clamp(stepValue(v, target.step), target.min, target.max)
191
+ },
192
+ [],
193
+ )
166
194
 
167
195
  const handleKeyDown = useCallback(
168
196
  (event: React.KeyboardEvent<HTMLDivElement>) => {
@@ -176,23 +204,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
176
204
  let delta = target.keyboard[1]
177
205
  if (key == 'ArrowLeft' || key == 'ArrowUp') delta *= -1
178
206
  if (target.reverse) delta *= -1
179
- let v
180
- if (target.keyboard[0] == 'normalized') {
181
- const n = normalizeValue(
182
- target.value,
183
- target.min,
184
- target.max,
185
- target.skew,
186
- )
187
- v = rawValue(n + delta, target.min, target.max, target.skew)
188
- } else {
189
- v = target.value + delta
190
- }
191
- const newValue = clamp(
192
- stepValue(v, target.step),
193
- target.min,
194
- target.max,
195
- )
207
+ const newValue = updateValueByEvent(target.keyboard[0], target, delta)
196
208
  if (isHorizontal) {
197
209
  onChange(newValue, y.value)
198
210
  } else {
@@ -200,16 +212,46 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
200
212
  }
201
213
  }
202
214
  },
203
- [x, y, onChange, readonly],
215
+ [onChange, readonly, x, y, updateValueByEvent],
204
216
  )
205
217
 
206
218
  // --- hooks ---
207
- const touchMoveRefCallback = useRefCallbackEvent(
208
- 'touchmove',
209
- handleValue,
210
- { passive: false },
211
- [x, onChange, readonly],
212
- )
219
+ const [touchMoveRefCallback, pointerDownHandler] =
220
+ useDragWithElement<HTMLDivElement>({
221
+ baseElementRef: areaElementRef,
222
+ onDrag: onDrag,
223
+ onDragStart: (nx, ny) => {
224
+ if (readonly) return
225
+ if (bodyNoSelect) addNoSelect()
226
+ thumbRef.current?.focus()
227
+ const valueX = clamp(
228
+ stepValue(rawValue(nx, x.min, x.max, x.skew), x.step),
229
+ x.min,
230
+ x.max,
231
+ )
232
+ const valueY = clamp(
233
+ stepValue(rawValue(ny, y.min, y.max, y.skew), y.step),
234
+ y.min,
235
+ y.max,
236
+ )
237
+ onDragStart?.(valueX, valueY)
238
+ },
239
+ onDragEnd: (nx, ny) => {
240
+ if (readonly) return
241
+ if (bodyNoSelect) removeNoSelect()
242
+ const valueX = clamp(
243
+ stepValue(rawValue(nx, x.min, x.max, x.skew), x.step),
244
+ x.min,
245
+ x.max,
246
+ )
247
+ const valueY = clamp(
248
+ stepValue(rawValue(ny, y.min, y.max, y.skew), y.step),
249
+ y.min,
250
+ y.max,
251
+ )
252
+ onDragEnd?.(valueX, valueY)
253
+ },
254
+ })
213
255
 
214
256
  const wheelRefCallback = useRefCallbackEvent(
215
257
  'wheel',
@@ -221,23 +263,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
221
263
  let delta = target.wheel[1]
222
264
  if (event.deltaY < 0) delta *= -1
223
265
  if (target.reverse) delta *= -1
224
- let v
225
- if (target.wheel[0] == 'normalized') {
226
- const n = normalizeValue(
227
- target.value,
228
- target.min,
229
- target.max,
230
- target.skew,
231
- )
232
- v = rawValue(n + delta, target.min, target.max, target.skew)
233
- } else {
234
- v = target.value + delta
235
- }
236
- const newValue = clamp(
237
- stepValue(v, target.step),
238
- target.min,
239
- target.max,
240
- )
266
+ const newValue = updateValueByEvent(target.wheel[0], target, delta)
241
267
  if (event.shiftKey) {
242
268
  onChange(newValue, y.value)
243
269
  } else {
@@ -247,18 +273,9 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
247
273
  {
248
274
  passive: false,
249
275
  },
250
- [x, y],
276
+ [x, y, onChange, readonly, updateValueByEvent],
251
277
  )
252
278
 
253
- useEventListener(globalThis.window, 'mousemove', (event) => {
254
- handleValue(event)
255
- })
256
-
257
- useEventListener(globalThis.window, 'mouseup', () => {
258
- thumbDragged.current = false
259
- if (bodyNoSelect) removeNoSelect()
260
- })
261
-
262
279
  useImperativeHandle(ref, () => {
263
280
  return {
264
281
  focus() {
@@ -288,12 +305,21 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
288
305
  ...style,
289
306
  }}
290
307
  onPointerDown={(event) => {
291
- thumbDragged.current = true
292
- handleValue(event)
308
+ pointerDownHandler(event)
309
+ onPointerDown?.(event)
310
+ }}
311
+ onKeyDown={(event) => {
312
+ handleKeyDown(event)
313
+ onKeyDown?.(event)
314
+ }}
315
+ onFocus={(event) => {
316
+ thumbRef.current?.focus()
317
+ onFocus?.(event)
318
+ }}
319
+ onBlur={(event) => {
320
+ thumbRef.current?.blur()
321
+ onBlur?.(event)
293
322
  }}
294
- onKeyDown={handleKeyDown}
295
- onFocus={thumbRef.current?.focus}
296
- onBlur={thumbRef.current?.blur}
297
323
  {...props}
298
324
  >
299
325
  <div className="tremolo-xy-pad-area-wrapper" ref={areaElementRef}>
@@ -52,13 +52,12 @@ export function useDrag<T extends Element>({
52
52
  dragOffsetY.current = screenY
53
53
  }
54
54
  if (Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold) return
55
- if (onDrag)
56
- onDrag(
57
- screenX - dragStartX.current,
58
- screenY - dragStartY.current,
59
- deltaX,
60
- deltaY,
61
- )
55
+ onDrag(
56
+ screenX - dragStartX.current,
57
+ screenY - dragStartY.current,
58
+ deltaX,
59
+ deltaY,
60
+ )
62
61
  },
63
62
  [threshold, onDrag],
64
63
  )
@@ -0,0 +1,76 @@
1
+ import { useRef, useCallback, RefObject } from 'react'
2
+
3
+ import { normalizeValue } from '@tremolo-ui/functions'
4
+
5
+ import { useEventListener } from './useEventListener'
6
+ import { useRefCallbackEvent } from './useRefCallbackEvent'
7
+
8
+ interface Props<T extends Element> {
9
+ baseElementRef: RefObject<T | null>
10
+ onDrag: (normalizedX: number, normalizedY: number) => void
11
+ onDragStart?: (normalizedX: number, normalizedY: number) => void
12
+ onDragEnd?: (normalizedX: number, normalizedY: number) => void
13
+ }
14
+
15
+ /**
16
+ * @returns [refCallback, pointerDownHandler]
17
+ */
18
+ export function useDragWithElement<T extends Element>({
19
+ baseElementRef,
20
+ onDrag,
21
+ onDragStart,
22
+ onDragEnd,
23
+ }: Props<T>): [
24
+ (div: EventTarget | null) => void,
25
+ (event: React.PointerEvent<T>) => void,
26
+ ] {
27
+ const dragged = useRef(false)
28
+ const normalizedX = useRef(0)
29
+ const normalizedY = useRef(0)
30
+
31
+ const handleDrag = useCallback(
32
+ (event: MouseEvent | React.PointerEvent<T> | TouchEvent) => {
33
+ if (!baseElementRef.current || !dragged.current) {
34
+ return
35
+ }
36
+ const isTouch = event instanceof TouchEvent
37
+ if (isTouch && event.cancelable) event.preventDefault()
38
+ const { left, top, right, bottom } =
39
+ baseElementRef.current.getBoundingClientRect()
40
+ const mouseX = isTouch ? event.touches[0].clientX : event.clientX
41
+ const mouseY = isTouch ? event.touches[0].clientY : event.clientY
42
+ const nx = normalizeValue(mouseX, left, right)
43
+ const ny = normalizeValue(mouseY, top, bottom)
44
+ normalizedX.current = nx
45
+ normalizedY.current = ny
46
+ onDrag(nx, ny)
47
+ },
48
+ [onDrag, baseElementRef],
49
+ )
50
+
51
+ const refHandler = useRefCallbackEvent(
52
+ 'touchmove',
53
+ handleDrag,
54
+ { passive: false },
55
+ [onDrag],
56
+ )
57
+
58
+ const pointerDownHandler = useCallback(
59
+ (event: React.PointerEvent<T>) => {
60
+ dragged.current = true
61
+ handleDrag(event)
62
+ onDragStart?.(normalizedX.current, normalizedY.current)
63
+ },
64
+ [handleDrag, onDragStart],
65
+ )
66
+
67
+ useEventListener(globalThis.window, 'pointermove', handleDrag)
68
+
69
+ useEventListener(globalThis.window, 'pointerup', () => {
70
+ if (!dragged.current) return
71
+ dragged.current = false
72
+ onDragEnd?.(normalizedX.current, normalizedY.current)
73
+ })
74
+
75
+ return [refHandler, pointerDownHandler]
76
+ }
@@ -0,0 +1,27 @@
1
+ import { useEffect, useRef } from 'react'
2
+
3
+ export function useInterval(callback: () => void, delay: number | null) {
4
+ const savedCallback = useRef(callback)
5
+
6
+ // Remember the latest callback if it changes.
7
+ useEffect(() => {
8
+ savedCallback.current = callback
9
+ }, [callback])
10
+
11
+ // Set up the interval.
12
+ useEffect(() => {
13
+ // Don't schedule if no delay is specified.
14
+ // Note: 0 is a valid value for delay.
15
+ if (delay === null) {
16
+ return
17
+ }
18
+
19
+ const id = setInterval(() => {
20
+ savedCallback.current()
21
+ }, delay)
22
+
23
+ return () => {
24
+ clearInterval(id)
25
+ }
26
+ }, [delay])
27
+ }
@@ -0,0 +1,31 @@
1
+ import { useCallback, useState } from 'react'
2
+
3
+ import { useEventListener } from './useEventListener'
4
+ import { useInterval } from './useInterval'
5
+
6
+ export function useLongPress(
7
+ callback: () => void,
8
+ initialDelay = 500,
9
+ interval = 40,
10
+ ) {
11
+ const [pressed, setPressed] = useState(false)
12
+ const [delay, setDelay] = useState(initialDelay)
13
+
14
+ useInterval(
15
+ () => {
16
+ callback()
17
+ setDelay(interval)
18
+ },
19
+ pressed ? delay : null,
20
+ )
21
+
22
+ useEventListener(globalThis.window, 'pointerup', () => {
23
+ setPressed(false)
24
+ setDelay(initialDelay)
25
+ })
26
+
27
+ return useCallback(() => {
28
+ callback()
29
+ setPressed(true)
30
+ }, [callback])
31
+ }
package/src/index.ts CHANGED
@@ -14,3 +14,9 @@ export * from './components/Piano'
14
14
  export * from './components/Slider'
15
15
  export * from './components/WheelObserver'
16
16
  export * from './components/XYPad'
17
+
18
+ export * from './hooks/useAnimationFrame'
19
+ export * from './hooks/useDrag'
20
+ export * from './hooks/useEventListener'
21
+ export * from './hooks/useInterval'
22
+ export * from './hooks/useLongPress'
@@ -1,6 +1,5 @@
1
1
  :root {
2
2
  --tremolo-theme-color: #4e76e6;
3
- --tremolo-theme-color-rgb: 78, 118, 230;
4
3
  }
5
4
 
6
5
  .tremolo-no-select {
@@ -1,5 +0,0 @@
1
- import React from 'react';
2
- import { KeyMethods, KeyProps } from './key';
3
- /** @category Piano */
4
- export declare const BlackKey: React.ForwardRefExoticComponent<KeyProps & Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, keyof KeyProps> & React.RefAttributes<KeyMethods>>;
5
- //# sourceMappingURL=BlackKey.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"BlackKey.d.ts","sourceRoot":"","sources":["../../../../../src/components/Piano/BlackKey.tsx"],"names":[],"mappings":"AACA,OAAO,KAKN,MAAM,OAAO,CAAA;AAEd,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAM5C,sBAAsB;AACtB,eAAO,MAAM,QAAQ,gMAsGpB,CAAA"}
@@ -1,7 +0,0 @@
1
- import React from 'react';
2
- import { KeyMethods, KeyProps } from './key';
3
- /** @category Piano */
4
- export declare const defaultWhiteKeyWidth = 40;
5
- /** @category Piano */
6
- export declare const WhiteKey: React.ForwardRefExoticComponent<KeyProps & Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, keyof KeyProps> & React.RefAttributes<KeyMethods>>;
7
- //# sourceMappingURL=WhiteKey.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"WhiteKey.d.ts","sourceRoot":"","sources":["../../../../../src/components/Piano/WhiteKey.tsx"],"names":[],"mappings":"AACA,OAAO,KAKN,MAAM,OAAO,CAAA;AAEd,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAK5C,sBAAsB;AACtB,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC,sBAAsB;AACtB,eAAO,MAAM,QAAQ,gMAsGpB,CAAA"}
@@ -1,5 +0,0 @@
1
- import React from 'react';
2
- import { KeyMethods, KeyProps } from './key';
3
- /** @category Piano */
4
- export declare const BlackKey: React.ForwardRefExoticComponent<KeyProps & Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, keyof KeyProps> & React.RefAttributes<KeyMethods>>;
5
- //# sourceMappingURL=BlackKey.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"BlackKey.d.ts","sourceRoot":"","sources":["../../../../../src/components/Piano/BlackKey.tsx"],"names":[],"mappings":"AACA,OAAO,KAKN,MAAM,OAAO,CAAA;AAEd,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAM5C,sBAAsB;AACtB,eAAO,MAAM,QAAQ,gMAsGpB,CAAA"}
@@ -1,7 +0,0 @@
1
- import React from 'react';
2
- import { KeyMethods, KeyProps } from './key';
3
- /** @category Piano */
4
- export declare const defaultWhiteKeyWidth = 40;
5
- /** @category Piano */
6
- export declare const WhiteKey: React.ForwardRefExoticComponent<KeyProps & Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, keyof KeyProps> & React.RefAttributes<KeyMethods>>;
7
- //# sourceMappingURL=WhiteKey.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"WhiteKey.d.ts","sourceRoot":"","sources":["../../../../../src/components/Piano/WhiteKey.tsx"],"names":[],"mappings":"AACA,OAAO,KAKN,MAAM,OAAO,CAAA;AAEd,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAK5C,sBAAsB;AACtB,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC,sBAAsB;AACtB,eAAO,MAAM,QAAQ,gMAsGpB,CAAA"}
@@ -1,118 +0,0 @@
1
- import clsx from 'clsx'
2
- import React, {
3
- ComponentPropsWithoutRef,
4
- forwardRef,
5
- useImperativeHandle,
6
- useState,
7
- } from 'react'
8
-
9
- import { KeyMethods, KeyProps } from './key'
10
- import { KeyLabel, KeyLabelProps } from './KeyLabel'
11
- import { defaultWhiteKeyWidth } from './WhiteKey'
12
-
13
- type Props = KeyProps & Omit<ComponentPropsWithoutRef<'div'>, keyof KeyProps>
14
-
15
- /** @category Piano */
16
- export const BlackKey = forwardRef<KeyMethods, Props>(
17
- (
18
- {
19
- width = defaultWhiteKeyWidth * 0.65,
20
- height = '60%',
21
- className,
22
- style,
23
- children,
24
- __glissando,
25
- __position,
26
- __note,
27
- __disabled,
28
- __index,
29
- __fill,
30
- __width,
31
- __onPlayNote,
32
- __onStopNote,
33
- __label,
34
- ...props
35
- }: Props,
36
- ref,
37
- ) => {
38
- const [played, setPlayed] = useState(false)
39
-
40
- useImperativeHandle(ref, () => {
41
- return {
42
- play() {
43
- if (__disabled) return
44
- setPlayed(true)
45
- if (__onPlayNote) __onPlayNote(__note!)
46
- },
47
- stop() {
48
- setPlayed(false)
49
- if (__onStopNote) __onStopNote(__note!)
50
- },
51
- played() {
52
- return played
53
- },
54
- }
55
- }, [__disabled, __note, __onPlayNote, __onStopNote, played])
56
-
57
- let keyLabelProps: KeyLabelProps = {}
58
- if (children != undefined) {
59
- React.Children.forEach(children, (child) => {
60
- if (React.isValidElement(child)) {
61
- if (child.type == KeyLabel) {
62
- keyLabelProps = child.props as KeyLabelProps
63
- } else {
64
- throw new Error('only <KeyLabel>')
65
- }
66
- } else {
67
- throw new Error('children is an invalid element.')
68
- }
69
- })
70
- }
71
-
72
- return (
73
- <div
74
- className={clsx(`tremolo-piano-black-key`, className)}
75
- aria-disabled={__disabled}
76
- data-active={played}
77
- style={{
78
- left: __position,
79
- width: __fill ? __width : (width ?? __width),
80
- height: height,
81
- ...style,
82
- }}
83
- onPointerDown={() => {
84
- if (__disabled) return
85
- setPlayed(true)
86
- if (__onPlayNote) __onPlayNote(__note!)
87
- }}
88
- onPointerEnter={() => {
89
- if (__disabled) return
90
- if (__glissando) {
91
- setPlayed(true)
92
- if (__onPlayNote) __onPlayNote(__note!)
93
- }
94
- }}
95
- onPointerUp={() => {
96
- if (__disabled) return
97
- setPlayed(false)
98
- if (__onStopNote) __onStopNote(__note!)
99
- }}
100
- onPointerLeave={() => {
101
- if (__disabled) return
102
- if (__glissando) {
103
- setPlayed(false)
104
- if (__onStopNote) __onStopNote(__note!)
105
- }
106
- }}
107
- {...props}
108
- >
109
- <KeyLabel
110
- __note={__note}
111
- __label={__label}
112
- __index={__index}
113
- {...keyLabelProps}
114
- />
115
- </div>
116
- )
117
- },
118
- )