@tremolo-ui/react 0.0.9 → 0.0.11

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 (148) hide show
  1. package/dist/cjs/index.js +434 -271
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/AnimationCanvas/index.d.ts +9 -4
  4. package/dist/cjs/types/components/AnimationCanvas/index.d.ts.map +1 -1
  5. package/dist/cjs/types/components/AnimationKnob/index.d.ts +2 -0
  6. package/dist/cjs/types/components/AnimationKnob/index.d.ts.map +1 -1
  7. package/dist/cjs/types/components/DragObserver/index.d.ts +3 -1
  8. package/dist/cjs/types/components/DragObserver/index.d.ts.map +1 -1
  9. package/dist/cjs/types/components/Knob/index.d.ts +2 -0
  10. package/dist/cjs/types/components/Knob/index.d.ts.map +1 -1
  11. package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts +7 -0
  12. package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts.map +1 -0
  13. package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts +7 -0
  14. package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts.map +1 -0
  15. package/dist/cjs/types/components/NumberInput/Stepper.d.ts +8 -0
  16. package/dist/cjs/types/components/NumberInput/Stepper.d.ts.map +1 -0
  17. package/dist/cjs/types/components/NumberInput/context.d.ts +27 -0
  18. package/dist/cjs/types/components/NumberInput/context.d.ts.map +1 -0
  19. package/dist/cjs/types/components/NumberInput/index.d.ts +31 -16
  20. package/dist/cjs/types/components/NumberInput/index.d.ts.map +1 -1
  21. package/dist/cjs/types/components/Piano/BlackKey.d.ts +1 -0
  22. package/dist/cjs/types/components/Piano/BlackKey.d.ts.map +1 -1
  23. package/dist/cjs/types/components/Piano/KeyLabel.d.ts +2 -0
  24. package/dist/cjs/types/components/Piano/KeyLabel.d.ts.map +1 -1
  25. package/dist/cjs/types/components/Piano/WhiteKey.d.ts +2 -0
  26. package/dist/cjs/types/components/Piano/WhiteKey.d.ts.map +1 -1
  27. package/dist/cjs/types/components/Piano/index.d.ts +6 -4
  28. package/dist/cjs/types/components/Piano/index.d.ts.map +1 -1
  29. package/dist/cjs/types/components/Piano/key.d.ts +8 -2
  30. package/dist/cjs/types/components/Piano/key.d.ts.map +1 -1
  31. package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts +2 -0
  32. package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts.map +1 -1
  33. package/dist/cjs/types/components/Slider/Scale.d.ts +2 -0
  34. package/dist/cjs/types/components/Slider/Scale.d.ts.map +1 -1
  35. package/dist/cjs/types/components/Slider/ScaleOption.d.ts +2 -0
  36. package/dist/cjs/types/components/Slider/ScaleOption.d.ts.map +1 -1
  37. package/dist/cjs/types/components/Slider/Thumb.d.ts +4 -0
  38. package/dist/cjs/types/components/Slider/Thumb.d.ts.map +1 -1
  39. package/dist/cjs/types/components/Slider/Track.d.ts +4 -1
  40. package/dist/cjs/types/components/Slider/Track.d.ts.map +1 -1
  41. package/dist/cjs/types/components/Slider/context.d.ts +7 -12
  42. package/dist/cjs/types/components/Slider/context.d.ts.map +1 -1
  43. package/dist/cjs/types/components/Slider/index.d.ts +8 -5
  44. package/dist/cjs/types/components/Slider/index.d.ts.map +1 -1
  45. package/dist/cjs/types/components/WheelObserver/index.d.ts +1 -0
  46. package/dist/cjs/types/components/WheelObserver/index.d.ts.map +1 -1
  47. package/dist/cjs/types/components/XYPad/Area.d.ts +2 -0
  48. package/dist/cjs/types/components/XYPad/Area.d.ts.map +1 -1
  49. package/dist/cjs/types/components/XYPad/Thumb.d.ts +3 -0
  50. package/dist/cjs/types/components/XYPad/Thumb.d.ts.map +1 -1
  51. package/dist/cjs/types/components/XYPad/index.d.ts +4 -0
  52. package/dist/cjs/types/components/XYPad/index.d.ts.map +1 -1
  53. package/dist/cjs/types/hooks/useAnimationFrame.d.ts +3 -0
  54. package/dist/cjs/types/hooks/useAnimationFrame.d.ts.map +1 -0
  55. package/dist/cjs/types/hooks/useDrag.d.ts +15 -0
  56. package/dist/cjs/types/hooks/useDrag.d.ts.map +1 -0
  57. package/dist/cjs/types/hooks/useRefCallbackEvent.d.ts +1 -4
  58. package/dist/cjs/types/hooks/useRefCallbackEvent.d.ts.map +1 -1
  59. package/dist/esm/index.js +432 -262
  60. package/dist/esm/index.js.map +1 -1
  61. package/dist/esm/types/components/AnimationCanvas/index.d.ts +9 -4
  62. package/dist/esm/types/components/AnimationCanvas/index.d.ts.map +1 -1
  63. package/dist/esm/types/components/AnimationKnob/index.d.ts +2 -0
  64. package/dist/esm/types/components/AnimationKnob/index.d.ts.map +1 -1
  65. package/dist/esm/types/components/DragObserver/index.d.ts +3 -1
  66. package/dist/esm/types/components/DragObserver/index.d.ts.map +1 -1
  67. package/dist/esm/types/components/Knob/index.d.ts +2 -0
  68. package/dist/esm/types/components/Knob/index.d.ts.map +1 -1
  69. package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts +7 -0
  70. package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts.map +1 -0
  71. package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts +7 -0
  72. package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts.map +1 -0
  73. package/dist/esm/types/components/NumberInput/Stepper.d.ts +8 -0
  74. package/dist/esm/types/components/NumberInput/Stepper.d.ts.map +1 -0
  75. package/dist/esm/types/components/NumberInput/context.d.ts +27 -0
  76. package/dist/esm/types/components/NumberInput/context.d.ts.map +1 -0
  77. package/dist/esm/types/components/NumberInput/index.d.ts +31 -16
  78. package/dist/esm/types/components/NumberInput/index.d.ts.map +1 -1
  79. package/dist/esm/types/components/Piano/BlackKey.d.ts +1 -0
  80. package/dist/esm/types/components/Piano/BlackKey.d.ts.map +1 -1
  81. package/dist/esm/types/components/Piano/KeyLabel.d.ts +2 -0
  82. package/dist/esm/types/components/Piano/KeyLabel.d.ts.map +1 -1
  83. package/dist/esm/types/components/Piano/WhiteKey.d.ts +2 -0
  84. package/dist/esm/types/components/Piano/WhiteKey.d.ts.map +1 -1
  85. package/dist/esm/types/components/Piano/index.d.ts +6 -4
  86. package/dist/esm/types/components/Piano/index.d.ts.map +1 -1
  87. package/dist/esm/types/components/Piano/key.d.ts +8 -2
  88. package/dist/esm/types/components/Piano/key.d.ts.map +1 -1
  89. package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts +2 -0
  90. package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts.map +1 -1
  91. package/dist/esm/types/components/Slider/Scale.d.ts +2 -0
  92. package/dist/esm/types/components/Slider/Scale.d.ts.map +1 -1
  93. package/dist/esm/types/components/Slider/ScaleOption.d.ts +2 -0
  94. package/dist/esm/types/components/Slider/ScaleOption.d.ts.map +1 -1
  95. package/dist/esm/types/components/Slider/Thumb.d.ts +4 -0
  96. package/dist/esm/types/components/Slider/Thumb.d.ts.map +1 -1
  97. package/dist/esm/types/components/Slider/Track.d.ts +4 -1
  98. package/dist/esm/types/components/Slider/Track.d.ts.map +1 -1
  99. package/dist/esm/types/components/Slider/context.d.ts +7 -12
  100. package/dist/esm/types/components/Slider/context.d.ts.map +1 -1
  101. package/dist/esm/types/components/Slider/index.d.ts +8 -5
  102. package/dist/esm/types/components/Slider/index.d.ts.map +1 -1
  103. package/dist/esm/types/components/WheelObserver/index.d.ts +1 -0
  104. package/dist/esm/types/components/WheelObserver/index.d.ts.map +1 -1
  105. package/dist/esm/types/components/XYPad/Area.d.ts +2 -0
  106. package/dist/esm/types/components/XYPad/Area.d.ts.map +1 -1
  107. package/dist/esm/types/components/XYPad/Thumb.d.ts +3 -0
  108. package/dist/esm/types/components/XYPad/Thumb.d.ts.map +1 -1
  109. package/dist/esm/types/components/XYPad/index.d.ts +4 -0
  110. package/dist/esm/types/components/XYPad/index.d.ts.map +1 -1
  111. package/dist/esm/types/hooks/useAnimationFrame.d.ts +3 -0
  112. package/dist/esm/types/hooks/useAnimationFrame.d.ts.map +1 -0
  113. package/dist/esm/types/hooks/useDrag.d.ts +15 -0
  114. package/dist/esm/types/hooks/useDrag.d.ts.map +1 -0
  115. package/dist/esm/types/hooks/useRefCallbackEvent.d.ts +1 -4
  116. package/dist/esm/types/hooks/useRefCallbackEvent.d.ts.map +1 -1
  117. package/dist/styles/index.css +110 -14
  118. package/package.json +41 -37
  119. package/src/components/AnimationCanvas/index.tsx +14 -5
  120. package/src/components/AnimationKnob/index.tsx +24 -42
  121. package/src/components/DragObserver/index.tsx +14 -52
  122. package/src/components/Knob/index.tsx +23 -40
  123. package/src/components/NumberInput/DecrementStepper.tsx +52 -0
  124. package/src/components/NumberInput/IncrementStepper.tsx +52 -0
  125. package/src/components/NumberInput/Stepper.tsx +25 -0
  126. package/src/components/NumberInput/context.tsx +150 -0
  127. package/src/components/NumberInput/index.css +102 -14
  128. package/src/components/NumberInput/index.tsx +164 -55
  129. package/src/components/Piano/BlackKey.tsx +13 -10
  130. package/src/components/Piano/KeyLabel.tsx +2 -0
  131. package/src/components/Piano/WhiteKey.tsx +14 -10
  132. package/src/components/Piano/index.tsx +14 -12
  133. package/src/components/Piano/key.ts +8 -2
  134. package/src/components/Piano/keyboardShortcuts.ts +2 -0
  135. package/src/components/Slider/Scale.tsx +8 -6
  136. package/src/components/Slider/ScaleOption.tsx +8 -6
  137. package/src/components/Slider/Thumb.tsx +9 -3
  138. package/src/components/Slider/Track.tsx +25 -15
  139. package/src/components/Slider/context.tsx +50 -51
  140. package/src/components/Slider/index.css +8 -0
  141. package/src/components/Slider/index.tsx +20 -10
  142. package/src/components/WheelObserver/index.tsx +1 -0
  143. package/src/components/XYPad/Area.tsx +2 -0
  144. package/src/components/XYPad/Thumb.tsx +3 -0
  145. package/src/components/XYPad/index.tsx +6 -2
  146. package/src/hooks/useAnimationFrame.ts +18 -0
  147. package/src/hooks/useDrag.ts +92 -0
  148. package/src/hooks/useRefCallbackEvent.ts +4 -7
@@ -1,22 +1,35 @@
1
1
  import clsx from 'clsx'
2
- import { ChangeEvent, ComponentPropsWithoutRef, useState } from 'react'
2
+ import { ComponentPropsWithoutRef, ReactNode, useMemo, useState } from 'react'
3
3
 
4
- import { clamp } from '@tremolo-ui/functions'
4
+ import { InputEventOption } from '@tremolo-ui/functions'
5
5
  import { parseValue, Units } from '@tremolo-ui/functions/NumberInput'
6
6
 
7
+ import {
8
+ NumberInputProvider,
9
+ safeClamp,
10
+ useNumberInputContext,
11
+ } from './context'
12
+
13
+ /*
14
+ TODO
15
+ - onBlurでは、parse + (clamp) + formatして返す
16
+ - numberMode: focusが当たった時だけ、parse + type="number"にする
17
+ */
18
+
19
+ /** @category NumberInput */
7
20
  export interface NumberInputProps {
8
21
  value: number | string
9
22
 
23
+ /**
24
+ * @default Number.MIN_SAFE_INTEGER
25
+ */
10
26
  min?: number
27
+ /**
28
+ * @default Number.MAX_SAFE_INTEGER
29
+ */
11
30
  max?: number
12
31
  step?: number
13
- defaultValue?: number
14
-
15
- // wrapping html input
16
- disabled?: boolean
17
- placeholder?: string
18
- readOnly?: boolean
19
- tabIndex?: number
32
+ defaultValue?: number | string
20
33
 
21
34
  /**
22
35
  * @example
@@ -26,11 +39,6 @@ export interface NumberInputProps {
26
39
  */
27
40
  units?: string | Units
28
41
 
29
- /**
30
- * NOTE: If units is specified, it is forced to `false`
31
- */
32
- typeNumber?: boolean
33
-
34
42
  /**
35
43
  * Digits for rounding numbers.
36
44
  * @example
@@ -41,76 +49,94 @@ export interface NumberInputProps {
41
49
  * value=100 -> 100Hz, value=1600 -> 1.60Hz
42
50
  */
43
51
  digit?: number
52
+
44
53
  selectWithFocus?: 'all' | 'number' | 'none'
45
- blurWithEnter?: boolean
46
- className?: string
47
- onChange?: (value: number, event: ChangeEvent<HTMLInputElement>) => void
54
+ blurOnEnter?: boolean
55
+ keepWithinRange?: boolean
56
+ clampValueOnBlur?: boolean
57
+
58
+ // TODO
59
+ /**
60
+ * wheel control option
61
+ */
62
+ wheel?: InputEventOption | null
63
+ /**
64
+ * keyboard control option
65
+ */
66
+ keyboard?: InputEventOption | null
67
+
68
+ wrapperClassName?: string
69
+
70
+ onChange?: (value: number, text: string) => void
48
71
  onFocus?: (
49
72
  value: number,
73
+ text: string,
50
74
  event: React.FocusEvent<HTMLInputElement, Element>,
51
75
  ) => void
52
76
  onBlur?: (
53
77
  value: number,
78
+ text: string,
54
79
  event: React.FocusEvent<HTMLInputElement, Element>,
55
80
  ) => void
81
+
82
+ children?: ReactNode
56
83
  }
57
84
 
58
- /**
59
- * Input with some useful functions for entering numerical values.
60
- */
61
- export function NumberInput({
62
- value,
85
+ function InternalInput({
63
86
  min,
64
87
  max,
65
88
  step,
66
- defaultValue = min,
89
+ defaultValue,
67
90
  units,
68
- disabled = false,
69
- placeholder,
70
- readOnly,
71
- tabIndex,
72
- typeNumber = false,
91
+ disabled,
73
92
  digit,
74
- selectWithFocus = 'none',
75
- blurWithEnter = true,
93
+ selectWithFocus,
94
+ blurOnEnter,
95
+ keepWithinRange,
96
+ clampValueOnBlur,
97
+ wheel,
98
+ keyboard,
99
+ wrapperClassName,
76
100
  className,
77
101
  onChange,
78
102
  onFocus,
79
103
  onBlur,
104
+ children: _children,
80
105
  ...props
81
- }: NumberInputProps &
106
+ }: Omit<NumberInputProps, 'value'> &
82
107
  Omit<ComponentPropsWithoutRef<'input'>, keyof NumberInputProps | 'type'>) {
83
- const [showValue, setShowValue] = useState(
84
- parseValue(String(value), units, digit, defaultValue).formatValue,
85
- )
86
- const calculatedTypeNumber = units ? false : typeNumber
108
+ const [editing, setEditing] = useState(false)
109
+ const value = useNumberInputContext((s) => s.value)
110
+ const valueAsNumber = useNumberInputContext((s) => s.valueAsNumber)
111
+ const change = useNumberInputContext((s) => s.change)
112
+
113
+ const error = useMemo(() => {
114
+ if (editing) return false
115
+ const v = valueAsNumber
116
+ return (min ?? -Infinity) > v || v > (max ?? Infinity)
117
+ }, [editing, max, min, valueAsNumber])
87
118
 
88
119
  return (
89
120
  <input
90
121
  className={clsx('tremolo-number-input', className)}
91
- value={showValue}
122
+ value={value}
92
123
  min={min}
93
124
  max={max}
94
125
  step={step}
95
- type={calculatedTypeNumber ? 'number' : 'text'}
96
- spellCheck={'false'}
126
+ type="text"
97
127
  disabled={disabled}
98
- placeholder={placeholder}
99
- readOnly={readOnly}
100
- tabIndex={tabIndex}
128
+ tabIndex={-1}
129
+ data-error={error}
101
130
  onChange={(event) => {
102
131
  const v = event.currentTarget.value
103
- setShowValue(v)
104
- const newValue = clamp(
105
- parseValue(v, units, digit, defaultValue).rawValue,
106
- min ?? -Infinity,
107
- max ?? Infinity,
108
- )
109
- if (onChange) onChange(newValue, event)
132
+ change(v)
133
+ const valueAsNumber = parseValue(v, units, digit).rawValue
134
+ onChange?.(valueAsNumber, v)
110
135
  }}
111
136
  onFocus={(event) => {
137
+ setEditing(true)
112
138
  const v = event.currentTarget.value
113
- const parsed = parseValue(v, units, digit, defaultValue)
139
+ const parsed = parseValue(v, units, digit)
114
140
  if (selectWithFocus == 'all') {
115
141
  event.currentTarget.setSelectionRange(0, v.length)
116
142
  } else if (selectWithFocus == 'number') {
@@ -119,17 +145,24 @@ export function NumberInput({
119
145
  parsed.formatValue.length - parsed.unit.length,
120
146
  )
121
147
  }
122
- if (onFocus) onFocus(parsed.rawValue, event)
148
+ onFocus?.(parsed.rawValue, parsed.formatValue, event)
123
149
  }}
124
150
  onBlur={(event) => {
151
+ setEditing(false)
125
152
  // update value
126
- const v = event.currentTarget.value
127
- const parsed = parseValue(v, units, digit, defaultValue)
128
- setShowValue(parsed.formatValue)
129
- if (onBlur) onBlur(parsed.rawValue, event)
153
+ let v = parseValue(event.currentTarget.value, units, digit).rawValue
154
+ if (clampValueOnBlur) {
155
+ v = safeClamp(v, min, max)
156
+ }
157
+ const parsed = parseValue(String(v), units, digit)
158
+ change(parsed.formatValue)
159
+ if (clampValueOnBlur) {
160
+ onChange?.(parsed.rawValue, parsed.formatValue)
161
+ }
162
+ onBlur?.(parsed.rawValue, parsed.formatValue, event)
130
163
  }}
131
164
  onKeyDown={(event) => {
132
- if (blurWithEnter && event.key == 'Enter') {
165
+ if (blurOnEnter && event.key == 'Enter') {
133
166
  event.currentTarget.blur()
134
167
  }
135
168
  }}
@@ -137,3 +170,79 @@ export function NumberInput({
137
170
  />
138
171
  )
139
172
  }
173
+
174
+ /**
175
+ * Input with some useful functions for entering numerical values.
176
+ * @category NumberInput
177
+ */
178
+ export function NumberInput({
179
+ value,
180
+ min,
181
+ max,
182
+ step = 1,
183
+ defaultValue,
184
+ units,
185
+ disabled = false,
186
+ digit,
187
+ selectWithFocus = 'none',
188
+ blurOnEnter = true,
189
+ keepWithinRange = true,
190
+ clampValueOnBlur = true,
191
+ wheel = ['raw', 1],
192
+ keyboard = ['raw', 1],
193
+ wrapperClassName,
194
+ className,
195
+ onChange,
196
+ onFocus,
197
+ onBlur,
198
+ children,
199
+ ...props
200
+ }: NumberInputProps &
201
+ Omit<ComponentPropsWithoutRef<'input'>, keyof NumberInputProps | 'type'>) {
202
+ return (
203
+ <NumberInputProvider
204
+ value={String(value)}
205
+ min={min}
206
+ max={max}
207
+ step={step}
208
+ units={units}
209
+ digit={digit}
210
+ keepWithinRange={keepWithinRange}
211
+ onChange={onChange}
212
+ >
213
+ <div
214
+ className={clsx('tremolo-number-input-wrapper', wrapperClassName)}
215
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
216
+ tabIndex={0}
217
+ data-stepper={!!children}
218
+ >
219
+ <InternalInput
220
+ min={min}
221
+ max={max}
222
+ step={step}
223
+ defaultValue={defaultValue}
224
+ units={units}
225
+ disabled={disabled}
226
+ digit={digit}
227
+ selectWithFocus={selectWithFocus}
228
+ blurOnEnter={blurOnEnter}
229
+ keepWithinRange={keepWithinRange}
230
+ clampValueOnBlur={clampValueOnBlur}
231
+ wheel={wheel}
232
+ keyboard={keyboard}
233
+ wrapperClassName={wrapperClassName}
234
+ className={className}
235
+ onChange={onChange}
236
+ onFocus={onFocus}
237
+ onBlur={onBlur}
238
+ {...props}
239
+ />
240
+ {children}
241
+ </div>
242
+ </NumberInputProvider>
243
+ )
244
+ }
245
+
246
+ export * from './Stepper'
247
+ export * from './IncrementStepper'
248
+ export * from './DecrementStepper'
@@ -12,6 +12,7 @@ import { defaultWhiteKeyWidth } from './WhiteKey'
12
12
 
13
13
  type Props = KeyProps & Omit<ComponentPropsWithoutRef<'div'>, keyof KeyProps>
14
14
 
15
+ /** @category Piano */
15
16
  export const BlackKey = forwardRef<KeyMethods, Props>(
16
17
  (
17
18
  {
@@ -27,8 +28,8 @@ export const BlackKey = forwardRef<KeyMethods, Props>(
27
28
  __index,
28
29
  __fill,
29
30
  __width,
30
- __playNote,
31
- __stopNote,
31
+ __onPlayNote,
32
+ __onStopNote,
32
33
  __label,
33
34
  ...props
34
35
  }: Props,
@@ -41,17 +42,17 @@ export const BlackKey = forwardRef<KeyMethods, Props>(
41
42
  play() {
42
43
  if (__disabled) return
43
44
  setPlayed(true)
44
- if (__playNote) __playNote(__note!)
45
+ if (__onPlayNote) __onPlayNote(__note!)
45
46
  },
46
47
  stop() {
47
48
  setPlayed(false)
48
- if (__stopNote) __stopNote(__note!)
49
+ if (__onStopNote) __onStopNote(__note!)
49
50
  },
50
51
  played() {
51
52
  return played
52
53
  },
53
54
  }
54
- }, [__disabled, __note, __playNote, __stopNote, played])
55
+ }, [__disabled, __note, __onPlayNote, __onStopNote, played])
55
56
 
56
57
  let keyLabelProps: KeyLabelProps = {}
57
58
  if (children != undefined) {
@@ -82,24 +83,26 @@ export const BlackKey = forwardRef<KeyMethods, Props>(
82
83
  onPointerDown={() => {
83
84
  if (__disabled) return
84
85
  setPlayed(true)
85
- if (__playNote) __playNote(__note!)
86
+ if (__onPlayNote) __onPlayNote(__note!)
86
87
  }}
87
88
  onPointerEnter={() => {
88
89
  if (__disabled) return
89
90
  if (__glissando) {
90
91
  setPlayed(true)
91
- if (__playNote) __playNote(__note!)
92
+ if (__onPlayNote) __onPlayNote(__note!)
92
93
  }
93
94
  }}
94
95
  onPointerUp={() => {
95
96
  if (__disabled) return
96
97
  setPlayed(false)
97
- if (__stopNote) __stopNote(__note!)
98
+ if (__onStopNote) __onStopNote(__note!)
98
99
  }}
99
100
  onPointerLeave={() => {
100
101
  if (__disabled) return
101
- setPlayed(false)
102
- if (__stopNote) __stopNote(__note!)
102
+ if (__glissando) {
103
+ setPlayed(false)
104
+ if (__onStopNote) __onStopNote(__note!)
105
+ }
103
106
  }}
104
107
  {...props}
105
108
  >
@@ -1,6 +1,7 @@
1
1
  import clsx from 'clsx'
2
2
  import { ComponentPropsWithoutRef, CSSProperties, ReactNode } from 'react'
3
3
 
4
+ /** @category Piano */
4
5
  export interface KeyLabelProps {
5
6
  /**
6
7
  * override Piano.label
@@ -18,6 +19,7 @@ export interface KeyLabelProps {
18
19
  __label?: (note: number, index: number) => ReactNode
19
20
  }
20
21
 
22
+ /** @category Piano */
21
23
  export function KeyLabel({
22
24
  label,
23
25
  className,
@@ -11,8 +11,10 @@ import { KeyLabel, KeyLabelProps } from './KeyLabel'
11
11
 
12
12
  type Props = KeyProps & Omit<ComponentPropsWithoutRef<'div'>, keyof KeyProps>
13
13
 
14
+ /** @category Piano */
14
15
  export const defaultWhiteKeyWidth = 40
15
16
 
17
+ /** @category Piano */
16
18
  export const WhiteKey = forwardRef<KeyMethods, Props>(
17
19
  (
18
20
  {
@@ -28,8 +30,8 @@ export const WhiteKey = forwardRef<KeyMethods, Props>(
28
30
  __index,
29
31
  __fill,
30
32
  __width,
31
- __playNote,
32
- __stopNote,
33
+ __onPlayNote,
34
+ __onStopNote,
33
35
  __label,
34
36
  ...props
35
37
  }: Props,
@@ -42,17 +44,17 @@ export const WhiteKey = forwardRef<KeyMethods, Props>(
42
44
  play() {
43
45
  if (__disabled) return
44
46
  setPlayed(true)
45
- if (__playNote) __playNote(__note!)
47
+ if (__onPlayNote) __onPlayNote(__note!)
46
48
  },
47
49
  stop() {
48
50
  setPlayed(false)
49
- if (__stopNote) __stopNote(__note!)
51
+ if (__onStopNote) __onStopNote(__note!)
50
52
  },
51
53
  played() {
52
54
  return played
53
55
  },
54
56
  }
55
- }, [__disabled, __note, __playNote, __stopNote, played])
57
+ }, [__disabled, __note, __onPlayNote, __onStopNote, played])
56
58
 
57
59
  let keyLabelProps: KeyLabelProps = {}
58
60
  if (children != undefined) {
@@ -83,24 +85,26 @@ export const WhiteKey = forwardRef<KeyMethods, Props>(
83
85
  onPointerDown={() => {
84
86
  if (__disabled) return
85
87
  setPlayed(true)
86
- if (__playNote) __playNote(__note!)
88
+ if (__onPlayNote) __onPlayNote(__note!)
87
89
  }}
88
90
  onPointerEnter={() => {
89
91
  if (__disabled) return
90
92
  if (__glissando) {
91
93
  setPlayed(true)
92
- if (__playNote) __playNote(__note!)
94
+ if (__onPlayNote) __onPlayNote(__note!)
93
95
  }
94
96
  }}
95
97
  onPointerUp={() => {
96
98
  if (__disabled) return
97
99
  setPlayed(false)
98
- if (__stopNote) __stopNote(__note!)
100
+ if (__onStopNote) __onStopNote(__note!)
99
101
  }}
100
102
  onPointerLeave={() => {
101
103
  if (__disabled) return
102
- setPlayed(false)
103
- if (__stopNote) __stopNote(__note!)
104
+ if (__glissando) {
105
+ setPlayed(false)
106
+ if (__onStopNote) __onStopNote(__note!)
107
+ }
104
108
  }}
105
109
  {...props}
106
110
  >
@@ -33,6 +33,7 @@ import { defaultWhiteKeyWidth, WhiteKey } from './WhiteKey'
33
33
  * - add scale highlight
34
34
  */
35
35
 
36
+ /** @category Piano */
36
37
  export interface PianoProps {
37
38
  // required
38
39
  noteRange: { first: number; last: number }
@@ -47,8 +48,8 @@ export interface PianoProps {
47
48
 
48
49
  style?: CSSProperties
49
50
 
50
- playNote?: (noteNumber: number) => void
51
- stopNote?: (noteNumber: number) => void
51
+ onPlayNote?: (noteNumber: number) => void
52
+ onStopNote?: (noteNumber: number) => void
52
53
 
53
54
  label?: (note: number, index: number) => ReactNode
54
55
 
@@ -58,6 +59,7 @@ export interface PianoProps {
58
59
  children?: ReactElement | ReactElement[]
59
60
  }
60
61
 
62
+ /** @category Piano */
61
63
  export function Piano({
62
64
  noteRange,
63
65
  glissando = true,
@@ -67,8 +69,8 @@ export function Piano({
67
69
  height = fill ? '100%' : 160,
68
70
  style,
69
71
  className,
70
- playNote,
71
- stopNote,
72
+ onPlayNote,
73
+ onStopNote,
72
74
  label,
73
75
  children,
74
76
  ...props
@@ -76,7 +78,7 @@ export function Piano({
76
78
  // -- state and ref ---
77
79
  const [pressed, setPressed] = useState(false)
78
80
  const [whiteNoteWidth, setWhiteNoteWidth] = useState(-1)
79
- const keyRefs = useRef<RefObject<KeyMethods>[]>([])
81
+ const keyRefs = useRef<RefObject<KeyMethods | null>[]>([])
80
82
  for (let i = 0; i < noteRange.last - noteRange.first + 1; i++) {
81
83
  keyRefs.current[i] = createRef<KeyMethods>()
82
84
  }
@@ -153,7 +155,7 @@ export function Piano({
153
155
  }
154
156
  }, [fill, whiteKeyProps?.width, whiteNoteCount])
155
157
 
156
- useEventListener(window, 'keydown', (e) => {
158
+ useEventListener(globalThis.window, 'keydown', (e) => {
157
159
  if (e.repeat) return
158
160
  if (!keyboardShortcuts) return
159
161
  const index = keyboardShortcuts.keys.indexOf(e.key)
@@ -164,7 +166,7 @@ export function Piano({
164
166
  }
165
167
  })
166
168
 
167
- useEventListener(window, 'keyup', (e) => {
169
+ useEventListener(globalThis.window, 'keyup', (e) => {
168
170
  if (e.repeat) return
169
171
  if (!keyboardShortcuts) return
170
172
  const index = keyboardShortcuts.keys.indexOf(e.key)
@@ -204,8 +206,8 @@ export function Piano({
204
206
  __index={index}
205
207
  __fill={fill}
206
208
  __width={whiteNoteWidth}
207
- __playNote={playNote}
208
- __stopNote={stopNote}
209
+ __onPlayNote={onPlayNote}
210
+ __onStopNote={onStopNote}
209
211
  __label={label}
210
212
  {...whiteKeyProps}
211
213
  />
@@ -220,8 +222,8 @@ export function Piano({
220
222
  __index={index}
221
223
  __fill={fill}
222
224
  __width={whiteNoteWidth * 0.65}
223
- __playNote={playNote}
224
- __stopNote={stopNote}
225
+ __onPlayNote={onPlayNote}
226
+ __onStopNote={onStopNote}
225
227
  __label={label}
226
228
  {...blackKeyProps}
227
229
  />
@@ -236,4 +238,4 @@ export * from './keyboardShortcuts'
236
238
 
237
239
  export { WhiteKey } from './WhiteKey'
238
240
  export { BlackKey } from './BlackKey'
239
- export { KeyLabel } from './KeyLabel'
241
+ export { KeyLabel, type KeyLabelProps } from './KeyLabel'
@@ -1,5 +1,8 @@
1
1
  import { CSSProperties, ReactElement, ReactNode } from 'react'
2
2
 
3
+ /**
4
+ * @category Piano
5
+ */
3
6
  export interface KeyProps {
4
7
  width?: number
5
8
  height?: number | `${number}%`
@@ -26,13 +29,16 @@ export interface KeyProps {
26
29
  /** @internal */
27
30
  __width?: number
28
31
  /** @internal */
29
- __playNote?: (noteNumber: number) => void
32
+ __onPlayNote?: (noteNumber: number) => void
30
33
  /** @internal */
31
- __stopNote?: (noteNumber: number) => void
34
+ __onStopNote?: (noteNumber: number) => void
32
35
  /** @internal */
33
36
  __label?: (note: number, index: number) => ReactNode
34
37
  }
35
38
 
39
+ /**
40
+ * @category Piano
41
+ */
36
42
  export interface KeyMethods {
37
43
  play: () => void
38
44
  stop: () => void
@@ -1,3 +1,4 @@
1
+ /** @category Piano */
1
2
  export type KeyBoardShortcuts = {
2
3
  keys: string[]
3
4
  // TODO
@@ -6,6 +7,7 @@ export type KeyBoardShortcuts = {
6
7
  }
7
8
  }
8
9
 
10
+ /** @category Piano */
9
11
  export const Shortcuts = {
10
12
  HOME_ROW: {
11
13
  keys: [
@@ -4,9 +4,10 @@ import { ComponentPropsWithoutRef, ReactNode } from 'react'
4
4
  import { xor } from '@tremolo-ui/functions'
5
5
  import { generateOptionsList, ScaleOptions } from '@tremolo-ui/functions/Slider'
6
6
 
7
- import { useMax, useMin, useReverse, useStep, useVertical } from './context'
7
+ import { useSliderContext } from './context'
8
8
  import { ScaleOption } from './ScaleOption'
9
9
 
10
+ /** @category Slider */
10
11
  export interface ScaleProps {
11
12
  gap?: number | string
12
13
  options?: ScaleOptions
@@ -16,6 +17,7 @@ export interface ScaleProps {
16
17
  type Props = ScaleProps &
17
18
  Omit<ComponentPropsWithoutRef<'div'>, keyof ScaleProps>
18
19
 
20
+ /** @category Slider */
19
21
  export function Scale({
20
22
  gap = 6,
21
23
  options,
@@ -24,11 +26,11 @@ export function Scale({
24
26
  style,
25
27
  ...props
26
28
  }: Props) {
27
- const min = useMin()
28
- const max = useMax()
29
- const step = useStep()
30
- const vertical = useVertical()
31
- const reverse = useReverse()
29
+ const min = useSliderContext((s) => s.min)
30
+ const max = useSliderContext((s) => s.max)
31
+ const step = useSliderContext((s) => s.step)
32
+ const vertical = useSliderContext((s) => s.vertical)
33
+ const reverse = useSliderContext((s) => s.reverse)
32
34
 
33
35
  const optionsList = options
34
36
  ? generateOptionsList(options, min, max, step)
@@ -4,8 +4,9 @@ import { ComponentPropsWithoutRef, CSSProperties, useCallback } from 'react'
4
4
  import { toFixed, xor, normalizeValue } from '@tremolo-ui/functions'
5
5
  import { ScaleType } from '@tremolo-ui/functions/Slider'
6
6
 
7
- import { useMax, useMin, useReverse, useSkew, useVertical } from './context'
7
+ import { useSliderContext } from './context'
8
8
 
9
+ /** @category Slider */
9
10
  export interface ScaleOptionProps {
10
11
  // required
11
12
  value: number
@@ -31,6 +32,7 @@ export interface ScaleOptionProps {
31
32
  }
32
33
  }
33
34
 
35
+ /** @category Slider */
34
36
  export function ScaleOption({
35
37
  value,
36
38
  type = 'mark-number',
@@ -46,11 +48,11 @@ export function ScaleOption({
46
48
  ...props
47
49
  }: ScaleOptionProps &
48
50
  Omit<ComponentPropsWithoutRef<'div'>, keyof ScaleOptionProps>) {
49
- const min = useMin()
50
- const max = useMax()
51
- const skew = useSkew()
52
- const vertical = useVertical()
53
- const reverse = useReverse()
51
+ const min = useSliderContext((s) => s.min)
52
+ const max = useSliderContext((s) => s.max)
53
+ const skew = useSliderContext((s) => s.skew)
54
+ const vertical = useSliderContext((s) => s.vertical)
55
+ const reverse = useSliderContext((s) => s.reverse)
54
56
 
55
57
  const calcPercent = useCallback(
56
58
  (rawValue: number) => {