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