@tremolo-ui/react 0.2.0 → 0.3.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 (64) hide show
  1. package/dist/index.cjs +1245 -1083
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +176 -159
  4. package/dist/index.d.cts +244 -253
  5. package/dist/index.d.cts.map +1 -1
  6. package/dist/index.d.ts +244 -253
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1218 -1067
  9. package/dist/index.js.map +1 -1
  10. package/package.json +18 -19
  11. package/src/components/AnimationCanvas/index.tsx +0 -7
  12. package/src/components/DragObserver/index.tsx +9 -7
  13. package/src/components/Knob/ActiveLine.tsx +29 -0
  14. package/src/components/Knob/InactiveLine.tsx +53 -0
  15. package/src/components/Knob/SVGRoot.tsx +39 -0
  16. package/src/components/Knob/Thumb.tsx +64 -0
  17. package/src/components/Knob/context.tsx +120 -0
  18. package/src/components/Knob/index.css +5 -1
  19. package/src/components/Knob/index.tsx +110 -164
  20. package/src/components/NumberInput/DecrementStepper.tsx +0 -2
  21. package/src/components/NumberInput/IncrementStepper.tsx +0 -2
  22. package/src/components/NumberInput/InternalInput.tsx +3 -1
  23. package/src/components/NumberInput/Stepper.tsx +0 -2
  24. package/src/components/NumberInput/context.tsx +0 -1
  25. package/src/components/NumberInput/index.tsx +6 -8
  26. package/src/components/Piano/KeyLabel.tsx +0 -2
  27. package/src/components/Piano/context.tsx +0 -2
  28. package/src/components/Piano/index.tsx +7 -11
  29. package/src/components/Piano/key.tsx +0 -10
  30. package/src/components/Piano/keyboardShortcuts.ts +0 -2
  31. package/src/components/PointsEditor/Background.tsx +17 -0
  32. package/src/components/PointsEditor/Container.tsx +26 -0
  33. package/src/components/PointsEditor/Point.tsx +137 -0
  34. package/src/components/PointsEditor/context.tsx +91 -0
  35. package/src/components/PointsEditor/index.css +30 -0
  36. package/src/components/PointsEditor/index.tsx +129 -0
  37. package/src/components/Slider/Scale.tsx +0 -2
  38. package/src/components/Slider/ScaleOption.tsx +0 -2
  39. package/src/components/Slider/Thumb.tsx +0 -4
  40. package/src/components/Slider/Track.tsx +0 -2
  41. package/src/components/Slider/context.tsx +0 -1
  42. package/src/components/Slider/index.tsx +42 -14
  43. package/src/components/WheelObserver/index.tsx +17 -19
  44. package/src/components/XYPad/Area.tsx +0 -2
  45. package/src/components/XYPad/Thumb.tsx +0 -3
  46. package/src/components/XYPad/index.tsx +52 -25
  47. package/src/components/_util/composeRefs.tsx +63 -0
  48. package/src/components/_util/index.ts +23 -6
  49. package/src/components/_util/type.ts +1 -0
  50. package/src/hooks/useAnimationFrame.ts +0 -3
  51. package/src/hooks/useCallbackRef.ts +2 -2
  52. package/src/hooks/useDrag.ts +12 -11
  53. package/src/hooks/useDragWithElement.ts +12 -17
  54. package/src/hooks/useEventListener.ts +6 -9
  55. package/src/hooks/useInterval.ts +0 -3
  56. package/src/hooks/useLongPress.ts +0 -3
  57. package/src/hooks/useMIDIAccess.ts +20 -28
  58. package/src/hooks/useMIDIInput.ts +10 -38
  59. package/src/hooks/useMIDIMessage.ts +4 -12
  60. package/src/hooks/usePianoDrag.ts +75 -0
  61. package/src/index.ts +8 -5
  62. package/src/styles/global.css +17 -1
  63. package/dist/index.css.map +0 -1
  64. package/src/components/AnimationKnob/index.tsx +0 -314
@@ -0,0 +1,26 @@
1
+ import clsx from 'clsx'
2
+ import { ComponentPropsWithoutRef, useRef } from 'react'
3
+
4
+ import { usePointsEditorContext } from './context'
5
+
6
+ export function Container({
7
+ className,
8
+ children,
9
+ ...props
10
+ }: ComponentPropsWithoutRef<'div'>) {
11
+ const containerElementRef = useRef<HTMLDivElement>(null)
12
+ const setContainerElementRef = usePointsEditorContext(
13
+ (s) => s.setContainerElementRef,
14
+ )
15
+ setContainerElementRef(containerElementRef)
16
+
17
+ return (
18
+ <div
19
+ ref={containerElementRef}
20
+ className={clsx('tremolo-points-editor-container', className)}
21
+ {...props}
22
+ >
23
+ {children}
24
+ </div>
25
+ )
26
+ }
@@ -0,0 +1,137 @@
1
+ import clsx from 'clsx'
2
+ import { ComponentPropsWithoutRef } from 'react'
3
+
4
+ import { clamp } from '@tremolo-ui/functions'
5
+
6
+ import { useDragWithElement } from '../../hooks/useDragWithElement'
7
+ import {
8
+ addUserSelectNone,
9
+ removeUserSelectNone,
10
+ resetCursorStyle,
11
+ setCursorStyle,
12
+ } from '../_util'
13
+
14
+ import { usePointsEditorContext } from './context'
15
+
16
+ export type PointBaseType = { x: number; y: number }
17
+
18
+ export function clampPoint(
19
+ point: PointBaseType,
20
+ min?: Partial<PointBaseType>,
21
+ max?: Partial<PointBaseType>,
22
+ ) {
23
+ const { x, y } = point
24
+ const newX = clamp(x, min?.x ?? 0, max?.x ?? 1)
25
+ const newY = clamp(y, min?.y ?? 0, max?.y ?? 1)
26
+ return { x: newX, y: newY }
27
+ }
28
+
29
+ export interface PointProps<T extends PointBaseType> {
30
+ value: T
31
+ min?: Partial<PointBaseType>
32
+ max?: Partial<PointBaseType>
33
+
34
+ size?: number | string
35
+ width?: number | string
36
+ height?: number | string
37
+ color?: string
38
+
39
+ disabled?: boolean
40
+ readonly?: boolean
41
+
42
+ onChange?: (value: PointBaseType) => void
43
+ onDragStart?: (value: PointBaseType) => void
44
+ onDragEnd?: (value: PointBaseType) => void
45
+ }
46
+
47
+ export function Point<T extends PointBaseType>({
48
+ value,
49
+ min,
50
+ max,
51
+ size,
52
+ width = 16,
53
+ height = 16,
54
+ color,
55
+
56
+ disabled,
57
+ readonly,
58
+
59
+ onChange,
60
+ onDragStart,
61
+ onDragEnd,
62
+
63
+ className,
64
+ style,
65
+ onPointerDown,
66
+ ...props
67
+ }: PointProps<T> & Omit<ComponentPropsWithoutRef<'div'>, keyof PointProps<T>>) {
68
+ const containerElementRef = usePointsEditorContext(
69
+ (s) => s.containerElementRef,
70
+ )
71
+ const externalStyles = usePointsEditorContext((s) => s.externalStyles)
72
+ const __disabled = usePointsEditorContext((s) => s.disabled)
73
+ const __readonly = usePointsEditorContext((s) => s.readonly)
74
+
75
+ // console.log(value.x, value.y)
76
+
77
+ // Piano: 値が更新されることで、イベントハンドラがリセットされる
78
+
79
+ const {
80
+ refHandler: touchMoveRefCallback,
81
+ pointerDownHandler,
82
+ dragging,
83
+ } = useDragWithElement<HTMLDivElement>({
84
+ baseElementRef: containerElementRef,
85
+ onDrag: (x, y) => {
86
+ if (readonly) return
87
+
88
+ onChange?.(clampPoint({ x, y }, min, max))
89
+ },
90
+ onDragStart: (x, y) => {
91
+ if (readonly) return
92
+ if (externalStyles.userSelectNone) addUserSelectNone()
93
+ if (externalStyles.cursor) setCursorStyle(externalStyles.cursor)
94
+
95
+ onDragStart?.(clampPoint({ x, y }, min, max))
96
+ },
97
+ onDragEnd: (x, y) => {
98
+ if (readonly) return
99
+ if (externalStyles.userSelectNone) removeUserSelectNone()
100
+ if (externalStyles.cursor) resetCursorStyle()
101
+
102
+ onDragEnd?.(clampPoint({ x, y }, min, max))
103
+ },
104
+ })
105
+
106
+ const colors: Record<string, string | undefined> = {
107
+ '--color': color,
108
+ }
109
+
110
+ return (
111
+ <div
112
+ ref={(div) => {
113
+ // wheelRefCallback(div)
114
+ touchMoveRefCallback(div)
115
+ }}
116
+ className={clsx('tremolo-points-editor-point', className)}
117
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
118
+ tabIndex={0}
119
+ aria-disabled={disabled != undefined ? disabled : __disabled}
120
+ aria-readonly={readonly != undefined ? readonly : __readonly}
121
+ data-dragging={dragging}
122
+ style={{
123
+ ...colors,
124
+ width: size ?? width,
125
+ height: size ?? height,
126
+ left: `${value.x * 100}%`,
127
+ top: `${value.y * 100}%`,
128
+ ...style,
129
+ }}
130
+ onPointerDown={(event) => {
131
+ pointerDownHandler(event)
132
+ onPointerDown?.(event)
133
+ }}
134
+ {...props}
135
+ />
136
+ )
137
+ }
@@ -0,0 +1,91 @@
1
+ import {
2
+ createContext,
3
+ createRef,
4
+ RefObject,
5
+ useContext,
6
+ useEffect,
7
+ useRef,
8
+ } from 'react'
9
+ import { createStore, useStore } from 'zustand'
10
+
11
+ import { Cursor } from '../_util'
12
+
13
+ type State = {
14
+ containerElementRef: RefObject<HTMLDivElement | null>
15
+ disabled: boolean
16
+ readonly: boolean
17
+ externalStyles: {
18
+ userSelectNone?: boolean
19
+ cursor?: Cursor
20
+ }
21
+ }
22
+
23
+ type Action = {
24
+ setContainerElementRef: (
25
+ containerElementRef: RefObject<HTMLDivElement | null>,
26
+ ) => void
27
+ }
28
+
29
+ type PointsEditorStore = ReturnType<typeof createPointsEditorStore>
30
+
31
+ const createPointsEditorStore = (initProps: Partial<State & Action>) => {
32
+ // const noteRange = initProps.noteRange || { first: 0, last: 127 }
33
+ const DEFAULT_PROPS: State = {
34
+ containerElementRef: createRef(),
35
+ disabled: false,
36
+ readonly: false,
37
+ externalStyles: {
38
+ userSelectNone: true,
39
+ cursor: 'grabbing',
40
+ },
41
+ }
42
+
43
+ return createStore<State & Action>()((set) => ({
44
+ ...DEFAULT_PROPS,
45
+ ...initProps,
46
+ setContainerElementRef: (containerElementRef) => {
47
+ set(() => ({
48
+ containerElementRef,
49
+ }))
50
+ },
51
+ }))
52
+ }
53
+
54
+ const PointsEditorContext = createContext<PointsEditorStore | null>(null)
55
+
56
+ type PointsEditorProviderProps = React.PropsWithChildren<
57
+ Partial<State & Action>
58
+ >
59
+
60
+ export function PointsEditorProvider({
61
+ children,
62
+ ...props
63
+ }: PointsEditorProviderProps) {
64
+ const storeRef = useRef<PointsEditorStore>(null)
65
+ if (!storeRef.current) {
66
+ storeRef.current = createPointsEditorStore(props)
67
+ }
68
+
69
+ useEffect(() => {
70
+ if (storeRef.current) {
71
+ storeRef.current.setState(props)
72
+ } else {
73
+ storeRef.current = createPointsEditorStore(props)
74
+ }
75
+ }, [props])
76
+
77
+ return (
78
+ <PointsEditorContext.Provider value={storeRef.current}>
79
+ {children}
80
+ </PointsEditorContext.Provider>
81
+ )
82
+ }
83
+
84
+ export function usePointsEditorContext<T>(
85
+ selector: (state: State & Action) => T,
86
+ ): T {
87
+ const store = useContext(PointsEditorContext)
88
+ if (!store)
89
+ throw new Error('Missing PointsEditorContext.Provider in the tree')
90
+ return useStore(store, selector)
91
+ }
@@ -0,0 +1,30 @@
1
+ .tremolo-points-editor {
2
+ position: relative;
3
+ }
4
+
5
+ .tremolo-points-editor-background {
6
+ position: absolute;
7
+ inset: 0;
8
+ z-index: 0;
9
+ }
10
+
11
+ .tremolo-points-editor-container {
12
+ position: absolute;
13
+ inset: 0;
14
+ z-index: 10;
15
+ }
16
+
17
+ .tremolo-points-editor-point {
18
+ --color: oklch(59.5% 0.17548 266.373);
19
+
20
+ position: absolute;
21
+ border-radius: 50%;
22
+ outline: none;
23
+ cursor: grab;
24
+ background-color: var(--color);
25
+ translate: -50% -50%;
26
+
27
+ &[data-dragging='true'] {
28
+ cursor: inherit;
29
+ }
30
+ }
@@ -0,0 +1,129 @@
1
+ import clsx from 'clsx'
2
+ import { ComponentPropsWithoutRef, forwardRef } from 'react'
3
+
4
+ import { InputEventOption } from '@tremolo-ui/functions'
5
+
6
+ import { Cursor } from '../_util'
7
+
8
+ import { Background } from './Background'
9
+ import { Container } from './Container'
10
+ import { PointsEditorProvider } from './context'
11
+ import { Point, PointBaseType } from './Point'
12
+
13
+ /*
14
+ TODO:
15
+
16
+ - 複数選択
17
+ - modifier
18
+ */
19
+
20
+ /*
21
+ import { useState } from 'react'
22
+
23
+ export default function App() {
24
+ const [points, setPoints] = useState(initialPoints)
25
+
26
+ return (
27
+ <PointsEditor.Root
28
+ onDoubleClick={({ x, y }, _e) => {
29
+ const newPoints = [...points, { x, y }]
30
+ setPoints(newPoints)
31
+ }}
32
+ >
33
+ {points.map((point) => (
34
+ <PointsEditor.Point key={`${point.x}, ${point.y}`} value={point} />
35
+ ))}
36
+ </PointsEditor.Root>
37
+ )
38
+ }
39
+ */
40
+
41
+ const defaultExternalStyles: PointsEditorProps['externalStyles'] = {
42
+ userSelectNone: true,
43
+ cursor: 'grabbing',
44
+ }
45
+
46
+ export interface PointsEditorProps {
47
+ width?: number | string
48
+ height?: number | string
49
+
50
+ // TODO
51
+ grid?: number | PointBaseType
52
+ // modifier?:
53
+
54
+ disabled?: boolean
55
+ readonly?: boolean
56
+
57
+ externalStyles?: {
58
+ userSelectNone?: boolean
59
+ cursor?: Cursor
60
+ }
61
+
62
+ // TODO
63
+ /**
64
+ * wheel control option
65
+ * If null, no event will be triggered
66
+ */
67
+ wheel?: InputEventOption | null
68
+ /**
69
+ * keyboard control option
70
+ * If null, no event will be triggered
71
+ */
72
+ keyboard?: InputEventOption | null
73
+ }
74
+
75
+ type Props = PointsEditorProps &
76
+ Omit<ComponentPropsWithoutRef<'div'>, keyof PointsEditorProps>
77
+
78
+ const Root = forwardRef<HTMLDivElement, Props>(
79
+ (
80
+ {
81
+ width = 200,
82
+ height = 100,
83
+ disabled = false,
84
+ readonly = false,
85
+ externalStyles: _externalStyles,
86
+ style,
87
+ className,
88
+ children,
89
+ ...props
90
+ },
91
+ forwardedRef,
92
+ ) => {
93
+ const externalStyles = { ...defaultExternalStyles, ..._externalStyles }
94
+
95
+ return (
96
+ <PointsEditorProvider
97
+ disabled={disabled}
98
+ readonly={readonly}
99
+ externalStyles={externalStyles}
100
+ >
101
+ <div
102
+ ref={forwardedRef}
103
+ className={clsx('tremolo-points-editor', className)}
104
+ style={{
105
+ width,
106
+ height,
107
+ ...style,
108
+ }}
109
+ {...props}
110
+ >
111
+ {children}
112
+ </div>
113
+ </PointsEditorProvider>
114
+ )
115
+ },
116
+ )
117
+
118
+ /**
119
+ * Multiple Point Controller
120
+ */
121
+ export const PointsEditor = {
122
+ Root,
123
+ Background,
124
+ Container,
125
+ Point,
126
+ }
127
+
128
+ export { usePointsEditorContext } from './context'
129
+ export { clampPoint, type PointBaseType, type PointProps } from './Point'
@@ -7,14 +7,12 @@ import { useSliderContext } from './context'
7
7
  import { ScaleOption } from './ScaleOption'
8
8
  import { generateOptionsList, ScaleOptions } from './type'
9
9
 
10
- /** @category Slider */
11
10
  export interface ScaleProps {
12
11
  gap?: number | string
13
12
  options?: ScaleOptions
14
13
  children?: ReactNode
15
14
  }
16
15
 
17
- /** @category Slider */
18
16
  export function Scale({
19
17
  gap = 6,
20
18
  options,
@@ -6,7 +6,6 @@ import { toFixed, xor, normalizeValue } from '@tremolo-ui/functions'
6
6
  import { useSliderContext } from './context'
7
7
  import { ScaleType } from './type'
8
8
 
9
- /** @category Slider */
10
9
  export interface ScaleOptionProps {
11
10
  // required
12
11
  value: number
@@ -32,7 +31,6 @@ export interface ScaleOptionProps {
32
31
  }
33
32
  }
34
33
 
35
- /** @category Slider */
36
34
  export function ScaleOption({
37
35
  value,
38
36
  type = 'mark-number',
@@ -9,7 +9,6 @@ import {
9
9
 
10
10
  import { useSliderContext } from './context'
11
11
 
12
- /** @category Slider */
13
12
  export interface SliderThumbProps {
14
13
  size?: number | string
15
14
  width?: number | string
@@ -25,16 +24,13 @@ export interface SliderThumbProps {
25
24
  __percent?: number
26
25
  }
27
26
 
28
- /** @category Slider */
29
27
  export interface SliderThumbMethods {
30
28
  focus: () => void
31
29
  blur: () => void
32
30
  }
33
31
 
34
- /** @category Slider */
35
32
  export const defaultThumbSize = 22
36
33
 
37
- /** @category Slider */
38
34
  export const Thumb = forwardRef<SliderThumbMethods, SliderThumbProps>(
39
35
  (
40
36
  {
@@ -8,7 +8,6 @@ import { useSliderContext } from './context'
8
8
  export const defaultLength = 140
9
9
  export const defaultThickness = 10
10
10
 
11
- /** @category Slider */
12
11
  export interface SliderTrackProps {
13
12
  length?: number | string
14
13
  thickness?: number | string
@@ -28,7 +27,6 @@ export interface SliderTrackProps {
28
27
  __percent?: number
29
28
  }
30
29
 
31
- /** @category Slider */
32
30
  export function Track({
33
31
  length = defaultLength,
34
32
  thickness = defaultThickness,
@@ -57,7 +57,6 @@ export function SliderProvider({ children, ...props }: SliderProviderProps) {
57
57
  )
58
58
  }
59
59
 
60
- /** @category Slider */
61
60
  export function useSliderContext<T>(selector: (state: State) => T): T {
62
61
  const store = useContext(SliderContext)
63
62
  if (!store) throw new Error('Missing SliderContext.Provider in the tree')
@@ -23,7 +23,13 @@ import {
23
23
 
24
24
  import { useDragWithElement } from '../../hooks/useDragWithElement'
25
25
  import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
26
- import { addNoSelect, removeNoSelect } from '../_util'
26
+ import {
27
+ addUserSelectNone,
28
+ Cursor,
29
+ removeUserSelectNone,
30
+ resetCursorStyle,
31
+ setCursorStyle,
32
+ } from '../_util'
27
33
 
28
34
  import { SliderProvider } from './context'
29
35
  import { Scale } from './Scale'
@@ -31,7 +37,11 @@ import { ScaleOption } from './ScaleOption'
31
37
  import { Thumb, SliderThumbMethods, SliderThumbProps } from './Thumb'
32
38
  import { Track, SliderTrackProps } from './Track'
33
39
 
34
- /** @category Slider */
40
+ const defaultExternalStyles: SliderProps['externalStyles'] = {
41
+ userSelectNone: true,
42
+ cursor: 'pointer',
43
+ }
44
+
35
45
  export interface SliderProps {
36
46
  // required
37
47
  value: number
@@ -41,24 +51,38 @@ export interface SliderProps {
41
51
  // optional
42
52
  step?: number
43
53
  skew?: number // TODO | SkewFunction
54
+ /**
55
+ * slider orientation
56
+ * aria-orientation property is also applied.
57
+ */
44
58
  vertical?: boolean
45
59
  reverse?: boolean
46
- bodyNoSelect?: boolean
60
+
61
+ /** Global style to apply when dragged */
62
+ externalStyles?: {
63
+ userSelectNone?: boolean
64
+ cursor?: Cursor
65
+ }
47
66
  /**
48
67
  * wheel control option
68
+ * If null, no event will be triggered
49
69
  */
50
70
  wheel?: InputEventOption | null
51
71
  /**
52
72
  * keyboard control option
73
+ * If null, no event will be triggered
53
74
  */
54
75
  keyboard?: InputEventOption | null
76
+
55
77
  /**
56
78
  * Only the appearance will change.
57
79
  * Please consider using with readonly.
80
+ * aria-disabled property is also applied.
58
81
  */
59
82
  disabled?: boolean
60
83
  /**
61
84
  * Make the value unchangeable.
85
+ * aria-readonly property is also applied.
62
86
  */
63
87
  readonly?: boolean
64
88
  className?: string
@@ -71,9 +95,6 @@ export interface SliderProps {
71
95
  // ReactElement<typeof SliderThumb, typeof SliderTrack>[]
72
96
  }
73
97
 
74
- /**
75
- * @category Slider
76
- */
77
98
  export interface SliderMethods {
78
99
  focus: () => void
79
100
  blur: () => void
@@ -82,7 +103,7 @@ export interface SliderMethods {
82
103
  type Props = SliderProps &
83
104
  Omit<ComponentPropsWithoutRef<'div'>, keyof SliderProps>
84
105
 
85
- const SliderImpl = forwardRef<SliderMethods, Props>(
106
+ const Root = forwardRef<SliderMethods, Props>(
86
107
  (
87
108
  {
88
109
  value,
@@ -92,7 +113,7 @@ const SliderImpl = forwardRef<SliderMethods, Props>(
92
113
  skew = 1,
93
114
  vertical = false,
94
115
  reverse = false,
95
- bodyNoSelect = true,
116
+ externalStyles: _externalStyles,
96
117
  wheel = ['raw', 1],
97
118
  keyboard = ['raw', 1],
98
119
  disabled = false,
@@ -116,6 +137,8 @@ const SliderImpl = forwardRef<SliderMethods, Props>(
116
137
  const thumbRef = useRef<SliderThumbMethods>(null)
117
138
 
118
139
  // --- interpret props ---
140
+ const externalStyles = { ...defaultExternalStyles, ..._externalStyles }
141
+
119
142
  const p = toFixed(normalizeValue(value, min, max, skew) * 100)
120
143
  const rev = toFixed(100 - p)
121
144
  // NOTE
@@ -192,13 +215,15 @@ const SliderImpl = forwardRef<SliderMethods, Props>(
192
215
  )
193
216
 
194
217
  // --- hooks ---
195
- const [touchMoveRefCallback, pointerDownHandler] =
218
+ const { refHandler: touchMoveRefCallback, pointerDownHandler } =
196
219
  useDragWithElement<HTMLDivElement>({
197
220
  baseElementRef: trackElementRef,
198
221
  onDrag: onDrag,
199
222
  onDragStart: (nx, ny) => {
200
223
  if (readonly) return
201
- if (bodyNoSelect) addNoSelect()
224
+ if (externalStyles.userSelectNone) addUserSelectNone()
225
+ if (externalStyles.cursor) setCursorStyle(externalStyles.cursor)
226
+
202
227
  thumbRef.current?.focus()
203
228
  onDragStart?.(
204
229
  clamp(
@@ -210,7 +235,10 @@ const SliderImpl = forwardRef<SliderMethods, Props>(
210
235
  },
211
236
  onDragEnd: (nx, ny) => {
212
237
  if (readonly) return
213
- if (bodyNoSelect) removeNoSelect()
238
+
239
+ if (externalStyles.userSelectNone) removeUserSelectNone()
240
+ if (externalStyles.cursor) resetCursorStyle()
241
+
214
242
  onDragEnd?.(
215
243
  clamp(
216
244
  stepValue(rawValue(vertical ? ny : nx, min, max, skew), step),
@@ -327,14 +355,14 @@ const SliderImpl = forwardRef<SliderMethods, Props>(
327
355
 
328
356
  /**
329
357
  * Customizable slider
330
- * @category Slider
331
358
  */
332
- export const Slider = Object.assign(SliderImpl, {
359
+ export const Slider = {
360
+ Root,
333
361
  Thumb,
334
362
  Track,
335
363
  Scale,
336
364
  ScaleOption,
337
- })
365
+ }
338
366
 
339
367
  export { useSliderContext } from './context'
340
368
  export { type SliderThumbMethods, type SliderThumbProps } from './Thumb'
@@ -1,20 +1,9 @@
1
- import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react'
1
+ import { ComponentProps, ElementType, ReactNode } from 'react'
2
2
 
3
3
  import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
4
+ import { composeRefs } from '../_util/composeRefs'
5
+ import { Override } from '../_util/type'
4
6
 
5
- /**
6
- * @example
7
- * <WheelObserver
8
- * onWheel={(event) => {
9
- * event.preventDefault()
10
- * console.log(event.deltaY)
11
- * }
12
- * >
13
- * <div>Wheel here</div>
14
- * </WheelObserver>
15
- */
16
-
17
- /** @category WheelObserver */
18
7
  export interface WheelObserverProps<T extends ElementType> {
19
8
  /**
20
9
  * React.ElementType
@@ -26,12 +15,21 @@ export interface WheelObserverProps<T extends ElementType> {
26
15
  onWheel?: (event: WheelEvent) => void
27
16
  }
28
17
 
29
- /** @category WheelObserver */
18
+ /**
19
+ * @example
20
+ * <WheelObserver
21
+ * onWheel={(event) => {
22
+ * event.preventDefault()
23
+ * console.log(event.deltaY)
24
+ * }
25
+ * >
26
+ * <div>Wheel here</div>
27
+ * </WheelObserver>
28
+ */
30
29
  export function WheelObserver<T extends ElementType = 'div'>(
31
- props: WheelObserverProps<T> &
32
- Omit<ComponentPropsWithoutRef<T>, keyof WheelObserverProps<T>>,
30
+ props: Override<WheelObserverProps<T>, ComponentProps<T>>,
33
31
  ) {
34
- const { children, onWheel, as: Component = 'div', ...attributes } = props
32
+ const { as: Component = 'div', children, onWheel, ref, ...attributes } = props
35
33
 
36
34
  const wheelRefCallback = useRefCallbackEvent(
37
35
  'wheel',
@@ -43,7 +41,7 @@ export function WheelObserver<T extends ElementType = 'div'>(
43
41
  )
44
42
 
45
43
  return (
46
- <Component ref={wheelRefCallback} {...attributes}>
44
+ <Component ref={composeRefs(ref, wheelRefCallback)} {...attributes}>
47
45
  {children}
48
46
  </Component>
49
47
  )