@tremolo-ui/react 0.1.5 → 0.1.6
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/index.cjs +171 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +79 -32
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +80 -56
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +82 -58
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +160 -91
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Knob/index.css +54 -23
- package/src/components/Knob/index.tsx +30 -25
- package/src/components/NumberInput/index.tsx +3 -4
- package/src/components/Piano/context.tsx +1 -1
- package/src/components/Piano/index.css +24 -8
- package/src/components/Piano/index.tsx +253 -217
- package/src/components/Piano/key.tsx +3 -3
- package/src/components/Slider/Track.tsx +2 -2
- package/src/components/Slider/index.tsx +2 -2
- package/src/components/XYPad/index.tsx +2 -2
- package/src/hooks/useAnimationFrame.ts +3 -0
- package/src/hooks/useCallbackRef.ts +2 -2
- package/src/hooks/useDrag.ts +2 -1
- package/src/hooks/useDragWithElement.ts +2 -1
- package/src/hooks/useEventListener.ts +3 -0
- package/src/hooks/useInterval.ts +3 -0
- package/src/hooks/useLongPress.ts +3 -0
- package/src/hooks/useMIDIAccess.ts +40 -0
- package/src/hooks/useMIDIInput.ts +50 -0
- package/src/hooks/useMIDIMessage.ts +24 -0
- package/src/hooks/useRefCallbackEvent.ts +4 -0
- package/src/index.ts +19 -12
- package/src/styles/global.css +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tremolo-ui/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "React component library for audio app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"react-dom": "^18 || ^19"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@tremolo-ui/functions": "^0.1.
|
|
49
|
+
"@tremolo-ui/functions": "^0.1.6",
|
|
50
50
|
"clsx": "^2.1.1",
|
|
51
51
|
"zustand": "^5.0.3"
|
|
52
52
|
},
|
|
@@ -4,45 +4,76 @@
|
|
|
4
4
|
overflow: visible;
|
|
5
5
|
outline: 0;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
7
|
+
&[aria-readonly='true'] {
|
|
8
|
+
cursor: not-allowed;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
.tremolo-knob-active-line {
|
|
13
|
+
overflow: visible;
|
|
14
|
+
color: oklch(69.2% 0.17548 266.373);
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
16
|
+
:where(.tremolo-knob:focus, .tremolo-knob:hover) & {
|
|
17
|
+
color: var(--tremolo-theme-color);
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
:where(.tremolo-knob[aria-disabled='true']) & {
|
|
21
|
+
color: oklch(50.5% 0.03307 270.021);
|
|
24
22
|
}
|
|
23
|
+
}
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
:where(.dark, [data-theme='dark']) {
|
|
26
|
+
.tremolo-knob-active-line {
|
|
27
|
+
color: var(--tremolo-theme-color);
|
|
28
|
+
|
|
29
|
+
:where(.tremolo-knob:focus, .tremolo-knob:hover) & {
|
|
30
|
+
color: oklch(65.2% 0.17548 266.373);
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
.tremolo-knob-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
.tremolo-knob-inactive-line {
|
|
36
|
+
color: oklch(95% 0 0);
|
|
37
|
+
|
|
38
|
+
:where(.tremolo-knob:focus, .tremolo-knob:hover) & {
|
|
39
|
+
color: oklch(90% 0 0);
|
|
40
|
+
}
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
.
|
|
39
|
-
|
|
43
|
+
:where(.dark, [data-theme='dark']) {
|
|
44
|
+
.tremolo-knob-inactive-line {
|
|
45
|
+
color: oklch(41.7% 0 0);
|
|
46
|
+
|
|
47
|
+
:where(.tremolo-knob:focus, .tremolo-knob:hover) & {
|
|
48
|
+
color: oklch(44.59% 0 0);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
40
51
|
}
|
|
41
52
|
|
|
42
53
|
.tremolo-knob-thumb {
|
|
43
|
-
color:
|
|
54
|
+
color: oklch(85.7% 0 0);
|
|
55
|
+
|
|
56
|
+
:where(.tremolo-knob:focus, .tremolo-knob:hover) & {
|
|
57
|
+
color: oklch(84.5% 0 0);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
:where(.dark, [data-theme='dark']) {
|
|
62
|
+
.tremolo-knob-thumb {
|
|
63
|
+
color: oklch(48.6% 0 0);
|
|
64
|
+
|
|
65
|
+
:where(.tremolo-knob:focus, .tremolo-knob:hover) & {
|
|
66
|
+
color: oklch(51.7% 0 0);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
44
69
|
}
|
|
45
70
|
|
|
46
71
|
.tremolo-knob-thumb-line {
|
|
47
|
-
color:
|
|
72
|
+
color: oklch(94.2% 0 0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
:where(.dark, [data-theme='dark']) {
|
|
76
|
+
.tremolo-knob-thumb-line {
|
|
77
|
+
color: oklch(84.2% 0 0);
|
|
78
|
+
}
|
|
48
79
|
}
|
|
@@ -43,8 +43,8 @@ export interface KnobProps {
|
|
|
43
43
|
*/
|
|
44
44
|
startValue?: number
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
size?: number
|
|
46
|
+
/** width and height */
|
|
47
|
+
size?: number | string
|
|
48
48
|
|
|
49
49
|
/** Whether to apply `{use-select: none}` when dragging */
|
|
50
50
|
bodyNoSelect?: boolean
|
|
@@ -69,15 +69,16 @@ export interface KnobProps {
|
|
|
69
69
|
thumb?: string
|
|
70
70
|
/** color */
|
|
71
71
|
thumbLine?: string
|
|
72
|
-
|
|
73
72
|
/** percent (0-100) */
|
|
74
73
|
thumbSize?: number
|
|
75
74
|
/** percent (0-100) */
|
|
76
75
|
thumbLineWeight?: number
|
|
77
76
|
/** percent (0-100) */
|
|
78
77
|
thumbLineLength?: number
|
|
79
|
-
/**
|
|
78
|
+
/** line weight [px] */
|
|
80
79
|
lineWeight?: number
|
|
80
|
+
/** angle range [degree] */
|
|
81
|
+
angleRange?: number
|
|
81
82
|
|
|
82
83
|
classes?: {
|
|
83
84
|
activeLine?: string
|
|
@@ -125,6 +126,11 @@ export const Knob = forwardRef<KnobMethods, Props>(
|
|
|
125
126
|
inactiveLine,
|
|
126
127
|
thumb,
|
|
127
128
|
thumbLine,
|
|
129
|
+
thumbSize = 84,
|
|
130
|
+
thumbLineWeight = 6,
|
|
131
|
+
thumbLineLength = 35,
|
|
132
|
+
lineWeight = 6,
|
|
133
|
+
angleRange = 270,
|
|
128
134
|
onChange,
|
|
129
135
|
onKeyDown,
|
|
130
136
|
onPointerDown,
|
|
@@ -133,26 +139,18 @@ export const Knob = forwardRef<KnobMethods, Props>(
|
|
|
133
139
|
classes,
|
|
134
140
|
...props
|
|
135
141
|
}: Props,
|
|
136
|
-
|
|
142
|
+
forwardedRef,
|
|
137
143
|
) => {
|
|
138
144
|
const valueRef = useRef(0)
|
|
139
145
|
const elmRef = useRef<HTMLOrSVGElement>(null)
|
|
140
146
|
|
|
141
|
-
// --- interpret props ---
|
|
142
|
-
const padding = 8 // %
|
|
143
|
-
const thumbLineWeight = 6 // %
|
|
144
|
-
const thumbLineLength = 35 // %
|
|
145
|
-
const lineWeight = size * 0.06 // px
|
|
146
|
-
const p = normalizeValue(value, min, max, skew)
|
|
147
|
-
const s = normalizeValue(startValue, min, max, skew)
|
|
148
|
-
|
|
149
147
|
// --- internal functions ---
|
|
150
148
|
const onDrag = useCallback(
|
|
151
149
|
(_x: number, y: number) => {
|
|
152
150
|
if (!onChange || readonly) return
|
|
153
151
|
const v = rawValue(valueRef.current - y / 100, min, max, skew)
|
|
154
|
-
const
|
|
155
|
-
onChange(
|
|
152
|
+
const clamped = clamp(stepValue(v, step), min, max)
|
|
153
|
+
onChange(clamped)
|
|
156
154
|
},
|
|
157
155
|
[max, min, onChange, readonly, skew, step],
|
|
158
156
|
)
|
|
@@ -210,7 +208,7 @@ export const Knob = forwardRef<KnobMethods, Props>(
|
|
|
210
208
|
[wheel, onChange, readonly, updateValueByEvent],
|
|
211
209
|
)
|
|
212
210
|
|
|
213
|
-
useImperativeHandle(
|
|
211
|
+
useImperativeHandle(forwardedRef, () => {
|
|
214
212
|
return {
|
|
215
213
|
focus() {
|
|
216
214
|
elmRef.current?.focus()
|
|
@@ -221,11 +219,17 @@ export const Knob = forwardRef<KnobMethods, Props>(
|
|
|
221
219
|
}
|
|
222
220
|
}, [])
|
|
223
221
|
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
const
|
|
228
|
-
const
|
|
222
|
+
const viewBoxSize = 100
|
|
223
|
+
const center = viewBoxSize / 2
|
|
224
|
+
const padding = (viewBoxSize - clamp(thumbSize, 0, 100)) / 2 // %
|
|
225
|
+
const p = normalizeValue(value, min, max, skew)
|
|
226
|
+
const s = normalizeValue(startValue, min, max, skew)
|
|
227
|
+
|
|
228
|
+
// 時計回りで描画していく
|
|
229
|
+
const r1 = -angleRange / 2 // ロータリー開始位置
|
|
230
|
+
const r2 = r1 + Math.min(p, s) * angleRange // activeLineの開始位置
|
|
231
|
+
const r3 = r1 + Math.max(p, s) * angleRange // activeLineの終了位置
|
|
232
|
+
const r4 = angleRange / 2 // ロータリー終了位置
|
|
229
233
|
const x1 = center + center * Math.cos(radian(r1 - 90))
|
|
230
234
|
const y1 = center + center * Math.sin(radian(r1 - 90))
|
|
231
235
|
const x2 = center + center * Math.cos(radian(r2 - 90))
|
|
@@ -237,12 +241,13 @@ export const Knob = forwardRef<KnobMethods, Props>(
|
|
|
237
241
|
|
|
238
242
|
return (
|
|
239
243
|
<svg
|
|
240
|
-
className={clsx('tremolo-knob', className)}
|
|
241
244
|
ref={(div) => {
|
|
242
245
|
elmRef.current = div
|
|
243
246
|
wheelRefCallback(div)
|
|
244
247
|
touchMoveRefCallback(div)
|
|
245
248
|
}}
|
|
249
|
+
className={clsx('tremolo-knob', className)}
|
|
250
|
+
viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}
|
|
246
251
|
width={size}
|
|
247
252
|
height={size}
|
|
248
253
|
tabIndex={0}
|
|
@@ -268,7 +273,7 @@ export const Knob = forwardRef<KnobMethods, Props>(
|
|
|
268
273
|
}}
|
|
269
274
|
{...props}
|
|
270
275
|
>
|
|
271
|
-
{/*
|
|
276
|
+
{/* lines */}
|
|
272
277
|
{startValue > min && (
|
|
273
278
|
<path
|
|
274
279
|
className={clsx(
|
|
@@ -305,7 +310,7 @@ export const Knob = forwardRef<KnobMethods, Props>(
|
|
|
305
310
|
<circle
|
|
306
311
|
cx="50%"
|
|
307
312
|
cy="50%"
|
|
308
|
-
r={`${
|
|
313
|
+
r={`${thumbSize / 2}%`}
|
|
309
314
|
fill={thumb || 'currentColor'}
|
|
310
315
|
/>
|
|
311
316
|
<line
|
|
@@ -320,7 +325,7 @@ export const Knob = forwardRef<KnobMethods, Props>(
|
|
|
320
325
|
// https://bugs.webkit.org/show_bug.cgi?id=201854
|
|
321
326
|
// https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/transform-origin#browser_compatibility
|
|
322
327
|
style={{
|
|
323
|
-
transform: `rotate(${
|
|
328
|
+
transform: `rotate(${r1 + p * angleRange}deg)`,
|
|
324
329
|
transformOrigin: '50% 50%',
|
|
325
330
|
}}
|
|
326
331
|
/>
|
|
@@ -55,7 +55,6 @@ export interface NumberInputProps {
|
|
|
55
55
|
keepWithinRange?: boolean
|
|
56
56
|
clampValueOnBlur?: boolean
|
|
57
57
|
|
|
58
|
-
// TODO
|
|
59
58
|
/**
|
|
60
59
|
* wheel control option
|
|
61
60
|
*/
|
|
@@ -126,9 +125,9 @@ export const NumberInput = forwardRef<NumberInputMethods, Props>(
|
|
|
126
125
|
children,
|
|
127
126
|
...props
|
|
128
127
|
}: Props,
|
|
129
|
-
|
|
128
|
+
forwardedRef,
|
|
130
129
|
) => {
|
|
131
|
-
const colors:
|
|
130
|
+
const colors: Record<string, string | undefined> = {
|
|
132
131
|
'--active-color': activeColor,
|
|
133
132
|
}
|
|
134
133
|
return (
|
|
@@ -154,7 +153,7 @@ export const NumberInput = forwardRef<NumberInputMethods, Props>(
|
|
|
154
153
|
data-variant={variant}
|
|
155
154
|
>
|
|
156
155
|
<InternalInput
|
|
157
|
-
ref={
|
|
156
|
+
ref={forwardedRef}
|
|
158
157
|
readonly={readonly}
|
|
159
158
|
disabled={disabled}
|
|
160
159
|
selectWithFocus={selectWithFocus}
|
|
@@ -12,7 +12,7 @@ type State = {
|
|
|
12
12
|
glissando: boolean
|
|
13
13
|
midiMax: number
|
|
14
14
|
fill: boolean
|
|
15
|
-
onPlayNote: (noteNumber: number) => void
|
|
15
|
+
onPlayNote: (noteNumber: number, velocity?: number) => void
|
|
16
16
|
onStopNote: (noteNumber: number) => void
|
|
17
17
|
label: (note: number, index: number) => ReactNode
|
|
18
18
|
}
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
.tremolo-piano-white-key {
|
|
10
|
-
--bg:
|
|
11
|
-
--color:
|
|
12
|
-
--active-bg:
|
|
13
|
-
--active-color:
|
|
10
|
+
--bg: oklch(100% 0 0);
|
|
11
|
+
--color: oklch(0% 0 0);
|
|
12
|
+
--active-bg: oklch(90% 0 0);
|
|
13
|
+
--active-color: oklch(0% 0 0);
|
|
14
14
|
|
|
15
15
|
box-sizing: border-box;
|
|
16
16
|
position: absolute;
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
.tremolo-piano-black-key {
|
|
36
|
-
--color:
|
|
37
|
-
--bg:
|
|
38
|
-
--active-color:
|
|
39
|
-
--active-bg:
|
|
36
|
+
--color: oklch(100% 0 0);
|
|
37
|
+
--bg: oklch(32.109% 0.00004 271.152);
|
|
38
|
+
--active-color: oklch(100% 0 0);
|
|
39
|
+
--active-bg: oklch(47.836% 0.00005 271.152);
|
|
40
40
|
|
|
41
41
|
box-sizing: border-box;
|
|
42
42
|
position: absolute;
|
|
@@ -79,3 +79,19 @@
|
|
|
79
79
|
aspect-ratio: 1;
|
|
80
80
|
max-width: calc(100% - 16px);
|
|
81
81
|
}
|
|
82
|
+
|
|
83
|
+
:where(.dark, [data-theme='dark']) {
|
|
84
|
+
.tremolo-piano-white-key {
|
|
85
|
+
--bg: oklch(48.193% 0.00005 271.152);
|
|
86
|
+
--color: oklch(100% 0 0);
|
|
87
|
+
--active-bg: oklch(52.78% 0.00006 271.152);
|
|
88
|
+
--active-color: oklch(100% 0 0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.tremolo-piano-black-key {
|
|
92
|
+
--color: oklch(100% 0 0);
|
|
93
|
+
--bg: oklch(32.109% 0 0);
|
|
94
|
+
--active-color: oklch(100% 0 0);
|
|
95
|
+
--active-bg: oklch(38.666% 0.00004 271.152);
|
|
96
|
+
}
|
|
97
|
+
}
|