@tremolo-ui/react 0.1.0-alpha.2 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -61,23 +61,52 @@ const drawingState = [
61
61
  'imageSmoothingEnabled',
62
62
  ];
63
63
 
64
+ /*
65
+ TODO
66
+ AbsoluteSizingProps, RelativeSizingPropsの型付けを強くする
67
+ AbsoluteSizingPropsの場合、width, heightは必須
68
+ RelativeSizingPropsのの場合、relativeSizeは必須、reduceFlickeringはオプショナル
69
+ */
64
70
  function setDprConfig(canvas, context, width, height, dpr) {
65
71
  canvas.width = width * dpr;
66
72
  canvas.height = height * dpr;
73
+ // Reset current transformation matrix to the identity matrix
74
+ context.setTransform(1, 0, 0, 1, 0, 0);
67
75
  context.scale(dpr, dpr);
68
76
  canvas.style.width = `${width}px`;
69
77
  canvas.style.height = `${height}px`;
70
78
  }
71
79
  function AnimationCanvas(_a) {
72
- var { width = 100, height = 100, relativeSize = false, reduceFlickering = true, options, init, draw, className, onContextMenu = (event) => event.preventDefault() } = _a, props = __rest(_a, ["width", "height", "relativeSize", "reduceFlickering", "options", "init", "draw", "className", "onContextMenu"]);
80
+ var {
81
+ // common
82
+ options, init, draw,
83
+ // absolute
84
+ width = 100, height = 100,
85
+ // relative
86
+ relativeSize, reduceFlickering = true,
87
+ // canvas props
88
+ className, onContextMenu = (event) => event.preventDefault() } = _a, props = __rest(_a, ["options", "init", "draw", "width", "height", "relativeSize", "reduceFlickering", "className", "onContextMenu"]);
73
89
  const canvasRef = React.useRef(null);
74
90
  const memoCanvasRef = React.useRef(null);
75
91
  const reqIdRef = React.useRef(-1);
76
92
  const widthRef = React.useRef(0);
77
93
  const heightRef = React.useRef(0);
94
+ const deltaMemoRef = React.useRef(-1);
95
+ const startTimeRef = React.useRef(-1);
78
96
  const loop = React.useCallback((context, width, height, count) => {
97
+ const now = performance.now();
98
+ const deltaTime = now - deltaMemoRef.current;
99
+ const elapsedTime = now - startTimeRef.current;
100
+ deltaMemoRef.current = now;
79
101
  reqIdRef.current = requestAnimationFrame(() => loop(context, width, height, count + 1));
80
- draw(context, width, height, count + 1);
102
+ draw(context, {
103
+ width: width.current,
104
+ height: height.current,
105
+ count: count + 1,
106
+ deltaTime,
107
+ elapsedTime,
108
+ fps: 1000 / deltaTime,
109
+ });
81
110
  }, [draw]);
82
111
  React.useEffect(() => {
83
112
  if (!canvasRef.current)
@@ -86,26 +115,22 @@ function AnimationCanvas(_a) {
86
115
  const context = canvas.getContext('2d', options);
87
116
  if (!context)
88
117
  throw new Error('Cannot get canvas context.');
89
- const dpr = globalThis.window.devicePixelRatio;
118
+ const dpr = globalThis.devicePixelRatio;
90
119
  if (relativeSize) {
91
120
  const parent = canvas.parentElement;
92
121
  if (!parent)
93
122
  throw new Error("Canvas doesn't have a parent element.");
94
123
  const memoCanvas = memoCanvasRef.current;
95
124
  const memoContext = memoCanvas === null || memoCanvas === void 0 ? void 0 : memoCanvas.getContext('2d', options);
96
- const resizeObserver = new ResizeObserver((entries) => {
97
- // TODO: If the parent element has no absolute height, the height increases infinitely.
98
- // : 親要素に絶対的な高さを持たない場合、高さは無限に増加します。
99
- // https://zenn.dev/megeton/articles/be1c677e174c84#%E6%A0%B9%E6%9C%AC%E7%9A%84%E3%81%AB-resizeobserver-loop-limit-exceeded-%E3%81%8C%E5%87%BA%E3%82%8B%E5%95%8F%E9%A1%8C%E3%81%AB%E5%90%91%E3%81%8D%E5%90%88%E3%81%86
125
+ const ro = new ResizeObserver((entries) => {
100
126
  for (const entry of entries) {
101
127
  // Prevents loss of some context when the canvas is resized
102
- // TODO: saving scale and translate
103
128
  const contextMemo = {};
104
129
  for (const prop of drawingState) {
105
130
  contextMemo[prop] = context[prop];
106
131
  }
107
- const w = Math.floor(entry.contentRect.width);
108
- const h = Math.floor(entry.contentRect.height);
132
+ const w = entry.contentRect.width;
133
+ const h = entry.contentRect.height;
109
134
  if (reduceFlickering && memoCanvas && memoContext) {
110
135
  memoCanvas.width = w * dpr;
111
136
  memoCanvas.height = h * dpr;
@@ -129,17 +154,20 @@ function AnimationCanvas(_a) {
129
154
  }
130
155
  }
131
156
  });
132
- resizeObserver.observe(parent);
133
- const w = Math.floor(parent.clientWidth);
134
- const h = Math.floor(parent.clientHeight);
157
+ ro.observe(parent);
158
+ const w = parent.clientWidth;
159
+ const h = parent.clientHeight;
135
160
  setDprConfig(canvas, context, w, h, dpr);
136
161
  if (init)
137
- init(context, widthRef, heightRef, 0);
138
- loop(context, widthRef, heightRef, 0);
162
+ init(context, { width: widthRef.current, height: heightRef.current });
163
+ const now = performance.now();
164
+ deltaMemoRef.current = now;
165
+ startTimeRef.current = now;
166
+ loop(context, widthRef, heightRef, -1);
139
167
  return () => {
140
168
  if (reqIdRef.current)
141
169
  cancelAnimationFrame(reqIdRef.current);
142
- resizeObserver.unobserve(parent);
170
+ ro.disconnect();
143
171
  };
144
172
  }
145
173
  else {
@@ -148,8 +176,11 @@ function AnimationCanvas(_a) {
148
176
  widthRef.current = rect.width;
149
177
  heightRef.current = rect.height;
150
178
  if (init)
151
- init(context, widthRef, heightRef, 0);
152
- loop(context, widthRef, heightRef, 0);
179
+ init(context, { width: widthRef.current, height: heightRef.current });
180
+ const now = performance.now();
181
+ deltaMemoRef.current = now;
182
+ startTimeRef.current = now;
183
+ loop(context, widthRef, heightRef, -1);
153
184
  return () => {
154
185
  if (reqIdRef.current)
155
186
  cancelAnimationFrame(reqIdRef.current);
@@ -527,16 +558,16 @@ const AnimationKnob = React.forwardRef((_a, ref) => {
527
558
  height: size !== null && size !== void 0 ? size : height,
528
559
  }), { draw: draw
529
560
  ? draw
530
- : (ctx, width, height) => {
561
+ : (ctx, { width, height }) => {
531
562
  const p = normalizeValue(value, min, max, skew);
532
563
  const startP = normalizeValue(startValue, min, max, skew);
533
- const w = width.current, cx = w / 2;
534
- const h = height.current, cy = h / 2;
564
+ const cx = width / 2;
565
+ const cy = height / 2;
535
566
  const r = Math.min(cx, cy);
536
567
  if (r <= 0)
537
568
  return;
538
569
  // reset
539
- ctx.clearRect(0, 0, w, h);
570
+ ctx.clearRect(0, 0, width, height);
540
571
  ctx.lineCap = 'butt';
541
572
  // bg
542
573
  ctx.beginPath();
@@ -719,14 +750,14 @@ const createStoreImpl = (createState) => {
719
750
  const initialState = state = createState(setState, getState, api);
720
751
  return api;
721
752
  };
722
- const createStore = (createState) => createState ? createStoreImpl(createState) : createStoreImpl;
753
+ const createStore = ((createState) => createState ? createStoreImpl(createState) : createStoreImpl);
723
754
 
724
755
  const identity = (arg) => arg;
725
756
  function useStore(api, selector = identity) {
726
757
  const slice = React.useSyncExternalStore(
727
758
  api.subscribe,
728
- () => selector(api.getState()),
729
- () => selector(api.getInitialState())
759
+ React.useCallback(() => selector(api.getState()), [api, selector]),
760
+ React.useCallback(() => selector(api.getInitialState()), [api, selector])
730
761
  );
731
762
  React.useDebugValue(slice);
732
763
  return slice;
@@ -906,7 +937,7 @@ const InternalInput = React.forwardRef((_a, ref) => {
906
937
  event.preventDefault();
907
938
  const x = key == 'ArrowUp' ? keyboard[1] : -keyboard[1];
908
939
  const parsed = parseValue(String(updateValueByEvent(keyboard[0], x)));
909
- console.log('key control');
940
+ // console.log('key control')
910
941
  onChange === null || onChange === void 0 ? void 0 : onChange(parsed.rawValue, parsed.formatValue);
911
942
  }, [keyboard, readonly, onChange, updateValueByEvent]);
912
943
  const wheelRefCallback = useRefCallbackEvent('wheel', (event) => {
@@ -917,7 +948,7 @@ const InternalInput = React.forwardRef((_a, ref) => {
917
948
  return;
918
949
  const x = Math.sign(event.deltaY) * -wheel[1];
919
950
  const parsed = parseValue(String(updateValueByEvent(wheel[0], x)));
920
- console.log('wheel control');
951
+ // console.log('wheel control')
921
952
  onChange === null || onChange === void 0 ? void 0 : onChange(parsed.rawValue, parsed.formatValue);
922
953
  }, { passive: false }, [wheel, readonly, onChange, updateValueByEvent]);
923
954
  React.useImperativeHandle(ref, () => {
@@ -1106,6 +1137,44 @@ function useDragWithElement({ baseElementRef, onDrag, onDragStart, onDragEnd, })
1106
1137
  return [refHandler, pointerDownHandler];
1107
1138
  }
1108
1139
 
1140
+ const createPianoStore = (initProps) => {
1141
+ // const noteRange = initProps.noteRange || { first: 0, last: 127 }
1142
+ const DEFAULT_PROPS = {
1143
+ noteRange: { first: 0, last: 127 },
1144
+ glissando: true,
1145
+ midiMax: 127,
1146
+ fill: false,
1147
+ label: () => undefined,
1148
+ onPlayNote: () => { },
1149
+ onStopNote: () => { },
1150
+ };
1151
+ return createStore()(() => (Object.assign(Object.assign(Object.assign({}, DEFAULT_PROPS), initProps), { notePosition: initProps.notePosition })));
1152
+ };
1153
+ const PianoContext = React.createContext(null);
1154
+ function PianoProvider(_a) {
1155
+ var { children } = _a, props = __rest(_a, ["children"]);
1156
+ const storeRef = React.useRef(null);
1157
+ if (!storeRef.current) {
1158
+ storeRef.current = createPianoStore(props);
1159
+ }
1160
+ React.useEffect(() => {
1161
+ if (storeRef.current) {
1162
+ storeRef.current.setState(props);
1163
+ }
1164
+ else {
1165
+ storeRef.current = createPianoStore(props);
1166
+ }
1167
+ }, [props]);
1168
+ return (jsxRuntime.jsx(PianoContext.Provider, { value: storeRef.current, children: children }));
1169
+ }
1170
+ /** @category Piano */
1171
+ function usePianoContext(selector) {
1172
+ const store = React.useContext(PianoContext);
1173
+ if (!store)
1174
+ throw new Error('Missing PianoContext.Provider in the tree');
1175
+ return useStore(store, selector);
1176
+ }
1177
+
1109
1178
  /** @category Piano */
1110
1179
  function KeyLabel(_a) {
1111
1180
  var { label, className, wrapperClassName, wrapperStyle, __note, __label } = _a, props = __rest(_a, ["label", "className", "wrapperClassName", "wrapperStyle", "__note", "__label"]);
@@ -1188,70 +1257,6 @@ const BlackKey = React.forwardRef((_a, ref) => {
1188
1257
  return (jsxRuntime.jsx(KeyImpl, Object.assign({ ref: ref, keyType: "black", width: width, height: height, onContextMenu: onContextMenu }, props)));
1189
1258
  });
1190
1259
 
1191
- const createPianoStore = (initProps) => {
1192
- const noteRange = (initProps === null || initProps === void 0 ? void 0 : initProps.noteRange) || { first: 0};
1193
- const DEFAULT_PROPS = {
1194
- noteRange: { first: 0, last: 127 },
1195
- glissando: true,
1196
- midiMax: 127,
1197
- fill: false,
1198
- label: () => undefined,
1199
- onPlayNote: () => { },
1200
- onStopNote: () => { },
1201
- };
1202
- return createStore()(() => (Object.assign(Object.assign(Object.assign({}, DEFAULT_PROPS), initProps), { notePosition: (note) => {
1203
- const pitchPositions = {
1204
- C: 0,
1205
- 'C#': 1,
1206
- D: 1,
1207
- 'D#': 2,
1208
- E: 2,
1209
- F: 3,
1210
- 'F#': 4,
1211
- G: 4,
1212
- 'G#': 5,
1213
- A: 5,
1214
- 'A#': 6,
1215
- B: 6,
1216
- };
1217
- const whiteNoteWidth = defaultWhiteKeyWidth;
1218
- const blackNoteWidth = defaultBlackKeyWidth;
1219
- const padding = 1;
1220
- const targetNoteKey = noteKey(note);
1221
- const firstNoteKey = noteKey(noteRange.first);
1222
- const octave = Math.floor((note - noteRange.first) / 12);
1223
- const octaveOffset = noteKeys.indexOf(firstNoteKey) > noteKeys.indexOf(targetNoteKey) ? 1 : 0;
1224
- const w = whiteNoteWidth + padding;
1225
- const pos = pitchPositions[targetNoteKey] - pitchPositions[firstNoteKey];
1226
- const blackKeyOffset = isBlackKey(note) ? blackNoteWidth / 2 : 0;
1227
- return pos * w + (octave + octaveOffset) * 7 * w - blackKeyOffset;
1228
- } })));
1229
- };
1230
- const PianoContext = React.createContext(null);
1231
- function PianoProvider(_a) {
1232
- var { children } = _a, props = __rest(_a, ["children"]);
1233
- const storeRef = React.useRef(null);
1234
- if (!storeRef.current) {
1235
- storeRef.current = createPianoStore(props);
1236
- }
1237
- React.useEffect(() => {
1238
- if (storeRef.current) {
1239
- storeRef.current.setState(props);
1240
- }
1241
- else {
1242
- storeRef.current = createPianoStore(props);
1243
- }
1244
- }, [props]);
1245
- return (jsxRuntime.jsx(PianoContext.Provider, { value: storeRef.current, children: children }));
1246
- }
1247
- /** @category Piano */
1248
- function usePianoContext(selector) {
1249
- const store = React.useContext(PianoContext);
1250
- if (!store)
1251
- throw new Error('Missing PianoContext.Provider in the tree');
1252
- return useStore(store, selector);
1253
- }
1254
-
1255
1260
  /** @category Piano */
1256
1261
  const SHORTCUTS = {
1257
1262
  HOME_ROW: {
@@ -1287,6 +1292,7 @@ const SHORTCUTS = {
1287
1292
  function getNoteRangeArray(noteRange) {
1288
1293
  return Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
1289
1294
  }
1295
+ const blackPerWhiteWidth = defaultBlackKeyWidth / defaultWhiteKeyWidth; // 0.65
1290
1296
  /**
1291
1297
  * Customizable piano component.
1292
1298
  * @category Piano
@@ -1309,19 +1315,17 @@ function Piano(_a) {
1309
1315
  const padding = 1;
1310
1316
  const staticWidth = (whiteNoteWidth + padding) * whiteNoteCount;
1311
1317
  // const blackNoteShiftPercent = 0.3
1312
- // TODO: if fill setWhiteNoteWidth
1313
- // useMemo
1314
- // __width
1315
1318
  const childrenWithProps = React.useMemo(() => {
1316
1319
  return (children &&
1317
1320
  React.Children.map(children, (child, index) => {
1318
1321
  if (React.isValidElement(child)) {
1319
- const props = { ref: keyRefs.current[index] };
1322
+ const __width = child.type == WhiteKey ? whiteNoteWidth : whiteNoteWidth * 0.65;
1323
+ const props = { ref: keyRefs.current[index], __width };
1320
1324
  return React.cloneElement(child, props);
1321
1325
  }
1322
1326
  return child;
1323
1327
  }));
1324
- }, [children]);
1328
+ }, [children, whiteNoteWidth]);
1325
1329
  // --- internal functions ---
1326
1330
  const notePosition = React.useCallback((note) => {
1327
1331
  const pitchPositions = {
@@ -1338,8 +1342,7 @@ function Piano(_a) {
1338
1342
  'A#': 6,
1339
1343
  B: 6,
1340
1344
  };
1341
- const whiteNoteWidth = defaultWhiteKeyWidth;
1342
- const blackNoteWidth = defaultBlackKeyWidth;
1345
+ const blackNoteWidth = whiteNoteWidth * blackPerWhiteWidth;
1343
1346
  const padding = 1;
1344
1347
  const targetNoteKey = noteKey(note);
1345
1348
  const firstNoteKey = noteKey(noteRange.first);
@@ -1349,7 +1352,7 @@ function Piano(_a) {
1349
1352
  const pos = pitchPositions[targetNoteKey] - pitchPositions[firstNoteKey];
1350
1353
  const blackKeyOffset = isBlackKey(note) ? blackNoteWidth / 2 : 0;
1351
1354
  return pos * w + (octave + octaveOffset) * 7 * w - blackKeyOffset;
1352
- }, [noteRange.first]);
1355
+ }, [noteRange.first, whiteNoteWidth]);
1353
1356
  const getHitKeyIndex = React.useCallback((x, y) => {
1354
1357
  if (!pianoRef.current)
1355
1358
  return -1;
@@ -1358,14 +1361,16 @@ function Piano(_a) {
1358
1361
  for (let i = 0; i < notes.length; i++) {
1359
1362
  const note = notes[i];
1360
1363
  const pos = notePosition(note);
1361
- const w = isWhiteKey(note) ? defaultWhiteKeyWidth : defaultBlackKeyWidth;
1364
+ const w = isWhiteKey(note)
1365
+ ? whiteNoteWidth
1366
+ : whiteNoteWidth * blackPerWhiteWidth;
1362
1367
  const h = isWhiteKey(note) ? containerHeight : containerHeight * 0.6;
1363
1368
  if (pos <= x && x < pos + w && 0 <= y && y < h) {
1364
1369
  return note;
1365
1370
  }
1366
1371
  }
1367
1372
  return -1;
1368
- }, [blackNotes, notePosition, whiteNotes]);
1373
+ }, [blackNotes, notePosition, whiteNoteWidth, whiteNotes]);
1369
1374
  // TODO: 単一のポインターに対しては、useDragで対応可能だが、
1370
1375
  // マルチタッチに対しては、TouchEventを使う必要がありそう
1371
1376
  // 取り敢えず、シングルタッチだけ対応
@@ -1440,14 +1445,14 @@ function Piano(_a) {
1440
1445
  (_b = (_a = keyRefs.current[index]) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.stop();
1441
1446
  }
1442
1447
  });
1443
- 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) => {
1448
+ return (jsxRuntime.jsx(PianoProvider, { notePosition: notePosition, noteRange: noteRange, glissando: glissando, midiMax: midiMax, fill: fill, onPlayNote: onPlayNote, onStopNote: onStopNote, label: label, children: jsxRuntime.jsx("div", Object.assign({ ref: (div) => {
1444
1449
  pianoRef.current = div;
1445
1450
  touchMoveRefCallback(div);
1446
1451
  }, className: clsx('tremolo-piano', className), style: Object.assign({ width: fill ? '100%' : staticWidth, height: height }, style), onPointerDown: (event) => {
1447
1452
  pointerDownHandler(event);
1448
1453
  onPointerDown === null || onPointerDown === void 0 ? void 0 : onPointerDown(event);
1449
1454
  } }, props, { children: childrenWithProps ||
1450
- 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))) })) }));
1455
+ noteRangeArray.map((note, index) => isWhiteKey(note) ? (jsxRuntime.jsx(WhiteKey, { ref: keyRefs.current[index], noteNumber: note, __width: whiteNoteWidth }, note)) : (jsxRuntime.jsx(BlackKey, { ref: keyRefs.current[index], noteNumber: note, __width: whiteNoteWidth * blackPerWhiteWidth }, note))) })) }));
1451
1456
  }
1452
1457
 
1453
1458
  const createSliderStore = (initProps) => {