@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
@@ -1,5 +1,11 @@
1
1
  import clsx from 'clsx'
2
- import { ComponentPropsWithoutRef, useCallback, useRef } from 'react'
2
+ import {
3
+ ComponentPropsWithoutRef,
4
+ forwardRef,
5
+ useCallback,
6
+ useImperativeHandle,
7
+ useRef,
8
+ } from 'react'
3
9
 
4
10
  import {
5
11
  clamp,
@@ -55,11 +61,24 @@ export interface KnobProps {
55
61
  disabled?: boolean
56
62
  readonly?: boolean
57
63
 
64
+ /** color */
58
65
  activeLine?: string
66
+ /** color */
59
67
  inactiveLine?: string
68
+ /** color */
60
69
  thumb?: string
70
+ /** color */
61
71
  thumbLine?: string
62
72
 
73
+ /** percent (0-100) */
74
+ thumbSize?: number
75
+ /** percent (0-100) */
76
+ thumbLineWeight?: number
77
+ /** percent (0-100) */
78
+ thumbLineLength?: number
79
+ /** percent (0-100) */
80
+ lineWeight?: number
81
+
63
82
  classes?: {
64
83
  activeLine?: string
65
84
  inactiveLine?: string
@@ -70,189 +89,243 @@ export interface KnobProps {
70
89
  onChange?: (value: number) => void
71
90
  }
72
91
 
73
- /** @category Knob */
74
- export function Knob({
75
- value,
76
- min,
77
- max,
78
- step = 1,
79
- skew = 1,
80
- defaultValue = min,
81
- startValue = min,
82
- size = 50,
83
- bodyNoSelect = true,
84
- wheel = ['raw', 1],
85
- keyboard = ['raw', 1],
86
- enableDoubleClickDefault = true,
87
- disabled = false,
88
- readonly = false,
89
- activeLine,
90
- inactiveLine,
91
- thumb,
92
- thumbLine,
93
- onChange,
94
- className,
95
- classes,
96
- ...props
97
- }: KnobProps & Omit<ComponentPropsWithoutRef<'svg'>, keyof KnobProps>) {
98
- const valueRef = useRef(0)
92
+ /**
93
+ * @category Knob
94
+ */
95
+ export interface KnobMethods {
96
+ focus: () => void
97
+ blur: () => void
98
+ }
99
99
 
100
- // --- interpret props ---
101
- const padding = 8 // %
102
- const thumbLineWeight = 6 // %
103
- const thumbLength = 35 // %
104
- const lineWeight = 3 // px
105
- const p = normalizeValue(value, min, max, skew)
106
- const s = normalizeValue(startValue, min, max, skew)
100
+ type Props = KnobProps & Omit<ComponentPropsWithoutRef<'svg'>, keyof KnobProps>
107
101
 
108
- // --- internal functions ---
109
- const onDrag = useCallback(
110
- (_x: number, y: number) => {
111
- if (!onChange || readonly) return
112
- const v = rawValue(valueRef.current - y / 100, min, max, skew)
113
- const v2 = clamp(stepValue(v, step), min, max)
114
- onChange(v2)
115
- },
116
- [max, min, onChange, readonly, skew, step],
117
- )
102
+ /**
103
+ * Interactive rotary knob component implemented in SVG.
104
+ *
105
+ * @category Knob
106
+ */
107
+ export const Knob = forwardRef<KnobMethods, Props>(
108
+ (
109
+ {
110
+ value,
111
+ min,
112
+ max,
113
+ step = 1,
114
+ skew = 1,
115
+ defaultValue = min,
116
+ startValue = min,
117
+ size = 50,
118
+ bodyNoSelect = true,
119
+ wheel = ['raw', 1],
120
+ keyboard = ['raw', 1],
121
+ enableDoubleClickDefault = true,
122
+ disabled = false,
123
+ readonly = false,
124
+ activeLine,
125
+ inactiveLine,
126
+ thumb,
127
+ thumbLine,
128
+ onChange,
129
+ onKeyDown,
130
+ onPointerDown,
131
+ onDoubleClick,
132
+ className,
133
+ classes,
134
+ ...props
135
+ }: Props,
136
+ ref,
137
+ ) => {
138
+ const valueRef = useRef(0)
139
+ const elmRef = useRef<HTMLOrSVGElement>(null)
118
140
 
119
- const handleKeyDown = useCallback(
120
- (event: React.KeyboardEvent<HTMLOrSVGElement>) => {
121
- if (!keyboard || !onChange || readonly) return
122
- const key = event.key
123
- if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(key)) {
124
- event.preventDefault()
125
- const x =
126
- key == 'ArrowRight' || key == 'ArrowUp' ? keyboard[1] : -keyboard[1]
127
- let v
128
- if (keyboard[0] == 'normalized') {
141
+ // --- interpret props ---
142
+ const padding = 8 // %
143
+ const thumbLineWeight = 6 // %
144
+ const thumbLineLength = 35 // %
145
+ const lineWeight = size * 0.06 // px
146
+ const p = normalizeValue(value, min, max, skew)
147
+ const s = normalizeValue(startValue, min, max, skew)
148
+
149
+ // --- internal functions ---
150
+ const onDrag = useCallback(
151
+ (_x: number, y: number) => {
152
+ if (!onChange || readonly) return
153
+ const v = rawValue(valueRef.current - y / 100, min, max, skew)
154
+ const v2 = clamp(stepValue(v, step), min, max)
155
+ onChange(v2)
156
+ },
157
+ [max, min, onChange, readonly, skew, step],
158
+ )
159
+
160
+ const updateValueByEvent = useCallback(
161
+ (eventType: InputEventOption[0], x: number) => {
162
+ let newValue
163
+ if (eventType == 'normalized') {
129
164
  const n = normalizeValue(value, min, max, skew)
130
- v = rawValue(n + x, min, max, skew)
165
+ newValue = rawValue(n + x, min, max, skew)
131
166
  } else {
132
- v = value + x
167
+ newValue = value + x
133
168
  }
134
- onChange(clamp(stepValue(v, step), min, max))
135
- }
136
- },
137
- [value, min, max, step, skew, keyboard, readonly, onChange],
138
- )
169
+ return clamp(stepValue(newValue, step), min, max)
170
+ },
171
+ [max, min, skew, step, value],
172
+ )
173
+
174
+ const handleKeyDown = useCallback(
175
+ (event: React.KeyboardEvent<HTMLOrSVGElement>) => {
176
+ if (!keyboard || !onChange || readonly) return
177
+ const key = event.key
178
+ if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(key)) {
179
+ event.preventDefault()
180
+ const x =
181
+ key == 'ArrowRight' || key == 'ArrowUp' ? keyboard[1] : -keyboard[1]
182
+ onChange(updateValueByEvent(keyboard[0], x))
183
+ }
184
+ },
185
+ [keyboard, onChange, readonly, updateValueByEvent],
186
+ )
139
187
 
140
- // --- hooks ---
141
- const [touchMoveRefCallback, pointerDownHandler] = useDrag<SVGElement>({
142
- onDrag: onDrag,
143
- onDragStart: () => {
144
- valueRef.current = normalizeValue(value, min, max, skew)
145
- if (bodyNoSelect) addNoSelect()
146
- },
147
- onDragEnd: () => {
148
- if (bodyNoSelect) removeNoSelect()
149
- },
150
- })
188
+ // --- hooks ---
189
+ const [touchMoveRefCallback, pointerDownHandler] = useDrag<SVGElement>({
190
+ onDrag: onDrag,
191
+ onDragStart: () => {
192
+ valueRef.current = normalizeValue(value, min, max, skew)
193
+ if (bodyNoSelect) addNoSelect()
194
+ },
195
+ onDragEnd: () => {
196
+ if (bodyNoSelect) removeNoSelect()
197
+ },
198
+ })
151
199
 
152
- const wheelRefCallback = useRefCallbackEvent(
153
- 'wheel',
154
- (event) => {
155
- if (!wheel) return
156
- event.preventDefault()
157
- const x = event.deltaY > 0 ? -wheel[1] : wheel[1]
158
- let v
159
- if (wheel[0] == 'normalized') {
160
- const n = normalizeValue(value, min, max, skew)
161
- v = rawValue(n + x, min, max, skew)
162
- } else {
163
- v = value + x
200
+ const wheelRefCallback = useRefCallbackEvent(
201
+ 'wheel',
202
+ (event) => {
203
+ if (!wheel || readonly) return
204
+ event.preventDefault()
205
+ if (!onChange || event.deltaY == 0) return
206
+ const x = Math.sign(event.deltaY) * -wheel[1]
207
+ onChange(updateValueByEvent(wheel[0], x))
208
+ },
209
+ { passive: false },
210
+ [wheel, onChange, readonly, updateValueByEvent],
211
+ )
212
+
213
+ useImperativeHandle(ref, () => {
214
+ return {
215
+ focus() {
216
+ elmRef.current?.focus()
217
+ },
218
+ blur() {
219
+ elmRef.current?.blur()
220
+ },
164
221
  }
165
- if (onChange) onChange(clamp(stepValue(v, step), min, max))
166
- },
167
- { passive: false },
168
- [wheel, value, min, max, skew, step, onChange],
169
- )
222
+ }, [])
170
223
 
171
- const center = size / 2
172
- const r1 = -135
173
- const r2 = -135 + Math.min(p, s) * 270
174
- const r3 = -135 + Math.max(p, s) * 270
175
- const r4 = 135
176
- const x1 = center + center * Math.cos(radian(r1 - 90))
177
- const y1 = center + center * Math.sin(radian(r1 - 90))
178
- const x2 = center + center * Math.cos(radian(r2 - 90))
179
- const y2 = center + center * Math.sin(radian(r2 - 90))
180
- const x3 = center + center * Math.cos(radian(r3 - 90))
181
- const y3 = center + center * Math.sin(radian(r3 - 90))
182
- const x4 = center + center * Math.cos(radian(r4 - 90))
183
- const y4 = center + center * Math.sin(radian(r4 - 90))
224
+ const center = size / 2
225
+ const r1 = -135
226
+ const r2 = -135 + Math.min(p, s) * 270
227
+ const r3 = -135 + Math.max(p, s) * 270
228
+ const r4 = 135
229
+ const x1 = center + center * Math.cos(radian(r1 - 90))
230
+ const y1 = center + center * Math.sin(radian(r1 - 90))
231
+ const x2 = center + center * Math.cos(radian(r2 - 90))
232
+ const y2 = center + center * Math.sin(radian(r2 - 90))
233
+ const x3 = center + center * Math.cos(radian(r3 - 90))
234
+ const y3 = center + center * Math.sin(radian(r3 - 90))
235
+ const x4 = center + center * Math.cos(radian(r4 - 90))
236
+ const y4 = center + center * Math.sin(radian(r4 - 90))
184
237
 
185
- return (
186
- <svg
187
- className={clsx('tremolo-knob', className)}
188
- ref={(div) => {
189
- wheelRefCallback(div)
190
- touchMoveRefCallback(div)
191
- }}
192
- width={size}
193
- height={size}
194
- tabIndex={0}
195
- aria-valuenow={value}
196
- aria-valuemin={min}
197
- aria-valuemax={max}
198
- aria-disabled={disabled}
199
- aria-readonly={readonly}
200
- onPointerDown={pointerDownHandler}
201
- onDoubleClick={() => {
202
- if (enableDoubleClickDefault && onChange) {
203
- onChange(defaultValue)
204
- }
205
- }}
206
- onKeyDown={handleKeyDown}
207
- onContextMenu={(e) => e.preventDefault()}
208
- {...props}
209
- >
210
- {/* rotary meter */}
211
- {startValue > min && (
212
- <path
213
- className={clsx('tremolo-knob-inactive-line', classes?.inactiveLine)}
214
- d={`M ${x1} ${y1} A ${center} ${center} -135 ${r2 - r1 > 180 ? 1 : 0} 1 ${x2}, ${y2}`}
215
- fill="none"
216
- stroke={inactiveLine || 'currentColor'}
217
- strokeWidth={lineWeight}
218
- />
219
- )}
220
- <path
221
- className={clsx('tremolo-knob-active-line', classes?.activeLine)}
222
- d={`M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3}, ${y3}`}
223
- fill="none"
224
- stroke={activeLine || 'currentColor'}
225
- strokeWidth={lineWeight}
226
- />
227
- {startValue < max && (
238
+ return (
239
+ <svg
240
+ className={clsx('tremolo-knob', className)}
241
+ ref={(div) => {
242
+ elmRef.current = div
243
+ wheelRefCallback(div)
244
+ touchMoveRefCallback(div)
245
+ }}
246
+ width={size}
247
+ height={size}
248
+ tabIndex={0}
249
+ role="slider"
250
+ aria-valuenow={value}
251
+ aria-valuemin={min}
252
+ aria-valuemax={max}
253
+ aria-disabled={disabled}
254
+ aria-readonly={readonly}
255
+ onPointerDown={(event) => {
256
+ pointerDownHandler(event)
257
+ onPointerDown?.(event)
258
+ }}
259
+ onDoubleClick={(event) => {
260
+ if (enableDoubleClickDefault && onChange) {
261
+ onChange(defaultValue)
262
+ }
263
+ onDoubleClick?.(event)
264
+ }}
265
+ onKeyDown={(event) => {
266
+ handleKeyDown(event)
267
+ onKeyDown?.(event)
268
+ }}
269
+ {...props}
270
+ >
271
+ {/* rotary meter */}
272
+ {startValue > min && (
273
+ <path
274
+ className={clsx(
275
+ 'tremolo-knob-inactive-line',
276
+ classes?.inactiveLine,
277
+ )}
278
+ d={`M ${x1} ${y1} A ${center} ${center} -135 ${r2 - r1 > 180 ? 1 : 0} 1 ${x2}, ${y2}`}
279
+ fill="none"
280
+ stroke={inactiveLine || 'currentColor'}
281
+ strokeWidth={lineWeight}
282
+ />
283
+ )}
228
284
  <path
229
- className={clsx('tremolo-knob-inactive-line', classes?.inactiveLine)}
230
- d={`M ${x3} ${y3} A ${center} ${center} -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4}, ${y4}`}
285
+ className={clsx('tremolo-knob-active-line', classes?.activeLine)}
286
+ d={`M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3}, ${y3}`}
231
287
  fill="none"
232
- stroke={inactiveLine || 'currentColor'}
288
+ stroke={activeLine || 'currentColor'}
233
289
  strokeWidth={lineWeight}
234
290
  />
235
- )}
236
- {/* thumb */}
237
- <svg className={clsx('tremolo-knob-thumb', classes?.thumb)}>
238
- <circle
239
- cx="50%"
240
- cy="50%"
241
- r={`${50 - padding}%`}
242
- fill={thumb || 'currentColor'}
243
- />
244
- <line
245
- className={clsx('tremolo-knob-thumb-line', classes?.thumbLine)}
246
- x1="50%"
247
- y1={`${padding}%`}
248
- x2="50%"
249
- y2={`${thumbLength}%`}
250
- stroke={thumbLine || 'currentColor'}
251
- strokeWidth={`${thumbLineWeight}%`}
252
- transform={`rotate(${-135 + p * 270})`}
253
- transform-origin="50% 50%"
254
- />
291
+ {startValue < max && (
292
+ <path
293
+ className={clsx(
294
+ 'tremolo-knob-inactive-line',
295
+ classes?.inactiveLine,
296
+ )}
297
+ d={`M ${x3} ${y3} A ${center} ${center} -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4}, ${y4}`}
298
+ fill="none"
299
+ stroke={inactiveLine || 'currentColor'}
300
+ strokeWidth={lineWeight}
301
+ />
302
+ )}
303
+ {/* thumb */}
304
+ <svg className={clsx('tremolo-knob-thumb', classes?.thumb)}>
305
+ <circle
306
+ cx="50%"
307
+ cy="50%"
308
+ r={`${50 - padding}%`}
309
+ fill={thumb || 'currentColor'}
310
+ />
311
+ <line
312
+ className={clsx('tremolo-knob-thumb-line', classes?.thumbLine)}
313
+ x1="50%"
314
+ y1={`${padding}%`}
315
+ x2="50%"
316
+ y2={`${thumbLineLength}%`}
317
+ stroke={thumbLine || 'currentColor'}
318
+ strokeWidth={`${thumbLineWeight}%`}
319
+ // NOTE
320
+ // https://bugs.webkit.org/show_bug.cgi?id=201854
321
+ // https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/transform-origin#browser_compatibility
322
+ style={{
323
+ transform: `rotate(${-135 + p * 270}deg)`,
324
+ transformOrigin: '50% 50%',
325
+ }}
326
+ />
327
+ </svg>
255
328
  </svg>
256
- </svg>
257
- )
258
- }
329
+ )
330
+ },
331
+ )
@@ -1,13 +1,17 @@
1
1
  import clsx from 'clsx'
2
2
  import { ComponentPropsWithoutRef, ReactNode } from 'react'
3
3
 
4
+ import { useLongPress } from '../../hooks/useLongPress'
5
+
4
6
  import { useNumberInputContext } from './context'
5
7
 
8
+ /** @category NumberInput */
6
9
  export interface DecrementStepperProps {
7
10
  size?: number
8
11
  children?: ReactNode
9
12
  }
10
13
 
14
+ /** @category NumberInput */
11
15
  export function DecrementStepper({
12
16
  size = 12,
13
17
  children,
@@ -17,12 +21,14 @@ export function DecrementStepper({
17
21
  Omit<ComponentPropsWithoutRef<'div'>, keyof DecrementStepperProps>) {
18
22
  const min = useNumberInputContext((s) => s.min)
19
23
  const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber)
24
+ const readonly = useNumberInputContext((s) => s.readonly)
20
25
  const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange)
21
26
  const decrement = useNumberInputContext((s) => s.decrement)
22
27
 
23
- // TODO: add long press event
28
+ const press = useLongPress(decrement)
24
29
 
25
30
  return (
31
+ // eslint-disable-next-line jsx-a11y/role-supports-aria-props
26
32
  <div
27
33
  className={clsx('tremolo-number-input-decrement-stepper', className)}
28
34
  role="button"
@@ -30,7 +36,8 @@ export function DecrementStepper({
30
36
  aria-disabled={
31
37
  keepWithinRange && valueAsNumber <= (min ?? Number.MIN_SAFE_INTEGER)
32
38
  }
33
- onPointerDown={decrement}
39
+ aria-readonly={readonly}
40
+ onPointerDown={press}
34
41
  {...props}
35
42
  >
36
43
  {children || (
@@ -1,13 +1,17 @@
1
1
  import clsx from 'clsx'
2
2
  import { ComponentPropsWithoutRef, ReactNode } from 'react'
3
3
 
4
+ import { useLongPress } from '../../hooks/useLongPress'
5
+
4
6
  import { useNumberInputContext } from './context'
5
7
 
8
+ /** @category NumberInput */
6
9
  export interface IncrementStepperProps {
7
10
  size?: number
8
11
  children?: ReactNode
9
12
  }
10
13
 
14
+ /** @category NumberInput */
11
15
  export function IncrementStepper({
12
16
  size = 12,
13
17
  children,
@@ -17,12 +21,14 @@ export function IncrementStepper({
17
21
  Omit<ComponentPropsWithoutRef<'div'>, keyof IncrementStepperProps>) {
18
22
  const max = useNumberInputContext((s) => s.max)
19
23
  const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber)
24
+ const readonly = useNumberInputContext((s) => s.readonly)
20
25
  const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange)
21
26
  const increment = useNumberInputContext((s) => s.increment)
22
27
 
23
- // TODO: add long press event
28
+ const press = useLongPress(increment)
24
29
 
25
30
  return (
31
+ // eslint-disable-next-line jsx-a11y/role-supports-aria-props
26
32
  <div
27
33
  className={clsx('tremolo-number-input-increment-stepper', className)}
28
34
  role="button"
@@ -30,7 +36,8 @@ export function IncrementStepper({
30
36
  aria-disabled={
31
37
  keepWithinRange && valueAsNumber >= (max ?? Number.MAX_SAFE_INTEGER)
32
38
  }
33
- onPointerDown={increment}
39
+ aria-readonly={readonly}
40
+ onPointerDown={press}
34
41
  {...props}
35
42
  >
36
43
  {children || (