@tremolo-ui/react 0.0.12 → 0.1.0-alpha.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 (57) hide show
  1. package/dist/cjs/index.js +316 -232
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/Knob/index.d.ts +12 -0
  4. package/dist/cjs/types/components/Knob/index.d.ts.map +1 -1
  5. package/dist/cjs/types/components/Piano/KeyLabel.d.ts +1 -3
  6. package/dist/cjs/types/components/Piano/KeyLabel.d.ts.map +1 -1
  7. package/dist/cjs/types/components/Piano/context.d.ts +24 -0
  8. package/dist/cjs/types/components/Piano/context.d.ts.map +1 -0
  9. package/dist/cjs/types/components/Piano/index.d.ts +9 -9
  10. package/dist/cjs/types/components/Piano/index.d.ts.map +1 -1
  11. package/dist/cjs/types/components/Piano/key.d.ts +10 -19
  12. package/dist/cjs/types/components/Piano/key.d.ts.map +1 -1
  13. package/dist/cjs/types/components/Slider/index.d.ts.map +1 -1
  14. package/dist/cjs/types/components/XYPad/index.d.ts.map +1 -1
  15. package/dist/cjs/types/hooks/useDrag.d.ts.map +1 -1
  16. package/dist/cjs/types/hooks/useDragWithElement.d.ts +16 -0
  17. package/dist/cjs/types/hooks/useDragWithElement.d.ts.map +1 -0
  18. package/dist/esm/index.js +316 -233
  19. package/dist/esm/index.js.map +1 -1
  20. package/dist/esm/types/components/Knob/index.d.ts +12 -0
  21. package/dist/esm/types/components/Knob/index.d.ts.map +1 -1
  22. package/dist/esm/types/components/Piano/KeyLabel.d.ts +1 -3
  23. package/dist/esm/types/components/Piano/KeyLabel.d.ts.map +1 -1
  24. package/dist/esm/types/components/Piano/context.d.ts +24 -0
  25. package/dist/esm/types/components/Piano/context.d.ts.map +1 -0
  26. package/dist/esm/types/components/Piano/index.d.ts +9 -9
  27. package/dist/esm/types/components/Piano/index.d.ts.map +1 -1
  28. package/dist/esm/types/components/Piano/key.d.ts +10 -19
  29. package/dist/esm/types/components/Piano/key.d.ts.map +1 -1
  30. package/dist/esm/types/components/Slider/index.d.ts.map +1 -1
  31. package/dist/esm/types/components/XYPad/index.d.ts.map +1 -1
  32. package/dist/esm/types/hooks/useDrag.d.ts.map +1 -1
  33. package/dist/esm/types/hooks/useDragWithElement.d.ts +16 -0
  34. package/dist/esm/types/hooks/useDragWithElement.d.ts.map +1 -0
  35. package/dist/styles/index.css +2 -0
  36. package/package.json +2 -1
  37. package/src/components/Knob/index.tsx +14 -0
  38. package/src/components/Piano/KeyLabel.tsx +9 -5
  39. package/src/components/Piano/context.tsx +108 -0
  40. package/src/components/Piano/index.css +2 -0
  41. package/src/components/Piano/index.tsx +186 -114
  42. package/src/components/Piano/key.tsx +215 -0
  43. package/src/components/Slider/index.tsx +40 -52
  44. package/src/components/XYPad/index.tsx +64 -54
  45. package/src/hooks/useDrag.ts +6 -7
  46. package/src/hooks/useDragWithElement.ts +76 -0
  47. package/dist/cjs/types/components/Piano/BlackKey.d.ts +0 -5
  48. package/dist/cjs/types/components/Piano/BlackKey.d.ts.map +0 -1
  49. package/dist/cjs/types/components/Piano/WhiteKey.d.ts +0 -7
  50. package/dist/cjs/types/components/Piano/WhiteKey.d.ts.map +0 -1
  51. package/dist/esm/types/components/Piano/BlackKey.d.ts +0 -5
  52. package/dist/esm/types/components/Piano/BlackKey.d.ts.map +0 -1
  53. package/dist/esm/types/components/Piano/WhiteKey.d.ts +0 -7
  54. package/dist/esm/types/components/Piano/WhiteKey.d.ts.map +0 -1
  55. package/src/components/Piano/BlackKey.tsx +0 -132
  56. package/src/components/Piano/WhiteKey.tsx +0 -134
  57. package/src/components/Piano/key.ts +0 -51
package/dist/cjs/index.js CHANGED
@@ -321,6 +321,13 @@ function isWhiteKey(note) {
321
321
  mod(n, 12) == 9 ||
322
322
  mod(n, 12) == 11);
323
323
  }
324
+ /**
325
+ * @param note noteNumber: 0 ~ 127 or noteName e.g. 'C3'
326
+ * @category MIDI
327
+ */
328
+ function isBlackKey(note) {
329
+ return !isWhiteKey(note);
330
+ }
324
331
 
325
332
  /**
326
333
  * This hook is user-land implementation of the experimental `useEffectEvent` hook.
@@ -401,8 +408,7 @@ function useDrag({ threshold = 1, onDrag, onDragStart, onDragEnd, }) {
401
408
  }
402
409
  if (Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold)
403
410
  return;
404
- if (onDrag)
405
- onDrag(screenX - dragStartX.current, screenY - dragStartY.current, deltaX, deltaY);
411
+ onDrag(screenX - dragStartX.current, screenY - dragStartY.current, deltaX, deltaY);
406
412
  }, [threshold, onDrag]);
407
413
  const refHandler = useRefCallbackEvent('touchmove', handleDrag, { passive: false }, [onDrag]);
408
414
  const pointerDownHandler = React.useCallback((event) => {
@@ -669,7 +675,7 @@ const Knob = React.forwardRef((_a, ref) => {
669
675
  elmRef.current = div;
670
676
  wheelRefCallback(div);
671
677
  touchMoveRefCallback(div);
672
- }, width: size, height: size, tabIndex: 0, "aria-valuenow": value, "aria-valuemin": min, "aria-valuemax": max, "aria-disabled": disabled, "aria-readonly": readonly, onPointerDown: (event) => {
678
+ }, width: size, height: size, tabIndex: 0, role: "slider", "aria-valuenow": value, "aria-valuemin": min, "aria-valuemax": max, "aria-disabled": disabled, "aria-readonly": readonly, onPointerDown: (event) => {
673
679
  pointerDownHandler(event);
674
680
  onPointerDown === null || onPointerDown === void 0 ? void 0 : onPointerDown(event);
675
681
  }, onDoubleClick: (event) => {
@@ -1059,21 +1065,73 @@ const NumberInput = React.forwardRef((_a, ref) => {
1059
1065
  tabIndex: 0, style: Object.assign({}, colors), "data-stepper": !!children, "data-variant": variant, children: [jsxRuntime.jsx(InternalInput, Object.assign({ ref: ref, readonly: readonly, disabled: disabled, selectWithFocus: selectWithFocus, blurOnEnter: blurOnEnter, clampValueOnBlur: clampValueOnBlur, wheel: wheel, keyboard: keyboard, className: className, onChange: onChange, onFocus: onFocus, onBlur: onBlur }, props)), children] }) }));
1060
1066
  });
1061
1067
 
1068
+ /**
1069
+ * @returns [refCallback, pointerDownHandler]
1070
+ */
1071
+ function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd, }) {
1072
+ const dragged = React.useRef(false);
1073
+ const normalizedX = React.useRef(0);
1074
+ const normalizedY = React.useRef(0);
1075
+ const handleDrag = React.useCallback((event) => {
1076
+ if (!baseElementRef.current || !dragged.current) {
1077
+ return;
1078
+ }
1079
+ const isTouch = event instanceof TouchEvent;
1080
+ if (isTouch && event.cancelable)
1081
+ event.preventDefault();
1082
+ const { left, top, right, bottom } = baseElementRef.current.getBoundingClientRect();
1083
+ const mouseX = isTouch ? event.touches[0].clientX : event.clientX;
1084
+ const mouseY = isTouch ? event.touches[0].clientY : event.clientY;
1085
+ const nx = normalizeValue(mouseX, left, right);
1086
+ const ny = normalizeValue(mouseY, top, bottom);
1087
+ normalizedX.current = nx;
1088
+ normalizedY.current = ny;
1089
+ onDrag(nx, ny);
1090
+ }, [onDrag, baseElementRef]);
1091
+ const refHandler = useRefCallbackEvent('touchmove', handleDrag, { passive: false }, [onDrag]);
1092
+ const pointerDownHandler = React.useCallback((event) => {
1093
+ dragged.current = true;
1094
+ handleDrag(event);
1095
+ onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart(normalizedX.current, normalizedY.current);
1096
+ }, [handleDrag, onDragStart]);
1097
+ useEventListener(globalThis.window, 'pointermove', handleDrag);
1098
+ useEventListener(globalThis.window, 'pointerup', () => {
1099
+ if (!dragged.current)
1100
+ return;
1101
+ dragged.current = false;
1102
+ onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(normalizedX.current, normalizedY.current);
1103
+ });
1104
+ return [refHandler, pointerDownHandler];
1105
+ }
1106
+
1062
1107
  /** @category Piano */
1063
1108
  function KeyLabel(_a) {
1064
- var { label, className, wrapperClassName, wrapperStyle, __note, __index, __label } = _a, props = __rest(_a, ["label", "className", "wrapperClassName", "wrapperStyle", "__note", "__index", "__label"]);
1109
+ var { label, className, wrapperClassName, wrapperStyle, __note, __label } = _a, props = __rest(_a, ["label", "className", "wrapperClassName", "wrapperStyle", "__note", "__label"]);
1110
+ const noteRange = usePianoContext((s) => s.noteRange);
1111
+ const noteRangeArray = getNoteRangeArray(noteRange);
1112
+ const index = noteRangeArray.indexOf(__note);
1065
1113
  const content = label
1066
- ? label(__note, __index)
1067
- : __label && __label(__note, __index);
1114
+ ? label(__note, index)
1115
+ : __label && __label(__note, index);
1068
1116
  return ((content != undefined || content != null) && (jsxRuntime.jsx("div", { className: clsx('tremolo-piano-key-label-wrapper', wrapperClassName), style: wrapperStyle, children: jsxRuntime.jsx("div", Object.assign({ className: clsx('tremolo-piano-key-label', className) }, props, { children: content })) })));
1069
1117
  }
1070
1118
 
1071
1119
  /** @category Piano */
1072
1120
  const defaultWhiteKeyWidth = 40;
1073
1121
  /** @category Piano */
1074
- const WhiteKey = React.forwardRef((_a, ref) => {
1075
- var { width = defaultWhiteKeyWidth, height = '100%', bg, color, activeBg, activeColor, className, style, children, __glissando, __position, __note, __disabled, __index, __fill, __width, __onPlayNote, __onStopNote, __label, onContextMenu = (e) => e.preventDefault() } = _a, props = __rest(_a, ["width", "height", "bg", "color", "activeBg", "activeColor", "className", "style", "children", "__glissando", "__position", "__note", "__disabled", "__index", "__fill", "__width", "__onPlayNote", "__onStopNote", "__label", "onContextMenu"]);
1122
+ const defaultBlackKeyWidth = 26;
1123
+ const KeyImpl = React.forwardRef((_a, ref) => {
1124
+ var { keyType, noteNumber, width, height, bg, color, activeBg, activeColor, className, style, children, __width } = _a, props = __rest(_a, ["keyType", "noteNumber", "width", "height", "bg", "color", "activeBg", "activeColor", "className", "style", "children", "__width"]);
1076
1125
  const [played, setPlayed] = React.useState(false);
1126
+ const midiMax = usePianoContext((s) => s.midiMax);
1127
+ const fill = usePianoContext((s) => s.fill);
1128
+ // const glissando = usePianoContext(s => s.glissando)
1129
+ const onPlayNote = usePianoContext((s) => s.onPlayNote);
1130
+ const onStopNote = usePianoContext((s) => s.onStopNote);
1131
+ const label = usePianoContext((s) => s.label);
1132
+ const notePosition = usePianoContext((s) => s.notePosition);
1133
+ const position = notePosition(noteNumber);
1134
+ const disabled = noteNumber > midiMax;
1077
1135
  const colors = {
1078
1136
  '--bg': bg,
1079
1137
  '--color': color,
@@ -1083,22 +1141,22 @@ const WhiteKey = React.forwardRef((_a, ref) => {
1083
1141
  React.useImperativeHandle(ref, () => {
1084
1142
  return {
1085
1143
  play() {
1086
- if (__disabled)
1144
+ if (disabled)
1087
1145
  return;
1088
1146
  setPlayed(true);
1089
- if (__onPlayNote)
1090
- __onPlayNote(__note);
1147
+ if (onPlayNote)
1148
+ onPlayNote(noteNumber);
1091
1149
  },
1092
1150
  stop() {
1093
1151
  setPlayed(false);
1094
- if (__onStopNote)
1095
- __onStopNote(__note);
1152
+ if (onStopNote)
1153
+ onStopNote(noteNumber);
1096
1154
  },
1097
1155
  played() {
1098
1156
  return played;
1099
1157
  },
1100
1158
  };
1101
- }, [__disabled, __note, __onPlayNote, __onStopNote, played]);
1159
+ }, [disabled, noteNumber, onPlayNote, onStopNote, played]);
1102
1160
  let keyLabelProps = {};
1103
1161
  if (children != undefined) {
1104
1162
  React.Children.forEach(children, (child) => {
@@ -1115,112 +1173,82 @@ const WhiteKey = React.forwardRef((_a, ref) => {
1115
1173
  }
1116
1174
  });
1117
1175
  }
1118
- return (jsxRuntime.jsx("div", Object.assign({ className: clsx(`tremolo-piano-white-key`, className), "aria-disabled": __disabled, "data-active": played, style: Object.assign(Object.assign(Object.assign({}, colors), { left: __position, width: __fill ? __width : (width !== null && width !== void 0 ? width : __width), height: height }), style), onPointerDown: () => {
1119
- if (__disabled)
1120
- return;
1121
- setPlayed(true);
1122
- if (__onPlayNote)
1123
- __onPlayNote(__note);
1124
- }, onPointerEnter: () => {
1125
- if (__disabled)
1126
- return;
1127
- if (__glissando) {
1128
- setPlayed(true);
1129
- if (__onPlayNote)
1130
- __onPlayNote(__note);
1131
- }
1132
- }, onPointerUp: () => {
1133
- if (__disabled)
1134
- return;
1135
- setPlayed(false);
1136
- if (__onStopNote)
1137
- __onStopNote(__note);
1138
- }, onPointerLeave: () => {
1139
- if (__disabled)
1140
- return;
1141
- if (__glissando) {
1142
- setPlayed(false);
1143
- if (__onStopNote)
1144
- __onStopNote(__note);
1145
- }
1146
- }, onContextMenu: onContextMenu }, props, { children: jsxRuntime.jsx(KeyLabel, Object.assign({ __note: __note, __label: __label, __index: __index }, keyLabelProps)) })));
1176
+ return (jsxRuntime.jsx("div", Object.assign({ className: clsx(`tremolo-piano-${keyType}-key`, className), "aria-disabled": disabled, "data-active": played, style: Object.assign(Object.assign(Object.assign({}, colors), { left: position, width: fill ? __width : width, height: height }), style) }, props, { children: jsxRuntime.jsx(KeyLabel, Object.assign({ label: label, __note: noteNumber }, keyLabelProps)) })));
1177
+ });
1178
+ /** @category Piano */
1179
+ const WhiteKey = React.forwardRef((_a, ref) => {
1180
+ var { width = defaultWhiteKeyWidth, height = '100%', onContextMenu = (e) => e.preventDefault() } = _a, props = __rest(_a, ["width", "height", "onContextMenu"]);
1181
+ return (jsxRuntime.jsx(KeyImpl, Object.assign({ ref: ref, keyType: "white", width: width, height: height, onContextMenu: onContextMenu }, props)));
1147
1182
  });
1148
-
1149
1183
  /** @category Piano */
1150
1184
  const BlackKey = React.forwardRef((_a, ref) => {
1151
- var { width = defaultWhiteKeyWidth * 0.65, height = '60%', bg, color, activeBg, activeColor, className, style, children, __glissando, __position, __note, __disabled, __index, __fill, __width, __onPlayNote, __onStopNote, __label, onContextMenu = (e) => e.preventDefault() } = _a, props = __rest(_a, ["width", "height", "bg", "color", "activeBg", "activeColor", "className", "style", "children", "__glissando", "__position", "__note", "__disabled", "__index", "__fill", "__width", "__onPlayNote", "__onStopNote", "__label", "onContextMenu"]);
1152
- const [played, setPlayed] = React.useState(false);
1153
- const colors = {
1154
- '--bg': bg,
1155
- '--color': color,
1156
- '--active-bg': activeBg,
1157
- '--active-color': activeColor,
1185
+ var { width = defaultBlackKeyWidth, height = '60%', onContextMenu = (e) => e.preventDefault() } = _a, props = __rest(_a, ["width", "height", "onContextMenu"]);
1186
+ return (jsxRuntime.jsx(KeyImpl, Object.assign({ ref: ref, keyType: "black", width: width, height: height, onContextMenu: onContextMenu }, props)));
1187
+ });
1188
+
1189
+ const createPianoStore = (initProps) => {
1190
+ const noteRange = (initProps === null || initProps === void 0 ? void 0 : initProps.noteRange) || { first: 0};
1191
+ const DEFAULT_PROPS = {
1192
+ noteRange: { first: 0, last: 127 },
1193
+ glissando: true,
1194
+ midiMax: 127,
1195
+ fill: false,
1196
+ label: () => undefined,
1197
+ onPlayNote: () => { },
1198
+ onStopNote: () => { },
1158
1199
  };
1159
- React.useImperativeHandle(ref, () => {
1160
- return {
1161
- play() {
1162
- if (__disabled)
1163
- return;
1164
- setPlayed(true);
1165
- if (__onPlayNote)
1166
- __onPlayNote(__note);
1167
- },
1168
- stop() {
1169
- setPlayed(false);
1170
- if (__onStopNote)
1171
- __onStopNote(__note);
1172
- },
1173
- played() {
1174
- return played;
1175
- },
1176
- };
1177
- }, [__disabled, __note, __onPlayNote, __onStopNote, played]);
1178
- let keyLabelProps = {};
1179
- if (children != undefined) {
1180
- React.Children.forEach(children, (child) => {
1181
- if (React.isValidElement(child)) {
1182
- if (child.type == KeyLabel) {
1183
- keyLabelProps = child.props;
1184
- }
1185
- else {
1186
- throw new Error('only <KeyLabel>');
1187
- }
1188
- }
1189
- else {
1190
- throw new Error('children is an invalid element.');
1191
- }
1192
- });
1200
+ return createStore()(() => (Object.assign(Object.assign(Object.assign({}, DEFAULT_PROPS), initProps), { notePosition: (note) => {
1201
+ const pitchPositions = {
1202
+ C: 0,
1203
+ 'C#': 1,
1204
+ D: 1,
1205
+ 'D#': 2,
1206
+ E: 2,
1207
+ F: 3,
1208
+ 'F#': 4,
1209
+ G: 4,
1210
+ 'G#': 5,
1211
+ A: 5,
1212
+ 'A#': 6,
1213
+ B: 6,
1214
+ };
1215
+ const whiteNoteWidth = defaultWhiteKeyWidth;
1216
+ const blackNoteWidth = defaultBlackKeyWidth;
1217
+ const padding = 1;
1218
+ const targetNoteKey = noteKey(note);
1219
+ const firstNoteKey = noteKey(noteRange.first);
1220
+ const octave = Math.floor((note - noteRange.first) / 12);
1221
+ const octaveOffset = noteKeys.indexOf(firstNoteKey) > noteKeys.indexOf(targetNoteKey) ? 1 : 0;
1222
+ const w = whiteNoteWidth + padding;
1223
+ const pos = pitchPositions[targetNoteKey] - pitchPositions[firstNoteKey];
1224
+ const blackKeyOffset = isBlackKey(note) ? blackNoteWidth / 2 : 0;
1225
+ return pos * w + (octave + octaveOffset) * 7 * w - blackKeyOffset;
1226
+ } })));
1227
+ };
1228
+ const PianoContext = React.createContext(null);
1229
+ function PianoProvider(_a) {
1230
+ var { children } = _a, props = __rest(_a, ["children"]);
1231
+ const storeRef = React.useRef(null);
1232
+ if (!storeRef.current) {
1233
+ storeRef.current = createPianoStore(props);
1193
1234
  }
1194
- return (jsxRuntime.jsx("div", Object.assign({ className: clsx(`tremolo-piano-black-key`, className), "aria-disabled": __disabled, "data-active": played, style: Object.assign(Object.assign(Object.assign({}, colors), { left: __position, width: __fill ? __width : (width !== null && width !== void 0 ? width : __width), height: height }), style), onPointerDown: () => {
1195
- if (__disabled)
1196
- return;
1197
- setPlayed(true);
1198
- if (__onPlayNote)
1199
- __onPlayNote(__note);
1200
- }, onPointerEnter: () => {
1201
- if (__disabled)
1202
- return;
1203
- if (__glissando) {
1204
- setPlayed(true);
1205
- if (__onPlayNote)
1206
- __onPlayNote(__note);
1207
- }
1208
- }, onPointerUp: () => {
1209
- if (__disabled)
1210
- return;
1211
- setPlayed(false);
1212
- if (__onStopNote)
1213
- __onStopNote(__note);
1214
- }, onPointerLeave: () => {
1215
- if (__disabled)
1216
- return;
1217
- if (__glissando) {
1218
- setPlayed(false);
1219
- if (__onStopNote)
1220
- __onStopNote(__note);
1221
- }
1222
- }, onContextMenu: onContextMenu }, props, { children: jsxRuntime.jsx(KeyLabel, Object.assign({ __note: __note, __label: __label, __index: __index }, keyLabelProps)) })));
1223
- });
1235
+ React.useEffect(() => {
1236
+ if (storeRef.current) {
1237
+ storeRef.current.setState(props);
1238
+ }
1239
+ else {
1240
+ storeRef.current = createPianoStore(props);
1241
+ }
1242
+ }, [props]);
1243
+ return (jsxRuntime.jsx(PianoContext.Provider, { value: storeRef.current, children: children }));
1244
+ }
1245
+ /** @category Piano */
1246
+ function usePianoContext(selector) {
1247
+ const store = React.useContext(PianoContext);
1248
+ if (!store)
1249
+ throw new Error('Missing PianoContext.Provider in the tree');
1250
+ return useStore(store, selector);
1251
+ }
1224
1252
 
1225
1253
  /** @category Piano */
1226
1254
  const SHORTCUTS = {
@@ -1247,66 +1275,121 @@ const SHORTCUTS = {
1247
1275
  },
1248
1276
  };
1249
1277
 
1278
+ /**
1279
+ * Piano component
1280
+ *
1281
+ * TODO:
1282
+ * - add scale highlight
1283
+ */
1284
+ /** @category Piano */
1285
+ function getNoteRangeArray(noteRange) {
1286
+ return Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
1287
+ }
1250
1288
  /**
1251
1289
  * Customizable piano component.
1252
1290
  * @category Piano
1253
1291
  */
1254
1292
  function Piano(_a) {
1255
- var { noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, height = fill ? '100%' : 160, style, className, onPlayNote, onStopNote, label, children, onPointerUp, onPointerDown } = _a, props = __rest(_a, ["noteRange", "glissando", "midiMax", "keyboardShortcuts", "fill", "height", "style", "className", "onPlayNote", "onStopNote", "label", "children", "onPointerUp", "onPointerDown"]);
1293
+ var { noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, height = fill ? '100%' : 160, whiteNoteWidth: _whiteNoteWidth = defaultWhiteKeyWidth, style, className, onPlayNote, onStopNote, label, children, onPointerDown } = _a, props = __rest(_a, ["noteRange", "glissando", "midiMax", "keyboardShortcuts", "fill", "height", "whiteNoteWidth", "style", "className", "onPlayNote", "onStopNote", "label", "children", "onPointerDown"]);
1256
1294
  // -- state and ref ---
1257
- const [pressed, setPressed] = React.useState(false);
1258
- const [whiteNoteWidth, setWhiteNoteWidth] = React.useState(-1);
1295
+ const [whiteNoteWidth, setWhiteNoteWidth] = React.useState(_whiteNoteWidth);
1259
1296
  const keyRefs = React.useRef([]);
1260
1297
  for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) {
1261
1298
  keyRefs.current[i] = React.createRef();
1262
1299
  }
1263
1300
  const pianoRef = React.useRef(null);
1301
+ const hitKeyIndex = React.useRef(-1);
1264
1302
  // --- interpret props ---
1265
- const noteRangeArray = Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
1266
- const whiteNoteCount = noteRangeArray.filter((v) => isWhiteKey(v)).length;
1267
- let whiteKeyProps = {}, blackKeyProps = {};
1268
- if (children != undefined) {
1269
- React.Children.forEach(children, (child) => {
1270
- if (React.isValidElement(child)) {
1271
- if (child.type == WhiteKey) {
1272
- whiteKeyProps = child.props;
1273
- }
1274
- else if (child.type == BlackKey) {
1275
- blackKeyProps = child.props;
1276
- }
1277
- else {
1278
- throw new Error('only <WhiteKey> or <BlackKey>');
1303
+ const noteRangeArray = getNoteRangeArray(noteRange);
1304
+ const whiteNotes = noteRangeArray.filter((v) => isWhiteKey(v));
1305
+ const blackNotes = noteRangeArray.filter((v) => isBlackKey(v));
1306
+ const whiteNoteCount = whiteNotes.length;
1307
+ const padding = 1;
1308
+ const staticWidth = (whiteNoteWidth + padding) * whiteNoteCount;
1309
+ // const blackNoteShiftPercent = 0.3
1310
+ // TODO: if fill setWhiteNoteWidth
1311
+ // useMemo
1312
+ // __width
1313
+ const childrenWithProps = React.useMemo(() => {
1314
+ return (children &&
1315
+ React.Children.map(children, (child, index) => {
1316
+ if (React.isValidElement(child)) {
1317
+ const props = { ref: keyRefs.current[index] };
1318
+ return React.cloneElement(child, props);
1279
1319
  }
1280
- }
1281
- else {
1282
- throw new Error('children is an invalid element.');
1283
- }
1284
- });
1285
- }
1320
+ return child;
1321
+ }));
1322
+ }, [children]);
1286
1323
  // --- internal functions ---
1287
- function notePosition(note) {
1324
+ const notePosition = React.useCallback((note) => {
1288
1325
  const pitchPositions = {
1289
1326
  C: 0,
1290
- 'C#': 0.55,
1327
+ 'C#': 1,
1291
1328
  D: 1,
1292
- 'D#': 1.8,
1329
+ 'D#': 2,
1293
1330
  E: 2,
1294
1331
  F: 3,
1295
- 'F#': 3.5,
1332
+ 'F#': 4,
1296
1333
  G: 4,
1297
- 'G#': 4.7,
1334
+ 'G#': 5,
1298
1335
  A: 5,
1299
- 'A#': 5.85,
1336
+ 'A#': 6,
1300
1337
  B: 6,
1301
1338
  };
1339
+ const whiteNoteWidth = defaultWhiteKeyWidth;
1340
+ const blackNoteWidth = defaultBlackKeyWidth;
1341
+ const padding = 1;
1302
1342
  const targetNoteKey = noteKey(note);
1303
1343
  const firstNoteKey = noteKey(noteRange.first);
1304
- const pos = pitchPositions[targetNoteKey] - pitchPositions[firstNoteKey];
1305
1344
  const octave = Math.floor((note - noteRange.first) / 12);
1306
1345
  const octaveOffset = noteKeys.indexOf(firstNoteKey) > noteKeys.indexOf(targetNoteKey) ? 1 : 0;
1307
- const length = whiteNoteWidth + 3;
1308
- return pos * length + (octave + octaveOffset) * 7 * length;
1309
- }
1346
+ const w = whiteNoteWidth + padding;
1347
+ const pos = pitchPositions[targetNoteKey] - pitchPositions[firstNoteKey];
1348
+ const blackKeyOffset = isBlackKey(note) ? blackNoteWidth / 2 : 0;
1349
+ return pos * w + (octave + octaveOffset) * 7 * w - blackKeyOffset;
1350
+ }, [noteRange.first]);
1351
+ const getHitKeyIndex = React.useCallback((x, y) => {
1352
+ if (!pianoRef.current)
1353
+ return -1;
1354
+ const containerHeight = pianoRef.current.clientHeight;
1355
+ const notes = [...blackNotes, ...whiteNotes];
1356
+ for (let i = 0; i < notes.length; i++) {
1357
+ const note = notes[i];
1358
+ const pos = notePosition(note);
1359
+ const w = isWhiteKey(note) ? defaultWhiteKeyWidth : defaultBlackKeyWidth;
1360
+ const h = isWhiteKey(note) ? containerHeight : containerHeight * 0.6;
1361
+ if (pos <= x && x < pos + w && 0 <= y && y < h) {
1362
+ return note;
1363
+ }
1364
+ }
1365
+ return -1;
1366
+ }, [blackNotes, notePosition, whiteNotes]);
1367
+ // TODO: 単一のポインターに対しては、useDragで対応可能だが、
1368
+ // マルチタッチに対しては、TouchEventを使う必要がありそう
1369
+ // 取り敢えず、シングルタッチだけ対応
1370
+ const onDrag = React.useCallback((perX, perY) => {
1371
+ var _a, _b, _c, _d;
1372
+ if (!pianoRef.current)
1373
+ return;
1374
+ const x = perX * staticWidth;
1375
+ const y = perY * pianoRef.current.clientHeight;
1376
+ const note = getHitKeyIndex(x, y);
1377
+ const index = noteRangeArray.indexOf(note);
1378
+ if (index == -1)
1379
+ return;
1380
+ if (hitKeyIndex.current != index) {
1381
+ (_b = (_a = keyRefs.current[hitKeyIndex.current]) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.stop();
1382
+ (_d = (_c = keyRefs.current[index]) === null || _c === void 0 ? void 0 : _c.current) === null || _d === void 0 ? void 0 : _d.play();
1383
+ hitKeyIndex.current = index;
1384
+ }
1385
+ }, [getHitKeyIndex, noteRangeArray, staticWidth]);
1386
+ const onDragEnd = React.useCallback(() => {
1387
+ var _a, _b, _c, _d;
1388
+ if ((_b = (_a = keyRefs.current[hitKeyIndex.current]) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.played()) {
1389
+ (_d = (_c = keyRefs.current[hitKeyIndex.current]) === null || _c === void 0 ? void 0 : _c.current) === null || _d === void 0 ? void 0 : _d.stop();
1390
+ }
1391
+ hitKeyIndex.current = -1;
1392
+ }, [keyRefs]);
1310
1393
  // --- hooks ---
1311
1394
  React.useEffect(() => {
1312
1395
  if (fill && pianoRef.current) {
@@ -1315,7 +1398,7 @@ function Piano(_a) {
1315
1398
  throw new Error("doesn't have a parent element.");
1316
1399
  const resizeObserver = new ResizeObserver(() => {
1317
1400
  const w = pianoRef.current.clientWidth;
1318
- setWhiteNoteWidth(w / whiteNoteCount - 3);
1401
+ setWhiteNoteWidth(w / whiteNoteCount - padding);
1319
1402
  });
1320
1403
  resizeObserver.observe(parent);
1321
1404
  return () => {
@@ -1323,9 +1406,14 @@ function Piano(_a) {
1323
1406
  };
1324
1407
  }
1325
1408
  else {
1326
- setWhiteNoteWidth((whiteKeyProps === null || whiteKeyProps === void 0 ? void 0 : whiteKeyProps.width) || defaultWhiteKeyWidth);
1409
+ setWhiteNoteWidth(_whiteNoteWidth);
1327
1410
  }
1328
- }, [fill, whiteKeyProps === null || whiteKeyProps === void 0 ? void 0 : whiteKeyProps.width, whiteNoteCount]);
1411
+ }, [fill, _whiteNoteWidth, whiteNoteCount]);
1412
+ const [touchMoveRefCallback, pointerDownHandler] = useDragWithElement({
1413
+ baseElementRef: pianoRef,
1414
+ onDrag: onDrag,
1415
+ onDragEnd: onDragEnd,
1416
+ });
1329
1417
  useEventListener(globalThis.window, 'keydown', (e) => {
1330
1418
  var _a, _b, _c, _d;
1331
1419
  if (e.repeat)
@@ -1350,15 +1438,14 @@ function Piano(_a) {
1350
1438
  (_b = (_a = keyRefs.current[index]) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.stop();
1351
1439
  }
1352
1440
  });
1353
- return (jsxRuntime.jsx("div", Object.assign({ ref: pianoRef, className: clsx('tremolo-piano', className), style: Object.assign({ width: fill ? '100%' : (whiteNoteWidth + 3) * whiteNoteCount, height: height }, style), onPointerDown: (e) => {
1354
- setPressed(true);
1355
- onPointerDown === null || onPointerDown === void 0 ? void 0 : onPointerDown(e);
1356
- }, onPointerUp: (e) => {
1357
- setPressed(false);
1358
- onPointerUp === null || onPointerUp === void 0 ? void 0 : onPointerUp(e);
1359
- } }, props, { children: noteRangeArray.map((note, index) => {
1360
- return isWhiteKey(note) ? (jsxRuntime.jsx(WhiteKey, Object.assign({ ref: keyRefs.current[index], __glissando: glissando && pressed, __position: notePosition(note), __note: note, __disabled: note > midiMax, __index: index, __fill: fill, __width: whiteNoteWidth, __onPlayNote: onPlayNote, __onStopNote: onStopNote, __label: label }, whiteKeyProps), note)) : (jsxRuntime.jsx(BlackKey, Object.assign({ ref: keyRefs.current[index], __glissando: glissando && pressed, __position: notePosition(note), __note: note, __disabled: note > midiMax, __index: index, __fill: fill, __width: whiteNoteWidth * 0.65, __onPlayNote: onPlayNote, __onStopNote: onStopNote, __label: label }, blackKeyProps), note));
1361
- }) })));
1441
+ return (jsxRuntime.jsx(PianoProvider, { noteRange: noteRange, glissando: glissando, midiMax: midiMax, fill: fill, onPlayNote: onPlayNote, onStopNote: onStopNote, label: label, children: jsxRuntime.jsx("div", Object.assign({ ref: (div) => {
1442
+ pianoRef.current = div;
1443
+ touchMoveRefCallback(div);
1444
+ }, className: clsx('tremolo-piano', className), style: Object.assign({ width: fill ? '100%' : staticWidth, height: height }, style), onPointerDown: (event) => {
1445
+ pointerDownHandler(event);
1446
+ onPointerDown === null || onPointerDown === void 0 ? void 0 : onPointerDown(event);
1447
+ } }, props, { children: childrenWithProps ||
1448
+ getNoteRangeArray(noteRange).map((note, index) => isWhiteKey(note) ? (jsxRuntime.jsx(WhiteKey, { ref: keyRefs.current[index], noteNumber: note }, note)) : (jsxRuntime.jsx(BlackKey, { ref: keyRefs.current[index], noteNumber: note }, note))) })) }));
1362
1449
  }
1363
1450
 
1364
1451
  const createSliderStore = (initProps) => {
@@ -1529,7 +1616,6 @@ const Slider = React.forwardRef((_a, ref) => {
1529
1616
  // -- state and ref ---
1530
1617
  const trackElementRef = React.useRef(null);
1531
1618
  const thumbRef = React.useRef(null);
1532
- const thumbDragged = React.useRef(false);
1533
1619
  // --- interpret props ---
1534
1620
  const p = toFixed$1(normalizeValue(value, min, max, skew) * 100);
1535
1621
  const rev = toFixed$1(100 - p);
@@ -1565,29 +1651,14 @@ const Slider = React.forwardRef((_a, ref) => {
1565
1651
  });
1566
1652
  }
1567
1653
  // --- internal functions ---
1568
- const handleValue = (event) => {
1569
- if (!trackElementRef.current ||
1570
- !thumbDragged.current ||
1571
- !onChange ||
1572
- readonly) {
1654
+ const onDrag = React.useCallback((nx, ny) => {
1655
+ if (!onChange || readonly)
1573
1656
  return;
1574
- }
1575
- const isTouch = event instanceof TouchEvent;
1576
- if (isTouch && event.cancelable)
1577
- event.preventDefault();
1578
- if (bodyNoSelect)
1579
- addNoSelect();
1580
- const { left: x1, top: y1, right: x2, bottom: y2, } = trackElementRef.current.getBoundingClientRect();
1581
- const mouseX = isTouch ? event.touches[0].clientX : event.clientX;
1582
- const mouseY = isTouch ? event.touches[0].clientY : event.clientY;
1583
- const n = vertical
1584
- ? normalizeValue(mouseY, y1, y2)
1585
- : normalizeValue(mouseX, x1, x2);
1657
+ const n = vertical ? ny : nx;
1586
1658
  const v = rawValue(displayReversed ? 1 - n : n, min, max, skew);
1587
1659
  const v2 = clamp(stepValue(v, step), min, max);
1588
1660
  onChange(v2);
1589
- return v2;
1590
- };
1661
+ }, [displayReversed, max, min, onChange, readonly, skew, step, vertical]);
1591
1662
  const updateValueByEvent = React.useCallback((eventType, x) => {
1592
1663
  let newValue;
1593
1664
  if (eventType == 'normalized') {
@@ -1612,7 +1683,26 @@ const Slider = React.forwardRef((_a, ref) => {
1612
1683
  }
1613
1684
  }, [keyboard, onChange, readonly, reverse, updateValueByEvent]);
1614
1685
  // --- hooks ---
1615
- const touchMoveRefCallback = useRefCallbackEvent('touchmove', handleValue, { passive: false }, [min, max, skew, step, vertical, reverse, onChange, readonly]);
1686
+ const [touchMoveRefCallback, pointerDownHandler] = useDragWithElement({
1687
+ baseElementRef: trackElementRef,
1688
+ onDrag: onDrag,
1689
+ onDragStart: (nx, ny) => {
1690
+ var _a;
1691
+ if (readonly)
1692
+ return;
1693
+ if (bodyNoSelect)
1694
+ addNoSelect();
1695
+ (_a = thumbRef.current) === null || _a === void 0 ? void 0 : _a.focus();
1696
+ onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart(clamp(stepValue(rawValue(vertical ? ny : nx, min, max, skew), step), min, max));
1697
+ },
1698
+ onDragEnd: (nx, ny) => {
1699
+ if (readonly)
1700
+ return;
1701
+ if (bodyNoSelect)
1702
+ removeNoSelect();
1703
+ onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(clamp(stepValue(rawValue(vertical ? ny : nx, min, max, skew), step), min, max));
1704
+ },
1705
+ });
1616
1706
  const wheelRefCallback = useRefCallbackEvent('wheel', (event) => {
1617
1707
  if (!wheel || !onChange || readonly)
1618
1708
  return;
@@ -1632,17 +1722,6 @@ const Slider = React.forwardRef((_a, ref) => {
1632
1722
  }, {
1633
1723
  passive: false,
1634
1724
  }, [wheel, vertical, readonly, onChange, updateValueByEvent]);
1635
- useEventListener(globalThis.window, 'mousemove', (event) => {
1636
- handleValue(event);
1637
- });
1638
- useEventListener(globalThis.window, 'mouseup', () => {
1639
- if (!thumbDragged.current || readonly)
1640
- return;
1641
- thumbDragged.current = false;
1642
- onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(value);
1643
- if (bodyNoSelect)
1644
- removeNoSelect();
1645
- });
1646
1725
  React.useImperativeHandle(ref, () => {
1647
1726
  return {
1648
1727
  focus() {
@@ -1659,10 +1738,7 @@ const Slider = React.forwardRef((_a, ref) => {
1659
1738
  wheelRefCallback(div);
1660
1739
  touchMoveRefCallback(div);
1661
1740
  }, tabIndex: -1, role: "slider", "aria-valuenow": value, "aria-valuemin": min, "aria-valuemax": max, "aria-orientation": vertical ? 'vertical' : 'horizontal', "aria-disabled": disabled, "aria-readonly": readonly, style: Object.assign({ margin: `calc(${styleHelper((_b = thumbProps === null || thumbProps === void 0 ? void 0 : thumbProps.size) !== null && _b !== void 0 ? _b : 22)} / 2)` }, style), onPointerDown: (event) => {
1662
- thumbDragged.current = true;
1663
- const v = handleValue(event);
1664
- if (!readonly)
1665
- onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart(v !== null && v !== void 0 ? v : value);
1741
+ pointerDownHandler(event);
1666
1742
  onPointerDown === null || onPointerDown === void 0 ? void 0 : onPointerDown(event);
1667
1743
  }, onKeyDown: (event) => {
1668
1744
  handleKeyDown(event);
@@ -1745,13 +1821,13 @@ const XYPad = React.forwardRef((_a, ref) => {
1745
1821
  // -- state and ref ---
1746
1822
  const areaElementRef = React.useRef(null);
1747
1823
  const thumbRef = React.useRef(null);
1748
- const thumbDragged = React.useRef(false);
1749
1824
  // --- interpret props ---
1750
1825
  const nx = normalizeValue(x.value, x.min, x.max, x.skew);
1751
1826
  const ny = normalizeValue(y.value, y.min, y.max, y.skew);
1752
1827
  const percentX = toFixed$1((x.reverse ? 1 - nx : nx) * 100);
1753
1828
  const percentY = toFixed$1((y.reverse ? 1 - ny : ny) * 100);
1754
- let areaProps = {}, thumbProps = {};
1829
+ let areaProps = {};
1830
+ let thumbProps = {};
1755
1831
  if (children != undefined) {
1756
1832
  React.Children.forEach(children, (child) => {
1757
1833
  if (React.isValidElement(child)) {
@@ -1771,29 +1847,28 @@ const XYPad = React.forwardRef((_a, ref) => {
1771
1847
  });
1772
1848
  }
1773
1849
  // --- internal functions ---
1774
- const handleValue = (event) => {
1775
- if (!areaElementRef.current ||
1776
- !thumbDragged.current ||
1777
- !onChange ||
1778
- readonly)
1850
+ const onDrag = React.useCallback((nx, ny) => {
1851
+ if (!onChange || readonly)
1779
1852
  return;
1780
- const isTouch = event instanceof TouchEvent;
1781
- if (isTouch && event.cancelable)
1782
- event.preventDefault();
1783
- if (bodyNoSelect)
1784
- addNoSelect();
1785
- const { left: x1, top: y1, right: x2, bottom: y2, } = areaElementRef.current.getBoundingClientRect();
1786
- const mouseX = isTouch ? event.touches[0].clientX : event.clientX;
1787
- const mouseY = isTouch ? event.touches[0].clientY : event.clientY;
1788
- const nx = normalizeValue(mouseX, x1, x2);
1789
- const ny = normalizeValue(mouseY, y1, y2);
1790
1853
  const vx = rawValue(x.reverse ? 1 - nx : nx, x.min, x.max, x.skew);
1791
1854
  const vy = rawValue(y.reverse ? 1 - ny : ny, y.min, y.max, y.skew);
1792
1855
  const newX = clamp(stepValue(vx, x.step), x.min, x.max);
1793
1856
  const newY = clamp(stepValue(vy, y.step), y.min, y.max);
1794
1857
  onChange(newX, newY);
1795
- return [newX, newY];
1796
- };
1858
+ }, [
1859
+ onChange,
1860
+ readonly,
1861
+ x.max,
1862
+ x.min,
1863
+ x.reverse,
1864
+ x.skew,
1865
+ x.step,
1866
+ y.max,
1867
+ y.min,
1868
+ y.reverse,
1869
+ y.skew,
1870
+ y.step,
1871
+ ]);
1797
1872
  const updateValueByEvent = React.useCallback((eventType, target, x) => {
1798
1873
  let v;
1799
1874
  if (eventType == 'normalized') {
@@ -1830,7 +1905,30 @@ const XYPad = React.forwardRef((_a, ref) => {
1830
1905
  }
1831
1906
  }, [onChange, readonly, x, y, updateValueByEvent]);
1832
1907
  // --- hooks ---
1833
- const touchMoveRefCallback = useRefCallbackEvent('touchmove', handleValue, { passive: false }, [x, onChange, readonly]);
1908
+ const [touchMoveRefCallback, pointerDownHandler] = useDragWithElement({
1909
+ baseElementRef: areaElementRef,
1910
+ onDrag: onDrag,
1911
+ onDragStart: (nx, ny) => {
1912
+ var _a;
1913
+ if (readonly)
1914
+ return;
1915
+ if (bodyNoSelect)
1916
+ addNoSelect();
1917
+ (_a = thumbRef.current) === null || _a === void 0 ? void 0 : _a.focus();
1918
+ const valueX = clamp(stepValue(rawValue(nx, x.min, x.max, x.skew), x.step), x.min, x.max);
1919
+ const valueY = clamp(stepValue(rawValue(ny, y.min, y.max, y.skew), y.step), y.min, y.max);
1920
+ onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart(valueX, valueY);
1921
+ },
1922
+ onDragEnd: (nx, ny) => {
1923
+ if (readonly)
1924
+ return;
1925
+ if (bodyNoSelect)
1926
+ removeNoSelect();
1927
+ const valueX = clamp(stepValue(rawValue(nx, x.min, x.max, x.skew), x.step), x.min, x.max);
1928
+ const valueY = clamp(stepValue(rawValue(ny, y.min, y.max, y.skew), y.step), y.min, y.max);
1929
+ onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(valueX, valueY);
1930
+ },
1931
+ });
1834
1932
  const wheelRefCallback = useRefCallbackEvent('wheel', (event) => {
1835
1933
  if (!onChange || readonly)
1836
1934
  return;
@@ -1853,17 +1951,6 @@ const XYPad = React.forwardRef((_a, ref) => {
1853
1951
  }, {
1854
1952
  passive: false,
1855
1953
  }, [x, y, onChange, readonly, updateValueByEvent]);
1856
- useEventListener(globalThis.window, 'mousemove', (event) => {
1857
- handleValue(event);
1858
- });
1859
- useEventListener(globalThis.window, 'mouseup', () => {
1860
- if (!thumbDragged.current || readonly)
1861
- return;
1862
- thumbDragged.current = false;
1863
- onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(x.value, y.value);
1864
- if (bodyNoSelect)
1865
- removeNoSelect();
1866
- });
1867
1954
  React.useImperativeHandle(ref, () => {
1868
1955
  return {
1869
1956
  focus() {
@@ -1882,11 +1969,7 @@ const XYPad = React.forwardRef((_a, ref) => {
1882
1969
  wheelRefCallback(div);
1883
1970
  touchMoveRefCallback(div);
1884
1971
  }, tabIndex: -1, "aria-disabled": disabled, "aria-readonly": readonly, style: Object.assign({ margin: `calc(${styleHelper((_b = thumbProps === null || thumbProps === void 0 ? void 0 : thumbProps.size) !== null && _b !== void 0 ? _b : 22)} / 2)`, cursor: readonly ? 'not-allowed' : 'pointer', WebkitTapHighlightColor: 'transparent' }, style), onPointerDown: (event) => {
1885
- var _a, _b;
1886
- thumbDragged.current = true;
1887
- const v = handleValue(event);
1888
- if (!readonly)
1889
- onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart((_a = v === null || v === void 0 ? void 0 : v[0]) !== null && _a !== void 0 ? _a : x.value, (_b = v === null || v === void 0 ? void 0 : v[1]) !== null && _b !== void 0 ? _b : y.value);
1972
+ pointerDownHandler(event);
1890
1973
  onPointerDown === null || onPointerDown === void 0 ? void 0 : onPointerDown(event);
1891
1974
  }, onKeyDown: (event) => {
1892
1975
  handleKeyDown(event);
@@ -1940,6 +2023,7 @@ exports.WhiteKey = WhiteKey;
1940
2023
  exports.XYPad = XYPad;
1941
2024
  exports.XYPadArea = XYPadArea;
1942
2025
  exports.XYPadThumb = XYPadThumb;
2026
+ exports.getNoteRangeArray = getNoteRangeArray;
1943
2027
  exports.useAnimationFrame = useAnimationFrame;
1944
2028
  exports.useDrag = useDrag;
1945
2029
  exports.useEventListener = useEventListener;