@tremolo-ui/react 0.0.9 → 0.0.11
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 +434 -271
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/AnimationCanvas/index.d.ts +9 -4
- package/dist/cjs/types/components/AnimationCanvas/index.d.ts.map +1 -1
- package/dist/cjs/types/components/AnimationKnob/index.d.ts +2 -0
- package/dist/cjs/types/components/AnimationKnob/index.d.ts.map +1 -1
- package/dist/cjs/types/components/DragObserver/index.d.ts +3 -1
- package/dist/cjs/types/components/DragObserver/index.d.ts.map +1 -1
- package/dist/cjs/types/components/Knob/index.d.ts +2 -0
- package/dist/cjs/types/components/Knob/index.d.ts.map +1 -1
- package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts +7 -0
- package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts.map +1 -0
- package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts +7 -0
- package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts.map +1 -0
- package/dist/cjs/types/components/NumberInput/Stepper.d.ts +8 -0
- package/dist/cjs/types/components/NumberInput/Stepper.d.ts.map +1 -0
- package/dist/cjs/types/components/NumberInput/context.d.ts +27 -0
- package/dist/cjs/types/components/NumberInput/context.d.ts.map +1 -0
- package/dist/cjs/types/components/NumberInput/index.d.ts +31 -16
- package/dist/cjs/types/components/NumberInput/index.d.ts.map +1 -1
- package/dist/cjs/types/components/Piano/BlackKey.d.ts +1 -0
- package/dist/cjs/types/components/Piano/BlackKey.d.ts.map +1 -1
- package/dist/cjs/types/components/Piano/KeyLabel.d.ts +2 -0
- package/dist/cjs/types/components/Piano/KeyLabel.d.ts.map +1 -1
- package/dist/cjs/types/components/Piano/WhiteKey.d.ts +2 -0
- package/dist/cjs/types/components/Piano/WhiteKey.d.ts.map +1 -1
- package/dist/cjs/types/components/Piano/index.d.ts +6 -4
- package/dist/cjs/types/components/Piano/index.d.ts.map +1 -1
- package/dist/cjs/types/components/Piano/key.d.ts +8 -2
- package/dist/cjs/types/components/Piano/key.d.ts.map +1 -1
- package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts +2 -0
- package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts.map +1 -1
- package/dist/cjs/types/components/Slider/Scale.d.ts +2 -0
- package/dist/cjs/types/components/Slider/Scale.d.ts.map +1 -1
- package/dist/cjs/types/components/Slider/ScaleOption.d.ts +2 -0
- package/dist/cjs/types/components/Slider/ScaleOption.d.ts.map +1 -1
- package/dist/cjs/types/components/Slider/Thumb.d.ts +4 -0
- package/dist/cjs/types/components/Slider/Thumb.d.ts.map +1 -1
- package/dist/cjs/types/components/Slider/Track.d.ts +4 -1
- package/dist/cjs/types/components/Slider/Track.d.ts.map +1 -1
- package/dist/cjs/types/components/Slider/context.d.ts +7 -12
- package/dist/cjs/types/components/Slider/context.d.ts.map +1 -1
- package/dist/cjs/types/components/Slider/index.d.ts +8 -5
- package/dist/cjs/types/components/Slider/index.d.ts.map +1 -1
- package/dist/cjs/types/components/WheelObserver/index.d.ts +1 -0
- package/dist/cjs/types/components/WheelObserver/index.d.ts.map +1 -1
- package/dist/cjs/types/components/XYPad/Area.d.ts +2 -0
- package/dist/cjs/types/components/XYPad/Area.d.ts.map +1 -1
- package/dist/cjs/types/components/XYPad/Thumb.d.ts +3 -0
- package/dist/cjs/types/components/XYPad/Thumb.d.ts.map +1 -1
- package/dist/cjs/types/components/XYPad/index.d.ts +4 -0
- package/dist/cjs/types/components/XYPad/index.d.ts.map +1 -1
- package/dist/cjs/types/hooks/useAnimationFrame.d.ts +3 -0
- package/dist/cjs/types/hooks/useAnimationFrame.d.ts.map +1 -0
- package/dist/cjs/types/hooks/useDrag.d.ts +15 -0
- package/dist/cjs/types/hooks/useDrag.d.ts.map +1 -0
- package/dist/cjs/types/hooks/useRefCallbackEvent.d.ts +1 -4
- package/dist/cjs/types/hooks/useRefCallbackEvent.d.ts.map +1 -1
- package/dist/esm/index.js +432 -262
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/AnimationCanvas/index.d.ts +9 -4
- package/dist/esm/types/components/AnimationCanvas/index.d.ts.map +1 -1
- package/dist/esm/types/components/AnimationKnob/index.d.ts +2 -0
- package/dist/esm/types/components/AnimationKnob/index.d.ts.map +1 -1
- package/dist/esm/types/components/DragObserver/index.d.ts +3 -1
- package/dist/esm/types/components/DragObserver/index.d.ts.map +1 -1
- package/dist/esm/types/components/Knob/index.d.ts +2 -0
- package/dist/esm/types/components/Knob/index.d.ts.map +1 -1
- package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts +7 -0
- package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts.map +1 -0
- package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts +7 -0
- package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts.map +1 -0
- package/dist/esm/types/components/NumberInput/Stepper.d.ts +8 -0
- package/dist/esm/types/components/NumberInput/Stepper.d.ts.map +1 -0
- package/dist/esm/types/components/NumberInput/context.d.ts +27 -0
- package/dist/esm/types/components/NumberInput/context.d.ts.map +1 -0
- package/dist/esm/types/components/NumberInput/index.d.ts +31 -16
- package/dist/esm/types/components/NumberInput/index.d.ts.map +1 -1
- package/dist/esm/types/components/Piano/BlackKey.d.ts +1 -0
- package/dist/esm/types/components/Piano/BlackKey.d.ts.map +1 -1
- package/dist/esm/types/components/Piano/KeyLabel.d.ts +2 -0
- package/dist/esm/types/components/Piano/KeyLabel.d.ts.map +1 -1
- package/dist/esm/types/components/Piano/WhiteKey.d.ts +2 -0
- package/dist/esm/types/components/Piano/WhiteKey.d.ts.map +1 -1
- package/dist/esm/types/components/Piano/index.d.ts +6 -4
- package/dist/esm/types/components/Piano/index.d.ts.map +1 -1
- package/dist/esm/types/components/Piano/key.d.ts +8 -2
- package/dist/esm/types/components/Piano/key.d.ts.map +1 -1
- package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts +2 -0
- package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts.map +1 -1
- package/dist/esm/types/components/Slider/Scale.d.ts +2 -0
- package/dist/esm/types/components/Slider/Scale.d.ts.map +1 -1
- package/dist/esm/types/components/Slider/ScaleOption.d.ts +2 -0
- package/dist/esm/types/components/Slider/ScaleOption.d.ts.map +1 -1
- package/dist/esm/types/components/Slider/Thumb.d.ts +4 -0
- package/dist/esm/types/components/Slider/Thumb.d.ts.map +1 -1
- package/dist/esm/types/components/Slider/Track.d.ts +4 -1
- package/dist/esm/types/components/Slider/Track.d.ts.map +1 -1
- package/dist/esm/types/components/Slider/context.d.ts +7 -12
- package/dist/esm/types/components/Slider/context.d.ts.map +1 -1
- package/dist/esm/types/components/Slider/index.d.ts +8 -5
- package/dist/esm/types/components/Slider/index.d.ts.map +1 -1
- package/dist/esm/types/components/WheelObserver/index.d.ts +1 -0
- package/dist/esm/types/components/WheelObserver/index.d.ts.map +1 -1
- package/dist/esm/types/components/XYPad/Area.d.ts +2 -0
- package/dist/esm/types/components/XYPad/Area.d.ts.map +1 -1
- package/dist/esm/types/components/XYPad/Thumb.d.ts +3 -0
- package/dist/esm/types/components/XYPad/Thumb.d.ts.map +1 -1
- package/dist/esm/types/components/XYPad/index.d.ts +4 -0
- package/dist/esm/types/components/XYPad/index.d.ts.map +1 -1
- package/dist/esm/types/hooks/useAnimationFrame.d.ts +3 -0
- package/dist/esm/types/hooks/useAnimationFrame.d.ts.map +1 -0
- package/dist/esm/types/hooks/useDrag.d.ts +15 -0
- package/dist/esm/types/hooks/useDrag.d.ts.map +1 -0
- package/dist/esm/types/hooks/useRefCallbackEvent.d.ts +1 -4
- package/dist/esm/types/hooks/useRefCallbackEvent.d.ts.map +1 -1
- package/dist/styles/index.css +110 -14
- package/package.json +41 -37
- package/src/components/AnimationCanvas/index.tsx +14 -5
- package/src/components/AnimationKnob/index.tsx +24 -42
- package/src/components/DragObserver/index.tsx +14 -52
- package/src/components/Knob/index.tsx +23 -40
- package/src/components/NumberInput/DecrementStepper.tsx +52 -0
- package/src/components/NumberInput/IncrementStepper.tsx +52 -0
- package/src/components/NumberInput/Stepper.tsx +25 -0
- package/src/components/NumberInput/context.tsx +150 -0
- package/src/components/NumberInput/index.css +102 -14
- package/src/components/NumberInput/index.tsx +164 -55
- package/src/components/Piano/BlackKey.tsx +13 -10
- package/src/components/Piano/KeyLabel.tsx +2 -0
- package/src/components/Piano/WhiteKey.tsx +14 -10
- package/src/components/Piano/index.tsx +14 -12
- package/src/components/Piano/key.ts +8 -2
- package/src/components/Piano/keyboardShortcuts.ts +2 -0
- package/src/components/Slider/Scale.tsx +8 -6
- package/src/components/Slider/ScaleOption.tsx +8 -6
- package/src/components/Slider/Thumb.tsx +9 -3
- package/src/components/Slider/Track.tsx +25 -15
- package/src/components/Slider/context.tsx +50 -51
- package/src/components/Slider/index.css +8 -0
- package/src/components/Slider/index.tsx +20 -10
- package/src/components/WheelObserver/index.tsx +1 -0
- package/src/components/XYPad/Area.tsx +2 -0
- package/src/components/XYPad/Thumb.tsx +3 -0
- package/src/components/XYPad/index.tsx +6 -2
- package/src/hooks/useAnimationFrame.ts +18 -0
- package/src/hooks/useDrag.ts +92 -0
- package/src/hooks/useRefCallbackEvent.ts +4 -7
|
@@ -1,22 +1,35 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
|
-
import {
|
|
2
|
+
import { ComponentPropsWithoutRef, ReactNode, useMemo, useState } from 'react'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { InputEventOption } from '@tremolo-ui/functions'
|
|
5
5
|
import { parseValue, Units } from '@tremolo-ui/functions/NumberInput'
|
|
6
6
|
|
|
7
|
+
import {
|
|
8
|
+
NumberInputProvider,
|
|
9
|
+
safeClamp,
|
|
10
|
+
useNumberInputContext,
|
|
11
|
+
} from './context'
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
TODO
|
|
15
|
+
- onBlurでは、parse + (clamp) + formatして返す
|
|
16
|
+
- numberMode: focusが当たった時だけ、parse + type="number"にする
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** @category NumberInput */
|
|
7
20
|
export interface NumberInputProps {
|
|
8
21
|
value: number | string
|
|
9
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @default Number.MIN_SAFE_INTEGER
|
|
25
|
+
*/
|
|
10
26
|
min?: number
|
|
27
|
+
/**
|
|
28
|
+
* @default Number.MAX_SAFE_INTEGER
|
|
29
|
+
*/
|
|
11
30
|
max?: number
|
|
12
31
|
step?: number
|
|
13
|
-
defaultValue?: number
|
|
14
|
-
|
|
15
|
-
// wrapping html input
|
|
16
|
-
disabled?: boolean
|
|
17
|
-
placeholder?: string
|
|
18
|
-
readOnly?: boolean
|
|
19
|
-
tabIndex?: number
|
|
32
|
+
defaultValue?: number | string
|
|
20
33
|
|
|
21
34
|
/**
|
|
22
35
|
* @example
|
|
@@ -26,11 +39,6 @@ export interface NumberInputProps {
|
|
|
26
39
|
*/
|
|
27
40
|
units?: string | Units
|
|
28
41
|
|
|
29
|
-
/**
|
|
30
|
-
* NOTE: If units is specified, it is forced to `false`
|
|
31
|
-
*/
|
|
32
|
-
typeNumber?: boolean
|
|
33
|
-
|
|
34
42
|
/**
|
|
35
43
|
* Digits for rounding numbers.
|
|
36
44
|
* @example
|
|
@@ -41,76 +49,94 @@ export interface NumberInputProps {
|
|
|
41
49
|
* value=100 -> 100Hz, value=1600 -> 1.60Hz
|
|
42
50
|
*/
|
|
43
51
|
digit?: number
|
|
52
|
+
|
|
44
53
|
selectWithFocus?: 'all' | 'number' | 'none'
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
blurOnEnter?: boolean
|
|
55
|
+
keepWithinRange?: boolean
|
|
56
|
+
clampValueOnBlur?: boolean
|
|
57
|
+
|
|
58
|
+
// TODO
|
|
59
|
+
/**
|
|
60
|
+
* wheel control option
|
|
61
|
+
*/
|
|
62
|
+
wheel?: InputEventOption | null
|
|
63
|
+
/**
|
|
64
|
+
* keyboard control option
|
|
65
|
+
*/
|
|
66
|
+
keyboard?: InputEventOption | null
|
|
67
|
+
|
|
68
|
+
wrapperClassName?: string
|
|
69
|
+
|
|
70
|
+
onChange?: (value: number, text: string) => void
|
|
48
71
|
onFocus?: (
|
|
49
72
|
value: number,
|
|
73
|
+
text: string,
|
|
50
74
|
event: React.FocusEvent<HTMLInputElement, Element>,
|
|
51
75
|
) => void
|
|
52
76
|
onBlur?: (
|
|
53
77
|
value: number,
|
|
78
|
+
text: string,
|
|
54
79
|
event: React.FocusEvent<HTMLInputElement, Element>,
|
|
55
80
|
) => void
|
|
81
|
+
|
|
82
|
+
children?: ReactNode
|
|
56
83
|
}
|
|
57
84
|
|
|
58
|
-
|
|
59
|
-
* Input with some useful functions for entering numerical values.
|
|
60
|
-
*/
|
|
61
|
-
export function NumberInput({
|
|
62
|
-
value,
|
|
85
|
+
function InternalInput({
|
|
63
86
|
min,
|
|
64
87
|
max,
|
|
65
88
|
step,
|
|
66
|
-
defaultValue
|
|
89
|
+
defaultValue,
|
|
67
90
|
units,
|
|
68
|
-
disabled
|
|
69
|
-
placeholder,
|
|
70
|
-
readOnly,
|
|
71
|
-
tabIndex,
|
|
72
|
-
typeNumber = false,
|
|
91
|
+
disabled,
|
|
73
92
|
digit,
|
|
74
|
-
selectWithFocus
|
|
75
|
-
|
|
93
|
+
selectWithFocus,
|
|
94
|
+
blurOnEnter,
|
|
95
|
+
keepWithinRange,
|
|
96
|
+
clampValueOnBlur,
|
|
97
|
+
wheel,
|
|
98
|
+
keyboard,
|
|
99
|
+
wrapperClassName,
|
|
76
100
|
className,
|
|
77
101
|
onChange,
|
|
78
102
|
onFocus,
|
|
79
103
|
onBlur,
|
|
104
|
+
children: _children,
|
|
80
105
|
...props
|
|
81
|
-
}: NumberInputProps &
|
|
106
|
+
}: Omit<NumberInputProps, 'value'> &
|
|
82
107
|
Omit<ComponentPropsWithoutRef<'input'>, keyof NumberInputProps | 'type'>) {
|
|
83
|
-
const [
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
const
|
|
108
|
+
const [editing, setEditing] = useState(false)
|
|
109
|
+
const value = useNumberInputContext((s) => s.value)
|
|
110
|
+
const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber)
|
|
111
|
+
const change = useNumberInputContext((s) => s.change)
|
|
112
|
+
|
|
113
|
+
const error = useMemo(() => {
|
|
114
|
+
if (editing) return false
|
|
115
|
+
const v = valueAsNumber
|
|
116
|
+
return (min ?? -Infinity) > v || v > (max ?? Infinity)
|
|
117
|
+
}, [editing, max, min, valueAsNumber])
|
|
87
118
|
|
|
88
119
|
return (
|
|
89
120
|
<input
|
|
90
121
|
className={clsx('tremolo-number-input', className)}
|
|
91
|
-
value={
|
|
122
|
+
value={value}
|
|
92
123
|
min={min}
|
|
93
124
|
max={max}
|
|
94
125
|
step={step}
|
|
95
|
-
type=
|
|
96
|
-
spellCheck={'false'}
|
|
126
|
+
type="text"
|
|
97
127
|
disabled={disabled}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
tabIndex={tabIndex}
|
|
128
|
+
tabIndex={-1}
|
|
129
|
+
data-error={error}
|
|
101
130
|
onChange={(event) => {
|
|
102
131
|
const v = event.currentTarget.value
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
min ?? -Infinity,
|
|
107
|
-
max ?? Infinity,
|
|
108
|
-
)
|
|
109
|
-
if (onChange) onChange(newValue, event)
|
|
132
|
+
change(v)
|
|
133
|
+
const valueAsNumber = parseValue(v, units, digit).rawValue
|
|
134
|
+
onChange?.(valueAsNumber, v)
|
|
110
135
|
}}
|
|
111
136
|
onFocus={(event) => {
|
|
137
|
+
setEditing(true)
|
|
112
138
|
const v = event.currentTarget.value
|
|
113
|
-
const parsed = parseValue(v, units, digit
|
|
139
|
+
const parsed = parseValue(v, units, digit)
|
|
114
140
|
if (selectWithFocus == 'all') {
|
|
115
141
|
event.currentTarget.setSelectionRange(0, v.length)
|
|
116
142
|
} else if (selectWithFocus == 'number') {
|
|
@@ -119,17 +145,24 @@ export function NumberInput({
|
|
|
119
145
|
parsed.formatValue.length - parsed.unit.length,
|
|
120
146
|
)
|
|
121
147
|
}
|
|
122
|
-
|
|
148
|
+
onFocus?.(parsed.rawValue, parsed.formatValue, event)
|
|
123
149
|
}}
|
|
124
150
|
onBlur={(event) => {
|
|
151
|
+
setEditing(false)
|
|
125
152
|
// update value
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
153
|
+
let v = parseValue(event.currentTarget.value, units, digit).rawValue
|
|
154
|
+
if (clampValueOnBlur) {
|
|
155
|
+
v = safeClamp(v, min, max)
|
|
156
|
+
}
|
|
157
|
+
const parsed = parseValue(String(v), units, digit)
|
|
158
|
+
change(parsed.formatValue)
|
|
159
|
+
if (clampValueOnBlur) {
|
|
160
|
+
onChange?.(parsed.rawValue, parsed.formatValue)
|
|
161
|
+
}
|
|
162
|
+
onBlur?.(parsed.rawValue, parsed.formatValue, event)
|
|
130
163
|
}}
|
|
131
164
|
onKeyDown={(event) => {
|
|
132
|
-
if (
|
|
165
|
+
if (blurOnEnter && event.key == 'Enter') {
|
|
133
166
|
event.currentTarget.blur()
|
|
134
167
|
}
|
|
135
168
|
}}
|
|
@@ -137,3 +170,79 @@ export function NumberInput({
|
|
|
137
170
|
/>
|
|
138
171
|
)
|
|
139
172
|
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Input with some useful functions for entering numerical values.
|
|
176
|
+
* @category NumberInput
|
|
177
|
+
*/
|
|
178
|
+
export function NumberInput({
|
|
179
|
+
value,
|
|
180
|
+
min,
|
|
181
|
+
max,
|
|
182
|
+
step = 1,
|
|
183
|
+
defaultValue,
|
|
184
|
+
units,
|
|
185
|
+
disabled = false,
|
|
186
|
+
digit,
|
|
187
|
+
selectWithFocus = 'none',
|
|
188
|
+
blurOnEnter = true,
|
|
189
|
+
keepWithinRange = true,
|
|
190
|
+
clampValueOnBlur = true,
|
|
191
|
+
wheel = ['raw', 1],
|
|
192
|
+
keyboard = ['raw', 1],
|
|
193
|
+
wrapperClassName,
|
|
194
|
+
className,
|
|
195
|
+
onChange,
|
|
196
|
+
onFocus,
|
|
197
|
+
onBlur,
|
|
198
|
+
children,
|
|
199
|
+
...props
|
|
200
|
+
}: NumberInputProps &
|
|
201
|
+
Omit<ComponentPropsWithoutRef<'input'>, keyof NumberInputProps | 'type'>) {
|
|
202
|
+
return (
|
|
203
|
+
<NumberInputProvider
|
|
204
|
+
value={String(value)}
|
|
205
|
+
min={min}
|
|
206
|
+
max={max}
|
|
207
|
+
step={step}
|
|
208
|
+
units={units}
|
|
209
|
+
digit={digit}
|
|
210
|
+
keepWithinRange={keepWithinRange}
|
|
211
|
+
onChange={onChange}
|
|
212
|
+
>
|
|
213
|
+
<div
|
|
214
|
+
className={clsx('tremolo-number-input-wrapper', wrapperClassName)}
|
|
215
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
216
|
+
tabIndex={0}
|
|
217
|
+
data-stepper={!!children}
|
|
218
|
+
>
|
|
219
|
+
<InternalInput
|
|
220
|
+
min={min}
|
|
221
|
+
max={max}
|
|
222
|
+
step={step}
|
|
223
|
+
defaultValue={defaultValue}
|
|
224
|
+
units={units}
|
|
225
|
+
disabled={disabled}
|
|
226
|
+
digit={digit}
|
|
227
|
+
selectWithFocus={selectWithFocus}
|
|
228
|
+
blurOnEnter={blurOnEnter}
|
|
229
|
+
keepWithinRange={keepWithinRange}
|
|
230
|
+
clampValueOnBlur={clampValueOnBlur}
|
|
231
|
+
wheel={wheel}
|
|
232
|
+
keyboard={keyboard}
|
|
233
|
+
wrapperClassName={wrapperClassName}
|
|
234
|
+
className={className}
|
|
235
|
+
onChange={onChange}
|
|
236
|
+
onFocus={onFocus}
|
|
237
|
+
onBlur={onBlur}
|
|
238
|
+
{...props}
|
|
239
|
+
/>
|
|
240
|
+
{children}
|
|
241
|
+
</div>
|
|
242
|
+
</NumberInputProvider>
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export * from './Stepper'
|
|
247
|
+
export * from './IncrementStepper'
|
|
248
|
+
export * from './DecrementStepper'
|
|
@@ -12,6 +12,7 @@ import { defaultWhiteKeyWidth } from './WhiteKey'
|
|
|
12
12
|
|
|
13
13
|
type Props = KeyProps & Omit<ComponentPropsWithoutRef<'div'>, keyof KeyProps>
|
|
14
14
|
|
|
15
|
+
/** @category Piano */
|
|
15
16
|
export const BlackKey = forwardRef<KeyMethods, Props>(
|
|
16
17
|
(
|
|
17
18
|
{
|
|
@@ -27,8 +28,8 @@ export const BlackKey = forwardRef<KeyMethods, Props>(
|
|
|
27
28
|
__index,
|
|
28
29
|
__fill,
|
|
29
30
|
__width,
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
__onPlayNote,
|
|
32
|
+
__onStopNote,
|
|
32
33
|
__label,
|
|
33
34
|
...props
|
|
34
35
|
}: Props,
|
|
@@ -41,17 +42,17 @@ export const BlackKey = forwardRef<KeyMethods, Props>(
|
|
|
41
42
|
play() {
|
|
42
43
|
if (__disabled) return
|
|
43
44
|
setPlayed(true)
|
|
44
|
-
if (
|
|
45
|
+
if (__onPlayNote) __onPlayNote(__note!)
|
|
45
46
|
},
|
|
46
47
|
stop() {
|
|
47
48
|
setPlayed(false)
|
|
48
|
-
if (
|
|
49
|
+
if (__onStopNote) __onStopNote(__note!)
|
|
49
50
|
},
|
|
50
51
|
played() {
|
|
51
52
|
return played
|
|
52
53
|
},
|
|
53
54
|
}
|
|
54
|
-
}, [__disabled, __note,
|
|
55
|
+
}, [__disabled, __note, __onPlayNote, __onStopNote, played])
|
|
55
56
|
|
|
56
57
|
let keyLabelProps: KeyLabelProps = {}
|
|
57
58
|
if (children != undefined) {
|
|
@@ -82,24 +83,26 @@ export const BlackKey = forwardRef<KeyMethods, Props>(
|
|
|
82
83
|
onPointerDown={() => {
|
|
83
84
|
if (__disabled) return
|
|
84
85
|
setPlayed(true)
|
|
85
|
-
if (
|
|
86
|
+
if (__onPlayNote) __onPlayNote(__note!)
|
|
86
87
|
}}
|
|
87
88
|
onPointerEnter={() => {
|
|
88
89
|
if (__disabled) return
|
|
89
90
|
if (__glissando) {
|
|
90
91
|
setPlayed(true)
|
|
91
|
-
if (
|
|
92
|
+
if (__onPlayNote) __onPlayNote(__note!)
|
|
92
93
|
}
|
|
93
94
|
}}
|
|
94
95
|
onPointerUp={() => {
|
|
95
96
|
if (__disabled) return
|
|
96
97
|
setPlayed(false)
|
|
97
|
-
if (
|
|
98
|
+
if (__onStopNote) __onStopNote(__note!)
|
|
98
99
|
}}
|
|
99
100
|
onPointerLeave={() => {
|
|
100
101
|
if (__disabled) return
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
if (__glissando) {
|
|
103
|
+
setPlayed(false)
|
|
104
|
+
if (__onStopNote) __onStopNote(__note!)
|
|
105
|
+
}
|
|
103
106
|
}}
|
|
104
107
|
{...props}
|
|
105
108
|
>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
2
|
import { ComponentPropsWithoutRef, CSSProperties, ReactNode } from 'react'
|
|
3
3
|
|
|
4
|
+
/** @category Piano */
|
|
4
5
|
export interface KeyLabelProps {
|
|
5
6
|
/**
|
|
6
7
|
* override Piano.label
|
|
@@ -18,6 +19,7 @@ export interface KeyLabelProps {
|
|
|
18
19
|
__label?: (note: number, index: number) => ReactNode
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
/** @category Piano */
|
|
21
23
|
export function KeyLabel({
|
|
22
24
|
label,
|
|
23
25
|
className,
|
|
@@ -11,8 +11,10 @@ import { KeyLabel, KeyLabelProps } from './KeyLabel'
|
|
|
11
11
|
|
|
12
12
|
type Props = KeyProps & Omit<ComponentPropsWithoutRef<'div'>, keyof KeyProps>
|
|
13
13
|
|
|
14
|
+
/** @category Piano */
|
|
14
15
|
export const defaultWhiteKeyWidth = 40
|
|
15
16
|
|
|
17
|
+
/** @category Piano */
|
|
16
18
|
export const WhiteKey = forwardRef<KeyMethods, Props>(
|
|
17
19
|
(
|
|
18
20
|
{
|
|
@@ -28,8 +30,8 @@ export const WhiteKey = forwardRef<KeyMethods, Props>(
|
|
|
28
30
|
__index,
|
|
29
31
|
__fill,
|
|
30
32
|
__width,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
__onPlayNote,
|
|
34
|
+
__onStopNote,
|
|
33
35
|
__label,
|
|
34
36
|
...props
|
|
35
37
|
}: Props,
|
|
@@ -42,17 +44,17 @@ export const WhiteKey = forwardRef<KeyMethods, Props>(
|
|
|
42
44
|
play() {
|
|
43
45
|
if (__disabled) return
|
|
44
46
|
setPlayed(true)
|
|
45
|
-
if (
|
|
47
|
+
if (__onPlayNote) __onPlayNote(__note!)
|
|
46
48
|
},
|
|
47
49
|
stop() {
|
|
48
50
|
setPlayed(false)
|
|
49
|
-
if (
|
|
51
|
+
if (__onStopNote) __onStopNote(__note!)
|
|
50
52
|
},
|
|
51
53
|
played() {
|
|
52
54
|
return played
|
|
53
55
|
},
|
|
54
56
|
}
|
|
55
|
-
}, [__disabled, __note,
|
|
57
|
+
}, [__disabled, __note, __onPlayNote, __onStopNote, played])
|
|
56
58
|
|
|
57
59
|
let keyLabelProps: KeyLabelProps = {}
|
|
58
60
|
if (children != undefined) {
|
|
@@ -83,24 +85,26 @@ export const WhiteKey = forwardRef<KeyMethods, Props>(
|
|
|
83
85
|
onPointerDown={() => {
|
|
84
86
|
if (__disabled) return
|
|
85
87
|
setPlayed(true)
|
|
86
|
-
if (
|
|
88
|
+
if (__onPlayNote) __onPlayNote(__note!)
|
|
87
89
|
}}
|
|
88
90
|
onPointerEnter={() => {
|
|
89
91
|
if (__disabled) return
|
|
90
92
|
if (__glissando) {
|
|
91
93
|
setPlayed(true)
|
|
92
|
-
if (
|
|
94
|
+
if (__onPlayNote) __onPlayNote(__note!)
|
|
93
95
|
}
|
|
94
96
|
}}
|
|
95
97
|
onPointerUp={() => {
|
|
96
98
|
if (__disabled) return
|
|
97
99
|
setPlayed(false)
|
|
98
|
-
if (
|
|
100
|
+
if (__onStopNote) __onStopNote(__note!)
|
|
99
101
|
}}
|
|
100
102
|
onPointerLeave={() => {
|
|
101
103
|
if (__disabled) return
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
if (__glissando) {
|
|
105
|
+
setPlayed(false)
|
|
106
|
+
if (__onStopNote) __onStopNote(__note!)
|
|
107
|
+
}
|
|
104
108
|
}}
|
|
105
109
|
{...props}
|
|
106
110
|
>
|
|
@@ -33,6 +33,7 @@ import { defaultWhiteKeyWidth, WhiteKey } from './WhiteKey'
|
|
|
33
33
|
* - add scale highlight
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
+
/** @category Piano */
|
|
36
37
|
export interface PianoProps {
|
|
37
38
|
// required
|
|
38
39
|
noteRange: { first: number; last: number }
|
|
@@ -47,8 +48,8 @@ export interface PianoProps {
|
|
|
47
48
|
|
|
48
49
|
style?: CSSProperties
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
onPlayNote?: (noteNumber: number) => void
|
|
52
|
+
onStopNote?: (noteNumber: number) => void
|
|
52
53
|
|
|
53
54
|
label?: (note: number, index: number) => ReactNode
|
|
54
55
|
|
|
@@ -58,6 +59,7 @@ export interface PianoProps {
|
|
|
58
59
|
children?: ReactElement | ReactElement[]
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
/** @category Piano */
|
|
61
63
|
export function Piano({
|
|
62
64
|
noteRange,
|
|
63
65
|
glissando = true,
|
|
@@ -67,8 +69,8 @@ export function Piano({
|
|
|
67
69
|
height = fill ? '100%' : 160,
|
|
68
70
|
style,
|
|
69
71
|
className,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
onPlayNote,
|
|
73
|
+
onStopNote,
|
|
72
74
|
label,
|
|
73
75
|
children,
|
|
74
76
|
...props
|
|
@@ -76,7 +78,7 @@ export function Piano({
|
|
|
76
78
|
// -- state and ref ---
|
|
77
79
|
const [pressed, setPressed] = useState(false)
|
|
78
80
|
const [whiteNoteWidth, setWhiteNoteWidth] = useState(-1)
|
|
79
|
-
const keyRefs = useRef<RefObject<KeyMethods>[]>([])
|
|
81
|
+
const keyRefs = useRef<RefObject<KeyMethods | null>[]>([])
|
|
80
82
|
for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) {
|
|
81
83
|
keyRefs.current[i] = createRef<KeyMethods>()
|
|
82
84
|
}
|
|
@@ -153,7 +155,7 @@ export function Piano({
|
|
|
153
155
|
}
|
|
154
156
|
}, [fill, whiteKeyProps?.width, whiteNoteCount])
|
|
155
157
|
|
|
156
|
-
useEventListener(window, 'keydown', (e) => {
|
|
158
|
+
useEventListener(globalThis.window, 'keydown', (e) => {
|
|
157
159
|
if (e.repeat) return
|
|
158
160
|
if (!keyboardShortcuts) return
|
|
159
161
|
const index = keyboardShortcuts.keys.indexOf(e.key)
|
|
@@ -164,7 +166,7 @@ export function Piano({
|
|
|
164
166
|
}
|
|
165
167
|
})
|
|
166
168
|
|
|
167
|
-
useEventListener(window, 'keyup', (e) => {
|
|
169
|
+
useEventListener(globalThis.window, 'keyup', (e) => {
|
|
168
170
|
if (e.repeat) return
|
|
169
171
|
if (!keyboardShortcuts) return
|
|
170
172
|
const index = keyboardShortcuts.keys.indexOf(e.key)
|
|
@@ -204,8 +206,8 @@ export function Piano({
|
|
|
204
206
|
__index={index}
|
|
205
207
|
__fill={fill}
|
|
206
208
|
__width={whiteNoteWidth}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
+
__onPlayNote={onPlayNote}
|
|
210
|
+
__onStopNote={onStopNote}
|
|
209
211
|
__label={label}
|
|
210
212
|
{...whiteKeyProps}
|
|
211
213
|
/>
|
|
@@ -220,8 +222,8 @@ export function Piano({
|
|
|
220
222
|
__index={index}
|
|
221
223
|
__fill={fill}
|
|
222
224
|
__width={whiteNoteWidth * 0.65}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
+
__onPlayNote={onPlayNote}
|
|
226
|
+
__onStopNote={onStopNote}
|
|
225
227
|
__label={label}
|
|
226
228
|
{...blackKeyProps}
|
|
227
229
|
/>
|
|
@@ -236,4 +238,4 @@ export * from './keyboardShortcuts'
|
|
|
236
238
|
|
|
237
239
|
export { WhiteKey } from './WhiteKey'
|
|
238
240
|
export { BlackKey } from './BlackKey'
|
|
239
|
-
export { KeyLabel } from './KeyLabel'
|
|
241
|
+
export { KeyLabel, type KeyLabelProps } from './KeyLabel'
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { CSSProperties, ReactElement, ReactNode } from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @category Piano
|
|
5
|
+
*/
|
|
3
6
|
export interface KeyProps {
|
|
4
7
|
width?: number
|
|
5
8
|
height?: number | `${number}%`
|
|
@@ -26,13 +29,16 @@ export interface KeyProps {
|
|
|
26
29
|
/** @internal */
|
|
27
30
|
__width?: number
|
|
28
31
|
/** @internal */
|
|
29
|
-
|
|
32
|
+
__onPlayNote?: (noteNumber: number) => void
|
|
30
33
|
/** @internal */
|
|
31
|
-
|
|
34
|
+
__onStopNote?: (noteNumber: number) => void
|
|
32
35
|
/** @internal */
|
|
33
36
|
__label?: (note: number, index: number) => ReactNode
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
/**
|
|
40
|
+
* @category Piano
|
|
41
|
+
*/
|
|
36
42
|
export interface KeyMethods {
|
|
37
43
|
play: () => void
|
|
38
44
|
stop: () => void
|
|
@@ -4,9 +4,10 @@ import { ComponentPropsWithoutRef, ReactNode } from 'react'
|
|
|
4
4
|
import { xor } from '@tremolo-ui/functions'
|
|
5
5
|
import { generateOptionsList, ScaleOptions } from '@tremolo-ui/functions/Slider'
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { useSliderContext } from './context'
|
|
8
8
|
import { ScaleOption } from './ScaleOption'
|
|
9
9
|
|
|
10
|
+
/** @category Slider */
|
|
10
11
|
export interface ScaleProps {
|
|
11
12
|
gap?: number | string
|
|
12
13
|
options?: ScaleOptions
|
|
@@ -16,6 +17,7 @@ export interface ScaleProps {
|
|
|
16
17
|
type Props = ScaleProps &
|
|
17
18
|
Omit<ComponentPropsWithoutRef<'div'>, keyof ScaleProps>
|
|
18
19
|
|
|
20
|
+
/** @category Slider */
|
|
19
21
|
export function Scale({
|
|
20
22
|
gap = 6,
|
|
21
23
|
options,
|
|
@@ -24,11 +26,11 @@ export function Scale({
|
|
|
24
26
|
style,
|
|
25
27
|
...props
|
|
26
28
|
}: Props) {
|
|
27
|
-
const min =
|
|
28
|
-
const max =
|
|
29
|
-
const step =
|
|
30
|
-
const vertical =
|
|
31
|
-
const reverse =
|
|
29
|
+
const min = useSliderContext((s) => s.min)
|
|
30
|
+
const max = useSliderContext((s) => s.max)
|
|
31
|
+
const step = useSliderContext((s) => s.step)
|
|
32
|
+
const vertical = useSliderContext((s) => s.vertical)
|
|
33
|
+
const reverse = useSliderContext((s) => s.reverse)
|
|
32
34
|
|
|
33
35
|
const optionsList = options
|
|
34
36
|
? generateOptionsList(options, min, max, step)
|
|
@@ -4,8 +4,9 @@ import { ComponentPropsWithoutRef, CSSProperties, useCallback } from 'react'
|
|
|
4
4
|
import { toFixed, xor, normalizeValue } from '@tremolo-ui/functions'
|
|
5
5
|
import { ScaleType } from '@tremolo-ui/functions/Slider'
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { useSliderContext } from './context'
|
|
8
8
|
|
|
9
|
+
/** @category Slider */
|
|
9
10
|
export interface ScaleOptionProps {
|
|
10
11
|
// required
|
|
11
12
|
value: number
|
|
@@ -31,6 +32,7 @@ export interface ScaleOptionProps {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
/** @category Slider */
|
|
34
36
|
export function ScaleOption({
|
|
35
37
|
value,
|
|
36
38
|
type = 'mark-number',
|
|
@@ -46,11 +48,11 @@ export function ScaleOption({
|
|
|
46
48
|
...props
|
|
47
49
|
}: ScaleOptionProps &
|
|
48
50
|
Omit<ComponentPropsWithoutRef<'div'>, keyof ScaleOptionProps>) {
|
|
49
|
-
const min =
|
|
50
|
-
const max =
|
|
51
|
-
const skew =
|
|
52
|
-
const vertical =
|
|
53
|
-
const reverse =
|
|
51
|
+
const min = useSliderContext((s) => s.min)
|
|
52
|
+
const max = useSliderContext((s) => s.max)
|
|
53
|
+
const skew = useSliderContext((s) => s.skew)
|
|
54
|
+
const vertical = useSliderContext((s) => s.vertical)
|
|
55
|
+
const reverse = useSliderContext((s) => s.reverse)
|
|
54
56
|
|
|
55
57
|
const calcPercent = useCallback(
|
|
56
58
|
(rawValue: number) => {
|