@tremolo-ui/react 0.5.0 → 0.7.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/compose-refs.cjs +72 -0
- package/dist/compose-refs.cjs.map +1 -0
- package/dist/compose-refs.d.cts +43 -0
- package/dist/compose-refs.d.cts.map +1 -0
- package/dist/compose-refs.d.ts +43 -0
- package/dist/compose-refs.d.ts.map +1 -0
- package/dist/compose-refs.js +70 -0
- package/dist/compose-refs.js.map +1 -0
- package/dist/index.cjs +1669 -541
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1571 -486
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1571 -486
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1640 -538
- package/dist/index.js.map +1 -1
- package/package.json +29 -27
- package/src/components/AnimationCanvas/index.tsx +115 -76
- package/src/components/DropZone/index.tsx +100 -0
- package/src/components/FileInput/Trigger.tsx +39 -0
- package/src/components/FileInput/context.tsx +24 -0
- package/src/components/FileInput/index.tsx +144 -0
- package/src/components/Knob/ActiveLine.tsx +7 -6
- package/src/components/Knob/InactiveLine.tsx +9 -10
- package/src/components/Knob/SVGRoot.tsx +5 -3
- package/src/components/Knob/Thumb.tsx +33 -11
- package/src/components/Knob/context.tsx +17 -1
- package/src/components/Knob/index.tsx +156 -60
- package/src/components/NumberInput/DecrementStepper.tsx +3 -10
- package/src/components/NumberInput/IncrementStepper.tsx +3 -10
- package/src/components/NumberInput/InputField.tsx +185 -108
- package/src/components/NumberInput/Stepper.tsx +120 -84
- package/src/components/NumberInput/StepperButton.tsx +100 -0
- package/src/components/NumberInput/context.tsx +34 -8
- package/src/components/NumberInput/index.tsx +270 -82
- package/src/components/Piano/index.tsx +363 -200
- package/src/components/PointsEditor/Background.tsx +13 -10
- package/src/components/PointsEditor/Container.tsx +83 -29
- package/src/components/PointsEditor/Point.tsx +241 -79
- package/src/components/PointsEditor/SelectionBox.tsx +48 -0
- package/src/components/PointsEditor/context.tsx +78 -10
- package/src/components/PointsEditor/index.tsx +440 -41
- package/src/components/Slider/Marks.tsx +48 -19
- package/src/components/Slider/MarksOption.tsx +64 -45
- package/src/components/Slider/Thumb.tsx +107 -55
- package/src/components/Slider/Track.tsx +66 -62
- package/src/components/Slider/context.tsx +4 -2
- package/src/components/Slider/index.tsx +136 -77
- package/src/components/Slider/type.ts +29 -9
- package/src/components/XYPad/Area.tsx +46 -41
- package/src/components/XYPad/Thumb.tsx +131 -64
- package/src/components/XYPad/context.tsx +4 -1
- package/src/components/XYPad/index.tsx +183 -53
- package/src/components/_util/Placement.tsx +68 -0
- package/src/components/_util/VisuallyHiddenRangeInput.tsx +12 -0
- package/src/components/_util/css-length.ts +13 -0
- package/src/components/_util/decimal-digits.ts +23 -0
- package/src/components/_util/visually-hidden.ts +25 -0
- package/src/{components/_util/composeRefs.tsx → compose-refs/index.tsx} +33 -5
- package/src/css-variables.ts +23 -0
- package/src/hooks/_internal/useCallbackRef.ts +3 -1
- package/src/hooks/_internal/useCheckSteps.ts +169 -0
- package/src/hooks/useAnimationFrame.ts +17 -7
- package/src/hooks/useDrag.ts +51 -12
- package/src/hooks/useDragValue.ts +71 -11
- package/src/hooks/useDropZone.ts +78 -0
- package/src/hooks/useEventListener.ts +10 -6
- package/src/hooks/useLongPress.ts +42 -8
- package/src/hooks/useMIDIAccess.ts +82 -11
- package/src/hooks/useMIDIInput.ts +45 -13
- package/src/hooks/useMIDIMessage.ts +23 -5
- package/src/hooks/useWheel.ts +4 -1
- package/src/index.ts +45 -25
- package/src/input-event.ts +32 -0
- package/dist/index.css +0 -501
- package/src/Globals.d.ts +0 -3
- package/src/components/Knob/index.css +0 -81
- package/src/components/NumberInput/index.css +0 -121
- package/src/components/NumberInput/stepperButton.tsx +0 -90
- package/src/components/Piano/index.css +0 -90
- package/src/components/PointsEditor/index.css +0 -35
- package/src/components/Slider/index.css +0 -125
- package/src/components/XYPad/index.css +0 -60
- package/src/components/_util/index.ts +0 -28
- package/src/styles/global.css +0 -19
- /package/src/components/Piano/{keyboardShortcuts.ts → keyboard-shortcuts.ts} +0 -0
|
@@ -1,26 +1,42 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ComponentPropsWithoutRef, ReactNode } from 'react'
|
|
1
|
+
import { ComponentPropsWithoutRef, CSSProperties } from 'react'
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import { cssLength } from '../_util/css-length'
|
|
4
|
+
import { Placement } from '../_util/Placement'
|
|
5
5
|
|
|
6
6
|
import { useSliderContext } from './context'
|
|
7
7
|
import { MarksOption } from './MarksOption'
|
|
8
8
|
import { generateOptionsList, MarksOptions } from './type'
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
import type { CSSVariables } from '../../css-variables'
|
|
11
|
+
|
|
12
|
+
export interface SliderMarksProps {
|
|
13
|
+
/**
|
|
14
|
+
* Space between the marks and the track. Sets `--gap`; the theme's own
|
|
15
|
+
* spacing stands when this is omitted.
|
|
16
|
+
*/
|
|
11
17
|
gap?: number | string
|
|
18
|
+
/**
|
|
19
|
+
* Build the marks instead of writing `Slider.MarksOption` out: a number puts
|
|
20
|
+
* one every that many, `'step'` one every `step`, and `{ per, mark, label }`
|
|
21
|
+
* also turns the mark or the label off for all of them. `children` is
|
|
22
|
+
* ignored while it is set.
|
|
23
|
+
*
|
|
24
|
+
* `'step'` makes one per step, which is a great many for a fine `step`.
|
|
25
|
+
*/
|
|
12
26
|
options?: MarksOptions
|
|
13
|
-
|
|
27
|
+
|
|
28
|
+
style?: CSSProperties & CSSVariables<'gap'>
|
|
14
29
|
}
|
|
15
30
|
|
|
16
31
|
export function Marks({
|
|
17
|
-
gap
|
|
32
|
+
gap,
|
|
18
33
|
options,
|
|
19
34
|
children,
|
|
20
35
|
className,
|
|
21
36
|
style,
|
|
22
37
|
...props
|
|
23
|
-
}:
|
|
38
|
+
}: SliderMarksProps &
|
|
39
|
+
Omit<ComponentPropsWithoutRef<'div'>, keyof SliderMarksProps>) {
|
|
24
40
|
const min = useSliderContext((s) => s.min)
|
|
25
41
|
const max = useSliderContext((s) => s.max)
|
|
26
42
|
const step = useSliderContext((s) => s.step)
|
|
@@ -30,23 +46,36 @@ export function Marks({
|
|
|
30
46
|
const optionsList = options
|
|
31
47
|
? generateOptionsList(options, min, max, step)
|
|
32
48
|
: []
|
|
33
|
-
if (
|
|
49
|
+
if (vertical !== reverse) optionsList.reverse()
|
|
34
50
|
|
|
35
51
|
return (
|
|
36
52
|
<div
|
|
37
|
-
className={
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
className={className}
|
|
54
|
+
data-orientation={vertical ? 'vertical' : 'horizontal'}
|
|
55
|
+
style={
|
|
56
|
+
{
|
|
57
|
+
'--gap': cssLength(gap),
|
|
58
|
+
// Each option inside is placed against this box.
|
|
59
|
+
position: 'relative',
|
|
60
|
+
...style,
|
|
61
|
+
} as CSSProperties
|
|
62
|
+
}
|
|
43
63
|
{...props}
|
|
44
64
|
>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
<Placement name="Slider.Marks">
|
|
66
|
+
{options
|
|
67
|
+
? optionsList.map(({ value, mark, label }, index) => {
|
|
68
|
+
return (
|
|
69
|
+
<MarksOption
|
|
70
|
+
key={index}
|
|
71
|
+
value={value}
|
|
72
|
+
mark={mark}
|
|
73
|
+
label={label ? undefined : null}
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
: children}
|
|
78
|
+
</Placement>
|
|
50
79
|
</div>
|
|
51
80
|
)
|
|
52
81
|
}
|
|
@@ -1,51 +1,68 @@
|
|
|
1
|
-
import clsx from 'clsx'
|
|
2
1
|
import { ComponentPropsWithoutRef, CSSProperties, useCallback } from 'react'
|
|
3
2
|
|
|
4
|
-
import { toFixed
|
|
3
|
+
import { toFixed } from '@tremolo-ui/functions'
|
|
4
|
+
|
|
5
|
+
import { cssLength } from '../_util/css-length'
|
|
6
|
+
import { useCheckPlacement } from '../_util/Placement'
|
|
5
7
|
|
|
6
8
|
import { useSliderContext } from './context'
|
|
7
|
-
import { MarksType } from './type'
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
import type { CSSVariables } from '../../css-variables'
|
|
11
|
+
|
|
12
|
+
export interface SliderMarksOptionProps {
|
|
13
|
+
/** Where the mark sits, on the same scale as the thumb. */
|
|
11
14
|
value: number
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Draw the mark itself. Turn it off to show the label alone.
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
mark?: boolean
|
|
21
|
+
/**
|
|
22
|
+
* Text shown next to the mark. Leave it out to show the value; `null` leaves
|
|
23
|
+
* the label out, and an empty string draws an empty one.
|
|
24
|
+
*/
|
|
25
|
+
label?: number | string | null
|
|
26
|
+
/** Thickness of the mark. Sets `--thickness`. */
|
|
18
27
|
thickness?: number | string
|
|
19
|
-
/** mark length */
|
|
28
|
+
/** Length of the mark. Sets `--length`. */
|
|
20
29
|
length?: number | string
|
|
21
|
-
/**
|
|
30
|
+
/** Space between the mark and the label. Sets `--gap`. */
|
|
22
31
|
gap?: number | string
|
|
32
|
+
/** Width of the label. Sets `--label-width`. */
|
|
23
33
|
labelWidth?: number | string
|
|
34
|
+
/** Classes for the mark and the label inside the option. */
|
|
24
35
|
classes?: {
|
|
25
36
|
mark?: string
|
|
26
37
|
label?: string
|
|
27
38
|
}
|
|
39
|
+
/** Styles for the mark and the label inside the option. */
|
|
28
40
|
styles?: {
|
|
29
41
|
mark?: CSSProperties
|
|
30
42
|
label?: CSSProperties
|
|
31
43
|
}
|
|
44
|
+
|
|
45
|
+
style?: CSSProperties &
|
|
46
|
+
CSSVariables<'thickness' | 'length' | 'gap' | 'label-width' | 'translate'>
|
|
32
47
|
}
|
|
33
48
|
|
|
34
49
|
export function MarksOption({
|
|
35
50
|
value,
|
|
36
|
-
|
|
51
|
+
mark = true,
|
|
37
52
|
label,
|
|
38
|
-
thickness
|
|
39
|
-
length
|
|
40
|
-
gap
|
|
53
|
+
thickness,
|
|
54
|
+
length,
|
|
55
|
+
gap,
|
|
41
56
|
labelWidth,
|
|
42
57
|
classes,
|
|
43
58
|
styles,
|
|
44
59
|
className,
|
|
45
60
|
style,
|
|
46
61
|
...props
|
|
47
|
-
}:
|
|
48
|
-
Omit<ComponentPropsWithoutRef<'div'>, keyof
|
|
62
|
+
}: SliderMarksOptionProps &
|
|
63
|
+
Omit<ComponentPropsWithoutRef<'div'>, keyof SliderMarksOptionProps>) {
|
|
64
|
+
useCheckPlacement('Slider.MarksOption', 'Slider.Marks')
|
|
65
|
+
|
|
49
66
|
const min = useSliderContext((s) => s.min)
|
|
50
67
|
const max = useSliderContext((s) => s.max)
|
|
51
68
|
const scale = useSliderContext((s) => s.scale)
|
|
@@ -56,44 +73,46 @@ export function MarksOption({
|
|
|
56
73
|
(value: number) => {
|
|
57
74
|
// The marks have to sit on the same curve the thumb runs along.
|
|
58
75
|
const percent = scale.normalize(value, min, max) * 100
|
|
59
|
-
return toFixed(
|
|
76
|
+
return toFixed(vertical !== reverse ? 100 - percent : percent)
|
|
60
77
|
},
|
|
61
78
|
[vertical, reverse, max, min, scale],
|
|
62
79
|
)
|
|
63
80
|
|
|
64
81
|
return (
|
|
65
82
|
<div
|
|
66
|
-
className={
|
|
67
|
-
style={
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
className={className}
|
|
84
|
+
style={
|
|
85
|
+
{
|
|
86
|
+
// The mark and the label read these, so they are set once here.
|
|
87
|
+
'--thickness': cssLength(thickness),
|
|
88
|
+
'--length': cssLength(length),
|
|
89
|
+
'--gap': cssLength(gap),
|
|
90
|
+
'--label-width': cssLength(labelWidth),
|
|
91
|
+
// The mechanics of the position below, as on the thumb.
|
|
92
|
+
position: 'absolute',
|
|
93
|
+
translate: vertical
|
|
94
|
+
? 'var(--translate, 0 -50%)'
|
|
95
|
+
: 'var(--translate, -50% 0)',
|
|
96
|
+
zIndex: 10,
|
|
97
|
+
...style,
|
|
98
|
+
// Where the mark belongs on the track: the value, not a style.
|
|
99
|
+
left: !vertical ? `${calcPercent(value)}%` : undefined,
|
|
100
|
+
top: vertical ? `${calcPercent(value)}%` : undefined,
|
|
101
|
+
} as CSSProperties
|
|
102
|
+
}
|
|
103
|
+
data-orientation={vertical ? 'vertical' : 'horizontal'}
|
|
73
104
|
{...props}
|
|
74
105
|
>
|
|
75
|
-
{
|
|
106
|
+
{mark && (
|
|
76
107
|
<div
|
|
77
|
-
className={
|
|
78
|
-
style={
|
|
79
|
-
|
|
80
|
-
height: vertical ? thickness : length,
|
|
81
|
-
marginBottom: !vertical ? gap : undefined,
|
|
82
|
-
marginRight: vertical ? gap : undefined,
|
|
83
|
-
...styles?.mark,
|
|
84
|
-
}}
|
|
85
|
-
data-vertical={vertical}
|
|
108
|
+
className={classes?.mark}
|
|
109
|
+
style={styles?.mark}
|
|
110
|
+
data-orientation={vertical ? 'vertical' : 'horizontal'}
|
|
86
111
|
></div>
|
|
87
112
|
)}
|
|
88
|
-
{
|
|
89
|
-
<div
|
|
90
|
-
|
|
91
|
-
style={{
|
|
92
|
-
width: labelWidth,
|
|
93
|
-
...styles?.label,
|
|
94
|
-
}}
|
|
95
|
-
>
|
|
96
|
-
{label || value}
|
|
113
|
+
{label !== null && (
|
|
114
|
+
<div className={classes?.label} style={styles?.label}>
|
|
115
|
+
{label ?? value}
|
|
97
116
|
</div>
|
|
98
117
|
)}
|
|
99
118
|
</div>
|
|
@@ -1,25 +1,27 @@
|
|
|
1
|
-
import clsx from 'clsx'
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Ref,
|
|
2
|
+
ComponentPropsWithoutRef,
|
|
3
|
+
forwardRef,
|
|
6
4
|
useImperativeHandle,
|
|
7
5
|
useRef,
|
|
6
|
+
CSSProperties,
|
|
8
7
|
} from 'react'
|
|
9
8
|
|
|
9
|
+
import { useCheckPlacement } from '../_util/Placement'
|
|
10
|
+
import { VisuallyHiddenRangeInput } from '../_util/VisuallyHiddenRangeInput'
|
|
11
|
+
|
|
10
12
|
import { useSliderContext } from './context'
|
|
11
13
|
|
|
14
|
+
import type { CSSVariables } from '../../css-variables'
|
|
15
|
+
|
|
12
16
|
export interface SliderThumbProps {
|
|
13
17
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
18
|
+
* Sets `--color`, for the theme to colour the thumb with. The size is not
|
|
19
|
+
* set here but by `--thumb-size` on `Slider.Root`, so that the root can
|
|
20
|
+
* leave the matching room around the track.
|
|
16
21
|
*/
|
|
17
22
|
color?: string
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
style?: CSSProperties
|
|
21
|
-
children?: ReactNode
|
|
22
|
-
ref?: Ref<SliderThumbMethods>
|
|
24
|
+
style?: CSSProperties & CSSVariables<'color' | 'translate'>
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export interface SliderThumbMethods {
|
|
@@ -27,55 +29,105 @@ export interface SliderThumbMethods {
|
|
|
27
29
|
blur: () => void
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
children,
|
|
33
|
-
className,
|
|
34
|
-
style,
|
|
35
|
-
ref,
|
|
36
|
-
}: SliderThumbProps) {
|
|
37
|
-
const elementRef = useRef<HTMLDivElement>(null)
|
|
38
|
-
const { vertical, disabled, readonly, percent, thumbRef } = useSliderContext()
|
|
32
|
+
type Props = SliderThumbProps &
|
|
33
|
+
Omit<ComponentPropsWithoutRef<'div'>, keyof SliderThumbProps>
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
export const Thumb = /* @__PURE__ */ forwardRef<SliderThumbMethods, Props>(
|
|
36
|
+
function Thumb(
|
|
37
|
+
{
|
|
38
|
+
color,
|
|
39
|
+
children,
|
|
40
|
+
className,
|
|
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.
|
|
47
|
+
'aria-label': ariaLabel,
|
|
48
|
+
'aria-labelledby': ariaLabelledby,
|
|
49
|
+
'aria-describedby': ariaDescribedby,
|
|
50
|
+
'aria-valuetext': ariaValuetext,
|
|
51
|
+
...props
|
|
43
52
|
},
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
forwardedRef,
|
|
54
|
+
) {
|
|
55
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
56
|
+
const {
|
|
57
|
+
value,
|
|
58
|
+
min,
|
|
59
|
+
max,
|
|
60
|
+
step,
|
|
61
|
+
vertical,
|
|
62
|
+
disabled,
|
|
63
|
+
readonly,
|
|
64
|
+
onChange,
|
|
65
|
+
percent,
|
|
66
|
+
thumbRef,
|
|
67
|
+
} = useSliderContext()
|
|
68
|
+
|
|
69
|
+
// The thumb is positioned against the track.
|
|
70
|
+
useCheckPlacement('Slider.Thumb', 'Slider.Track')
|
|
48
71
|
|
|
49
|
-
|
|
50
|
-
|
|
72
|
+
const methods = () => ({
|
|
73
|
+
focus() {
|
|
74
|
+
if (!disabled) inputRef.current?.focus()
|
|
75
|
+
},
|
|
76
|
+
blur() {
|
|
77
|
+
inputRef.current?.blur()
|
|
78
|
+
},
|
|
79
|
+
})
|
|
51
80
|
|
|
52
|
-
|
|
81
|
+
useImperativeHandle(forwardedRef, methods, [disabled])
|
|
82
|
+
// Root focuses the thumb when a drag starts, wherever the user placed it.
|
|
83
|
+
useImperativeHandle(thumbRef, methods, [disabled])
|
|
53
84
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
className={className}
|
|
88
|
+
data-disabled={disabled ? '' : undefined}
|
|
89
|
+
data-readonly={readonly ? '' : undefined}
|
|
90
|
+
{...props}
|
|
91
|
+
style={{
|
|
92
|
+
...{ '--color': color },
|
|
93
|
+
// Not a style but the mechanics of the position below: the thumb is
|
|
94
|
+
// placed by a percentage of the track, which needs it out of flow
|
|
95
|
+
// and measured from its own centre. `--translate` is there for a
|
|
96
|
+
// thumb that should hang off its edge instead.
|
|
97
|
+
position: 'absolute',
|
|
98
|
+
translate: 'var(--translate, -50% -50%)',
|
|
99
|
+
zIndex: 100,
|
|
100
|
+
...style,
|
|
101
|
+
// Where the thumb sits is the component's decision, not a style: a
|
|
102
|
+
// `left` from the caller would take it off the track, so it is
|
|
103
|
+
// written after theirs.
|
|
104
|
+
top: vertical ? `${percent}%` : '50%',
|
|
105
|
+
left: !vertical ? `${percent}%` : '50%',
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
<VisuallyHiddenRangeInput
|
|
109
|
+
ref={inputRef}
|
|
110
|
+
value={value}
|
|
111
|
+
min={min}
|
|
112
|
+
max={max}
|
|
113
|
+
step={step}
|
|
114
|
+
disabled={disabled}
|
|
72
115
|
aria-readonly={readonly}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
116
|
+
aria-orientation={vertical ? 'vertical' : 'horizontal'}
|
|
117
|
+
aria-label={ariaLabel}
|
|
118
|
+
aria-labelledby={ariaLabelledby}
|
|
119
|
+
aria-describedby={ariaDescribedby}
|
|
120
|
+
aria-valuetext={ariaValuetext}
|
|
121
|
+
onChange={(event) => {
|
|
122
|
+
if (readonly) {
|
|
123
|
+
event.currentTarget.value = String(value)
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
onChange?.(event.currentTarget.valueAsNumber)
|
|
76
127
|
}}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
128
|
+
/>
|
|
129
|
+
{children}
|
|
130
|
+
</div>
|
|
131
|
+
)
|
|
132
|
+
},
|
|
133
|
+
)
|
|
@@ -1,79 +1,83 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ComponentPropsWithoutRef, CSSProperties, ReactNode, Ref } from 'react'
|
|
1
|
+
import { ComponentPropsWithoutRef, CSSProperties, forwardRef } from 'react'
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import {
|
|
3
|
+
import { useComposedRefs } from '../../compose-refs'
|
|
4
|
+
import { cssLength } from '../_util/css-length'
|
|
5
|
+
import { Placement } from '../_util/Placement'
|
|
7
6
|
|
|
8
7
|
import { useSliderContext } from './context'
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
export const defaultThickness = 10
|
|
9
|
+
import type { CSSVariables } from '../../css-variables'
|
|
12
10
|
|
|
13
11
|
export interface SliderTrackProps {
|
|
12
|
+
/**
|
|
13
|
+
* How long the track is along the axis the slider runs. Sets `--length`;
|
|
14
|
+
* the size the theme gives it stands when this is omitted.
|
|
15
|
+
*/
|
|
14
16
|
length?: number | string
|
|
17
|
+
/** How thick the track is across that axis. Sets `--thickness`. */
|
|
15
18
|
thickness?: number | string
|
|
16
19
|
|
|
20
|
+
/** Colour of the part from `min` to the value. Sets `--active`. */
|
|
17
21
|
active?: string
|
|
22
|
+
/** Colour of the rest of the track. Sets `--inactive`. */
|
|
18
23
|
inactive?: string
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
className?: string
|
|
23
|
-
style?: CSSProperties
|
|
24
|
-
/** `<Slider.Thumb />` goes here. */
|
|
25
|
-
children?: ReactNode
|
|
26
|
-
ref?: Ref<HTMLDivElement>
|
|
25
|
+
style?: CSSProperties &
|
|
26
|
+
CSSVariables<'length' | 'thickness' | 'active' | 'inactive' | 'percent'>
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
thickness = defaultThickness,
|
|
32
|
-
active,
|
|
33
|
-
inactive,
|
|
34
|
-
children,
|
|
35
|
-
className,
|
|
36
|
-
style,
|
|
37
|
-
defaultStyle = true,
|
|
38
|
-
ref,
|
|
39
|
-
...props
|
|
40
|
-
}: SliderTrackProps &
|
|
41
|
-
Omit<ComponentPropsWithoutRef<'div'>, keyof SliderTrackProps>) {
|
|
42
|
-
const { vertical, reverse, disabled, percent, trackRef } = useSliderContext()
|
|
29
|
+
type Props = SliderTrackProps &
|
|
30
|
+
Omit<ComponentPropsWithoutRef<'div'>, keyof SliderTrackProps>
|
|
43
31
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
32
|
+
export const Track = /* @__PURE__ */ forwardRef<HTMLDivElement, Props>(
|
|
33
|
+
function Track(
|
|
34
|
+
{
|
|
35
|
+
length,
|
|
36
|
+
thickness,
|
|
37
|
+
active,
|
|
38
|
+
inactive,
|
|
39
|
+
children,
|
|
40
|
+
className,
|
|
41
|
+
style,
|
|
42
|
+
...props
|
|
43
|
+
},
|
|
44
|
+
forwardedRef,
|
|
45
|
+
) {
|
|
46
|
+
const { vertical, reverse, disabled, percent, trackRef } =
|
|
47
|
+
useSliderContext()
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
'--inactive': inactive,
|
|
52
|
-
}
|
|
49
|
+
// The track is what the pointer position is normalized against, so the
|
|
50
|
+
// context ref is composed with any ref the caller passed.
|
|
51
|
+
const composedRef = useComposedRefs<HTMLDivElement>(forwardedRef, trackRef)
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
ref={composedRef}
|
|
56
|
+
className={className}
|
|
57
|
+
data-disabled={disabled ? '' : undefined}
|
|
58
|
+
data-orientation={vertical ? 'vertical' : 'horizontal'}
|
|
59
|
+
// Which end the value grows from. `percent` is already the position on
|
|
60
|
+
// screen, so this only says which side of it is the filled one.
|
|
61
|
+
data-flipped={vertical !== reverse ? '' : undefined}
|
|
62
|
+
style={
|
|
63
|
+
{
|
|
64
|
+
'--active': active,
|
|
65
|
+
'--inactive': inactive,
|
|
66
|
+
'--length': cssLength(length),
|
|
67
|
+
'--thickness': cssLength(thickness),
|
|
68
|
+
// Where the value sits, for the theme to paint the fill with. The
|
|
69
|
+
// component draws nothing itself: this is the one number CSS cannot
|
|
70
|
+
// work out on its own.
|
|
71
|
+
'--percent': `${percent}%`,
|
|
72
|
+
// The thumb inside is placed against this box.
|
|
73
|
+
position: 'relative',
|
|
74
|
+
...style,
|
|
75
|
+
} as CSSProperties
|
|
76
|
+
}
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
<Placement name="Slider.Track">{children}</Placement>
|
|
80
|
+
</div>
|
|
81
|
+
)
|
|
82
|
+
},
|
|
83
|
+
)
|
|
@@ -15,7 +15,7 @@ export type SliderContextValue = {
|
|
|
15
15
|
reverse: boolean
|
|
16
16
|
disabled: boolean
|
|
17
17
|
readonly: boolean
|
|
18
|
-
|
|
18
|
+
onChange?: (value: number) => void
|
|
19
19
|
/**
|
|
20
20
|
* Position of the thumb, 0-100, already accounting for the display direction
|
|
21
21
|
* implied by `vertical` and `reverse`.
|
|
@@ -28,7 +28,9 @@ export type SliderContextValue = {
|
|
|
28
28
|
thumbRef: RefObject<SliderThumbMethods | null>
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const SliderContext = createContext<SliderContextValue | null>(
|
|
31
|
+
const SliderContext = /* @__PURE__ */ createContext<SliderContextValue | null>(
|
|
32
|
+
null,
|
|
33
|
+
)
|
|
32
34
|
|
|
33
35
|
export const SliderProvider = SliderContext.Provider
|
|
34
36
|
|