@tremolo-ui/react 0.0.11 → 0.1.0-alpha.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 (153) hide show
  1. package/dist/cjs/index.js +711 -395
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/AnimationCanvas/index.d.ts +2 -2
  4. package/dist/cjs/types/components/AnimationCanvas/index.d.ts.map +1 -1
  5. package/dist/cjs/types/components/AnimationKnob/index.d.ts +10 -3
  6. package/dist/cjs/types/components/AnimationKnob/index.d.ts.map +1 -1
  7. package/dist/cjs/types/components/Knob/index.d.ts +25 -3
  8. package/dist/cjs/types/components/Knob/index.d.ts.map +1 -1
  9. package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts +2 -0
  10. package/dist/cjs/types/components/NumberInput/DecrementStepper.d.ts.map +1 -1
  11. package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts +2 -0
  12. package/dist/cjs/types/components/NumberInput/IncrementStepper.d.ts.map +1 -1
  13. package/dist/cjs/types/components/NumberInput/InternalInput.d.ts +18 -0
  14. package/dist/cjs/types/components/NumberInput/InternalInput.d.ts.map +1 -0
  15. package/dist/cjs/types/components/NumberInput/Stepper.d.ts +2 -0
  16. package/dist/cjs/types/components/NumberInput/Stepper.d.ts.map +1 -1
  17. package/dist/cjs/types/components/NumberInput/context.d.ts +3 -2
  18. package/dist/cjs/types/components/NumberInput/context.d.ts.map +1 -1
  19. package/dist/cjs/types/components/NumberInput/index.d.ts +12 -3
  20. package/dist/cjs/types/components/NumberInput/index.d.ts.map +1 -1
  21. package/dist/cjs/types/components/Piano/KeyLabel.d.ts +1 -3
  22. package/dist/cjs/types/components/Piano/KeyLabel.d.ts.map +1 -1
  23. package/dist/cjs/types/components/Piano/context.d.ts +24 -0
  24. package/dist/cjs/types/components/Piano/context.d.ts.map +1 -0
  25. package/dist/cjs/types/components/Piano/index.d.ts +15 -12
  26. package/dist/cjs/types/components/Piano/index.d.ts.map +1 -1
  27. package/dist/cjs/types/components/Piano/key.d.ts +14 -19
  28. package/dist/cjs/types/components/Piano/key.d.ts.map +1 -1
  29. package/dist/cjs/types/components/Piano/keyboardShortcuts.d.ts +2 -2
  30. package/dist/cjs/types/components/Slider/Scale.d.ts +1 -3
  31. package/dist/cjs/types/components/Slider/Scale.d.ts.map +1 -1
  32. package/dist/cjs/types/components/Slider/Thumb.d.ts +1 -0
  33. package/dist/cjs/types/components/Slider/Thumb.d.ts.map +1 -1
  34. package/dist/cjs/types/components/Slider/Track.d.ts +2 -2
  35. package/dist/cjs/types/components/Slider/Track.d.ts.map +1 -1
  36. package/dist/cjs/types/components/Slider/index.d.ts +5 -1
  37. package/dist/cjs/types/components/Slider/index.d.ts.map +1 -1
  38. package/dist/cjs/types/components/WheelObserver/index.d.ts +3 -3
  39. package/dist/cjs/types/components/WheelObserver/index.d.ts.map +1 -1
  40. package/dist/cjs/types/components/XYPad/Area.d.ts +3 -3
  41. package/dist/cjs/types/components/XYPad/Area.d.ts.map +1 -1
  42. package/dist/cjs/types/components/XYPad/Thumb.d.ts +1 -0
  43. package/dist/cjs/types/components/XYPad/Thumb.d.ts.map +1 -1
  44. package/dist/cjs/types/components/XYPad/index.d.ts +6 -1
  45. package/dist/cjs/types/components/XYPad/index.d.ts.map +1 -1
  46. package/dist/cjs/types/hooks/useDrag.d.ts.map +1 -1
  47. package/dist/cjs/types/hooks/useDragWithElement.d.ts +16 -0
  48. package/dist/cjs/types/hooks/useDragWithElement.d.ts.map +1 -0
  49. package/dist/cjs/types/hooks/useInterval.d.ts +2 -0
  50. package/dist/cjs/types/hooks/useInterval.d.ts.map +1 -0
  51. package/dist/cjs/types/hooks/useLongPress.d.ts +2 -0
  52. package/dist/cjs/types/hooks/useLongPress.d.ts.map +1 -0
  53. package/dist/cjs/types/index.d.ts +5 -0
  54. package/dist/cjs/types/index.d.ts.map +1 -1
  55. package/dist/esm/index.js +706 -396
  56. package/dist/esm/index.js.map +1 -1
  57. package/dist/esm/types/components/AnimationCanvas/index.d.ts +2 -2
  58. package/dist/esm/types/components/AnimationCanvas/index.d.ts.map +1 -1
  59. package/dist/esm/types/components/AnimationKnob/index.d.ts +10 -3
  60. package/dist/esm/types/components/AnimationKnob/index.d.ts.map +1 -1
  61. package/dist/esm/types/components/Knob/index.d.ts +25 -3
  62. package/dist/esm/types/components/Knob/index.d.ts.map +1 -1
  63. package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts +2 -0
  64. package/dist/esm/types/components/NumberInput/DecrementStepper.d.ts.map +1 -1
  65. package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts +2 -0
  66. package/dist/esm/types/components/NumberInput/IncrementStepper.d.ts.map +1 -1
  67. package/dist/esm/types/components/NumberInput/InternalInput.d.ts +18 -0
  68. package/dist/esm/types/components/NumberInput/InternalInput.d.ts.map +1 -0
  69. package/dist/esm/types/components/NumberInput/Stepper.d.ts +2 -0
  70. package/dist/esm/types/components/NumberInput/Stepper.d.ts.map +1 -1
  71. package/dist/esm/types/components/NumberInput/context.d.ts +3 -2
  72. package/dist/esm/types/components/NumberInput/context.d.ts.map +1 -1
  73. package/dist/esm/types/components/NumberInput/index.d.ts +12 -3
  74. package/dist/esm/types/components/NumberInput/index.d.ts.map +1 -1
  75. package/dist/esm/types/components/Piano/KeyLabel.d.ts +1 -3
  76. package/dist/esm/types/components/Piano/KeyLabel.d.ts.map +1 -1
  77. package/dist/esm/types/components/Piano/context.d.ts +24 -0
  78. package/dist/esm/types/components/Piano/context.d.ts.map +1 -0
  79. package/dist/esm/types/components/Piano/index.d.ts +15 -12
  80. package/dist/esm/types/components/Piano/index.d.ts.map +1 -1
  81. package/dist/esm/types/components/Piano/key.d.ts +14 -19
  82. package/dist/esm/types/components/Piano/key.d.ts.map +1 -1
  83. package/dist/esm/types/components/Piano/keyboardShortcuts.d.ts +2 -2
  84. package/dist/esm/types/components/Slider/Scale.d.ts +1 -3
  85. package/dist/esm/types/components/Slider/Scale.d.ts.map +1 -1
  86. package/dist/esm/types/components/Slider/Thumb.d.ts +1 -0
  87. package/dist/esm/types/components/Slider/Thumb.d.ts.map +1 -1
  88. package/dist/esm/types/components/Slider/Track.d.ts +2 -2
  89. package/dist/esm/types/components/Slider/Track.d.ts.map +1 -1
  90. package/dist/esm/types/components/Slider/index.d.ts +5 -1
  91. package/dist/esm/types/components/Slider/index.d.ts.map +1 -1
  92. package/dist/esm/types/components/WheelObserver/index.d.ts +3 -3
  93. package/dist/esm/types/components/WheelObserver/index.d.ts.map +1 -1
  94. package/dist/esm/types/components/XYPad/Area.d.ts +3 -3
  95. package/dist/esm/types/components/XYPad/Area.d.ts.map +1 -1
  96. package/dist/esm/types/components/XYPad/Thumb.d.ts +1 -0
  97. package/dist/esm/types/components/XYPad/Thumb.d.ts.map +1 -1
  98. package/dist/esm/types/components/XYPad/index.d.ts +6 -1
  99. package/dist/esm/types/components/XYPad/index.d.ts.map +1 -1
  100. package/dist/esm/types/hooks/useDrag.d.ts.map +1 -1
  101. package/dist/esm/types/hooks/useDragWithElement.d.ts +16 -0
  102. package/dist/esm/types/hooks/useDragWithElement.d.ts.map +1 -0
  103. package/dist/esm/types/hooks/useInterval.d.ts +2 -0
  104. package/dist/esm/types/hooks/useInterval.d.ts.map +1 -0
  105. package/dist/esm/types/hooks/useLongPress.d.ts +2 -0
  106. package/dist/esm/types/hooks/useLongPress.d.ts.map +1 -0
  107. package/dist/esm/types/index.d.ts +5 -0
  108. package/dist/esm/types/index.d.ts.map +1 -1
  109. package/dist/styles/index.css +117 -33
  110. package/package.json +3 -2
  111. package/src/components/AnimationCanvas/index.tsx +14 -6
  112. package/src/components/AnimationKnob/index.tsx +216 -187
  113. package/src/components/Knob/index.tsx +244 -171
  114. package/src/components/NumberInput/DecrementStepper.tsx +9 -2
  115. package/src/components/NumberInput/IncrementStepper.tsx +9 -2
  116. package/src/components/NumberInput/InternalInput.tsx +219 -0
  117. package/src/components/NumberInput/Stepper.tsx +2 -0
  118. package/src/components/NumberInput/context.tsx +20 -8
  119. package/src/components/NumberInput/index.css +76 -22
  120. package/src/components/NumberInput/index.tsx +94 -162
  121. package/src/components/Piano/KeyLabel.tsx +9 -5
  122. package/src/components/Piano/context.tsx +108 -0
  123. package/src/components/Piano/index.css +24 -6
  124. package/src/components/Piano/index.tsx +192 -122
  125. package/src/components/Piano/key.tsx +215 -0
  126. package/src/components/Piano/keyboardShortcuts.ts +2 -2
  127. package/src/components/Slider/Scale.tsx +1 -4
  128. package/src/components/Slider/Thumb.tsx +4 -0
  129. package/src/components/Slider/Track.tsx +5 -2
  130. package/src/components/Slider/index.css +5 -2
  131. package/src/components/Slider/index.tsx +84 -71
  132. package/src/components/WheelObserver/index.tsx +4 -2
  133. package/src/components/XYPad/Area.tsx +7 -6
  134. package/src/components/XYPad/Thumb.tsx +6 -0
  135. package/src/components/XYPad/index.css +12 -2
  136. package/src/components/XYPad/index.tsx +116 -90
  137. package/src/hooks/useDrag.ts +6 -7
  138. package/src/hooks/useDragWithElement.ts +76 -0
  139. package/src/hooks/useInterval.ts +27 -0
  140. package/src/hooks/useLongPress.ts +31 -0
  141. package/src/index.ts +6 -0
  142. package/src/styles/index.css +0 -1
  143. package/dist/cjs/types/components/Piano/BlackKey.d.ts +0 -5
  144. package/dist/cjs/types/components/Piano/BlackKey.d.ts.map +0 -1
  145. package/dist/cjs/types/components/Piano/WhiteKey.d.ts +0 -7
  146. package/dist/cjs/types/components/Piano/WhiteKey.d.ts.map +0 -1
  147. package/dist/esm/types/components/Piano/BlackKey.d.ts +0 -5
  148. package/dist/esm/types/components/Piano/BlackKey.d.ts.map +0 -1
  149. package/dist/esm/types/components/Piano/WhiteKey.d.ts +0 -7
  150. package/dist/esm/types/components/Piano/WhiteKey.d.ts.map +0 -1
  151. package/src/components/Piano/BlackKey.tsx +0 -118
  152. package/src/components/Piano/WhiteKey.tsx +0 -120
  153. package/src/components/Piano/key.ts +0 -46
@@ -1,19 +1,16 @@
1
1
  import clsx from 'clsx'
2
- import { ComponentPropsWithoutRef, ReactNode, useMemo, useState } from 'react'
2
+ import { ComponentPropsWithoutRef, forwardRef, ReactNode } from 'react'
3
3
 
4
4
  import { InputEventOption } from '@tremolo-ui/functions'
5
- import { parseValue, Units } from '@tremolo-ui/functions/NumberInput'
5
+ import { Units } from '@tremolo-ui/functions/NumberInput'
6
6
 
7
- import {
8
- NumberInputProvider,
9
- safeClamp,
10
- useNumberInputContext,
11
- } from './context'
7
+ import { NumberInputProvider } from './context'
8
+ import { InternalInput } from './InternalInput'
12
9
 
13
10
  /*
14
11
  TODO
15
- - onBlurでは、parse + (clamp) + formatして返す
16
12
  - numberMode: focusが当たった時だけ、parse + type="number"にする
13
+ - skew
17
14
  */
18
15
 
19
16
  /** @category NumberInput */
@@ -29,7 +26,6 @@ export interface NumberInputProps {
29
26
  */
30
27
  max?: number
31
28
  step?: number
32
- defaultValue?: number | string
33
29
 
34
30
  /**
35
31
  * @example
@@ -50,6 +46,10 @@ export interface NumberInputProps {
50
46
  */
51
47
  digit?: number
52
48
 
49
+ readonly?: boolean
50
+
51
+ variant?: 'outline' | 'filled' | 'flushed' | 'unstyled'
52
+
53
53
  selectWithFocus?: 'all' | 'number' | 'none'
54
54
  blurOnEnter?: boolean
55
55
  keepWithinRange?: boolean
@@ -65,6 +65,8 @@ export interface NumberInputProps {
65
65
  */
66
66
  keyboard?: InputEventOption | null
67
67
 
68
+ activeColor?: string
69
+
68
70
  wrapperClassName?: string
69
71
 
70
72
  onChange?: (value: number, text: string) => void
@@ -82,166 +84,96 @@ export interface NumberInputProps {
82
84
  children?: ReactNode
83
85
  }
84
86
 
85
- function InternalInput({
86
- min,
87
- max,
88
- step,
89
- defaultValue,
90
- units,
91
- disabled,
92
- digit,
93
- selectWithFocus,
94
- blurOnEnter,
95
- keepWithinRange,
96
- clampValueOnBlur,
97
- wheel,
98
- keyboard,
99
- wrapperClassName,
100
- className,
101
- onChange,
102
- onFocus,
103
- onBlur,
104
- children: _children,
105
- ...props
106
- }: Omit<NumberInputProps, 'value'> &
107
- Omit<ComponentPropsWithoutRef<'input'>, keyof NumberInputProps | 'type'>) {
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])
118
-
119
- return (
120
- <input
121
- className={clsx('tremolo-number-input', className)}
122
- value={value}
123
- min={min}
124
- max={max}
125
- step={step}
126
- type="text"
127
- disabled={disabled}
128
- tabIndex={-1}
129
- data-error={error}
130
- onChange={(event) => {
131
- const v = event.currentTarget.value
132
- change(v)
133
- const valueAsNumber = parseValue(v, units, digit).rawValue
134
- onChange?.(valueAsNumber, v)
135
- }}
136
- onFocus={(event) => {
137
- setEditing(true)
138
- const v = event.currentTarget.value
139
- const parsed = parseValue(v, units, digit)
140
- if (selectWithFocus == 'all') {
141
- event.currentTarget.setSelectionRange(0, v.length)
142
- } else if (selectWithFocus == 'number') {
143
- event.currentTarget.setSelectionRange(
144
- 0,
145
- parsed.formatValue.length - parsed.unit.length,
146
- )
147
- }
148
- onFocus?.(parsed.rawValue, parsed.formatValue, event)
149
- }}
150
- onBlur={(event) => {
151
- setEditing(false)
152
- // update value
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)
163
- }}
164
- onKeyDown={(event) => {
165
- if (blurOnEnter && event.key == 'Enter') {
166
- event.currentTarget.blur()
167
- }
168
- }}
169
- {...props}
170
- />
171
- )
87
+ /**
88
+ * @category NumberInput
89
+ */
90
+ export interface NumberInputMethods {
91
+ focus: () => void
92
+ blur: () => void
172
93
  }
173
94
 
95
+ type Props = NumberInputProps &
96
+ Omit<ComponentPropsWithoutRef<'input'>, keyof NumberInputProps | 'type'>
97
+
174
98
  /**
175
99
  * Input with some useful functions for entering numerical values.
176
100
  * @category NumberInput
177
101
  */
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}
102
+ export const NumberInput = forwardRef<NumberInputMethods, Props>(
103
+ (
104
+ {
105
+ value,
106
+ min,
107
+ max,
108
+ step = 1,
109
+ units,
110
+ readonly = false,
111
+ disabled = false,
112
+ digit,
113
+ variant = 'outline',
114
+ selectWithFocus = 'none',
115
+ blurOnEnter = true,
116
+ keepWithinRange = true,
117
+ clampValueOnBlur = true,
118
+ wheel = ['raw', 1],
119
+ keyboard = ['raw', 1],
120
+ activeColor,
121
+ wrapperClassName,
122
+ className,
123
+ onChange,
124
+ onFocus,
125
+ onBlur,
126
+ children,
127
+ ...props
128
+ }: Props,
129
+ ref,
130
+ ) => {
131
+ const colors: { [key: string]: string | undefined } = {
132
+ '--active-color': activeColor,
133
+ }
134
+ return (
135
+ <NumberInputProvider
136
+ value={String(value)}
137
+ min={min}
138
+ max={max}
139
+ step={step}
140
+ units={units}
141
+ digit={digit}
142
+ readonly={readonly}
143
+ keepWithinRange={keepWithinRange}
144
+ onChange={onChange}
218
145
  >
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
- }
146
+ <div
147
+ className={clsx('tremolo-number-input-wrapper', wrapperClassName)}
148
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
149
+ tabIndex={0}
150
+ style={{
151
+ ...colors,
152
+ }}
153
+ data-stepper={!!children}
154
+ data-variant={variant}
155
+ >
156
+ <InternalInput
157
+ ref={ref}
158
+ readonly={readonly}
159
+ disabled={disabled}
160
+ selectWithFocus={selectWithFocus}
161
+ blurOnEnter={blurOnEnter}
162
+ clampValueOnBlur={clampValueOnBlur}
163
+ wheel={wheel}
164
+ keyboard={keyboard}
165
+ className={className}
166
+ onChange={onChange}
167
+ onFocus={onFocus}
168
+ onBlur={onBlur}
169
+ {...props}
170
+ />
171
+ {children}
172
+ </div>
173
+ </NumberInputProvider>
174
+ )
175
+ },
176
+ )
245
177
 
246
178
  export * from './Stepper'
247
179
  export * from './IncrementStepper'
@@ -1,6 +1,10 @@
1
1
  import clsx from 'clsx'
2
2
  import { ComponentPropsWithoutRef, CSSProperties, ReactNode } from 'react'
3
3
 
4
+ import { usePianoContext } from './context'
5
+
6
+ import { getNoteRangeArray } from '.'
7
+
4
8
  /** @category Piano */
5
9
  export interface KeyLabelProps {
6
10
  /**
@@ -14,8 +18,6 @@ export interface KeyLabelProps {
14
18
  /** @internal */
15
19
  __note?: number
16
20
  /** @internal */
17
- __index?: number
18
- /** @internal */
19
21
  __label?: (note: number, index: number) => ReactNode
20
22
  }
21
23
 
@@ -26,13 +28,15 @@ export function KeyLabel({
26
28
  wrapperClassName,
27
29
  wrapperStyle,
28
30
  __note,
29
- __index,
30
31
  __label,
31
32
  ...props
32
33
  }: KeyLabelProps & Omit<ComponentPropsWithoutRef<'div'>, keyof KeyLabelProps>) {
34
+ const noteRange = usePianoContext((s) => s.noteRange)
35
+ const noteRangeArray = getNoteRangeArray(noteRange)
36
+ const index = noteRangeArray.indexOf(__note!)
33
37
  const content = label
34
- ? label(__note!, __index!)
35
- : __label && __label(__note!, __index!)
38
+ ? label(__note!, index)
39
+ : __label && __label(__note!, index)
36
40
 
37
41
  return (
38
42
  (content != undefined || content != null) && (
@@ -0,0 +1,108 @@
1
+ import { createContext, ReactNode, useContext, useEffect, useRef } from 'react'
2
+ import { createStore, useStore } from 'zustand'
3
+
4
+ import { isBlackKey, noteKey, NoteKey, noteKeys } from '@tremolo-ui/functions'
5
+
6
+ import { defaultBlackKeyWidth, defaultWhiteKeyWidth } from './key'
7
+
8
+ /** @category Piano */
9
+ export type NoteRange = {
10
+ first: number
11
+ last: number
12
+ }
13
+
14
+ type State = {
15
+ noteRange: NoteRange
16
+ glissando: boolean
17
+ midiMax: number
18
+ fill: boolean
19
+ onPlayNote: (noteNumber: number) => void
20
+ onStopNote: (noteNumber: number) => void
21
+ label: (note: number, index: number) => ReactNode
22
+ }
23
+
24
+ type Action = {
25
+ notePosition: (note: number) => number
26
+ }
27
+
28
+ type PianoStore = ReturnType<typeof createPianoStore>
29
+
30
+ const createPianoStore = (initProps?: Partial<State>) => {
31
+ const noteRange = initProps?.noteRange || { first: 0, last: 127 }
32
+ const DEFAULT_PROPS: State = {
33
+ noteRange: { first: 0, last: 127 },
34
+ glissando: true,
35
+ midiMax: 127,
36
+ fill: false,
37
+ label: () => undefined,
38
+ onPlayNote: () => {},
39
+ onStopNote: () => {},
40
+ }
41
+
42
+ return createStore<State & Action>()(() => ({
43
+ ...DEFAULT_PROPS,
44
+ ...initProps,
45
+ notePosition: (note: number) => {
46
+ const pitchPositions: Record<NoteKey, number> = {
47
+ C: 0,
48
+ 'C#': 1,
49
+ D: 1,
50
+ 'D#': 2,
51
+ E: 2,
52
+ F: 3,
53
+ 'F#': 4,
54
+ G: 4,
55
+ 'G#': 5,
56
+ A: 5,
57
+ 'A#': 6,
58
+ B: 6,
59
+ }
60
+
61
+ const whiteNoteWidth = defaultWhiteKeyWidth
62
+ const blackNoteWidth = defaultBlackKeyWidth
63
+ const padding = 1
64
+
65
+ const targetNoteKey = noteKey(note)
66
+ const firstNoteKey = noteKey(noteRange.first)
67
+ const octave = Math.floor((note - noteRange.first) / 12)
68
+ const octaveOffset =
69
+ noteKeys.indexOf(firstNoteKey) > noteKeys.indexOf(targetNoteKey) ? 1 : 0
70
+ const w = whiteNoteWidth + padding
71
+ const pos = pitchPositions[targetNoteKey] - pitchPositions[firstNoteKey]
72
+ const blackKeyOffset = isBlackKey(note) ? blackNoteWidth / 2 : 0
73
+ return pos * w + (octave + octaveOffset) * 7 * w - blackKeyOffset
74
+ },
75
+ }))
76
+ }
77
+
78
+ const PianoContext = createContext<PianoStore | null>(null)
79
+
80
+ type PianoProviderProps = React.PropsWithChildren<Partial<State>>
81
+
82
+ export function PianoProvider({ children, ...props }: PianoProviderProps) {
83
+ const storeRef = useRef<PianoStore>(null)
84
+ if (!storeRef.current) {
85
+ storeRef.current = createPianoStore(props)
86
+ }
87
+
88
+ useEffect(() => {
89
+ if (storeRef.current) {
90
+ storeRef.current.setState(props)
91
+ } else {
92
+ storeRef.current = createPianoStore(props)
93
+ }
94
+ }, [props])
95
+
96
+ return (
97
+ <PianoContext.Provider value={storeRef.current}>
98
+ {children}
99
+ </PianoContext.Provider>
100
+ )
101
+ }
102
+
103
+ /** @category Piano */
104
+ export function usePianoContext<T>(selector: (state: State & Action) => T): T {
105
+ const store = useContext(PianoContext)
106
+ if (!store) throw new Error('Missing PianoContext.Provider in the tree')
107
+ return useStore(store, selector)
108
+ }
@@ -2,20 +2,29 @@
2
2
  display: inline-block;
3
3
  box-sizing: border-box;
4
4
  user-select: none;
5
+ touch-action: none;
5
6
  position: relative;
6
7
  }
7
8
 
8
9
  .tremolo-piano-white-key {
10
+ --bg: white;
11
+ --color: black;
12
+ --active-bg: #ccc;
13
+ --active-color: black;
14
+
15
+ box-sizing: border-box;
9
16
  position: absolute;
10
- background-color: white;
11
- color: black;
17
+ background-color: var(--bg);
18
+ -webkit-tap-highlight-color: transparent;
19
+ color: var(--color);
12
20
  border: 1px solid #555;
13
21
  border-radius: 0px 0px 8px 8px;
14
22
  cursor: pointer;
15
23
  z-index: 1;
16
24
 
17
25
  &[data-active='true'] {
18
- background-color: #ccc;
26
+ background-color: var(--active-bg);
27
+ color: var(--active-color);
19
28
  }
20
29
 
21
30
  &[aria-disabled='true'] {
@@ -24,16 +33,24 @@
24
33
  }
25
34
 
26
35
  .tremolo-piano-black-key {
36
+ --color: white;
37
+ --bg: #333;
38
+ --active-color: white;
39
+ --active-bg: #666;
40
+
41
+ box-sizing: border-box;
27
42
  position: absolute;
28
- background-color: #333;
29
- color: white;
43
+ background-color: var(--bg);
44
+ -webkit-tap-highlight-color: transparent;
45
+ color: var(--color);
30
46
  border: 1px solid #555;
31
47
  border-radius: 0px 0px 8px 8px;
32
48
  cursor: pointer;
33
49
  z-index: 2;
34
50
 
35
51
  &[data-active='true'] {
36
- background-color: #666;
52
+ background-color: var(--active-bg);
53
+ color: var(--active-color);
37
54
  }
38
55
 
39
56
  &[aria-disabled='true'] {
@@ -49,6 +66,7 @@
49
66
  }
50
67
 
51
68
  .tremolo-piano-key-label {
69
+ box-sizing: content-box;
52
70
  display: flex;
53
71
  justify-content: center;
54
72
  align-items: center;