@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.
Files changed (86) hide show
  1. package/dist/compose-refs.cjs +72 -0
  2. package/dist/compose-refs.cjs.map +1 -0
  3. package/dist/compose-refs.d.cts +43 -0
  4. package/dist/compose-refs.d.cts.map +1 -0
  5. package/dist/compose-refs.d.ts +43 -0
  6. package/dist/compose-refs.d.ts.map +1 -0
  7. package/dist/compose-refs.js +70 -0
  8. package/dist/compose-refs.js.map +1 -0
  9. package/dist/index.cjs +1669 -541
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +1571 -486
  12. package/dist/index.d.cts.map +1 -1
  13. package/dist/index.d.ts +1571 -486
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1640 -538
  16. package/dist/index.js.map +1 -1
  17. package/package.json +29 -27
  18. package/src/components/AnimationCanvas/index.tsx +115 -76
  19. package/src/components/DropZone/index.tsx +100 -0
  20. package/src/components/FileInput/Trigger.tsx +39 -0
  21. package/src/components/FileInput/context.tsx +24 -0
  22. package/src/components/FileInput/index.tsx +144 -0
  23. package/src/components/Knob/ActiveLine.tsx +7 -6
  24. package/src/components/Knob/InactiveLine.tsx +9 -10
  25. package/src/components/Knob/SVGRoot.tsx +5 -3
  26. package/src/components/Knob/Thumb.tsx +33 -11
  27. package/src/components/Knob/context.tsx +17 -1
  28. package/src/components/Knob/index.tsx +156 -60
  29. package/src/components/NumberInput/DecrementStepper.tsx +3 -10
  30. package/src/components/NumberInput/IncrementStepper.tsx +3 -10
  31. package/src/components/NumberInput/InputField.tsx +185 -108
  32. package/src/components/NumberInput/Stepper.tsx +120 -84
  33. package/src/components/NumberInput/StepperButton.tsx +100 -0
  34. package/src/components/NumberInput/context.tsx +34 -8
  35. package/src/components/NumberInput/index.tsx +270 -82
  36. package/src/components/Piano/index.tsx +363 -200
  37. package/src/components/PointsEditor/Background.tsx +13 -10
  38. package/src/components/PointsEditor/Container.tsx +83 -29
  39. package/src/components/PointsEditor/Point.tsx +241 -79
  40. package/src/components/PointsEditor/SelectionBox.tsx +48 -0
  41. package/src/components/PointsEditor/context.tsx +78 -10
  42. package/src/components/PointsEditor/index.tsx +440 -41
  43. package/src/components/Slider/Marks.tsx +48 -19
  44. package/src/components/Slider/MarksOption.tsx +64 -45
  45. package/src/components/Slider/Thumb.tsx +107 -55
  46. package/src/components/Slider/Track.tsx +66 -62
  47. package/src/components/Slider/context.tsx +4 -2
  48. package/src/components/Slider/index.tsx +136 -77
  49. package/src/components/Slider/type.ts +29 -9
  50. package/src/components/XYPad/Area.tsx +46 -41
  51. package/src/components/XYPad/Thumb.tsx +131 -64
  52. package/src/components/XYPad/context.tsx +4 -1
  53. package/src/components/XYPad/index.tsx +183 -53
  54. package/src/components/_util/Placement.tsx +68 -0
  55. package/src/components/_util/VisuallyHiddenRangeInput.tsx +12 -0
  56. package/src/components/_util/css-length.ts +13 -0
  57. package/src/components/_util/decimal-digits.ts +23 -0
  58. package/src/components/_util/visually-hidden.ts +25 -0
  59. package/src/{components/_util/composeRefs.tsx → compose-refs/index.tsx} +33 -5
  60. package/src/css-variables.ts +23 -0
  61. package/src/hooks/_internal/useCallbackRef.ts +3 -1
  62. package/src/hooks/_internal/useCheckSteps.ts +169 -0
  63. package/src/hooks/useAnimationFrame.ts +17 -7
  64. package/src/hooks/useDrag.ts +51 -12
  65. package/src/hooks/useDragValue.ts +71 -11
  66. package/src/hooks/useDropZone.ts +78 -0
  67. package/src/hooks/useEventListener.ts +10 -6
  68. package/src/hooks/useLongPress.ts +42 -8
  69. package/src/hooks/useMIDIAccess.ts +82 -11
  70. package/src/hooks/useMIDIInput.ts +45 -13
  71. package/src/hooks/useMIDIMessage.ts +23 -5
  72. package/src/hooks/useWheel.ts +4 -1
  73. package/src/index.ts +45 -25
  74. package/src/input-event.ts +32 -0
  75. package/dist/index.css +0 -501
  76. package/src/Globals.d.ts +0 -3
  77. package/src/components/Knob/index.css +0 -81
  78. package/src/components/NumberInput/index.css +0 -121
  79. package/src/components/NumberInput/stepperButton.tsx +0 -90
  80. package/src/components/Piano/index.css +0 -90
  81. package/src/components/PointsEditor/index.css +0 -35
  82. package/src/components/Slider/index.css +0 -125
  83. package/src/components/XYPad/index.css +0 -60
  84. package/src/components/_util/index.ts +0 -28
  85. package/src/styles/global.css +0 -19
  86. /package/src/components/Piano/{keyboardShortcuts.ts → keyboard-shortcuts.ts} +0 -0
@@ -1,26 +1,42 @@
1
- import clsx from 'clsx'
2
- import { ComponentPropsWithoutRef, ReactNode } from 'react'
1
+ import { ComponentPropsWithoutRef, CSSProperties } from 'react'
3
2
 
4
- import { xor } from '@tremolo-ui/functions'
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
- export interface MarksProps {
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
- children?: ReactNode
27
+
28
+ style?: CSSProperties & CSSVariables<'gap'>
14
29
  }
15
30
 
16
31
  export function Marks({
17
- gap = 6,
32
+ gap,
18
33
  options,
19
34
  children,
20
35
  className,
21
36
  style,
22
37
  ...props
23
- }: MarksProps & Omit<ComponentPropsWithoutRef<'div'>, keyof MarksProps>) {
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 (xor(vertical, reverse)) optionsList.reverse()
49
+ if (vertical !== reverse) optionsList.reverse()
34
50
 
35
51
  return (
36
52
  <div
37
- className={clsx('tremolo-slider-marks', className)}
38
- style={{
39
- marginLeft: vertical ? gap : undefined,
40
- marginTop: !vertical ? gap : undefined,
41
- ...style,
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
- {options
46
- ? optionsList.map(({ value, type }, index) => {
47
- return <MarksOption key={index} value={value} type={type} />
48
- })
49
- : children}
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, xor } from '@tremolo-ui/functions'
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
- export interface MarksOptionProps {
10
- // required
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
- // optional
14
- type?: MarksType
15
- /** Display text instead of value. */
16
- label?: string
17
- /** mark thickness */
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
- /** Gap between mark and label. */
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
- type = 'mark-number',
51
+ mark = true,
37
52
  label,
38
- thickness = 1,
39
- length = '0.5rem',
40
- gap = 2,
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
- }: MarksOptionProps &
48
- Omit<ComponentPropsWithoutRef<'div'>, keyof MarksOptionProps>) {
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(xor(vertical, reverse) ? 100 - percent : percent)
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={clsx('tremolo-slider-marks-option', className)}
67
- style={{
68
- left: !vertical ? `${calcPercent(value)}%` : undefined,
69
- top: vertical ? `${calcPercent(value)}%` : undefined,
70
- ...style,
71
- }}
72
- data-vertical={vertical}
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
- {type != 'number' && (
106
+ {mark && (
76
107
  <div
77
- className={clsx('tremolo-slider-marks-option-mark', classes?.mark)}
78
- style={{
79
- width: !vertical ? thickness : length,
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
- {type != 'mark' && (
89
- <div
90
- className={clsx('tremolo-slider-marks-option-label', classes?.label)}
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
- CSSProperties,
4
- ReactNode,
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
- * Size comes from the `--thumb-size` CSS variable on `Slider.Root`, so that
15
- * the root can reserve the matching amount of space around the track.
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
- className?: string
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
- export function Thumb({
31
- color,
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
- const methods = () => ({
41
- focus() {
42
- elementRef.current?.focus()
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
- blur() {
45
- elementRef.current?.blur()
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
- useImperativeHandle(ref, methods, [])
50
- // Root focuses the thumb when a drag starts, wherever the user placed it.
72
+ const methods = () => ({
73
+ focus() {
74
+ if (!disabled) inputRef.current?.focus()
75
+ },
76
+ blur() {
77
+ inputRef.current?.blur()
78
+ },
79
+ })
51
80
 
52
- useImperativeHandle(thumbRef, methods, [])
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
- return (
55
- <div
56
- className="tremolo-slider-thumb-wrapper"
57
- style={{
58
- top: vertical ? `${percent}%` : '50%',
59
- left: !vertical ? `${percent}%` : '50%',
60
- }}
61
- >
62
- {children ? (
63
- children
64
- ) : (
65
- // default slider thumb
66
- <div
67
- ref={elementRef}
68
- className={clsx('tremolo-slider-thumb', className)}
69
- // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
70
- tabIndex={0}
71
- aria-disabled={disabled}
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
- style={{
74
- ...{ '--color': color },
75
- ...style,
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
- ></div>
78
- )}
79
- </div>
80
- )
81
- }
128
+ />
129
+ {children}
130
+ </div>
131
+ )
132
+ },
133
+ )
@@ -1,79 +1,83 @@
1
- import clsx from 'clsx'
2
- import { ComponentPropsWithoutRef, CSSProperties, ReactNode, Ref } from 'react'
1
+ import { ComponentPropsWithoutRef, CSSProperties, forwardRef } from 'react'
3
2
 
4
- import { styleHelper, xor } from '@tremolo-ui/functions'
5
-
6
- import { useComposedRefs } from '../_util/composeRefs'
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
- export const defaultLength = 140
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
- defaultStyle?: boolean
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
- export function Track({
30
- length = defaultLength,
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
- // The track is what the pointer position is normalized against, so the
45
- // context ref is composed with any ref the caller passed.
46
- const composedRef = useComposedRefs<HTMLDivElement>(ref, trackRef)
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
- const direction = vertical ? 'bottom' : 'right'
49
- const colors = {
50
- '--active': active,
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
- return (
55
- <div
56
- ref={composedRef}
57
- className={clsx('tremolo-slider-track', className)}
58
- aria-disabled={disabled}
59
- data-vertical={vertical}
60
- style={
61
- !defaultStyle
62
- ? style
63
- : {
64
- ...colors,
65
- background: xor(vertical, reverse)
66
- ? `linear-gradient(to ${direction}, var(--inactive) ${percent}%, var(--active) ${percent}%)`
67
- : `linear-gradient(to ${direction}, var(--active) ${percent}%, var(--inactive) ${percent}%)`,
68
- borderRadius: styleHelper(thickness!, '/', 2),
69
- width: !vertical ? length : thickness,
70
- height: vertical ? length : thickness,
71
- ...style,
72
- }
73
- }
74
- {...props}
75
- >
76
- {children}
77
- </div>
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>(null)
31
+ const SliderContext = /* @__PURE__ */ createContext<SliderContextValue | null>(
32
+ null,
33
+ )
32
34
 
33
35
  export const SliderProvider = SliderContext.Provider
34
36