@tremolo-ui/react 0.3.0 → 0.5.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/index.cjs +1162 -1554
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +58 -93
- package/dist/index.d.cts +592 -278
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +592 -278
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1163 -1555
- package/dist/index.js.map +1 -1
- package/package.json +12 -13
- package/src/components/AnimationCanvas/index.tsx +84 -155
- package/src/components/Knob/ActiveLine.tsx +6 -6
- package/src/components/Knob/InactiveLine.tsx +9 -11
- package/src/components/Knob/SVGRoot.tsx +9 -18
- package/src/components/Knob/context.tsx +57 -89
- package/src/components/Knob/index.css +6 -2
- package/src/components/Knob/index.tsx +95 -83
- package/src/components/NumberInput/DecrementStepper.tsx +17 -50
- package/src/components/NumberInput/IncrementStepper.tsx +17 -50
- package/src/components/NumberInput/InputField.tsx +123 -0
- package/src/components/NumberInput/Stepper.tsx +90 -12
- package/src/components/NumberInput/context.tsx +74 -146
- package/src/components/NumberInput/index.css +38 -102
- package/src/components/NumberInput/index.tsx +252 -98
- package/src/components/NumberInput/stepperButton.tsx +90 -0
- package/src/components/Piano/index.tsx +272 -288
- package/src/components/Piano/keyboardShortcuts.ts +34 -4
- package/src/components/PointsEditor/Background.tsx +8 -2
- package/src/components/PointsEditor/Container.tsx +19 -9
- package/src/components/PointsEditor/Point.tsx +117 -52
- package/src/components/PointsEditor/context.tsx +28 -74
- package/src/components/PointsEditor/index.css +5 -0
- package/src/components/PointsEditor/index.tsx +77 -43
- package/src/components/Slider/{Scale.tsx → Marks.tsx} +8 -8
- package/src/components/Slider/{ScaleOption.tsx → MarksOption.tsx} +16 -18
- package/src/components/Slider/Thumb.tsx +55 -68
- package/src/components/Slider/Track.tsx +15 -15
- package/src/components/Slider/context.tsx +36 -51
- package/src/components/Slider/index.css +18 -5
- package/src/components/Slider/index.tsx +136 -170
- package/src/components/Slider/type.ts +4 -4
- package/src/components/XYPad/Area.tsx +16 -7
- package/src/components/XYPad/Thumb.tsx +62 -74
- package/src/components/XYPad/context.tsx +46 -0
- package/src/components/XYPad/index.css +12 -0
- package/src/components/XYPad/index.tsx +193 -250
- package/src/hooks/useDrag.ts +41 -76
- package/src/hooks/useDragValue.ts +149 -0
- package/src/hooks/useEventListener.ts +1 -1
- package/src/hooks/useWheel.ts +51 -0
- package/src/index.ts +15 -13
- package/src/components/AnimationCanvas/canvas.ts +0 -46
- package/src/components/DragObserver/index.tsx +0 -58
- package/src/components/NumberInput/InternalInput.tsx +0 -221
- 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/key.tsx +0 -205
- package/src/components/WheelObserver/index.tsx +0 -48
- package/src/components/_util/type.ts +0 -1
- package/src/hooks/useDragWithElement.ts +0 -72
- package/src/hooks/usePianoDrag.ts +0 -75
- package/src/hooks/useRefCallbackEvent.ts +0 -34
- /package/src/hooks/{useCallbackRef.ts → _internal/useCallbackRef.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,146 +1,77 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
|
-
import
|
|
2
|
+
import { createContext, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useInsertionEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
3
|
+
import { NOT_SUPPORTED, PERMISSION_DENIED, createAnimationCanvas, createDrag, createDragValue, createMIDIAccess, createMIDIInput, createMIDIMessage, createPianoInput, createWheel, elementMapping, relativeMapping, toXY } from "@tremolo-ui/dom";
|
|
3
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
-
import { clamp, decimalPart,
|
|
5
|
-
import { createStore, useStore } from "zustand";
|
|
6
|
-
import { NOT_SUPPORTED, PERMISSION_DENIED, createMIDIAccess, createMIDIInput, createMIDIMessage } from "@tremolo-ui/dom";
|
|
7
|
-
//#region src/components/AnimationCanvas/canvas.ts
|
|
8
|
-
const drawingState = [
|
|
9
|
-
"strokeStyle",
|
|
10
|
-
"fillStyle",
|
|
11
|
-
"globalAlpha",
|
|
12
|
-
"lineWidth",
|
|
13
|
-
"lineCap",
|
|
14
|
-
"lineJoin",
|
|
15
|
-
"miterLimit",
|
|
16
|
-
"lineDashOffset",
|
|
17
|
-
"shadowOffsetX",
|
|
18
|
-
"shadowOffsetY",
|
|
19
|
-
"shadowBlur",
|
|
20
|
-
"shadowColor",
|
|
21
|
-
"globalCompositeOperation",
|
|
22
|
-
"font",
|
|
23
|
-
"textAlign",
|
|
24
|
-
"textBaseline",
|
|
25
|
-
"direction",
|
|
26
|
-
"imageSmoothingEnabled"
|
|
27
|
-
];
|
|
28
|
-
function setDprConfig(canvas, context, width, height, dpr) {
|
|
29
|
-
canvas.width = width * dpr;
|
|
30
|
-
canvas.height = height * dpr;
|
|
31
|
-
context.setTransform(1, 0, 0, 1, 0, 0);
|
|
32
|
-
context.scale(dpr, dpr);
|
|
33
|
-
canvas.style.width = `${width}px`;
|
|
34
|
-
canvas.style.height = `${height}px`;
|
|
35
|
-
}
|
|
36
|
-
//#endregion
|
|
5
|
+
import { applyDelta, blackKeyWidth, clamp, decimalPart, formatValue, getNoteRangeArray, isWhiteKey, linearScale, noteKey, notePosition, parseValue, pianoWidth, radian, styleHelper, toFixed, xor } from "@tremolo-ui/functions";
|
|
37
6
|
//#region src/components/AnimationCanvas/index.tsx
|
|
38
|
-
function AnimationCanvas({ draw, init, animate = true, options, width
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
7
|
+
function AnimationCanvas({ draw, init, animate = true, options, width = 100, height = 100, relativeSize = false, reduceFlickering = true, className, onContextMenu = (event) => event.preventDefault(), ...props }) {
|
|
8
|
+
const [node, setNode] = useState(null);
|
|
9
|
+
const latest = useRef({
|
|
10
|
+
draw,
|
|
11
|
+
init,
|
|
12
|
+
animate,
|
|
13
|
+
width,
|
|
14
|
+
height,
|
|
15
|
+
reduceFlickering,
|
|
16
|
+
options
|
|
17
|
+
});
|
|
18
|
+
const instanceRef = useRef(null);
|
|
19
|
+
/** Set while the instance below is newer than the effect that pushes into it. */
|
|
20
|
+
const justCreated = useRef(false);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!node) return;
|
|
23
|
+
const current = latest.current;
|
|
24
|
+
const instance = createAnimationCanvas(node, {
|
|
25
|
+
draw: (context, frame) => latest.current.draw(context, frame),
|
|
26
|
+
init: (context, size) => latest.current.init?.(context, size),
|
|
27
|
+
animate: current.animate,
|
|
28
|
+
size: {
|
|
29
|
+
width: current.width,
|
|
30
|
+
height: current.height
|
|
31
|
+
},
|
|
32
|
+
reduceFlickering: current.reduceFlickering,
|
|
33
|
+
relativeSize,
|
|
34
|
+
contextAttributes: current.options
|
|
59
35
|
});
|
|
60
|
-
|
|
36
|
+
instanceRef.current = instance;
|
|
37
|
+
justCreated.current = true;
|
|
38
|
+
return () => {
|
|
39
|
+
instanceRef.current = null;
|
|
40
|
+
instance.destroy();
|
|
41
|
+
};
|
|
42
|
+
}, [node, relativeSize]);
|
|
61
43
|
useEffect(() => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
heightRef.current = height;
|
|
71
|
-
if (init) init(context, {
|
|
72
|
-
width,
|
|
73
|
-
height
|
|
74
|
-
});
|
|
75
|
-
const now = performance.now();
|
|
76
|
-
deltaMemoRef.current = now;
|
|
77
|
-
startTimeRef.current = now;
|
|
78
|
-
loop(context, widthRef, heightRef, -1);
|
|
44
|
+
latest.current = {
|
|
45
|
+
draw,
|
|
46
|
+
init,
|
|
47
|
+
animate,
|
|
48
|
+
width,
|
|
49
|
+
height,
|
|
50
|
+
reduceFlickering,
|
|
51
|
+
options
|
|
79
52
|
};
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const memoCanvas = memoCanvasRef.current;
|
|
84
|
-
const memoContext = memoCanvas?.getContext("2d", options);
|
|
85
|
-
const ro = new ResizeObserver((entries) => {
|
|
86
|
-
for (const entry of entries) {
|
|
87
|
-
const w = entry.contentRect.width;
|
|
88
|
-
const h = entry.contentRect.height;
|
|
89
|
-
const contextMemo = {};
|
|
90
|
-
if (reduceFlickering && memoCanvas && memoContext) {
|
|
91
|
-
for (const prop of drawingState) contextMemo[prop] = context[prop];
|
|
92
|
-
memoCanvas.width = w * dpr;
|
|
93
|
-
memoCanvas.height = h * dpr;
|
|
94
|
-
memoContext.scale(1 / dpr, 1 / dpr);
|
|
95
|
-
if (canvas.width > 0 && canvas.height > 0) memoContext.drawImage(canvas, 0, 0);
|
|
96
|
-
}
|
|
97
|
-
setDprConfig(canvas, context, w, h, dpr);
|
|
98
|
-
widthRef.current = w;
|
|
99
|
-
heightRef.current = h;
|
|
100
|
-
if (reduceFlickering && memoContext && memoCanvas && memoCanvas.width > 0 && memoCanvas.height > 0) {
|
|
101
|
-
for (const prop of drawingState) context[prop] = contextMemo[prop];
|
|
102
|
-
context.drawImage(memoContext.canvas, 0, 0);
|
|
103
|
-
}
|
|
104
|
-
if (!animate) loop(context, widthRef, heightRef, -1);
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
ro.observe(parent);
|
|
108
|
-
const width = parent.clientWidth;
|
|
109
|
-
const height = parent.clientHeight;
|
|
110
|
-
firstRendering(width, height);
|
|
111
|
-
return () => {
|
|
112
|
-
if (reqIdRef.current) cancelAnimationFrame(reqIdRef.current);
|
|
113
|
-
ro.disconnect();
|
|
114
|
-
};
|
|
115
|
-
} else {
|
|
116
|
-
const { width, height } = canvas.getBoundingClientRect();
|
|
117
|
-
firstRendering(width, height);
|
|
118
|
-
return () => {
|
|
119
|
-
if (reqIdRef.current) cancelAnimationFrame(reqIdRef.current);
|
|
120
|
-
};
|
|
53
|
+
if (justCreated.current) {
|
|
54
|
+
justCreated.current = false;
|
|
55
|
+
return;
|
|
121
56
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
57
|
+
instanceRef.current?.update({
|
|
58
|
+
animate,
|
|
59
|
+
size: {
|
|
60
|
+
width,
|
|
61
|
+
height
|
|
62
|
+
},
|
|
63
|
+
reduceFlickering
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
return /* @__PURE__ */ jsx("canvas", {
|
|
131
67
|
className: clsx("tremolo-animation-canvas", className),
|
|
132
|
-
|
|
133
|
-
height: relativeSize ? 0 : _height,
|
|
134
|
-
ref: canvasRef,
|
|
68
|
+
ref: setNode,
|
|
135
69
|
onContextMenu,
|
|
136
70
|
...props
|
|
137
|
-
})
|
|
138
|
-
style: { display: "none" },
|
|
139
|
-
ref: memoCanvasRef
|
|
140
|
-
})] });
|
|
71
|
+
});
|
|
141
72
|
}
|
|
142
73
|
//#endregion
|
|
143
|
-
//#region src/hooks/useCallbackRef.ts
|
|
74
|
+
//#region src/hooks/_internal/useCallbackRef.ts
|
|
144
75
|
/**
|
|
145
76
|
* Internal
|
|
146
77
|
* @private
|
|
@@ -155,110 +86,96 @@ function useCallbackRef(callback, deps = []) {
|
|
|
155
86
|
return useCallback((...args) => callbackRef.current?.(...args), deps);
|
|
156
87
|
}
|
|
157
88
|
//#endregion
|
|
158
|
-
//#region src/hooks/
|
|
159
|
-
|
|
160
|
-
|
|
89
|
+
//#region src/hooks/useDragValue.ts
|
|
90
|
+
/**
|
|
91
|
+
* Drive a value with a pointer drag.
|
|
92
|
+
*
|
|
93
|
+
* @returns a ref callback for the element that starts the drag, and whether a drag is in progress
|
|
94
|
+
*/
|
|
95
|
+
function useDragValue(options) {
|
|
96
|
+
const { axis, baseElementRef, pixelRange, updateOnPointerDown, threshold, cursor } = options;
|
|
97
|
+
if (!baseElementRef && !options.getValue) throw new Error("useDragValue: give either baseElementRef or getValue, so that the drag has something to move");
|
|
98
|
+
const [dragging, setDragging] = useState(false);
|
|
99
|
+
const valueGetter = useCallbackRef(options.getValue);
|
|
100
|
+
const changeHandler = useCallbackRef(options.onChange);
|
|
101
|
+
const dragStartHandler = useCallbackRef(options.onDragStart);
|
|
102
|
+
const dragEndHandler = useCallbackRef(options.onDragEnd);
|
|
103
|
+
const latest = useRef(options);
|
|
104
|
+
const instanceRef = useRef(null);
|
|
105
|
+
const [node, setNode] = useState(null);
|
|
106
|
+
const [pixelRangeX, pixelRangeY] = Array.isArray(pixelRange) ? pixelRange : [pixelRange, pixelRange];
|
|
161
107
|
useEffect(() => {
|
|
162
|
-
const node = typeof target === "function" ? target() : target ?? document;
|
|
163
108
|
if (!node) return;
|
|
164
|
-
node
|
|
109
|
+
const instance = createDragValue(node, {
|
|
110
|
+
axis: latest.current.axis,
|
|
111
|
+
mapping: baseElementRef ? elementMapping(() => baseElementRef.current) : relativeMapping({ pixelRange: [pixelRangeX ?? 100, pixelRangeY ?? 100] }),
|
|
112
|
+
getValue: () => valueGetter() ?? [0, 0],
|
|
113
|
+
updateOnPointerDown: latest.current.updateOnPointerDown,
|
|
114
|
+
threshold: latest.current.threshold,
|
|
115
|
+
cursor: latest.current.cursor,
|
|
116
|
+
onChange: (value) => changeHandler(value),
|
|
117
|
+
onDragStart: (value) => {
|
|
118
|
+
setDragging(true);
|
|
119
|
+
dragStartHandler(value);
|
|
120
|
+
},
|
|
121
|
+
onDragEnd: (value) => {
|
|
122
|
+
setDragging(false);
|
|
123
|
+
dragEndHandler(value);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
instanceRef.current = instance;
|
|
165
127
|
return () => {
|
|
166
|
-
|
|
128
|
+
instanceRef.current = null;
|
|
129
|
+
instance.destroy();
|
|
167
130
|
};
|
|
168
131
|
}, [
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
132
|
+
node,
|
|
133
|
+
baseElementRef,
|
|
134
|
+
pixelRangeX,
|
|
135
|
+
pixelRangeY,
|
|
136
|
+
valueGetter,
|
|
137
|
+
changeHandler,
|
|
138
|
+
dragStartHandler,
|
|
139
|
+
dragEndHandler
|
|
173
140
|
]);
|
|
174
|
-
|
|
175
|
-
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
latest.current = options;
|
|
143
|
+
instanceRef.current?.update({
|
|
144
|
+
axis,
|
|
145
|
+
updateOnPointerDown,
|
|
146
|
+
threshold,
|
|
147
|
+
cursor
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
refCallback: setNode,
|
|
152
|
+
dragging
|
|
176
153
|
};
|
|
177
154
|
}
|
|
178
155
|
//#endregion
|
|
179
|
-
//#region src/hooks/
|
|
180
|
-
function useRefCallbackEvent(event, handler, options, deps = []) {
|
|
181
|
-
return useMemo(() => {
|
|
182
|
-
let cleanup;
|
|
183
|
-
return (node) => {
|
|
184
|
-
if (!node) {
|
|
185
|
-
cleanup?.();
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
node.addEventListener(event, handler, options);
|
|
189
|
-
cleanup = () => {
|
|
190
|
-
node.removeEventListener(event, handler, options);
|
|
191
|
-
};
|
|
192
|
-
};
|
|
193
|
-
}, deps);
|
|
194
|
-
}
|
|
195
|
-
//#endregion
|
|
196
|
-
//#region src/hooks/useDrag.ts
|
|
156
|
+
//#region src/hooks/useWheel.ts
|
|
197
157
|
/**
|
|
198
|
-
*
|
|
158
|
+
* Listen to wheel events on an element.
|
|
159
|
+
*
|
|
160
|
+
* The listener is not passive, so the handler may call `preventDefault()`.
|
|
161
|
+
*
|
|
162
|
+
* @returns a ref callback to attach to the element, unused when `target` is given
|
|
199
163
|
*/
|
|
200
|
-
function
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
dragStartY.current = screenY;
|
|
216
|
-
}
|
|
217
|
-
if (dragOffsetX.current) {
|
|
218
|
-
deltaX = screenX - dragOffsetX.current;
|
|
219
|
-
dragOffsetX.current = screenX;
|
|
220
|
-
}
|
|
221
|
-
if (dragOffsetY.current) {
|
|
222
|
-
deltaY = screenY - dragOffsetY.current;
|
|
223
|
-
dragOffsetY.current = screenY;
|
|
224
|
-
}
|
|
225
|
-
if (Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold) return;
|
|
226
|
-
onDrag?.(screenX - dragStartX.current, screenY - dragStartY.current, deltaX, deltaY);
|
|
227
|
-
}, [threshold, onDrag]);
|
|
228
|
-
const pointerDownHandler = useCallback((event) => {
|
|
229
|
-
dragOffsetX.current = event.screenX;
|
|
230
|
-
dragOffsetY.current = event.screenY;
|
|
231
|
-
handleDrag(event, true);
|
|
232
|
-
onDragStart?.();
|
|
233
|
-
}, [handleDrag, onDragStart]);
|
|
234
|
-
const refHandler = useRefCallbackEvent("touchmove", handleDrag, { passive: false }, [onDrag]);
|
|
235
|
-
useEventListener(globalThis.window, "mousemove", handleDrag);
|
|
236
|
-
useEventListener(globalThis.window, "pointerup", () => {
|
|
237
|
-
dragOffsetX.current = void 0;
|
|
238
|
-
dragOffsetY.current = void 0;
|
|
239
|
-
onDragEnd?.();
|
|
240
|
-
});
|
|
241
|
-
return [refHandler, pointerDownHandler];
|
|
242
|
-
}
|
|
243
|
-
//#endregion
|
|
244
|
-
//#region src/components/DragObserver/index.tsx
|
|
245
|
-
function DragObserver(props) {
|
|
246
|
-
const { as: Component = "div", threshold = 1, onDrag, onDragStart, onDragEnd, children, onPointerDown, ...attributes } = props;
|
|
247
|
-
const [refCallback, pointerDownHandler] = useDrag({
|
|
248
|
-
threshold,
|
|
249
|
-
onDrag,
|
|
250
|
-
onDragStart,
|
|
251
|
-
onDragEnd
|
|
252
|
-
});
|
|
253
|
-
return /* @__PURE__ */ jsx(Component, {
|
|
254
|
-
ref: refCallback,
|
|
255
|
-
onPointerDown: (e) => {
|
|
256
|
-
pointerDownHandler(e);
|
|
257
|
-
onPointerDown?.(e);
|
|
258
|
-
},
|
|
259
|
-
...attributes,
|
|
260
|
-
children
|
|
261
|
-
});
|
|
164
|
+
function useWheel(onWheel, { requireFocus = false, target } = {}) {
|
|
165
|
+
const wheelHandler = useCallbackRef(onWheel);
|
|
166
|
+
const [node, setNode] = useState(null);
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
const element = target ? target.current : node;
|
|
169
|
+
if (!element) return;
|
|
170
|
+
const instance = createWheel(element, (event) => wheelHandler(event), { requireFocus });
|
|
171
|
+
return () => instance.destroy();
|
|
172
|
+
}, [
|
|
173
|
+
node,
|
|
174
|
+
target,
|
|
175
|
+
wheelHandler,
|
|
176
|
+
requireFocus
|
|
177
|
+
]);
|
|
178
|
+
return setNode;
|
|
262
179
|
}
|
|
263
180
|
//#endregion
|
|
264
181
|
//#region src/components/_util/index.ts
|
|
@@ -268,84 +185,92 @@ function addUserSelectNone() {
|
|
|
268
185
|
function removeUserSelectNone() {
|
|
269
186
|
window.document.body.classList.remove("tremolo-user-select-none");
|
|
270
187
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
function
|
|
279
|
-
|
|
280
|
-
window.document.body.classList.add(cursorStyles[type]);
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/components/_util/composeRefs.tsx
|
|
190
|
+
/**
|
|
191
|
+
* Set a given ref to a given value
|
|
192
|
+
* This utility takes care of different types of refs: callback refs and RefObject(s)
|
|
193
|
+
*/
|
|
194
|
+
function setRef(ref, value) {
|
|
195
|
+
if (typeof ref === "function") return ref(value);
|
|
196
|
+
else if (ref !== null && ref !== void 0) ref.current = value;
|
|
281
197
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
198
|
+
/**
|
|
199
|
+
* A utility to compose multiple refs together
|
|
200
|
+
* Accepts callback refs and RefObject(s)
|
|
201
|
+
*/
|
|
202
|
+
function composeRefs(...refs) {
|
|
203
|
+
return (node) => {
|
|
204
|
+
let hasCleanup = false;
|
|
205
|
+
const cleanups = refs.map((ref) => {
|
|
206
|
+
const cleanup = setRef(ref, node);
|
|
207
|
+
if (!hasCleanup && typeof cleanup == "function") hasCleanup = true;
|
|
208
|
+
return cleanup;
|
|
209
|
+
});
|
|
210
|
+
if (hasCleanup) return () => {
|
|
211
|
+
for (let i = 0; i < cleanups.length; i++) {
|
|
212
|
+
const cleanup = cleanups[i];
|
|
213
|
+
if (typeof cleanup == "function") cleanup();
|
|
214
|
+
else setRef(refs[i], null);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* A custom hook that composes multiple refs
|
|
221
|
+
* Accepts callback refs and RefObject(s)
|
|
222
|
+
*/
|
|
223
|
+
function useComposedRefs(...refs) {
|
|
224
|
+
return useCallback(composeRefs(...refs), refs);
|
|
286
225
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
226
|
+
/**
|
|
227
|
+
* 角度から円弧上の点を求める。
|
|
228
|
+
*
|
|
229
|
+
* 半径は線の太さの分だけ内側に取る必要があるが、太さは ActiveLine / InactiveLine が
|
|
230
|
+
* それぞれ持つため、座標はストアで先に計算せず各コンポーネント側で求める。
|
|
231
|
+
*/
|
|
232
|
+
function pointOnArc(angle, radius) {
|
|
233
|
+
return {
|
|
234
|
+
x: 50 + radius * Math.cos(radian(angle - 90)),
|
|
235
|
+
y: 50 + radius * Math.sin(radian(angle - 90))
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/** 線の太さが viewBox からはみ出さないようにした半径 */
|
|
239
|
+
function arcRadius(strokeWidth) {
|
|
240
|
+
const width = typeof strokeWidth === "number" ? strokeWidth : Number.parseFloat(String(strokeWidth));
|
|
241
|
+
return Number.isFinite(width) ? 50 - width / 2 : 50;
|
|
242
|
+
}
|
|
243
|
+
/** 設定から描画に必要な角度を導出する。レンダー中に呼ぶ。 */
|
|
244
|
+
function calcAngles({ value, min, max, scale, startValue, angleRange }) {
|
|
245
|
+
const p = scale.normalize(value, min, max);
|
|
246
|
+
const s = scale.normalize(startValue, min, max);
|
|
290
247
|
const r1 = -angleRange / 2;
|
|
291
|
-
const r2 = r1 + Math.min(p, s) * angleRange;
|
|
292
|
-
const r3 = r1 + Math.max(p, s) * angleRange;
|
|
293
|
-
const r4 = angleRange / 2;
|
|
294
248
|
return {
|
|
295
249
|
p,
|
|
296
250
|
r1,
|
|
297
|
-
r2,
|
|
298
|
-
r3,
|
|
299
|
-
r4
|
|
300
|
-
x1: 50 + 50 * Math.cos(radian(r1 - 90)),
|
|
301
|
-
y1: 50 + 50 * Math.sin(radian(r1 - 90)),
|
|
302
|
-
x2: 50 + 50 * Math.cos(radian(r2 - 90)),
|
|
303
|
-
y2: 50 + 50 * Math.sin(radian(r2 - 90)),
|
|
304
|
-
x3: 50 + 50 * Math.cos(radian(r3 - 90)),
|
|
305
|
-
y3: 50 + 50 * Math.sin(radian(r3 - 90)),
|
|
306
|
-
x4: 50 + 50 * Math.cos(radian(r4 - 90)),
|
|
307
|
-
y4: 50 + 50 * Math.sin(radian(r4 - 90))
|
|
251
|
+
r2: r1 + Math.min(p, s) * angleRange,
|
|
252
|
+
r3: r1 + Math.max(p, s) * angleRange,
|
|
253
|
+
r4: angleRange / 2
|
|
308
254
|
};
|
|
309
255
|
}
|
|
310
|
-
const createKnobStore = (initProps) => {
|
|
311
|
-
return createStore()(() => ({
|
|
312
|
-
...initProps,
|
|
313
|
-
...calcDrawingState(initProps)
|
|
314
|
-
}));
|
|
315
|
-
};
|
|
316
256
|
const KnobContext = createContext(null);
|
|
317
|
-
|
|
318
|
-
const storeRef = useRef(null);
|
|
319
|
-
if (!storeRef.current) storeRef.current = createKnobStore(props);
|
|
320
|
-
useEffect(() => {
|
|
321
|
-
if (storeRef.current) storeRef.current.setState({
|
|
322
|
-
...props,
|
|
323
|
-
...calcDrawingState(props)
|
|
324
|
-
});
|
|
325
|
-
else storeRef.current = createKnobStore(props);
|
|
326
|
-
}, [props]);
|
|
327
|
-
return /* @__PURE__ */ jsx(KnobContext.Provider, {
|
|
328
|
-
value: storeRef.current,
|
|
329
|
-
children
|
|
330
|
-
});
|
|
331
|
-
}
|
|
257
|
+
const KnobProvider = KnobContext.Provider;
|
|
332
258
|
function useKnobContext(selector) {
|
|
333
|
-
const
|
|
334
|
-
if (!
|
|
335
|
-
return
|
|
259
|
+
const context = useContext(KnobContext);
|
|
260
|
+
if (!context) throw new Error("Missing KnobContext.Provider in the tree");
|
|
261
|
+
return selector ? selector(context) : context;
|
|
336
262
|
}
|
|
337
263
|
//#endregion
|
|
338
264
|
//#region src/components/Knob/ActiveLine.tsx
|
|
339
265
|
function ActiveLine({ stroke = "currentColor", strokeWidth = 6, className, ...props }) {
|
|
340
266
|
const r2 = useKnobContext((s) => s.r2);
|
|
341
267
|
const r3 = useKnobContext((s) => s.r3);
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
const y3 = useKnobContext((s) => s.y3);
|
|
268
|
+
const radius = arcRadius(strokeWidth);
|
|
269
|
+
const start = pointOnArc(r2, radius);
|
|
270
|
+
const end = pointOnArc(r3, radius);
|
|
346
271
|
return /* @__PURE__ */ jsx("path", {
|
|
347
272
|
className: clsx("tremolo-knob-active-line", className),
|
|
348
|
-
d: `M ${
|
|
273
|
+
d: `M ${start.x} ${start.y} A ${radius} ${radius} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${end.x} ${end.y}`,
|
|
349
274
|
fill: "none",
|
|
350
275
|
stroke,
|
|
351
276
|
strokeWidth,
|
|
@@ -362,24 +287,21 @@ function InactiveLine({ stroke = "currentColor", strokeWidth = 6, className, ...
|
|
|
362
287
|
const r2 = useKnobContext((s) => s.r2);
|
|
363
288
|
const r3 = useKnobContext((s) => s.r3);
|
|
364
289
|
const r4 = useKnobContext((s) => s.r4);
|
|
365
|
-
const
|
|
366
|
-
const
|
|
367
|
-
const
|
|
368
|
-
const
|
|
369
|
-
const
|
|
370
|
-
const y3 = useKnobContext((s) => s.y3);
|
|
371
|
-
const x4 = useKnobContext((s) => s.x4);
|
|
372
|
-
const y4 = useKnobContext((s) => s.y4);
|
|
290
|
+
const radius = arcRadius(strokeWidth);
|
|
291
|
+
const p1 = pointOnArc(r1, radius);
|
|
292
|
+
const p2 = pointOnArc(r2, radius);
|
|
293
|
+
const p3 = pointOnArc(r3, radius);
|
|
294
|
+
const p4 = pointOnArc(r4, radius);
|
|
373
295
|
return /* @__PURE__ */ jsxs(Fragment, { children: [startValue > min && /* @__PURE__ */ jsx("path", {
|
|
374
296
|
className: clsx("tremolo-knob-inactive-line", className),
|
|
375
|
-
d: `M ${
|
|
297
|
+
d: `M ${p1.x} ${p1.y} A ${radius} ${radius} -135 ${r2 - r1 > 180 ? 1 : 0} 1 ${p2.x} ${p2.y}`,
|
|
376
298
|
fill: "none",
|
|
377
299
|
stroke,
|
|
378
300
|
strokeWidth,
|
|
379
301
|
...props
|
|
380
302
|
}), startValue < max && /* @__PURE__ */ jsx("path", {
|
|
381
303
|
className: clsx("tremolo-knob-inactive-line", className),
|
|
382
|
-
d: `M ${
|
|
304
|
+
d: `M ${p3.x} ${p3.y} A ${radius} ${radius} -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${p4.x} ${p4.y}`,
|
|
383
305
|
fill: "none",
|
|
384
306
|
stroke,
|
|
385
307
|
strokeWidth,
|
|
@@ -387,6 +309,19 @@ function InactiveLine({ stroke = "currentColor", strokeWidth = 6, className, ...
|
|
|
387
309
|
})] });
|
|
388
310
|
}
|
|
389
311
|
//#endregion
|
|
312
|
+
//#region src/components/Knob/SVGRoot.tsx
|
|
313
|
+
function SVGRoot({ children, style, ...props }) {
|
|
314
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
315
|
+
viewBox: `0 0 100 100`,
|
|
316
|
+
style: {
|
|
317
|
+
display: "block",
|
|
318
|
+
...style
|
|
319
|
+
},
|
|
320
|
+
...props,
|
|
321
|
+
children
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
390
325
|
//#region src/components/Knob/Thumb.tsx
|
|
391
326
|
function Thumb$2({ className, thumb = "currentColor", thumbLine = "currentColor", thumbSize = 84, thumbLineWeight = 6, thumbLineLength = 35, classes, ...props }) {
|
|
392
327
|
const angleRange = useKnobContext((s) => s.angleRange);
|
|
@@ -416,63 +351,36 @@ function Thumb$2({ className, thumb = "currentColor", thumbLine = "currentColor"
|
|
|
416
351
|
});
|
|
417
352
|
}
|
|
418
353
|
//#endregion
|
|
419
|
-
//#region src/components/Knob/SVGRoot.tsx
|
|
420
|
-
function SVGRoot({ children, style, ...props }) {
|
|
421
|
-
return /* @__PURE__ */ jsx("svg", {
|
|
422
|
-
viewBox: `0 0 100 100`,
|
|
423
|
-
style: {
|
|
424
|
-
display: "block",
|
|
425
|
-
overflow: "visible",
|
|
426
|
-
...style
|
|
427
|
-
},
|
|
428
|
-
...props,
|
|
429
|
-
children: children || /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
430
|
-
/* @__PURE__ */ jsx(InactiveLine, {}),
|
|
431
|
-
/* @__PURE__ */ jsx(ActiveLine, {}),
|
|
432
|
-
/* @__PURE__ */ jsx(Thumb$2, {})
|
|
433
|
-
] })
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
//#endregion
|
|
437
354
|
//#region src/components/Knob/index.tsx
|
|
438
|
-
const defaultExternalStyles$
|
|
355
|
+
const defaultExternalStyles$2 = {
|
|
439
356
|
userSelectNone: true,
|
|
440
357
|
cursor: "grabbing"
|
|
441
358
|
};
|
|
442
359
|
/**
|
|
360
|
+
* The wheel only acts while the focus is inside, so that scrolling a page past
|
|
361
|
+
* the control does not change its value.
|
|
362
|
+
*/
|
|
363
|
+
const WHEEL_OPTIONS$2 = { requireFocus: true };
|
|
364
|
+
/**
|
|
443
365
|
* Interactive rotary knob component implemented in SVG.
|
|
444
366
|
*/
|
|
445
367
|
const Knob = {
|
|
446
|
-
Root: forwardRef(({ value, min, max, step = 1,
|
|
447
|
-
const [dragging, setDragging] = useState(false);
|
|
448
|
-
const valueRef = useRef(0);
|
|
368
|
+
Root: forwardRef(({ value, min, max, step = 1, scale = linearScale, defaultValue = min, startValue = min, size, externalStyles: _externalStyles, wheel = ["raw", 1], keyboard = ["raw", 1], enableDoubleClickDefault = true, disabled = false, readonly = false, angleRange = 270, onChange, onKeyDown, onPointerDown, onDoubleClick, className, style, children, ...props }, forwardedRef) => {
|
|
449
369
|
const elmRef = useRef(null);
|
|
450
370
|
const externalStyles = {
|
|
451
|
-
...defaultExternalStyles$
|
|
371
|
+
...defaultExternalStyles$2,
|
|
452
372
|
..._externalStyles
|
|
453
373
|
};
|
|
454
|
-
const
|
|
455
|
-
if (!onChange || readonly) return;
|
|
456
|
-
onChange(clamp(stepValue(rawValue(valueRef.current - y / 100, min, max, skew), step), min, max));
|
|
457
|
-
}, [
|
|
458
|
-
max,
|
|
374
|
+
const range = useMemo(() => ({
|
|
459
375
|
min,
|
|
460
|
-
onChange,
|
|
461
|
-
readonly,
|
|
462
|
-
skew,
|
|
463
|
-
step
|
|
464
|
-
]);
|
|
465
|
-
const updateValueByEvent = useCallback((eventType, x) => {
|
|
466
|
-
let newValue;
|
|
467
|
-
if (eventType == "normalized") newValue = rawValue(normalizeValue(value, min, max, skew) + x, min, max, skew);
|
|
468
|
-
else newValue = value + x;
|
|
469
|
-
return clamp(stepValue(newValue, step), min, max);
|
|
470
|
-
}, [
|
|
471
376
|
max,
|
|
377
|
+
step,
|
|
378
|
+
scale
|
|
379
|
+
}), [
|
|
472
380
|
min,
|
|
473
|
-
|
|
381
|
+
max,
|
|
474
382
|
step,
|
|
475
|
-
|
|
383
|
+
scale
|
|
476
384
|
]);
|
|
477
385
|
const handleKeyDown = useCallback((event) => {
|
|
478
386
|
if (!keyboard || !onChange || readonly) return;
|
|
@@ -484,40 +392,62 @@ const Knob = {
|
|
|
484
392
|
"ArrowDown"
|
|
485
393
|
].includes(key)) {
|
|
486
394
|
event.preventDefault();
|
|
487
|
-
|
|
488
|
-
onChange(updateValueByEvent(keyboard[0], x));
|
|
395
|
+
onChange(applyDelta(value, key == "ArrowRight" || key == "ArrowUp" ? 1 : -1, keyboard, range));
|
|
489
396
|
}
|
|
490
397
|
}, [
|
|
491
398
|
keyboard,
|
|
492
399
|
onChange,
|
|
493
400
|
readonly,
|
|
494
|
-
|
|
401
|
+
value,
|
|
402
|
+
range
|
|
495
403
|
]);
|
|
496
|
-
const
|
|
497
|
-
|
|
404
|
+
const { refCallback: dragRefCallback, dragging } = useDragValue({
|
|
405
|
+
axis: useMemo(() => [range, {
|
|
406
|
+
...range,
|
|
407
|
+
reverse: true
|
|
408
|
+
}], [range]),
|
|
409
|
+
getValue: () => [value, value],
|
|
410
|
+
threshold: 1,
|
|
411
|
+
cursor: readonly ? void 0 : externalStyles.cursor,
|
|
412
|
+
onChange: (v) => {
|
|
413
|
+
if (readonly) return;
|
|
414
|
+
onChange?.(v[1]);
|
|
415
|
+
},
|
|
498
416
|
onDragStart: () => {
|
|
499
|
-
setDragging(true);
|
|
500
|
-
valueRef.current = normalizeValue(value, min, max, skew);
|
|
501
417
|
if (externalStyles.userSelectNone) addUserSelectNone();
|
|
502
|
-
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
|
|
503
418
|
},
|
|
504
419
|
onDragEnd: () => {
|
|
505
|
-
setDragging(false);
|
|
506
420
|
if (externalStyles.userSelectNone) removeUserSelectNone();
|
|
507
|
-
if (externalStyles.cursor) resetCursorStyle();
|
|
508
421
|
}
|
|
509
422
|
});
|
|
510
|
-
const
|
|
423
|
+
const rootRefCallback = useComposedRefs(elmRef, dragRefCallback, useWheel((event) => {
|
|
511
424
|
if (!wheel || readonly) return;
|
|
512
425
|
event.preventDefault();
|
|
513
426
|
if (!onChange || event.deltaY == 0) return;
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
427
|
+
onChange(applyDelta(value, -Math.sign(event.deltaY), wheel, range));
|
|
428
|
+
}, WHEEL_OPTIONS$2));
|
|
429
|
+
const context = useMemo(() => {
|
|
430
|
+
const config = {
|
|
431
|
+
value,
|
|
432
|
+
min,
|
|
433
|
+
max,
|
|
434
|
+
step,
|
|
435
|
+
scale,
|
|
436
|
+
startValue,
|
|
437
|
+
angleRange
|
|
438
|
+
};
|
|
439
|
+
return {
|
|
440
|
+
...config,
|
|
441
|
+
...calcAngles(config)
|
|
442
|
+
};
|
|
443
|
+
}, [
|
|
444
|
+
value,
|
|
445
|
+
min,
|
|
446
|
+
max,
|
|
447
|
+
step,
|
|
448
|
+
scale,
|
|
449
|
+
startValue,
|
|
450
|
+
angleRange
|
|
521
451
|
]);
|
|
522
452
|
useImperativeHandle(forwardedRef, () => {
|
|
523
453
|
return {
|
|
@@ -530,19 +460,9 @@ const Knob = {
|
|
|
530
460
|
};
|
|
531
461
|
}, []);
|
|
532
462
|
return /* @__PURE__ */ jsx(KnobProvider, {
|
|
533
|
-
value,
|
|
534
|
-
min,
|
|
535
|
-
max,
|
|
536
|
-
step,
|
|
537
|
-
skew,
|
|
538
|
-
startValue,
|
|
539
|
-
angleRange,
|
|
463
|
+
value: context,
|
|
540
464
|
children: /* @__PURE__ */ jsx("div", {
|
|
541
|
-
ref:
|
|
542
|
-
elmRef.current = elm;
|
|
543
|
-
wheelRefCallback(elm);
|
|
544
|
-
touchMoveRefCallback(elm);
|
|
545
|
-
},
|
|
465
|
+
ref: rootRefCallback,
|
|
546
466
|
className: clsx("tremolo-knob", className),
|
|
547
467
|
tabIndex: 0,
|
|
548
468
|
role: "slider",
|
|
@@ -557,10 +477,7 @@ const Knob = {
|
|
|
557
477
|
height: size,
|
|
558
478
|
...style
|
|
559
479
|
},
|
|
560
|
-
onPointerDown
|
|
561
|
-
pointerDownHandler(event);
|
|
562
|
-
onPointerDown?.(event);
|
|
563
|
-
},
|
|
480
|
+
onPointerDown,
|
|
564
481
|
onDoubleClick: (event) => {
|
|
565
482
|
if (enableDoubleClickDefault && onChange) onChange(defaultValue);
|
|
566
483
|
onDoubleClick?.(event);
|
|
@@ -570,7 +487,7 @@ const Knob = {
|
|
|
570
487
|
onKeyDown?.(event);
|
|
571
488
|
},
|
|
572
489
|
...props,
|
|
573
|
-
children
|
|
490
|
+
children
|
|
574
491
|
})
|
|
575
492
|
});
|
|
576
493
|
}),
|
|
@@ -580,121 +497,39 @@ const Knob = {
|
|
|
580
497
|
Thumb: Thumb$2
|
|
581
498
|
};
|
|
582
499
|
//#endregion
|
|
583
|
-
//#region src/components/NumberInput/
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
500
|
+
//#region src/components/NumberInput/context.tsx
|
|
501
|
+
const NumberInputContext = createContext(null);
|
|
502
|
+
const NumberInputProvider = NumberInputContext.Provider;
|
|
503
|
+
function useNumberInputContext(selector) {
|
|
504
|
+
const context = useContext(NumberInputContext);
|
|
505
|
+
if (!context) throw new Error("Missing NumberInputContext.Provider in the tree");
|
|
506
|
+
return selector ? selector(context) : context;
|
|
588
507
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
let v = Number(m?.[0] ?? "0");
|
|
594
|
-
v = isNaN(v) ? 0 : v;
|
|
595
|
-
return {
|
|
596
|
-
rawValue: v,
|
|
597
|
-
formatValue: (digit != void 0 ? v.toFixed(digit) : v) + (units ?? ""),
|
|
598
|
-
unit: units ?? ""
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
const unitList = units.map(([u, _s]) => u);
|
|
602
|
-
const scaleList = units.map(([_u, s]) => s);
|
|
603
|
-
let rawValue = 0;
|
|
604
|
-
let unit = unitList[0];
|
|
605
|
-
let formatValue = (digit != void 0 ? rawValue.toFixed(digit) : rawValue) + unit;
|
|
606
|
-
const m = str.match(/^(-?\d+(\.\d+)?)\s*(\w*)$/);
|
|
607
|
-
if (m) {
|
|
608
|
-
rawValue = Number(m[1]) || 0;
|
|
609
|
-
const uIndex = unitList.indexOf(m[3]);
|
|
610
|
-
rawValue *= uIndex != -1 ? scaleList[uIndex] : 1;
|
|
611
|
-
const [u, scale] = selectUnit(units, rawValue);
|
|
612
|
-
const v = rawValue / scale;
|
|
613
|
-
formatValue = (digit != void 0 ? v.toFixed(digit) : v) + u;
|
|
614
|
-
unit = u;
|
|
615
|
-
}
|
|
616
|
-
return {
|
|
617
|
-
rawValue,
|
|
618
|
-
formatValue,
|
|
619
|
-
unit
|
|
620
|
-
};
|
|
508
|
+
const StepperContext = createContext(null);
|
|
509
|
+
const StepperProvider = StepperContext.Provider;
|
|
510
|
+
function useStepperContext() {
|
|
511
|
+
return useContext(StepperContext);
|
|
621
512
|
}
|
|
622
513
|
//#endregion
|
|
623
|
-
//#region src/
|
|
624
|
-
function
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
const createNumberInputStore = (initProps) => {
|
|
628
|
-
const DEFAULT_PROPS = {
|
|
629
|
-
value: "",
|
|
630
|
-
valueAsNumber: 0,
|
|
631
|
-
min: 0,
|
|
632
|
-
max: 0,
|
|
633
|
-
step: 1,
|
|
634
|
-
readonly: false,
|
|
635
|
-
keepWithinRange: true,
|
|
636
|
-
onChange: (_v, _t) => {}
|
|
637
|
-
};
|
|
638
|
-
return createStore()((set) => ({
|
|
639
|
-
...DEFAULT_PROPS,
|
|
640
|
-
...initProps,
|
|
641
|
-
value: parseValue(String(initProps?.value || ""), initProps?.units, initProps?.digit).formatValue,
|
|
642
|
-
valueAsNumber: parseValue(String(initProps?.value || ""), initProps?.units, initProps?.digit).rawValue,
|
|
643
|
-
increment: () => set((state) => {
|
|
644
|
-
if (state.readonly) return {};
|
|
645
|
-
let next = parseValue(state.value, state.units, state.digit).rawValue + (state.step ?? 1);
|
|
646
|
-
if (state.keepWithinRange) next = safeClamp(next, state.min, state.max);
|
|
647
|
-
const v = parseValue(String(next), state.units, state.digit).formatValue;
|
|
648
|
-
state?.onChange?.(next, v);
|
|
649
|
-
return {
|
|
650
|
-
value: v,
|
|
651
|
-
valueAsNumber: next
|
|
652
|
-
};
|
|
653
|
-
}),
|
|
654
|
-
decrement: () => set((state) => {
|
|
655
|
-
if (state.readonly) return {};
|
|
656
|
-
let next = parseValue(state.value, state.units, state.digit).rawValue - (state.step ?? 1);
|
|
657
|
-
if (state.keepWithinRange) next = safeClamp(next, state.min, state.max);
|
|
658
|
-
const v = parseValue(String(next), state.units, state.digit).formatValue;
|
|
659
|
-
state?.onChange?.(next, v);
|
|
660
|
-
return {
|
|
661
|
-
value: v,
|
|
662
|
-
valueAsNumber: next
|
|
663
|
-
};
|
|
664
|
-
}),
|
|
665
|
-
change: (value) => set((state) => {
|
|
666
|
-
if (state.readonly) return {};
|
|
667
|
-
const v = value ?? "";
|
|
668
|
-
return {
|
|
669
|
-
value: v,
|
|
670
|
-
valueAsNumber: parseValue(v, state.units, state.digit).rawValue
|
|
671
|
-
};
|
|
672
|
-
})
|
|
673
|
-
}));
|
|
674
|
-
};
|
|
675
|
-
const NumberInputContext = createContext(null);
|
|
676
|
-
function NumberInputProvider({ children, ...props }) {
|
|
677
|
-
const storeRef = useRef(null);
|
|
678
|
-
if (!storeRef.current) storeRef.current = createNumberInputStore(props);
|
|
514
|
+
//#region src/hooks/useEventListener.ts
|
|
515
|
+
function useEventListener(target, event, handler, options) {
|
|
516
|
+
const listener = useCallbackRef(handler);
|
|
679
517
|
useEffect(() => {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
const store = useContext(NumberInputContext);
|
|
696
|
-
if (!store) throw new Error("Missing NumberInputContext.Provider in the tree");
|
|
697
|
-
return useStore(store, selector);
|
|
518
|
+
const node = typeof target === "function" ? target() : target ?? document;
|
|
519
|
+
if (!node) return;
|
|
520
|
+
node.addEventListener(event, listener, options);
|
|
521
|
+
return () => {
|
|
522
|
+
node.removeEventListener(event, listener, options);
|
|
523
|
+
};
|
|
524
|
+
}, [
|
|
525
|
+
event,
|
|
526
|
+
target,
|
|
527
|
+
options,
|
|
528
|
+
listener
|
|
529
|
+
]);
|
|
530
|
+
return () => {
|
|
531
|
+
(typeof target === "function" ? target() : target ?? document)?.removeEventListener(event, listener, options);
|
|
532
|
+
};
|
|
698
533
|
}
|
|
699
534
|
//#endregion
|
|
700
535
|
//#region src/hooks/useInterval.ts
|
|
@@ -732,641 +567,554 @@ function useLongPress(callback, initialDelay = 500, interval = 40) {
|
|
|
732
567
|
}, [callback]);
|
|
733
568
|
}
|
|
734
569
|
//#endregion
|
|
735
|
-
//#region src/components/NumberInput/
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const
|
|
570
|
+
//#region src/components/NumberInput/stepperButton.tsx
|
|
571
|
+
/**
|
|
572
|
+
* Shared body of `IncrementStepper` and `DecrementStepper`: the two differ only
|
|
573
|
+
* in which way they move the value and which arrow they draw.
|
|
574
|
+
*/
|
|
575
|
+
function StepperButton({ direction, variant, icon, className, style, children, onPointerDown, ...props }) {
|
|
576
|
+
const { step, readonly, atMin, atMax, nudge } = useNumberInputContext();
|
|
577
|
+
const stepper = useStepperContext();
|
|
578
|
+
const press = useLongPress(useCallback(() => {
|
|
579
|
+
if (stepper?.draggingRef.current) return;
|
|
580
|
+
nudge(direction, ["raw", step]);
|
|
581
|
+
}, [
|
|
582
|
+
stepper,
|
|
583
|
+
nudge,
|
|
584
|
+
direction,
|
|
585
|
+
step
|
|
586
|
+
]));
|
|
742
587
|
return /* @__PURE__ */ jsx("div", {
|
|
743
|
-
className: clsx(
|
|
588
|
+
className: clsx(`tremolo-number-input-${variant}-stepper`, className),
|
|
744
589
|
role: "button",
|
|
745
590
|
tabIndex: -1,
|
|
746
|
-
"aria-
|
|
591
|
+
"aria-label": direction > 0 ? "Increment" : "Decrement",
|
|
592
|
+
"aria-disabled": direction > 0 ? atMax : atMin,
|
|
747
593
|
"aria-readonly": readonly,
|
|
748
|
-
|
|
594
|
+
style,
|
|
595
|
+
onPointerDown: (event) => {
|
|
596
|
+
if (!readonly) press();
|
|
597
|
+
onPointerDown?.(event);
|
|
598
|
+
},
|
|
749
599
|
...props,
|
|
750
|
-
children: children
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
600
|
+
children: children ?? icon
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
function StepperArrow({ up }) {
|
|
604
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
605
|
+
className: "tremolo-number-input-stepper-icon",
|
|
606
|
+
viewBox: "0 0 24 24",
|
|
607
|
+
fill: "none",
|
|
608
|
+
stroke: "currentColor",
|
|
609
|
+
strokeWidth: "2",
|
|
610
|
+
strokeLinecap: "round",
|
|
611
|
+
strokeLinejoin: "round",
|
|
612
|
+
children: /* @__PURE__ */ jsx("polyline", { points: up ? "18 15 12 9 6 15" : "6 9 12 15 18 9" })
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
//#endregion
|
|
616
|
+
//#region src/components/NumberInput/DecrementStepper.tsx
|
|
617
|
+
/** Lowers the value by one `step`, repeating while held. */
|
|
618
|
+
function DecrementStepper(props) {
|
|
619
|
+
return /* @__PURE__ */ jsx(StepperButton, {
|
|
620
|
+
direction: -1,
|
|
621
|
+
variant: "decrement",
|
|
622
|
+
icon: /* @__PURE__ */ jsx(StepperArrow, { up: false }),
|
|
623
|
+
...props
|
|
761
624
|
});
|
|
762
625
|
}
|
|
763
626
|
//#endregion
|
|
764
627
|
//#region src/components/NumberInput/IncrementStepper.tsx
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
className: clsx("tremolo-number-input-increment-stepper", className),
|
|
773
|
-
role: "button",
|
|
774
|
-
tabIndex: -1,
|
|
775
|
-
"aria-disabled": keepWithinRange && valueAsNumber >= (max ?? Number.MAX_SAFE_INTEGER),
|
|
776
|
-
"aria-readonly": readonly,
|
|
777
|
-
onPointerDown: press,
|
|
778
|
-
...props,
|
|
779
|
-
children: children || /* @__PURE__ */ jsx("svg", {
|
|
780
|
-
width: size,
|
|
781
|
-
height: size,
|
|
782
|
-
viewBox: "0 0 24 24",
|
|
783
|
-
fill: "none",
|
|
784
|
-
stroke: "currentColor",
|
|
785
|
-
strokeWidth: "2",
|
|
786
|
-
strokeLinecap: "round",
|
|
787
|
-
strokeLinejoin: "round",
|
|
788
|
-
children: /* @__PURE__ */ jsx("polyline", { points: "18 15 12 9 6 15" })
|
|
789
|
-
})
|
|
628
|
+
/** Raises the value by one `step`, repeating while held. */
|
|
629
|
+
function IncrementStepper(props) {
|
|
630
|
+
return /* @__PURE__ */ jsx(StepperButton, {
|
|
631
|
+
direction: 1,
|
|
632
|
+
variant: "increment",
|
|
633
|
+
icon: /* @__PURE__ */ jsx(StepperArrow, { up: true }),
|
|
634
|
+
...props
|
|
790
635
|
});
|
|
791
636
|
}
|
|
792
637
|
//#endregion
|
|
793
|
-
//#region src/components/NumberInput/
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
const
|
|
804
|
-
const change = useNumberInputContext((s) => s.change);
|
|
805
|
-
const error = useMemo(() => {
|
|
806
|
-
if (editing) return false;
|
|
807
|
-
const v = valueAsNumber;
|
|
808
|
-
return (min ?? -Infinity) > v || v > (max ?? Infinity);
|
|
809
|
-
}, [
|
|
810
|
-
editing,
|
|
811
|
-
max,
|
|
812
|
-
min,
|
|
813
|
-
valueAsNumber
|
|
814
|
-
]);
|
|
815
|
-
const updateValueByEvent = useCallback((eventType, x) => {
|
|
816
|
-
let newValue;
|
|
817
|
-
if (eventType == "normalized") {
|
|
818
|
-
if (!min || !max) throw new Error("[NumberInput] \"min\" and \"max\" are required when InputEventOption[0] is set to \"normalized\".");
|
|
819
|
-
newValue = rawValue(normalizeValue(valueAsNumber, min, max) + x, min, max);
|
|
820
|
-
return clamp(stepValue(newValue, step), min, max);
|
|
821
|
-
} else {
|
|
822
|
-
newValue = valueAsNumber + x;
|
|
823
|
-
return stepValue(newValue, step);
|
|
824
|
-
}
|
|
825
|
-
}, [
|
|
826
|
-
max,
|
|
827
|
-
min,
|
|
828
|
-
step,
|
|
829
|
-
valueAsNumber
|
|
830
|
-
]);
|
|
831
|
-
const handleKeyDown = useCallback((event) => {
|
|
832
|
-
if (!keyboard || !onChange || readonly) return;
|
|
833
|
-
const key = event.key;
|
|
834
|
-
if (!["ArrowUp", "ArrowDown"].includes(key)) return;
|
|
835
|
-
event.preventDefault();
|
|
836
|
-
const x = key == "ArrowUp" ? keyboard[1] : -keyboard[1];
|
|
837
|
-
const parsed = parseValue(String(updateValueByEvent(keyboard[0], x)));
|
|
838
|
-
onChange?.(parsed.rawValue, parsed.formatValue);
|
|
839
|
-
}, [
|
|
840
|
-
keyboard,
|
|
841
|
-
readonly,
|
|
842
|
-
onChange,
|
|
843
|
-
updateValueByEvent
|
|
844
|
-
]);
|
|
845
|
-
const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
|
|
846
|
-
if (!wheel || readonly) return;
|
|
847
|
-
event.preventDefault();
|
|
848
|
-
if (!onChange || event.deltaY == 0) return;
|
|
849
|
-
const x = Math.sign(event.deltaY) * -wheel[1];
|
|
850
|
-
const parsed = parseValue(String(updateValueByEvent(wheel[0], x)));
|
|
851
|
-
onChange?.(parsed.rawValue, parsed.formatValue);
|
|
852
|
-
}, { passive: false }, [
|
|
853
|
-
wheel,
|
|
854
|
-
readonly,
|
|
855
|
-
onChange,
|
|
856
|
-
updateValueByEvent
|
|
857
|
-
]);
|
|
858
|
-
useImperativeHandle(ref, () => {
|
|
859
|
-
return {
|
|
860
|
-
focus() {
|
|
861
|
-
elmRef.current?.focus();
|
|
862
|
-
},
|
|
863
|
-
blur() {
|
|
864
|
-
elmRef.current?.blur();
|
|
865
|
-
}
|
|
866
|
-
};
|
|
867
|
-
}, []);
|
|
638
|
+
//#region src/components/NumberInput/InputField.tsx
|
|
639
|
+
/** The leading number of the displayed text, whatever the format put around it. */
|
|
640
|
+
const NUMBER_PREFIX = /^\s*-?[\d.,]*/;
|
|
641
|
+
/**
|
|
642
|
+
* The text field of a `NumberInput`, and the only place the value can be typed.
|
|
643
|
+
*
|
|
644
|
+
* While the user types, their own text stands rather than `format(value)`, so
|
|
645
|
+
* that a half-finished entry is not rewritten under the caret.
|
|
646
|
+
*/
|
|
647
|
+
function InputField({ selectOnFocus = "none", blurOnEnter = true, className, style, ref, onFocus, onBlur, onKeyDown, ...props }) {
|
|
648
|
+
const { value, min, max, step, disabled, readonly, keyboard, text, outOfRange, setDraft, commitDraft, nudge, inputRef } = useNumberInputContext();
|
|
868
649
|
return /* @__PURE__ */ jsx("input", {
|
|
869
|
-
ref: (
|
|
870
|
-
|
|
871
|
-
wheelRefCallback(input);
|
|
872
|
-
},
|
|
873
|
-
className: clsx("tremolo-number-input", className),
|
|
874
|
-
value,
|
|
875
|
-
min,
|
|
876
|
-
max,
|
|
877
|
-
step,
|
|
650
|
+
ref: useComposedRefs(ref, inputRef),
|
|
651
|
+
className: clsx("tremolo-number-input-field", className),
|
|
878
652
|
type: "text",
|
|
653
|
+
inputMode: "decimal",
|
|
654
|
+
role: "spinbutton",
|
|
655
|
+
value: text,
|
|
656
|
+
readOnly: readonly,
|
|
879
657
|
"aria-disabled": disabled,
|
|
880
658
|
"aria-readonly": readonly,
|
|
881
|
-
|
|
882
|
-
"
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
onChange?.(valueAsNumber, v);
|
|
890
|
-
if (cursorPos) {
|
|
891
|
-
event.target.selectionStart = cursorPos;
|
|
892
|
-
event.target.selectionEnd = cursorPos;
|
|
893
|
-
}
|
|
894
|
-
},
|
|
659
|
+
"aria-valuenow": value,
|
|
660
|
+
"aria-valuemin": min,
|
|
661
|
+
"aria-valuemax": max,
|
|
662
|
+
"aria-valuetext": text,
|
|
663
|
+
step,
|
|
664
|
+
"data-out-of-range": outOfRange,
|
|
665
|
+
style,
|
|
666
|
+
onChange: (event) => setDraft(event.currentTarget.value),
|
|
895
667
|
onFocus: (event) => {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
else if (selectWithFocus == "number") event.currentTarget.setSelectionRange(0, parsed.formatValue.length - parsed.unit.length);
|
|
901
|
-
onFocus?.(parsed.rawValue, parsed.formatValue, event);
|
|
668
|
+
const input = event.currentTarget;
|
|
669
|
+
if (selectOnFocus == "all") input.setSelectionRange(0, input.value.length);
|
|
670
|
+
else if (selectOnFocus == "number") input.setSelectionRange(0, input.value.match(NUMBER_PREFIX)?.[0].length ?? 0);
|
|
671
|
+
onFocus?.(event);
|
|
902
672
|
},
|
|
903
673
|
onBlur: (event) => {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
if (clampValueOnBlur) v = safeClamp(v, min, max);
|
|
907
|
-
const parsed = parseValue(String(v), units, digit);
|
|
908
|
-
change(parsed.formatValue);
|
|
909
|
-
if (clampValueOnBlur) onChange?.(parsed.rawValue, parsed.formatValue);
|
|
910
|
-
onBlur?.(parsed.rawValue, parsed.formatValue, event);
|
|
674
|
+
commitDraft();
|
|
675
|
+
onBlur?.(event);
|
|
911
676
|
},
|
|
912
677
|
onKeyDown: (event) => {
|
|
913
|
-
|
|
914
|
-
|
|
678
|
+
const key = event.key;
|
|
679
|
+
if (key == "Enter") {
|
|
680
|
+
commitDraft();
|
|
681
|
+
if (blurOnEnter) event.currentTarget.blur();
|
|
682
|
+
} else if (keyboard && !readonly && (key == "ArrowUp" || key == "ArrowDown")) {
|
|
683
|
+
event.preventDefault();
|
|
684
|
+
nudge(key == "ArrowUp" ? 1 : -1, keyboard);
|
|
685
|
+
}
|
|
915
686
|
onKeyDown?.(event);
|
|
916
687
|
},
|
|
917
688
|
...props
|
|
918
689
|
});
|
|
919
|
-
}
|
|
690
|
+
}
|
|
691
|
+
//#endregion
|
|
692
|
+
//#region src/hooks/useDrag.ts
|
|
693
|
+
/**
|
|
694
|
+
* Track a pointer drag on an element.
|
|
695
|
+
*
|
|
696
|
+
* @returns a ref callback to attach to the element being dragged
|
|
697
|
+
*/
|
|
698
|
+
function useDrag({ threshold = 1, cursor, onDrag, onDragStart, onDragEnd }) {
|
|
699
|
+
const dragHandler = useCallbackRef(onDrag);
|
|
700
|
+
const dragStartHandler = useCallbackRef(onDragStart);
|
|
701
|
+
const dragEndHandler = useCallbackRef(onDragEnd);
|
|
702
|
+
const [node, setNode] = useState(null);
|
|
703
|
+
useEffect(() => {
|
|
704
|
+
if (!node) return;
|
|
705
|
+
const instance = createDrag(node, {
|
|
706
|
+
threshold,
|
|
707
|
+
cursor,
|
|
708
|
+
onDragStart: () => dragStartHandler(),
|
|
709
|
+
onDrag: ({ x, y, deltaX, deltaY }) => dragHandler(x, y, deltaX, deltaY),
|
|
710
|
+
onDragEnd: () => dragEndHandler()
|
|
711
|
+
});
|
|
712
|
+
return () => instance.destroy();
|
|
713
|
+
}, [
|
|
714
|
+
node,
|
|
715
|
+
threshold,
|
|
716
|
+
cursor,
|
|
717
|
+
dragHandler,
|
|
718
|
+
dragStartHandler,
|
|
719
|
+
dragEndHandler
|
|
720
|
+
]);
|
|
721
|
+
return setNode;
|
|
722
|
+
}
|
|
920
723
|
//#endregion
|
|
921
724
|
//#region src/components/NumberInput/Stepper.tsx
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
725
|
+
/**
|
|
726
|
+
* The area the steppers sit in, and a drag handle in its own right: dragging it
|
|
727
|
+
* up and down moves the value one `step` every `drag` pixels.
|
|
728
|
+
*
|
|
729
|
+
* The drag lives here rather than on `InputField` because `createDrag` turns
|
|
730
|
+
* off text selection on whatever element it is attached to.
|
|
731
|
+
*/
|
|
732
|
+
function Stepper({ className, style, children, ref, ...props }) {
|
|
733
|
+
const { value, step, readonly, drag, range, changeValue } = useNumberInputContext();
|
|
734
|
+
/**
|
|
735
|
+
* Set once the drag has actually moved the value, so that the press-and-hold
|
|
736
|
+
* repeat of a stepper stands down and leaves the value to the drag. A ref,
|
|
737
|
+
* because it is read from inside the repeat rather than rendered.
|
|
738
|
+
*/
|
|
739
|
+
const draggingRef = useRef(false);
|
|
740
|
+
/**
|
|
741
|
+
* Where the drag started, taken on the first move rather than on pointer
|
|
742
|
+
* down: the steppers act on pointer down, so by then the value may already
|
|
743
|
+
* have been nudged once, and the drag should carry on from there.
|
|
744
|
+
*/
|
|
745
|
+
const originRef = useRef(null);
|
|
746
|
+
const dragRefCallback = useDrag({
|
|
747
|
+
threshold: 1,
|
|
748
|
+
cursor: readonly ? void 0 : "ns-resize",
|
|
749
|
+
onDragStart: () => {
|
|
750
|
+
originRef.current = null;
|
|
751
|
+
draggingRef.current = false;
|
|
752
|
+
},
|
|
753
|
+
onDrag: (_x, y) => {
|
|
754
|
+
if (readonly || drag == null) return;
|
|
755
|
+
if (!originRef.current) {
|
|
756
|
+
originRef.current = {
|
|
757
|
+
y,
|
|
758
|
+
value
|
|
759
|
+
};
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
const steps = Math.round(-(y - originRef.current.y) / drag);
|
|
763
|
+
if (steps == 0) return;
|
|
764
|
+
draggingRef.current = true;
|
|
765
|
+
changeValue(applyDelta(originRef.current.value, steps, ["raw", step], range));
|
|
766
|
+
},
|
|
767
|
+
onDragEnd: () => {
|
|
768
|
+
draggingRef.current = false;
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
const composedRef = useComposedRefs(ref, drag != null && !readonly ? dragRefCallback : void 0);
|
|
772
|
+
return /* @__PURE__ */ jsx(StepperProvider, {
|
|
773
|
+
value: useMemo(() => ({ draggingRef }), []),
|
|
774
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
775
|
+
ref: composedRef,
|
|
776
|
+
className: clsx("tremolo-number-input-stepper", className),
|
|
777
|
+
style,
|
|
778
|
+
...props,
|
|
779
|
+
children
|
|
780
|
+
})
|
|
928
781
|
});
|
|
929
782
|
}
|
|
930
783
|
/**
|
|
931
784
|
* Input with some useful functions for entering numerical values.
|
|
932
785
|
*/
|
|
933
786
|
const NumberInput = {
|
|
934
|
-
Root: forwardRef(({ value, min, max, step = 1,
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
787
|
+
Root: forwardRef(({ value, min, max, step = 1, scale = linearScale, units, digit, format: formatProp, parse: parseProp, clampValue = true, wheel = ["raw", 1], keyboard = ["raw", 1], drag = 1, disabled = false, readonly = false, className, style, onChange, children, ...props }, forwardedRef) => {
|
|
788
|
+
const inputRef = useRef(null);
|
|
789
|
+
/**
|
|
790
|
+
* The text being typed. The only state here: it is not a copy of `value`,
|
|
791
|
+
* but the half-finished entry that has no value to be derived from yet.
|
|
792
|
+
*/
|
|
793
|
+
const [draft, setDraft] = useState(null);
|
|
794
|
+
const format = useCallback((v) => formatProp ? formatProp(v) : formatValue(v, units, digit), [
|
|
795
|
+
formatProp,
|
|
796
|
+
units,
|
|
797
|
+
digit
|
|
798
|
+
]);
|
|
799
|
+
const parse = useCallback((t) => parseProp ? parseProp(t) : parseValue(t, units), [parseProp, units]);
|
|
800
|
+
const range = useMemo(() => ({
|
|
801
|
+
min: (clampValue ? min : void 0) ?? Number.MIN_SAFE_INTEGER,
|
|
802
|
+
max: (clampValue ? max : void 0) ?? Number.MAX_SAFE_INTEGER,
|
|
803
|
+
step,
|
|
804
|
+
scale
|
|
805
|
+
}), [
|
|
806
|
+
clampValue,
|
|
938
807
|
min,
|
|
939
808
|
max,
|
|
940
809
|
step,
|
|
941
|
-
|
|
942
|
-
|
|
810
|
+
scale
|
|
811
|
+
]);
|
|
812
|
+
const text = draft ?? format(value);
|
|
813
|
+
const editing = draft != null;
|
|
814
|
+
const outOfRange = !editing && (min != void 0 && value < min || max != void 0 && value > max);
|
|
815
|
+
const handleDraft = useCallback((next) => {
|
|
816
|
+
if (readonly) return;
|
|
817
|
+
setDraft(next);
|
|
818
|
+
const parsed = parse(next);
|
|
819
|
+
if (Number.isFinite(parsed)) onChange?.(parsed);
|
|
820
|
+
}, [
|
|
943
821
|
readonly,
|
|
944
|
-
|
|
945
|
-
onChange
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
822
|
+
parse,
|
|
823
|
+
onChange
|
|
824
|
+
]);
|
|
825
|
+
const changeValue = useCallback((next) => {
|
|
826
|
+
if (readonly) return;
|
|
827
|
+
setDraft(null);
|
|
828
|
+
if (next != value) onChange?.(next);
|
|
829
|
+
}, [
|
|
830
|
+
readonly,
|
|
831
|
+
value,
|
|
832
|
+
onChange
|
|
833
|
+
]);
|
|
834
|
+
const commitDraft = useCallback(() => {
|
|
835
|
+
if (draft == null || readonly) return;
|
|
836
|
+
changeValue(clamp(parse(draft), range.min, range.max));
|
|
837
|
+
}, [
|
|
838
|
+
draft,
|
|
839
|
+
readonly,
|
|
840
|
+
parse,
|
|
841
|
+
range,
|
|
842
|
+
changeValue
|
|
843
|
+
]);
|
|
844
|
+
const nudge = useCallback((direction, option) => {
|
|
845
|
+
changeValue(applyDelta(value, direction, option, range));
|
|
846
|
+
}, [
|
|
847
|
+
changeValue,
|
|
848
|
+
value,
|
|
849
|
+
range
|
|
850
|
+
]);
|
|
851
|
+
const wheelRefCallback = useWheel((event) => {
|
|
852
|
+
if (!wheel || readonly || event.deltaY == 0) return;
|
|
853
|
+
event.preventDefault();
|
|
854
|
+
nudge(-Math.sign(event.deltaY), wheel);
|
|
855
|
+
}, { requireFocus: true });
|
|
856
|
+
const context = useMemo(() => ({
|
|
857
|
+
value,
|
|
858
|
+
min,
|
|
859
|
+
max,
|
|
860
|
+
step,
|
|
861
|
+
scale,
|
|
862
|
+
disabled,
|
|
863
|
+
readonly,
|
|
864
|
+
clampValue,
|
|
865
|
+
range,
|
|
866
|
+
keyboard,
|
|
867
|
+
text,
|
|
868
|
+
editing,
|
|
869
|
+
outOfRange,
|
|
870
|
+
atMin: clampValue && min != void 0 && value <= min,
|
|
871
|
+
atMax: clampValue && max != void 0 && value >= max,
|
|
872
|
+
drag,
|
|
873
|
+
format,
|
|
874
|
+
parse,
|
|
875
|
+
setDraft: handleDraft,
|
|
876
|
+
commitDraft,
|
|
877
|
+
changeValue,
|
|
878
|
+
nudge,
|
|
879
|
+
inputRef
|
|
880
|
+
}), [
|
|
881
|
+
value,
|
|
882
|
+
min,
|
|
883
|
+
max,
|
|
884
|
+
step,
|
|
885
|
+
scale,
|
|
886
|
+
disabled,
|
|
887
|
+
readonly,
|
|
888
|
+
clampValue,
|
|
889
|
+
range,
|
|
890
|
+
keyboard,
|
|
891
|
+
text,
|
|
892
|
+
editing,
|
|
893
|
+
outOfRange,
|
|
894
|
+
drag,
|
|
895
|
+
format,
|
|
896
|
+
parse,
|
|
897
|
+
handleDraft,
|
|
898
|
+
commitDraft,
|
|
899
|
+
changeValue,
|
|
900
|
+
nudge
|
|
901
|
+
]);
|
|
902
|
+
useImperativeHandle(forwardedRef, () => {
|
|
903
|
+
return {
|
|
904
|
+
focus() {
|
|
905
|
+
inputRef.current?.focus();
|
|
906
|
+
},
|
|
907
|
+
blur() {
|
|
908
|
+
inputRef.current?.blur();
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
}, []);
|
|
912
|
+
return /* @__PURE__ */ jsx(NumberInputProvider, {
|
|
913
|
+
value: context,
|
|
914
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
915
|
+
ref: wheelRefCallback,
|
|
916
|
+
className: clsx("tremolo-number-input", className),
|
|
917
|
+
"aria-disabled": disabled,
|
|
918
|
+
"aria-readonly": readonly,
|
|
919
|
+
style,
|
|
920
|
+
...props,
|
|
921
|
+
children
|
|
967
922
|
})
|
|
968
923
|
});
|
|
969
924
|
}),
|
|
925
|
+
InputField,
|
|
970
926
|
Stepper,
|
|
971
927
|
IncrementStepper,
|
|
972
928
|
DecrementStepper
|
|
973
929
|
};
|
|
974
930
|
//#endregion
|
|
975
|
-
//#region src/
|
|
931
|
+
//#region src/components/Piano/keyboardShortcuts.ts
|
|
976
932
|
/**
|
|
977
|
-
*
|
|
978
|
-
* @returns [refCallback, pointerDownHandler]
|
|
979
|
-
* @private
|
|
933
|
+
* Ready-made keyboard layouts. Both assume `noteRange.first` is a C.
|
|
980
934
|
*/
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
first: 0,
|
|
1021
|
-
last: 127
|
|
1022
|
-
},
|
|
1023
|
-
glissando: true,
|
|
1024
|
-
midiMax: 127,
|
|
1025
|
-
fill: false,
|
|
1026
|
-
label: () => void 0,
|
|
1027
|
-
onPlayNote: () => {},
|
|
1028
|
-
onStopNote: () => {}
|
|
1029
|
-
};
|
|
1030
|
-
return createStore()(() => ({
|
|
1031
|
-
...DEFAULT_PROPS,
|
|
1032
|
-
...initProps,
|
|
1033
|
-
notePosition: initProps.notePosition
|
|
1034
|
-
}));
|
|
935
|
+
const SHORTCUTS = {
|
|
936
|
+
HOME_ROW: { keys: [
|
|
937
|
+
"a",
|
|
938
|
+
"w",
|
|
939
|
+
"s",
|
|
940
|
+
"e",
|
|
941
|
+
"d",
|
|
942
|
+
"f",
|
|
943
|
+
"t",
|
|
944
|
+
"g",
|
|
945
|
+
"y",
|
|
946
|
+
"h",
|
|
947
|
+
"u",
|
|
948
|
+
"j",
|
|
949
|
+
"k",
|
|
950
|
+
"o",
|
|
951
|
+
"l",
|
|
952
|
+
"p",
|
|
953
|
+
";"
|
|
954
|
+
] },
|
|
955
|
+
HOME_ROW_NATURAL: { keys: [
|
|
956
|
+
"a",
|
|
957
|
+
"",
|
|
958
|
+
"s",
|
|
959
|
+
"",
|
|
960
|
+
"d",
|
|
961
|
+
"f",
|
|
962
|
+
"",
|
|
963
|
+
"g",
|
|
964
|
+
"",
|
|
965
|
+
"h",
|
|
966
|
+
"",
|
|
967
|
+
"j",
|
|
968
|
+
"k",
|
|
969
|
+
"",
|
|
970
|
+
"l",
|
|
971
|
+
"",
|
|
972
|
+
";"
|
|
973
|
+
] }
|
|
1035
974
|
};
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
975
|
+
/**
|
|
976
|
+
* Customizable piano component.
|
|
977
|
+
*/
|
|
978
|
+
const Piano = { Root: forwardRef(function Root({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, whiteKeyWidth = 40, keyGap = 1, blackKeyWidthRatio = .65, blackKeyHeightRatio = .6, height = fill ? "100%" : 160, style, className, label, keyProps, onPlayNote, onStopNote, ...props }, forwardedRef) {
|
|
979
|
+
const [node, setNode] = useState(null);
|
|
980
|
+
const [activeNotes, setActiveNotes] = useState([]);
|
|
981
|
+
/** Set while `fill` is on, measured from the parent. */
|
|
982
|
+
const [filledKeyWidth, setFilledKeyWidth] = useState(whiteKeyWidth);
|
|
983
|
+
const notes = useMemo(() => getNoteRangeArray(noteRange), [noteRange]);
|
|
984
|
+
const whiteKeyCount = useMemo(() => notes.filter(isWhiteKey).length, [notes]);
|
|
985
|
+
const layout = useMemo(() => ({
|
|
986
|
+
noteRange,
|
|
987
|
+
whiteKeyWidth: fill ? filledKeyWidth : whiteKeyWidth,
|
|
988
|
+
keyGap,
|
|
989
|
+
blackKeyWidthRatio,
|
|
990
|
+
blackKeyHeightRatio
|
|
991
|
+
}), [
|
|
992
|
+
noteRange,
|
|
993
|
+
fill,
|
|
994
|
+
filledKeyWidth,
|
|
995
|
+
whiteKeyWidth,
|
|
996
|
+
keyGap,
|
|
997
|
+
blackKeyWidthRatio,
|
|
998
|
+
blackKeyHeightRatio
|
|
999
|
+
]);
|
|
1000
|
+
const latest = useRef({
|
|
1001
|
+
layout,
|
|
1002
|
+
glissando,
|
|
1003
|
+
midiMax,
|
|
1004
|
+
onPlayNote,
|
|
1005
|
+
onStopNote
|
|
1067
1006
|
});
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
setPlayed(false);
|
|
1093
|
-
if (onStopNote) onStopNote(noteNumber);
|
|
1094
|
-
},
|
|
1095
|
-
played() {
|
|
1096
|
-
return played;
|
|
1097
|
-
}
|
|
1007
|
+
const instanceRef = useRef(null);
|
|
1008
|
+
useEffect(() => {
|
|
1009
|
+
if (!node) return;
|
|
1010
|
+
const instance = createPianoInput(node, {
|
|
1011
|
+
layout: latest.current.layout,
|
|
1012
|
+
glissando: latest.current.glissando,
|
|
1013
|
+
midiMax: latest.current.midiMax,
|
|
1014
|
+
onPlayNote: (note, velocity) => latest.current.onPlayNote?.(note, velocity),
|
|
1015
|
+
onStopNote: (note) => latest.current.onStopNote?.(note),
|
|
1016
|
+
onActiveNotesChange: setActiveNotes
|
|
1017
|
+
});
|
|
1018
|
+
instanceRef.current = instance;
|
|
1019
|
+
return () => {
|
|
1020
|
+
instanceRef.current = null;
|
|
1021
|
+
instance.destroy();
|
|
1022
|
+
};
|
|
1023
|
+
}, [node]);
|
|
1024
|
+
useEffect(() => {
|
|
1025
|
+
latest.current = {
|
|
1026
|
+
layout,
|
|
1027
|
+
glissando,
|
|
1028
|
+
midiMax,
|
|
1029
|
+
onPlayNote,
|
|
1030
|
+
onStopNote
|
|
1098
1031
|
};
|
|
1032
|
+
instanceRef.current?.update({
|
|
1033
|
+
layout,
|
|
1034
|
+
glissando,
|
|
1035
|
+
midiMax
|
|
1036
|
+
});
|
|
1037
|
+
});
|
|
1038
|
+
useEffect(() => {
|
|
1039
|
+
if (!fill || !node) return;
|
|
1040
|
+
const parent = node.parentElement;
|
|
1041
|
+
if (!parent) throw new Error("doesn't have a parent element.");
|
|
1042
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
1043
|
+
setFilledKeyWidth(node.clientWidth / whiteKeyCount - keyGap);
|
|
1044
|
+
});
|
|
1045
|
+
resizeObserver.observe(parent);
|
|
1046
|
+
return () => resizeObserver.disconnect();
|
|
1099
1047
|
}, [
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
played
|
|
1048
|
+
fill,
|
|
1049
|
+
node,
|
|
1050
|
+
whiteKeyCount,
|
|
1051
|
+
keyGap
|
|
1105
1052
|
]);
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
if (
|
|
1109
|
-
|
|
1110
|
-
|
|
1053
|
+
/** The note a shortcut key plays, or null when it has none. */
|
|
1054
|
+
function shortcutNote(key) {
|
|
1055
|
+
if (!keyboardShortcuts || key === "") return null;
|
|
1056
|
+
const index = keyboardShortcuts.keys.indexOf(key);
|
|
1057
|
+
return index === -1 ? null : noteRange.first + index;
|
|
1058
|
+
}
|
|
1059
|
+
useEventListener(globalThis.window, "keydown", (e) => {
|
|
1060
|
+
if (e.repeat) return;
|
|
1061
|
+
const note = shortcutNote(e.key);
|
|
1062
|
+
if (note !== null) instanceRef.current?.noteOn(note, { source: "keyboard" });
|
|
1063
|
+
});
|
|
1064
|
+
useEventListener(globalThis.window, "keyup", (e) => {
|
|
1065
|
+
const note = shortcutNote(e.key);
|
|
1066
|
+
if (note !== null) instanceRef.current?.noteOff(note, { source: "keyboard" });
|
|
1111
1067
|
});
|
|
1068
|
+
useImperativeHandle(forwardedRef, () => ({
|
|
1069
|
+
playNote: (note, velocity) => instanceRef.current?.noteOn(note, {
|
|
1070
|
+
source: "api",
|
|
1071
|
+
velocity
|
|
1072
|
+
}),
|
|
1073
|
+
stopNote: (note) => instanceRef.current?.noteOff(note, { source: "api" })
|
|
1074
|
+
}), []);
|
|
1112
1075
|
return /* @__PURE__ */ jsx("div", {
|
|
1113
|
-
|
|
1114
|
-
"
|
|
1115
|
-
"data-active": played,
|
|
1076
|
+
ref: setNode,
|
|
1077
|
+
className: clsx("tremolo-piano", className),
|
|
1116
1078
|
style: {
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
});
|
|
1130
|
-
});
|
|
1131
|
-
const WhiteKey = forwardRef(({ width = 40, height = "100%", onContextMenu = (e) => e.preventDefault(), ...props }, ref) => {
|
|
1132
|
-
return /* @__PURE__ */ jsx(KeyImpl, {
|
|
1133
|
-
ref,
|
|
1134
|
-
keyType: "white",
|
|
1135
|
-
width,
|
|
1136
|
-
height,
|
|
1137
|
-
onContextMenu,
|
|
1138
|
-
...props
|
|
1139
|
-
});
|
|
1140
|
-
});
|
|
1141
|
-
const BlackKey = forwardRef(({ width = 26, height = "60%", onContextMenu = (e) => e.preventDefault(), ...props }, ref) => {
|
|
1142
|
-
return /* @__PURE__ */ jsx(KeyImpl, {
|
|
1143
|
-
ref,
|
|
1144
|
-
keyType: "black",
|
|
1145
|
-
width,
|
|
1146
|
-
height,
|
|
1147
|
-
onContextMenu,
|
|
1148
|
-
...props
|
|
1149
|
-
});
|
|
1150
|
-
});
|
|
1151
|
-
//#endregion
|
|
1152
|
-
//#region src/components/Piano/keyboardShortcuts.ts
|
|
1153
|
-
const SHORTCUTS = { HOME_ROW: { keys: [
|
|
1154
|
-
"a",
|
|
1155
|
-
"w",
|
|
1156
|
-
"s",
|
|
1157
|
-
"e",
|
|
1158
|
-
"d",
|
|
1159
|
-
"f",
|
|
1160
|
-
"t",
|
|
1161
|
-
"g",
|
|
1162
|
-
"y",
|
|
1163
|
-
"h",
|
|
1164
|
-
"u",
|
|
1165
|
-
"j",
|
|
1166
|
-
"k",
|
|
1167
|
-
"o",
|
|
1168
|
-
"l",
|
|
1169
|
-
"p",
|
|
1170
|
-
";"
|
|
1171
|
-
] } };
|
|
1172
|
-
//#endregion
|
|
1173
|
-
//#region src/components/Piano/index.tsx
|
|
1174
|
-
/**
|
|
1175
|
-
* [noteRange.first, noteRange.first + 1 ..., noteRange.last]
|
|
1176
|
-
*/
|
|
1177
|
-
function getNoteRangeArray(noteRange) {
|
|
1178
|
-
return Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
|
|
1179
|
-
}
|
|
1180
|
-
const blackPerWhiteWidth = 26 / 40;
|
|
1181
|
-
/**
|
|
1182
|
-
* Customizable piano component.
|
|
1183
|
-
*/
|
|
1184
|
-
const Piano = {
|
|
1185
|
-
Root: forwardRef(({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, height = fill ? "100%" : 160, whiteNoteWidth: _whiteNoteWidth = 40, style, className, onPlayNote, onStopNote, label, children, onPointerDown, ...props }, forwardedRef) => {
|
|
1186
|
-
const [whiteNoteWidth, setWhiteNoteWidth] = useState(_whiteNoteWidth);
|
|
1187
|
-
const keyRefs = useRef([]);
|
|
1188
|
-
for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) keyRefs.current[i] = createRef();
|
|
1189
|
-
const pianoRef = useRef(null);
|
|
1190
|
-
const hitKeyIndex = useRef(-1);
|
|
1191
|
-
const noteRangeArray = getNoteRangeArray(noteRange);
|
|
1192
|
-
const whiteNotes = noteRangeArray.filter((v) => isWhiteKey(v));
|
|
1193
|
-
const blackNotes = noteRangeArray.filter((v) => isBlackKey(v));
|
|
1194
|
-
const whiteNoteCount = whiteNotes.length;
|
|
1195
|
-
const padding = 1;
|
|
1196
|
-
const staticWidth = (whiteNoteWidth + padding) * whiteNoteCount;
|
|
1197
|
-
const childrenWithProps = useMemo(() => {
|
|
1198
|
-
return children && React.Children.map(children, (child, index) => {
|
|
1199
|
-
if (React.isValidElement(child)) {
|
|
1200
|
-
const __width = child.type == WhiteKey ? whiteNoteWidth : whiteNoteWidth * .65;
|
|
1201
|
-
const props = {
|
|
1202
|
-
ref: keyRefs.current[index],
|
|
1203
|
-
__width
|
|
1204
|
-
};
|
|
1205
|
-
return React.cloneElement(child, props);
|
|
1206
|
-
}
|
|
1207
|
-
return child;
|
|
1208
|
-
});
|
|
1209
|
-
}, [children, whiteNoteWidth]);
|
|
1210
|
-
const notePosition = useCallback((note) => {
|
|
1211
|
-
const pitchPositions = {
|
|
1212
|
-
C: 0,
|
|
1213
|
-
"C#": 1,
|
|
1214
|
-
D: 1,
|
|
1215
|
-
"D#": 2,
|
|
1216
|
-
E: 2,
|
|
1217
|
-
F: 3,
|
|
1218
|
-
"F#": 4,
|
|
1219
|
-
G: 4,
|
|
1220
|
-
"G#": 5,
|
|
1221
|
-
A: 5,
|
|
1222
|
-
"A#": 6,
|
|
1223
|
-
B: 6
|
|
1224
|
-
};
|
|
1225
|
-
const blackNoteWidth = whiteNoteWidth * blackPerWhiteWidth;
|
|
1226
|
-
const padding = 1;
|
|
1227
|
-
const targetNoteKey = noteKey(note);
|
|
1228
|
-
const firstNoteKey = noteKey(noteRange.first);
|
|
1229
|
-
const octave = Math.floor((note - noteRange.first) / 12);
|
|
1230
|
-
const octaveOffset = noteKeys.indexOf(firstNoteKey) > noteKeys.indexOf(targetNoteKey) ? 1 : 0;
|
|
1231
|
-
const w = whiteNoteWidth + padding;
|
|
1232
|
-
const pos = pitchPositions[targetNoteKey] - pitchPositions[firstNoteKey];
|
|
1233
|
-
const blackKeyOffset = isBlackKey(note) ? blackNoteWidth / 2 : 0;
|
|
1234
|
-
return pos * w + (octave + octaveOffset) * 7 * w - blackKeyOffset;
|
|
1235
|
-
}, [noteRange.first, whiteNoteWidth]);
|
|
1236
|
-
const getHitKeyIndex = useCallback((x, y) => {
|
|
1237
|
-
if (!pianoRef.current) return -1;
|
|
1238
|
-
const containerHeight = pianoRef.current.clientHeight;
|
|
1239
|
-
const notes = [...blackNotes, ...whiteNotes];
|
|
1240
|
-
for (let i = 0; i < notes.length; i++) {
|
|
1241
|
-
const note = notes[i];
|
|
1242
|
-
const pos = notePosition(note);
|
|
1243
|
-
const w = isWhiteKey(note) ? whiteNoteWidth : whiteNoteWidth * blackPerWhiteWidth;
|
|
1244
|
-
const h = isWhiteKey(note) ? containerHeight : containerHeight * .6;
|
|
1245
|
-
if (pos <= x && x < pos + w && 0 <= y && y < h) return note;
|
|
1246
|
-
}
|
|
1247
|
-
return -1;
|
|
1248
|
-
}, [
|
|
1249
|
-
blackNotes,
|
|
1250
|
-
notePosition,
|
|
1251
|
-
whiteNoteWidth,
|
|
1252
|
-
whiteNotes
|
|
1253
|
-
]);
|
|
1254
|
-
const onDrag = useCallback((perX, perY) => {
|
|
1255
|
-
if (!pianoRef.current) return;
|
|
1256
|
-
const note = getHitKeyIndex(perX * staticWidth, perY * pianoRef.current.clientHeight);
|
|
1257
|
-
const index = noteRangeArray.indexOf(note);
|
|
1258
|
-
if (index == -1) return;
|
|
1259
|
-
if (hitKeyIndex.current != index) {
|
|
1260
|
-
keyRefs.current[hitKeyIndex.current]?.current?.stop();
|
|
1261
|
-
keyRefs.current[index]?.current?.play();
|
|
1262
|
-
hitKeyIndex.current = index;
|
|
1263
|
-
}
|
|
1264
|
-
}, [
|
|
1265
|
-
getHitKeyIndex,
|
|
1266
|
-
noteRangeArray,
|
|
1267
|
-
staticWidth
|
|
1268
|
-
]);
|
|
1269
|
-
const onDragEnd = useCallback(() => {
|
|
1270
|
-
if (keyRefs.current[hitKeyIndex.current]?.current?.played()) keyRefs.current[hitKeyIndex.current]?.current?.stop();
|
|
1271
|
-
hitKeyIndex.current = -1;
|
|
1272
|
-
}, [keyRefs]);
|
|
1273
|
-
useEffect(() => {
|
|
1274
|
-
if (fill && pianoRef.current) {
|
|
1275
|
-
const parent = pianoRef.current.parentElement;
|
|
1276
|
-
if (!parent) throw new Error("doesn't have a parent element.");
|
|
1277
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
1278
|
-
const w = pianoRef.current.clientWidth;
|
|
1279
|
-
setWhiteNoteWidth(w / whiteNoteCount - padding);
|
|
1280
|
-
});
|
|
1281
|
-
resizeObserver.observe(parent);
|
|
1282
|
-
return () => {
|
|
1283
|
-
resizeObserver.unobserve(parent);
|
|
1284
|
-
};
|
|
1285
|
-
} else setWhiteNoteWidth(_whiteNoteWidth);
|
|
1286
|
-
}, [
|
|
1287
|
-
fill,
|
|
1288
|
-
_whiteNoteWidth,
|
|
1289
|
-
whiteNoteCount
|
|
1290
|
-
]);
|
|
1291
|
-
const { refHandler: touchMoveRefCallback, pointerDownHandler } = usePianoDrag({
|
|
1292
|
-
baseElementRef: pianoRef,
|
|
1293
|
-
onDrag,
|
|
1294
|
-
onDragEnd
|
|
1295
|
-
});
|
|
1296
|
-
useEventListener(globalThis.window, "keydown", (e) => {
|
|
1297
|
-
if (e.repeat) return;
|
|
1298
|
-
if (!keyboardShortcuts) return;
|
|
1299
|
-
const index = keyboardShortcuts.keys.indexOf(e.key);
|
|
1300
|
-
if (index != -1) {
|
|
1301
|
-
if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play();
|
|
1302
|
-
}
|
|
1303
|
-
});
|
|
1304
|
-
useEventListener(globalThis.window, "keyup", (e) => {
|
|
1305
|
-
if (e.repeat) return;
|
|
1306
|
-
if (!keyboardShortcuts) return;
|
|
1307
|
-
const index = keyboardShortcuts.keys.indexOf(e.key);
|
|
1308
|
-
if (index != -1) keyRefs.current[index]?.current?.stop();
|
|
1309
|
-
});
|
|
1310
|
-
useImperativeHandle(forwardedRef, () => {
|
|
1311
|
-
return {
|
|
1312
|
-
playNote(note, velocity) {
|
|
1313
|
-
const index = noteRangeArray.indexOf(note);
|
|
1314
|
-
if (index != -1) {
|
|
1315
|
-
if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play(velocity);
|
|
1316
|
-
} else onPlayNote?.(note, velocity);
|
|
1317
|
-
},
|
|
1318
|
-
stopNote(note) {
|
|
1319
|
-
const index = noteRangeArray.indexOf(note);
|
|
1320
|
-
if (index != -1) keyRefs.current[index]?.current?.stop();
|
|
1321
|
-
else onStopNote?.(note);
|
|
1322
|
-
}
|
|
1079
|
+
width: fill ? "100%" : pianoWidth(layout),
|
|
1080
|
+
height,
|
|
1081
|
+
...style
|
|
1082
|
+
},
|
|
1083
|
+
...props,
|
|
1084
|
+
children: notes.map((note, index) => {
|
|
1085
|
+
const keyType = isWhiteKey(note) ? "white" : "black";
|
|
1086
|
+
const state = {
|
|
1087
|
+
index,
|
|
1088
|
+
keyType,
|
|
1089
|
+
active: activeNotes.includes(note),
|
|
1090
|
+
disabled: note > midiMax
|
|
1323
1091
|
};
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
midiMax,
|
|
1334
|
-
fill,
|
|
1335
|
-
onPlayNote,
|
|
1336
|
-
onStopNote,
|
|
1337
|
-
label,
|
|
1338
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
1339
|
-
ref: (div) => {
|
|
1340
|
-
pianoRef.current = div;
|
|
1341
|
-
touchMoveRefCallback(div);
|
|
1342
|
-
},
|
|
1343
|
-
className: clsx("tremolo-piano", className),
|
|
1092
|
+
const { className: keyClassName, style: keyStyle, ...rest } = keyProps?.(note, state) ?? {};
|
|
1093
|
+
const content = label?.(note, state);
|
|
1094
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1095
|
+
className: clsx(`tremolo-piano-${keyType}-key`, keyClassName),
|
|
1096
|
+
"data-note": note,
|
|
1097
|
+
"data-note-key": noteKey(note),
|
|
1098
|
+
"data-active": state.active,
|
|
1099
|
+
"aria-disabled": state.disabled,
|
|
1100
|
+
...rest,
|
|
1344
1101
|
style: {
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
onPointerDown: (event) => {
|
|
1350
|
-
pointerDownHandler(event);
|
|
1351
|
-
onPointerDown?.(event);
|
|
1102
|
+
...keyStyle,
|
|
1103
|
+
left: notePosition(note, layout),
|
|
1104
|
+
width: keyType === "white" ? layout.whiteKeyWidth : blackKeyWidth(layout),
|
|
1105
|
+
height: keyType === "white" ? "100%" : `${blackKeyHeightRatio * 100}%`
|
|
1352
1106
|
},
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
});
|
|
1365
|
-
}),
|
|
1366
|
-
WhiteKey,
|
|
1367
|
-
BlackKey,
|
|
1368
|
-
KeyLabel
|
|
1369
|
-
};
|
|
1107
|
+
children: content !== "" && content !== null && content !== void 0 && /* @__PURE__ */ jsx("div", {
|
|
1108
|
+
className: "tremolo-piano-key-label-wrapper",
|
|
1109
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1110
|
+
className: "tremolo-piano-key-label",
|
|
1111
|
+
children: content
|
|
1112
|
+
})
|
|
1113
|
+
})
|
|
1114
|
+
}, note);
|
|
1115
|
+
})
|
|
1116
|
+
});
|
|
1117
|
+
}) };
|
|
1370
1118
|
//#endregion
|
|
1371
1119
|
//#region src/components/PointsEditor/Background.tsx
|
|
1372
1120
|
function Background({ className, children, ...props }) {
|
|
@@ -1378,96 +1126,24 @@ function Background({ className, children, ...props }) {
|
|
|
1378
1126
|
}
|
|
1379
1127
|
//#endregion
|
|
1380
1128
|
//#region src/components/PointsEditor/context.tsx
|
|
1381
|
-
const createPointsEditorStore = (initProps) => {
|
|
1382
|
-
const DEFAULT_PROPS = {
|
|
1383
|
-
containerElementRef: createRef(),
|
|
1384
|
-
disabled: false,
|
|
1385
|
-
readonly: false,
|
|
1386
|
-
externalStyles: {
|
|
1387
|
-
userSelectNone: true,
|
|
1388
|
-
cursor: "grabbing"
|
|
1389
|
-
}
|
|
1390
|
-
};
|
|
1391
|
-
return createStore()((set) => ({
|
|
1392
|
-
...DEFAULT_PROPS,
|
|
1393
|
-
...initProps,
|
|
1394
|
-
setContainerElementRef: (containerElementRef) => {
|
|
1395
|
-
set(() => ({ containerElementRef }));
|
|
1396
|
-
}
|
|
1397
|
-
}));
|
|
1398
|
-
};
|
|
1399
1129
|
const PointsEditorContext = createContext(null);
|
|
1400
|
-
|
|
1401
|
-
const storeRef = useRef(null);
|
|
1402
|
-
if (!storeRef.current) storeRef.current = createPointsEditorStore(props);
|
|
1403
|
-
useEffect(() => {
|
|
1404
|
-
if (storeRef.current) storeRef.current.setState(props);
|
|
1405
|
-
else storeRef.current = createPointsEditorStore(props);
|
|
1406
|
-
}, [props]);
|
|
1407
|
-
return /* @__PURE__ */ jsx(PointsEditorContext.Provider, {
|
|
1408
|
-
value: storeRef.current,
|
|
1409
|
-
children
|
|
1410
|
-
});
|
|
1411
|
-
}
|
|
1130
|
+
const PointsEditorProvider = PointsEditorContext.Provider;
|
|
1412
1131
|
function usePointsEditorContext(selector) {
|
|
1413
|
-
const
|
|
1414
|
-
if (!
|
|
1415
|
-
return
|
|
1132
|
+
const context = useContext(PointsEditorContext);
|
|
1133
|
+
if (!context) throw new Error("Missing PointsEditorContext.Provider in the tree");
|
|
1134
|
+
return selector ? selector(context) : context;
|
|
1416
1135
|
}
|
|
1417
1136
|
//#endregion
|
|
1418
1137
|
//#region src/components/PointsEditor/Container.tsx
|
|
1419
|
-
function Container({ className,
|
|
1420
|
-
const containerElementRef = useRef(null);
|
|
1421
|
-
usePointsEditorContext((s) => s.setContainerElementRef)(containerElementRef);
|
|
1138
|
+
function Container({ children, className, ref, ...props }) {
|
|
1422
1139
|
return /* @__PURE__ */ jsx("div", {
|
|
1423
|
-
ref:
|
|
1140
|
+
ref: useComposedRefs(ref, usePointsEditorContext((s) => s.containerRef)),
|
|
1424
1141
|
className: clsx("tremolo-points-editor-container", className),
|
|
1425
1142
|
...props,
|
|
1426
1143
|
children
|
|
1427
1144
|
});
|
|
1428
1145
|
}
|
|
1429
1146
|
//#endregion
|
|
1430
|
-
//#region src/hooks/useDragWithElement.ts
|
|
1431
|
-
function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
|
|
1432
|
-
const [dragging, setDragging] = useState(false);
|
|
1433
|
-
const normalizedX = useRef(0);
|
|
1434
|
-
const normalizedY = useRef(0);
|
|
1435
|
-
const handleDrag = useCallback((event) => {
|
|
1436
|
-
if (!baseElementRef.current || !dragging) return;
|
|
1437
|
-
const isTouch = event instanceof TouchEvent;
|
|
1438
|
-
if (isTouch && event.cancelable) event.preventDefault();
|
|
1439
|
-
const { left, top, right, bottom } = baseElementRef.current.getBoundingClientRect();
|
|
1440
|
-
const mouseX = isTouch ? event.touches[0].clientX : event.clientX;
|
|
1441
|
-
const mouseY = isTouch ? event.touches[0].clientY : event.clientY;
|
|
1442
|
-
const nx = normalizeValue(mouseX, left, right);
|
|
1443
|
-
const ny = normalizeValue(mouseY, top, bottom);
|
|
1444
|
-
normalizedX.current = nx;
|
|
1445
|
-
normalizedY.current = ny;
|
|
1446
|
-
onDrag(nx, ny);
|
|
1447
|
-
}, [
|
|
1448
|
-
onDrag,
|
|
1449
|
-
baseElementRef,
|
|
1450
|
-
dragging
|
|
1451
|
-
]);
|
|
1452
|
-
const refHandler = useRefCallbackEvent("touchmove", handleDrag, { passive: false }, [onDrag]);
|
|
1453
|
-
const pointerDownHandler = useCallback((event) => {
|
|
1454
|
-
setDragging(true);
|
|
1455
|
-
handleDrag(event);
|
|
1456
|
-
onDragStart?.(normalizedX.current, normalizedY.current);
|
|
1457
|
-
}, [handleDrag, onDragStart]);
|
|
1458
|
-
useEventListener(globalThis.window, "pointermove", handleDrag);
|
|
1459
|
-
useEventListener(globalThis.window, "pointerup", () => {
|
|
1460
|
-
if (!dragging) return;
|
|
1461
|
-
setDragging(false);
|
|
1462
|
-
onDragEnd?.(normalizedX.current, normalizedY.current);
|
|
1463
|
-
});
|
|
1464
|
-
return {
|
|
1465
|
-
refHandler,
|
|
1466
|
-
pointerDownHandler,
|
|
1467
|
-
dragging
|
|
1468
|
-
};
|
|
1469
|
-
}
|
|
1470
|
-
//#endregion
|
|
1471
1147
|
//#region src/components/PointsEditor/Point.tsx
|
|
1472
1148
|
function clampPoint(point, min, max) {
|
|
1473
1149
|
const { x, y } = point;
|
|
@@ -1476,48 +1152,88 @@ function clampPoint(point, min, max) {
|
|
|
1476
1152
|
y: clamp(y, min?.y ?? 0, max?.y ?? 1)
|
|
1477
1153
|
};
|
|
1478
1154
|
}
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1155
|
+
/**
|
|
1156
|
+
* A point is placed by its position within the container, so its value is a
|
|
1157
|
+
* position: 0..1 on each axis, with y growing downwards.
|
|
1158
|
+
*/
|
|
1159
|
+
const AXIS = {
|
|
1160
|
+
min: 0,
|
|
1161
|
+
max: 1
|
|
1162
|
+
};
|
|
1163
|
+
function Point({ value, min, max, size, width = 16, height = 16, color, disabled: _disabled, readonly: _readonly, wheel: _wheel, keyboard: _keyboard, onChange, onDragStart, onDragEnd, className, style, onPointerDown, onKeyDown, ...props }) {
|
|
1164
|
+
const { containerRef, externalStyles, disabled: rootDisabled, readonly: rootReadonly, wheel: rootWheel, keyboard: rootKeyboard } = usePointsEditorContext();
|
|
1165
|
+
const disabled = _disabled ?? rootDisabled;
|
|
1166
|
+
const readonly = _readonly ?? rootReadonly;
|
|
1167
|
+
const wheel = _wheel === void 0 ? rootWheel : _wheel;
|
|
1168
|
+
const keyboard = _keyboard === void 0 ? rootKeyboard : _keyboard;
|
|
1169
|
+
const [element, setElement] = useState(null);
|
|
1170
|
+
const { refCallback: dragRefCallback, dragging } = useDragValue({
|
|
1171
|
+
axis: AXIS,
|
|
1172
|
+
baseElementRef: containerRef,
|
|
1173
|
+
cursor: readonly ? void 0 : externalStyles.cursor,
|
|
1174
|
+
onChange: ([x, y]) => {
|
|
1487
1175
|
if (readonly) return;
|
|
1488
1176
|
onChange?.(clampPoint({
|
|
1489
1177
|
x,
|
|
1490
1178
|
y
|
|
1491
1179
|
}, min, max));
|
|
1492
1180
|
},
|
|
1493
|
-
onDragStart: (x, y) => {
|
|
1181
|
+
onDragStart: ([x, y]) => {
|
|
1494
1182
|
if (readonly) return;
|
|
1495
1183
|
if (externalStyles.userSelectNone) addUserSelectNone();
|
|
1496
|
-
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
|
|
1497
1184
|
onDragStart?.(clampPoint({
|
|
1498
1185
|
x,
|
|
1499
1186
|
y
|
|
1500
1187
|
}, min, max));
|
|
1501
1188
|
},
|
|
1502
|
-
onDragEnd: (x, y) => {
|
|
1189
|
+
onDragEnd: ([x, y]) => {
|
|
1503
1190
|
if (readonly) return;
|
|
1504
1191
|
if (externalStyles.userSelectNone) removeUserSelectNone();
|
|
1505
|
-
if (externalStyles.cursor) resetCursorStyle();
|
|
1506
1192
|
onDragEnd?.(clampPoint({
|
|
1507
1193
|
x,
|
|
1508
1194
|
y
|
|
1509
1195
|
}, min, max));
|
|
1510
1196
|
}
|
|
1511
1197
|
});
|
|
1198
|
+
const nudge = useCallback((axis, direction, option) => {
|
|
1199
|
+
const next = applyDelta(value[axis], direction, option, AXIS);
|
|
1200
|
+
onChange?.(clampPoint({
|
|
1201
|
+
...value,
|
|
1202
|
+
[axis]: next
|
|
1203
|
+
}, min, max));
|
|
1204
|
+
}, [
|
|
1205
|
+
value,
|
|
1206
|
+
min,
|
|
1207
|
+
max,
|
|
1208
|
+
onChange
|
|
1209
|
+
]);
|
|
1210
|
+
useWheel((event) => {
|
|
1211
|
+
if (!onChange || readonly || !wheel) return;
|
|
1212
|
+
if (!element || element.ownerDocument.activeElement !== element) return;
|
|
1213
|
+
event.preventDefault();
|
|
1214
|
+
nudge(event.shiftKey ? "x" : "y", event.deltaY < 0 ? -1 : 1, wheel);
|
|
1215
|
+
}, { target: containerRef });
|
|
1216
|
+
const refCallback = useComposedRefs(dragRefCallback, setElement);
|
|
1217
|
+
const handleKeyDown = (event) => {
|
|
1218
|
+
if (!onChange || readonly || !keyboard) return;
|
|
1219
|
+
const key = event.key;
|
|
1220
|
+
if ([
|
|
1221
|
+
"ArrowRight",
|
|
1222
|
+
"ArrowLeft",
|
|
1223
|
+
"ArrowUp",
|
|
1224
|
+
"ArrowDown"
|
|
1225
|
+
].includes(key)) {
|
|
1226
|
+
event.preventDefault();
|
|
1227
|
+
nudge(key === "ArrowRight" || key === "ArrowLeft" ? "x" : "y", key === "ArrowLeft" || key === "ArrowUp" ? -1 : 1, keyboard);
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1512
1230
|
const colors = { "--color": color };
|
|
1513
1231
|
return /* @__PURE__ */ jsx("div", {
|
|
1514
|
-
ref:
|
|
1515
|
-
touchMoveRefCallback(div);
|
|
1516
|
-
},
|
|
1232
|
+
ref: refCallback,
|
|
1517
1233
|
className: clsx("tremolo-points-editor-point", className),
|
|
1518
1234
|
tabIndex: 0,
|
|
1519
|
-
"aria-disabled": disabled
|
|
1520
|
-
"aria-readonly": readonly
|
|
1235
|
+
"aria-disabled": disabled,
|
|
1236
|
+
"aria-readonly": readonly,
|
|
1521
1237
|
"data-dragging": dragging,
|
|
1522
1238
|
style: {
|
|
1523
1239
|
...colors,
|
|
@@ -1527,34 +1243,52 @@ function Point({ value, min, max, size, width = 16, height = 16, color, disabled
|
|
|
1527
1243
|
top: `${value.y * 100}%`,
|
|
1528
1244
|
...style
|
|
1529
1245
|
},
|
|
1530
|
-
onPointerDown
|
|
1531
|
-
|
|
1532
|
-
|
|
1246
|
+
onPointerDown,
|
|
1247
|
+
onKeyDown: (event) => {
|
|
1248
|
+
handleKeyDown(event);
|
|
1249
|
+
onKeyDown?.(event);
|
|
1533
1250
|
},
|
|
1534
1251
|
...props
|
|
1535
1252
|
});
|
|
1536
1253
|
}
|
|
1537
1254
|
//#endregion
|
|
1538
1255
|
//#region src/components/PointsEditor/index.tsx
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1256
|
+
/**
|
|
1257
|
+
* A point moves over 0..1 in both axes, so a nudge of 0.01 crosses the editor
|
|
1258
|
+
* in a hundred steps whatever its pixel size.
|
|
1259
|
+
*/
|
|
1260
|
+
const DEFAULT_INPUT_EVENT_OPTION = ["normalized", .01];
|
|
1543
1261
|
/**
|
|
1544
1262
|
* Multiple Point Controller
|
|
1545
1263
|
*/
|
|
1546
1264
|
const PointsEditor = {
|
|
1547
|
-
Root: forwardRef(({ width = 200, height = 100, disabled = false, readonly = false,
|
|
1265
|
+
Root: forwardRef(({ width = 200, height = 100, disabled = false, readonly = false, wheel = DEFAULT_INPUT_EVENT_OPTION, keyboard = DEFAULT_INPUT_EVENT_OPTION, externalStyles, style, className, children, ...props }, forwardedRef) => {
|
|
1266
|
+
const containerRef = useRef(null);
|
|
1267
|
+
const { userSelectNone = true, cursor = "grabbing" } = externalStyles ?? {};
|
|
1548
1268
|
return /* @__PURE__ */ jsx(PointsEditorProvider, {
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1269
|
+
value: useMemo(() => ({
|
|
1270
|
+
disabled,
|
|
1271
|
+
readonly,
|
|
1272
|
+
wheel,
|
|
1273
|
+
keyboard,
|
|
1274
|
+
externalStyles: {
|
|
1275
|
+
userSelectNone,
|
|
1276
|
+
cursor
|
|
1277
|
+
},
|
|
1278
|
+
containerRef
|
|
1279
|
+
}), [
|
|
1280
|
+
disabled,
|
|
1281
|
+
readonly,
|
|
1282
|
+
wheel,
|
|
1283
|
+
keyboard,
|
|
1284
|
+
userSelectNone,
|
|
1285
|
+
cursor
|
|
1286
|
+
]),
|
|
1555
1287
|
children: /* @__PURE__ */ jsx("div", {
|
|
1556
1288
|
ref: forwardedRef,
|
|
1557
1289
|
className: clsx("tremolo-points-editor", className),
|
|
1290
|
+
"aria-disabled": disabled,
|
|
1291
|
+
"aria-readonly": readonly,
|
|
1558
1292
|
style: {
|
|
1559
1293
|
width,
|
|
1560
1294
|
height,
|
|
@@ -1571,59 +1305,33 @@ const PointsEditor = {
|
|
|
1571
1305
|
};
|
|
1572
1306
|
//#endregion
|
|
1573
1307
|
//#region src/components/Slider/context.tsx
|
|
1574
|
-
const createSliderStore = (initProps) => {
|
|
1575
|
-
const DEFAULT_PROPS = {
|
|
1576
|
-
min: 0,
|
|
1577
|
-
max: 0,
|
|
1578
|
-
step: 0,
|
|
1579
|
-
skew: 0,
|
|
1580
|
-
vertical: false,
|
|
1581
|
-
reverse: false,
|
|
1582
|
-
disabled: false,
|
|
1583
|
-
readonly: false
|
|
1584
|
-
};
|
|
1585
|
-
return createStore()(() => ({
|
|
1586
|
-
...DEFAULT_PROPS,
|
|
1587
|
-
...initProps
|
|
1588
|
-
}));
|
|
1589
|
-
};
|
|
1590
1308
|
const SliderContext = createContext(null);
|
|
1591
|
-
|
|
1592
|
-
const storeRef = useRef(null);
|
|
1593
|
-
if (!storeRef.current) storeRef.current = createSliderStore(props);
|
|
1594
|
-
useEffect(() => {
|
|
1595
|
-
if (storeRef.current) storeRef.current.setState(props);
|
|
1596
|
-
else storeRef.current = createSliderStore(props);
|
|
1597
|
-
}, [props]);
|
|
1598
|
-
return /* @__PURE__ */ jsx(SliderContext.Provider, {
|
|
1599
|
-
value: storeRef.current,
|
|
1600
|
-
children
|
|
1601
|
-
});
|
|
1602
|
-
}
|
|
1309
|
+
const SliderProvider = SliderContext.Provider;
|
|
1603
1310
|
function useSliderContext(selector) {
|
|
1604
|
-
const
|
|
1605
|
-
if (!
|
|
1606
|
-
return
|
|
1311
|
+
const context = useContext(SliderContext);
|
|
1312
|
+
if (!context) throw new Error("Missing SliderContext.Provider in the tree");
|
|
1313
|
+
return selector ? selector(context) : context;
|
|
1607
1314
|
}
|
|
1608
1315
|
//#endregion
|
|
1609
|
-
//#region src/components/Slider/
|
|
1610
|
-
function
|
|
1316
|
+
//#region src/components/Slider/MarksOption.tsx
|
|
1317
|
+
function MarksOption({ value, type = "mark-number", label, thickness = 1, length = "0.5rem", gap = 2, labelWidth, classes, styles, className, style, ...props }) {
|
|
1611
1318
|
const min = useSliderContext((s) => s.min);
|
|
1612
1319
|
const max = useSliderContext((s) => s.max);
|
|
1613
|
-
const
|
|
1320
|
+
const scale = useSliderContext((s) => s.scale);
|
|
1614
1321
|
const vertical = useSliderContext((s) => s.vertical);
|
|
1615
1322
|
const reverse = useSliderContext((s) => s.reverse);
|
|
1616
|
-
const calcPercent = useCallback((
|
|
1617
|
-
|
|
1323
|
+
const calcPercent = useCallback((value) => {
|
|
1324
|
+
const percent = scale.normalize(value, min, max) * 100;
|
|
1325
|
+
return toFixed(xor(vertical, reverse) ? 100 - percent : percent);
|
|
1618
1326
|
}, [
|
|
1619
1327
|
vertical,
|
|
1620
1328
|
reverse,
|
|
1621
1329
|
max,
|
|
1622
1330
|
min,
|
|
1623
|
-
|
|
1331
|
+
scale
|
|
1624
1332
|
]);
|
|
1625
1333
|
return /* @__PURE__ */ jsxs("div", {
|
|
1626
|
-
className: clsx("tremolo-slider-
|
|
1334
|
+
className: clsx("tremolo-slider-marks-option", className),
|
|
1627
1335
|
style: {
|
|
1628
1336
|
left: !vertical ? `${calcPercent(value)}%` : void 0,
|
|
1629
1337
|
top: vertical ? `${calcPercent(value)}%` : void 0,
|
|
@@ -1632,7 +1340,7 @@ function ScaleOption({ value, type = "mark-number", label, thickness = 1, length
|
|
|
1632
1340
|
"data-vertical": vertical,
|
|
1633
1341
|
...props,
|
|
1634
1342
|
children: [type != "number" && /* @__PURE__ */ jsx("div", {
|
|
1635
|
-
className: clsx("tremolo-slider-
|
|
1343
|
+
className: clsx("tremolo-slider-marks-option-mark", classes?.mark),
|
|
1636
1344
|
style: {
|
|
1637
1345
|
width: !vertical ? thickness : length,
|
|
1638
1346
|
height: vertical ? thickness : length,
|
|
@@ -1642,7 +1350,7 @@ function ScaleOption({ value, type = "mark-number", label, thickness = 1, length
|
|
|
1642
1350
|
},
|
|
1643
1351
|
"data-vertical": vertical
|
|
1644
1352
|
}), type != "mark" && /* @__PURE__ */ jsx("div", {
|
|
1645
|
-
className: clsx("tremolo-slider-
|
|
1353
|
+
className: clsx("tremolo-slider-marks-option-label", classes?.label),
|
|
1646
1354
|
style: {
|
|
1647
1355
|
width: labelWidth,
|
|
1648
1356
|
...styles?.label
|
|
@@ -1667,8 +1375,8 @@ function generateOptionsList(options, min, max, step) {
|
|
|
1667
1375
|
return optionsList;
|
|
1668
1376
|
}
|
|
1669
1377
|
//#endregion
|
|
1670
|
-
//#region src/components/Slider/
|
|
1671
|
-
function
|
|
1378
|
+
//#region src/components/Slider/Marks.tsx
|
|
1379
|
+
function Marks({ gap = 6, options, children, className, style, ...props }) {
|
|
1672
1380
|
const min = useSliderContext((s) => s.min);
|
|
1673
1381
|
const max = useSliderContext((s) => s.max);
|
|
1674
1382
|
const step = useSliderContext((s) => s.step);
|
|
@@ -1677,7 +1385,7 @@ function Scale({ gap = 6, options, children, className, style, ...props }) {
|
|
|
1677
1385
|
const optionsList = options ? generateOptionsList(options, min, max, step) : [];
|
|
1678
1386
|
if (xor(vertical, reverse)) optionsList.reverse();
|
|
1679
1387
|
return /* @__PURE__ */ jsx("div", {
|
|
1680
|
-
className: clsx("tremolo-slider-
|
|
1388
|
+
className: clsx("tremolo-slider-marks", className),
|
|
1681
1389
|
style: {
|
|
1682
1390
|
marginLeft: vertical ? gap : void 0,
|
|
1683
1391
|
marginTop: !vertical ? gap : void 0,
|
|
@@ -1685,72 +1393,70 @@ function Scale({ gap = 6, options, children, className, style, ...props }) {
|
|
|
1685
1393
|
},
|
|
1686
1394
|
...props,
|
|
1687
1395
|
children: options ? optionsList.map(({ value, type }, index) => {
|
|
1688
|
-
return /* @__PURE__ */ jsx(
|
|
1396
|
+
return /* @__PURE__ */ jsx(MarksOption, {
|
|
1689
1397
|
value,
|
|
1690
1398
|
type
|
|
1691
1399
|
}, index);
|
|
1692
1400
|
}) : children
|
|
1693
1401
|
});
|
|
1694
1402
|
}
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
const
|
|
1699
|
-
const readonly = useSliderContext(
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
focus()
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
blur()
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1403
|
+
//#endregion
|
|
1404
|
+
//#region src/components/Slider/Thumb.tsx
|
|
1405
|
+
function Thumb$1({ color, children, className, style, ref }) {
|
|
1406
|
+
const elementRef = useRef(null);
|
|
1407
|
+
const { vertical, disabled, readonly, percent, thumbRef } = useSliderContext();
|
|
1408
|
+
const methods = () => ({
|
|
1409
|
+
focus() {
|
|
1410
|
+
elementRef.current?.focus();
|
|
1411
|
+
},
|
|
1412
|
+
blur() {
|
|
1413
|
+
elementRef.current?.blur();
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
useImperativeHandle(ref, methods, []);
|
|
1417
|
+
useImperativeHandle(thumbRef, methods, []);
|
|
1710
1418
|
return /* @__PURE__ */ jsx("div", {
|
|
1711
1419
|
className: "tremolo-slider-thumb-wrapper",
|
|
1712
1420
|
style: {
|
|
1713
|
-
top: vertical ? `${
|
|
1714
|
-
left: !vertical ? `${
|
|
1421
|
+
top: vertical ? `${percent}%` : "50%",
|
|
1422
|
+
left: !vertical ? `${percent}%` : "50%"
|
|
1715
1423
|
},
|
|
1716
1424
|
children: children ? children : /* @__PURE__ */ jsx("div", {
|
|
1717
|
-
ref:
|
|
1425
|
+
ref: elementRef,
|
|
1718
1426
|
className: clsx("tremolo-slider-thumb", className),
|
|
1719
1427
|
tabIndex: 0,
|
|
1720
1428
|
"aria-disabled": disabled,
|
|
1721
1429
|
"aria-readonly": readonly,
|
|
1722
1430
|
style: {
|
|
1723
1431
|
"--color": color,
|
|
1724
|
-
width: size ?? width,
|
|
1725
|
-
height: size ?? height,
|
|
1726
1432
|
...style
|
|
1727
1433
|
}
|
|
1728
1434
|
})
|
|
1729
1435
|
});
|
|
1730
|
-
}
|
|
1731
|
-
function Track({ length = 140, thickness = 10, active, inactive, children, className, style, defaultStyle = true,
|
|
1732
|
-
const vertical = useSliderContext(
|
|
1733
|
-
const
|
|
1734
|
-
const disabled = useSliderContext((s) => s.disabled);
|
|
1436
|
+
}
|
|
1437
|
+
function Track({ length = 140, thickness = 10, active, inactive, children, className, style, defaultStyle = true, ref, ...props }) {
|
|
1438
|
+
const { vertical, reverse, disabled, percent, trackRef } = useSliderContext();
|
|
1439
|
+
const composedRef = useComposedRefs(ref, trackRef);
|
|
1735
1440
|
const direction = vertical ? "bottom" : "right";
|
|
1736
1441
|
const colors = {
|
|
1737
1442
|
"--active": active,
|
|
1738
1443
|
"--inactive": inactive
|
|
1739
1444
|
};
|
|
1740
|
-
return /* @__PURE__ */
|
|
1445
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1446
|
+
ref: composedRef,
|
|
1741
1447
|
className: clsx("tremolo-slider-track", className),
|
|
1742
1448
|
"aria-disabled": disabled,
|
|
1743
1449
|
"data-vertical": vertical,
|
|
1744
1450
|
style: !defaultStyle ? style : {
|
|
1745
1451
|
...colors,
|
|
1746
|
-
background: xor(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive) ${
|
|
1452
|
+
background: xor(vertical, reverse) ? `linear-gradient(to ${direction}, var(--inactive) ${percent}%, var(--active) ${percent}%)` : `linear-gradient(to ${direction}, var(--active) ${percent}%, var(--inactive) ${percent}%)`,
|
|
1747
1453
|
borderRadius: styleHelper(thickness, "/", 2),
|
|
1748
1454
|
width: !vertical ? length : thickness,
|
|
1749
1455
|
height: vertical ? length : thickness,
|
|
1750
1456
|
...style
|
|
1751
1457
|
},
|
|
1752
1458
|
...props,
|
|
1753
|
-
children
|
|
1459
|
+
children
|
|
1754
1460
|
});
|
|
1755
1461
|
}
|
|
1756
1462
|
//#endregion
|
|
@@ -1760,55 +1466,37 @@ const defaultExternalStyles$1 = {
|
|
|
1760
1466
|
cursor: "pointer"
|
|
1761
1467
|
};
|
|
1762
1468
|
/**
|
|
1469
|
+
* The wheel only acts while the focus is inside, so that scrolling a page past
|
|
1470
|
+
* the control does not change its value.
|
|
1471
|
+
*/
|
|
1472
|
+
const WHEEL_OPTIONS$1 = { requireFocus: true };
|
|
1473
|
+
/**
|
|
1763
1474
|
* Customizable slider
|
|
1764
1475
|
*/
|
|
1765
1476
|
const Slider = {
|
|
1766
|
-
Root: forwardRef(({ value, min, max, step = 1,
|
|
1767
|
-
const
|
|
1477
|
+
Root: forwardRef(({ value, min, max, step = 1, scale = linearScale, vertical = false, reverse = false, externalStyles: _externalStyles, wheel = ["raw", 1], keyboard = ["raw", 1], disabled = false, readonly = false, onChange, onDragStart, onDragEnd, className, style, children, onFocus, onBlur, onPointerDown, onKeyDown, ...props }, forwardedRef) => {
|
|
1478
|
+
const trackRef = useRef(null);
|
|
1768
1479
|
const thumbRef = useRef(null);
|
|
1769
1480
|
const externalStyles = {
|
|
1770
1481
|
...defaultExternalStyles$1,
|
|
1771
1482
|
..._externalStyles
|
|
1772
1483
|
};
|
|
1773
|
-
const p = toFixed(
|
|
1484
|
+
const p = toFixed(scale.normalize(value, min, max) * 100);
|
|
1774
1485
|
const rev = toFixed(100 - p);
|
|
1775
1486
|
const displayReversed = useMemo(() => xor(vertical, reverse), [vertical, reverse]);
|
|
1776
1487
|
const percent = displayReversed ? rev : p;
|
|
1777
|
-
|
|
1778
|
-
let thumbProps = {};
|
|
1779
|
-
let scaleComponent = void 0;
|
|
1780
|
-
if (children != void 0) React.Children.forEach(children, (child) => {
|
|
1781
|
-
if (React.isValidElement(child)) if (child.type == Thumb$1) thumbProps = child.props;
|
|
1782
|
-
else if (child.type == Track) trackProps = child.props;
|
|
1783
|
-
else if (child.type == Scale) scaleComponent = child;
|
|
1784
|
-
else throw new Error("only <SliderThumb> or <SliderTrack>");
|
|
1785
|
-
else throw new Error("children is an invalid element.");
|
|
1786
|
-
});
|
|
1787
|
-
const onDrag = useCallback((nx, ny) => {
|
|
1788
|
-
if (!onChange || readonly) return;
|
|
1789
|
-
const n = vertical ? ny : nx;
|
|
1790
|
-
onChange(clamp(stepValue(rawValue(displayReversed ? 1 - n : n, min, max, skew), step), min, max));
|
|
1791
|
-
}, [
|
|
1792
|
-
displayReversed,
|
|
1793
|
-
max,
|
|
1488
|
+
const axis = useMemo(() => ({
|
|
1794
1489
|
min,
|
|
1795
|
-
onChange,
|
|
1796
|
-
readonly,
|
|
1797
|
-
skew,
|
|
1798
|
-
step,
|
|
1799
|
-
vertical
|
|
1800
|
-
]);
|
|
1801
|
-
const updateValueByEvent = useCallback((eventType, x) => {
|
|
1802
|
-
let newValue;
|
|
1803
|
-
if (eventType == "normalized") newValue = rawValue(normalizeValue(value, min, max, skew) + x, min, max, skew);
|
|
1804
|
-
else newValue = value + x;
|
|
1805
|
-
return clamp(stepValue(newValue, step), min, max);
|
|
1806
|
-
}, [
|
|
1807
1490
|
max,
|
|
1491
|
+
step,
|
|
1492
|
+
scale,
|
|
1493
|
+
reverse: displayReversed
|
|
1494
|
+
}), [
|
|
1808
1495
|
min,
|
|
1809
|
-
|
|
1496
|
+
max,
|
|
1810
1497
|
step,
|
|
1811
|
-
|
|
1498
|
+
scale,
|
|
1499
|
+
displayReversed
|
|
1812
1500
|
]);
|
|
1813
1501
|
const handleKeyDown = useCallback((event) => {
|
|
1814
1502
|
if (!keyboard || !onChange || readonly) return;
|
|
@@ -1820,51 +1508,76 @@ const Slider = {
|
|
|
1820
1508
|
"ArrowDown"
|
|
1821
1509
|
].includes(key)) {
|
|
1822
1510
|
event.preventDefault();
|
|
1823
|
-
let
|
|
1824
|
-
if (reverse)
|
|
1825
|
-
onChange(
|
|
1511
|
+
let direction = key == "ArrowRight" || key == "ArrowUp" ? 1 : -1;
|
|
1512
|
+
if (reverse) direction *= -1;
|
|
1513
|
+
onChange(applyDelta(value, direction, keyboard, axis));
|
|
1826
1514
|
}
|
|
1827
1515
|
}, [
|
|
1828
1516
|
keyboard,
|
|
1829
1517
|
onChange,
|
|
1830
1518
|
readonly,
|
|
1831
1519
|
reverse,
|
|
1832
|
-
|
|
1520
|
+
value,
|
|
1521
|
+
axis
|
|
1833
1522
|
]);
|
|
1834
|
-
const
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1523
|
+
const valueOf = (v) => v[vertical ? 1 : 0];
|
|
1524
|
+
const { refCallback: dragRefCallback } = useDragValue({
|
|
1525
|
+
axis,
|
|
1526
|
+
baseElementRef: trackRef,
|
|
1527
|
+
cursor: readonly ? void 0 : externalStyles.cursor,
|
|
1528
|
+
updateOnPointerDown: true,
|
|
1529
|
+
onChange: (v) => {
|
|
1530
|
+
if (readonly) return;
|
|
1531
|
+
onChange?.(valueOf(v));
|
|
1532
|
+
},
|
|
1533
|
+
onDragStart: (v) => {
|
|
1838
1534
|
if (readonly) return;
|
|
1839
1535
|
if (externalStyles.userSelectNone) addUserSelectNone();
|
|
1840
|
-
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
|
|
1841
1536
|
thumbRef.current?.focus();
|
|
1842
|
-
onDragStart?.(
|
|
1537
|
+
onDragStart?.(valueOf(v));
|
|
1843
1538
|
},
|
|
1844
|
-
onDragEnd: (
|
|
1539
|
+
onDragEnd: (v) => {
|
|
1845
1540
|
if (readonly) return;
|
|
1846
1541
|
if (externalStyles.userSelectNone) removeUserSelectNone();
|
|
1847
|
-
|
|
1848
|
-
onDragEnd?.(clamp(stepValue(rawValue(vertical ? ny : nx, min, max, skew), step), min, max));
|
|
1542
|
+
onDragEnd?.(valueOf(v));
|
|
1849
1543
|
}
|
|
1850
1544
|
});
|
|
1851
|
-
const
|
|
1545
|
+
const rootRefCallback = useComposedRefs(dragRefCallback, useWheel((event) => {
|
|
1852
1546
|
if (!wheel || !onChange || readonly) return;
|
|
1853
1547
|
event.preventDefault();
|
|
1854
|
-
let
|
|
1855
|
-
if (!vertical && event.deltaX != 0)
|
|
1548
|
+
let direction;
|
|
1549
|
+
if (!vertical && event.deltaX != 0) direction = event.deltaX > 0 ? 1 : -1;
|
|
1856
1550
|
else {
|
|
1857
1551
|
if (event.deltaY == 0) return;
|
|
1858
|
-
|
|
1552
|
+
direction = event.deltaY > 0 ? -1 : 1;
|
|
1859
1553
|
}
|
|
1860
|
-
if (vertical && reverse)
|
|
1861
|
-
onChange(
|
|
1862
|
-
},
|
|
1863
|
-
|
|
1554
|
+
if (vertical && reverse) direction *= -1;
|
|
1555
|
+
onChange(applyDelta(value, direction, wheel, axis));
|
|
1556
|
+
}, WHEEL_OPTIONS$1));
|
|
1557
|
+
const context = useMemo(() => ({
|
|
1558
|
+
value,
|
|
1559
|
+
min,
|
|
1560
|
+
max,
|
|
1561
|
+
step,
|
|
1562
|
+
scale,
|
|
1864
1563
|
vertical,
|
|
1564
|
+
reverse,
|
|
1565
|
+
disabled,
|
|
1865
1566
|
readonly,
|
|
1866
|
-
|
|
1867
|
-
|
|
1567
|
+
percent,
|
|
1568
|
+
trackRef,
|
|
1569
|
+
thumbRef
|
|
1570
|
+
}), [
|
|
1571
|
+
value,
|
|
1572
|
+
min,
|
|
1573
|
+
max,
|
|
1574
|
+
step,
|
|
1575
|
+
scale,
|
|
1576
|
+
vertical,
|
|
1577
|
+
reverse,
|
|
1578
|
+
disabled,
|
|
1579
|
+
readonly,
|
|
1580
|
+
percent
|
|
1868
1581
|
]);
|
|
1869
1582
|
useImperativeHandle(forwardedRef, () => {
|
|
1870
1583
|
return {
|
|
@@ -1877,20 +1590,10 @@ const Slider = {
|
|
|
1877
1590
|
};
|
|
1878
1591
|
}, []);
|
|
1879
1592
|
return /* @__PURE__ */ jsx(SliderProvider, {
|
|
1880
|
-
|
|
1881
|
-
max,
|
|
1882
|
-
step,
|
|
1883
|
-
skew,
|
|
1884
|
-
vertical,
|
|
1885
|
-
reverse,
|
|
1886
|
-
disabled,
|
|
1887
|
-
readonly,
|
|
1593
|
+
value: context,
|
|
1888
1594
|
children: /* @__PURE__ */ jsx("div", {
|
|
1889
1595
|
className: clsx("tremolo-slider", className),
|
|
1890
|
-
ref:
|
|
1891
|
-
wheelRefCallback(div);
|
|
1892
|
-
touchMoveRefCallback(div);
|
|
1893
|
-
},
|
|
1596
|
+
ref: rootRefCallback,
|
|
1894
1597
|
tabIndex: -1,
|
|
1895
1598
|
role: "slider",
|
|
1896
1599
|
"aria-valuenow": value,
|
|
@@ -1899,14 +1602,9 @@ const Slider = {
|
|
|
1899
1602
|
"aria-orientation": vertical ? "vertical" : "horizontal",
|
|
1900
1603
|
"aria-disabled": disabled,
|
|
1901
1604
|
"aria-readonly": readonly,
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
},
|
|
1906
|
-
onPointerDown: (event) => {
|
|
1907
|
-
pointerDownHandler(event);
|
|
1908
|
-
onPointerDown?.(event);
|
|
1909
|
-
},
|
|
1605
|
+
"data-vertical": vertical,
|
|
1606
|
+
style,
|
|
1607
|
+
onPointerDown,
|
|
1910
1608
|
onKeyDown: (event) => {
|
|
1911
1609
|
handleKeyDown(event);
|
|
1912
1610
|
onKeyDown?.(event);
|
|
@@ -1920,93 +1618,30 @@ const Slider = {
|
|
|
1920
1618
|
onBlur?.(event);
|
|
1921
1619
|
},
|
|
1922
1620
|
...props,
|
|
1923
|
-
children
|
|
1924
|
-
style: {
|
|
1925
|
-
display: "flex",
|
|
1926
|
-
flexDirection: vertical ? "row" : "column",
|
|
1927
|
-
width: !vertical ? "100%" : void 0,
|
|
1928
|
-
height: vertical ? "100%" : void 0
|
|
1929
|
-
},
|
|
1930
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
1931
|
-
className: "tremolo-slider-track-wrapper",
|
|
1932
|
-
ref: trackElementRef,
|
|
1933
|
-
children: /* @__PURE__ */ jsx(Track, {
|
|
1934
|
-
__percent: percent,
|
|
1935
|
-
__thumb: /* @__PURE__ */ jsx(Thumb$1, {
|
|
1936
|
-
ref: thumbRef,
|
|
1937
|
-
__percent: percent,
|
|
1938
|
-
...thumbProps
|
|
1939
|
-
}),
|
|
1940
|
-
...trackProps
|
|
1941
|
-
})
|
|
1942
|
-
}), scaleComponent]
|
|
1943
|
-
})
|
|
1621
|
+
children
|
|
1944
1622
|
})
|
|
1945
1623
|
});
|
|
1946
1624
|
}),
|
|
1947
1625
|
Thumb: Thumb$1,
|
|
1948
1626
|
Track,
|
|
1949
|
-
|
|
1950
|
-
|
|
1627
|
+
Marks,
|
|
1628
|
+
MarksOption
|
|
1951
1629
|
};
|
|
1952
1630
|
//#endregion
|
|
1953
|
-
//#region src/components/
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
else if (ref !== null && ref !== void 0) ref.current = value;
|
|
1961
|
-
}
|
|
1962
|
-
/**
|
|
1963
|
-
* A utility to compose multiple refs together
|
|
1964
|
-
* Accepts callback refs and RefObject(s)
|
|
1965
|
-
*/
|
|
1966
|
-
function composeRefs(...refs) {
|
|
1967
|
-
return (node) => {
|
|
1968
|
-
let hasCleanup = false;
|
|
1969
|
-
const cleanups = refs.map((ref) => {
|
|
1970
|
-
const cleanup = setRef(ref, node);
|
|
1971
|
-
if (!hasCleanup && typeof cleanup == "function") hasCleanup = true;
|
|
1972
|
-
return cleanup;
|
|
1973
|
-
});
|
|
1974
|
-
if (hasCleanup) return () => {
|
|
1975
|
-
for (let i = 0; i < cleanups.length; i++) {
|
|
1976
|
-
const cleanup = cleanups[i];
|
|
1977
|
-
if (typeof cleanup == "function") cleanup();
|
|
1978
|
-
else setRef(refs[i], null);
|
|
1979
|
-
}
|
|
1980
|
-
};
|
|
1981
|
-
};
|
|
1982
|
-
}
|
|
1983
|
-
//#endregion
|
|
1984
|
-
//#region src/components/WheelObserver/index.tsx
|
|
1985
|
-
/**
|
|
1986
|
-
* @example
|
|
1987
|
-
* <WheelObserver
|
|
1988
|
-
* onWheel={(event) => {
|
|
1989
|
-
* event.preventDefault()
|
|
1990
|
-
* console.log(event.deltaY)
|
|
1991
|
-
* }
|
|
1992
|
-
* >
|
|
1993
|
-
* <div>Wheel here</div>
|
|
1994
|
-
* </WheelObserver>
|
|
1995
|
-
*/
|
|
1996
|
-
function WheelObserver(props) {
|
|
1997
|
-
const { as: Component = "div", children, onWheel, ref, ...attributes } = props;
|
|
1998
|
-
return /* @__PURE__ */ jsx(Component, {
|
|
1999
|
-
ref: composeRefs(ref, useRefCallbackEvent("wheel", (event) => {
|
|
2000
|
-
if (onWheel) onWheel(event);
|
|
2001
|
-
}, { passive: false }, [onWheel])),
|
|
2002
|
-
...attributes,
|
|
2003
|
-
children
|
|
2004
|
-
});
|
|
1631
|
+
//#region src/components/XYPad/context.tsx
|
|
1632
|
+
const XYPadContext = createContext(null);
|
|
1633
|
+
const XYPadProvider = XYPadContext.Provider;
|
|
1634
|
+
function useXYPadContext(selector) {
|
|
1635
|
+
const context = useContext(XYPadContext);
|
|
1636
|
+
if (!context) throw new Error("Missing XYPadContext.Provider in the tree");
|
|
1637
|
+
return selector ? selector(context) : context;
|
|
2005
1638
|
}
|
|
2006
1639
|
//#endregion
|
|
2007
1640
|
//#region src/components/XYPad/Area.tsx
|
|
2008
|
-
function Area({ width = 120, height = 120, color, children, className, style,
|
|
2009
|
-
|
|
1641
|
+
function Area({ width = 120, height = 120, color, children, className, style, ref, ...props }) {
|
|
1642
|
+
const { areaRef } = useXYPadContext();
|
|
1643
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1644
|
+
ref: useComposedRefs(ref, areaRef),
|
|
2010
1645
|
className: clsx("tremolo-xy-pad-area", className),
|
|
2011
1646
|
style: {
|
|
2012
1647
|
"--color": color,
|
|
@@ -2015,121 +1650,105 @@ function Area({ width = 120, height = 120, color, children, className, style, __
|
|
|
2015
1650
|
...style
|
|
2016
1651
|
},
|
|
2017
1652
|
...props,
|
|
2018
|
-
children
|
|
1653
|
+
children
|
|
2019
1654
|
});
|
|
2020
1655
|
}
|
|
2021
1656
|
//#endregion
|
|
2022
1657
|
//#region src/components/XYPad/Thumb.tsx
|
|
2023
|
-
|
|
2024
|
-
const
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
1658
|
+
function Thumb({ color, children, wrapperClassName, wrapperStyle, className, style, ref, ...props }) {
|
|
1659
|
+
const elementRef = useRef(null);
|
|
1660
|
+
const { disabled, readonly, percent, thumbRef } = useXYPadContext();
|
|
1661
|
+
const methods = () => ({
|
|
1662
|
+
focus() {
|
|
1663
|
+
elementRef.current?.focus();
|
|
1664
|
+
},
|
|
1665
|
+
blur() {
|
|
1666
|
+
elementRef.current?.blur();
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
useImperativeHandle(ref, methods, []);
|
|
1670
|
+
useImperativeHandle(thumbRef, methods, []);
|
|
2035
1671
|
return /* @__PURE__ */ jsx("div", {
|
|
2036
1672
|
className: clsx("tremolo-xy-pad-thumb-wrapper", wrapperClassName),
|
|
2037
1673
|
style: {
|
|
2038
|
-
|
|
1674
|
+
left: `${percent[0]}%`,
|
|
1675
|
+
top: `${percent[1]}%`,
|
|
2039
1676
|
...wrapperStyle
|
|
2040
1677
|
},
|
|
2041
1678
|
...props,
|
|
2042
1679
|
children: children ? children : /* @__PURE__ */ jsx("div", {
|
|
2043
|
-
ref:
|
|
1680
|
+
ref: elementRef,
|
|
2044
1681
|
className: clsx("tremolo-xy-pad-thumb", className),
|
|
2045
1682
|
tabIndex: 0,
|
|
2046
|
-
"aria-disabled":
|
|
2047
|
-
"aria-readonly":
|
|
1683
|
+
"aria-disabled": disabled,
|
|
1684
|
+
"aria-readonly": readonly,
|
|
2048
1685
|
style: {
|
|
2049
1686
|
"--color": color,
|
|
2050
|
-
width: size ?? width,
|
|
2051
|
-
height: size ?? height,
|
|
2052
1687
|
...style
|
|
2053
1688
|
}
|
|
2054
1689
|
})
|
|
2055
1690
|
});
|
|
2056
|
-
}
|
|
1691
|
+
}
|
|
2057
1692
|
//#endregion
|
|
2058
1693
|
//#region src/components/XYPad/index.tsx
|
|
2059
|
-
const defaultValueOptions = {
|
|
2060
|
-
skew: 1,
|
|
2061
|
-
step: 1,
|
|
2062
|
-
reverse: false,
|
|
2063
|
-
wheel: ["raw", 1],
|
|
2064
|
-
keyboard: ["raw", 1]
|
|
2065
|
-
};
|
|
2066
1694
|
const defaultExternalStyles = {
|
|
2067
1695
|
userSelectNone: true,
|
|
2068
1696
|
cursor: "pointer"
|
|
2069
1697
|
};
|
|
2070
1698
|
/**
|
|
1699
|
+
* The wheel only acts while the focus is inside, so that scrolling a page past
|
|
1700
|
+
* the control does not change its value.
|
|
1701
|
+
*/
|
|
1702
|
+
const WHEEL_OPTIONS = { requireFocus: true };
|
|
1703
|
+
/**
|
|
2071
1704
|
* Simple XYPad
|
|
2072
1705
|
*/
|
|
2073
1706
|
const XYPad = {
|
|
2074
|
-
Root: forwardRef(({
|
|
2075
|
-
const x = useMemo(() => {
|
|
2076
|
-
return {
|
|
2077
|
-
...defaultValueOptions,
|
|
2078
|
-
..._x
|
|
2079
|
-
};
|
|
2080
|
-
}, [_x]);
|
|
2081
|
-
const y = useMemo(() => {
|
|
2082
|
-
return {
|
|
2083
|
-
...defaultValueOptions,
|
|
2084
|
-
..._y
|
|
2085
|
-
};
|
|
2086
|
-
}, [_y]);
|
|
1707
|
+
Root: forwardRef(({ value, min: _min, max: _max, step: _step = 1, scale: _scale = linearScale, reverse: _reverse = false, wheel = ["raw", 1], keyboard = ["raw", 1], className, style, externalStyles: _externalStyles, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children, ...props }, forwardedRef) => {
|
|
2087
1708
|
const rootRef = useRef(null);
|
|
2088
|
-
const
|
|
1709
|
+
const areaRef = useRef(null);
|
|
2089
1710
|
const thumbRef = useRef(null);
|
|
2090
1711
|
const externalStyles = {
|
|
2091
1712
|
...defaultExternalStyles,
|
|
2092
1713
|
..._externalStyles
|
|
2093
1714
|
};
|
|
2094
|
-
const
|
|
2095
|
-
const
|
|
2096
|
-
const
|
|
2097
|
-
const
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
else if (child.type == Area) areaProps = child.props;
|
|
2103
|
-
else throw new Error("only <XYPadThumb> or <XYPadArea>");
|
|
2104
|
-
else throw new Error("children is an invalid element.");
|
|
2105
|
-
});
|
|
2106
|
-
const onDrag = useCallback((nx, ny) => {
|
|
2107
|
-
if (!onChange || readonly) return;
|
|
2108
|
-
const vx = rawValue(x.reverse ? 1 - nx : nx, x.min, x.max, x.skew);
|
|
2109
|
-
const vy = rawValue(y.reverse ? 1 - ny : ny, y.min, y.max, y.skew);
|
|
2110
|
-
onChange(clamp(stepValue(vx, x.step), x.min, x.max), clamp(stepValue(vy, y.step), y.min, y.max));
|
|
1715
|
+
const min = useMemo(() => toXY(_min), [_min]);
|
|
1716
|
+
const max = useMemo(() => toXY(_max), [_max]);
|
|
1717
|
+
const step = useMemo(() => toXY(_step), [_step]);
|
|
1718
|
+
const scale = useMemo(() => toXY(_scale), [_scale]);
|
|
1719
|
+
const reverse = useMemo(() => toXY(_reverse), [_reverse]);
|
|
1720
|
+
const percent = useMemo(() => {
|
|
1721
|
+
const normalized = [0, 1].map((i) => scale[i].normalize(value[i], min[i], max[i]));
|
|
1722
|
+
return [0, 1].map((i) => toFixed((reverse[i] ? 1 - normalized[i] : normalized[i]) * 100));
|
|
2111
1723
|
}, [
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
1724
|
+
value,
|
|
1725
|
+
min,
|
|
1726
|
+
max,
|
|
1727
|
+
scale,
|
|
1728
|
+
reverse
|
|
1729
|
+
]);
|
|
1730
|
+
const axis = useMemo(() => [0, 1].map((i) => ({
|
|
1731
|
+
min: min[i],
|
|
1732
|
+
max: max[i],
|
|
1733
|
+
step: step[i],
|
|
1734
|
+
scale: scale[i],
|
|
1735
|
+
reverse: reverse[i]
|
|
1736
|
+
})), [
|
|
1737
|
+
min,
|
|
1738
|
+
max,
|
|
1739
|
+
step,
|
|
1740
|
+
scale,
|
|
1741
|
+
reverse
|
|
1742
|
+
]);
|
|
1743
|
+
const withAxis = useCallback((axis, next) => axis == 0 ? [next, value[1]] : [value[0], next], [value]);
|
|
1744
|
+
/** @param i 0 = x, 1 = y */
|
|
1745
|
+
const nudge = useCallback((i, direction, option) => withAxis(i, applyDelta(value[i], direction, option, axis[i])), [
|
|
1746
|
+
value,
|
|
1747
|
+
axis,
|
|
1748
|
+
withAxis
|
|
2124
1749
|
]);
|
|
2125
|
-
const updateValueByEvent = useCallback((eventType, target, x) => {
|
|
2126
|
-
let v;
|
|
2127
|
-
if (eventType == "normalized") v = rawValue(normalizeValue(target.value, target.min, target.max, target.skew) + x, target.min, target.max, target.skew);
|
|
2128
|
-
else v = target.value + x;
|
|
2129
|
-
return clamp(stepValue(v, target.step), target.min, target.max);
|
|
2130
|
-
}, []);
|
|
2131
1750
|
const handleKeyDown = useCallback((event) => {
|
|
2132
|
-
if (!onChange || readonly) return;
|
|
1751
|
+
if (!onChange || readonly || !keyboard) return;
|
|
2133
1752
|
const key = event.key;
|
|
2134
1753
|
if ([
|
|
2135
1754
|
"ArrowRight",
|
|
@@ -2137,62 +1756,72 @@ const XYPad = {
|
|
|
2137
1756
|
"ArrowUp",
|
|
2138
1757
|
"ArrowDown"
|
|
2139
1758
|
].includes(key)) {
|
|
2140
|
-
const
|
|
2141
|
-
const target = isHorizontal ? x : y;
|
|
2142
|
-
if (!target.keyboard) return;
|
|
1759
|
+
const i = key == "ArrowRight" || key == "ArrowLeft" ? 0 : 1;
|
|
2143
1760
|
event.preventDefault();
|
|
2144
|
-
let
|
|
2145
|
-
if (key == "ArrowLeft" || key == "ArrowUp")
|
|
2146
|
-
if (
|
|
2147
|
-
|
|
2148
|
-
if (isHorizontal) onChange(newValue, y.value);
|
|
2149
|
-
else onChange(x.value, newValue);
|
|
1761
|
+
let direction = 1;
|
|
1762
|
+
if (key == "ArrowLeft" || key == "ArrowUp") direction *= -1;
|
|
1763
|
+
if (reverse[i]) direction *= -1;
|
|
1764
|
+
onChange(nudge(i, direction, keyboard));
|
|
2150
1765
|
}
|
|
2151
1766
|
}, [
|
|
2152
1767
|
onChange,
|
|
2153
1768
|
readonly,
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
1769
|
+
keyboard,
|
|
1770
|
+
reverse,
|
|
1771
|
+
nudge
|
|
2157
1772
|
]);
|
|
2158
|
-
const {
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
1773
|
+
const { refCallback: dragRefCallback } = useDragValue({
|
|
1774
|
+
axis,
|
|
1775
|
+
baseElementRef: areaRef,
|
|
1776
|
+
updateOnPointerDown: true,
|
|
1777
|
+
cursor: readonly ? void 0 : externalStyles.cursor,
|
|
1778
|
+
onChange: (v) => {
|
|
1779
|
+
if (readonly) return;
|
|
1780
|
+
onChange?.(v);
|
|
1781
|
+
},
|
|
1782
|
+
onDragStart: (v) => {
|
|
2162
1783
|
if (readonly) return;
|
|
2163
1784
|
if (externalStyles.userSelectNone) addUserSelectNone();
|
|
2164
|
-
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
|
|
2165
1785
|
thumbRef.current?.focus();
|
|
2166
|
-
|
|
2167
|
-
const valueY = clamp(stepValue(rawValue(ny, y.min, y.max, y.skew), y.step), y.min, y.max);
|
|
2168
|
-
onDragStart?.(valueX, valueY);
|
|
1786
|
+
onDragStart?.(v);
|
|
2169
1787
|
},
|
|
2170
|
-
onDragEnd: (
|
|
1788
|
+
onDragEnd: (v) => {
|
|
2171
1789
|
if (readonly) return;
|
|
2172
1790
|
if (externalStyles.userSelectNone) removeUserSelectNone();
|
|
2173
|
-
|
|
2174
|
-
const valueX = clamp(stepValue(rawValue(nx, x.min, x.max, x.skew), x.step), x.min, x.max);
|
|
2175
|
-
const valueY = clamp(stepValue(rawValue(ny, y.min, y.max, y.skew), y.step), y.min, y.max);
|
|
2176
|
-
onDragEnd?.(valueX, valueY);
|
|
1791
|
+
onDragEnd?.(v);
|
|
2177
1792
|
}
|
|
2178
1793
|
});
|
|
2179
|
-
const
|
|
2180
|
-
if (!onChange || readonly) return;
|
|
2181
|
-
const
|
|
2182
|
-
if (!target.wheel) return;
|
|
1794
|
+
const rootRefCallback = useComposedRefs(rootRef, dragRefCallback, useWheel((event) => {
|
|
1795
|
+
if (!onChange || readonly || !wheel) return;
|
|
1796
|
+
const i = event.shiftKey ? 0 : 1;
|
|
2183
1797
|
event.preventDefault();
|
|
2184
|
-
let
|
|
2185
|
-
if (event.deltaY < 0)
|
|
2186
|
-
if (
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
1798
|
+
let direction = 1;
|
|
1799
|
+
if (event.deltaY < 0) direction *= -1;
|
|
1800
|
+
if (reverse[i]) direction *= -1;
|
|
1801
|
+
onChange(nudge(i, direction, wheel));
|
|
1802
|
+
}, WHEEL_OPTIONS));
|
|
1803
|
+
const context = useMemo(() => ({
|
|
1804
|
+
value,
|
|
1805
|
+
min,
|
|
1806
|
+
max,
|
|
1807
|
+
step,
|
|
1808
|
+
scale,
|
|
1809
|
+
reverse,
|
|
1810
|
+
disabled,
|
|
1811
|
+
readonly,
|
|
1812
|
+
percent,
|
|
1813
|
+
areaRef,
|
|
1814
|
+
thumbRef
|
|
1815
|
+
}), [
|
|
1816
|
+
value,
|
|
1817
|
+
min,
|
|
1818
|
+
max,
|
|
1819
|
+
step,
|
|
1820
|
+
scale,
|
|
1821
|
+
reverse,
|
|
1822
|
+
disabled,
|
|
2194
1823
|
readonly,
|
|
2195
|
-
|
|
1824
|
+
percent
|
|
2196
1825
|
]);
|
|
2197
1826
|
useImperativeHandle(forwardedRef, () => {
|
|
2198
1827
|
return {
|
|
@@ -2205,51 +1834,30 @@ const XYPad = {
|
|
|
2205
1834
|
original: rootRef
|
|
2206
1835
|
};
|
|
2207
1836
|
}, []);
|
|
2208
|
-
return /* @__PURE__ */ jsx(
|
|
2209
|
-
|
|
2210
|
-
ref: composeRefs(rootRef, wheelRefCallback, touchMoveRefCallback),
|
|
2211
|
-
tabIndex: -1,
|
|
2212
|
-
"aria-disabled": disabled,
|
|
2213
|
-
"aria-readonly": readonly,
|
|
2214
|
-
style: {
|
|
2215
|
-
margin: `calc(${styleHelper(thumbProps?.size ?? 22)} / 2)`,
|
|
2216
|
-
cursor: readonly ? "not-allowed" : "pointer",
|
|
2217
|
-
WebkitTapHighlightColor: "transparent",
|
|
2218
|
-
...style
|
|
2219
|
-
},
|
|
2220
|
-
onPointerDown: (event) => {
|
|
2221
|
-
pointerDownHandler(event);
|
|
2222
|
-
onPointerDown?.(event);
|
|
2223
|
-
},
|
|
2224
|
-
onKeyDown: (event) => {
|
|
2225
|
-
handleKeyDown(event);
|
|
2226
|
-
onKeyDown?.(event);
|
|
2227
|
-
},
|
|
2228
|
-
onFocus: (event) => {
|
|
2229
|
-
thumbRef.current?.focus();
|
|
2230
|
-
onFocus?.(event);
|
|
2231
|
-
},
|
|
2232
|
-
onBlur: (event) => {
|
|
2233
|
-
thumbRef.current?.blur();
|
|
2234
|
-
onBlur?.(event);
|
|
2235
|
-
},
|
|
2236
|
-
...props,
|
|
1837
|
+
return /* @__PURE__ */ jsx(XYPadProvider, {
|
|
1838
|
+
value: context,
|
|
2237
1839
|
children: /* @__PURE__ */ jsx("div", {
|
|
2238
|
-
className: "tremolo-xy-pad
|
|
2239
|
-
ref:
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
}
|
|
1840
|
+
className: clsx("tremolo-xy-pad", className),
|
|
1841
|
+
ref: rootRefCallback,
|
|
1842
|
+
tabIndex: -1,
|
|
1843
|
+
"aria-disabled": disabled,
|
|
1844
|
+
"aria-readonly": readonly,
|
|
1845
|
+
style,
|
|
1846
|
+
onPointerDown,
|
|
1847
|
+
onKeyDown: (event) => {
|
|
1848
|
+
handleKeyDown(event);
|
|
1849
|
+
onKeyDown?.(event);
|
|
1850
|
+
},
|
|
1851
|
+
onFocus: (event) => {
|
|
1852
|
+
thumbRef.current?.focus();
|
|
1853
|
+
onFocus?.(event);
|
|
1854
|
+
},
|
|
1855
|
+
onBlur: (event) => {
|
|
1856
|
+
thumbRef.current?.blur();
|
|
1857
|
+
onBlur?.(event);
|
|
1858
|
+
},
|
|
1859
|
+
...props,
|
|
1860
|
+
children
|
|
2253
1861
|
})
|
|
2254
1862
|
});
|
|
2255
1863
|
}),
|
|
@@ -2321,6 +1929,6 @@ function useMIDIMessage(midiAccess, onMIDIMessage) {
|
|
|
2321
1929
|
}, [midiAccess, onMIDIMessage]);
|
|
2322
1930
|
}
|
|
2323
1931
|
//#endregion
|
|
2324
|
-
export { AnimationCanvas,
|
|
1932
|
+
export { AnimationCanvas, Knob, NOT_SUPPORTED, NumberInput, PERMISSION_DENIED, Piano, PointsEditor, SHORTCUTS, Slider, XYPad, clampPoint, useAnimationFrame, useDrag, useDragValue, useEventListener, useInterval, useLongPress, useMIDIAccess, useMIDIInput, useMIDIMessage, useNumberInputContext, usePointsEditorContext, useSliderContext, useWheel };
|
|
2325
1933
|
|
|
2326
1934
|
//# sourceMappingURL=index.js.map
|