@tremolo-ui/react 0.4.0 → 0.6.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.
- package/dist/compose-refs.cjs +72 -0
- package/dist/compose-refs.cjs.map +1 -0
- package/dist/compose-refs.d.cts +43 -0
- package/dist/compose-refs.d.cts.map +1 -0
- package/dist/compose-refs.d.ts +43 -0
- package/dist/compose-refs.d.ts.map +1 -0
- package/dist/compose-refs.js +70 -0
- package/dist/compose-refs.js.map +1 -0
- package/dist/index.cjs +2272 -1650
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1461 -475
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1461 -475
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2258 -1648
- package/dist/index.js.map +1 -1
- package/package.json +32 -31
- package/src/components/AnimationCanvas/index.tsx +162 -194
- package/src/components/Knob/ActiveLine.tsx +9 -8
- package/src/components/Knob/InactiveLine.tsx +11 -14
- package/src/components/Knob/SVGRoot.tsx +10 -17
- package/src/components/Knob/Thumb.tsx +33 -11
- package/src/components/Knob/context.tsx +73 -89
- package/src/components/Knob/index.tsx +227 -105
- package/src/components/NumberInput/DecrementStepper.tsx +21 -50
- package/src/components/NumberInput/IncrementStepper.tsx +22 -50
- package/src/components/NumberInput/InputField.tsx +206 -0
- package/src/components/NumberInput/Stepper.tsx +150 -21
- package/src/components/NumberInput/StepperButton.tsx +95 -0
- package/src/components/NumberInput/context.tsx +104 -149
- package/src/components/NumberInput/index.tsx +461 -110
- package/src/components/Piano/index.tsx +413 -263
- package/src/components/Piano/keyboard-shortcuts.ts +61 -0
- package/src/components/PointsEditor/Background.tsx +19 -4
- package/src/components/PointsEditor/Container.tsx +92 -22
- package/src/components/PointsEditor/Point.tsx +293 -53
- package/src/components/PointsEditor/SelectionBox.tsx +54 -0
- package/src/components/PointsEditor/context.tsx +97 -75
- package/src/components/PointsEditor/index.tsx +497 -62
- package/src/components/Slider/Marks.tsx +84 -0
- package/src/components/Slider/MarksOption.tsx +115 -0
- package/src/components/Slider/Thumb.tsx +99 -54
- package/src/components/Slider/Track.tsx +73 -62
- package/src/components/Slider/context.tsx +40 -53
- package/src/components/Slider/index.tsx +237 -190
- package/src/components/Slider/type.ts +27 -9
- package/src/components/XYPad/Area.tsx +52 -33
- package/src/components/XYPad/Thumb.tsx +121 -60
- package/src/components/XYPad/context.tsx +49 -0
- package/src/components/XYPad/index.tsx +338 -246
- package/src/components/_util/Placement.tsx +68 -0
- package/src/components/_util/VisuallyHiddenRangeInput.tsx +24 -0
- package/src/components/_util/css-length.ts +13 -0
- package/src/{components/_util/composeRefs.tsx → compose-refs/index.tsx} +33 -5
- package/src/hooks/{useCallbackRef.ts → _internal/useCallbackRef.ts} +3 -1
- package/src/hooks/_internal/useCheckSteps.ts +170 -0
- package/src/hooks/useAnimationFrame.ts +17 -7
- package/src/hooks/useDrag.ts +39 -9
- package/src/hooks/useDragValue.ts +194 -0
- package/src/hooks/useEventListener.ts +11 -7
- package/src/hooks/useLongPress.ts +42 -8
- package/src/hooks/useMIDIAccess.ts +82 -11
- package/src/hooks/useMIDIInput.ts +45 -13
- package/src/hooks/useMIDIMessage.ts +23 -5
- package/src/hooks/useWheel.ts +29 -9
- package/src/index.ts +50 -27
- package/src/input-event.ts +32 -0
- package/dist/index.css +0 -536
- package/src/Globals.d.ts +0 -3
- package/src/components/AnimationCanvas/canvas.ts +0 -46
- package/src/components/Knob/index.css +0 -77
- package/src/components/NumberInput/InternalInput.tsx +0 -216
- package/src/components/NumberInput/index.css +0 -185
- package/src/components/NumberInput/type.ts +0 -58
- package/src/components/Piano/KeyLabel.tsx +0 -51
- package/src/components/Piano/context.tsx +0 -73
- package/src/components/Piano/index.css +0 -90
- package/src/components/Piano/key.tsx +0 -205
- package/src/components/Piano/keyboardShortcuts.ts +0 -31
- package/src/components/PointsEditor/index.css +0 -30
- package/src/components/Slider/Scale.tsx +0 -52
- package/src/components/Slider/ScaleOption.tsx +0 -103
- package/src/components/Slider/index.css +0 -112
- package/src/components/XYPad/index.css +0 -48
- package/src/components/_util/index.ts +0 -28
- package/src/components/_util/type.ts +0 -1
- package/src/hooks/useDragWithElement.ts +0 -100
- package/src/hooks/usePianoDrag.ts +0 -75
- package/src/hooks/useRefCallbackEvent.ts +0 -34
- package/src/styles/global.css +0 -19
|
@@ -1,89 +1,205 @@
|
|
|
1
|
-
import clsx from 'clsx'
|
|
2
1
|
import {
|
|
3
2
|
ComponentPropsWithoutRef,
|
|
3
|
+
CSSProperties,
|
|
4
4
|
forwardRef,
|
|
5
|
+
ReactNode,
|
|
5
6
|
useCallback,
|
|
6
7
|
useImperativeHandle,
|
|
8
|
+
useMemo,
|
|
7
9
|
useRef,
|
|
8
|
-
useState,
|
|
9
10
|
} from 'react'
|
|
10
11
|
|
|
12
|
+
import type { AxisOptions, XY } from '@tremolo-ui/dom'
|
|
11
13
|
import {
|
|
12
|
-
|
|
13
|
-
InputEventOption,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
applyDelta,
|
|
15
|
+
type InputEventOption,
|
|
16
|
+
type ModifierValue,
|
|
17
|
+
selectModifier,
|
|
18
|
+
linearScale,
|
|
19
|
+
type Scale,
|
|
20
|
+
type ValueRange,
|
|
17
21
|
} from '@tremolo-ui/functions'
|
|
18
22
|
|
|
19
|
-
import {
|
|
23
|
+
import { useComposedRefs } from '../../compose-refs'
|
|
24
|
+
import { useCheckSteps } from '../../hooks/_internal/useCheckSteps'
|
|
25
|
+
import { useDragValue } from '../../hooks/useDragValue'
|
|
20
26
|
import { useWheel } from '../../hooks/useWheel'
|
|
21
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
DEFAULT_DRAG_SENSITIVITY,
|
|
29
|
+
DEFAULT_KEYBOARD_OPTIONS,
|
|
30
|
+
DEFAULT_WHEEL_OPTIONS,
|
|
31
|
+
} from '../../input-event'
|
|
32
|
+
import { cssLength } from '../_util/css-length'
|
|
22
33
|
|
|
23
34
|
import { ActiveLine } from './ActiveLine'
|
|
24
|
-
import { KnobProvider } from './context'
|
|
35
|
+
import { calcAngles, KnobProvider } from './context'
|
|
25
36
|
import { InactiveLine } from './InactiveLine'
|
|
26
37
|
import { SVGRoot } from './SVGRoot'
|
|
27
38
|
import { Thumb } from './Thumb'
|
|
28
39
|
|
|
29
40
|
const defaultExternalStyles: KnobProps['externalStyles'] = {
|
|
30
|
-
userSelectNone: true,
|
|
31
41
|
cursor: 'grabbing',
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
export interface KnobProps {
|
|
35
|
-
|
|
45
|
+
/** The current value. The knob shows only this, so update it from `onChange`. */
|
|
36
46
|
value: number
|
|
47
|
+
/** The value with the knob turned all the way down. */
|
|
37
48
|
min: number
|
|
49
|
+
/** The value with the knob turned all the way up. */
|
|
38
50
|
max: number
|
|
39
51
|
|
|
40
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Granularity of the value. A drag, the wheel and the arrow keys snap it to
|
|
54
|
+
* multiples of `step`.
|
|
55
|
+
*
|
|
56
|
+
* @default 1
|
|
57
|
+
*/
|
|
41
58
|
step?: number
|
|
42
|
-
skew?: number // | SkewFunction // TODO
|
|
43
59
|
/**
|
|
44
|
-
* value
|
|
45
|
-
*
|
|
60
|
+
* How the value is distributed across the travel.
|
|
61
|
+
*
|
|
62
|
+
* Pick one of the scales from `@tremolo-ui/functions`: `linearScale`,
|
|
63
|
+
* `exponentialScale`, `curveScale(n)`, `symmetricSkewScale(n)`, or
|
|
64
|
+
* `skewScale(n)` for a value that has to match a JUCE parameter.
|
|
65
|
+
*
|
|
66
|
+
* @default linearScale
|
|
67
|
+
*/
|
|
68
|
+
scale?: Scale
|
|
69
|
+
/**
|
|
70
|
+
* The value a double click restores, while `enableDoubleClickDefault` is on.
|
|
46
71
|
* @default min
|
|
47
|
-
* @see enableDoubleClickDefault
|
|
48
72
|
*/
|
|
49
73
|
defaultValue?: number
|
|
50
74
|
|
|
51
75
|
/**
|
|
52
|
-
*
|
|
76
|
+
* Where the active arc starts. Put it at the centre of a bipolar control,
|
|
77
|
+
* such as a pan knob, so that the arc grows from there either way.
|
|
53
78
|
* @default min
|
|
54
79
|
*/
|
|
55
80
|
startValue?: number
|
|
56
81
|
|
|
57
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Width and height of the knob. Sets `--knob-size`; the size the theme
|
|
84
|
+
* gives it stands when this is omitted.
|
|
85
|
+
*/
|
|
58
86
|
size?: number | string
|
|
59
87
|
|
|
60
88
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
89
|
+
* The cursor to show while dragging. It is set on the dragged element, so it
|
|
90
|
+
* stays while the pointer is outside the knob.
|
|
91
|
+
*
|
|
92
|
+
* @default { cursor: 'grabbing' }
|
|
63
93
|
*/
|
|
64
94
|
externalStyles?: {
|
|
65
|
-
|
|
66
|
-
cursor?: Cursor
|
|
95
|
+
cursor?: CSSProperties['cursor']
|
|
67
96
|
}
|
|
68
97
|
/**
|
|
69
|
-
* wheel
|
|
70
|
-
*
|
|
98
|
+
* How much one notch of the wheel moves the value. It only acts while the
|
|
99
|
+
* focus is inside, so that scrolling the page past the knob leaves it alone.
|
|
100
|
+
*
|
|
101
|
+
* `['raw', n]` moves the value by `n`, and `['normalized', n]` by `n` of the
|
|
102
|
+
* range between `min` and `max`. The result is snapped to `step`, except for
|
|
103
|
+
* an amount set on a modifier key (`{ default: …, shift: … }`). `null` turns
|
|
104
|
+
* the wheel off.
|
|
105
|
+
*
|
|
106
|
+
* @default ['raw', 1]
|
|
71
107
|
*/
|
|
72
|
-
wheel?: InputEventOption | null
|
|
108
|
+
wheel?: ModifierValue<InputEventOption> | null
|
|
73
109
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
110
|
+
* How much a drag moves the value, per modifier key.
|
|
111
|
+
*
|
|
112
|
+
* `1` is the normal travel of 100px for the whole range; `0.1` makes the
|
|
113
|
+
* same movement cover a tenth of it. Shift is bound to `0.1` by default, to
|
|
114
|
+
* match what it does on the arrow keys.
|
|
115
|
+
*
|
|
116
|
+
* Pressing or releasing the key mid-drag does not disturb the value: the
|
|
117
|
+
* travel so far is kept and the new sensitivity applies from there. It takes
|
|
118
|
+
* effect on the next movement, since a key on its own produces no pointer
|
|
119
|
+
* event.
|
|
120
|
+
*
|
|
121
|
+
* @default { default: 1, shift: 0.1 }
|
|
122
|
+
*/
|
|
123
|
+
dragSensitivity?: ModifierValue<number>
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Hide the cursor while dragging and read the pointer movement directly,
|
|
127
|
+
* rather than letting it wander off across the screen.
|
|
128
|
+
*
|
|
129
|
+
* A knob does not care where the pointer is, only how far it moved, and
|
|
130
|
+
* letting it wander costs twice: the cursor ends up far from the knob it is
|
|
131
|
+
* holding, and **the drag stops at the edge of the screen**, where the
|
|
132
|
+
* operating system pins the pointer and the coordinates stop changing. A
|
|
133
|
+
* `dragSensitivity` below 1 reaches that edge quickly.
|
|
134
|
+
*
|
|
135
|
+
* Off by default because it is not free: the browser shows its own notice,
|
|
136
|
+
* Esc takes the lock back, and the request needs a user gesture and can be
|
|
137
|
+
* refused. A refused request is not an error — the drag simply carries on as
|
|
138
|
+
* an ordinary one.
|
|
139
|
+
*
|
|
140
|
+
* @default false
|
|
141
|
+
*/
|
|
142
|
+
pointerLock?: boolean
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* How much one arrow key press moves the value.
|
|
146
|
+
*
|
|
147
|
+
* `['raw', n]` moves the value by `n`, and `['normalized', n]` by `n` of the
|
|
148
|
+
* range between `min` and `max`. The result is snapped to `step`, except for
|
|
149
|
+
* an amount set on a modifier key, which is what lets shift move off the
|
|
150
|
+
* grid. `null` turns the arrow keys off.
|
|
151
|
+
*
|
|
152
|
+
* The default moves by 1, and by 0.1 with shift. With a `step` above 1, raise
|
|
153
|
+
* the amount to match: 1 would round straight back to where it started, and
|
|
154
|
+
* a development build warns about it.
|
|
155
|
+
*
|
|
156
|
+
* @default { default: ['raw', 1], shift: ['raw', 0.1] }
|
|
157
|
+
*/
|
|
158
|
+
keyboard?: ModifierValue<InputEventOption> | null
|
|
159
|
+
/**
|
|
160
|
+
* Restore `defaultValue` on a double click.
|
|
161
|
+
* @default true
|
|
76
162
|
*/
|
|
77
|
-
keyboard?: InputEventOption | null
|
|
78
163
|
enableDoubleClickDefault?: boolean
|
|
79
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Make the knob unchangeable and remove it from the tab order.
|
|
167
|
+
* The parts carry `data-disabled` while it is set.
|
|
168
|
+
*/
|
|
80
169
|
disabled?: boolean
|
|
170
|
+
/**
|
|
171
|
+
* Make the knob unchangeable while leaving it focusable.
|
|
172
|
+
* The parts carry `data-readonly` while it is set.
|
|
173
|
+
*/
|
|
81
174
|
readonly?: boolean
|
|
82
175
|
|
|
83
|
-
/**
|
|
176
|
+
/**
|
|
177
|
+
* How far the knob turns from `min` to `max`, in degrees, centred on the
|
|
178
|
+
* top.
|
|
179
|
+
* @default 270
|
|
180
|
+
*/
|
|
84
181
|
angleRange?: number
|
|
85
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Called with the new value when a drag, the wheel, an arrow key or a double
|
|
185
|
+
* click moves it.
|
|
186
|
+
*/
|
|
86
187
|
onChange?: (value: number) => void
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The knob renders exactly what you compose here; there is no default
|
|
191
|
+
* markup to fall back to.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* <Knob.Root value={value} min={0} max={100} onChange={setValue}>
|
|
195
|
+
* <Knob.SVGRoot>
|
|
196
|
+
* <Knob.InactiveLine />
|
|
197
|
+
* <Knob.ActiveLine />
|
|
198
|
+
* <Knob.Thumb />
|
|
199
|
+
* </Knob.SVGRoot>
|
|
200
|
+
* </Knob.Root>
|
|
201
|
+
*/
|
|
202
|
+
children: ReactNode
|
|
87
203
|
}
|
|
88
204
|
|
|
89
205
|
export interface KnobMethods {
|
|
@@ -93,20 +209,28 @@ export interface KnobMethods {
|
|
|
93
209
|
|
|
94
210
|
type Props = KnobProps & Omit<ComponentPropsWithoutRef<'div'>, keyof KnobProps>
|
|
95
211
|
|
|
96
|
-
|
|
212
|
+
/**
|
|
213
|
+
* The wheel only acts while the focus is inside, so that scrolling a page past
|
|
214
|
+
* the control does not change its value.
|
|
215
|
+
*/
|
|
216
|
+
const WHEEL_OPTIONS = { requireFocus: true }
|
|
217
|
+
|
|
218
|
+
export const Root = /* @__PURE__ */ forwardRef<KnobMethods, Props>(
|
|
97
219
|
(
|
|
98
220
|
{
|
|
99
221
|
value,
|
|
100
222
|
min,
|
|
101
223
|
max,
|
|
102
224
|
step = 1,
|
|
103
|
-
|
|
225
|
+
scale = linearScale,
|
|
104
226
|
defaultValue = min,
|
|
105
227
|
startValue = min,
|
|
106
228
|
size,
|
|
107
229
|
externalStyles: _externalStyles,
|
|
108
|
-
wheel =
|
|
109
|
-
keyboard =
|
|
230
|
+
wheel = DEFAULT_WHEEL_OPTIONS,
|
|
231
|
+
keyboard = DEFAULT_KEYBOARD_OPTIONS,
|
|
232
|
+
dragSensitivity = DEFAULT_DRAG_SENSITIVITY,
|
|
233
|
+
pointerLock = false,
|
|
110
234
|
enableDoubleClickDefault = true,
|
|
111
235
|
disabled = false,
|
|
112
236
|
readonly = false,
|
|
@@ -122,118 +246,114 @@ const Root = forwardRef<KnobMethods, Props>(
|
|
|
122
246
|
}: Props,
|
|
123
247
|
forwardedRef,
|
|
124
248
|
) => {
|
|
125
|
-
const [dragging, setDragging] = useState(false)
|
|
126
|
-
const valueRef = useRef(0)
|
|
127
249
|
const elmRef = useRef<HTMLElement | SVGElement>(null)
|
|
128
250
|
|
|
129
251
|
const externalStyles = { ...defaultExternalStyles, ..._externalStyles }
|
|
252
|
+
const inactive = disabled || readonly
|
|
130
253
|
|
|
131
254
|
// --- internal functions ---
|
|
132
|
-
const
|
|
133
|
-
(
|
|
134
|
-
|
|
135
|
-
const v = rawValue(valueRef.current - y / 100, min, max, skew)
|
|
136
|
-
const clamped = clamp(stepValue(v, step), min, max)
|
|
137
|
-
onChange(clamped)
|
|
138
|
-
},
|
|
139
|
-
[max, min, onChange, readonly, skew, step],
|
|
255
|
+
const range: ValueRange = useMemo(
|
|
256
|
+
() => ({ min, max, step, scale }),
|
|
257
|
+
[min, max, step, scale],
|
|
140
258
|
)
|
|
141
259
|
|
|
142
|
-
|
|
143
|
-
(eventType: InputEventOption[0], x: number) => {
|
|
144
|
-
let newValue
|
|
145
|
-
if (eventType == 'normalized') {
|
|
146
|
-
const n = normalizeValue(value, min, max, skew)
|
|
147
|
-
newValue = rawValue(n + x, min, max, skew)
|
|
148
|
-
} else {
|
|
149
|
-
newValue = value + x
|
|
150
|
-
}
|
|
151
|
-
return clamp(stepValue(newValue, step), min, max)
|
|
152
|
-
},
|
|
153
|
-
[max, min, skew, step, value],
|
|
154
|
-
)
|
|
260
|
+
useCheckSteps({ component: 'Knob', range, keyboard, wheel })
|
|
155
261
|
|
|
156
262
|
const handleKeyDown = useCallback(
|
|
157
263
|
(event: React.KeyboardEvent<HTMLOrSVGElement>) => {
|
|
158
|
-
if (!keyboard || !onChange ||
|
|
264
|
+
if (!keyboard || !onChange || inactive) return
|
|
159
265
|
const key = event.key
|
|
160
266
|
if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(key)) {
|
|
161
267
|
event.preventDefault()
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
onChange(updateValueByEvent(keyboard[0], x))
|
|
268
|
+
const direction = key === 'ArrowRight' || key === 'ArrowUp' ? 1 : -1
|
|
269
|
+
onChange(applyDelta(value, direction, keyboard, range, event))
|
|
165
270
|
}
|
|
166
271
|
},
|
|
167
|
-
[keyboard, onChange,
|
|
272
|
+
[keyboard, onChange, inactive, value, range],
|
|
168
273
|
)
|
|
169
274
|
|
|
170
275
|
// --- hooks ---
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
276
|
+
// The knob has no travel of its own: the value moves away from where it
|
|
277
|
+
// stood when the drag started, 100px of movement spanning the whole range.
|
|
278
|
+
// Only the vertical axis carries a value, reversed so that dragging up
|
|
279
|
+
// raises it.
|
|
280
|
+
const axis = useMemo(
|
|
281
|
+
(): XY<AxisOptions> => [range, { ...range, reverse: true }],
|
|
282
|
+
[range],
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
const { refCallback: dragRefCallback, dragging } = useDragValue<
|
|
286
|
+
HTMLElement | SVGElement
|
|
287
|
+
>({
|
|
288
|
+
axis,
|
|
289
|
+
getValue: () => [value, value],
|
|
290
|
+
sensitivity: (state) =>
|
|
291
|
+
selectModifier(dragSensitivity, state.event).value,
|
|
292
|
+
threshold: 1,
|
|
293
|
+
cursor: inactive ? undefined : externalStyles.cursor,
|
|
294
|
+
pointerLock: inactive ? false : pointerLock,
|
|
295
|
+
shouldStart: () => !inactive,
|
|
296
|
+
onChange: (v) => {
|
|
297
|
+
if (inactive) return
|
|
298
|
+
onChange?.(v[1])
|
|
182
299
|
},
|
|
183
300
|
})
|
|
184
301
|
|
|
185
302
|
const wheelRefCallback = useWheel<HTMLElement>((event) => {
|
|
186
|
-
if (!wheel ||
|
|
303
|
+
if (!wheel || inactive) return
|
|
187
304
|
event.preventDefault()
|
|
188
|
-
if (!onChange || event.deltaY
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
305
|
+
if (!onChange || event.deltaY === 0) return
|
|
306
|
+
onChange(applyDelta(value, -Math.sign(event.deltaY), wheel, range, event))
|
|
307
|
+
}, WHEEL_OPTIONS)
|
|
308
|
+
|
|
309
|
+
// Composed once, so React attaches the refs a single time instead of
|
|
310
|
+
// detaching and re-attaching on every render.
|
|
311
|
+
const rootRefCallback = useComposedRefs<HTMLElement | SVGElement>(
|
|
312
|
+
elmRef,
|
|
313
|
+
dragRefCallback,
|
|
314
|
+
wheelRefCallback,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
const context = useMemo(() => {
|
|
318
|
+
const config = { value, min, max, step, scale, startValue, angleRange }
|
|
319
|
+
return { ...config, ...calcAngles(config) }
|
|
320
|
+
}, [value, min, max, step, scale, startValue, angleRange])
|
|
192
321
|
|
|
193
322
|
useImperativeHandle(forwardedRef, () => {
|
|
194
323
|
return {
|
|
195
324
|
focus() {
|
|
196
|
-
elmRef.current?.focus()
|
|
325
|
+
if (!disabled) elmRef.current?.focus()
|
|
197
326
|
},
|
|
198
327
|
blur() {
|
|
199
328
|
elmRef.current?.blur()
|
|
200
329
|
},
|
|
201
330
|
}
|
|
202
|
-
}, [])
|
|
331
|
+
}, [disabled])
|
|
203
332
|
|
|
204
333
|
return (
|
|
205
|
-
<KnobProvider
|
|
206
|
-
value={value}
|
|
207
|
-
min={min}
|
|
208
|
-
max={max}
|
|
209
|
-
step={step}
|
|
210
|
-
skew={skew}
|
|
211
|
-
startValue={startValue}
|
|
212
|
-
angleRange={angleRange}
|
|
213
|
-
>
|
|
334
|
+
<KnobProvider value={context}>
|
|
214
335
|
<div
|
|
215
|
-
ref={
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
dragRefCallback(elm)
|
|
219
|
-
}}
|
|
220
|
-
className={clsx('tremolo-knob', className)}
|
|
221
|
-
tabIndex={0}
|
|
336
|
+
ref={rootRefCallback}
|
|
337
|
+
className={className}
|
|
338
|
+
tabIndex={disabled ? -1 : 0}
|
|
222
339
|
role="slider"
|
|
223
340
|
aria-valuenow={value}
|
|
224
341
|
aria-valuemin={min}
|
|
225
342
|
aria-valuemax={max}
|
|
226
343
|
aria-disabled={disabled}
|
|
227
344
|
aria-readonly={readonly}
|
|
228
|
-
data-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
345
|
+
data-disabled={disabled ? '' : undefined}
|
|
346
|
+
data-readonly={readonly ? '' : undefined}
|
|
347
|
+
data-dragging={dragging ? '' : undefined}
|
|
348
|
+
style={
|
|
349
|
+
{
|
|
350
|
+
'--knob-size': cssLength(size),
|
|
351
|
+
...style,
|
|
352
|
+
} as CSSProperties
|
|
353
|
+
}
|
|
234
354
|
onPointerDown={onPointerDown}
|
|
235
355
|
onDoubleClick={(event) => {
|
|
236
|
-
if (enableDoubleClickDefault && onChange) {
|
|
356
|
+
if (!inactive && enableDoubleClickDefault && onChange) {
|
|
237
357
|
onChange(defaultValue)
|
|
238
358
|
}
|
|
239
359
|
onDoubleClick?.(event)
|
|
@@ -244,7 +364,7 @@ const Root = forwardRef<KnobMethods, Props>(
|
|
|
244
364
|
}}
|
|
245
365
|
{...props}
|
|
246
366
|
>
|
|
247
|
-
{children
|
|
367
|
+
{children}
|
|
248
368
|
</div>
|
|
249
369
|
</KnobProvider>
|
|
250
370
|
)
|
|
@@ -262,4 +382,6 @@ export const Knob = {
|
|
|
262
382
|
Thumb,
|
|
263
383
|
}
|
|
264
384
|
|
|
265
|
-
export { useKnobContext } from './context'
|
|
385
|
+
export { useKnobContext, type KnobContextValue } from './context'
|
|
386
|
+
export { type KnobSVGRootProps } from './SVGRoot'
|
|
387
|
+
export { type KnobThumbProps } from './Thumb'
|
|
@@ -1,57 +1,28 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ComponentPropsWithoutRef, ReactNode } from 'react'
|
|
1
|
+
import { ComponentPropsWithoutRef } from 'react'
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
StepperArrow,
|
|
5
|
+
StepperButton,
|
|
6
|
+
StepperButtonProps,
|
|
7
|
+
} from './StepperButton'
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export interface DecrementStepperProps {
|
|
9
|
-
size?: number
|
|
10
|
-
children?: ReactNode
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function DecrementStepper({
|
|
14
|
-
size = 12,
|
|
15
|
-
children,
|
|
16
|
-
className,
|
|
17
|
-
...props
|
|
18
|
-
}: DecrementStepperProps &
|
|
19
|
-
Omit<ComponentPropsWithoutRef<'div'>, keyof DecrementStepperProps>) {
|
|
20
|
-
const min = useNumberInputContext((s) => s.min)
|
|
21
|
-
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber)
|
|
22
|
-
const readonly = useNumberInputContext((s) => s.readonly)
|
|
23
|
-
const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange)
|
|
24
|
-
const decrement = useNumberInputContext((s) => s.decrement)
|
|
25
|
-
|
|
26
|
-
const press = useLongPress(decrement)
|
|
9
|
+
// An interface rather than an alias; see `NumberInputIncrementStepperProps`.
|
|
10
|
+
export interface NumberInputDecrementStepperProps extends StepperButtonProps {}
|
|
27
11
|
|
|
12
|
+
/** Lowers the value by one `step`, repeating while held. */
|
|
13
|
+
export function DecrementStepper(
|
|
14
|
+
props: NumberInputDecrementStepperProps &
|
|
15
|
+
Omit<
|
|
16
|
+
ComponentPropsWithoutRef<'div'>,
|
|
17
|
+
keyof NumberInputDecrementStepperProps
|
|
18
|
+
>,
|
|
19
|
+
) {
|
|
28
20
|
return (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
tabIndex={-1}
|
|
34
|
-
aria-disabled={
|
|
35
|
-
keepWithinRange && valueAsNumber <= (min ?? Number.MIN_SAFE_INTEGER)
|
|
36
|
-
}
|
|
37
|
-
aria-readonly={readonly}
|
|
38
|
-
onPointerDown={press}
|
|
21
|
+
<StepperButton
|
|
22
|
+
direction={-1}
|
|
23
|
+
variant="decrement"
|
|
24
|
+
icon={<StepperArrow up={false} />}
|
|
39
25
|
{...props}
|
|
40
|
-
|
|
41
|
-
{children || (
|
|
42
|
-
<svg
|
|
43
|
-
width={size}
|
|
44
|
-
height={size}
|
|
45
|
-
viewBox="0 0 24 24"
|
|
46
|
-
fill="none"
|
|
47
|
-
stroke="currentColor"
|
|
48
|
-
strokeWidth="2"
|
|
49
|
-
strokeLinecap="round"
|
|
50
|
-
strokeLinejoin="round"
|
|
51
|
-
>
|
|
52
|
-
<polyline points="6 9 12 15 18 9" />
|
|
53
|
-
</svg>
|
|
54
|
-
)}
|
|
55
|
-
</div>
|
|
26
|
+
/>
|
|
56
27
|
)
|
|
57
28
|
}
|
|
@@ -1,57 +1,29 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ComponentPropsWithoutRef, ReactNode } from 'react'
|
|
1
|
+
import { ComponentPropsWithoutRef } from 'react'
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
StepperArrow,
|
|
5
|
+
StepperButton,
|
|
6
|
+
StepperButtonProps,
|
|
7
|
+
} from './StepperButton'
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export interface
|
|
9
|
-
size?: number
|
|
10
|
-
children?: ReactNode
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function IncrementStepper({
|
|
14
|
-
size = 12,
|
|
15
|
-
children,
|
|
16
|
-
className,
|
|
17
|
-
...props
|
|
18
|
-
}: IncrementStepperProps &
|
|
19
|
-
Omit<ComponentPropsWithoutRef<'div'>, keyof IncrementStepperProps>) {
|
|
20
|
-
const max = useNumberInputContext((s) => s.max)
|
|
21
|
-
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber)
|
|
22
|
-
const readonly = useNumberInputContext((s) => s.readonly)
|
|
23
|
-
const keepWithinRange = useNumberInputContext((s) => s.keepWithinRange)
|
|
24
|
-
const increment = useNumberInputContext((s) => s.increment)
|
|
25
|
-
|
|
26
|
-
const press = useLongPress(increment)
|
|
9
|
+
// An interface rather than an alias, so that typedoc lists the props: the
|
|
10
|
+
// shared `StepperButtonProps` is not exported for it to follow.
|
|
11
|
+
export interface NumberInputIncrementStepperProps extends StepperButtonProps {}
|
|
27
12
|
|
|
13
|
+
/** Raises the value by one `step`, repeating while held. */
|
|
14
|
+
export function IncrementStepper(
|
|
15
|
+
props: NumberInputIncrementStepperProps &
|
|
16
|
+
Omit<
|
|
17
|
+
ComponentPropsWithoutRef<'div'>,
|
|
18
|
+
keyof NumberInputIncrementStepperProps
|
|
19
|
+
>,
|
|
20
|
+
) {
|
|
28
21
|
return (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
tabIndex={-1}
|
|
34
|
-
aria-disabled={
|
|
35
|
-
keepWithinRange && valueAsNumber >= (max ?? Number.MAX_SAFE_INTEGER)
|
|
36
|
-
}
|
|
37
|
-
aria-readonly={readonly}
|
|
38
|
-
onPointerDown={press}
|
|
22
|
+
<StepperButton
|
|
23
|
+
direction={1}
|
|
24
|
+
variant="increment"
|
|
25
|
+
icon={<StepperArrow up />}
|
|
39
26
|
{...props}
|
|
40
|
-
|
|
41
|
-
{children || (
|
|
42
|
-
<svg
|
|
43
|
-
width={size}
|
|
44
|
-
height={size}
|
|
45
|
-
viewBox="0 0 24 24"
|
|
46
|
-
fill="none"
|
|
47
|
-
stroke="currentColor"
|
|
48
|
-
strokeWidth="2"
|
|
49
|
-
strokeLinecap="round"
|
|
50
|
-
strokeLinejoin="round"
|
|
51
|
-
>
|
|
52
|
-
<polyline points="18 15 12 9 6 15" />
|
|
53
|
-
</svg>
|
|
54
|
-
)}
|
|
55
|
-
</div>
|
|
27
|
+
/>
|
|
56
28
|
)
|
|
57
29
|
}
|