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