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