@tremolo-ui/react 0.1.6 → 0.2.1

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