@tremolo-ui/react 0.6.0 → 0.8.0
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 +522 -997
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +601 -329
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +601 -329
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +487 -953
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/components/DropZone/index.tsx +101 -0
- package/src/components/FileInput/Trigger.tsx +39 -0
- package/src/components/FileInput/context.tsx +24 -0
- package/src/components/FileInput/index.tsx +139 -0
- package/src/components/Knob/ActiveLine.tsx +5 -3
- package/src/components/Knob/InactiveLine.tsx +6 -4
- package/src/components/Knob/SVGRoot.tsx +3 -3
- package/src/components/Knob/Thumb.tsx +3 -2
- package/src/components/Knob/context.tsx +3 -74
- package/src/components/Knob/index.tsx +27 -21
- package/src/components/NumberInput/DecrementStepper.tsx +2 -13
- package/src/components/NumberInput/IncrementStepper.tsx +2 -14
- package/src/components/NumberInput/InputField.tsx +17 -38
- package/src/components/NumberInput/Stepper.tsx +44 -107
- package/src/components/NumberInput/StepperButton.tsx +16 -12
- package/src/components/NumberInput/context.tsx +8 -8
- package/src/components/NumberInput/index.tsx +41 -81
- package/src/components/Piano/index.tsx +39 -149
- package/src/components/PointsEditor/Background.tsx +2 -8
- package/src/components/PointsEditor/Container.tsx +17 -34
- package/src/components/PointsEditor/Point.tsx +41 -86
- package/src/components/PointsEditor/SelectionBox.tsx +2 -8
- package/src/components/PointsEditor/context.tsx +10 -61
- package/src/components/PointsEditor/index.tsx +31 -315
- package/src/components/Slider/Marks.tsx +8 -13
- package/src/components/Slider/MarksOption.tsx +11 -17
- package/src/components/Slider/Thumb.tsx +9 -15
- package/src/components/Slider/Track.tsx +7 -13
- package/src/components/Slider/index.tsx +33 -39
- package/src/components/XYPad/Area.tsx +26 -53
- package/src/components/XYPad/Thumb.tsx +8 -12
- package/src/components/XYPad/context.tsx +1 -3
- package/src/components/XYPad/index.tsx +35 -50
- package/src/components/_util/VisuallyHiddenRangeInput.tsx +3 -15
- package/src/css-variables.ts +23 -0
- package/src/hooks/_internal/useCheckSteps.ts +9 -147
- package/src/hooks/useDrag.ts +13 -4
- package/src/hooks/useDragValue.ts +17 -2
- package/src/hooks/useDropZone.ts +78 -0
- package/src/hooks/useLongPress.ts +35 -51
- package/src/hooks/useMIDIAccess.ts +0 -8
- package/src/hooks/useMIDIInput.ts +0 -3
- package/src/hooks/useWheel.ts +4 -1
- package/src/index.ts +14 -36
- package/src/components/Piano/keyboard-shortcuts.ts +0 -61
- package/src/components/Slider/type.ts +0 -44
- package/src/components/_util/css-length.ts +0 -13
- package/src/input-event.ts +0 -32
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AriaAttributes,
|
|
3
2
|
ComponentPropsWithoutRef,
|
|
4
|
-
CSSProperties,
|
|
5
3
|
forwardRef,
|
|
6
|
-
ReactNode,
|
|
7
4
|
useImperativeHandle,
|
|
8
5
|
useRef,
|
|
6
|
+
CSSProperties,
|
|
9
7
|
} from 'react'
|
|
10
8
|
|
|
11
9
|
import { useCheckPlacement } from '../_util/Placement'
|
|
@@ -13,6 +11,8 @@ import { VisuallyHiddenRangeInput } from '../_util/VisuallyHiddenRangeInput'
|
|
|
13
11
|
|
|
14
12
|
import { useSliderContext } from './context'
|
|
15
13
|
|
|
14
|
+
import type { CSSVariables } from '../../css-variables'
|
|
15
|
+
|
|
16
16
|
export interface SliderThumbProps {
|
|
17
17
|
/**
|
|
18
18
|
* Sets `--color`, for the theme to colour the thumb with. The size is not
|
|
@@ -21,18 +21,7 @@ export interface SliderThumbProps {
|
|
|
21
21
|
*/
|
|
22
22
|
color?: string
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
style?: CSSProperties
|
|
26
|
-
/**
|
|
27
|
-
* Rendered inside the thumb. The thumb is one element either way, so what
|
|
28
|
-
* is passed here is decoration on top of it rather than a replacement for
|
|
29
|
-
* it — `className` and `style` are how its own appearance is changed.
|
|
30
|
-
*/
|
|
31
|
-
children?: ReactNode
|
|
32
|
-
'aria-label'?: AriaAttributes['aria-label']
|
|
33
|
-
'aria-labelledby'?: AriaAttributes['aria-labelledby']
|
|
34
|
-
'aria-describedby'?: AriaAttributes['aria-describedby']
|
|
35
|
-
'aria-valuetext'?: AriaAttributes['aria-valuetext']
|
|
24
|
+
style?: CSSProperties & CSSVariables<'color' | 'translate'>
|
|
36
25
|
}
|
|
37
26
|
|
|
38
27
|
export interface SliderThumbMethods {
|
|
@@ -50,6 +39,11 @@ export const Thumb = /* @__PURE__ */ forwardRef<SliderThumbMethods, Props>(
|
|
|
50
39
|
children,
|
|
51
40
|
className,
|
|
52
41
|
style,
|
|
42
|
+
// Pulled out of `props` so that they land on the range input rather than
|
|
43
|
+
// on the thumb: the input is the control, it takes the focus, and a name
|
|
44
|
+
// on a plain div would be ignored. No declaration in `SliderThumbProps`
|
|
45
|
+
// is needed for this — they are already on the div attributes it is
|
|
46
|
+
// merged with, with the same types.
|
|
53
47
|
'aria-label': ariaLabel,
|
|
54
48
|
'aria-labelledby': ariaLabelledby,
|
|
55
49
|
'aria-describedby': ariaDescribedby,
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ComponentPropsWithoutRef,
|
|
3
|
-
CSSProperties,
|
|
4
|
-
forwardRef,
|
|
5
|
-
ReactNode,
|
|
6
|
-
} from 'react'
|
|
1
|
+
import { ComponentPropsWithoutRef, CSSProperties, forwardRef } from 'react'
|
|
7
2
|
|
|
8
|
-
import {
|
|
3
|
+
import { cssLength } from '@tremolo-ui/dom'
|
|
9
4
|
|
|
10
5
|
import { useComposedRefs } from '../../compose-refs'
|
|
11
|
-
import { cssLength } from '../_util/css-length'
|
|
12
6
|
import { Placement } from '../_util/Placement'
|
|
13
7
|
|
|
14
8
|
import { useSliderContext } from './context'
|
|
15
9
|
|
|
10
|
+
import type { CSSVariables } from '../../css-variables'
|
|
11
|
+
|
|
16
12
|
export interface SliderTrackProps {
|
|
17
13
|
/**
|
|
18
14
|
* How long the track is along the axis the slider runs. Sets `--length`;
|
|
@@ -27,10 +23,8 @@ export interface SliderTrackProps {
|
|
|
27
23
|
/** Colour of the rest of the track. Sets `--inactive`. */
|
|
28
24
|
inactive?: string
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/** `<Slider.Thumb />` goes here. */
|
|
33
|
-
children?: ReactNode
|
|
26
|
+
style?: CSSProperties &
|
|
27
|
+
CSSVariables<'length' | 'thickness' | 'active' | 'inactive' | 'percent'>
|
|
34
28
|
}
|
|
35
29
|
|
|
36
30
|
type Props = SliderTrackProps &
|
|
@@ -65,7 +59,7 @@ export const Track = /* @__PURE__ */ forwardRef<HTMLDivElement, Props>(
|
|
|
65
59
|
data-orientation={vertical ? 'vertical' : 'horizontal'}
|
|
66
60
|
// Which end the value grows from. `percent` is already the position on
|
|
67
61
|
// screen, so this only says which side of it is the filled one.
|
|
68
|
-
data-flipped={
|
|
62
|
+
data-flipped={vertical !== reverse ? '' : undefined}
|
|
69
63
|
style={
|
|
70
64
|
{
|
|
71
65
|
'--active': active,
|
|
@@ -9,27 +9,26 @@ import React, {
|
|
|
9
9
|
useRef,
|
|
10
10
|
} from 'react'
|
|
11
11
|
|
|
12
|
-
import type { AxisOptions, XY } from '@tremolo-ui/dom'
|
|
13
12
|
import {
|
|
14
13
|
applyDelta,
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
arrowKeyDirection,
|
|
15
|
+
type AxisOptions,
|
|
16
|
+
DEFAULT_DRAG_SENSITIVITY,
|
|
17
|
+
DEFAULT_KEYBOARD_OPTIONS,
|
|
18
|
+
DEFAULT_WHEEL_OPTIONS,
|
|
17
19
|
type InputEventOption,
|
|
18
20
|
type ModifierValue,
|
|
19
21
|
selectModifier,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
valuePercent,
|
|
23
|
+
wheelDirection,
|
|
24
|
+
type XY,
|
|
25
|
+
} from '@tremolo-ui/dom'
|
|
26
|
+
import { linearScale, type Scale } from '@tremolo-ui/functions'
|
|
23
27
|
|
|
24
28
|
import { useComposedRefs } from '../../compose-refs'
|
|
25
29
|
import { useCheckSteps } from '../../hooks/_internal/useCheckSteps'
|
|
26
30
|
import { useDragValue } from '../../hooks/useDragValue'
|
|
27
31
|
import { useWheel } from '../../hooks/useWheel'
|
|
28
|
-
import {
|
|
29
|
-
DEFAULT_DRAG_SENSITIVITY,
|
|
30
|
-
DEFAULT_KEYBOARD_OPTIONS,
|
|
31
|
-
DEFAULT_WHEEL_OPTIONS,
|
|
32
|
-
} from '../../input-event'
|
|
33
32
|
|
|
34
33
|
import { SliderProvider } from './context'
|
|
35
34
|
import { Marks } from './Marks'
|
|
@@ -147,8 +146,6 @@ export interface SliderProps {
|
|
|
147
146
|
* The parts carry `data-readonly` while it is set.
|
|
148
147
|
*/
|
|
149
148
|
readonly?: boolean
|
|
150
|
-
className?: string
|
|
151
|
-
style?: CSSProperties
|
|
152
149
|
/** Called with the new value when a drag, the wheel or an arrow key moves it. */
|
|
153
150
|
onChange?: (value: number) => void
|
|
154
151
|
/** Called when a drag starts, with the value where the track was pressed. */
|
|
@@ -220,18 +217,13 @@ export const Root = /* @__PURE__ */ forwardRef<SliderMethods, Props>(
|
|
|
220
217
|
const externalStyles = { ...defaultExternalStyles, ..._externalStyles }
|
|
221
218
|
const inactive = disabled || readonly
|
|
222
219
|
|
|
223
|
-
|
|
224
|
-
const rev = toFixed(100 - p)
|
|
225
|
-
// NOTE
|
|
220
|
+
// Measured from the left or the top, as CSS places things:
|
|
226
221
|
// normal -> normal (right)
|
|
227
|
-
// vertical ->
|
|
228
|
-
// reverse ->
|
|
222
|
+
// vertical -> reversed (up)
|
|
223
|
+
// reverse -> reversed (left)
|
|
229
224
|
// vertical & reverse -> normal (down)
|
|
230
|
-
const displayReversed =
|
|
231
|
-
|
|
232
|
-
[vertical, reverse],
|
|
233
|
-
)
|
|
234
|
-
const percent = displayReversed ? rev : p
|
|
225
|
+
const displayReversed = vertical !== reverse
|
|
226
|
+
const percent = valuePercent(value, { min, max, scale }, displayReversed)
|
|
235
227
|
|
|
236
228
|
// --- internal functions ---
|
|
237
229
|
// The pointer is normalized against the track on both axes; only the one
|
|
@@ -246,14 +238,19 @@ export const Root = /* @__PURE__ */ forwardRef<SliderMethods, Props>(
|
|
|
246
238
|
|
|
247
239
|
const handleKeyDown = useCallback(
|
|
248
240
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
249
|
-
const
|
|
250
|
-
if (
|
|
251
|
-
return
|
|
241
|
+
const direction = arrowKeyDirection(event.key)
|
|
242
|
+
if (direction === null) return
|
|
252
243
|
event.preventDefault()
|
|
253
244
|
if (!keyboard || !onChange || inactive) return
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
245
|
+
onChange(
|
|
246
|
+
applyDelta(
|
|
247
|
+
value,
|
|
248
|
+
reverse ? -direction : direction,
|
|
249
|
+
keyboard,
|
|
250
|
+
axis,
|
|
251
|
+
event,
|
|
252
|
+
),
|
|
253
|
+
)
|
|
257
254
|
},
|
|
258
255
|
[keyboard, onChange, inactive, reverse, value, axis],
|
|
259
256
|
)
|
|
@@ -286,16 +283,14 @@ export const Root = /* @__PURE__ */ forwardRef<SliderMethods, Props>(
|
|
|
286
283
|
|
|
287
284
|
const wheelRefCallback = useWheel<HTMLDivElement>((event) => {
|
|
288
285
|
if (!wheel || !onChange || inactive) return
|
|
286
|
+
// A notch the slider does not read — a sideways scroll on a vertical
|
|
287
|
+
// slider — is left to the page rather than swallowed.
|
|
288
|
+
const direction = wheelDirection(event, { horizontal: !vertical })
|
|
289
|
+
if (direction === null) return
|
|
289
290
|
event.preventDefault()
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
} else {
|
|
294
|
-
if (event.deltaY === 0) return
|
|
295
|
-
direction = event.deltaY > 0 ? -1 : 1
|
|
296
|
-
}
|
|
297
|
-
if (reverse) direction *= -1
|
|
298
|
-
onChange(applyDelta(value, direction, wheel, axis, event))
|
|
291
|
+
onChange(
|
|
292
|
+
applyDelta(value, reverse ? -direction : direction, wheel, axis, event),
|
|
293
|
+
)
|
|
299
294
|
}, WHEEL_OPTIONS)
|
|
300
295
|
|
|
301
296
|
// Composed once, so React attaches the refs a single time instead of
|
|
@@ -401,4 +396,3 @@ export { type SliderThumbMethods, type SliderThumbProps } from './Thumb'
|
|
|
401
396
|
export { type SliderTrackProps } from './Track'
|
|
402
397
|
export { type SliderMarksProps } from './Marks'
|
|
403
398
|
export { type SliderMarksOptionProps } from './MarksOption'
|
|
404
|
-
export { type MarksOptions } from './type'
|
|
@@ -1,61 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ComponentPropsWithoutRef,
|
|
3
|
-
CSSProperties,
|
|
4
|
-
forwardRef,
|
|
5
|
-
ReactNode,
|
|
6
|
-
} from 'react'
|
|
1
|
+
import { ComponentPropsWithoutRef, CSSProperties, forwardRef } from 'react'
|
|
7
2
|
|
|
8
3
|
import { useComposedRefs } from '../../compose-refs'
|
|
9
|
-
import { cssLength } from '../_util/css-length'
|
|
10
4
|
import { Placement } from '../_util/Placement'
|
|
11
5
|
|
|
12
6
|
import { useXYPadContext } from './context'
|
|
13
7
|
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/** Sets `--color`, for the theme to colour the area with. */
|
|
20
|
-
color?: string
|
|
21
|
-
className?: string
|
|
22
|
-
style?: CSSProperties
|
|
23
|
-
/** `<XYPad.Thumb />` goes here. */
|
|
24
|
-
children?: ReactNode
|
|
25
|
-
}
|
|
8
|
+
export const Area = /* @__PURE__ */ forwardRef<
|
|
9
|
+
HTMLDivElement,
|
|
10
|
+
ComponentPropsWithoutRef<'div'>
|
|
11
|
+
>(function Area({ children, className, style, ...props }, forwardedRef) {
|
|
12
|
+
const { areaRef } = useXYPadContext()
|
|
26
13
|
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
// The area is what the pointer position is normalized against, so the
|
|
15
|
+
// context ref is composed with any ref the caller passed.
|
|
16
|
+
const composedRef = useComposedRefs<HTMLDivElement>(forwardedRef, areaRef)
|
|
29
17
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
'--color': color,
|
|
48
|
-
'--width': cssLength(width),
|
|
49
|
-
'--height': cssLength(height),
|
|
50
|
-
// The thumb inside is placed against this box.
|
|
51
|
-
position: 'relative',
|
|
52
|
-
...style,
|
|
53
|
-
} as CSSProperties
|
|
54
|
-
}
|
|
55
|
-
{...props}
|
|
56
|
-
>
|
|
57
|
-
<Placement name="XYPad.Area">{children}</Placement>
|
|
58
|
-
</div>
|
|
59
|
-
)
|
|
60
|
-
},
|
|
61
|
-
)
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
ref={composedRef}
|
|
21
|
+
className={className}
|
|
22
|
+
style={
|
|
23
|
+
{
|
|
24
|
+
// The thumb inside is placed against this box.
|
|
25
|
+
position: 'relative',
|
|
26
|
+
...style,
|
|
27
|
+
} as CSSProperties
|
|
28
|
+
}
|
|
29
|
+
{...props}
|
|
30
|
+
>
|
|
31
|
+
<Placement name="XYPad.Area">{children}</Placement>
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
})
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AriaAttributes,
|
|
3
3
|
ComponentPropsWithoutRef,
|
|
4
|
-
CSSProperties,
|
|
5
4
|
forwardRef,
|
|
6
|
-
ReactNode,
|
|
7
5
|
useImperativeHandle,
|
|
8
6
|
useRef,
|
|
7
|
+
CSSProperties,
|
|
9
8
|
} from 'react'
|
|
10
9
|
|
|
10
|
+
import { toXY, type XYInput } from '@tremolo-ui/dom'
|
|
11
|
+
|
|
11
12
|
import { useCheckPlacement } from '../_util/Placement'
|
|
12
13
|
import { VisuallyHiddenRangeInput } from '../_util/VisuallyHiddenRangeInput'
|
|
13
14
|
|
|
14
|
-
import {
|
|
15
|
+
import { useXYPadContext } from './context'
|
|
16
|
+
|
|
17
|
+
import type { CSSVariables } from '../../css-variables'
|
|
15
18
|
|
|
16
19
|
export interface XYPadThumbProps {
|
|
17
20
|
/**
|
|
@@ -21,15 +24,6 @@ export interface XYPadThumbProps {
|
|
|
21
24
|
*/
|
|
22
25
|
color?: string
|
|
23
26
|
|
|
24
|
-
className?: string
|
|
25
|
-
style?: CSSProperties
|
|
26
|
-
/**
|
|
27
|
-
* Rendered inside the thumb. The thumb is one element either way, so what
|
|
28
|
-
* is passed here is decoration on top of it rather than a replacement for
|
|
29
|
-
* it — `className` and `style` are how its own appearance is changed.
|
|
30
|
-
*/
|
|
31
|
-
children?: ReactNode
|
|
32
|
-
|
|
33
27
|
/**
|
|
34
28
|
* The accessible name of each axis, as `[x, y]`. There are two range inputs
|
|
35
29
|
* inside the thumb, so each takes a name of its own; a single string names
|
|
@@ -45,6 +39,8 @@ export interface XYPadThumbProps {
|
|
|
45
39
|
* `[x, y]` or one for both.
|
|
46
40
|
*/
|
|
47
41
|
'aria-valuetext'?: XYInput<AriaAttributes['aria-valuetext']>
|
|
42
|
+
|
|
43
|
+
style?: CSSProperties & CSSVariables<'color' | 'translate'>
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
export interface XYPadThumbMethods {
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { createContext, RefObject, useContext } from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { type XY } from '@tremolo-ui/dom'
|
|
4
4
|
import type { Scale } from '@tremolo-ui/functions'
|
|
5
5
|
|
|
6
6
|
import type { XYPadThumbMethods } from './Thumb'
|
|
7
7
|
|
|
8
|
-
export { toXY, type XY, type XYInput }
|
|
9
|
-
|
|
10
8
|
export type XYPadContextValue = {
|
|
11
9
|
value: XY<number>
|
|
12
10
|
min: XY<number>
|
|
@@ -10,30 +10,32 @@ import {
|
|
|
10
10
|
useRef,
|
|
11
11
|
} from 'react'
|
|
12
12
|
|
|
13
|
-
import type { AxisOptions } from '@tremolo-ui/dom'
|
|
14
13
|
import {
|
|
15
14
|
applyDelta,
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
arrowKeyMove,
|
|
16
|
+
type AxisOptions,
|
|
17
|
+
DEFAULT_DRAG_SENSITIVITY,
|
|
18
|
+
DEFAULT_KEYBOARD_OPTIONS,
|
|
19
|
+
DEFAULT_WHEEL_OPTIONS,
|
|
18
20
|
InputEventOption,
|
|
19
21
|
ModifierState,
|
|
20
22
|
type ModifierValue,
|
|
21
23
|
selectModifier,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
toXY,
|
|
25
|
+
valuePercent,
|
|
26
|
+
wheelMove,
|
|
27
|
+
type XY,
|
|
28
|
+
type XYInput,
|
|
29
|
+
} from '@tremolo-ui/dom'
|
|
30
|
+
import { linearScale, type Scale } from '@tremolo-ui/functions'
|
|
24
31
|
|
|
25
32
|
import { useComposedRefs } from '../../compose-refs'
|
|
26
33
|
import { useCheckSteps } from '../../hooks/_internal/useCheckSteps'
|
|
27
34
|
import { useDragValue } from '../../hooks/useDragValue'
|
|
28
35
|
import { useWheel } from '../../hooks/useWheel'
|
|
29
|
-
import {
|
|
30
|
-
DEFAULT_DRAG_SENSITIVITY,
|
|
31
|
-
DEFAULT_KEYBOARD_OPTIONS,
|
|
32
|
-
DEFAULT_WHEEL_OPTIONS,
|
|
33
|
-
} from '../../input-event'
|
|
34
36
|
|
|
35
37
|
import { Area } from './Area'
|
|
36
|
-
import {
|
|
38
|
+
import { XYPadProvider } from './context'
|
|
37
39
|
import { Thumb, XYPadThumbMethods } from './Thumb'
|
|
38
40
|
|
|
39
41
|
const defaultExternalStyles: XYPadProps['externalStyles'] = {
|
|
@@ -229,14 +231,17 @@ export const Root = /* @__PURE__ */ forwardRef<XYPadMethods, Props>(
|
|
|
229
231
|
const scale = useMemo(() => toXY(_scale), [_scale])
|
|
230
232
|
const reverse = useMemo(() => toXY(_reverse), [_reverse])
|
|
231
233
|
|
|
232
|
-
const percent = useMemo(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
234
|
+
const percent = useMemo(
|
|
235
|
+
(): XY<number> =>
|
|
236
|
+
[0, 1].map((i) =>
|
|
237
|
+
valuePercent(
|
|
238
|
+
value[i],
|
|
239
|
+
{ min: min[i], max: max[i], scale: scale[i] },
|
|
240
|
+
reverse[i],
|
|
241
|
+
),
|
|
242
|
+
) as XY<number>,
|
|
243
|
+
[value, min, max, scale, reverse],
|
|
244
|
+
)
|
|
240
245
|
|
|
241
246
|
// --- internal functions ---
|
|
242
247
|
// `AxisOptions` extends `ValueRange`, so the same pair also describes the
|
|
@@ -293,21 +298,15 @@ export const Root = /* @__PURE__ */ forwardRef<XYPadMethods, Props>(
|
|
|
293
298
|
|
|
294
299
|
const handleKeyDown = useCallback(
|
|
295
300
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
296
|
-
const key = event.key
|
|
297
|
-
if (!['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(key))
|
|
298
|
-
return
|
|
299
|
-
|
|
300
301
|
// The key picks the axis, whichever of the two inputs holds the
|
|
301
|
-
// focus: the
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
302
|
+
// focus: the focus lands on the x input, so reading the axis off the
|
|
303
|
+
// input would leave the y axis with no keys at all.
|
|
304
|
+
const move = arrowKeyMove(event.key)
|
|
305
|
+
if (!move) return
|
|
305
306
|
event.preventDefault()
|
|
306
307
|
if (!onChange || inactive || !keyboard) return
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (reverse[i]) direction *= -1
|
|
310
|
-
onChange(nudge(i, direction, keyboard, event))
|
|
308
|
+
const { axis: i, direction } = move
|
|
309
|
+
onChange(nudge(i, reverse[i] ? -direction : direction, keyboard, event))
|
|
311
310
|
},
|
|
312
311
|
[onChange, inactive, keyboard, reverse, nudge],
|
|
313
312
|
)
|
|
@@ -339,19 +338,11 @@ export const Root = /* @__PURE__ */ forwardRef<XYPadMethods, Props>(
|
|
|
339
338
|
|
|
340
339
|
const wheelRefCallback = useWheel<HTMLDivElement>((event) => {
|
|
341
340
|
if (!onChange || inactive || !wheel) return
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
// moved keeps shift working as the x-axis modifier — and picks up a
|
|
345
|
-
// trackpad's own horizontal gesture, which never had a modifier.
|
|
346
|
-
const horizontal = event.deltaX !== 0
|
|
347
|
-
const delta = horizontal ? event.deltaX : event.deltaY
|
|
348
|
-
if (delta === 0) return
|
|
349
|
-
const i: 0 | 1 = horizontal || event.shiftKey ? 0 : 1
|
|
341
|
+
const move = wheelMove(event)
|
|
342
|
+
if (!move) return
|
|
350
343
|
event.preventDefault()
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (reverse[i]) direction *= -1
|
|
354
|
-
onChange(nudge(i, direction, wheel, event))
|
|
344
|
+
const { axis: i, direction } = move
|
|
345
|
+
onChange(nudge(i, reverse[i] ? -direction : direction, wheel, event))
|
|
355
346
|
}, WHEEL_OPTIONS)
|
|
356
347
|
|
|
357
348
|
// Composed once, so React attaches the refs a single time instead of
|
|
@@ -446,10 +437,4 @@ export const XYPad = {
|
|
|
446
437
|
}
|
|
447
438
|
|
|
448
439
|
export { type XYPadThumbProps, type XYPadThumbMethods } from './Thumb'
|
|
449
|
-
export { type
|
|
450
|
-
export {
|
|
451
|
-
useXYPadContext,
|
|
452
|
-
type XYPadContextValue,
|
|
453
|
-
type XY,
|
|
454
|
-
type XYInput,
|
|
455
|
-
} from './context'
|
|
440
|
+
export { useXYPadContext, type XYPadContextValue } from './context'
|
|
@@ -1,18 +1,6 @@
|
|
|
1
|
-
import { ComponentPropsWithoutRef,
|
|
1
|
+
import { ComponentPropsWithoutRef, forwardRef } from 'react'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
position: 'absolute',
|
|
5
|
-
width: 1,
|
|
6
|
-
height: 1,
|
|
7
|
-
padding: 0,
|
|
8
|
-
margin: -1,
|
|
9
|
-
overflow: 'hidden',
|
|
10
|
-
clip: 'rect(0, 0, 0, 0)',
|
|
11
|
-
clipPath: 'inset(50%)',
|
|
12
|
-
whiteSpace: 'nowrap',
|
|
13
|
-
border: 0,
|
|
14
|
-
pointerEvents: 'none',
|
|
15
|
-
}
|
|
3
|
+
import { visuallyHiddenStyle } from '@tremolo-ui/dom'
|
|
16
4
|
|
|
17
5
|
type Props = Omit<ComponentPropsWithoutRef<'input'>, 'type' | 'style'>
|
|
18
6
|
|
|
@@ -20,5 +8,5 @@ export const VisuallyHiddenRangeInput = /* @__PURE__ */ forwardRef<
|
|
|
20
8
|
HTMLInputElement,
|
|
21
9
|
Props
|
|
22
10
|
>((props, ref) => (
|
|
23
|
-
<input {...props} ref={ref} type="range" style={
|
|
11
|
+
<input {...props} ref={ref} type="range" style={visuallyHiddenStyle} />
|
|
24
12
|
))
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS custom properties, for a `style` object.
|
|
3
|
+
*
|
|
4
|
+
* React's `CSSProperties` has no key starting with `--`, so
|
|
5
|
+
* `style={{ '--color': 'red' }}` is refused as an unknown property even though
|
|
6
|
+
* the browser takes it. Every part accepts this alongside `CSSProperties`,
|
|
7
|
+
* which is how a variable the component or a theme reads is set inline.
|
|
8
|
+
*
|
|
9
|
+
* `T` names the custom properties the part itself writes or reads, without the
|
|
10
|
+
* leading `--`, so that an editor can suggest them. Any other `--` name is
|
|
11
|
+
* still accepted — a theme reads variables of its own, such as the colours of
|
|
12
|
+
* each kind of key.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* CSSVariables<'thickness' | 'length'>
|
|
16
|
+
* // { '--thickness'?: string | number, '--length'?: string | number,
|
|
17
|
+
* // [name: `--${string}`]: string | number | undefined }
|
|
18
|
+
*/
|
|
19
|
+
export type CSSVariables<T extends string = never> = {
|
|
20
|
+
[K in T as `--${K}`]?: string | number
|
|
21
|
+
} & {
|
|
22
|
+
[K in `--${string}`]?: string | number
|
|
23
|
+
}
|