@tremolo-ui/react 0.2.0 → 0.3.0

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