@tremolo-ui/react 0.0.11 → 0.0.12

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 (130) hide show
  1. package/dist/cjs/index.js +428 -196
  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 +13 -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/BlackKey.d.ts.map +1 -1
  22. package/dist/cjs/types/components/Piano/WhiteKey.d.ts.map +1 -1
  23. package/dist/cjs/types/components/Piano/index.d.ts +7 -4
  24. package/dist/cjs/types/components/Piano/index.d.ts.map +1 -1
  25. package/dist/cjs/types/components/Piano/key.d.ts +4 -0
  26. package/dist/cjs/types/components/Piano/key.d.ts.map +1 -1
  27. package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts +2 -2
  28. package/dist/cjs/types/components/Slider/Scale.d.ts +1 -3
  29. package/dist/cjs/types/components/Slider/Scale.d.ts.map +1 -1
  30. package/dist/cjs/types/components/Slider/Thumb.d.ts +1 -0
  31. package/dist/cjs/types/components/Slider/Thumb.d.ts.map +1 -1
  32. package/dist/cjs/types/components/Slider/Track.d.ts +2 -2
  33. package/dist/cjs/types/components/Slider/Track.d.ts.map +1 -1
  34. package/dist/cjs/types/components/Slider/index.d.ts +5 -1
  35. package/dist/cjs/types/components/Slider/index.d.ts.map +1 -1
  36. package/dist/cjs/types/components/WheelObserver/index.d.ts +3 -3
  37. package/dist/cjs/types/components/WheelObserver/index.d.ts.map +1 -1
  38. package/dist/cjs/types/components/XYPad/Area.d.ts +3 -3
  39. package/dist/cjs/types/components/XYPad/Area.d.ts.map +1 -1
  40. package/dist/cjs/types/components/XYPad/Thumb.d.ts +1 -0
  41. package/dist/cjs/types/components/XYPad/Thumb.d.ts.map +1 -1
  42. package/dist/cjs/types/components/XYPad/index.d.ts +6 -1
  43. package/dist/cjs/types/components/XYPad/index.d.ts.map +1 -1
  44. package/dist/cjs/types/hooks/useInterval.d.ts +2 -0
  45. package/dist/cjs/types/hooks/useInterval.d.ts.map +1 -0
  46. package/dist/cjs/types/hooks/useLongPress.d.ts +2 -0
  47. package/dist/cjs/types/hooks/useLongPress.d.ts.map +1 -0
  48. package/dist/cjs/types/index.d.ts +5 -0
  49. package/dist/cjs/types/index.d.ts.map +1 -1
  50. package/dist/esm/index.js +424 -197
  51. package/dist/esm/index.js.map +1 -1
  52. package/dist/esm/types/components/AnimationCanvas/index.d.ts +2 -2
  53. package/dist/esm/types/components/AnimationCanvas/index.d.ts.map +1 -1
  54. package/dist/esm/types/components/AnimationKnob/index.d.ts +10 -3
  55. package/dist/esm/types/components/AnimationKnob/index.d.ts.map +1 -1
  56. package/dist/esm/types/components/Knob/index.d.ts +13 -3
  57. package/dist/esm/types/components/Knob/index.d.ts.map +1 -1
  58. package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts +2 -0
  59. package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts.map +1 -1
  60. package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts +2 -0
  61. package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts.map +1 -1
  62. package/dist/esm/types/components/NumberInput/InternalInput.d.ts +18 -0
  63. package/dist/esm/types/components/NumberInput/InternalInput.d.ts.map +1 -0
  64. package/dist/esm/types/components/NumberInput/Stepper.d.ts +2 -0
  65. package/dist/esm/types/components/NumberInput/Stepper.d.ts.map +1 -1
  66. package/dist/esm/types/components/NumberInput/context.d.ts +3 -2
  67. package/dist/esm/types/components/NumberInput/context.d.ts.map +1 -1
  68. package/dist/esm/types/components/NumberInput/index.d.ts +12 -3
  69. package/dist/esm/types/components/NumberInput/index.d.ts.map +1 -1
  70. package/dist/esm/types/components/Piano/BlackKey.d.ts.map +1 -1
  71. package/dist/esm/types/components/Piano/WhiteKey.d.ts.map +1 -1
  72. package/dist/esm/types/components/Piano/index.d.ts +7 -4
  73. package/dist/esm/types/components/Piano/index.d.ts.map +1 -1
  74. package/dist/esm/types/components/Piano/key.d.ts +4 -0
  75. package/dist/esm/types/components/Piano/key.d.ts.map +1 -1
  76. package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts +2 -2
  77. package/dist/esm/types/components/Slider/Scale.d.ts +1 -3
  78. package/dist/esm/types/components/Slider/Scale.d.ts.map +1 -1
  79. package/dist/esm/types/components/Slider/Thumb.d.ts +1 -0
  80. package/dist/esm/types/components/Slider/Thumb.d.ts.map +1 -1
  81. package/dist/esm/types/components/Slider/Track.d.ts +2 -2
  82. package/dist/esm/types/components/Slider/Track.d.ts.map +1 -1
  83. package/dist/esm/types/components/Slider/index.d.ts +5 -1
  84. package/dist/esm/types/components/Slider/index.d.ts.map +1 -1
  85. package/dist/esm/types/components/WheelObserver/index.d.ts +3 -3
  86. package/dist/esm/types/components/WheelObserver/index.d.ts.map +1 -1
  87. package/dist/esm/types/components/XYPad/Area.d.ts +3 -3
  88. package/dist/esm/types/components/XYPad/Area.d.ts.map +1 -1
  89. package/dist/esm/types/components/XYPad/Thumb.d.ts +1 -0
  90. package/dist/esm/types/components/XYPad/Thumb.d.ts.map +1 -1
  91. package/dist/esm/types/components/XYPad/index.d.ts +6 -1
  92. package/dist/esm/types/components/XYPad/index.d.ts.map +1 -1
  93. package/dist/esm/types/hooks/useInterval.d.ts +2 -0
  94. package/dist/esm/types/hooks/useInterval.d.ts.map +1 -0
  95. package/dist/esm/types/hooks/useLongPress.d.ts +2 -0
  96. package/dist/esm/types/hooks/useLongPress.d.ts.map +1 -0
  97. package/dist/esm/types/index.d.ts +5 -0
  98. package/dist/esm/types/index.d.ts.map +1 -1
  99. package/dist/styles/index.css +115 -33
  100. package/package.json +2 -2
  101. package/src/components/AnimationCanvas/index.tsx +14 -6
  102. package/src/components/AnimationKnob/index.tsx +216 -187
  103. package/src/components/Knob/index.tsx +230 -171
  104. package/src/components/NumberInput/DecrementStepper.tsx +9 -2
  105. package/src/components/NumberInput/IncrementStepper.tsx +9 -2
  106. package/src/components/NumberInput/InternalInput.tsx +219 -0
  107. package/src/components/NumberInput/Stepper.tsx +2 -0
  108. package/src/components/NumberInput/context.tsx +20 -8
  109. package/src/components/NumberInput/index.css +76 -22
  110. package/src/components/NumberInput/index.tsx +94 -162
  111. package/src/components/Piano/BlackKey.tsx +14 -0
  112. package/src/components/Piano/WhiteKey.tsx +14 -0
  113. package/src/components/Piano/index.css +22 -6
  114. package/src/components/Piano/index.tsx +19 -21
  115. package/src/components/Piano/key.ts +5 -0
  116. package/src/components/Piano/keyboardShortcuts.ts +2 -2
  117. package/src/components/Slider/Scale.tsx +1 -4
  118. package/src/components/Slider/Thumb.tsx +4 -0
  119. package/src/components/Slider/Track.tsx +5 -2
  120. package/src/components/Slider/index.css +5 -2
  121. package/src/components/Slider/index.tsx +51 -26
  122. package/src/components/WheelObserver/index.tsx +4 -2
  123. package/src/components/XYPad/Area.tsx +7 -6
  124. package/src/components/XYPad/Thumb.tsx +6 -0
  125. package/src/components/XYPad/index.css +12 -2
  126. package/src/components/XYPad/index.tsx +57 -41
  127. package/src/hooks/useInterval.ts +27 -0
  128. package/src/hooks/useLongPress.ts +31 -0
  129. package/src/index.ts +6 -0
  130. package/src/styles/index.css +0 -1
@@ -63,12 +63,16 @@ export interface SliderProps {
63
63
  className?: string
64
64
  style?: CSSProperties
65
65
  onChange?: (value: number) => void
66
+ onDragStart?: (value: number) => void
67
+ onDragEnd?: (value: number) => void
66
68
  /** \<SliderThumb /> | \<SliderTrack /> */
67
69
  children?: ReactElement | ReactElement[]
68
70
  // ReactElement<typeof SliderThumb, typeof SliderTrack>[]
69
71
  }
70
72
 
71
- /** @category Slider */
73
+ /**
74
+ * @category Slider
75
+ */
72
76
  export interface SliderMethods {
73
77
  focus: () => void
74
78
  blur: () => void
@@ -91,15 +95,21 @@ export const Slider = forwardRef<SliderMethods, Props>(
91
95
  skew = 1,
92
96
  vertical = false,
93
97
  reverse = false,
94
- className,
95
- style,
96
98
  bodyNoSelect = true,
97
99
  wheel = ['raw', 1],
98
100
  keyboard = ['raw', 1],
99
101
  disabled = false,
100
102
  readonly = false,
101
103
  onChange,
104
+ onDragStart,
105
+ onDragEnd,
106
+ className,
107
+ style,
102
108
  children,
109
+ onFocus,
110
+ onBlur,
111
+ onPointerDown,
112
+ onKeyDown,
103
113
  ...props
104
114
  }: Props,
105
115
  ref,
@@ -171,9 +181,25 @@ export const Slider = forwardRef<SliderMethods, Props>(
171
181
  ? normalizeValue(mouseY, y1, y2)
172
182
  : normalizeValue(mouseX, x1, x2)
173
183
  const v = rawValue(displayReversed ? 1 - n : n, min, max, skew)
174
- onChange(clamp(stepValue(v, step), min, max))
184
+ const v2 = clamp(stepValue(v, step), min, max)
185
+ onChange(v2)
186
+ return v2
175
187
  }
176
188
 
189
+ const updateValueByEvent = useCallback(
190
+ (eventType: InputEventOption[0], x: number) => {
191
+ let newValue
192
+ if (eventType == 'normalized') {
193
+ const n = normalizeValue(value, min, max, skew)
194
+ newValue = rawValue(n + x, min, max, skew)
195
+ } else {
196
+ newValue = value + x
197
+ }
198
+ return clamp(stepValue(newValue, step), min, max)
199
+ },
200
+ [max, min, skew, step, value],
201
+ )
202
+
177
203
  const handleKeyDown = useCallback(
178
204
  (event: React.KeyboardEvent<HTMLDivElement>) => {
179
205
  if (!keyboard || !onChange || readonly) return
@@ -183,17 +209,10 @@ export const Slider = forwardRef<SliderMethods, Props>(
183
209
  let x =
184
210
  key == 'ArrowRight' || key == 'ArrowUp' ? keyboard[1] : -keyboard[1]
185
211
  if (reverse) x *= -1
186
- let v
187
- if (keyboard[0] == 'normalized') {
188
- const n = normalizeValue(value, min, max, skew)
189
- v = rawValue(n + x, min, max, skew)
190
- } else {
191
- v = value + x
192
- }
193
- onChange(clamp(stepValue(v, step), min, max))
212
+ onChange(updateValueByEvent(keyboard[0], x))
194
213
  }
195
214
  },
196
- [value, min, max, step, skew, reverse, keyboard, onChange, readonly],
215
+ [keyboard, onChange, readonly, reverse, updateValueByEvent],
197
216
  )
198
217
 
199
218
  // --- hooks ---
@@ -217,19 +236,12 @@ export const Slider = forwardRef<SliderMethods, Props>(
217
236
  x = event.deltaY > 0 ? -wheel[1] : wheel[1]
218
237
  }
219
238
  if (vertical && reverse) x *= -1
220
- let v
221
- if (wheel[0] == 'normalized') {
222
- const n = normalizeValue(value, min, max, skew)
223
- v = rawValue(n + x, min, max, skew)
224
- } else {
225
- v = value + x
226
- }
227
- onChange(clamp(stepValue(v, step), min, max))
239
+ onChange(updateValueByEvent(wheel[0], x))
228
240
  },
229
241
  {
230
242
  passive: false,
231
243
  },
232
- [wheel, value, min, max, skew, step],
244
+ [wheel, vertical, readonly, onChange, updateValueByEvent],
233
245
  )
234
246
 
235
247
  useEventListener(globalThis.window, 'mousemove', (event) => {
@@ -237,7 +249,9 @@ export const Slider = forwardRef<SliderMethods, Props>(
237
249
  })
238
250
 
239
251
  useEventListener(globalThis.window, 'mouseup', () => {
252
+ if (!thumbDragged.current || readonly) return
240
253
  thumbDragged.current = false
254
+ onDragEnd?.(value)
241
255
  if (bodyNoSelect) removeNoSelect()
242
256
  })
243
257
 
@@ -283,11 +297,22 @@ export const Slider = forwardRef<SliderMethods, Props>(
283
297
  }}
284
298
  onPointerDown={(event) => {
285
299
  thumbDragged.current = true
286
- handleValue(event)
300
+ const v = handleValue(event)
301
+ if (!readonly) onDragStart?.(v ?? value)
302
+ onPointerDown?.(event)
303
+ }}
304
+ onKeyDown={(event) => {
305
+ handleKeyDown(event)
306
+ onKeyDown?.(event)
307
+ }}
308
+ onFocus={(event) => {
309
+ thumbRef.current?.focus()
310
+ onFocus?.(event)
311
+ }}
312
+ onBlur={(event) => {
313
+ thumbRef.current?.blur()
314
+ onBlur?.(event)
287
315
  }}
288
- onKeyDown={handleKeyDown}
289
- onFocus={thumbRef.current?.focus}
290
- onBlur={thumbRef.current?.blur}
291
316
  {...props}
292
317
  >
293
318
  <div
@@ -14,7 +14,8 @@ import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
14
14
  * </WheelObserver>
15
15
  */
16
16
 
17
- interface Props<T extends ElementType> {
17
+ /** @category WheelObserver */
18
+ export interface WheelObserverProps<T extends ElementType> {
18
19
  /**
19
20
  * React.ElementType
20
21
  * @default div
@@ -27,7 +28,8 @@ interface Props<T extends ElementType> {
27
28
 
28
29
  /** @category WheelObserver */
29
30
  export function WheelObserver<T extends ElementType = 'div'>(
30
- props: Props<T> & Omit<ComponentPropsWithoutRef<T>, keyof Props<T>>,
31
+ props: WheelObserverProps<T> &
32
+ Omit<ComponentPropsWithoutRef<T>, keyof WheelObserverProps<T>>,
31
33
  ) {
32
34
  const { children, onWheel, as: Component = 'div', ...attributes } = props
33
35
 
@@ -1,11 +1,11 @@
1
1
  import clsx from 'clsx'
2
- import { CSSProperties, ReactElement } from 'react'
2
+ import { ComponentPropsWithoutRef, CSSProperties, ReactElement } from 'react'
3
3
 
4
4
  /** @category XYPad */
5
5
  export interface AreaProps {
6
6
  width?: number | string
7
7
  height?: number | string
8
- bg?: string
8
+ color?: string
9
9
  className?: string
10
10
  style?: CSSProperties
11
11
  children?: ReactElement
@@ -18,22 +18,23 @@ export interface AreaProps {
18
18
  export function XYPadArea({
19
19
  width = 120,
20
20
  height = 120,
21
- bg = '#eee',
21
+ color,
22
22
  children,
23
23
  className,
24
24
  style,
25
25
  __thumb,
26
- }: AreaProps) {
26
+ ...props
27
+ }: AreaProps & Omit<ComponentPropsWithoutRef<'div'>, keyof AreaProps>) {
27
28
  return (
28
29
  <div
29
30
  className={clsx('tremolo-xy-pad-area', className)}
30
31
  style={{
31
- position: 'relative',
32
+ ...{ '--color': color },
32
33
  width: width,
33
34
  height: height,
34
- background: bg,
35
35
  ...style,
36
36
  }}
37
+ {...props}
37
38
  >
38
39
  {children}
39
40
  {__thumb}
@@ -14,6 +14,8 @@ export interface ThumbProps {
14
14
  width?: number | string
15
15
  height?: number | string
16
16
 
17
+ color?: string
18
+
17
19
  wrapperClassName?: string
18
20
  wrapperStyle?: CSSProperties
19
21
 
@@ -41,6 +43,7 @@ export const XYPadThumb = forwardRef<XYPadThumbMethods, Props>(
41
43
  size,
42
44
  width = 22,
43
45
  height = 22,
46
+ color,
44
47
  children,
45
48
  wrapperClassName,
46
49
  wrapperStyle,
@@ -48,6 +51,7 @@ export const XYPadThumb = forwardRef<XYPadThumbMethods, Props>(
48
51
  style,
49
52
  __disabled,
50
53
  __css,
54
+ ...props
51
55
  }: Props,
52
56
  ref,
53
57
  ) => {
@@ -71,6 +75,7 @@ export const XYPadThumb = forwardRef<XYPadThumbMethods, Props>(
71
75
  ...__css,
72
76
  ...wrapperStyle,
73
77
  }}
78
+ {...props}
74
79
  >
75
80
  {children ? (
76
81
  children
@@ -83,6 +88,7 @@ export const XYPadThumb = forwardRef<XYPadThumbMethods, Props>(
83
88
  tabIndex={0}
84
89
  aria-disabled={__disabled}
85
90
  style={{
91
+ ...{ '--color': color },
86
92
  width: size ?? width,
87
93
  height: size ?? height,
88
94
  ...style,
@@ -3,6 +3,13 @@
3
3
  cursor: pointer;
4
4
  }
5
5
 
6
+ .tremolo-xy-pad-area {
7
+ --color: #eee;
8
+
9
+ position: relative;
10
+ background-color: var(--color);
11
+ }
12
+
6
13
  .tremolo-xy-pad-thumb-wrapper {
7
14
  position: absolute;
8
15
  translate: -50% -50%;
@@ -10,15 +17,18 @@
10
17
  }
11
18
 
12
19
  .tremolo-xy-pad-thumb {
20
+ --color: var(--tremolo-theme-color);
21
+
13
22
  border-radius: 50%;
14
23
  outline: none;
15
- background-color: #4e76e6;
24
+ background-color: var(--color);
16
25
 
17
26
  &[aria-disabled='true'] {
18
27
  background-color: #5d6478;
19
28
  }
20
29
 
21
30
  &:focus {
22
- box-shadow: 0px 0px 0px 3px rgba(var(--tremolo-theme-color-rgb), 0.2);
31
+ box-shadow: 0px 0px 0px 3px
32
+ color-mix(in srgb, var(--color) 20%, transparent);
23
33
  }
24
34
  }
@@ -26,7 +26,10 @@ import { addNoSelect, removeNoSelect } from '../_util'
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,
@@ -162,8 +173,32 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
162
173
  const newX = clamp(stepValue(vx, x.step), x.min, x.max)
163
174
  const newY = clamp(stepValue(vy, y.step), y.min, y.max)
164
175
  onChange(newX, newY)
176
+ return [newX, newY]
165
177
  }
166
178
 
179
+ const updateValueByEvent = useCallback(
180
+ (
181
+ eventType: InputEventOption[0],
182
+ target: Required<ValueOptions>,
183
+ x: number,
184
+ ) => {
185
+ let v
186
+ if (eventType == 'normalized') {
187
+ const n = normalizeValue(
188
+ target.value,
189
+ target.min,
190
+ target.max,
191
+ target.skew,
192
+ )
193
+ v = rawValue(n + x, target.min, target.max, target.skew)
194
+ } else {
195
+ v = target.value + x
196
+ }
197
+ return clamp(stepValue(v, target.step), target.min, target.max)
198
+ },
199
+ [],
200
+ )
201
+
167
202
  const handleKeyDown = useCallback(
168
203
  (event: React.KeyboardEvent<HTMLDivElement>) => {
169
204
  if (!onChange || readonly) return
@@ -176,23 +211,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
176
211
  let delta = target.keyboard[1]
177
212
  if (key == 'ArrowLeft' || key == 'ArrowUp') delta *= -1
178
213
  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
- )
214
+ const newValue = updateValueByEvent(target.keyboard[0], target, delta)
196
215
  if (isHorizontal) {
197
216
  onChange(newValue, y.value)
198
217
  } else {
@@ -200,7 +219,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
200
219
  }
201
220
  }
202
221
  },
203
- [x, y, onChange, readonly],
222
+ [onChange, readonly, x, y, updateValueByEvent],
204
223
  )
205
224
 
206
225
  // --- hooks ---
@@ -221,23 +240,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
221
240
  let delta = target.wheel[1]
222
241
  if (event.deltaY < 0) delta *= -1
223
242
  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
- )
243
+ const newValue = updateValueByEvent(target.wheel[0], target, delta)
241
244
  if (event.shiftKey) {
242
245
  onChange(newValue, y.value)
243
246
  } else {
@@ -247,7 +250,7 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
247
250
  {
248
251
  passive: false,
249
252
  },
250
- [x, y],
253
+ [x, y, onChange, readonly, updateValueByEvent],
251
254
  )
252
255
 
253
256
  useEventListener(globalThis.window, 'mousemove', (event) => {
@@ -255,7 +258,9 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
255
258
  })
256
259
 
257
260
  useEventListener(globalThis.window, 'mouseup', () => {
261
+ if (!thumbDragged.current || readonly) return
258
262
  thumbDragged.current = false
263
+ onDragEnd?.(x.value, y.value)
259
264
  if (bodyNoSelect) removeNoSelect()
260
265
  })
261
266
 
@@ -289,11 +294,22 @@ export const XYPad = forwardRef<XYPadMethods, Props>(
289
294
  }}
290
295
  onPointerDown={(event) => {
291
296
  thumbDragged.current = true
292
- handleValue(event)
297
+ const v = handleValue(event)
298
+ if (!readonly) onDragStart?.(v?.[0] ?? x.value, v?.[1] ?? y.value)
299
+ onPointerDown?.(event)
300
+ }}
301
+ onKeyDown={(event) => {
302
+ handleKeyDown(event)
303
+ onKeyDown?.(event)
304
+ }}
305
+ onFocus={(event) => {
306
+ thumbRef.current?.focus()
307
+ onFocus?.(event)
308
+ }}
309
+ onBlur={(event) => {
310
+ thumbRef.current?.blur()
311
+ onBlur?.(event)
293
312
  }}
294
- onKeyDown={handleKeyDown}
295
- onFocus={thumbRef.current?.focus}
296
- onBlur={thumbRef.current?.blur}
297
313
  {...props}
298
314
  >
299
315
  <div className="tremolo-xy-pad-area-wrapper" ref={areaElementRef}>
@@ -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 {