@tremolo-ui/react 0.0.6 → 0.0.8

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 (133) hide show
  1. package/dist/cjs/index.js +954 -217
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/AnimationCanvas/index.d.ts +16 -9
  4. package/dist/cjs/types/components/AnimationCanvas/index.d.ts.map +1 -1
  5. package/dist/cjs/types/components/AnimationKnob/index.d.ts +53 -0
  6. package/dist/cjs/types/components/AnimationKnob/index.d.ts.map +1 -0
  7. package/dist/cjs/types/components/DragObserver/index.d.ts +19 -0
  8. package/dist/cjs/types/components/DragObserver/index.d.ts.map +1 -0
  9. package/dist/cjs/types/components/Knob/index.d.ts +26 -26
  10. package/dist/cjs/types/components/Knob/index.d.ts.map +1 -1
  11. package/dist/cjs/types/components/NumberInput/index.d.ts +2 -5
  12. package/dist/cjs/types/components/NumberInput/index.d.ts.map +1 -1
  13. package/dist/cjs/types/components/Piano/BlackKey.d.ts +3 -3
  14. package/dist/cjs/types/components/Piano/BlackKey.d.ts.map +1 -1
  15. package/dist/cjs/types/components/Piano/KeyLabel.d.ts +5 -8
  16. package/dist/cjs/types/components/Piano/KeyLabel.d.ts.map +1 -1
  17. package/dist/cjs/types/components/Piano/WhiteKey.d.ts +3 -3
  18. package/dist/cjs/types/components/Piano/WhiteKey.d.ts.map +1 -1
  19. package/dist/cjs/types/components/Piano/index.d.ts +4 -5
  20. package/dist/cjs/types/components/Piano/index.d.ts.map +1 -1
  21. package/dist/cjs/types/components/Piano/key.d.ts +4 -7
  22. package/dist/cjs/types/components/Piano/key.d.ts.map +1 -1
  23. package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts.map +1 -1
  24. package/dist/cjs/types/components/Slider/Scale.d.ts +11 -0
  25. package/dist/cjs/types/components/Slider/Scale.d.ts.map +1 -0
  26. package/dist/cjs/types/components/Slider/ScaleOption.d.ts +25 -0
  27. package/dist/cjs/types/components/Slider/ScaleOption.d.ts.map +1 -0
  28. package/dist/cjs/types/components/Slider/Thumb.d.ts +5 -13
  29. package/dist/cjs/types/components/Slider/Thumb.d.ts.map +1 -1
  30. package/dist/cjs/types/components/Slider/Track.d.ts +10 -21
  31. package/dist/cjs/types/components/Slider/Track.d.ts.map +1 -1
  32. package/dist/cjs/types/components/Slider/context.d.ts +21 -0
  33. package/dist/cjs/types/components/Slider/context.d.ts.map +1 -0
  34. package/dist/cjs/types/components/Slider/index.d.ts +18 -12
  35. package/dist/cjs/types/components/Slider/index.d.ts.map +1 -1
  36. package/dist/cjs/types/components/WheelObserver/index.d.ts +24 -0
  37. package/dist/cjs/types/components/WheelObserver/index.d.ts.map +1 -0
  38. package/dist/cjs/types/components/XYPad/Area.d.ts +13 -0
  39. package/dist/cjs/types/components/XYPad/Area.d.ts.map +1 -0
  40. package/dist/cjs/types/components/XYPad/Thumb.d.ts +19 -0
  41. package/dist/cjs/types/components/XYPad/Thumb.d.ts.map +1 -0
  42. package/dist/cjs/types/components/XYPad/index.d.ts +39 -0
  43. package/dist/cjs/types/components/XYPad/index.d.ts.map +1 -0
  44. package/dist/cjs/types/hooks/useCallbackRef.d.ts.map +1 -1
  45. package/dist/cjs/types/index.d.ts +11 -0
  46. package/dist/cjs/types/index.d.ts.map +1 -1
  47. package/dist/esm/index.js +932 -218
  48. package/dist/esm/index.js.map +1 -1
  49. package/dist/esm/types/components/AnimationCanvas/index.d.ts +16 -9
  50. package/dist/esm/types/components/AnimationCanvas/index.d.ts.map +1 -1
  51. package/dist/esm/types/components/AnimationKnob/index.d.ts +53 -0
  52. package/dist/esm/types/components/AnimationKnob/index.d.ts.map +1 -0
  53. package/dist/esm/types/components/DragObserver/index.d.ts +19 -0
  54. package/dist/esm/types/components/DragObserver/index.d.ts.map +1 -0
  55. package/dist/esm/types/components/Knob/index.d.ts +26 -26
  56. package/dist/esm/types/components/Knob/index.d.ts.map +1 -1
  57. package/dist/esm/types/components/NumberInput/index.d.ts +2 -5
  58. package/dist/esm/types/components/NumberInput/index.d.ts.map +1 -1
  59. package/dist/esm/types/components/Piano/BlackKey.d.ts +3 -3
  60. package/dist/esm/types/components/Piano/BlackKey.d.ts.map +1 -1
  61. package/dist/esm/types/components/Piano/KeyLabel.d.ts +5 -8
  62. package/dist/esm/types/components/Piano/KeyLabel.d.ts.map +1 -1
  63. package/dist/esm/types/components/Piano/WhiteKey.d.ts +3 -3
  64. package/dist/esm/types/components/Piano/WhiteKey.d.ts.map +1 -1
  65. package/dist/esm/types/components/Piano/index.d.ts +4 -5
  66. package/dist/esm/types/components/Piano/index.d.ts.map +1 -1
  67. package/dist/esm/types/components/Piano/key.d.ts +4 -7
  68. package/dist/esm/types/components/Piano/key.d.ts.map +1 -1
  69. package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts.map +1 -1
  70. package/dist/esm/types/components/Slider/Scale.d.ts +11 -0
  71. package/dist/esm/types/components/Slider/Scale.d.ts.map +1 -0
  72. package/dist/esm/types/components/Slider/ScaleOption.d.ts +25 -0
  73. package/dist/esm/types/components/Slider/ScaleOption.d.ts.map +1 -0
  74. package/dist/esm/types/components/Slider/Thumb.d.ts +5 -13
  75. package/dist/esm/types/components/Slider/Thumb.d.ts.map +1 -1
  76. package/dist/esm/types/components/Slider/Track.d.ts +10 -21
  77. package/dist/esm/types/components/Slider/Track.d.ts.map +1 -1
  78. package/dist/esm/types/components/Slider/context.d.ts +21 -0
  79. package/dist/esm/types/components/Slider/context.d.ts.map +1 -0
  80. package/dist/esm/types/components/Slider/index.d.ts +18 -12
  81. package/dist/esm/types/components/Slider/index.d.ts.map +1 -1
  82. package/dist/esm/types/components/WheelObserver/index.d.ts +24 -0
  83. package/dist/esm/types/components/WheelObserver/index.d.ts.map +1 -0
  84. package/dist/esm/types/components/XYPad/Area.d.ts +13 -0
  85. package/dist/esm/types/components/XYPad/Area.d.ts.map +1 -0
  86. package/dist/esm/types/components/XYPad/Thumb.d.ts +19 -0
  87. package/dist/esm/types/components/XYPad/Thumb.d.ts.map +1 -0
  88. package/dist/esm/types/components/XYPad/index.d.ts +39 -0
  89. package/dist/esm/types/components/XYPad/index.d.ts.map +1 -0
  90. package/dist/esm/types/hooks/useCallbackRef.d.ts.map +1 -1
  91. package/dist/esm/types/index.d.ts +11 -0
  92. package/dist/esm/types/index.d.ts.map +1 -1
  93. package/dist/styles/index.css +241 -0
  94. package/package.json +2 -5
  95. package/src/components/AnimationCanvas/index.tsx +87 -57
  96. package/src/components/AnimationKnob/index.tsx +305 -0
  97. package/src/components/DragObserver/index.tsx +94 -0
  98. package/src/components/Knob/index.css +48 -0
  99. package/src/components/Knob/index.tsx +152 -165
  100. package/src/components/NumberInput/index.css +25 -0
  101. package/src/components/NumberInput/index.tsx +8 -41
  102. package/src/components/Piano/BlackKey.tsx +104 -98
  103. package/src/components/Piano/KeyLabel.tsx +18 -40
  104. package/src/components/Piano/WhiteKey.tsx +92 -87
  105. package/src/components/Piano/index.css +63 -0
  106. package/src/components/Piano/index.tsx +78 -61
  107. package/src/components/Piano/key.ts +4 -7
  108. package/src/components/Piano/keyboardShortcuts.ts +23 -4
  109. package/src/components/Slider/Scale.tsx +55 -0
  110. package/src/components/Slider/ScaleOption.tsx +103 -0
  111. package/src/components/Slider/Thumb.tsx +69 -76
  112. package/src/components/Slider/Track.tsx +37 -48
  113. package/src/components/Slider/context.tsx +66 -0
  114. package/src/components/Slider/index.css +76 -0
  115. package/src/components/Slider/index.tsx +256 -298
  116. package/src/components/WheelObserver/index.tsx +47 -0
  117. package/src/components/XYPad/Area.tsx +40 -0
  118. package/src/components/XYPad/Thumb.tsx +92 -0
  119. package/src/components/XYPad/index.css +24 -0
  120. package/src/components/XYPad/index.tsx +317 -0
  121. package/src/hooks/useCallbackRef.ts +1 -0
  122. package/src/hooks/useRefCallbackEvent.ts +1 -0
  123. package/src/index.ts +12 -0
  124. package/dist/cjs/types/components/Slider/type.d.ts +0 -7
  125. package/dist/cjs/types/components/Slider/type.d.ts.map +0 -1
  126. package/dist/cjs/types/system/pseudo.d.ts +0 -14
  127. package/dist/cjs/types/system/pseudo.d.ts.map +0 -1
  128. package/dist/esm/types/components/Slider/type.d.ts +0 -7
  129. package/dist/esm/types/components/Slider/type.d.ts.map +0 -1
  130. package/dist/esm/types/system/pseudo.d.ts +0 -14
  131. package/dist/esm/types/system/pseudo.d.ts.map +0 -1
  132. package/src/components/Slider/type.ts +0 -16
  133. package/src/system/pseudo.ts +0 -55
@@ -1,28 +1,36 @@
1
- import { css, CSSObject, Global } from '@emotion/react'
2
1
  import clsx from 'clsx'
2
+ import React, {
3
+ ComponentPropsWithoutRef,
4
+ CSSProperties,
5
+ forwardRef,
6
+ ReactElement,
7
+ useCallback,
8
+ useImperativeHandle,
9
+ useMemo,
10
+ useRef,
11
+ } from 'react'
12
+
3
13
  import {
4
- Direction,
5
- isHorizontal,
6
- isReversed,
7
- isVertical,
8
- parseScaleOrderList,
9
- ScaleOrderList,
10
- ScaleType,
11
- } from '@tremolo-ui/functions/Slider'
12
- import { clamp, normalizeValue, rawValue, stepValue, toFixed, InputEventOption, styleHelper } from '@tremolo-ui/functions'
13
- import React, { forwardRef, ReactElement, useCallback, useImperativeHandle, useRef } from 'react'
14
+ clamp,
15
+ normalizeValue,
16
+ rawValue,
17
+ stepValue,
18
+ toFixed,
19
+ InputEventOption,
20
+ styleHelper,
21
+ xor,
22
+ } from '@tremolo-ui/functions'
14
23
 
15
24
  import { useEventListener } from '../../hooks/useEventListener'
16
25
  import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
17
- import { UserActionPseudoProps } from '../../system/pseudo'
18
26
  import { addNoSelect, removeNoSelect } from '../_util'
19
27
 
20
- import { SliderThumb, SliderThumbMethods } from './Thumb'
21
- import { SliderTrack } from './Track'
22
- import { ScaleOption } from './type'
28
+ import { SliderValuesProvider } from './context'
29
+ import { Scale } from './Scale'
30
+ import { SliderThumb, SliderThumbMethods, SliderThumbProps } from './Thumb'
31
+ import { SliderTrack, SliderTrackProps } from './Track'
23
32
 
24
33
  export interface SliderProps {
25
- // TODO: property docs
26
34
  // required
27
35
  value: number
28
36
  min: number
@@ -31,24 +39,30 @@ export interface SliderProps {
31
39
  // optional
32
40
  step?: number
33
41
  skew?: number // TODO | SkewFunction
34
- direction?: Direction // TODO: vertical & reverse
35
- // TODO: scales component
36
- scale?: ['step', ScaleType] | [number, ScaleType] | ScaleOrderList[]
37
- scaleOption?: ScaleOption
42
+ vertical?: boolean
43
+ reverse?: boolean
38
44
  bodyNoSelect?: boolean
39
45
  /**
40
46
  * wheel control option
41
47
  */
42
- wheel?: InputEventOption
48
+ wheel?: InputEventOption | null
43
49
  /**
44
50
  * keyboard control option
45
51
  */
46
- keyboard?: InputEventOption
52
+ keyboard?: InputEventOption | null
53
+ /**
54
+ * Only the appearance will change.
55
+ * Please consider using with readonly.
56
+ */
47
57
  disabled?: boolean
58
+ /**
59
+ * Make the value unchangeable.
60
+ */
48
61
  readonly?: boolean
49
62
  className?: string
50
- style?: CSSObject
63
+ style?: CSSProperties
51
64
  onChange?: (value: number) => void
65
+ /** \<SliderThumb /> | \<SliderTrack /> */
52
66
  children?: ReactElement | ReactElement[]
53
67
  // ReactElement<typeof SliderThumb, typeof SliderTrack>[]
54
68
  }
@@ -58,305 +72,249 @@ export interface SliderMethods {
58
72
  blur: () => void
59
73
  }
60
74
 
75
+ type Props = SliderProps &
76
+ Omit<ComponentPropsWithoutRef<'div'>, keyof SliderProps>
77
+
61
78
  /**
62
79
  * Customizable slider
63
80
  */
64
- export const Slider = forwardRef<SliderMethods, SliderProps>(({
65
- value,
66
- min,
67
- max,
68
- step = 1,
69
- skew = 1,
70
- direction = 'right',
71
- scale,
72
- scaleOption,
73
- className,
74
- style,
75
- bodyNoSelect = true,
76
- wheel = ['raw', 1],
77
- keyboard = ['raw', 1],
78
- disabled = false,
79
- readonly = false,
80
- onChange,
81
- children,
82
- ...pseudo
83
- }: SliderProps & UserActionPseudoProps, ref) => {
84
- // -- state and ref ---
85
- const trackElementRef = useRef<HTMLDivElement>(null)
86
- const thumbRef = useRef<SliderThumbMethods>(null)
87
- const thumbDragged = useRef(false)
81
+ export const Slider = forwardRef<SliderMethods, Props>(
82
+ (
83
+ {
84
+ value,
85
+ min,
86
+ max,
87
+ step = 1,
88
+ skew = 1,
89
+ vertical = false,
90
+ reverse = false,
91
+ className,
92
+ style,
93
+ bodyNoSelect = true,
94
+ wheel = ['raw', 1],
95
+ keyboard = ['raw', 1],
96
+ disabled = false,
97
+ readonly = false,
98
+ onChange,
99
+ children,
100
+ ...props
101
+ }: Props,
102
+ ref,
103
+ ) => {
104
+ // -- state and ref ---
105
+ const trackElementRef = useRef<HTMLDivElement>(null)
106
+ const thumbRef = useRef<SliderThumbMethods>(null)
107
+ const thumbDragged = useRef(false)
88
108
 
89
- // --- interpret props ---
90
- const { _active, _focus, _hover } = pseudo
91
- const percent = toFixed(normalizeValue(value, min, max, skew) * 100)
92
- const percentRev = isReversed(direction) ? toFixed(100 - percent) : percent
93
- const calcPercent = (v: number) => {
94
- return toFixed(
95
- isReversed(direction)
96
- ? 100 - normalizeValue(v, min, max, skew) * 100
97
- : normalizeValue(v, min, max, skew) * 100
109
+ // --- interpret props ---
110
+ const p = toFixed(normalizeValue(value, min, max, skew) * 100)
111
+ const rev = toFixed(100 - p)
112
+ // NOTE
113
+ // normal -> normal (right)
114
+ // vertical -> rev (up)
115
+ // reverse -> rev (left)
116
+ // vertical & reverse -> normal (down)
117
+ const displayReversed = useMemo(
118
+ () => xor(vertical, reverse),
119
+ [vertical, reverse],
98
120
  )
99
- }
100
- if (wheel.length != 2) {
101
- throw new Error(`Slider.wheel expect ['normalized' | 'raw', number]`)
102
- }
103
- if (keyboard.length != 2) {
104
- throw new Error(`Slider.keyboard expect ['normalized' | 'raw', number]`)
105
- }
121
+ const percent = displayReversed ? rev : p
106
122
 
107
- const scalesList = parseScaleOrderList(scale, min, max, step)
108
- if (isReversed(direction)) scalesList.reverse()
109
-
110
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
- let trackProps: any, thumbProps: any
112
- if (children != undefined) {
113
- React.Children.forEach(children, (child) => {
114
- if (React.isValidElement(child)) {
115
- if (child.type == SliderThumb) {
116
- thumbProps = child.props
117
- } else if (child.type == SliderTrack) {
118
- trackProps = child.props
123
+ let trackProps: SliderTrackProps = {}
124
+ let thumbProps: SliderThumbProps = {}
125
+ let scaleComponent: ReactElement | undefined = undefined
126
+ if (children != undefined) {
127
+ React.Children.forEach(children, (child) => {
128
+ if (React.isValidElement(child)) {
129
+ if (child.type == SliderThumb) {
130
+ thumbProps = child.props as SliderThumbProps
131
+ } else if (child.type == SliderTrack) {
132
+ trackProps = child.props as SliderTrackProps
133
+ } else if (child.type == Scale) {
134
+ scaleComponent = child
135
+ } else {
136
+ throw new Error('only <SliderThumb> or <SliderTrack>')
137
+ }
119
138
  } else {
120
- throw new Error('only <SliderThumb> or <SliderTrack>')
139
+ throw new Error('children is an invalid element.')
121
140
  }
122
- } else {
123
- throw new Error('children is an invalid element.')
124
- }
125
- })
126
- }
127
-
128
- // --- internal functions ---
129
- const handleValue = (
130
- event: MouseEvent | React.PointerEvent<HTMLDivElement> | TouchEvent,
131
- ) => {
132
- if (!trackElementRef.current || !thumbDragged.current || !onChange || readonly) return
133
- const isTouch = event instanceof TouchEvent
134
- if (isTouch && event.cancelable) event.preventDefault()
135
- if (bodyNoSelect) addNoSelect()
136
- const {
137
- left: x1,
138
- top: y1,
139
- right: x2,
140
- bottom: y2,
141
- } = trackElementRef.current.getBoundingClientRect()
142
- const mouseX = isTouch ? event.touches[0].clientX : event.clientX
143
- const mouseY = isTouch ? event.touches[0].clientY : event.clientY
144
- const n = isHorizontal(direction)
145
- ? normalizeValue(mouseX, x1, x2)
146
- : normalizeValue(mouseY, y1, y2)
147
- const v = rawValue(isReversed(direction) ? 1 - n : n, min, max, skew)
148
- onChange(clamp(stepValue(v, step), min, max))
149
- }
150
-
151
- const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {
152
- if (!keyboard || !onChange || readonly) return
153
- const key = event.key
154
- if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(key)) {
155
- event.preventDefault()
156
- let x = (key == 'ArrowRight' || key == 'ArrowUp') ? keyboard[1] : -keyboard[1]
157
- if (direction == 'down' || direction == 'left') x *= -1
158
- let v
159
- if (keyboard[0] == 'normalized') {
160
- const n = normalizeValue(value, min, max, skew)
161
- v = rawValue(n + x, min, max, skew)
162
- } else {
163
- v = value + x
164
- }
165
- onChange(clamp(stepValue(v, step), min, max))
141
+ })
166
142
  }
167
- }, [value, min, max, step, skew, direction, keyboard, onChange, readonly])
168
-
169
- // --- hooks ---
170
- const touchMoveRefCallback = useRefCallbackEvent(
171
- 'touchmove',
172
- handleValue,
173
- { passive: false },
174
- [min, max, skew, step, direction, onChange, readonly],
175
- )
176
143
 
177
- const wheelRefCallback = useRefCallbackEvent(
178
- 'wheel',
179
- (event) => {
180
- if (!wheel || !onChange || readonly) return
181
- event.preventDefault()
182
- let x = event.deltaY > 0 ? wheel[1] : -wheel[1]
183
- if (isReversed(direction) || isHorizontal(direction)) x *= -1
184
- let v
185
- if (wheel[0] == 'normalized') {
186
- const n = normalizeValue(value, min, max, skew)
187
- v = rawValue(n + x, min, max, skew)
188
- } else {
189
- v = value + x
144
+ // --- internal functions ---
145
+ const handleValue = (
146
+ event: MouseEvent | React.PointerEvent<HTMLDivElement> | TouchEvent,
147
+ ) => {
148
+ if (
149
+ !trackElementRef.current ||
150
+ !thumbDragged.current ||
151
+ !onChange ||
152
+ readonly
153
+ ) {
154
+ return
190
155
  }
156
+ const isTouch = event instanceof TouchEvent
157
+ if (isTouch && event.cancelable) event.preventDefault()
158
+ if (bodyNoSelect) addNoSelect()
159
+ const {
160
+ left: x1,
161
+ top: y1,
162
+ right: x2,
163
+ bottom: y2,
164
+ } = trackElementRef.current.getBoundingClientRect()
165
+ const mouseX = isTouch ? event.touches[0].clientX : event.clientX
166
+ const mouseY = isTouch ? event.touches[0].clientY : event.clientY
167
+ const n = vertical
168
+ ? normalizeValue(mouseY, y1, y2)
169
+ : normalizeValue(mouseX, x1, x2)
170
+ const v = rawValue(displayReversed ? 1 - n : n, min, max, skew)
191
171
  onChange(clamp(stepValue(v, step), min, max))
192
- },
193
- {
194
- passive: false,
195
- },
196
- [wheel, value, min, max, skew, step],
197
- )
172
+ }
198
173
 
199
- useEventListener(window, 'mousemove', (event) => {
200
- handleValue(event)
201
- })
174
+ const handleKeyDown = useCallback(
175
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
176
+ if (!keyboard || !onChange || readonly) return
177
+ const key = event.key
178
+ if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(key)) {
179
+ event.preventDefault()
180
+ let x =
181
+ key == 'ArrowRight' || key == 'ArrowUp' ? keyboard[1] : -keyboard[1]
182
+ if (reverse) x *= -1
183
+ let v
184
+ if (keyboard[0] == 'normalized') {
185
+ const n = normalizeValue(value, min, max, skew)
186
+ v = rawValue(n + x, min, max, skew)
187
+ } else {
188
+ v = value + x
189
+ }
190
+ onChange(clamp(stepValue(v, step), min, max))
191
+ }
192
+ },
193
+ [value, min, max, step, skew, reverse, keyboard, onChange, readonly],
194
+ )
202
195
 
203
- useEventListener(window, 'mouseup', () => {
204
- thumbDragged.current = false
205
- if (bodyNoSelect) removeNoSelect()
206
- })
196
+ // --- hooks ---
197
+ const touchMoveRefCallback = useRefCallbackEvent(
198
+ 'touchmove',
199
+ handleValue,
200
+ { passive: false },
201
+ [min, max, skew, step, vertical, reverse, onChange, readonly],
202
+ )
207
203
 
208
- useImperativeHandle(ref, () => {
209
- return {
210
- focus() {
211
- thumbRef.current?.focus()
204
+ const wheelRefCallback = useRefCallbackEvent(
205
+ 'wheel',
206
+ (event) => {
207
+ if (!wheel || !onChange || readonly) return
208
+ event.preventDefault()
209
+ let x
210
+ if (!vertical && event.deltaX != 0) {
211
+ x = event.deltaX > 0 ? wheel[1] : -wheel[1]
212
+ } else {
213
+ if (event.deltaY == 0) return
214
+ x = event.deltaY > 0 ? -wheel[1] : wheel[1]
215
+ }
216
+ if (vertical && reverse) x *= -1
217
+ let v
218
+ if (wheel[0] == 'normalized') {
219
+ const n = normalizeValue(value, min, max, skew)
220
+ v = rawValue(n + x, min, max, skew)
221
+ } else {
222
+ v = value + x
223
+ }
224
+ onChange(clamp(stepValue(v, step), min, max))
212
225
  },
213
- blur() {
214
- thumbRef.current?.blur()
226
+ {
227
+ passive: false,
215
228
  },
216
- }
217
- }, [])
229
+ [wheel, value, min, max, skew, step],
230
+ )
231
+
232
+ useEventListener(window, 'mousemove', (event) => {
233
+ handleValue(event)
234
+ })
235
+
236
+ useEventListener(window, 'mouseup', () => {
237
+ thumbDragged.current = false
238
+ if (bodyNoSelect) removeNoSelect()
239
+ })
240
+
241
+ useImperativeHandle(ref, () => {
242
+ return {
243
+ focus() {
244
+ thumbRef.current?.focus()
245
+ },
246
+ blur() {
247
+ thumbRef.current?.blur()
248
+ },
249
+ }
250
+ }, [])
218
251
 
219
- return (
220
- <div
221
- className={clsx('tremolo-slider', className)}
222
- ref={(div) => {
223
- wheelRefCallback(div)
224
- touchMoveRefCallback(div)
225
- }}
226
- tabIndex={-1}
227
- role="slider"
228
- aria-valuenow={value}
229
- aria-valuemin={min}
230
- aria-valuemax={max}
231
- aria-orientation={isHorizontal(direction) ? 'horizontal' : 'vertical'}
232
- aria-disabled={disabled}
233
- aria-readonly={readonly}
234
- css={css({
235
- display: 'inline-block',
236
- margin: `calc(${styleHelper(thumbProps?.size ?? 22)} / 2)`, // half thumb size
237
- cursor: readonly ? 'not-allowed' : 'pointer',
238
- outline: 0,
239
- ...style,
240
- })}
241
- onPointerDown={(event) => {
242
- thumbDragged.current = true
243
- handleValue(event)
244
- }}
245
- onKeyDown={handleKeyDown}
246
- onFocus={thumbRef.current?.focus}
247
- onBlur={thumbRef.current?.blur}
248
- >
249
- <Global
250
- styles={{
251
- '.no-select': {
252
- userSelect: 'none',
253
- },
254
- }}
255
- />
256
- <div
257
- css={css({
258
- display: 'flex',
259
- flexDirection: isHorizontal(direction) ? 'column' : 'row',
260
- })}
252
+ return (
253
+ <SliderValuesProvider
254
+ min={min}
255
+ max={max}
256
+ step={step}
257
+ skew={skew}
258
+ vertical={vertical}
259
+ reverse={reverse}
260
+ disabled={disabled}
261
261
  >
262
262
  <div
263
- className="tremolo-slider-track-wrapper"
264
- ref={trackElementRef}
263
+ className={clsx('tremolo-slider', className)}
264
+ ref={(div) => {
265
+ wheelRefCallback(div)
266
+ touchMoveRefCallback(div)
267
+ }}
268
+ tabIndex={-1}
269
+ role="slider"
270
+ aria-valuenow={value}
271
+ aria-valuemin={min}
272
+ aria-valuemax={max}
273
+ aria-orientation={vertical ? 'vertical' : 'horizontal'}
274
+ aria-disabled={disabled}
275
+ aria-readonly={readonly}
276
+ style={{
277
+ margin: `calc(${styleHelper(thumbProps?.size ?? 22)} / 2)`, // half thumb size
278
+ ...style,
279
+ }}
280
+ onPointerDown={(event) => {
281
+ thumbDragged.current = true
282
+ handleValue(event)
283
+ }}
284
+ onKeyDown={handleKeyDown}
285
+ onFocus={thumbRef.current?.focus}
286
+ onBlur={thumbRef.current?.blur}
287
+ {...props}
265
288
  >
266
- <SliderTrack
267
- __direction={direction}
268
- __disabled={disabled}
269
- __percent={percent}
270
- __thumb={
271
- <SliderThumb
272
- ref={thumbRef}
273
- __disabled={disabled}
274
- __css={{
275
- top: isHorizontal(direction) ? '50%' : `${percentRev}%`,
276
- left: isHorizontal(direction) ? `${percentRev}%` : '50%',
277
- }}
278
- {...thumbProps}
279
- />
280
- }
281
- {...trackProps}
282
- />
283
- </div>
284
- {/* TODO: scales component */}
285
- {scale && (
286
289
  <div
287
- className="tremolo-slider-scale-wrapper"
288
- css={css({
289
- display: 'block',
290
- position: 'relative',
291
- marginLeft: isVertical(direction)
292
- ? (scaleOption?.gap ??
293
- `calc((22px - ${styleHelper(trackProps?.thickness ?? 10)}) / 2)`)
294
- : undefined,
295
- marginTop: isHorizontal(direction)
296
- ? (scaleOption?.gap ??
297
- `calc((22px - ${styleHelper(trackProps?.thickness ?? 10)}) / 2)`)
298
- : undefined,
299
- ...scaleOption?.style,
300
- // zIndex: 10,
301
- })}
290
+ style={{
291
+ display: 'flex',
292
+ flexDirection: vertical ? 'row' : 'column',
293
+ }}
302
294
  >
303
- {scalesList.map((item, index) => (
304
- <div
305
- key={index}
306
- css={css({
307
- display: 'flex',
308
- flexDirection: isHorizontal(direction) ? 'column' : 'row',
309
- justifyContent: 'center',
310
- alignItems: 'center',
311
- position: 'absolute',
312
- left: isHorizontal(direction) ? `${calcPercent(item.at)}%` : undefined,
313
- top: isVertical(direction) ? `${calcPercent(item.at)}%` : undefined,
314
- translate: isHorizontal(direction) ? '-50% 0' : '0 -50%',
315
- zIndex: 10,
316
- })}
317
- >
318
- {(item.type ?? scaleOption?.defaultType ?? 'mark-number') !=
319
- 'number' && (
320
- <div
321
- className="tremolo-slider-scale-mark"
322
- css={css({
323
- width: isHorizontal(direction)
324
- ? (item.style?.thickness ?? 1)
325
- : (item.style?.length ?? '0.5rem'),
326
- height: isVertical(direction)
327
- ? (item.style?.thickness ?? 1)
328
- : (item.style?.length ?? '0.5rem'),
329
- backgroundColor:
330
- item.style?.markColor ??
331
- scaleOption?.markColor ??
332
- '#222',
333
- marginBottom: isHorizontal(direction) ? 2 : undefined,
334
- marginRight: isVertical(direction) ? 2 : undefined,
335
- })}
336
- ></div>
337
- )}
338
- {(item.type ?? scaleOption?.defaultType ?? 'mark-number') !=
339
- 'mark' && (
340
- <div
341
- className="tremolo-slider-scale-number"
342
- css={css({
343
- color: item.style?.labelColor ?? scaleOption?.labelColor,
344
- width: scaleOption?.labelWidth,
345
- textAlign: 'right',
346
- })}
347
- >
348
- {item.text ?? item.at}
349
- </div>
350
- )}
351
- </div>
352
- ))}
295
+ <div className="tremolo-slider-track-wrapper" ref={trackElementRef}>
296
+ <SliderTrack
297
+ __percent={percent}
298
+ __thumb={
299
+ <SliderThumb
300
+ ref={thumbRef}
301
+ __percent={percent}
302
+ {...thumbProps}
303
+ />
304
+ }
305
+ {...trackProps}
306
+ />
307
+ </div>
308
+ {scaleComponent}
353
309
  </div>
354
- )}
355
- </div>
356
- </div>
357
- )
358
- })
310
+ </div>
311
+ </SliderValuesProvider>
312
+ )
313
+ },
314
+ )
359
315
 
316
+ export * from './context'
360
317
  export * from './Thumb'
361
318
  export * from './Track'
362
- export * from './type'
319
+ export * from './Scale'
320
+ export * from './ScaleOption'
@@ -0,0 +1,47 @@
1
+ import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react'
2
+
3
+ import { useRefCallbackEvent } from '../../hooks/useRefCallbackEvent'
4
+
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
+ interface Props<T extends ElementType> {
18
+ /**
19
+ * React.ElementType
20
+ * @default div
21
+ */
22
+ as?: T
23
+
24
+ children?: ReactNode
25
+ onWheel?: (event: WheelEvent) => void
26
+ }
27
+
28
+ export function WheelObserver<T extends ElementType = 'div'>(
29
+ props: Props<T> & Omit<ComponentPropsWithoutRef<T>, keyof Props<T>>,
30
+ ) {
31
+ const { children, onWheel, as: Component = 'div', ...attributes } = props
32
+
33
+ const wheelRefCallback = useRefCallbackEvent(
34
+ 'wheel',
35
+ (event) => {
36
+ if (onWheel) onWheel(event)
37
+ },
38
+ { passive: false },
39
+ [onWheel],
40
+ )
41
+
42
+ return (
43
+ <Component ref={wheelRefCallback} {...attributes}>
44
+ {children}
45
+ </Component>
46
+ )
47
+ }