@tremolo-ui/react 0.0.6 → 0.0.7
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 +316 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/Piano/index.d.ts +1 -1
- package/dist/cjs/types/components/Piano/index.d.ts.map +1 -1
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/esm/index.js +313 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/Piano/index.d.ts +1 -1
- package/dist/esm/types/components/Piano/index.d.ts.map +1 -1
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/esm/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Piano/index.tsx +2 -2
- package/src/index.ts +1 -0
package/dist/cjs/index.js
CHANGED
|
@@ -242,6 +242,62 @@ function styleHelper(value, op, influencer) {
|
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* @category Utility
|
|
247
|
+
*/
|
|
248
|
+
function mod(n, m) {
|
|
249
|
+
return (((n % m) + m) % m);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* @category MIDI
|
|
253
|
+
*/
|
|
254
|
+
const noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
|
|
255
|
+
/**
|
|
256
|
+
* @category MIDI
|
|
257
|
+
*/
|
|
258
|
+
function parseNoteName(noteName) {
|
|
259
|
+
const m = noteName.match(/^([a-g])(#{0,2}|b{0,2})(-?\d+)$/i);
|
|
260
|
+
if (!m)
|
|
261
|
+
throw new Error('Invalid note name');
|
|
262
|
+
const [, letter, accidental, octave] = m;
|
|
263
|
+
return {
|
|
264
|
+
letter: letter.toLocaleUpperCase(),
|
|
265
|
+
accidental: accidental,
|
|
266
|
+
octave: Number(octave),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
*
|
|
271
|
+
* @category MIDI
|
|
272
|
+
*/
|
|
273
|
+
function noteNumber(noteName) {
|
|
274
|
+
const { letter, accidental, octave, } = parseNoteName(noteName);
|
|
275
|
+
const noteIndex = noteNames.indexOf(letter.toLocaleUpperCase());
|
|
276
|
+
const accidentalValue = (accidental[0] == 'b' ? -1 : 1) * accidental.length;
|
|
277
|
+
return noteIndex + 12 * (Number(octave) + 1) + accidentalValue;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
*
|
|
281
|
+
* @category MIDI
|
|
282
|
+
*/
|
|
283
|
+
function noteName(noteNumber) {
|
|
284
|
+
const noteIndex = mod(noteNumber, 12);
|
|
285
|
+
const octave = Math.floor(noteNumber / 12) - 1;
|
|
286
|
+
return noteNames[noteIndex] + octave;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* @category MIDI
|
|
290
|
+
*/
|
|
291
|
+
function isWhiteKey(note) {
|
|
292
|
+
const n = typeof note == 'string' ? noteNumber(note) : note;
|
|
293
|
+
return (mod(n, 12) == 0 ||
|
|
294
|
+
mod(n, 12) == 2 ||
|
|
295
|
+
mod(n, 12) == 4 ||
|
|
296
|
+
mod(n, 12) == 5 ||
|
|
297
|
+
mod(n, 12) == 7 ||
|
|
298
|
+
mod(n, 12) == 9 ||
|
|
299
|
+
mod(n, 12) == 11);
|
|
300
|
+
}
|
|
245
301
|
|
|
246
302
|
/**
|
|
247
303
|
* This hook is user-land implementation of the experimental `useEffectEvent` hook.
|
|
@@ -537,6 +593,261 @@ function NumberInput(_a) {
|
|
|
537
593
|
} }, rest)));
|
|
538
594
|
}
|
|
539
595
|
|
|
596
|
+
function KeyLabel({ label, className, style, wrapperStyle, __note, __index, __label, }) {
|
|
597
|
+
const content = label ? label(__note, __index) : __label && __label(__note, __index);
|
|
598
|
+
return ((content != undefined || content != null) &&
|
|
599
|
+
jsxRuntime.jsx("div", { className: 'tremolo-piano-key-label-wrapper', css: react.css(Object.assign({ display: 'flex', justifyContent: 'center', alignItems: 'end', height: '100%' }, wrapperStyle)), children: jsxRuntime.jsx("div", { className: clsx('tremolo-piano-key-label', className), css: react.css(Object.assign({ display: 'flex', justifyContent: 'center', alignItems: 'center', fontSize: '0.6rem', height: 10, marginBottom: 10, padding: 4, borderRadius: 4, borderStyle: 'solid', borderWidth: 1, borderColor: '#888', aspectRatio: 1, maxWidth: 'calc(100% - 16px)' }, style)), children: content }) }));
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const WhiteKey = React.forwardRef(({ width = 40, height = '100%', bg = 'white', activeBg = '#ccc', style, children, __glissando, __position, __note, __disabled, __index, __fill, __width, __playNote, __stopNote, __label, }, ref) => {
|
|
603
|
+
const [played, setPlayed] = React.useState(false);
|
|
604
|
+
React.useImperativeHandle(ref, () => {
|
|
605
|
+
return {
|
|
606
|
+
play() {
|
|
607
|
+
if (__disabled)
|
|
608
|
+
return;
|
|
609
|
+
setPlayed(true);
|
|
610
|
+
if (__playNote)
|
|
611
|
+
__playNote(__note);
|
|
612
|
+
},
|
|
613
|
+
stop() {
|
|
614
|
+
setPlayed(false);
|
|
615
|
+
if (__stopNote)
|
|
616
|
+
__stopNote(__note);
|
|
617
|
+
},
|
|
618
|
+
played() {
|
|
619
|
+
return played;
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
}, []);
|
|
623
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
624
|
+
let keyLabelProps;
|
|
625
|
+
if (children != undefined) {
|
|
626
|
+
React.Children.forEach(children, (child) => {
|
|
627
|
+
if (React.isValidElement(child)) {
|
|
628
|
+
if (child.type == KeyLabel) {
|
|
629
|
+
keyLabelProps = child.props;
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
throw new Error('only <KeyLabel>');
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
throw new Error('children is an invalid element.');
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
return (jsxRuntime.jsx("div", { className: `tremolo-piano-white-key`, "aria-disabled": __disabled, css: react.css(Object.assign({ position: 'absolute', backgroundColor: played ? activeBg : bg, color: 'black', left: __position, width: __fill ? __width : (width !== null && width !== undefined ? width : __width), height: height, border: '1px solid #555', borderRadius: '0 0 8px 8px', cursor: __disabled ? 'not-allowed' : 'pointer', zIndex: 1 }, style)), onPointerDown: () => {
|
|
641
|
+
if (__disabled)
|
|
642
|
+
return;
|
|
643
|
+
setPlayed(true);
|
|
644
|
+
if (__playNote)
|
|
645
|
+
__playNote(__note);
|
|
646
|
+
}, onPointerEnter: () => {
|
|
647
|
+
if (__disabled)
|
|
648
|
+
return;
|
|
649
|
+
if (__glissando) {
|
|
650
|
+
setPlayed(true);
|
|
651
|
+
if (__playNote)
|
|
652
|
+
__playNote(__note);
|
|
653
|
+
}
|
|
654
|
+
}, onPointerUp: () => {
|
|
655
|
+
if (__disabled)
|
|
656
|
+
return;
|
|
657
|
+
setPlayed(false);
|
|
658
|
+
if (__stopNote)
|
|
659
|
+
__stopNote(__note);
|
|
660
|
+
}, onPointerLeave: () => {
|
|
661
|
+
if (__disabled)
|
|
662
|
+
return;
|
|
663
|
+
setPlayed(false);
|
|
664
|
+
if (__stopNote)
|
|
665
|
+
__stopNote(__note);
|
|
666
|
+
}, children: jsxRuntime.jsx(KeyLabel, Object.assign({ __note: __note, __label: __label, __index: __index }, keyLabelProps)) }));
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
const BlackKey = React.forwardRef(({ width = 40 * 0.65, height = '60%', bg = '#333', activeBg = '#666', style, children, __glissando, __position, __note, __disabled, __index, __fill, __width, __playNote, __stopNote, __label, }, ref) => {
|
|
670
|
+
const [played, setPlayed] = React.useState(false);
|
|
671
|
+
React.useImperativeHandle(ref, () => {
|
|
672
|
+
return {
|
|
673
|
+
play() {
|
|
674
|
+
if (__disabled)
|
|
675
|
+
return;
|
|
676
|
+
setPlayed(true);
|
|
677
|
+
if (__playNote)
|
|
678
|
+
__playNote(__note);
|
|
679
|
+
},
|
|
680
|
+
stop() {
|
|
681
|
+
setPlayed(false);
|
|
682
|
+
if (__stopNote)
|
|
683
|
+
__stopNote(__note);
|
|
684
|
+
},
|
|
685
|
+
played() {
|
|
686
|
+
return played;
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
}, []);
|
|
690
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
691
|
+
let keyLabelProps;
|
|
692
|
+
if (children != undefined) {
|
|
693
|
+
React.Children.forEach(children, (child) => {
|
|
694
|
+
if (React.isValidElement(child)) {
|
|
695
|
+
if (child.type == KeyLabel) {
|
|
696
|
+
keyLabelProps = child.props;
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
throw new Error('only <KeyLabel>');
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
throw new Error('children is an invalid element.');
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
return (jsxRuntime.jsx("div", { className: `tremolo-piano-black-key`, "aria-disabled": __disabled, css: react.css(Object.assign({ position: 'absolute', backgroundColor: played ? activeBg : bg, color: 'white', left: __position, width: __fill ? __width : (width !== null && width !== undefined ? width : __width), height: height, border: '1px solid #555', borderRadius: '0 0 8px 8px', cursor: __disabled ? 'not-allowed' : 'pointer', zIndex: 2 }, style)), onPointerDown: () => {
|
|
708
|
+
if (__disabled)
|
|
709
|
+
return;
|
|
710
|
+
setPlayed(true);
|
|
711
|
+
if (__playNote)
|
|
712
|
+
__playNote(__note);
|
|
713
|
+
}, onPointerEnter: () => {
|
|
714
|
+
if (__disabled)
|
|
715
|
+
return;
|
|
716
|
+
if (__glissando) {
|
|
717
|
+
setPlayed(true);
|
|
718
|
+
if (__playNote)
|
|
719
|
+
__playNote(__note);
|
|
720
|
+
}
|
|
721
|
+
}, onPointerUp: () => {
|
|
722
|
+
if (__disabled)
|
|
723
|
+
return;
|
|
724
|
+
setPlayed(false);
|
|
725
|
+
if (__stopNote)
|
|
726
|
+
__stopNote(__note);
|
|
727
|
+
}, onPointerLeave: () => {
|
|
728
|
+
if (__disabled)
|
|
729
|
+
return;
|
|
730
|
+
setPlayed(false);
|
|
731
|
+
if (__stopNote)
|
|
732
|
+
__stopNote(__note);
|
|
733
|
+
}, children: jsxRuntime.jsx(KeyLabel, Object.assign({ __note: __note, __label: __label, __index: __index }, keyLabelProps)) }));
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
const Shortcuts = {
|
|
737
|
+
HOME_ROW: {
|
|
738
|
+
keys: ['a', 'w', 's', 'e', 'd', 'f', 't', 'g', 'y', 'h', 'u', 'j', 'k', 'o', 'l', 'p', ';']
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
function Piano({ noteRange, glissando = true, midiMax = 127, keyboardShortcuts, fill = false, height = fill ? '100%' : 160, style, playNote, stopNote, label, children, }) {
|
|
743
|
+
// -- state and ref ---
|
|
744
|
+
const [pressed, setPressed] = React.useState(false);
|
|
745
|
+
const [whiteNoteWidth, setWhiteNoteWidth] = React.useState(-1);
|
|
746
|
+
const keyRefs = React.useRef([]);
|
|
747
|
+
for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) {
|
|
748
|
+
keyRefs.current[i] = React.createRef();
|
|
749
|
+
}
|
|
750
|
+
const pianoRef = React.useRef(null);
|
|
751
|
+
// --- interpret props ---
|
|
752
|
+
const noteRangeArray = Array.from({ length: noteRange.last - noteRange.first + 1 }, (_, i) => i + noteRange.first);
|
|
753
|
+
const whiteNoteCount = noteRangeArray.filter((v) => isWhiteKey(v)).length;
|
|
754
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
755
|
+
let whiteKeyProps, blackKeyProps;
|
|
756
|
+
if (children != undefined) {
|
|
757
|
+
React.Children.forEach(children, (child) => {
|
|
758
|
+
if (React.isValidElement(child)) {
|
|
759
|
+
if (child.type == WhiteKey) {
|
|
760
|
+
whiteKeyProps = child.props;
|
|
761
|
+
}
|
|
762
|
+
else if (child.type == BlackKey) {
|
|
763
|
+
blackKeyProps = child.props;
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
throw new Error('only <WhiteKey> or <BlackKey>');
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
throw new Error('children is an invalid element.');
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
// --- internal functions ---
|
|
775
|
+
function notePosition(note) {
|
|
776
|
+
const pitchPositions = {
|
|
777
|
+
C: 0,
|
|
778
|
+
'C#': 0.55,
|
|
779
|
+
D: 1,
|
|
780
|
+
'D#': 1.8,
|
|
781
|
+
E: 2,
|
|
782
|
+
F: 3,
|
|
783
|
+
'F#': 3.5,
|
|
784
|
+
G: 4,
|
|
785
|
+
'G#': 4.7,
|
|
786
|
+
A: 5,
|
|
787
|
+
'A#': 5.85,
|
|
788
|
+
B: 6,
|
|
789
|
+
};
|
|
790
|
+
const toNoteName = (n) => {
|
|
791
|
+
const { letter, accidental } = parseNoteName(noteName(n));
|
|
792
|
+
return (letter + accidental);
|
|
793
|
+
};
|
|
794
|
+
const _noteName = toNoteName(note);
|
|
795
|
+
const firstNoteName = toNoteName(noteRange.first);
|
|
796
|
+
const pos = pitchPositions[_noteName] - pitchPositions[firstNoteName];
|
|
797
|
+
const octave = Math.floor((note - noteRange.first) / 12);
|
|
798
|
+
const octaveOffset = noteNames.indexOf(firstNoteName) > noteNames.indexOf(_noteName) ? 1 : 0;
|
|
799
|
+
const length = whiteNoteWidth + 3;
|
|
800
|
+
return pos * length + (octave + octaveOffset) * 7 * length;
|
|
801
|
+
}
|
|
802
|
+
// --- hooks ---
|
|
803
|
+
React.useEffect(() => {
|
|
804
|
+
if (fill && pianoRef.current) {
|
|
805
|
+
const parent = pianoRef.current.parentElement;
|
|
806
|
+
if (!parent)
|
|
807
|
+
throw new Error("doesn't have a parent element.");
|
|
808
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
809
|
+
const w = pianoRef.current.clientWidth;
|
|
810
|
+
setWhiteNoteWidth(w / whiteNoteCount - 3);
|
|
811
|
+
});
|
|
812
|
+
resizeObserver.observe(parent);
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
setWhiteNoteWidth((whiteKeyProps === null || whiteKeyProps === undefined ? undefined : whiteKeyProps.width) || 40);
|
|
816
|
+
}
|
|
817
|
+
}, []);
|
|
818
|
+
useEventListener(window, 'keydown', (e) => {
|
|
819
|
+
var _a, _b, _c, _d;
|
|
820
|
+
if (e.repeat)
|
|
821
|
+
return;
|
|
822
|
+
if (!keyboardShortcuts)
|
|
823
|
+
return;
|
|
824
|
+
const index = keyboardShortcuts.keys.indexOf(e.key);
|
|
825
|
+
if (index != -1) {
|
|
826
|
+
if (!((_b = (_a = keyRefs.current[index]) === null || _a === undefined ? undefined : _a.current) === null || _b === undefined ? undefined : _b.played())) {
|
|
827
|
+
(_d = (_c = keyRefs.current[index]) === null || _c === undefined ? undefined : _c.current) === null || _d === undefined ? undefined : _d.play();
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
useEventListener(window, 'keyup', (e) => {
|
|
832
|
+
var _a, _b;
|
|
833
|
+
if (e.repeat)
|
|
834
|
+
return;
|
|
835
|
+
if (!keyboardShortcuts)
|
|
836
|
+
return;
|
|
837
|
+
const index = keyboardShortcuts.keys.indexOf(e.key);
|
|
838
|
+
if (index != -1) {
|
|
839
|
+
(_b = (_a = keyRefs.current[index]) === null || _a === undefined ? undefined : _a.current) === null || _b === undefined ? undefined : _b.stop();
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
return (jsxRuntime.jsx("div", { ref: pianoRef, className: "tremolo-piano", css: react.css(Object.assign({ display: 'inline-block', boxSizing: 'border-box', userSelect: 'none', width: fill ? '100%' : undefined, height: height, position: 'relative' }, style)), onPointerDown: () => {
|
|
843
|
+
setPressed(true);
|
|
844
|
+
}, onPointerUp: () => {
|
|
845
|
+
setPressed(false);
|
|
846
|
+
}, children: noteRangeArray.map((note, index) => {
|
|
847
|
+
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, __playNote: playNote, __stopNote: stopNote, __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, __playNote: playNote, __stopNote: stopNote, __label: label }, blackKeyProps), note)));
|
|
848
|
+
}) }));
|
|
849
|
+
}
|
|
850
|
+
|
|
540
851
|
/**
|
|
541
852
|
* clamp value between min and max
|
|
542
853
|
* @category Math
|
|
@@ -807,10 +1118,15 @@ const Slider = React.forwardRef((_a, ref) => {
|
|
|
807
1118
|
});
|
|
808
1119
|
|
|
809
1120
|
exports.AnimationCanvas = AnimationCanvas;
|
|
1121
|
+
exports.BlackKey = BlackKey;
|
|
1122
|
+
exports.KeyLabel = KeyLabel;
|
|
810
1123
|
exports.Knob = Knob;
|
|
811
1124
|
exports.NumberInput = NumberInput;
|
|
1125
|
+
exports.Piano = Piano;
|
|
1126
|
+
exports.Shortcuts = Shortcuts;
|
|
812
1127
|
exports.Slider = Slider;
|
|
813
1128
|
exports.SliderThumb = SliderThumb;
|
|
814
1129
|
exports.SliderTrack = SliderTrack;
|
|
1130
|
+
exports.WhiteKey = WhiteKey;
|
|
815
1131
|
exports.gradientDirection = gradientDirection;
|
|
816
1132
|
//# sourceMappingURL=index.js.map
|