@tremolo-ui/react 0.2.0 → 0.2.1
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 +1203 -1036
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +176 -159
- package/dist/index.d.cts +241 -246
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +241 -246
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1187 -1021
- package/dist/index.js.map +1 -1
- package/package.json +17 -19
- package/src/components/AnimationCanvas/index.tsx +0 -7
- package/src/components/DragObserver/index.tsx +9 -7
- package/src/components/Knob/ActiveLine.tsx +29 -0
- package/src/components/Knob/InactiveLine.tsx +53 -0
- package/src/components/Knob/SVGRoot.tsx +39 -0
- package/src/components/Knob/Thumb.tsx +64 -0
- package/src/components/Knob/context.tsx +120 -0
- package/src/components/Knob/index.css +5 -1
- package/src/components/Knob/index.tsx +110 -164
- package/src/components/NumberInput/DecrementStepper.tsx +0 -2
- package/src/components/NumberInput/IncrementStepper.tsx +0 -2
- package/src/components/NumberInput/InternalInput.tsx +3 -1
- package/src/components/NumberInput/Stepper.tsx +0 -2
- package/src/components/NumberInput/context.tsx +0 -1
- package/src/components/NumberInput/index.tsx +6 -8
- package/src/components/Piano/KeyLabel.tsx +0 -2
- package/src/components/Piano/context.tsx +0 -2
- package/src/components/Piano/index.tsx +7 -11
- package/src/components/Piano/key.tsx +0 -10
- package/src/components/Piano/keyboardShortcuts.ts +0 -2
- package/src/components/PointsEditor/Background.tsx +17 -0
- package/src/components/PointsEditor/Container.tsx +26 -0
- package/src/components/PointsEditor/Point.tsx +137 -0
- package/src/components/PointsEditor/context.tsx +91 -0
- package/src/components/PointsEditor/index.css +30 -0
- package/src/components/PointsEditor/index.tsx +129 -0
- package/src/components/Slider/Scale.tsx +0 -2
- package/src/components/Slider/ScaleOption.tsx +0 -2
- package/src/components/Slider/Thumb.tsx +0 -4
- package/src/components/Slider/Track.tsx +0 -2
- package/src/components/Slider/context.tsx +0 -1
- package/src/components/Slider/index.tsx +42 -14
- package/src/components/WheelObserver/index.tsx +17 -19
- package/src/components/XYPad/Area.tsx +0 -2
- package/src/components/XYPad/Thumb.tsx +0 -3
- package/src/components/XYPad/index.tsx +52 -25
- package/src/components/_util/composeRefs.tsx +63 -0
- package/src/components/_util/index.ts +23 -6
- package/src/components/_util/type.ts +1 -0
- package/src/hooks/useAnimationFrame.ts +0 -3
- package/src/hooks/useCallbackRef.ts +2 -2
- package/src/hooks/useDrag.ts +12 -11
- package/src/hooks/useDragWithElement.ts +12 -17
- package/src/hooks/useEventListener.ts +6 -9
- package/src/hooks/useInterval.ts +0 -3
- package/src/hooks/useLongPress.ts +0 -3
- package/src/hooks/useMIDIAccess.ts +0 -1
- package/src/hooks/useMIDIInput.ts +0 -1
- package/src/hooks/useMIDIMessage.ts +0 -1
- package/src/hooks/usePianoDrag.ts +75 -0
- package/src/index.ts +8 -5
- package/src/styles/global.css +17 -1
- package/dist/index.css.map +0 -1
- package/src/components/AnimationKnob/index.tsx +0 -314
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -19,16 +20,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
19
20
|
value: mod,
|
|
20
21
|
enumerable: true
|
|
21
22
|
}) : target, mod));
|
|
22
|
-
|
|
23
23
|
//#endregion
|
|
24
24
|
let clsx = require("clsx");
|
|
25
25
|
clsx = __toESM(clsx);
|
|
26
26
|
let react = require("react");
|
|
27
27
|
react = __toESM(react);
|
|
28
28
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
29
|
-
let
|
|
29
|
+
let _tremolo_ui_functions = require("@tremolo-ui/functions");
|
|
30
30
|
let zustand = require("zustand");
|
|
31
|
-
|
|
32
31
|
//#region src/components/AnimationCanvas/canvas.ts
|
|
33
32
|
const drawingState = [
|
|
34
33
|
"strokeStyle",
|
|
@@ -58,10 +57,9 @@ function setDprConfig(canvas, context, width, height, dpr) {
|
|
|
58
57
|
canvas.style.width = `${width}px`;
|
|
59
58
|
canvas.style.height = `${height}px`;
|
|
60
59
|
}
|
|
61
|
-
|
|
62
60
|
//#endregion
|
|
63
61
|
//#region src/components/AnimationCanvas/index.tsx
|
|
64
|
-
function AnimationCanvas({ draw, init, animate = true, options, width: _width = 100, height: _height = 100, relativeSize, reduceFlickering = true, className, onContextMenu = (event) => event.preventDefault()
|
|
62
|
+
function AnimationCanvas({ draw, init, animate = true, options, width: _width = 100, height: _height = 100, relativeSize, reduceFlickering = true, className, onContextMenu = (event) => event.preventDefault(), ...props }) {
|
|
65
63
|
const canvasRef = (0, react.useRef)(null);
|
|
66
64
|
const memoCanvasRef = (0, react.useRef)(null);
|
|
67
65
|
const reqIdRef = (0, react.useRef)(-1);
|
|
@@ -165,7 +163,6 @@ function AnimationCanvas({ draw, init, animate = true, options, width: _width =
|
|
|
165
163
|
ref: memoCanvasRef
|
|
166
164
|
})] });
|
|
167
165
|
}
|
|
168
|
-
|
|
169
166
|
//#endregion
|
|
170
167
|
//#region src/hooks/useCallbackRef.ts
|
|
171
168
|
/**
|
|
@@ -181,14 +178,13 @@ function useCallbackRef(callback, deps = []) {
|
|
|
181
178
|
});
|
|
182
179
|
return (0, react.useCallback)((...args) => callbackRef.current?.(...args), deps);
|
|
183
180
|
}
|
|
184
|
-
|
|
185
181
|
//#endregion
|
|
186
182
|
//#region src/hooks/useEventListener.ts
|
|
187
183
|
function useEventListener(target, event, handler, options) {
|
|
188
184
|
const listener = useCallbackRef(handler);
|
|
189
185
|
(0, react.useEffect)(() => {
|
|
190
186
|
const node = typeof target === "function" ? target() : target ?? document;
|
|
191
|
-
if (!
|
|
187
|
+
if (!node) return;
|
|
192
188
|
node.addEventListener(event, listener, options);
|
|
193
189
|
return () => {
|
|
194
190
|
node.removeEventListener(event, listener, options);
|
|
@@ -197,14 +193,12 @@ function useEventListener(target, event, handler, options) {
|
|
|
197
193
|
event,
|
|
198
194
|
target,
|
|
199
195
|
options,
|
|
200
|
-
listener
|
|
201
|
-
handler
|
|
196
|
+
listener
|
|
202
197
|
]);
|
|
203
198
|
return () => {
|
|
204
199
|
(typeof target === "function" ? target() : target ?? document)?.removeEventListener(event, listener, options);
|
|
205
200
|
};
|
|
206
201
|
}
|
|
207
|
-
|
|
208
202
|
//#endregion
|
|
209
203
|
//#region src/hooks/useRefCallbackEvent.ts
|
|
210
204
|
function useRefCallbackEvent(event, handler, options, deps = []) {
|
|
@@ -222,18 +216,17 @@ function useRefCallbackEvent(event, handler, options, deps = []) {
|
|
|
222
216
|
};
|
|
223
217
|
}, deps);
|
|
224
218
|
}
|
|
225
|
-
|
|
226
219
|
//#endregion
|
|
227
220
|
//#region src/hooks/useDrag.ts
|
|
228
221
|
/**
|
|
229
|
-
* @category hooks
|
|
230
222
|
* @returns [refCallback, pointerDownHandler]
|
|
231
223
|
*/
|
|
232
|
-
function useDrag({ threshold = 1, onDrag, onDragStart, onDragEnd }) {
|
|
224
|
+
function useDrag({ threshold: _threshold = 1, onDrag, onDragStart, onDragEnd }) {
|
|
233
225
|
const dragOffsetX = (0, react.useRef)(void 0);
|
|
234
226
|
const dragOffsetY = (0, react.useRef)(void 0);
|
|
235
227
|
const dragStartX = (0, react.useRef)(0);
|
|
236
228
|
const dragStartY = (0, react.useRef)(0);
|
|
229
|
+
const threshold = Math.max(_threshold, 1);
|
|
237
230
|
const handleDrag = (0, react.useCallback)((event, first = false) => {
|
|
238
231
|
const isTouch = event instanceof TouchEvent;
|
|
239
232
|
if (isTouch && event.cancelable) event.preventDefault();
|
|
@@ -254,15 +247,15 @@ function useDrag({ threshold = 1, onDrag, onDragStart, onDragEnd }) {
|
|
|
254
247
|
dragOffsetY.current = screenY;
|
|
255
248
|
}
|
|
256
249
|
if (Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold) return;
|
|
257
|
-
onDrag(screenX - dragStartX.current, screenY - dragStartY.current, deltaX, deltaY);
|
|
250
|
+
onDrag?.(screenX - dragStartX.current, screenY - dragStartY.current, deltaX, deltaY);
|
|
258
251
|
}, [threshold, onDrag]);
|
|
259
|
-
const refHandler = useRefCallbackEvent("touchmove", handleDrag, { passive: false }, [onDrag]);
|
|
260
252
|
const pointerDownHandler = (0, react.useCallback)((event) => {
|
|
261
253
|
dragOffsetX.current = event.screenX;
|
|
262
254
|
dragOffsetY.current = event.screenY;
|
|
263
255
|
handleDrag(event, true);
|
|
264
256
|
onDragStart?.();
|
|
265
257
|
}, [handleDrag, onDragStart]);
|
|
258
|
+
const refHandler = useRefCallbackEvent("touchmove", handleDrag, { passive: false }, [onDrag]);
|
|
266
259
|
useEventListener(globalThis.window, "mousemove", handleDrag);
|
|
267
260
|
useEventListener(globalThis.window, "pointerup", () => {
|
|
268
261
|
dragOffsetX.current = void 0;
|
|
@@ -271,168 +264,10 @@ function useDrag({ threshold = 1, onDrag, onDragStart, onDragEnd }) {
|
|
|
271
264
|
});
|
|
272
265
|
return [refHandler, pointerDownHandler];
|
|
273
266
|
}
|
|
274
|
-
|
|
275
|
-
//#endregion
|
|
276
|
-
//#region src/components/_util/index.ts
|
|
277
|
-
function addNoSelect() {
|
|
278
|
-
document.body.classList.add("tremolo-no-select");
|
|
279
|
-
}
|
|
280
|
-
function removeNoSelect() {
|
|
281
|
-
document.body.classList.remove("tremolo-no-select");
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
//#endregion
|
|
285
|
-
//#region src/components/AnimationKnob/index.tsx
|
|
286
|
-
/**
|
|
287
|
-
* Animatable rotary knob.
|
|
288
|
-
* @category AnimationKnob
|
|
289
|
-
*/
|
|
290
|
-
const AnimationKnob = (0, react.forwardRef)(({ value, min, max, defaultValue = min, startValue = min, step = 1, skew = 1, size, width = 50, height = 50, activeColor = "#7998ec", inactiveColor = "#eee", bg = "#ccc", thumb = "#4e76e6", lineWeight = 6, thumbWeight = 4, draw, bodyNoSelect = true, wheel = ["raw", 1], keyboard = ["raw", 1], enableDoubleClickDefault = true, style, className, onChange,...props }, ref) => {
|
|
291
|
-
const valueRef = (0, react.useRef)(0);
|
|
292
|
-
const elmRef = (0, react.useRef)(null);
|
|
293
|
-
const isRelativeSize = typeof (size ?? width) == "string" || typeof height == "string";
|
|
294
|
-
const onDrag = (0, react.useCallback)((_x, y) => {
|
|
295
|
-
if (!onChange) return;
|
|
296
|
-
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));
|
|
297
|
-
}, [
|
|
298
|
-
max,
|
|
299
|
-
min,
|
|
300
|
-
onChange,
|
|
301
|
-
skew,
|
|
302
|
-
step
|
|
303
|
-
]);
|
|
304
|
-
const handleKeyDown = (0, react.useCallback)((event) => {
|
|
305
|
-
if (!keyboard || !onChange) return;
|
|
306
|
-
const key = event.key;
|
|
307
|
-
if ([
|
|
308
|
-
"ArrowRight",
|
|
309
|
-
"ArrowLeft",
|
|
310
|
-
"ArrowUp",
|
|
311
|
-
"ArrowDown"
|
|
312
|
-
].includes(key)) {
|
|
313
|
-
event.preventDefault();
|
|
314
|
-
const x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
|
|
315
|
-
let v;
|
|
316
|
-
if (keyboard[0] == "normalized") v = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
|
|
317
|
-
else v = value + x;
|
|
318
|
-
onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, step), min, max));
|
|
319
|
-
}
|
|
320
|
-
}, [
|
|
321
|
-
value,
|
|
322
|
-
min,
|
|
323
|
-
max,
|
|
324
|
-
step,
|
|
325
|
-
skew,
|
|
326
|
-
keyboard,
|
|
327
|
-
onChange
|
|
328
|
-
]);
|
|
329
|
-
const [touchMoveRefCallback, pointerDownHandler] = useDrag({
|
|
330
|
-
onDrag,
|
|
331
|
-
onDragStart: () => {
|
|
332
|
-
valueRef.current = (0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew);
|
|
333
|
-
if (bodyNoSelect) addNoSelect();
|
|
334
|
-
},
|
|
335
|
-
onDragEnd: () => {
|
|
336
|
-
if (bodyNoSelect) removeNoSelect();
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
|
|
340
|
-
if (!wheel) return;
|
|
341
|
-
event.preventDefault();
|
|
342
|
-
const x = event.deltaY > 0 ? -wheel[1] : wheel[1];
|
|
343
|
-
let v;
|
|
344
|
-
if (wheel[0] == "normalized") v = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
|
|
345
|
-
else v = value + x;
|
|
346
|
-
if (onChange) onChange((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(v, step), min, max));
|
|
347
|
-
}, { passive: false }, [
|
|
348
|
-
wheel,
|
|
349
|
-
value,
|
|
350
|
-
min,
|
|
351
|
-
max,
|
|
352
|
-
skew,
|
|
353
|
-
step,
|
|
354
|
-
onChange
|
|
355
|
-
]);
|
|
356
|
-
(0, react.useImperativeHandle)(ref, () => {
|
|
357
|
-
return {
|
|
358
|
-
focus() {
|
|
359
|
-
elmRef.current?.focus();
|
|
360
|
-
},
|
|
361
|
-
blur() {
|
|
362
|
-
elmRef.current?.blur();
|
|
363
|
-
}
|
|
364
|
-
};
|
|
365
|
-
}, []);
|
|
366
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
367
|
-
className: (0, clsx.default)("tremolo-animation-knob", className),
|
|
368
|
-
ref: (div) => {
|
|
369
|
-
elmRef.current = div;
|
|
370
|
-
wheelRefCallback(div);
|
|
371
|
-
touchMoveRefCallback(div);
|
|
372
|
-
},
|
|
373
|
-
role: "slider",
|
|
374
|
-
tabIndex: 0,
|
|
375
|
-
"aria-valuenow": value,
|
|
376
|
-
"aria-valuemax": max,
|
|
377
|
-
"aria-valuemin": min,
|
|
378
|
-
style: {
|
|
379
|
-
display: "inline-block",
|
|
380
|
-
width: size ?? width,
|
|
381
|
-
height: size ?? height,
|
|
382
|
-
cursor: "pointer",
|
|
383
|
-
WebkitTapHighlightColor: "transparent",
|
|
384
|
-
...style
|
|
385
|
-
},
|
|
386
|
-
onPointerDown: pointerDownHandler,
|
|
387
|
-
onDoubleClick: () => {
|
|
388
|
-
if (enableDoubleClickDefault && onChange) onChange(defaultValue);
|
|
389
|
-
},
|
|
390
|
-
onKeyDown: handleKeyDown,
|
|
391
|
-
...props,
|
|
392
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnimationCanvas, {
|
|
393
|
-
...isRelativeSize ? { relativeSize: true } : {
|
|
394
|
-
width: size ?? width,
|
|
395
|
-
height: size ?? height
|
|
396
|
-
},
|
|
397
|
-
draw: draw ? draw : (ctx, { width: width$1, height: height$1 }) => {
|
|
398
|
-
const p = (0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew);
|
|
399
|
-
const startP = (0, __tremolo_ui_functions.normalizeValue)(startValue, min, max, skew);
|
|
400
|
-
const cx = width$1 / 2;
|
|
401
|
-
const cy = height$1 / 2;
|
|
402
|
-
const r = Math.min(cx, cy);
|
|
403
|
-
if (r <= 0) return;
|
|
404
|
-
ctx.clearRect(0, 0, width$1, height$1);
|
|
405
|
-
ctx.lineCap = "butt";
|
|
406
|
-
ctx.beginPath();
|
|
407
|
-
ctx.fillStyle = bg;
|
|
408
|
-
ctx.arc(cx, cy, r - .5, 0, 2 * Math.PI);
|
|
409
|
-
ctx.fill();
|
|
410
|
-
ctx.lineWidth = lineWeight;
|
|
411
|
-
ctx.beginPath();
|
|
412
|
-
ctx.strokeStyle = inactiveColor;
|
|
413
|
-
ctx.arc(cx, cy, r - .5 - lineWeight / 2, (0, __tremolo_ui_functions.radian)(135), (0, __tremolo_ui_functions.radian)(45));
|
|
414
|
-
ctx.stroke();
|
|
415
|
-
ctx.beginPath();
|
|
416
|
-
ctx.strokeStyle = activeColor;
|
|
417
|
-
ctx.arc(cx, cy, r - .5 - lineWeight / 2, (0, __tremolo_ui_functions.radian)(135 + 270 * startP), (0, __tremolo_ui_functions.radian)(135 + 270 * p), p < startP);
|
|
418
|
-
ctx.stroke();
|
|
419
|
-
ctx.lineCap = "round";
|
|
420
|
-
ctx.beginPath();
|
|
421
|
-
ctx.moveTo(cx, cy);
|
|
422
|
-
ctx.lineTo(cx + Math.cos((0, __tremolo_ui_functions.radian)(135 + 270 * p)) * (r - .5 - thumbWeight / 2), cy + Math.sin((0, __tremolo_ui_functions.radian)(135 + 270 * p)) * (r - .5 - thumbWeight / 2));
|
|
423
|
-
ctx.strokeStyle = thumb;
|
|
424
|
-
ctx.lineWidth = thumbWeight;
|
|
425
|
-
ctx.stroke();
|
|
426
|
-
}
|
|
427
|
-
})
|
|
428
|
-
});
|
|
429
|
-
});
|
|
430
|
-
|
|
431
267
|
//#endregion
|
|
432
268
|
//#region src/components/DragObserver/index.tsx
|
|
433
|
-
/** @category DragObserver */
|
|
434
269
|
function DragObserver(props) {
|
|
435
|
-
const { as: Component = "div", threshold = 1,
|
|
270
|
+
const { as: Component = "div", threshold = 1, onDrag, onDragStart, onDragEnd, children, onPointerDown, ...attributes } = props;
|
|
436
271
|
const [refCallback, pointerDownHandler] = useDrag({
|
|
437
272
|
threshold,
|
|
438
273
|
onDrag,
|
|
@@ -441,190 +276,333 @@ function DragObserver(props) {
|
|
|
441
276
|
});
|
|
442
277
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
|
|
443
278
|
ref: refCallback,
|
|
444
|
-
onPointerDown:
|
|
279
|
+
onPointerDown: (e) => {
|
|
280
|
+
pointerDownHandler(e);
|
|
281
|
+
onPointerDown?.(e);
|
|
282
|
+
},
|
|
445
283
|
...attributes,
|
|
446
284
|
children
|
|
447
285
|
});
|
|
448
286
|
}
|
|
449
|
-
|
|
450
287
|
//#endregion
|
|
451
|
-
//#region src/components/
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
let newValue;
|
|
473
|
-
if (eventType == "normalized") newValue = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
|
|
474
|
-
else newValue = value + x;
|
|
475
|
-
return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(newValue, step), min, max);
|
|
476
|
-
}, [
|
|
477
|
-
max,
|
|
478
|
-
min,
|
|
479
|
-
skew,
|
|
480
|
-
step,
|
|
481
|
-
value
|
|
482
|
-
]);
|
|
483
|
-
const handleKeyDown = (0, react.useCallback)((event) => {
|
|
484
|
-
if (!keyboard || !onChange || readonly) return;
|
|
485
|
-
const key = event.key;
|
|
486
|
-
if ([
|
|
487
|
-
"ArrowRight",
|
|
488
|
-
"ArrowLeft",
|
|
489
|
-
"ArrowUp",
|
|
490
|
-
"ArrowDown"
|
|
491
|
-
].includes(key)) {
|
|
492
|
-
event.preventDefault();
|
|
493
|
-
const x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
|
|
494
|
-
onChange(updateValueByEvent(keyboard[0], x));
|
|
495
|
-
}
|
|
496
|
-
}, [
|
|
497
|
-
keyboard,
|
|
498
|
-
onChange,
|
|
499
|
-
readonly,
|
|
500
|
-
updateValueByEvent
|
|
501
|
-
]);
|
|
502
|
-
const [touchMoveRefCallback, pointerDownHandler] = useDrag({
|
|
503
|
-
onDrag,
|
|
504
|
-
onDragStart: () => {
|
|
505
|
-
valueRef.current = (0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew);
|
|
506
|
-
if (bodyNoSelect) addNoSelect();
|
|
507
|
-
},
|
|
508
|
-
onDragEnd: () => {
|
|
509
|
-
if (bodyNoSelect) removeNoSelect();
|
|
510
|
-
}
|
|
288
|
+
//#region src/components/_util/index.ts
|
|
289
|
+
function addUserSelectNone() {
|
|
290
|
+
window.document.body.classList.add("tremolo-user-select-none");
|
|
291
|
+
}
|
|
292
|
+
function removeUserSelectNone() {
|
|
293
|
+
window.document.body.classList.remove("tremolo-user-select-none");
|
|
294
|
+
}
|
|
295
|
+
const cursorStyles = {
|
|
296
|
+
grabbing: "tremolo-cursor-grabbing",
|
|
297
|
+
grab: "tremolo-cursor-grab",
|
|
298
|
+
pointer: "tremolo-cursor-pointer",
|
|
299
|
+
move: "tremolo-cursor-move",
|
|
300
|
+
none: "tremolo-cursor-none"
|
|
301
|
+
};
|
|
302
|
+
function setCursorStyle(type) {
|
|
303
|
+
resetCursorStyle();
|
|
304
|
+
window.document.body.classList.add(cursorStyles[type]);
|
|
305
|
+
}
|
|
306
|
+
function resetCursorStyle() {
|
|
307
|
+
Object.values(cursorStyles).forEach((s) => {
|
|
308
|
+
window.document.body.classList.remove(s);
|
|
511
309
|
});
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
const x = Math.sign(event.deltaY) * -wheel[1];
|
|
517
|
-
onChange(updateValueByEvent(wheel[0], x));
|
|
518
|
-
}, { passive: false }, [
|
|
519
|
-
wheel,
|
|
520
|
-
onChange,
|
|
521
|
-
readonly,
|
|
522
|
-
updateValueByEvent
|
|
523
|
-
]);
|
|
524
|
-
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
525
|
-
return {
|
|
526
|
-
focus() {
|
|
527
|
-
elmRef.current?.focus();
|
|
528
|
-
},
|
|
529
|
-
blur() {
|
|
530
|
-
elmRef.current?.blur();
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
}, []);
|
|
534
|
-
const viewBoxSize = 100;
|
|
535
|
-
const center = viewBoxSize / 2;
|
|
536
|
-
const padding = (viewBoxSize - (0, __tremolo_ui_functions.clamp)(thumbSize, 0, 100)) / 2;
|
|
537
|
-
const p = (0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew);
|
|
538
|
-
const s = (0, __tremolo_ui_functions.normalizeValue)(startValue, min, max, skew);
|
|
310
|
+
}
|
|
311
|
+
function calcDrawingState({ value, min, max, skew, startValue, angleRange }) {
|
|
312
|
+
const p = (0, _tremolo_ui_functions.normalizeValue)(value, min, max, skew);
|
|
313
|
+
const s = (0, _tremolo_ui_functions.normalizeValue)(startValue, min, max, skew);
|
|
539
314
|
const r1 = -angleRange / 2;
|
|
540
315
|
const r2 = r1 + Math.min(p, s) * angleRange;
|
|
541
316
|
const r3 = r1 + Math.max(p, s) * angleRange;
|
|
542
317
|
const r4 = angleRange / 2;
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
318
|
+
return {
|
|
319
|
+
p,
|
|
320
|
+
r1,
|
|
321
|
+
r2,
|
|
322
|
+
r3,
|
|
323
|
+
r4,
|
|
324
|
+
x1: 50 + 50 * Math.cos((0, _tremolo_ui_functions.radian)(r1 - 90)),
|
|
325
|
+
y1: 50 + 50 * Math.sin((0, _tremolo_ui_functions.radian)(r1 - 90)),
|
|
326
|
+
x2: 50 + 50 * Math.cos((0, _tremolo_ui_functions.radian)(r2 - 90)),
|
|
327
|
+
y2: 50 + 50 * Math.sin((0, _tremolo_ui_functions.radian)(r2 - 90)),
|
|
328
|
+
x3: 50 + 50 * Math.cos((0, _tremolo_ui_functions.radian)(r3 - 90)),
|
|
329
|
+
y3: 50 + 50 * Math.sin((0, _tremolo_ui_functions.radian)(r3 - 90)),
|
|
330
|
+
x4: 50 + 50 * Math.cos((0, _tremolo_ui_functions.radian)(r4 - 90)),
|
|
331
|
+
y4: 50 + 50 * Math.sin((0, _tremolo_ui_functions.radian)(r4 - 90))
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
const createKnobStore = (initProps) => {
|
|
335
|
+
return (0, zustand.createStore)()(() => ({
|
|
336
|
+
...initProps,
|
|
337
|
+
...calcDrawingState(initProps)
|
|
338
|
+
}));
|
|
339
|
+
};
|
|
340
|
+
const KnobContext = (0, react.createContext)(null);
|
|
341
|
+
function KnobProvider({ children, ...props }) {
|
|
342
|
+
const storeRef = (0, react.useRef)(null);
|
|
343
|
+
if (!storeRef.current) storeRef.current = createKnobStore(props);
|
|
344
|
+
(0, react.useEffect)(() => {
|
|
345
|
+
if (storeRef.current) storeRef.current.setState({
|
|
346
|
+
...props,
|
|
347
|
+
...calcDrawingState(props)
|
|
348
|
+
});
|
|
349
|
+
else storeRef.current = createKnobStore(props);
|
|
350
|
+
}, [props]);
|
|
351
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KnobContext.Provider, {
|
|
352
|
+
value: storeRef.current,
|
|
353
|
+
children
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
function useKnobContext(selector) {
|
|
357
|
+
const store = (0, react.useContext)(KnobContext);
|
|
358
|
+
if (!store) throw new Error("Missing KnobContext.Provider in the tree");
|
|
359
|
+
return (0, zustand.useStore)(store, selector);
|
|
360
|
+
}
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region src/components/Knob/ActiveLine.tsx
|
|
363
|
+
function ActiveLine({ stroke = "currentColor", strokeWidth = 6, className, ...props }) {
|
|
364
|
+
const r2 = useKnobContext((s) => s.r2);
|
|
365
|
+
const r3 = useKnobContext((s) => s.r3);
|
|
366
|
+
const x2 = useKnobContext((s) => s.x2);
|
|
367
|
+
const y2 = useKnobContext((s) => s.y2);
|
|
368
|
+
const x3 = useKnobContext((s) => s.x3);
|
|
369
|
+
const y3 = useKnobContext((s) => s.y3);
|
|
370
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
371
|
+
className: (0, clsx.default)("tremolo-knob-active-line", className),
|
|
372
|
+
d: `M ${x2} ${y2} A 50 50 -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3} ${y3}`,
|
|
373
|
+
fill: "none",
|
|
374
|
+
stroke,
|
|
375
|
+
strokeWidth,
|
|
376
|
+
...props
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
//#endregion
|
|
380
|
+
//#region src/components/Knob/InactiveLine.tsx
|
|
381
|
+
function InactiveLine({ stroke = "currentColor", strokeWidth = 6, className, ...props }) {
|
|
382
|
+
const min = useKnobContext((s) => s.min);
|
|
383
|
+
const max = useKnobContext((s) => s.max);
|
|
384
|
+
const startValue = useKnobContext((s) => s.startValue);
|
|
385
|
+
const r1 = useKnobContext((s) => s.r1);
|
|
386
|
+
const r2 = useKnobContext((s) => s.r2);
|
|
387
|
+
const r3 = useKnobContext((s) => s.r3);
|
|
388
|
+
const r4 = useKnobContext((s) => s.r4);
|
|
389
|
+
const x1 = useKnobContext((s) => s.x1);
|
|
390
|
+
const y1 = useKnobContext((s) => s.y1);
|
|
391
|
+
const x2 = useKnobContext((s) => s.x2);
|
|
392
|
+
const y2 = useKnobContext((s) => s.y2);
|
|
393
|
+
const x3 = useKnobContext((s) => s.x3);
|
|
394
|
+
const y3 = useKnobContext((s) => s.y3);
|
|
395
|
+
const x4 = useKnobContext((s) => s.x4);
|
|
396
|
+
const y4 = useKnobContext((s) => s.y4);
|
|
397
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [startValue > min && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
398
|
+
className: (0, clsx.default)("tremolo-knob-inactive-line", className),
|
|
399
|
+
d: `M ${x1} ${y1} A 50 50 -135 ${r2 - r1 > 180 ? 1 : 0} 1 ${x2} ${y2}`,
|
|
400
|
+
fill: "none",
|
|
401
|
+
stroke,
|
|
402
|
+
strokeWidth,
|
|
403
|
+
...props
|
|
404
|
+
}), startValue < max && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
405
|
+
className: (0, clsx.default)("tremolo-knob-inactive-line", className),
|
|
406
|
+
d: `M ${x3} ${y3} A 50 50 -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4} ${y4}`,
|
|
407
|
+
fill: "none",
|
|
408
|
+
stroke,
|
|
409
|
+
strokeWidth,
|
|
410
|
+
...props
|
|
411
|
+
})] });
|
|
412
|
+
}
|
|
413
|
+
//#endregion
|
|
414
|
+
//#region src/components/Knob/Thumb.tsx
|
|
415
|
+
function Thumb$2({ className, thumb = "currentColor", thumbLine = "currentColor", thumbSize = 84, thumbLineWeight = 6, thumbLineLength = 35, classes, ...props }) {
|
|
416
|
+
const angleRange = useKnobContext((s) => s.angleRange);
|
|
417
|
+
const p = useKnobContext((s) => s.p);
|
|
418
|
+
const r1 = useKnobContext((s) => s.r1);
|
|
551
419
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
420
|
+
className: (0, clsx.default)("tremolo-knob-thumb", classes?.thumb),
|
|
421
|
+
...props,
|
|
422
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
423
|
+
cx: "50%",
|
|
424
|
+
cy: "50%",
|
|
425
|
+
r: `${thumbSize / 2}%`,
|
|
426
|
+
fill: thumb
|
|
427
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
|
|
428
|
+
className: (0, clsx.default)("tremolo-knob-thumb-line", classes?.thumbLine),
|
|
429
|
+
x1: "50%",
|
|
430
|
+
y1: `${(100 - (0, _tremolo_ui_functions.clamp)(thumbSize, 0, 100)) / 2}%`,
|
|
431
|
+
x2: "50%",
|
|
432
|
+
y2: `${thumbLineLength}%`,
|
|
433
|
+
stroke: thumbLine,
|
|
434
|
+
strokeWidth: `${thumbLineWeight}%`,
|
|
435
|
+
style: {
|
|
436
|
+
transform: `rotate(${r1 + p * angleRange}deg)`,
|
|
437
|
+
transformOrigin: "50% 50%"
|
|
438
|
+
}
|
|
439
|
+
})]
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region src/components/Knob/SVGRoot.tsx
|
|
444
|
+
function SVGRoot({ children, style, ...props }) {
|
|
445
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
446
|
+
viewBox: `0 0 100 100`,
|
|
447
|
+
style: {
|
|
448
|
+
display: "block",
|
|
449
|
+
overflow: "visible",
|
|
450
|
+
...style
|
|
579
451
|
},
|
|
580
452
|
...props,
|
|
581
|
-
children: [
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
stroke: inactiveLine || "currentColor",
|
|
587
|
-
strokeWidth: lineWeight
|
|
588
|
-
}),
|
|
589
|
-
startValue < max && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
590
|
-
className: (0, clsx.default)("tremolo-knob-inactive-line", classes?.inactiveLine),
|
|
591
|
-
d: `M ${x3} ${y3} A ${center} ${center} -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4}, ${y4}`,
|
|
592
|
-
fill: "none",
|
|
593
|
-
stroke: inactiveLine || "currentColor",
|
|
594
|
-
strokeWidth: lineWeight
|
|
595
|
-
}),
|
|
596
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
597
|
-
className: (0, clsx.default)("tremolo-knob-active-line", classes?.activeLine),
|
|
598
|
-
d: `M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3}, ${y3}`,
|
|
599
|
-
fill: "none",
|
|
600
|
-
stroke: activeLine || "currentColor",
|
|
601
|
-
strokeWidth: lineWeight
|
|
602
|
-
}),
|
|
603
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
604
|
-
className: (0, clsx.default)("tremolo-knob-thumb", classes?.thumb),
|
|
605
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
606
|
-
cx: "50%",
|
|
607
|
-
cy: "50%",
|
|
608
|
-
r: `${thumbSize / 2}%`,
|
|
609
|
-
fill: thumb || "currentColor"
|
|
610
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
|
|
611
|
-
className: (0, clsx.default)("tremolo-knob-thumb-line", classes?.thumbLine),
|
|
612
|
-
x1: "50%",
|
|
613
|
-
y1: `${padding}%`,
|
|
614
|
-
x2: "50%",
|
|
615
|
-
y2: `${thumbLineLength}%`,
|
|
616
|
-
stroke: thumbLine || "currentColor",
|
|
617
|
-
strokeWidth: `${thumbLineWeight}%`,
|
|
618
|
-
style: {
|
|
619
|
-
transform: `rotate(${r1 + p * angleRange}deg)`,
|
|
620
|
-
transformOrigin: "50% 50%"
|
|
621
|
-
}
|
|
622
|
-
})]
|
|
623
|
-
})
|
|
624
|
-
]
|
|
453
|
+
children: children || /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
454
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InactiveLine, {}),
|
|
455
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActiveLine, {}),
|
|
456
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Thumb$2, {})
|
|
457
|
+
] })
|
|
625
458
|
});
|
|
626
|
-
}
|
|
627
|
-
|
|
459
|
+
}
|
|
460
|
+
//#endregion
|
|
461
|
+
//#region src/components/Knob/index.tsx
|
|
462
|
+
const defaultExternalStyles$3 = {
|
|
463
|
+
userSelectNone: true,
|
|
464
|
+
cursor: "grabbing"
|
|
465
|
+
};
|
|
466
|
+
/**
|
|
467
|
+
* Interactive rotary knob component implemented in SVG.
|
|
468
|
+
*/
|
|
469
|
+
const Knob = {
|
|
470
|
+
Root: (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, 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) => {
|
|
471
|
+
const [dragging, setDragging] = (0, react.useState)(false);
|
|
472
|
+
const valueRef = (0, react.useRef)(0);
|
|
473
|
+
const elmRef = (0, react.useRef)(null);
|
|
474
|
+
const externalStyles = {
|
|
475
|
+
...defaultExternalStyles$3,
|
|
476
|
+
..._externalStyles
|
|
477
|
+
};
|
|
478
|
+
const onDrag = (0, react.useCallback)((_x, y) => {
|
|
479
|
+
if (!onChange || readonly) return;
|
|
480
|
+
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));
|
|
481
|
+
}, [
|
|
482
|
+
max,
|
|
483
|
+
min,
|
|
484
|
+
onChange,
|
|
485
|
+
readonly,
|
|
486
|
+
skew,
|
|
487
|
+
step
|
|
488
|
+
]);
|
|
489
|
+
const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
|
|
490
|
+
let newValue;
|
|
491
|
+
if (eventType == "normalized") newValue = (0, _tremolo_ui_functions.rawValue)((0, _tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
|
|
492
|
+
else newValue = value + x;
|
|
493
|
+
return (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)(newValue, step), min, max);
|
|
494
|
+
}, [
|
|
495
|
+
max,
|
|
496
|
+
min,
|
|
497
|
+
skew,
|
|
498
|
+
step,
|
|
499
|
+
value
|
|
500
|
+
]);
|
|
501
|
+
const handleKeyDown = (0, react.useCallback)((event) => {
|
|
502
|
+
if (!keyboard || !onChange || readonly) return;
|
|
503
|
+
const key = event.key;
|
|
504
|
+
if ([
|
|
505
|
+
"ArrowRight",
|
|
506
|
+
"ArrowLeft",
|
|
507
|
+
"ArrowUp",
|
|
508
|
+
"ArrowDown"
|
|
509
|
+
].includes(key)) {
|
|
510
|
+
event.preventDefault();
|
|
511
|
+
const x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
|
|
512
|
+
onChange(updateValueByEvent(keyboard[0], x));
|
|
513
|
+
}
|
|
514
|
+
}, [
|
|
515
|
+
keyboard,
|
|
516
|
+
onChange,
|
|
517
|
+
readonly,
|
|
518
|
+
updateValueByEvent
|
|
519
|
+
]);
|
|
520
|
+
const [touchMoveRefCallback, pointerDownHandler] = useDrag({
|
|
521
|
+
onDrag,
|
|
522
|
+
onDragStart: () => {
|
|
523
|
+
setDragging(true);
|
|
524
|
+
valueRef.current = (0, _tremolo_ui_functions.normalizeValue)(value, min, max, skew);
|
|
525
|
+
if (externalStyles.userSelectNone) addUserSelectNone();
|
|
526
|
+
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
|
|
527
|
+
},
|
|
528
|
+
onDragEnd: () => {
|
|
529
|
+
setDragging(false);
|
|
530
|
+
if (externalStyles.userSelectNone) removeUserSelectNone();
|
|
531
|
+
if (externalStyles.cursor) resetCursorStyle();
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
|
|
535
|
+
if (!wheel || readonly) return;
|
|
536
|
+
event.preventDefault();
|
|
537
|
+
if (!onChange || event.deltaY == 0) return;
|
|
538
|
+
const x = Math.sign(event.deltaY) * -wheel[1];
|
|
539
|
+
onChange(updateValueByEvent(wheel[0], x));
|
|
540
|
+
}, { passive: false }, [
|
|
541
|
+
wheel,
|
|
542
|
+
onChange,
|
|
543
|
+
readonly,
|
|
544
|
+
updateValueByEvent
|
|
545
|
+
]);
|
|
546
|
+
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
547
|
+
return {
|
|
548
|
+
focus() {
|
|
549
|
+
elmRef.current?.focus();
|
|
550
|
+
},
|
|
551
|
+
blur() {
|
|
552
|
+
elmRef.current?.blur();
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
}, []);
|
|
556
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KnobProvider, {
|
|
557
|
+
value,
|
|
558
|
+
min,
|
|
559
|
+
max,
|
|
560
|
+
step,
|
|
561
|
+
skew,
|
|
562
|
+
startValue,
|
|
563
|
+
angleRange,
|
|
564
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
565
|
+
ref: (elm) => {
|
|
566
|
+
elmRef.current = elm;
|
|
567
|
+
wheelRefCallback(elm);
|
|
568
|
+
touchMoveRefCallback(elm);
|
|
569
|
+
},
|
|
570
|
+
className: (0, clsx.default)("tremolo-knob", className),
|
|
571
|
+
tabIndex: 0,
|
|
572
|
+
role: "slider",
|
|
573
|
+
"aria-valuenow": value,
|
|
574
|
+
"aria-valuemin": min,
|
|
575
|
+
"aria-valuemax": max,
|
|
576
|
+
"aria-disabled": disabled,
|
|
577
|
+
"aria-readonly": readonly,
|
|
578
|
+
"data-dragging": dragging,
|
|
579
|
+
style: {
|
|
580
|
+
width: size,
|
|
581
|
+
height: size,
|
|
582
|
+
...style
|
|
583
|
+
},
|
|
584
|
+
onPointerDown: (event) => {
|
|
585
|
+
pointerDownHandler(event);
|
|
586
|
+
onPointerDown?.(event);
|
|
587
|
+
},
|
|
588
|
+
onDoubleClick: (event) => {
|
|
589
|
+
if (enableDoubleClickDefault && onChange) onChange(defaultValue);
|
|
590
|
+
onDoubleClick?.(event);
|
|
591
|
+
},
|
|
592
|
+
onKeyDown: (event) => {
|
|
593
|
+
handleKeyDown(event);
|
|
594
|
+
onKeyDown?.(event);
|
|
595
|
+
},
|
|
596
|
+
...props,
|
|
597
|
+
children: children || /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SVGRoot, {})
|
|
598
|
+
})
|
|
599
|
+
});
|
|
600
|
+
}),
|
|
601
|
+
SVGRoot,
|
|
602
|
+
InactiveLine,
|
|
603
|
+
ActiveLine,
|
|
604
|
+
Thumb: Thumb$2
|
|
605
|
+
};
|
|
628
606
|
//#endregion
|
|
629
607
|
//#region src/components/NumberInput/type.ts
|
|
630
608
|
function selectUnit(units, value) {
|
|
@@ -635,8 +613,8 @@ function selectUnit(units, value) {
|
|
|
635
613
|
function parseValue(inputString, units, digit) {
|
|
636
614
|
const str = inputString.trim();
|
|
637
615
|
if (!units || typeof units == "string") {
|
|
638
|
-
const m
|
|
639
|
-
let v = Number(m
|
|
616
|
+
const m = str.match(/-?\d+(\.\d+)?/);
|
|
617
|
+
let v = Number(m?.[0] ?? "0");
|
|
640
618
|
v = isNaN(v) ? 0 : v;
|
|
641
619
|
return {
|
|
642
620
|
rawValue: v,
|
|
@@ -646,30 +624,29 @@ function parseValue(inputString, units, digit) {
|
|
|
646
624
|
}
|
|
647
625
|
const unitList = units.map(([u, _s]) => u);
|
|
648
626
|
const scaleList = units.map(([_u, s]) => s);
|
|
649
|
-
let rawValue
|
|
627
|
+
let rawValue = 0;
|
|
650
628
|
let unit = unitList[0];
|
|
651
|
-
let formatValue = (digit != void 0 ? rawValue
|
|
629
|
+
let formatValue = (digit != void 0 ? rawValue.toFixed(digit) : rawValue) + unit;
|
|
652
630
|
const m = str.match(/^(-?\d+(\.\d+)?)\s*(\w*)$/);
|
|
653
631
|
if (m) {
|
|
654
|
-
rawValue
|
|
632
|
+
rawValue = Number(m[1]) || 0;
|
|
655
633
|
const uIndex = unitList.indexOf(m[3]);
|
|
656
|
-
rawValue
|
|
657
|
-
const [u, scale] = selectUnit(units, rawValue
|
|
658
|
-
const v = rawValue
|
|
634
|
+
rawValue *= uIndex != -1 ? scaleList[uIndex] : 1;
|
|
635
|
+
const [u, scale] = selectUnit(units, rawValue);
|
|
636
|
+
const v = rawValue / scale;
|
|
659
637
|
formatValue = (digit != void 0 ? v.toFixed(digit) : v) + u;
|
|
660
638
|
unit = u;
|
|
661
639
|
}
|
|
662
640
|
return {
|
|
663
|
-
rawValue
|
|
641
|
+
rawValue,
|
|
664
642
|
formatValue,
|
|
665
643
|
unit
|
|
666
644
|
};
|
|
667
645
|
}
|
|
668
|
-
|
|
669
646
|
//#endregion
|
|
670
647
|
//#region src/components/NumberInput/context.tsx
|
|
671
648
|
function safeClamp(value, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
|
|
672
|
-
return (0,
|
|
649
|
+
return (0, _tremolo_ui_functions.clamp)(value, min, max);
|
|
673
650
|
}
|
|
674
651
|
const createNumberInputStore = (initProps) => {
|
|
675
652
|
const DEFAULT_PROPS = {
|
|
@@ -720,7 +697,7 @@ const createNumberInputStore = (initProps) => {
|
|
|
720
697
|
}));
|
|
721
698
|
};
|
|
722
699
|
const NumberInputContext = (0, react.createContext)(null);
|
|
723
|
-
function NumberInputProvider({ children
|
|
700
|
+
function NumberInputProvider({ children, ...props }) {
|
|
724
701
|
const storeRef = (0, react.useRef)(null);
|
|
725
702
|
if (!storeRef.current) storeRef.current = createNumberInputStore(props);
|
|
726
703
|
(0, react.useEffect)(() => {
|
|
@@ -738,18 +715,13 @@ function NumberInputProvider({ children,...props }) {
|
|
|
738
715
|
children
|
|
739
716
|
});
|
|
740
717
|
}
|
|
741
|
-
/** @category NumberInput */
|
|
742
718
|
function useNumberInputContext(selector) {
|
|
743
719
|
const store = (0, react.useContext)(NumberInputContext);
|
|
744
720
|
if (!store) throw new Error("Missing NumberInputContext.Provider in the tree");
|
|
745
721
|
return (0, zustand.useStore)(store, selector);
|
|
746
722
|
}
|
|
747
|
-
|
|
748
723
|
//#endregion
|
|
749
724
|
//#region src/hooks/useInterval.ts
|
|
750
|
-
/**
|
|
751
|
-
* @category hooks
|
|
752
|
-
*/
|
|
753
725
|
function useInterval(callback, delay) {
|
|
754
726
|
const savedCallback = (0, react.useRef)(callback);
|
|
755
727
|
(0, react.useEffect)(() => {
|
|
@@ -765,12 +737,8 @@ function useInterval(callback, delay) {
|
|
|
765
737
|
};
|
|
766
738
|
}, [delay]);
|
|
767
739
|
}
|
|
768
|
-
|
|
769
740
|
//#endregion
|
|
770
741
|
//#region src/hooks/useLongPress.ts
|
|
771
|
-
/**
|
|
772
|
-
* @category hooks
|
|
773
|
-
*/
|
|
774
742
|
function useLongPress(callback, initialDelay = 500, interval = 40) {
|
|
775
743
|
const [pressed, setPressed] = (0, react.useState)(false);
|
|
776
744
|
const [delay, setDelay] = (0, react.useState)(initialDelay);
|
|
@@ -787,11 +755,9 @@ function useLongPress(callback, initialDelay = 500, interval = 40) {
|
|
|
787
755
|
setPressed(true);
|
|
788
756
|
}, [callback]);
|
|
789
757
|
}
|
|
790
|
-
|
|
791
758
|
//#endregion
|
|
792
759
|
//#region src/components/NumberInput/DecrementStepper.tsx
|
|
793
|
-
|
|
794
|
-
function DecrementStepper({ size = 12, children, className,...props }) {
|
|
760
|
+
function DecrementStepper({ size = 12, children, className, ...props }) {
|
|
795
761
|
const min = useNumberInputContext((s) => s.min);
|
|
796
762
|
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
|
|
797
763
|
const readonly = useNumberInputContext((s) => s.readonly);
|
|
@@ -818,11 +784,9 @@ function DecrementStepper({ size = 12, children, className,...props }) {
|
|
|
818
784
|
})
|
|
819
785
|
});
|
|
820
786
|
}
|
|
821
|
-
|
|
822
787
|
//#endregion
|
|
823
788
|
//#region src/components/NumberInput/IncrementStepper.tsx
|
|
824
|
-
|
|
825
|
-
function IncrementStepper({ size = 12, children, className,...props }) {
|
|
789
|
+
function IncrementStepper({ size = 12, children, className, ...props }) {
|
|
826
790
|
const max = useNumberInputContext((s) => s.max);
|
|
827
791
|
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber);
|
|
828
792
|
const readonly = useNumberInputContext((s) => s.readonly);
|
|
@@ -849,10 +813,9 @@ function IncrementStepper({ size = 12, children, className,...props }) {
|
|
|
849
813
|
})
|
|
850
814
|
});
|
|
851
815
|
}
|
|
852
|
-
|
|
853
816
|
//#endregion
|
|
854
817
|
//#region src/components/NumberInput/InternalInput.tsx
|
|
855
|
-
const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFocus, blurOnEnter, clampValueOnBlur, wheel, keyboard, className, onChange, onFocus, onBlur, onKeyDown, children: _children
|
|
818
|
+
const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFocus, blurOnEnter, clampValueOnBlur, wheel, keyboard, className, onChange, onFocus, onBlur, onKeyDown, children: _children, ...props }, ref) => {
|
|
856
819
|
const elmRef = (0, react.useRef)(null);
|
|
857
820
|
const [editing, setEditing] = (0, react.useState)(false);
|
|
858
821
|
const value = useNumberInputContext((s) => s.value);
|
|
@@ -876,12 +839,12 @@ const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFoc
|
|
|
876
839
|
const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
|
|
877
840
|
let newValue;
|
|
878
841
|
if (eventType == "normalized") {
|
|
879
|
-
if (!min || !max) throw new Error("
|
|
880
|
-
newValue = (0,
|
|
881
|
-
return (0,
|
|
842
|
+
if (!min || !max) throw new Error("[NumberInput] \"min\" and \"max\" are required when InputEventOption[0] is set to \"normalized\".");
|
|
843
|
+
newValue = (0, _tremolo_ui_functions.rawValue)((0, _tremolo_ui_functions.normalizeValue)(valueAsNumber, min, max) + x, min, max);
|
|
844
|
+
return (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)(newValue, step), min, max);
|
|
882
845
|
} else {
|
|
883
846
|
newValue = valueAsNumber + x;
|
|
884
|
-
return (0,
|
|
847
|
+
return (0, _tremolo_ui_functions.stepValue)(newValue, step);
|
|
885
848
|
}
|
|
886
849
|
}, [
|
|
887
850
|
max,
|
|
@@ -946,8 +909,8 @@ const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFoc
|
|
|
946
909
|
const v = event.currentTarget.value;
|
|
947
910
|
const cursorPos = event.target.selectionStart;
|
|
948
911
|
change(v);
|
|
949
|
-
const valueAsNumber
|
|
950
|
-
onChange?.(valueAsNumber
|
|
912
|
+
const valueAsNumber = parseValue(v, units, digit).rawValue;
|
|
913
|
+
onChange?.(valueAsNumber, v);
|
|
951
914
|
if (cursorPos) {
|
|
952
915
|
event.target.selectionStart = cursorPos;
|
|
953
916
|
event.target.selectionEnd = cursorPos;
|
|
@@ -978,11 +941,9 @@ const InternalInput = (0, react.forwardRef)(({ disabled, readonly, selectWithFoc
|
|
|
978
941
|
...props
|
|
979
942
|
});
|
|
980
943
|
});
|
|
981
|
-
|
|
982
944
|
//#endregion
|
|
983
945
|
//#region src/components/NumberInput/Stepper.tsx
|
|
984
|
-
|
|
985
|
-
function Stepper({ dynamic = true, children, className,...props }) {
|
|
946
|
+
function Stepper({ dynamic = true, children, className, ...props }) {
|
|
986
947
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
987
948
|
className: (0, clsx.default)("tremolo-number-input-stepper", className),
|
|
988
949
|
"data-dynamic": dynamic,
|
|
@@ -990,62 +951,58 @@ function Stepper({ dynamic = true, children, className,...props }) {
|
|
|
990
951
|
children
|
|
991
952
|
});
|
|
992
953
|
}
|
|
993
|
-
|
|
994
|
-
//#endregion
|
|
995
|
-
//#region src/components/NumberInput/index.tsx
|
|
996
|
-
const NumberInputImpl = (0, react.forwardRef)(({ value, min, max, step = 1, units, readonly = false, disabled = false, digit, variant = "outline", selectWithFocus = "none", blurOnEnter = true, keepWithinRange = true, clampValueOnBlur = true, wheel = ["raw", 1], keyboard = ["raw", 1], activeColor, wrapperClassName, className, onChange, onFocus, onBlur, children,...props }, forwardedRef) => {
|
|
997
|
-
const colors = { "--active-color": activeColor };
|
|
998
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberInputProvider, {
|
|
999
|
-
value: String(value),
|
|
1000
|
-
min,
|
|
1001
|
-
max,
|
|
1002
|
-
step,
|
|
1003
|
-
units,
|
|
1004
|
-
digit,
|
|
1005
|
-
readonly,
|
|
1006
|
-
keepWithinRange,
|
|
1007
|
-
onChange,
|
|
1008
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1009
|
-
className: (0, clsx.default)("tremolo-number-input-wrapper", wrapperClassName),
|
|
1010
|
-
tabIndex: 0,
|
|
1011
|
-
style: { ...colors },
|
|
1012
|
-
"data-stepper": !!children,
|
|
1013
|
-
"data-variant": variant,
|
|
1014
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InternalInput, {
|
|
1015
|
-
ref: forwardedRef,
|
|
1016
|
-
readonly,
|
|
1017
|
-
disabled,
|
|
1018
|
-
selectWithFocus,
|
|
1019
|
-
blurOnEnter,
|
|
1020
|
-
clampValueOnBlur,
|
|
1021
|
-
wheel,
|
|
1022
|
-
keyboard,
|
|
1023
|
-
className,
|
|
1024
|
-
onChange,
|
|
1025
|
-
onFocus,
|
|
1026
|
-
onBlur,
|
|
1027
|
-
...props
|
|
1028
|
-
}), children]
|
|
1029
|
-
})
|
|
1030
|
-
});
|
|
1031
|
-
});
|
|
1032
954
|
/**
|
|
1033
955
|
* Input with some useful functions for entering numerical values.
|
|
1034
|
-
* @category NumberInput
|
|
1035
956
|
*/
|
|
1036
|
-
const NumberInput =
|
|
957
|
+
const NumberInput = {
|
|
958
|
+
Root: (0, react.forwardRef)(({ value, min, max, step = 1, units, readonly = false, disabled = false, digit, variant = "outline", selectWithFocus = "none", blurOnEnter = true, keepWithinRange = true, clampValueOnBlur = true, wheel = ["raw", 1], keyboard = ["raw", 1], activeColor, wrapperClassName, className, onChange, onFocus, onBlur, children, ...props }, forwardedRef) => {
|
|
959
|
+
const colors = { "--active-color": activeColor };
|
|
960
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberInputProvider, {
|
|
961
|
+
value: String(value),
|
|
962
|
+
min,
|
|
963
|
+
max,
|
|
964
|
+
step,
|
|
965
|
+
units,
|
|
966
|
+
digit,
|
|
967
|
+
readonly,
|
|
968
|
+
keepWithinRange,
|
|
969
|
+
onChange,
|
|
970
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
971
|
+
className: (0, clsx.default)("tremolo-number-input-wrapper", wrapperClassName),
|
|
972
|
+
tabIndex: 0,
|
|
973
|
+
style: { ...colors },
|
|
974
|
+
"data-stepper": !!children,
|
|
975
|
+
"data-variant": variant,
|
|
976
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InternalInput, {
|
|
977
|
+
ref: forwardedRef,
|
|
978
|
+
readonly,
|
|
979
|
+
disabled,
|
|
980
|
+
selectWithFocus,
|
|
981
|
+
blurOnEnter,
|
|
982
|
+
clampValueOnBlur,
|
|
983
|
+
wheel,
|
|
984
|
+
keyboard,
|
|
985
|
+
className,
|
|
986
|
+
onChange,
|
|
987
|
+
onFocus,
|
|
988
|
+
onBlur,
|
|
989
|
+
...props
|
|
990
|
+
}), children]
|
|
991
|
+
})
|
|
992
|
+
});
|
|
993
|
+
}),
|
|
1037
994
|
Stepper,
|
|
1038
995
|
IncrementStepper,
|
|
1039
996
|
DecrementStepper
|
|
1040
|
-
}
|
|
1041
|
-
|
|
997
|
+
};
|
|
1042
998
|
//#endregion
|
|
1043
|
-
//#region src/hooks/
|
|
999
|
+
//#region src/hooks/usePianoDrag.ts
|
|
1044
1000
|
/**
|
|
1045
|
-
*
|
|
1001
|
+
* Internal
|
|
1046
1002
|
* @returns [refCallback, pointerDownHandler]
|
|
1003
|
+
* @private
|
|
1047
1004
|
*/
|
|
1048
|
-
function
|
|
1005
|
+
function usePianoDrag({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
|
|
1049
1006
|
const dragged = (0, react.useRef)(false);
|
|
1050
1007
|
const normalizedX = (0, react.useRef)(0);
|
|
1051
1008
|
const normalizedY = (0, react.useRef)(0);
|
|
@@ -1056,8 +1013,8 @@ function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd })
|
|
|
1056
1013
|
const { left, top, right, bottom } = baseElementRef.current.getBoundingClientRect();
|
|
1057
1014
|
const mouseX = isTouch ? event.touches[0].clientX : event.clientX;
|
|
1058
1015
|
const mouseY = isTouch ? event.touches[0].clientY : event.clientY;
|
|
1059
|
-
const nx = (0,
|
|
1060
|
-
const ny = (0,
|
|
1016
|
+
const nx = (0, _tremolo_ui_functions.normalizeValue)(mouseX, left, right);
|
|
1017
|
+
const ny = (0, _tremolo_ui_functions.normalizeValue)(mouseY, top, bottom);
|
|
1061
1018
|
normalizedX.current = nx;
|
|
1062
1019
|
normalizedY.current = ny;
|
|
1063
1020
|
onDrag(nx, ny);
|
|
@@ -1074,9 +1031,11 @@ function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd })
|
|
|
1074
1031
|
dragged.current = false;
|
|
1075
1032
|
onDragEnd?.(normalizedX.current, normalizedY.current);
|
|
1076
1033
|
});
|
|
1077
|
-
return
|
|
1034
|
+
return {
|
|
1035
|
+
refHandler,
|
|
1036
|
+
pointerDownHandler
|
|
1037
|
+
};
|
|
1078
1038
|
}
|
|
1079
|
-
|
|
1080
1039
|
//#endregion
|
|
1081
1040
|
//#region src/components/Piano/context.tsx
|
|
1082
1041
|
const createPianoStore = (initProps) => {
|
|
@@ -1099,7 +1058,7 @@ const createPianoStore = (initProps) => {
|
|
|
1099
1058
|
}));
|
|
1100
1059
|
};
|
|
1101
1060
|
const PianoContext = (0, react.createContext)(null);
|
|
1102
|
-
function PianoProvider({ children
|
|
1061
|
+
function PianoProvider({ children, ...props }) {
|
|
1103
1062
|
const storeRef = (0, react.useRef)(null);
|
|
1104
1063
|
if (!storeRef.current) storeRef.current = createPianoStore(props);
|
|
1105
1064
|
(0, react.useEffect)(() => {
|
|
@@ -1111,17 +1070,14 @@ function PianoProvider({ children,...props }) {
|
|
|
1111
1070
|
children
|
|
1112
1071
|
});
|
|
1113
1072
|
}
|
|
1114
|
-
/** @category Piano */
|
|
1115
1073
|
function usePianoContext(selector) {
|
|
1116
1074
|
const store = (0, react.useContext)(PianoContext);
|
|
1117
1075
|
if (!store) throw new Error("Missing PianoContext.Provider in the tree");
|
|
1118
1076
|
return (0, zustand.useStore)(store, selector);
|
|
1119
1077
|
}
|
|
1120
|
-
|
|
1121
1078
|
//#endregion
|
|
1122
1079
|
//#region src/components/Piano/KeyLabel.tsx
|
|
1123
|
-
|
|
1124
|
-
function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __label,...props }) {
|
|
1080
|
+
function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __label, ...props }) {
|
|
1125
1081
|
const index = getNoteRangeArray(usePianoContext((s) => s.noteRange)).indexOf(__note);
|
|
1126
1082
|
const content = label ? label(__note, index) : __label && __label(__note, index);
|
|
1127
1083
|
return (content != void 0 || content != null) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -1134,14 +1090,7 @@ function KeyLabel({ label, className, wrapperClassName, wrapperStyle, __note, __
|
|
|
1134
1090
|
})
|
|
1135
1091
|
});
|
|
1136
1092
|
}
|
|
1137
|
-
|
|
1138
|
-
//#endregion
|
|
1139
|
-
//#region src/components/Piano/key.tsx
|
|
1140
|
-
/** @category Piano */
|
|
1141
|
-
const defaultWhiteKeyWidth = 40;
|
|
1142
|
-
/** @category Piano */
|
|
1143
|
-
const defaultBlackKeyWidth = 26;
|
|
1144
|
-
const KeyImpl = (0, react.forwardRef)(({ keyType, noteNumber, width, height, bg, color, activeBg, activeColor, className, style, children, __width,...props }, ref) => {
|
|
1093
|
+
const KeyImpl = (0, react.forwardRef)(({ keyType, noteNumber, width, height, bg, color, activeBg, activeColor, className, style, children, __width, ...props }, ref) => {
|
|
1145
1094
|
const [played, setPlayed] = (0, react.useState)(false);
|
|
1146
1095
|
const midiMax = usePianoContext((s) => s.midiMax);
|
|
1147
1096
|
const fill = usePianoContext((s) => s.fill);
|
|
@@ -1203,8 +1152,7 @@ const KeyImpl = (0, react.forwardRef)(({ keyType, noteNumber, width, height, bg,
|
|
|
1203
1152
|
})
|
|
1204
1153
|
});
|
|
1205
1154
|
});
|
|
1206
|
-
|
|
1207
|
-
const WhiteKey = (0, react.forwardRef)(({ width = defaultWhiteKeyWidth, height = "100%", onContextMenu = (e) => e.preventDefault(),...props }, ref) => {
|
|
1155
|
+
const WhiteKey = (0, react.forwardRef)(({ width = 40, height = "100%", onContextMenu = (e) => e.preventDefault(), ...props }, ref) => {
|
|
1208
1156
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyImpl, {
|
|
1209
1157
|
ref,
|
|
1210
1158
|
keyType: "white",
|
|
@@ -1214,8 +1162,7 @@ const WhiteKey = (0, react.forwardRef)(({ width = defaultWhiteKeyWidth, height =
|
|
|
1214
1162
|
...props
|
|
1215
1163
|
});
|
|
1216
1164
|
});
|
|
1217
|
-
|
|
1218
|
-
const BlackKey = (0, react.forwardRef)(({ width = defaultBlackKeyWidth, height = "60%", onContextMenu = (e) => e.preventDefault(),...props }, ref) => {
|
|
1165
|
+
const BlackKey = (0, react.forwardRef)(({ width = 26, height = "60%", onContextMenu = (e) => e.preventDefault(), ...props }, ref) => {
|
|
1219
1166
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyImpl, {
|
|
1220
1167
|
ref,
|
|
1221
1168
|
keyType: "black",
|
|
@@ -1225,10 +1172,8 @@ const BlackKey = (0, react.forwardRef)(({ width = defaultBlackKeyWidth, height =
|
|
|
1225
1172
|
...props
|
|
1226
1173
|
});
|
|
1227
1174
|
});
|
|
1228
|
-
|
|
1229
1175
|
//#endregion
|
|
1230
1176
|
//#region src/components/Piano/keyboardShortcuts.ts
|
|
1231
|
-
/** @category Piano */
|
|
1232
1177
|
const SHORTCUTS = { HOME_ROW: { keys: [
|
|
1233
1178
|
"a",
|
|
1234
1179
|
"w",
|
|
@@ -1248,208 +1193,406 @@ const SHORTCUTS = { HOME_ROW: { keys: [
|
|
|
1248
1193
|
"p",
|
|
1249
1194
|
";"
|
|
1250
1195
|
] } };
|
|
1251
|
-
|
|
1252
1196
|
//#endregion
|
|
1253
1197
|
//#region src/components/Piano/index.tsx
|
|
1254
1198
|
/**
|
|
1255
1199
|
* [noteRange.first, noteRange.first + 1 ..., noteRange.last]
|
|
1256
|
-
* @category Piano
|
|
1257
1200
|
*/
|
|
1258
1201
|
function getNoteRangeArray(noteRange) {
|
|
1259
1202
|
return Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
|
|
1260
1203
|
}
|
|
1261
|
-
const blackPerWhiteWidth =
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1204
|
+
const blackPerWhiteWidth = 26 / 40;
|
|
1205
|
+
/**
|
|
1206
|
+
* Customizable piano component.
|
|
1207
|
+
*/
|
|
1208
|
+
const Piano = {
|
|
1209
|
+
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) => {
|
|
1210
|
+
const [whiteNoteWidth, setWhiteNoteWidth] = (0, react.useState)(_whiteNoteWidth);
|
|
1211
|
+
const keyRefs = (0, react.useRef)([]);
|
|
1212
|
+
for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) keyRefs.current[i] = (0, react.createRef)();
|
|
1213
|
+
const pianoRef = (0, react.useRef)(null);
|
|
1214
|
+
const hitKeyIndex = (0, react.useRef)(-1);
|
|
1215
|
+
const noteRangeArray = getNoteRangeArray(noteRange);
|
|
1216
|
+
const whiteNotes = noteRangeArray.filter((v) => (0, _tremolo_ui_functions.isWhiteKey)(v));
|
|
1217
|
+
const blackNotes = noteRangeArray.filter((v) => (0, _tremolo_ui_functions.isBlackKey)(v));
|
|
1218
|
+
const whiteNoteCount = whiteNotes.length;
|
|
1219
|
+
const padding = 1;
|
|
1220
|
+
const staticWidth = (whiteNoteWidth + padding) * whiteNoteCount;
|
|
1221
|
+
const childrenWithProps = (0, react.useMemo)(() => {
|
|
1222
|
+
return children && react.default.Children.map(children, (child, index) => {
|
|
1223
|
+
if (react.default.isValidElement(child)) {
|
|
1224
|
+
const __width = child.type == WhiteKey ? whiteNoteWidth : whiteNoteWidth * .65;
|
|
1225
|
+
const props = {
|
|
1226
|
+
ref: keyRefs.current[index],
|
|
1227
|
+
__width
|
|
1228
|
+
};
|
|
1229
|
+
return react.default.cloneElement(child, props);
|
|
1230
|
+
}
|
|
1231
|
+
return child;
|
|
1232
|
+
});
|
|
1233
|
+
}, [children, whiteNoteWidth]);
|
|
1234
|
+
const notePosition = (0, react.useCallback)((note) => {
|
|
1235
|
+
const pitchPositions = {
|
|
1236
|
+
C: 0,
|
|
1237
|
+
"C#": 1,
|
|
1238
|
+
D: 1,
|
|
1239
|
+
"D#": 2,
|
|
1240
|
+
E: 2,
|
|
1241
|
+
F: 3,
|
|
1242
|
+
"F#": 4,
|
|
1243
|
+
G: 4,
|
|
1244
|
+
"G#": 5,
|
|
1245
|
+
A: 5,
|
|
1246
|
+
"A#": 6,
|
|
1247
|
+
B: 6
|
|
1248
|
+
};
|
|
1249
|
+
const blackNoteWidth = whiteNoteWidth * blackPerWhiteWidth;
|
|
1250
|
+
const padding = 1;
|
|
1251
|
+
const targetNoteKey = (0, _tremolo_ui_functions.noteKey)(note);
|
|
1252
|
+
const firstNoteKey = (0, _tremolo_ui_functions.noteKey)(noteRange.first);
|
|
1253
|
+
const octave = Math.floor((note - noteRange.first) / 12);
|
|
1254
|
+
const octaveOffset = _tremolo_ui_functions.noteKeys.indexOf(firstNoteKey) > _tremolo_ui_functions.noteKeys.indexOf(targetNoteKey) ? 1 : 0;
|
|
1255
|
+
const w = whiteNoteWidth + padding;
|
|
1256
|
+
const pos = pitchPositions[targetNoteKey] - pitchPositions[firstNoteKey];
|
|
1257
|
+
const blackKeyOffset = (0, _tremolo_ui_functions.isBlackKey)(note) ? blackNoteWidth / 2 : 0;
|
|
1258
|
+
return pos * w + (octave + octaveOffset) * 7 * w - blackKeyOffset;
|
|
1259
|
+
}, [noteRange.first, whiteNoteWidth]);
|
|
1260
|
+
const getHitKeyIndex = (0, react.useCallback)((x, y) => {
|
|
1261
|
+
if (!pianoRef.current) return -1;
|
|
1262
|
+
const containerHeight = pianoRef.current.clientHeight;
|
|
1263
|
+
const notes = [...blackNotes, ...whiteNotes];
|
|
1264
|
+
for (let i = 0; i < notes.length; i++) {
|
|
1265
|
+
const note = notes[i];
|
|
1266
|
+
const pos = notePosition(note);
|
|
1267
|
+
const w = (0, _tremolo_ui_functions.isWhiteKey)(note) ? whiteNoteWidth : whiteNoteWidth * blackPerWhiteWidth;
|
|
1268
|
+
const h = (0, _tremolo_ui_functions.isWhiteKey)(note) ? containerHeight : containerHeight * .6;
|
|
1269
|
+
if (pos <= x && x < pos + w && 0 <= y && y < h) return note;
|
|
1270
|
+
}
|
|
1271
|
+
return -1;
|
|
1272
|
+
}, [
|
|
1273
|
+
blackNotes,
|
|
1274
|
+
notePosition,
|
|
1275
|
+
whiteNoteWidth,
|
|
1276
|
+
whiteNotes
|
|
1277
|
+
]);
|
|
1278
|
+
const onDrag = (0, react.useCallback)((perX, perY) => {
|
|
1279
|
+
if (!pianoRef.current) return;
|
|
1280
|
+
const note = getHitKeyIndex(perX * staticWidth, perY * pianoRef.current.clientHeight);
|
|
1281
|
+
const index = noteRangeArray.indexOf(note);
|
|
1282
|
+
if (index == -1) return;
|
|
1283
|
+
if (hitKeyIndex.current != index) {
|
|
1284
|
+
keyRefs.current[hitKeyIndex.current]?.current?.stop();
|
|
1285
|
+
keyRefs.current[index]?.current?.play();
|
|
1286
|
+
hitKeyIndex.current = index;
|
|
1287
|
+
}
|
|
1288
|
+
}, [
|
|
1289
|
+
getHitKeyIndex,
|
|
1290
|
+
noteRangeArray,
|
|
1291
|
+
staticWidth
|
|
1292
|
+
]);
|
|
1293
|
+
const onDragEnd = (0, react.useCallback)(() => {
|
|
1294
|
+
if (keyRefs.current[hitKeyIndex.current]?.current?.played()) keyRefs.current[hitKeyIndex.current]?.current?.stop();
|
|
1295
|
+
hitKeyIndex.current = -1;
|
|
1296
|
+
}, [keyRefs]);
|
|
1297
|
+
(0, react.useEffect)(() => {
|
|
1298
|
+
if (fill && pianoRef.current) {
|
|
1299
|
+
const parent = pianoRef.current.parentElement;
|
|
1300
|
+
if (!parent) throw new Error("doesn't have a parent element.");
|
|
1301
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
1302
|
+
const w = pianoRef.current.clientWidth;
|
|
1303
|
+
setWhiteNoteWidth(w / whiteNoteCount - padding);
|
|
1304
|
+
});
|
|
1305
|
+
resizeObserver.observe(parent);
|
|
1306
|
+
return () => {
|
|
1307
|
+
resizeObserver.unobserve(parent);
|
|
1281
1308
|
};
|
|
1282
|
-
|
|
1309
|
+
} else setWhiteNoteWidth(_whiteNoteWidth);
|
|
1310
|
+
}, [
|
|
1311
|
+
fill,
|
|
1312
|
+
_whiteNoteWidth,
|
|
1313
|
+
whiteNoteCount
|
|
1314
|
+
]);
|
|
1315
|
+
const { refHandler: touchMoveRefCallback, pointerDownHandler } = usePianoDrag({
|
|
1316
|
+
baseElementRef: pianoRef,
|
|
1317
|
+
onDrag,
|
|
1318
|
+
onDragEnd
|
|
1319
|
+
});
|
|
1320
|
+
useEventListener(globalThis.window, "keydown", (e) => {
|
|
1321
|
+
if (e.repeat) return;
|
|
1322
|
+
if (!keyboardShortcuts) return;
|
|
1323
|
+
const index = keyboardShortcuts.keys.indexOf(e.key);
|
|
1324
|
+
if (index != -1) {
|
|
1325
|
+
if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play();
|
|
1283
1326
|
}
|
|
1284
|
-
return child;
|
|
1285
1327
|
});
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
return
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1328
|
+
useEventListener(globalThis.window, "keyup", (e) => {
|
|
1329
|
+
if (e.repeat) return;
|
|
1330
|
+
if (!keyboardShortcuts) return;
|
|
1331
|
+
const index = keyboardShortcuts.keys.indexOf(e.key);
|
|
1332
|
+
if (index != -1) keyRefs.current[index]?.current?.stop();
|
|
1333
|
+
});
|
|
1334
|
+
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
1335
|
+
return {
|
|
1336
|
+
playNote(note, velocity) {
|
|
1337
|
+
const index = noteRangeArray.indexOf(note);
|
|
1338
|
+
if (index != -1) {
|
|
1339
|
+
if (!keyRefs.current[index]?.current?.played()) keyRefs.current[index]?.current?.play(velocity);
|
|
1340
|
+
} else onPlayNote?.(note, velocity);
|
|
1341
|
+
},
|
|
1342
|
+
stopNote(note) {
|
|
1343
|
+
const index = noteRangeArray.indexOf(note);
|
|
1344
|
+
if (index != -1) keyRefs.current[index]?.current?.stop();
|
|
1345
|
+
else onStopNote?.(note);
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
}, [
|
|
1349
|
+
noteRangeArray,
|
|
1350
|
+
onPlayNote,
|
|
1351
|
+
onStopNote
|
|
1352
|
+
]);
|
|
1353
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PianoProvider, {
|
|
1354
|
+
notePosition,
|
|
1355
|
+
noteRange,
|
|
1356
|
+
glissando,
|
|
1357
|
+
midiMax,
|
|
1358
|
+
fill,
|
|
1359
|
+
onPlayNote,
|
|
1360
|
+
onStopNote,
|
|
1361
|
+
label,
|
|
1362
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1363
|
+
ref: (div) => {
|
|
1364
|
+
pianoRef.current = div;
|
|
1365
|
+
touchMoveRefCallback(div);
|
|
1366
|
+
},
|
|
1367
|
+
className: (0, clsx.default)("tremolo-piano", className),
|
|
1368
|
+
style: {
|
|
1369
|
+
width: fill ? "100%" : staticWidth,
|
|
1370
|
+
height,
|
|
1371
|
+
...style
|
|
1372
|
+
},
|
|
1373
|
+
onPointerDown: (event) => {
|
|
1374
|
+
pointerDownHandler(event);
|
|
1375
|
+
onPointerDown?.(event);
|
|
1376
|
+
},
|
|
1377
|
+
...props,
|
|
1378
|
+
children: childrenWithProps || noteRangeArray.map((note, index) => (0, _tremolo_ui_functions.isWhiteKey)(note) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WhiteKey, {
|
|
1379
|
+
ref: keyRefs.current[index],
|
|
1380
|
+
noteNumber: note,
|
|
1381
|
+
__width: whiteNoteWidth
|
|
1382
|
+
}, note) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlackKey, {
|
|
1383
|
+
ref: keyRefs.current[index],
|
|
1384
|
+
noteNumber: note,
|
|
1385
|
+
__width: whiteNoteWidth * blackPerWhiteWidth
|
|
1386
|
+
}, note))
|
|
1387
|
+
})
|
|
1388
|
+
});
|
|
1389
|
+
}),
|
|
1390
|
+
WhiteKey,
|
|
1391
|
+
BlackKey,
|
|
1392
|
+
KeyLabel
|
|
1393
|
+
};
|
|
1394
|
+
//#endregion
|
|
1395
|
+
//#region src/components/PointsEditor/Background.tsx
|
|
1396
|
+
function Background({ className, children, ...props }) {
|
|
1397
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1398
|
+
className: (0, clsx.default)("tremolo-points-editor-background", className),
|
|
1399
|
+
...props,
|
|
1400
|
+
children
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
//#endregion
|
|
1404
|
+
//#region src/components/PointsEditor/context.tsx
|
|
1405
|
+
const createPointsEditorStore = (initProps) => {
|
|
1406
|
+
const DEFAULT_PROPS = {
|
|
1407
|
+
containerElementRef: (0, react.createRef)(),
|
|
1408
|
+
disabled: false,
|
|
1409
|
+
readonly: false,
|
|
1410
|
+
externalStyles: {
|
|
1411
|
+
userSelectNone: true,
|
|
1412
|
+
cursor: "grabbing"
|
|
1323
1413
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
]);
|
|
1331
|
-
const onDrag = (0, react.useCallback)((perX, perY) => {
|
|
1332
|
-
if (!pianoRef.current) return;
|
|
1333
|
-
const note = getHitKeyIndex(perX * staticWidth, perY * pianoRef.current.clientHeight);
|
|
1334
|
-
const index = noteRangeArray.indexOf(note);
|
|
1335
|
-
if (index == -1) return;
|
|
1336
|
-
if (hitKeyIndex.current != index) {
|
|
1337
|
-
keyRefs.current[hitKeyIndex.current]?.current?.stop();
|
|
1338
|
-
keyRefs.current[index]?.current?.play();
|
|
1339
|
-
hitKeyIndex.current = index;
|
|
1414
|
+
};
|
|
1415
|
+
return (0, zustand.createStore)()((set) => ({
|
|
1416
|
+
...DEFAULT_PROPS,
|
|
1417
|
+
...initProps,
|
|
1418
|
+
setContainerElementRef: (containerElementRef) => {
|
|
1419
|
+
set(() => ({ containerElementRef }));
|
|
1340
1420
|
}
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
if (keyRefs.current[hitKeyIndex.current]?.current?.played()) keyRefs.current[hitKeyIndex.current]?.current?.stop();
|
|
1348
|
-
hitKeyIndex.current = -1;
|
|
1349
|
-
}, [keyRefs]);
|
|
1421
|
+
}));
|
|
1422
|
+
};
|
|
1423
|
+
const PointsEditorContext = (0, react.createContext)(null);
|
|
1424
|
+
function PointsEditorProvider({ children, ...props }) {
|
|
1425
|
+
const storeRef = (0, react.useRef)(null);
|
|
1426
|
+
if (!storeRef.current) storeRef.current = createPointsEditorStore(props);
|
|
1350
1427
|
(0, react.useEffect)(() => {
|
|
1351
|
-
if (
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1428
|
+
if (storeRef.current) storeRef.current.setState(props);
|
|
1429
|
+
else storeRef.current = createPointsEditorStore(props);
|
|
1430
|
+
}, [props]);
|
|
1431
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PointsEditorContext.Provider, {
|
|
1432
|
+
value: storeRef.current,
|
|
1433
|
+
children
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
function usePointsEditorContext(selector) {
|
|
1437
|
+
const store = (0, react.useContext)(PointsEditorContext);
|
|
1438
|
+
if (!store) throw new Error("Missing PointsEditorContext.Provider in the tree");
|
|
1439
|
+
return (0, zustand.useStore)(store, selector);
|
|
1440
|
+
}
|
|
1441
|
+
//#endregion
|
|
1442
|
+
//#region src/components/PointsEditor/Container.tsx
|
|
1443
|
+
function Container({ className, children, ...props }) {
|
|
1444
|
+
const containerElementRef = (0, react.useRef)(null);
|
|
1445
|
+
usePointsEditorContext((s) => s.setContainerElementRef)(containerElementRef);
|
|
1446
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1447
|
+
ref: containerElementRef,
|
|
1448
|
+
className: (0, clsx.default)("tremolo-points-editor-container", className),
|
|
1449
|
+
...props,
|
|
1450
|
+
children
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
//#endregion
|
|
1454
|
+
//#region src/hooks/useDragWithElement.ts
|
|
1455
|
+
function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd }) {
|
|
1456
|
+
const [dragging, setDragging] = (0, react.useState)(false);
|
|
1457
|
+
const normalizedX = (0, react.useRef)(0);
|
|
1458
|
+
const normalizedY = (0, react.useRef)(0);
|
|
1459
|
+
const handleDrag = (0, react.useCallback)((event) => {
|
|
1460
|
+
if (!baseElementRef.current || !dragging) return;
|
|
1461
|
+
const isTouch = event instanceof TouchEvent;
|
|
1462
|
+
if (isTouch && event.cancelable) event.preventDefault();
|
|
1463
|
+
const { left, top, right, bottom } = baseElementRef.current.getBoundingClientRect();
|
|
1464
|
+
const mouseX = isTouch ? event.touches[0].clientX : event.clientX;
|
|
1465
|
+
const mouseY = isTouch ? event.touches[0].clientY : event.clientY;
|
|
1466
|
+
const nx = (0, _tremolo_ui_functions.normalizeValue)(mouseX, left, right);
|
|
1467
|
+
const ny = (0, _tremolo_ui_functions.normalizeValue)(mouseY, top, bottom);
|
|
1468
|
+
normalizedX.current = nx;
|
|
1469
|
+
normalizedY.current = ny;
|
|
1470
|
+
onDrag(nx, ny);
|
|
1363
1471
|
}, [
|
|
1364
|
-
fill,
|
|
1365
|
-
_whiteNoteWidth,
|
|
1366
|
-
whiteNoteCount
|
|
1367
|
-
]);
|
|
1368
|
-
const [touchMoveRefCallback, pointerDownHandler] = useDragWithElement({
|
|
1369
|
-
baseElementRef: pianoRef,
|
|
1370
1472
|
onDrag,
|
|
1371
|
-
|
|
1473
|
+
baseElementRef,
|
|
1474
|
+
dragging
|
|
1475
|
+
]);
|
|
1476
|
+
const refHandler = useRefCallbackEvent("touchmove", handleDrag, { passive: false }, [onDrag]);
|
|
1477
|
+
const pointerDownHandler = (0, react.useCallback)((event) => {
|
|
1478
|
+
setDragging(true);
|
|
1479
|
+
handleDrag(event);
|
|
1480
|
+
onDragStart?.(normalizedX.current, normalizedY.current);
|
|
1481
|
+
}, [handleDrag, onDragStart]);
|
|
1482
|
+
useEventListener(globalThis.window, "pointermove", handleDrag);
|
|
1483
|
+
useEventListener(globalThis.window, "pointerup", () => {
|
|
1484
|
+
if (!dragging) return;
|
|
1485
|
+
setDragging(false);
|
|
1486
|
+
onDragEnd?.(normalizedX.current, normalizedY.current);
|
|
1372
1487
|
});
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1488
|
+
return {
|
|
1489
|
+
refHandler,
|
|
1490
|
+
pointerDownHandler,
|
|
1491
|
+
dragging
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
//#endregion
|
|
1495
|
+
//#region src/components/PointsEditor/Point.tsx
|
|
1496
|
+
function clampPoint(point, min, max) {
|
|
1497
|
+
const { x, y } = point;
|
|
1498
|
+
return {
|
|
1499
|
+
x: (0, _tremolo_ui_functions.clamp)(x, min?.x ?? 0, max?.x ?? 1),
|
|
1500
|
+
y: (0, _tremolo_ui_functions.clamp)(y, min?.y ?? 0, max?.y ?? 1)
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
function Point({ value, min, max, size, width = 16, height = 16, color, disabled, readonly, onChange, onDragStart, onDragEnd, className, style, onPointerDown, ...props }) {
|
|
1504
|
+
const containerElementRef = usePointsEditorContext((s) => s.containerElementRef);
|
|
1505
|
+
const externalStyles = usePointsEditorContext((s) => s.externalStyles);
|
|
1506
|
+
const __disabled = usePointsEditorContext((s) => s.disabled);
|
|
1507
|
+
const __readonly = usePointsEditorContext((s) => s.readonly);
|
|
1508
|
+
const { refHandler: touchMoveRefCallback, pointerDownHandler, dragging } = useDragWithElement({
|
|
1509
|
+
baseElementRef: containerElementRef,
|
|
1510
|
+
onDrag: (x, y) => {
|
|
1511
|
+
if (readonly) return;
|
|
1512
|
+
onChange?.(clampPoint({
|
|
1513
|
+
x,
|
|
1514
|
+
y
|
|
1515
|
+
}, min, max));
|
|
1516
|
+
},
|
|
1517
|
+
onDragStart: (x, y) => {
|
|
1518
|
+
if (readonly) return;
|
|
1519
|
+
if (externalStyles.userSelectNone) addUserSelectNone();
|
|
1520
|
+
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
|
|
1521
|
+
onDragStart?.(clampPoint({
|
|
1522
|
+
x,
|
|
1523
|
+
y
|
|
1524
|
+
}, min, max));
|
|
1525
|
+
},
|
|
1526
|
+
onDragEnd: (x, y) => {
|
|
1527
|
+
if (readonly) return;
|
|
1528
|
+
if (externalStyles.userSelectNone) removeUserSelectNone();
|
|
1529
|
+
if (externalStyles.cursor) resetCursorStyle();
|
|
1530
|
+
onDragEnd?.(clampPoint({
|
|
1531
|
+
x,
|
|
1532
|
+
y
|
|
1533
|
+
}, min, max));
|
|
1379
1534
|
}
|
|
1380
1535
|
});
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
onStopNote
|
|
1405
|
-
]);
|
|
1406
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PianoProvider, {
|
|
1407
|
-
notePosition,
|
|
1408
|
-
noteRange,
|
|
1409
|
-
glissando,
|
|
1410
|
-
midiMax,
|
|
1411
|
-
fill,
|
|
1412
|
-
onPlayNote,
|
|
1413
|
-
onStopNote,
|
|
1414
|
-
label,
|
|
1415
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1416
|
-
ref: (div) => {
|
|
1417
|
-
pianoRef.current = div;
|
|
1418
|
-
touchMoveRefCallback(div);
|
|
1419
|
-
},
|
|
1420
|
-
className: (0, clsx.default)("tremolo-piano", className),
|
|
1421
|
-
style: {
|
|
1422
|
-
width: fill ? "100%" : staticWidth,
|
|
1423
|
-
height,
|
|
1424
|
-
...style
|
|
1425
|
-
},
|
|
1426
|
-
onPointerDown: (event) => {
|
|
1427
|
-
pointerDownHandler(event);
|
|
1428
|
-
onPointerDown?.(event);
|
|
1429
|
-
},
|
|
1430
|
-
...props,
|
|
1431
|
-
children: childrenWithProps || noteRangeArray.map((note, index) => (0, __tremolo_ui_functions.isWhiteKey)(note) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WhiteKey, {
|
|
1432
|
-
ref: keyRefs.current[index],
|
|
1433
|
-
noteNumber: note,
|
|
1434
|
-
__width: whiteNoteWidth
|
|
1435
|
-
}, note) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlackKey, {
|
|
1436
|
-
ref: keyRefs.current[index],
|
|
1437
|
-
noteNumber: note,
|
|
1438
|
-
__width: whiteNoteWidth * blackPerWhiteWidth
|
|
1439
|
-
}, note))
|
|
1440
|
-
})
|
|
1536
|
+
const colors = { "--color": color };
|
|
1537
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1538
|
+
ref: (div) => {
|
|
1539
|
+
touchMoveRefCallback(div);
|
|
1540
|
+
},
|
|
1541
|
+
className: (0, clsx.default)("tremolo-points-editor-point", className),
|
|
1542
|
+
tabIndex: 0,
|
|
1543
|
+
"aria-disabled": disabled != void 0 ? disabled : __disabled,
|
|
1544
|
+
"aria-readonly": readonly != void 0 ? readonly : __readonly,
|
|
1545
|
+
"data-dragging": dragging,
|
|
1546
|
+
style: {
|
|
1547
|
+
...colors,
|
|
1548
|
+
width: size ?? width,
|
|
1549
|
+
height: size ?? height,
|
|
1550
|
+
left: `${value.x * 100}%`,
|
|
1551
|
+
top: `${value.y * 100}%`,
|
|
1552
|
+
...style
|
|
1553
|
+
},
|
|
1554
|
+
onPointerDown: (event) => {
|
|
1555
|
+
pointerDownHandler(event);
|
|
1556
|
+
onPointerDown?.(event);
|
|
1557
|
+
},
|
|
1558
|
+
...props
|
|
1441
1559
|
});
|
|
1442
|
-
}
|
|
1560
|
+
}
|
|
1561
|
+
//#endregion
|
|
1562
|
+
//#region src/components/PointsEditor/index.tsx
|
|
1563
|
+
const defaultExternalStyles$2 = {
|
|
1564
|
+
userSelectNone: true,
|
|
1565
|
+
cursor: "grabbing"
|
|
1566
|
+
};
|
|
1443
1567
|
/**
|
|
1444
|
-
*
|
|
1445
|
-
* @category Piano
|
|
1568
|
+
* Multiple Point Controller
|
|
1446
1569
|
*/
|
|
1447
|
-
const
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1570
|
+
const PointsEditor = {
|
|
1571
|
+
Root: (0, react.forwardRef)(({ width = 200, height = 100, disabled = false, readonly = false, externalStyles: _externalStyles, style, className, children, ...props }, forwardedRef) => {
|
|
1572
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PointsEditorProvider, {
|
|
1573
|
+
disabled,
|
|
1574
|
+
readonly,
|
|
1575
|
+
externalStyles: {
|
|
1576
|
+
...defaultExternalStyles$2,
|
|
1577
|
+
..._externalStyles
|
|
1578
|
+
},
|
|
1579
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1580
|
+
ref: forwardedRef,
|
|
1581
|
+
className: (0, clsx.default)("tremolo-points-editor", className),
|
|
1582
|
+
style: {
|
|
1583
|
+
width,
|
|
1584
|
+
height,
|
|
1585
|
+
...style
|
|
1586
|
+
},
|
|
1587
|
+
...props,
|
|
1588
|
+
children
|
|
1589
|
+
})
|
|
1590
|
+
});
|
|
1591
|
+
}),
|
|
1592
|
+
Background,
|
|
1593
|
+
Container,
|
|
1594
|
+
Point
|
|
1595
|
+
};
|
|
1453
1596
|
//#endregion
|
|
1454
1597
|
//#region src/components/Slider/context.tsx
|
|
1455
1598
|
const createSliderStore = (initProps) => {
|
|
@@ -1469,7 +1612,7 @@ const createSliderStore = (initProps) => {
|
|
|
1469
1612
|
}));
|
|
1470
1613
|
};
|
|
1471
1614
|
const SliderContext = (0, react.createContext)(null);
|
|
1472
|
-
function SliderProvider({ children
|
|
1615
|
+
function SliderProvider({ children, ...props }) {
|
|
1473
1616
|
const storeRef = (0, react.useRef)(null);
|
|
1474
1617
|
if (!storeRef.current) storeRef.current = createSliderStore(props);
|
|
1475
1618
|
(0, react.useEffect)(() => {
|
|
@@ -1481,24 +1624,21 @@ function SliderProvider({ children,...props }) {
|
|
|
1481
1624
|
children
|
|
1482
1625
|
});
|
|
1483
1626
|
}
|
|
1484
|
-
/** @category Slider */
|
|
1485
1627
|
function useSliderContext(selector) {
|
|
1486
1628
|
const store = (0, react.useContext)(SliderContext);
|
|
1487
1629
|
if (!store) throw new Error("Missing SliderContext.Provider in the tree");
|
|
1488
1630
|
return (0, zustand.useStore)(store, selector);
|
|
1489
1631
|
}
|
|
1490
|
-
|
|
1491
1632
|
//#endregion
|
|
1492
1633
|
//#region src/components/Slider/ScaleOption.tsx
|
|
1493
|
-
|
|
1494
|
-
function ScaleOption({ value, type = "mark-number", label, thickness = 1, length = "0.5rem", gap = 2, labelWidth, classes, styles, className, style,...props }) {
|
|
1634
|
+
function ScaleOption({ value, type = "mark-number", label, thickness = 1, length = "0.5rem", gap = 2, labelWidth, classes, styles, className, style, ...props }) {
|
|
1495
1635
|
const min = useSliderContext((s) => s.min);
|
|
1496
1636
|
const max = useSliderContext((s) => s.max);
|
|
1497
1637
|
const skew = useSliderContext((s) => s.skew);
|
|
1498
1638
|
const vertical = useSliderContext((s) => s.vertical);
|
|
1499
1639
|
const reverse = useSliderContext((s) => s.reverse);
|
|
1500
|
-
const calcPercent = (0, react.useCallback)((rawValue
|
|
1501
|
-
return (0,
|
|
1640
|
+
const calcPercent = (0, react.useCallback)((rawValue) => {
|
|
1641
|
+
return (0, _tremolo_ui_functions.toFixed)((0, _tremolo_ui_functions.xor)(vertical, reverse) ? 100 - (0, _tremolo_ui_functions.normalizeValue)(rawValue, min, max, skew) * 100 : (0, _tremolo_ui_functions.normalizeValue)(rawValue, min, max, skew) * 100);
|
|
1502
1642
|
}, [
|
|
1503
1643
|
vertical,
|
|
1504
1644
|
reverse,
|
|
@@ -1535,7 +1675,6 @@ function ScaleOption({ value, type = "mark-number", label, thickness = 1, length
|
|
|
1535
1675
|
})]
|
|
1536
1676
|
});
|
|
1537
1677
|
}
|
|
1538
|
-
|
|
1539
1678
|
//#endregion
|
|
1540
1679
|
//#region src/components/Slider/type.ts
|
|
1541
1680
|
function generateOptionsList(options, min, max, step) {
|
|
@@ -1543,7 +1682,7 @@ function generateOptionsList(options, min, max, step) {
|
|
|
1543
1682
|
const per = options[0] == "step" ? step : options[0];
|
|
1544
1683
|
const count = Math.floor(max / per) - Math.ceil(min / per) + 1;
|
|
1545
1684
|
for (let i = 0; i < count; i++) {
|
|
1546
|
-
const value = (0,
|
|
1685
|
+
const value = (0, _tremolo_ui_functions.toFixed)(per * (Math.ceil(min / per) + i), (0, _tremolo_ui_functions.decimalPart)(per)?.length);
|
|
1547
1686
|
optionsList.push({
|
|
1548
1687
|
value,
|
|
1549
1688
|
type: options[1]
|
|
@@ -1551,18 +1690,16 @@ function generateOptionsList(options, min, max, step) {
|
|
|
1551
1690
|
}
|
|
1552
1691
|
return optionsList;
|
|
1553
1692
|
}
|
|
1554
|
-
|
|
1555
1693
|
//#endregion
|
|
1556
1694
|
//#region src/components/Slider/Scale.tsx
|
|
1557
|
-
|
|
1558
|
-
function Scale({ gap = 6, options, children, className, style,...props }) {
|
|
1695
|
+
function Scale({ gap = 6, options, children, className, style, ...props }) {
|
|
1559
1696
|
const min = useSliderContext((s) => s.min);
|
|
1560
1697
|
const max = useSliderContext((s) => s.max);
|
|
1561
1698
|
const step = useSliderContext((s) => s.step);
|
|
1562
1699
|
const vertical = useSliderContext((s) => s.vertical);
|
|
1563
1700
|
const reverse = useSliderContext((s) => s.reverse);
|
|
1564
1701
|
const optionsList = options ? generateOptionsList(options, min, max, step) : [];
|
|
1565
|
-
if ((0,
|
|
1702
|
+
if ((0, _tremolo_ui_functions.xor)(vertical, reverse)) optionsList.reverse();
|
|
1566
1703
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1567
1704
|
className: (0, clsx.default)("tremolo-slider-scale", className),
|
|
1568
1705
|
style: {
|
|
@@ -1579,13 +1716,7 @@ function Scale({ gap = 6, options, children, className, style,...props }) {
|
|
|
1579
1716
|
}) : children
|
|
1580
1717
|
});
|
|
1581
1718
|
}
|
|
1582
|
-
|
|
1583
|
-
//#endregion
|
|
1584
|
-
//#region src/components/Slider/Thumb.tsx
|
|
1585
|
-
/** @category Slider */
|
|
1586
|
-
const defaultThumbSize = 22;
|
|
1587
|
-
/** @category Slider */
|
|
1588
|
-
const Thumb$1 = (0, react.forwardRef)(({ size, width = defaultThumbSize, height = defaultThumbSize, color, children, className, style, __percent }, ref) => {
|
|
1719
|
+
const Thumb$1 = (0, react.forwardRef)(({ size, width = 22, height = 22, color, children, className, style, __percent }, ref) => {
|
|
1589
1720
|
const wrapperRef = (0, react.useRef)(null);
|
|
1590
1721
|
const vertical = useSliderContext((s) => s.vertical);
|
|
1591
1722
|
const disabled = useSliderContext((s) => s.disabled);
|
|
@@ -1621,13 +1752,7 @@ const Thumb$1 = (0, react.forwardRef)(({ size, width = defaultThumbSize, height
|
|
|
1621
1752
|
})
|
|
1622
1753
|
});
|
|
1623
1754
|
});
|
|
1624
|
-
|
|
1625
|
-
//#endregion
|
|
1626
|
-
//#region src/components/Slider/Track.tsx
|
|
1627
|
-
const defaultLength = 140;
|
|
1628
|
-
const defaultThickness = 10;
|
|
1629
|
-
/** @category Slider */
|
|
1630
|
-
function Track({ length = defaultLength, thickness = defaultThickness, active, inactive, children, className, style, defaultStyle = true, __thumb, __percent = 0,...props }) {
|
|
1755
|
+
function Track({ length = 140, thickness = 10, active, inactive, children, className, style, defaultStyle = true, __thumb, __percent = 0, ...props }) {
|
|
1631
1756
|
const vertical = useSliderContext((s) => s.vertical);
|
|
1632
1757
|
const reverse = useSliderContext((s) => s.reverse);
|
|
1633
1758
|
const disabled = useSliderContext((s) => s.disabled);
|
|
@@ -1642,8 +1767,8 @@ function Track({ length = defaultLength, thickness = defaultThickness, active, i
|
|
|
1642
1767
|
"data-vertical": vertical,
|
|
1643
1768
|
style: !defaultStyle ? style : {
|
|
1644
1769
|
...colors,
|
|
1645
|
-
background: (0,
|
|
1646
|
-
borderRadius: (0,
|
|
1770
|
+
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}%)`,
|
|
1771
|
+
borderRadius: (0, _tremolo_ui_functions.styleHelper)(thickness, "/", 2),
|
|
1647
1772
|
width: !vertical ? length : thickness,
|
|
1648
1773
|
height: vertical ? length : thickness,
|
|
1649
1774
|
...style
|
|
@@ -1652,213 +1777,259 @@ function Track({ length = defaultLength, thickness = defaultThickness, active, i
|
|
|
1652
1777
|
children: [children, __thumb]
|
|
1653
1778
|
});
|
|
1654
1779
|
}
|
|
1655
|
-
|
|
1656
1780
|
//#endregion
|
|
1657
1781
|
//#region src/components/Slider/index.tsx
|
|
1658
|
-
const
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
else throw new Error("only <SliderThumb> or <SliderTrack>");
|
|
1673
|
-
else throw new Error("children is an invalid element.");
|
|
1674
|
-
});
|
|
1675
|
-
const onDrag = (0, react.useCallback)((nx, ny) => {
|
|
1676
|
-
if (!onChange || readonly) return;
|
|
1677
|
-
const n = vertical ? ny : nx;
|
|
1678
|
-
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));
|
|
1679
|
-
}, [
|
|
1680
|
-
displayReversed,
|
|
1681
|
-
max,
|
|
1682
|
-
min,
|
|
1683
|
-
onChange,
|
|
1684
|
-
readonly,
|
|
1685
|
-
skew,
|
|
1686
|
-
step,
|
|
1687
|
-
vertical
|
|
1688
|
-
]);
|
|
1689
|
-
const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
|
|
1690
|
-
let newValue;
|
|
1691
|
-
if (eventType == "normalized") newValue = (0, __tremolo_ui_functions.rawValue)((0, __tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
|
|
1692
|
-
else newValue = value + x;
|
|
1693
|
-
return (0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)(newValue, step), min, max);
|
|
1694
|
-
}, [
|
|
1695
|
-
max,
|
|
1696
|
-
min,
|
|
1697
|
-
skew,
|
|
1698
|
-
step,
|
|
1699
|
-
value
|
|
1700
|
-
]);
|
|
1701
|
-
const handleKeyDown = (0, react.useCallback)((event) => {
|
|
1702
|
-
if (!keyboard || !onChange || readonly) return;
|
|
1703
|
-
const key = event.key;
|
|
1704
|
-
if ([
|
|
1705
|
-
"ArrowRight",
|
|
1706
|
-
"ArrowLeft",
|
|
1707
|
-
"ArrowUp",
|
|
1708
|
-
"ArrowDown"
|
|
1709
|
-
].includes(key)) {
|
|
1710
|
-
event.preventDefault();
|
|
1711
|
-
let x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
|
|
1712
|
-
if (reverse) x *= -1;
|
|
1713
|
-
onChange(updateValueByEvent(keyboard[0], x));
|
|
1714
|
-
}
|
|
1715
|
-
}, [
|
|
1716
|
-
keyboard,
|
|
1717
|
-
onChange,
|
|
1718
|
-
readonly,
|
|
1719
|
-
reverse,
|
|
1720
|
-
updateValueByEvent
|
|
1721
|
-
]);
|
|
1722
|
-
const [touchMoveRefCallback, pointerDownHandler] = useDragWithElement({
|
|
1723
|
-
baseElementRef: trackElementRef,
|
|
1724
|
-
onDrag,
|
|
1725
|
-
onDragStart: (nx, ny) => {
|
|
1726
|
-
if (readonly) return;
|
|
1727
|
-
if (bodyNoSelect) addNoSelect();
|
|
1728
|
-
thumbRef.current?.focus();
|
|
1729
|
-
onDragStart?.((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(vertical ? ny : nx, min, max, skew), step), min, max));
|
|
1730
|
-
},
|
|
1731
|
-
onDragEnd: (nx, ny) => {
|
|
1732
|
-
if (readonly) return;
|
|
1733
|
-
if (bodyNoSelect) removeNoSelect();
|
|
1734
|
-
onDragEnd?.((0, __tremolo_ui_functions.clamp)((0, __tremolo_ui_functions.stepValue)((0, __tremolo_ui_functions.rawValue)(vertical ? ny : nx, min, max, skew), step), min, max));
|
|
1735
|
-
}
|
|
1736
|
-
});
|
|
1737
|
-
const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
|
|
1738
|
-
if (!wheel || !onChange || readonly) return;
|
|
1739
|
-
event.preventDefault();
|
|
1740
|
-
let x;
|
|
1741
|
-
if (!vertical && event.deltaX != 0) x = event.deltaX > 0 ? wheel[1] : -wheel[1];
|
|
1742
|
-
else {
|
|
1743
|
-
if (event.deltaY == 0) return;
|
|
1744
|
-
x = event.deltaY > 0 ? -wheel[1] : wheel[1];
|
|
1745
|
-
}
|
|
1746
|
-
if (vertical && reverse) x *= -1;
|
|
1747
|
-
onChange(updateValueByEvent(wheel[0], x));
|
|
1748
|
-
}, { passive: false }, [
|
|
1749
|
-
wheel,
|
|
1750
|
-
vertical,
|
|
1751
|
-
readonly,
|
|
1752
|
-
onChange,
|
|
1753
|
-
updateValueByEvent
|
|
1754
|
-
]);
|
|
1755
|
-
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
1756
|
-
return {
|
|
1757
|
-
focus() {
|
|
1758
|
-
thumbRef.current?.focus();
|
|
1759
|
-
},
|
|
1760
|
-
blur() {
|
|
1761
|
-
thumbRef.current?.blur();
|
|
1762
|
-
}
|
|
1782
|
+
const defaultExternalStyles$1 = {
|
|
1783
|
+
userSelectNone: true,
|
|
1784
|
+
cursor: "pointer"
|
|
1785
|
+
};
|
|
1786
|
+
/**
|
|
1787
|
+
* Customizable slider
|
|
1788
|
+
*/
|
|
1789
|
+
const Slider = {
|
|
1790
|
+
Root: (0, react.forwardRef)(({ value, min, max, step = 1, skew = 1, 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) => {
|
|
1791
|
+
const trackElementRef = (0, react.useRef)(null);
|
|
1792
|
+
const thumbRef = (0, react.useRef)(null);
|
|
1793
|
+
const externalStyles = {
|
|
1794
|
+
...defaultExternalStyles$1,
|
|
1795
|
+
..._externalStyles
|
|
1763
1796
|
};
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1797
|
+
const p = (0, _tremolo_ui_functions.toFixed)((0, _tremolo_ui_functions.normalizeValue)(value, min, max, skew) * 100);
|
|
1798
|
+
const rev = (0, _tremolo_ui_functions.toFixed)(100 - p);
|
|
1799
|
+
const displayReversed = (0, react.useMemo)(() => (0, _tremolo_ui_functions.xor)(vertical, reverse), [vertical, reverse]);
|
|
1800
|
+
const percent = displayReversed ? rev : p;
|
|
1801
|
+
let trackProps = {};
|
|
1802
|
+
let thumbProps = {};
|
|
1803
|
+
let scaleComponent = void 0;
|
|
1804
|
+
if (children != void 0) react.default.Children.forEach(children, (child) => {
|
|
1805
|
+
if (react.default.isValidElement(child)) if (child.type == Thumb$1) thumbProps = child.props;
|
|
1806
|
+
else if (child.type == Track) trackProps = child.props;
|
|
1807
|
+
else if (child.type == Scale) scaleComponent = child;
|
|
1808
|
+
else throw new Error("only <SliderThumb> or <SliderTrack>");
|
|
1809
|
+
else throw new Error("children is an invalid element.");
|
|
1810
|
+
});
|
|
1811
|
+
const onDrag = (0, react.useCallback)((nx, ny) => {
|
|
1812
|
+
if (!onChange || readonly) return;
|
|
1813
|
+
const n = vertical ? ny : nx;
|
|
1814
|
+
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));
|
|
1815
|
+
}, [
|
|
1816
|
+
displayReversed,
|
|
1817
|
+
max,
|
|
1818
|
+
min,
|
|
1819
|
+
onChange,
|
|
1820
|
+
readonly,
|
|
1821
|
+
skew,
|
|
1822
|
+
step,
|
|
1823
|
+
vertical
|
|
1824
|
+
]);
|
|
1825
|
+
const updateValueByEvent = (0, react.useCallback)((eventType, x) => {
|
|
1826
|
+
let newValue;
|
|
1827
|
+
if (eventType == "normalized") newValue = (0, _tremolo_ui_functions.rawValue)((0, _tremolo_ui_functions.normalizeValue)(value, min, max, skew) + x, min, max, skew);
|
|
1828
|
+
else newValue = value + x;
|
|
1829
|
+
return (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)(newValue, step), min, max);
|
|
1830
|
+
}, [
|
|
1831
|
+
max,
|
|
1832
|
+
min,
|
|
1833
|
+
skew,
|
|
1834
|
+
step,
|
|
1835
|
+
value
|
|
1836
|
+
]);
|
|
1837
|
+
const handleKeyDown = (0, react.useCallback)((event) => {
|
|
1838
|
+
if (!keyboard || !onChange || readonly) return;
|
|
1839
|
+
const key = event.key;
|
|
1840
|
+
if ([
|
|
1841
|
+
"ArrowRight",
|
|
1842
|
+
"ArrowLeft",
|
|
1843
|
+
"ArrowUp",
|
|
1844
|
+
"ArrowDown"
|
|
1845
|
+
].includes(key)) {
|
|
1846
|
+
event.preventDefault();
|
|
1847
|
+
let x = key == "ArrowRight" || key == "ArrowUp" ? keyboard[1] : -keyboard[1];
|
|
1848
|
+
if (reverse) x *= -1;
|
|
1849
|
+
onChange(updateValueByEvent(keyboard[0], x));
|
|
1850
|
+
}
|
|
1851
|
+
}, [
|
|
1852
|
+
keyboard,
|
|
1853
|
+
onChange,
|
|
1854
|
+
readonly,
|
|
1855
|
+
reverse,
|
|
1856
|
+
updateValueByEvent
|
|
1857
|
+
]);
|
|
1858
|
+
const { refHandler: touchMoveRefCallback, pointerDownHandler } = useDragWithElement({
|
|
1859
|
+
baseElementRef: trackElementRef,
|
|
1860
|
+
onDrag,
|
|
1861
|
+
onDragStart: (nx, ny) => {
|
|
1862
|
+
if (readonly) return;
|
|
1863
|
+
if (externalStyles.userSelectNone) addUserSelectNone();
|
|
1864
|
+
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
|
|
1801
1865
|
thumbRef.current?.focus();
|
|
1802
|
-
|
|
1866
|
+
onDragStart?.((0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)((0, _tremolo_ui_functions.rawValue)(vertical ? ny : nx, min, max, skew), step), min, max));
|
|
1803
1867
|
},
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1868
|
+
onDragEnd: (nx, ny) => {
|
|
1869
|
+
if (readonly) return;
|
|
1870
|
+
if (externalStyles.userSelectNone) removeUserSelectNone();
|
|
1871
|
+
if (externalStyles.cursor) resetCursorStyle();
|
|
1872
|
+
onDragEnd?.((0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)((0, _tremolo_ui_functions.rawValue)(vertical ? ny : nx, min, max, skew), step), min, max));
|
|
1873
|
+
}
|
|
1874
|
+
});
|
|
1875
|
+
const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
|
|
1876
|
+
if (!wheel || !onChange || readonly) return;
|
|
1877
|
+
event.preventDefault();
|
|
1878
|
+
let x;
|
|
1879
|
+
if (!vertical && event.deltaX != 0) x = event.deltaX > 0 ? wheel[1] : -wheel[1];
|
|
1880
|
+
else {
|
|
1881
|
+
if (event.deltaY == 0) return;
|
|
1882
|
+
x = event.deltaY > 0 ? -wheel[1] : wheel[1];
|
|
1883
|
+
}
|
|
1884
|
+
if (vertical && reverse) x *= -1;
|
|
1885
|
+
onChange(updateValueByEvent(wheel[0], x));
|
|
1886
|
+
}, { passive: false }, [
|
|
1887
|
+
wheel,
|
|
1888
|
+
vertical,
|
|
1889
|
+
readonly,
|
|
1890
|
+
onChange,
|
|
1891
|
+
updateValueByEvent
|
|
1892
|
+
]);
|
|
1893
|
+
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
1894
|
+
return {
|
|
1895
|
+
focus() {
|
|
1896
|
+
thumbRef.current?.focus();
|
|
1897
|
+
},
|
|
1898
|
+
blur() {
|
|
1899
|
+
thumbRef.current?.blur();
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1902
|
+
}, []);
|
|
1903
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderProvider, {
|
|
1904
|
+
min,
|
|
1905
|
+
max,
|
|
1906
|
+
step,
|
|
1907
|
+
skew,
|
|
1908
|
+
vertical,
|
|
1909
|
+
reverse,
|
|
1910
|
+
disabled,
|
|
1911
|
+
readonly,
|
|
1912
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1913
|
+
className: (0, clsx.default)("tremolo-slider", className),
|
|
1914
|
+
ref: (div) => {
|
|
1915
|
+
wheelRefCallback(div);
|
|
1916
|
+
touchMoveRefCallback(div);
|
|
1917
|
+
},
|
|
1918
|
+
tabIndex: -1,
|
|
1919
|
+
role: "slider",
|
|
1920
|
+
"aria-valuenow": value,
|
|
1921
|
+
"aria-valuemin": min,
|
|
1922
|
+
"aria-valuemax": max,
|
|
1923
|
+
"aria-orientation": vertical ? "vertical" : "horizontal",
|
|
1924
|
+
"aria-disabled": disabled,
|
|
1925
|
+
"aria-readonly": readonly,
|
|
1810
1926
|
style: {
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
width: !vertical ? "100%" : void 0,
|
|
1814
|
-
height: vertical ? "100%" : void 0
|
|
1927
|
+
margin: `calc(${(0, _tremolo_ui_functions.styleHelper)(thumbProps?.size ?? 22)} / 2)`,
|
|
1928
|
+
...style
|
|
1815
1929
|
},
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1930
|
+
onPointerDown: (event) => {
|
|
1931
|
+
pointerDownHandler(event);
|
|
1932
|
+
onPointerDown?.(event);
|
|
1933
|
+
},
|
|
1934
|
+
onKeyDown: (event) => {
|
|
1935
|
+
handleKeyDown(event);
|
|
1936
|
+
onKeyDown?.(event);
|
|
1937
|
+
},
|
|
1938
|
+
onFocus: (event) => {
|
|
1939
|
+
thumbRef.current?.focus();
|
|
1940
|
+
onFocus?.(event);
|
|
1941
|
+
},
|
|
1942
|
+
onBlur: (event) => {
|
|
1943
|
+
thumbRef.current?.blur();
|
|
1944
|
+
onBlur?.(event);
|
|
1945
|
+
},
|
|
1946
|
+
...props,
|
|
1947
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1948
|
+
style: {
|
|
1949
|
+
display: "flex",
|
|
1950
|
+
flexDirection: vertical ? "row" : "column",
|
|
1951
|
+
width: !vertical ? "100%" : void 0,
|
|
1952
|
+
height: vertical ? "100%" : void 0
|
|
1953
|
+
},
|
|
1954
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1955
|
+
className: "tremolo-slider-track-wrapper",
|
|
1956
|
+
ref: trackElementRef,
|
|
1957
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Track, {
|
|
1823
1958
|
__percent: percent,
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1959
|
+
__thumb: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Thumb$1, {
|
|
1960
|
+
ref: thumbRef,
|
|
1961
|
+
__percent: percent,
|
|
1962
|
+
...thumbProps
|
|
1963
|
+
}),
|
|
1964
|
+
...trackProps
|
|
1965
|
+
})
|
|
1966
|
+
}), scaleComponent]
|
|
1967
|
+
})
|
|
1829
1968
|
})
|
|
1830
|
-
})
|
|
1831
|
-
})
|
|
1832
|
-
});
|
|
1833
|
-
/**
|
|
1834
|
-
* Customizable slider
|
|
1835
|
-
* @category Slider
|
|
1836
|
-
*/
|
|
1837
|
-
const Slider = Object.assign(SliderImpl, {
|
|
1969
|
+
});
|
|
1970
|
+
}),
|
|
1838
1971
|
Thumb: Thumb$1,
|
|
1839
1972
|
Track,
|
|
1840
1973
|
Scale,
|
|
1841
1974
|
ScaleOption
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1975
|
+
};
|
|
1976
|
+
//#endregion
|
|
1977
|
+
//#region src/components/_util/composeRefs.tsx
|
|
1978
|
+
/**
|
|
1979
|
+
* Set a given ref to a given value
|
|
1980
|
+
* This utility takes care of different types of refs: callback refs and RefObject(s)
|
|
1981
|
+
*/
|
|
1982
|
+
function setRef(ref, value) {
|
|
1983
|
+
if (typeof ref === "function") return ref(value);
|
|
1984
|
+
else if (ref !== null && ref !== void 0) ref.current = value;
|
|
1985
|
+
}
|
|
1986
|
+
/**
|
|
1987
|
+
* A utility to compose multiple refs together
|
|
1988
|
+
* Accepts callback refs and RefObject(s)
|
|
1989
|
+
*/
|
|
1990
|
+
function composeRefs(...refs) {
|
|
1991
|
+
return (node) => {
|
|
1992
|
+
let hasCleanup = false;
|
|
1993
|
+
const cleanups = refs.map((ref) => {
|
|
1994
|
+
const cleanup = setRef(ref, node);
|
|
1995
|
+
if (!hasCleanup && typeof cleanup == "function") hasCleanup = true;
|
|
1996
|
+
return cleanup;
|
|
1997
|
+
});
|
|
1998
|
+
if (hasCleanup) return () => {
|
|
1999
|
+
for (let i = 0; i < cleanups.length; i++) {
|
|
2000
|
+
const cleanup = cleanups[i];
|
|
2001
|
+
if (typeof cleanup == "function") cleanup();
|
|
2002
|
+
else setRef(refs[i], null);
|
|
2003
|
+
}
|
|
2004
|
+
};
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
1844
2007
|
//#endregion
|
|
1845
2008
|
//#region src/components/WheelObserver/index.tsx
|
|
1846
|
-
/**
|
|
2009
|
+
/**
|
|
2010
|
+
* @example
|
|
2011
|
+
* <WheelObserver
|
|
2012
|
+
* onWheel={(event) => {
|
|
2013
|
+
* event.preventDefault()
|
|
2014
|
+
* console.log(event.deltaY)
|
|
2015
|
+
* }
|
|
2016
|
+
* >
|
|
2017
|
+
* <div>Wheel here</div>
|
|
2018
|
+
* </WheelObserver>
|
|
2019
|
+
*/
|
|
1847
2020
|
function WheelObserver(props) {
|
|
1848
|
-
const {
|
|
2021
|
+
const { as: Component = "div", children, onWheel, ref, ...attributes } = props;
|
|
1849
2022
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
|
|
1850
|
-
ref: useRefCallbackEvent("wheel", (event) => {
|
|
2023
|
+
ref: composeRefs(ref, useRefCallbackEvent("wheel", (event) => {
|
|
1851
2024
|
if (onWheel) onWheel(event);
|
|
1852
|
-
}, { passive: false }, [onWheel]),
|
|
2025
|
+
}, { passive: false }, [onWheel])),
|
|
1853
2026
|
...attributes,
|
|
1854
2027
|
children
|
|
1855
2028
|
});
|
|
1856
2029
|
}
|
|
1857
|
-
|
|
1858
2030
|
//#endregion
|
|
1859
2031
|
//#region src/components/XYPad/Area.tsx
|
|
1860
|
-
|
|
1861
|
-
function Area({ width = 120, height = 120, color, children, className, style, __thumb,...props }) {
|
|
2032
|
+
function Area({ width = 120, height = 120, color, children, className, style, __thumb, ...props }) {
|
|
1862
2033
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1863
2034
|
className: (0, clsx.default)("tremolo-xy-pad-area", className),
|
|
1864
2035
|
style: {
|
|
@@ -1871,11 +2042,9 @@ function Area({ width = 120, height = 120, color, children, className, style, __
|
|
|
1871
2042
|
children: [children, __thumb]
|
|
1872
2043
|
});
|
|
1873
2044
|
}
|
|
1874
|
-
|
|
1875
2045
|
//#endregion
|
|
1876
2046
|
//#region src/components/XYPad/Thumb.tsx
|
|
1877
|
-
|
|
1878
|
-
const Thumb = (0, react.forwardRef)(({ size, width = 22, height = 22, color, children, wrapperClassName, wrapperStyle, className, style, __disabled, __readonly, __css,...props }, ref) => {
|
|
2047
|
+
const Thumb = (0, react.forwardRef)(({ size, width = 22, height = 22, color, children, wrapperClassName, wrapperStyle, className, style, __disabled, __readonly, __css, ...props }, ref) => {
|
|
1879
2048
|
const wrapperRef = (0, react.useRef)(null);
|
|
1880
2049
|
(0, react.useImperativeHandle)(ref, () => {
|
|
1881
2050
|
return {
|
|
@@ -1909,7 +2078,6 @@ const Thumb = (0, react.forwardRef)(({ size, width = 22, height = 22, color, chi
|
|
|
1909
2078
|
})
|
|
1910
2079
|
});
|
|
1911
2080
|
});
|
|
1912
|
-
|
|
1913
2081
|
//#endregion
|
|
1914
2082
|
//#region src/components/XYPad/index.tsx
|
|
1915
2083
|
const defaultValueOptions = {
|
|
@@ -1919,197 +2087,201 @@ const defaultValueOptions = {
|
|
|
1919
2087
|
wheel: ["raw", 1],
|
|
1920
2088
|
keyboard: ["raw", 1]
|
|
1921
2089
|
};
|
|
1922
|
-
const
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
2090
|
+
const defaultExternalStyles = {
|
|
2091
|
+
userSelectNone: true,
|
|
2092
|
+
cursor: "pointer"
|
|
2093
|
+
};
|
|
2094
|
+
/**
|
|
2095
|
+
* Simple XYPad
|
|
2096
|
+
*/
|
|
2097
|
+
const XYPad = {
|
|
2098
|
+
Root: (0, react.forwardRef)(({ x: _x, y: _y, className, style, externalStyles: _externalStyles, disabled = false, readonly = false, onChange, onDragStart, onDragEnd, onPointerDown, onKeyDown, onFocus, onBlur, children, ...props }, forwardedRef) => {
|
|
2099
|
+
const x = (0, react.useMemo)(() => {
|
|
2100
|
+
return {
|
|
2101
|
+
...defaultValueOptions,
|
|
2102
|
+
..._x
|
|
2103
|
+
};
|
|
2104
|
+
}, [_x]);
|
|
2105
|
+
const y = (0, react.useMemo)(() => {
|
|
2106
|
+
return {
|
|
2107
|
+
...defaultValueOptions,
|
|
2108
|
+
..._y
|
|
2109
|
+
};
|
|
2110
|
+
}, [_y]);
|
|
2111
|
+
const rootRef = (0, react.useRef)(null);
|
|
2112
|
+
const areaElementRef = (0, react.useRef)(null);
|
|
2113
|
+
const thumbRef = (0, react.useRef)(null);
|
|
2114
|
+
const externalStyles = {
|
|
2115
|
+
...defaultExternalStyles,
|
|
2116
|
+
..._externalStyles
|
|
1933
2117
|
};
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
2118
|
+
const nx = (0, _tremolo_ui_functions.normalizeValue)(x.value, x.min, x.max, x.skew);
|
|
2119
|
+
const ny = (0, _tremolo_ui_functions.normalizeValue)(y.value, y.min, y.max, y.skew);
|
|
2120
|
+
const percentX = (0, _tremolo_ui_functions.toFixed)((x.reverse ? 1 - nx : nx) * 100);
|
|
2121
|
+
const percentY = (0, _tremolo_ui_functions.toFixed)((y.reverse ? 1 - ny : ny) * 100);
|
|
2122
|
+
let areaProps = {};
|
|
2123
|
+
let thumbProps = {};
|
|
2124
|
+
if (children != void 0) react.default.Children.forEach(children, (child) => {
|
|
2125
|
+
if (react.default.isValidElement(child)) if (child.type == Thumb) thumbProps = child.props;
|
|
2126
|
+
else if (child.type == Area) areaProps = child.props;
|
|
2127
|
+
else throw new Error("only <XYPadThumb> or <XYPadArea>");
|
|
2128
|
+
else throw new Error("children is an invalid element.");
|
|
2129
|
+
});
|
|
2130
|
+
const onDrag = (0, react.useCallback)((nx, ny) => {
|
|
2131
|
+
if (!onChange || readonly) return;
|
|
2132
|
+
const vx = (0, _tremolo_ui_functions.rawValue)(x.reverse ? 1 - nx : nx, x.min, x.max, x.skew);
|
|
2133
|
+
const vy = (0, _tremolo_ui_functions.rawValue)(y.reverse ? 1 - ny : ny, y.min, y.max, y.skew);
|
|
2134
|
+
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));
|
|
2135
|
+
}, [
|
|
2136
|
+
onChange,
|
|
2137
|
+
readonly,
|
|
2138
|
+
x.max,
|
|
2139
|
+
x.min,
|
|
2140
|
+
x.reverse,
|
|
2141
|
+
x.skew,
|
|
2142
|
+
x.step,
|
|
2143
|
+
y.max,
|
|
2144
|
+
y.min,
|
|
2145
|
+
y.reverse,
|
|
2146
|
+
y.skew,
|
|
2147
|
+
y.step
|
|
2148
|
+
]);
|
|
2149
|
+
const updateValueByEvent = (0, react.useCallback)((eventType, target, x) => {
|
|
2150
|
+
let v;
|
|
2151
|
+
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);
|
|
2152
|
+
else v = target.value + x;
|
|
2153
|
+
return (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)(v, target.step), target.min, target.max);
|
|
2154
|
+
}, []);
|
|
2155
|
+
const handleKeyDown = (0, react.useCallback)((event) => {
|
|
2156
|
+
if (!onChange || readonly) return;
|
|
2157
|
+
const key = event.key;
|
|
2158
|
+
if ([
|
|
2159
|
+
"ArrowRight",
|
|
2160
|
+
"ArrowLeft",
|
|
2161
|
+
"ArrowUp",
|
|
2162
|
+
"ArrowDown"
|
|
2163
|
+
].includes(key)) {
|
|
2164
|
+
const isHorizontal = key == "ArrowRight" || key == "ArrowLeft";
|
|
2165
|
+
const target = isHorizontal ? x : y;
|
|
2166
|
+
if (!target.keyboard) return;
|
|
2167
|
+
event.preventDefault();
|
|
2168
|
+
let delta = target.keyboard[1];
|
|
2169
|
+
if (key == "ArrowLeft" || key == "ArrowUp") delta *= -1;
|
|
2170
|
+
if (target.reverse) delta *= -1;
|
|
2171
|
+
const newValue = updateValueByEvent(target.keyboard[0], target, delta);
|
|
2172
|
+
if (isHorizontal) onChange(newValue, y.value);
|
|
2173
|
+
else onChange(x.value, newValue);
|
|
2174
|
+
}
|
|
2175
|
+
}, [
|
|
2176
|
+
onChange,
|
|
2177
|
+
readonly,
|
|
2178
|
+
x,
|
|
2179
|
+
y,
|
|
2180
|
+
updateValueByEvent
|
|
2181
|
+
]);
|
|
2182
|
+
const { refHandler: touchMoveRefCallback, pointerDownHandler } = useDragWithElement({
|
|
2183
|
+
baseElementRef: areaElementRef,
|
|
2184
|
+
onDrag,
|
|
2185
|
+
onDragStart: (nx, ny) => {
|
|
2186
|
+
if (readonly) return;
|
|
2187
|
+
if (externalStyles.userSelectNone) addUserSelectNone();
|
|
2188
|
+
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor);
|
|
2189
|
+
thumbRef.current?.focus();
|
|
2190
|
+
const valueX = (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)((0, _tremolo_ui_functions.rawValue)(nx, x.min, x.max, x.skew), x.step), x.min, x.max);
|
|
2191
|
+
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);
|
|
2192
|
+
onDragStart?.(valueX, valueY);
|
|
2193
|
+
},
|
|
2194
|
+
onDragEnd: (nx, ny) => {
|
|
2195
|
+
if (readonly) return;
|
|
2196
|
+
if (externalStyles.userSelectNone) removeUserSelectNone();
|
|
2197
|
+
if (externalStyles.cursor) resetCursorStyle();
|
|
2198
|
+
const valueX = (0, _tremolo_ui_functions.clamp)((0, _tremolo_ui_functions.stepValue)((0, _tremolo_ui_functions.rawValue)(nx, x.min, x.max, x.skew), x.step), x.min, x.max);
|
|
2199
|
+
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);
|
|
2200
|
+
onDragEnd?.(valueX, valueY);
|
|
2201
|
+
}
|
|
2202
|
+
});
|
|
2203
|
+
const wheelRefCallback = useRefCallbackEvent("wheel", (event) => {
|
|
2204
|
+
if (!onChange || readonly) return;
|
|
2205
|
+
const target = event.shiftKey ? x : y;
|
|
2206
|
+
if (!target.wheel) return;
|
|
1986
2207
|
event.preventDefault();
|
|
1987
|
-
let delta = target.
|
|
1988
|
-
if (
|
|
2208
|
+
let delta = target.wheel[1];
|
|
2209
|
+
if (event.deltaY < 0) delta *= -1;
|
|
1989
2210
|
if (target.reverse) delta *= -1;
|
|
1990
|
-
const newValue = updateValueByEvent(target.
|
|
1991
|
-
if (
|
|
2211
|
+
const newValue = updateValueByEvent(target.wheel[0], target, delta);
|
|
2212
|
+
if (event.shiftKey) onChange(newValue, y.value);
|
|
1992
2213
|
else onChange(x.value, newValue);
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
x,
|
|
2033
|
-
y,
|
|
2034
|
-
onChange,
|
|
2035
|
-
readonly,
|
|
2036
|
-
updateValueByEvent
|
|
2037
|
-
]);
|
|
2038
|
-
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
2039
|
-
return {
|
|
2040
|
-
focus() {
|
|
2214
|
+
}, { passive: false }, [
|
|
2215
|
+
x,
|
|
2216
|
+
y,
|
|
2217
|
+
onChange,
|
|
2218
|
+
readonly,
|
|
2219
|
+
updateValueByEvent
|
|
2220
|
+
]);
|
|
2221
|
+
(0, react.useImperativeHandle)(forwardedRef, () => {
|
|
2222
|
+
return {
|
|
2223
|
+
focus() {
|
|
2224
|
+
thumbRef.current?.focus();
|
|
2225
|
+
},
|
|
2226
|
+
blur() {
|
|
2227
|
+
thumbRef.current?.blur();
|
|
2228
|
+
},
|
|
2229
|
+
original: rootRef
|
|
2230
|
+
};
|
|
2231
|
+
}, []);
|
|
2232
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2233
|
+
className: (0, clsx.default)("tremolo-xy-pad", className),
|
|
2234
|
+
ref: composeRefs(rootRef, wheelRefCallback, touchMoveRefCallback),
|
|
2235
|
+
tabIndex: -1,
|
|
2236
|
+
"aria-disabled": disabled,
|
|
2237
|
+
"aria-readonly": readonly,
|
|
2238
|
+
style: {
|
|
2239
|
+
margin: `calc(${(0, _tremolo_ui_functions.styleHelper)(thumbProps?.size ?? 22)} / 2)`,
|
|
2240
|
+
cursor: readonly ? "not-allowed" : "pointer",
|
|
2241
|
+
WebkitTapHighlightColor: "transparent",
|
|
2242
|
+
...style
|
|
2243
|
+
},
|
|
2244
|
+
onPointerDown: (event) => {
|
|
2245
|
+
pointerDownHandler(event);
|
|
2246
|
+
onPointerDown?.(event);
|
|
2247
|
+
},
|
|
2248
|
+
onKeyDown: (event) => {
|
|
2249
|
+
handleKeyDown(event);
|
|
2250
|
+
onKeyDown?.(event);
|
|
2251
|
+
},
|
|
2252
|
+
onFocus: (event) => {
|
|
2041
2253
|
thumbRef.current?.focus();
|
|
2254
|
+
onFocus?.(event);
|
|
2042
2255
|
},
|
|
2043
|
-
|
|
2256
|
+
onBlur: (event) => {
|
|
2044
2257
|
thumbRef.current?.blur();
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
pointerDownHandler(event);
|
|
2065
|
-
onPointerDown?.(event);
|
|
2066
|
-
},
|
|
2067
|
-
onKeyDown: (event) => {
|
|
2068
|
-
handleKeyDown(event);
|
|
2069
|
-
onKeyDown?.(event);
|
|
2070
|
-
},
|
|
2071
|
-
onFocus: (event) => {
|
|
2072
|
-
thumbRef.current?.focus();
|
|
2073
|
-
onFocus?.(event);
|
|
2074
|
-
},
|
|
2075
|
-
onBlur: (event) => {
|
|
2076
|
-
thumbRef.current?.blur();
|
|
2077
|
-
onBlur?.(event);
|
|
2078
|
-
},
|
|
2079
|
-
...props,
|
|
2080
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2081
|
-
className: "tremolo-xy-pad-area-wrapper",
|
|
2082
|
-
ref: areaElementRef,
|
|
2083
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Area, {
|
|
2084
|
-
__thumb: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Thumb, {
|
|
2085
|
-
ref: thumbRef,
|
|
2086
|
-
__disabled: disabled,
|
|
2087
|
-
__readonly: readonly,
|
|
2088
|
-
__css: {
|
|
2089
|
-
top: `${percentY}%`,
|
|
2090
|
-
left: `${percentX}%`
|
|
2091
|
-
},
|
|
2092
|
-
...thumbProps
|
|
2093
|
-
}),
|
|
2094
|
-
...areaProps
|
|
2258
|
+
onBlur?.(event);
|
|
2259
|
+
},
|
|
2260
|
+
...props,
|
|
2261
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2262
|
+
className: "tremolo-xy-pad-area-wrapper",
|
|
2263
|
+
ref: areaElementRef,
|
|
2264
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Area, {
|
|
2265
|
+
__thumb: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Thumb, {
|
|
2266
|
+
ref: thumbRef,
|
|
2267
|
+
__disabled: disabled,
|
|
2268
|
+
__readonly: readonly,
|
|
2269
|
+
__css: {
|
|
2270
|
+
top: `${percentY}%`,
|
|
2271
|
+
left: `${percentX}%`
|
|
2272
|
+
},
|
|
2273
|
+
...thumbProps
|
|
2274
|
+
}),
|
|
2275
|
+
...areaProps
|
|
2276
|
+
})
|
|
2095
2277
|
})
|
|
2096
|
-
})
|
|
2097
|
-
})
|
|
2098
|
-
});
|
|
2099
|
-
/**
|
|
2100
|
-
* Simple XYPad
|
|
2101
|
-
* @category XYPad
|
|
2102
|
-
*/
|
|
2103
|
-
const XYPad = Object.assign(XYPadImpl, {
|
|
2278
|
+
});
|
|
2279
|
+
}),
|
|
2104
2280
|
Thumb,
|
|
2105
2281
|
Area
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2282
|
+
};
|
|
2108
2283
|
//#endregion
|
|
2109
2284
|
//#region src/hooks/useAnimationFrame.ts
|
|
2110
|
-
/**
|
|
2111
|
-
* @category hooks
|
|
2112
|
-
*/
|
|
2113
2285
|
function useAnimationFrame(callback = () => {}, deps = []) {
|
|
2114
2286
|
const reqIdRef = (0, react.useRef)(-1);
|
|
2115
2287
|
const loop = (0, react.useCallback)(() => {
|
|
@@ -2121,7 +2293,6 @@ function useAnimationFrame(callback = () => {}, deps = []) {
|
|
|
2121
2293
|
return () => cancelAnimationFrame(reqIdRef.current);
|
|
2122
2294
|
}, [loop]);
|
|
2123
2295
|
}
|
|
2124
|
-
|
|
2125
2296
|
//#endregion
|
|
2126
2297
|
//#region src/hooks/useMIDIAccess.ts
|
|
2127
2298
|
/** @private */
|
|
@@ -2130,7 +2301,6 @@ const PERMISSION_DENIED = "PERMISSION_DENIED";
|
|
|
2130
2301
|
const NOT_SUPPORTED = "NOT_SUPPORTED";
|
|
2131
2302
|
/**
|
|
2132
2303
|
* Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
|
|
2133
|
-
* @category hooks
|
|
2134
2304
|
*/
|
|
2135
2305
|
function useMIDIAccess(requestOnMount = true) {
|
|
2136
2306
|
const [midiAccess, setMidiAccess] = (0, react.useState)(null);
|
|
@@ -2153,12 +2323,10 @@ function useMIDIAccess(requestOnMount = true) {
|
|
|
2153
2323
|
error
|
|
2154
2324
|
};
|
|
2155
2325
|
}
|
|
2156
|
-
|
|
2157
2326
|
//#endregion
|
|
2158
2327
|
//#region src/hooks/useMIDIMessage.ts
|
|
2159
2328
|
/**
|
|
2160
2329
|
* Hooks for when you want to process MIDI events in more detail than useMIDIInput.
|
|
2161
|
-
* @category hooks
|
|
2162
2330
|
*/
|
|
2163
2331
|
function useMIDIMessage(midiAccess, onMIDIMessage) {
|
|
2164
2332
|
(0, react.useEffect)(() => {
|
|
@@ -2169,7 +2337,6 @@ function useMIDIMessage(midiAccess, onMIDIMessage) {
|
|
|
2169
2337
|
};
|
|
2170
2338
|
}, [midiAccess, onMIDIMessage]);
|
|
2171
2339
|
}
|
|
2172
|
-
|
|
2173
2340
|
//#endregion
|
|
2174
2341
|
//#region src/hooks/useMIDIInput.ts
|
|
2175
2342
|
const MIDI_EVENT_TO_NUMBER = {
|
|
@@ -2179,7 +2346,6 @@ const MIDI_EVENT_TO_NUMBER = {
|
|
|
2179
2346
|
};
|
|
2180
2347
|
/**
|
|
2181
2348
|
* Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
|
|
2182
|
-
* @category hooks
|
|
2183
2349
|
*/
|
|
2184
2350
|
function useMIDIInput(midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEvent) {
|
|
2185
2351
|
useMIDIMessage(midiAccess, (0, react.useCallback)((event) => {
|
|
@@ -2194,20 +2360,20 @@ function useMIDIInput(midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEven
|
|
|
2194
2360
|
onPitchBendEvent
|
|
2195
2361
|
]));
|
|
2196
2362
|
}
|
|
2197
|
-
|
|
2198
2363
|
//#endregion
|
|
2199
2364
|
exports.AnimationCanvas = AnimationCanvas;
|
|
2200
|
-
exports.AnimationKnob = AnimationKnob;
|
|
2201
2365
|
exports.DragObserver = DragObserver;
|
|
2202
2366
|
exports.Knob = Knob;
|
|
2203
2367
|
exports.NOT_SUPPORTED = NOT_SUPPORTED;
|
|
2204
2368
|
exports.NumberInput = NumberInput;
|
|
2205
2369
|
exports.PERMISSION_DENIED = PERMISSION_DENIED;
|
|
2206
2370
|
exports.Piano = Piano;
|
|
2371
|
+
exports.PointsEditor = PointsEditor;
|
|
2207
2372
|
exports.SHORTCUTS = SHORTCUTS;
|
|
2208
2373
|
exports.Slider = Slider;
|
|
2209
2374
|
exports.WheelObserver = WheelObserver;
|
|
2210
2375
|
exports.XYPad = XYPad;
|
|
2376
|
+
exports.clampPoint = clampPoint;
|
|
2211
2377
|
exports.getNoteRangeArray = getNoteRangeArray;
|
|
2212
2378
|
exports.useAnimationFrame = useAnimationFrame;
|
|
2213
2379
|
exports.useDrag = useDrag;
|
|
@@ -2219,4 +2385,5 @@ exports.useMIDIAccess = useMIDIAccess;
|
|
|
2219
2385
|
exports.useMIDIInput = useMIDIInput;
|
|
2220
2386
|
exports.useMIDIMessage = useMIDIMessage;
|
|
2221
2387
|
exports.useSliderContext = useSliderContext;
|
|
2388
|
+
|
|
2222
2389
|
//# sourceMappingURL=index.cjs.map
|