@tremolo-ui/react 0.1.6 → 0.2.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/index.cjs +1329 -1152
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +238 -183
- package/dist/index.d.cts +367 -366
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +367 -366
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1313 -1125
- package/dist/index.js.map +1 -1
- package/package.json +17 -19
- package/src/components/AnimationCanvas/index.tsx +0 -7
- package/src/components/DragObserver/index.tsx +9 -7
- package/src/components/Knob/ActiveLine.tsx +29 -0
- package/src/components/Knob/InactiveLine.tsx +53 -0
- package/src/components/Knob/SVGRoot.tsx +39 -0
- package/src/components/Knob/Thumb.tsx +64 -0
- package/src/components/Knob/context.tsx +120 -0
- package/src/components/Knob/index.css +25 -27
- package/src/components/Knob/index.tsx +110 -164
- package/src/components/NumberInput/DecrementStepper.tsx +0 -2
- package/src/components/NumberInput/IncrementStepper.tsx +0 -2
- package/src/components/NumberInput/InternalInput.tsx +3 -1
- package/src/components/NumberInput/Stepper.tsx +0 -2
- package/src/components/NumberInput/context.tsx +0 -1
- package/src/components/NumberInput/index.css +31 -15
- package/src/components/NumberInput/index.tsx +21 -14
- package/src/components/Piano/KeyLabel.tsx +0 -2
- package/src/components/Piano/context.tsx +0 -2
- package/src/components/Piano/index.css +7 -14
- package/src/components/Piano/index.tsx +14 -15
- package/src/components/Piano/key.tsx +0 -10
- package/src/components/Piano/keyboardShortcuts.ts +0 -2
- package/src/components/PointsEditor/Background.tsx +17 -0
- package/src/components/PointsEditor/Container.tsx +26 -0
- package/src/components/PointsEditor/Point.tsx +137 -0
- package/src/components/PointsEditor/context.tsx +91 -0
- package/src/components/PointsEditor/index.css +30 -0
- package/src/components/PointsEditor/index.tsx +129 -0
- package/src/components/Slider/Scale.tsx +0 -2
- package/src/components/Slider/ScaleOption.tsx +0 -2
- package/src/components/Slider/Thumb.tsx +1 -5
- package/src/components/Slider/Track.tsx +1 -3
- package/src/components/Slider/context.tsx +0 -1
- package/src/components/Slider/index.css +33 -8
- package/src/components/Slider/index.tsx +61 -33
- package/src/components/WheelObserver/index.tsx +17 -19
- package/src/components/XYPad/Area.tsx +4 -5
- package/src/components/XYPad/Thumb.tsx +8 -7
- package/src/components/XYPad/index.css +18 -4
- package/src/components/XYPad/index.tsx +71 -38
- package/src/components/_util/composeRefs.tsx +63 -0
- package/src/components/_util/index.ts +23 -6
- package/src/components/_util/type.ts +1 -0
- package/src/hooks/useAnimationFrame.ts +0 -3
- package/src/hooks/useCallbackRef.ts +2 -2
- package/src/hooks/useDrag.ts +12 -11
- package/src/hooks/useDragWithElement.ts +12 -17
- package/src/hooks/useEventListener.ts +6 -9
- package/src/hooks/useInterval.ts +0 -3
- package/src/hooks/useLongPress.ts +0 -3
- package/src/hooks/useMIDIAccess.ts +0 -1
- package/src/hooks/useMIDIInput.ts +0 -1
- package/src/hooks/useMIDIMessage.ts +0 -1
- package/src/hooks/usePianoDrag.ts +75 -0
- package/src/index.ts +21 -30
- package/src/styles/global.css +16 -4
- package/dist/index.css.map +0 -1
- package/src/components/AnimationKnob/index.tsx +0 -314
|
@@ -5,22 +5,38 @@ import {
|
|
|
5
5
|
useCallback,
|
|
6
6
|
useImperativeHandle,
|
|
7
7
|
useRef,
|
|
8
|
+
useState,
|
|
8
9
|
} from 'react'
|
|
9
10
|
|
|
10
11
|
import {
|
|
11
12
|
clamp,
|
|
12
13
|
InputEventOption,
|
|
13
14
|
normalizeValue,
|
|
14
|
-
radian,
|
|
15
15
|
rawValue,
|
|
16
16
|
stepValue,
|
|
17
17
|
} from '@tremolo-ui/functions'
|
|
18
18
|
|
|
19
19
|
import { useDrag } from '../../hooks/useDrag'
|
|
20
20
|
import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
addUserSelectNone,
|
|
23
|
+
Cursor,
|
|
24
|
+
removeUserSelectNone,
|
|
25
|
+
resetCursorStyle,
|
|
26
|
+
setCursorStyle,
|
|
27
|
+
} from '../_util'
|
|
28
|
+
|
|
29
|
+
import { ActiveLine } from './ActiveLine'
|
|
30
|
+
import { KnobProvider } from './context'
|
|
31
|
+
import { InactiveLine } from './InactiveLine'
|
|
32
|
+
import { SVGRoot } from './SVGRoot'
|
|
33
|
+
import { Thumb } from './Thumb'
|
|
34
|
+
|
|
35
|
+
const defaultExternalStyles: KnobProps['externalStyles'] = {
|
|
36
|
+
userSelectNone: true,
|
|
37
|
+
cursor: 'grabbing',
|
|
38
|
+
}
|
|
22
39
|
|
|
23
|
-
/** @category Knob */
|
|
24
40
|
export interface KnobProps {
|
|
25
41
|
// required
|
|
26
42
|
value: number
|
|
@@ -29,9 +45,10 @@ export interface KnobProps {
|
|
|
29
45
|
|
|
30
46
|
// optional
|
|
31
47
|
step?: number
|
|
32
|
-
skew?: number // | SkewFunction
|
|
48
|
+
skew?: number // | SkewFunction // TODO
|
|
33
49
|
/**
|
|
34
50
|
* value set when double-clicking
|
|
51
|
+
* restriction: enableDoubleClickDefault = true
|
|
35
52
|
* @default min
|
|
36
53
|
* @see enableDoubleClickDefault
|
|
37
54
|
*/
|
|
@@ -46,14 +63,22 @@ export interface KnobProps {
|
|
|
46
63
|
/** width and height */
|
|
47
64
|
size?: number | string
|
|
48
65
|
|
|
49
|
-
/**
|
|
50
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Global style to apply when dragged
|
|
68
|
+
* @default defaultExternalStyles
|
|
69
|
+
*/
|
|
70
|
+
externalStyles?: {
|
|
71
|
+
userSelectNone?: boolean
|
|
72
|
+
cursor?: Cursor
|
|
73
|
+
}
|
|
51
74
|
/**
|
|
52
75
|
* wheel control option
|
|
76
|
+
* If null, no event will be triggered
|
|
53
77
|
*/
|
|
54
78
|
wheel?: InputEventOption | null
|
|
55
79
|
/**
|
|
56
80
|
* keyboard control option
|
|
81
|
+
* If null, no event will be triggered
|
|
57
82
|
*/
|
|
58
83
|
keyboard?: InputEventOption | null
|
|
59
84
|
enableDoubleClickDefault?: boolean
|
|
@@ -61,51 +86,20 @@ export interface KnobProps {
|
|
|
61
86
|
disabled?: boolean
|
|
62
87
|
readonly?: boolean
|
|
63
88
|
|
|
64
|
-
/** color */
|
|
65
|
-
activeLine?: string
|
|
66
|
-
/** color */
|
|
67
|
-
inactiveLine?: string
|
|
68
|
-
/** color */
|
|
69
|
-
thumb?: string
|
|
70
|
-
/** color */
|
|
71
|
-
thumbLine?: string
|
|
72
|
-
/** percent (0-100) */
|
|
73
|
-
thumbSize?: number
|
|
74
|
-
/** percent (0-100) */
|
|
75
|
-
thumbLineWeight?: number
|
|
76
|
-
/** percent (0-100) */
|
|
77
|
-
thumbLineLength?: number
|
|
78
|
-
/** line weight [px] */
|
|
79
|
-
lineWeight?: number
|
|
80
89
|
/** angle range [degree] */
|
|
81
90
|
angleRange?: number
|
|
82
91
|
|
|
83
|
-
classes?: {
|
|
84
|
-
activeLine?: string
|
|
85
|
-
inactiveLine?: string
|
|
86
|
-
thumb?: string
|
|
87
|
-
thumbLine?: string
|
|
88
|
-
}
|
|
89
|
-
|
|
90
92
|
onChange?: (value: number) => void
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
/**
|
|
94
|
-
* @category Knob
|
|
95
|
-
*/
|
|
96
95
|
export interface KnobMethods {
|
|
97
96
|
focus: () => void
|
|
98
97
|
blur: () => void
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
type Props = KnobProps & Omit<ComponentPropsWithoutRef<'
|
|
100
|
+
type Props = KnobProps & Omit<ComponentPropsWithoutRef<'div'>, keyof KnobProps>
|
|
102
101
|
|
|
103
|
-
|
|
104
|
-
* Interactive rotary knob component implemented in SVG.
|
|
105
|
-
*
|
|
106
|
-
* @category Knob
|
|
107
|
-
*/
|
|
108
|
-
export const Knob = forwardRef<KnobMethods, Props>(
|
|
102
|
+
const Root = forwardRef<KnobMethods, Props>(
|
|
109
103
|
(
|
|
110
104
|
{
|
|
111
105
|
value,
|
|
@@ -115,34 +109,30 @@ export const Knob = forwardRef<KnobMethods, Props>(
|
|
|
115
109
|
skew = 1,
|
|
116
110
|
defaultValue = min,
|
|
117
111
|
startValue = min,
|
|
118
|
-
size
|
|
119
|
-
|
|
112
|
+
size,
|
|
113
|
+
externalStyles: _externalStyles,
|
|
120
114
|
wheel = ['raw', 1],
|
|
121
115
|
keyboard = ['raw', 1],
|
|
122
116
|
enableDoubleClickDefault = true,
|
|
123
117
|
disabled = false,
|
|
124
118
|
readonly = false,
|
|
125
|
-
activeLine,
|
|
126
|
-
inactiveLine,
|
|
127
|
-
thumb,
|
|
128
|
-
thumbLine,
|
|
129
|
-
thumbSize = 84,
|
|
130
|
-
thumbLineWeight = 6,
|
|
131
|
-
thumbLineLength = 35,
|
|
132
|
-
lineWeight = 6,
|
|
133
119
|
angleRange = 270,
|
|
134
120
|
onChange,
|
|
135
121
|
onKeyDown,
|
|
136
122
|
onPointerDown,
|
|
137
123
|
onDoubleClick,
|
|
138
124
|
className,
|
|
139
|
-
|
|
125
|
+
style,
|
|
126
|
+
children,
|
|
140
127
|
...props
|
|
141
128
|
}: Props,
|
|
142
129
|
forwardedRef,
|
|
143
130
|
) => {
|
|
131
|
+
const [dragging, setDragging] = useState(false)
|
|
144
132
|
const valueRef = useRef(0)
|
|
145
|
-
const elmRef = useRef<
|
|
133
|
+
const elmRef = useRef<HTMLElement | SVGElement>(null)
|
|
134
|
+
|
|
135
|
+
const externalStyles = { ...defaultExternalStyles, ..._externalStyles }
|
|
146
136
|
|
|
147
137
|
// --- internal functions ---
|
|
148
138
|
const onDrag = useCallback(
|
|
@@ -184,14 +174,20 @@ export const Knob = forwardRef<KnobMethods, Props>(
|
|
|
184
174
|
)
|
|
185
175
|
|
|
186
176
|
// --- hooks ---
|
|
187
|
-
const [touchMoveRefCallback, pointerDownHandler] = useDrag<
|
|
177
|
+
const [touchMoveRefCallback, pointerDownHandler] = useDrag<
|
|
178
|
+
HTMLElement | SVGElement
|
|
179
|
+
>({
|
|
188
180
|
onDrag: onDrag,
|
|
189
181
|
onDragStart: () => {
|
|
182
|
+
setDragging(true)
|
|
190
183
|
valueRef.current = normalizeValue(value, min, max, skew)
|
|
191
|
-
if (
|
|
184
|
+
if (externalStyles.userSelectNone) addUserSelectNone()
|
|
185
|
+
if (externalStyles.cursor) setCursorStyle(externalStyles.cursor)
|
|
192
186
|
},
|
|
193
187
|
onDragEnd: () => {
|
|
194
|
-
|
|
188
|
+
setDragging(false)
|
|
189
|
+
if (externalStyles.userSelectNone) removeUserSelectNone()
|
|
190
|
+
if (externalStyles.cursor) resetCursorStyle()
|
|
195
191
|
},
|
|
196
192
|
})
|
|
197
193
|
|
|
@@ -219,118 +215,68 @@ export const Knob = forwardRef<KnobMethods, Props>(
|
|
|
219
215
|
}
|
|
220
216
|
}, [])
|
|
221
217
|
|
|
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 // ロータリー終了位置
|
|
233
|
-
const x1 = center + center * Math.cos(radian(r1 - 90))
|
|
234
|
-
const y1 = center + center * Math.sin(radian(r1 - 90))
|
|
235
|
-
const x2 = center + center * Math.cos(radian(r2 - 90))
|
|
236
|
-
const y2 = center + center * Math.sin(radian(r2 - 90))
|
|
237
|
-
const x3 = center + center * Math.cos(radian(r3 - 90))
|
|
238
|
-
const y3 = center + center * Math.sin(radian(r3 - 90))
|
|
239
|
-
const x4 = center + center * Math.cos(radian(r4 - 90))
|
|
240
|
-
const y4 = center + center * Math.sin(radian(r4 - 90))
|
|
241
|
-
|
|
242
218
|
return (
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
width={size}
|
|
252
|
-
height={size}
|
|
253
|
-
tabIndex={0}
|
|
254
|
-
role="slider"
|
|
255
|
-
aria-valuenow={value}
|
|
256
|
-
aria-valuemin={min}
|
|
257
|
-
aria-valuemax={max}
|
|
258
|
-
aria-disabled={disabled}
|
|
259
|
-
aria-readonly={readonly}
|
|
260
|
-
onPointerDown={(event) => {
|
|
261
|
-
pointerDownHandler(event)
|
|
262
|
-
onPointerDown?.(event)
|
|
263
|
-
}}
|
|
264
|
-
onDoubleClick={(event) => {
|
|
265
|
-
if (enableDoubleClickDefault && onChange) {
|
|
266
|
-
onChange(defaultValue)
|
|
267
|
-
}
|
|
268
|
-
onDoubleClick?.(event)
|
|
269
|
-
}}
|
|
270
|
-
onKeyDown={(event) => {
|
|
271
|
-
handleKeyDown(event)
|
|
272
|
-
onKeyDown?.(event)
|
|
273
|
-
}}
|
|
274
|
-
{...props}
|
|
219
|
+
<KnobProvider
|
|
220
|
+
value={value}
|
|
221
|
+
min={min}
|
|
222
|
+
max={max}
|
|
223
|
+
step={step}
|
|
224
|
+
skew={skew}
|
|
225
|
+
startValue={startValue}
|
|
226
|
+
angleRange={angleRange}
|
|
275
227
|
>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
/>
|
|
316
|
-
<line
|
|
317
|
-
className={clsx('tremolo-knob-thumb-line', classes?.thumbLine)}
|
|
318
|
-
x1="50%"
|
|
319
|
-
y1={`${padding}%`}
|
|
320
|
-
x2="50%"
|
|
321
|
-
y2={`${thumbLineLength}%`}
|
|
322
|
-
stroke={thumbLine || 'currentColor'}
|
|
323
|
-
strokeWidth={`${thumbLineWeight}%`}
|
|
324
|
-
// NOTE
|
|
325
|
-
// https://bugs.webkit.org/show_bug.cgi?id=201854
|
|
326
|
-
// https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/transform-origin#browser_compatibility
|
|
327
|
-
style={{
|
|
328
|
-
transform: `rotate(${r1 + p * angleRange}deg)`,
|
|
329
|
-
transformOrigin: '50% 50%',
|
|
330
|
-
}}
|
|
331
|
-
/>
|
|
332
|
-
</svg>
|
|
333
|
-
</svg>
|
|
228
|
+
<div
|
|
229
|
+
ref={(elm) => {
|
|
230
|
+
elmRef.current = elm
|
|
231
|
+
wheelRefCallback(elm)
|
|
232
|
+
touchMoveRefCallback(elm)
|
|
233
|
+
}}
|
|
234
|
+
className={clsx('tremolo-knob', className)}
|
|
235
|
+
tabIndex={0}
|
|
236
|
+
role="slider"
|
|
237
|
+
aria-valuenow={value}
|
|
238
|
+
aria-valuemin={min}
|
|
239
|
+
aria-valuemax={max}
|
|
240
|
+
aria-disabled={disabled}
|
|
241
|
+
aria-readonly={readonly}
|
|
242
|
+
data-dragging={dragging}
|
|
243
|
+
style={{
|
|
244
|
+
width: size,
|
|
245
|
+
height: size,
|
|
246
|
+
...style,
|
|
247
|
+
}}
|
|
248
|
+
onPointerDown={(event) => {
|
|
249
|
+
pointerDownHandler(event)
|
|
250
|
+
onPointerDown?.(event)
|
|
251
|
+
}}
|
|
252
|
+
onDoubleClick={(event) => {
|
|
253
|
+
if (enableDoubleClickDefault && onChange) {
|
|
254
|
+
onChange(defaultValue)
|
|
255
|
+
}
|
|
256
|
+
onDoubleClick?.(event)
|
|
257
|
+
}}
|
|
258
|
+
onKeyDown={(event) => {
|
|
259
|
+
handleKeyDown(event)
|
|
260
|
+
onKeyDown?.(event)
|
|
261
|
+
}}
|
|
262
|
+
{...props}
|
|
263
|
+
>
|
|
264
|
+
{children || <SVGRoot />}
|
|
265
|
+
</div>
|
|
266
|
+
</KnobProvider>
|
|
334
267
|
)
|
|
335
268
|
},
|
|
336
269
|
)
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Interactive rotary knob component implemented in SVG.
|
|
273
|
+
*/
|
|
274
|
+
export const Knob = {
|
|
275
|
+
Root,
|
|
276
|
+
SVGRoot,
|
|
277
|
+
InactiveLine,
|
|
278
|
+
ActiveLine,
|
|
279
|
+
Thumb,
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export { useKnobContext } from './context'
|
|
@@ -5,13 +5,11 @@ import { useLongPress } from '../../hooks/useLongPress'
|
|
|
5
5
|
|
|
6
6
|
import { useNumberInputContext } from './context'
|
|
7
7
|
|
|
8
|
-
/** @category NumberInput */
|
|
9
8
|
export interface DecrementStepperProps {
|
|
10
9
|
size?: number
|
|
11
10
|
children?: ReactNode
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
/** @category NumberInput */
|
|
15
13
|
export function DecrementStepper({
|
|
16
14
|
size = 12,
|
|
17
15
|
children,
|
|
@@ -5,13 +5,11 @@ import { useLongPress } from '../../hooks/useLongPress'
|
|
|
5
5
|
|
|
6
6
|
import { useNumberInputContext } from './context'
|
|
7
7
|
|
|
8
|
-
/** @category NumberInput */
|
|
9
8
|
export interface IncrementStepperProps {
|
|
10
9
|
size?: number
|
|
11
10
|
children?: ReactNode
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
/** @category NumberInput */
|
|
15
13
|
export function IncrementStepper({
|
|
16
14
|
size = 12,
|
|
17
15
|
children,
|
|
@@ -96,7 +96,9 @@ export const InternalInput = forwardRef<
|
|
|
96
96
|
let newValue
|
|
97
97
|
if (eventType == 'normalized') {
|
|
98
98
|
if (!min || !max) {
|
|
99
|
-
throw new Error(
|
|
99
|
+
throw new Error(
|
|
100
|
+
'[NumberInput] "min" and "max" are required when InputEventOption[0] is set to "normalized".',
|
|
101
|
+
)
|
|
100
102
|
}
|
|
101
103
|
const n = normalizeValue(valueAsNumber, min, max)
|
|
102
104
|
newValue = rawValue(n + x, min, max)
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
2
|
import { ComponentPropsWithoutRef, ReactNode } from 'react'
|
|
3
3
|
|
|
4
|
-
/** @category NumberInput */
|
|
5
4
|
export interface StepperProps {
|
|
6
5
|
/** Display only when hovering. */
|
|
7
6
|
dynamic?: boolean
|
|
8
7
|
children?: ReactNode
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
/** @category NumberInput */
|
|
12
10
|
export function Stepper({
|
|
13
11
|
dynamic = true,
|
|
14
12
|
children,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
.tremolo-number-input-wrapper {
|
|
2
2
|
--border-radius: 4px;
|
|
3
3
|
--stepper-width: 20px;
|
|
4
|
-
--active-color:
|
|
4
|
+
--active-color: oklch(59.5% 0.17548 266.373);
|
|
5
|
+
--border-color: oklch(81% 0 0);
|
|
5
6
|
|
|
6
7
|
display: inline-flex;
|
|
7
8
|
flex-direction: row;
|
|
@@ -9,13 +10,13 @@
|
|
|
9
10
|
width: 140px;
|
|
10
11
|
height: min-content;
|
|
11
12
|
border-style: solid;
|
|
12
|
-
border-color:
|
|
13
|
+
border-color: var(--border-color);
|
|
13
14
|
border-width: 0px;
|
|
14
15
|
outline: none;
|
|
15
16
|
transition: all 0.1s;
|
|
16
17
|
|
|
17
18
|
&:not(:focus-within):has(> .tremolo-number-input[data-error='true']) {
|
|
18
|
-
border-color:
|
|
19
|
+
border-color: oklch(64.245% 0.21266 25.499);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
&[data-variant='outline'] {
|
|
@@ -23,29 +24,30 @@
|
|
|
23
24
|
border-radius: var(--border-radius);
|
|
24
25
|
|
|
25
26
|
&:hover {
|
|
26
|
-
border-color:
|
|
27
|
+
--border-color: oklch(65.335% 0 0);
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
&:focus-within {
|
|
30
|
-
border-color: var(--active-color);
|
|
31
|
+
--border-color: var(--active-color);
|
|
31
32
|
box-shadow: 0px 0px 0px 2px
|
|
32
33
|
color-mix(in srgb, var(--active-color) 10%, transparent);
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
&[data-variant='filled'] {
|
|
38
|
+
--bg: oklch(95.88% 0 0);
|
|
37
39
|
border-width: 1px;
|
|
38
40
|
border-radius: var(--border-radius);
|
|
39
41
|
border-color: transparent;
|
|
40
|
-
background-color:
|
|
42
|
+
background-color: var(--bg);
|
|
41
43
|
|
|
42
44
|
&:hover {
|
|
43
|
-
|
|
45
|
+
--bg: oklch(93.166% 0 0);
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
&:focus-within {
|
|
47
49
|
background-color: inherit;
|
|
48
|
-
border-color: var(--active-color);
|
|
50
|
+
--border-color: var(--active-color);
|
|
49
51
|
box-shadow: 0px 0px 0px 2px
|
|
50
52
|
color-mix(in srgb, var(--active-color) 10%, transparent);
|
|
51
53
|
}
|
|
@@ -55,11 +57,11 @@
|
|
|
55
57
|
border-bottom-width: 1px;
|
|
56
58
|
|
|
57
59
|
&:hover {
|
|
58
|
-
border-color:
|
|
60
|
+
--border-color: oklch(65.335% 0 0);
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
&:focus-within {
|
|
62
|
-
border-color: var(--active-color);
|
|
64
|
+
--border-color: var(--active-color);
|
|
63
65
|
box-shadow: 0px 2px 0px 0px
|
|
64
66
|
color-mix(in srgb, var(--active-color) 10%, transparent);
|
|
65
67
|
}
|
|
@@ -116,7 +118,7 @@
|
|
|
116
118
|
.tremolo-number-input-increment-stepper {
|
|
117
119
|
border-inline-start-width: 1px;
|
|
118
120
|
border-inline-start-style: solid;
|
|
119
|
-
border-inline-start-color:
|
|
121
|
+
border-inline-start-color: oklch(84.522% 0 0);
|
|
120
122
|
font-size: calc(0.75rem);
|
|
121
123
|
display: flex;
|
|
122
124
|
justify-content: center;
|
|
@@ -128,7 +130,7 @@
|
|
|
128
130
|
border-top-right-radius: var(--border-radius);
|
|
129
131
|
|
|
130
132
|
&[aria-disabled='false']:active {
|
|
131
|
-
background-color:
|
|
133
|
+
background-color: oklch(37.798% 0.03812 254.612 / 0.055);
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
&[aria-disabled='true'] {
|
|
@@ -143,8 +145,8 @@
|
|
|
143
145
|
.tremolo-number-input-decrement-stepper {
|
|
144
146
|
border-inline-start-width: 1px;
|
|
145
147
|
border-inline-start-style: solid;
|
|
146
|
-
border-inline-start-color:
|
|
147
|
-
border-top: 1px solid
|
|
148
|
+
border-inline-start-color: oklch(84.522% 0 0);
|
|
149
|
+
border-top: 1px solid oklch(84.522% 0 0);
|
|
148
150
|
font-size: calc(0.75rem);
|
|
149
151
|
display: flex;
|
|
150
152
|
justify-content: center;
|
|
@@ -156,7 +158,7 @@
|
|
|
156
158
|
border-bottom-right-radius: var(--border-radius);
|
|
157
159
|
|
|
158
160
|
&[aria-disabled='false']:active {
|
|
159
|
-
background-color:
|
|
161
|
+
background-color: oklch(37.798% 0.03812 254.612 / 0.055);
|
|
160
162
|
}
|
|
161
163
|
|
|
162
164
|
&[aria-disabled='true'] {
|
|
@@ -167,3 +169,17 @@
|
|
|
167
169
|
cursor: not-allowed;
|
|
168
170
|
}
|
|
169
171
|
}
|
|
172
|
+
|
|
173
|
+
:where(.dark, [data-theme='dark']) {
|
|
174
|
+
.tremolo-number-input-wrapper {
|
|
175
|
+
--border-color: oklch(52% 0 0);
|
|
176
|
+
|
|
177
|
+
&[data-variant='filled'] {
|
|
178
|
+
--bg: oklch(43.5% 0 0);
|
|
179
|
+
|
|
180
|
+
&:hover {
|
|
181
|
+
--bg: oklch(46.6% 0 0);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -4,7 +4,10 @@ import { ComponentPropsWithoutRef, forwardRef, ReactNode } from 'react'
|
|
|
4
4
|
import { InputEventOption } from '@tremolo-ui/functions'
|
|
5
5
|
|
|
6
6
|
import { NumberInputProvider } from './context'
|
|
7
|
+
import { DecrementStepper } from './DecrementStepper'
|
|
8
|
+
import { IncrementStepper } from './IncrementStepper'
|
|
7
9
|
import { InternalInput } from './InternalInput'
|
|
10
|
+
import { Stepper } from './Stepper'
|
|
8
11
|
import { Units } from './type'
|
|
9
12
|
|
|
10
13
|
/*
|
|
@@ -13,16 +16,15 @@ TODO
|
|
|
13
16
|
- skew
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
|
-
/** @category NumberInput */
|
|
17
19
|
export interface NumberInputProps {
|
|
18
20
|
value: number | string
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
|
-
*
|
|
23
|
+
* Number.MIN_SAFE_INTEGER
|
|
22
24
|
*/
|
|
23
25
|
min?: number
|
|
24
26
|
/**
|
|
25
|
-
*
|
|
27
|
+
* Number.MAX_SAFE_INTEGER
|
|
26
28
|
*/
|
|
27
29
|
max?: number
|
|
28
30
|
step?: number
|
|
@@ -57,10 +59,12 @@ export interface NumberInputProps {
|
|
|
57
59
|
|
|
58
60
|
/**
|
|
59
61
|
* wheel control option
|
|
62
|
+
* If null, no event will be triggered
|
|
60
63
|
*/
|
|
61
64
|
wheel?: InputEventOption | null
|
|
62
65
|
/**
|
|
63
66
|
* keyboard control option
|
|
67
|
+
* If null, no event will be triggered
|
|
64
68
|
*/
|
|
65
69
|
keyboard?: InputEventOption | null
|
|
66
70
|
|
|
@@ -83,9 +87,6 @@ export interface NumberInputProps {
|
|
|
83
87
|
children?: ReactNode
|
|
84
88
|
}
|
|
85
89
|
|
|
86
|
-
/**
|
|
87
|
-
* @category NumberInput
|
|
88
|
-
*/
|
|
89
90
|
export interface NumberInputMethods {
|
|
90
91
|
focus: () => void
|
|
91
92
|
blur: () => void
|
|
@@ -94,11 +95,7 @@ export interface NumberInputMethods {
|
|
|
94
95
|
type Props = NumberInputProps &
|
|
95
96
|
Omit<ComponentPropsWithoutRef<'input'>, keyof NumberInputProps | 'type'>
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
* Input with some useful functions for entering numerical values.
|
|
99
|
-
* @category NumberInput
|
|
100
|
-
*/
|
|
101
|
-
export const NumberInput = forwardRef<NumberInputMethods, Props>(
|
|
98
|
+
const Root = forwardRef<NumberInputMethods, Props>(
|
|
102
99
|
(
|
|
103
100
|
{
|
|
104
101
|
value,
|
|
@@ -174,7 +171,17 @@ export const NumberInput = forwardRef<NumberInputMethods, Props>(
|
|
|
174
171
|
},
|
|
175
172
|
)
|
|
176
173
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Input with some useful functions for entering numerical values.
|
|
176
|
+
*/
|
|
177
|
+
export const NumberInput = {
|
|
178
|
+
Root,
|
|
179
|
+
Stepper,
|
|
180
|
+
IncrementStepper,
|
|
181
|
+
DecrementStepper,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export { type StepperProps } from './Stepper'
|
|
185
|
+
export { type IncrementStepperProps } from './IncrementStepper'
|
|
186
|
+
export { type DecrementStepperProps } from './DecrementStepper'
|
|
180
187
|
export { type Units } from './type'
|
|
@@ -5,7 +5,6 @@ import { usePianoContext } from './context'
|
|
|
5
5
|
|
|
6
6
|
import { getNoteRangeArray } from '.'
|
|
7
7
|
|
|
8
|
-
/** @category Piano */
|
|
9
8
|
export interface KeyLabelProps {
|
|
10
9
|
/**
|
|
11
10
|
* override Piano.label
|
|
@@ -21,7 +20,6 @@ export interface KeyLabelProps {
|
|
|
21
20
|
__label?: (note: number, index: number) => ReactNode
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
/** @category Piano */
|
|
25
23
|
export function KeyLabel({
|
|
26
24
|
label,
|
|
27
25
|
className,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createContext, ReactNode, useContext, useEffect, useRef } from 'react'
|
|
2
2
|
import { createStore, useStore } from 'zustand'
|
|
3
3
|
|
|
4
|
-
/** @category Piano */
|
|
5
4
|
export type NoteRange = {
|
|
6
5
|
first: number
|
|
7
6
|
last: number
|
|
@@ -67,7 +66,6 @@ export function PianoProvider({ children, ...props }: PianoProviderProps) {
|
|
|
67
66
|
)
|
|
68
67
|
}
|
|
69
68
|
|
|
70
|
-
/** @category Piano */
|
|
71
69
|
export function usePianoContext<T>(selector: (state: State & Action) => T): T {
|
|
72
70
|
const store = useContext(PianoContext)
|
|
73
71
|
if (!store) throw new Error('Missing PianoContext.Provider in the tree')
|