zds-pickers 4.0.5 → 4.0.6

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 (74) hide show
  1. package/.editorconfig +19 -0
  2. package/.github/workflows/codeql-analysis.yml +67 -0
  3. package/.nvmrc +1 -0
  4. package/.prettierignore +0 -0
  5. package/.prettierrc +13 -0
  6. package/.storybook/main.ts +42 -0
  7. package/.storybook/manager.ts +12 -0
  8. package/.storybook/preview.tsx +26 -0
  9. package/.vscode/extensions.json +32 -0
  10. package/.vscode/javascript.code-snippets +229 -0
  11. package/.vscode/settings.json +358 -0
  12. package/biome.jsonc +158 -0
  13. package/dist/index.es.js +3 -1
  14. package/dist/types/index.d.ts +266 -2
  15. package/dist/types/pickers/CCPicker.d.ts +2 -1
  16. package/dist/types/pickers/ChannelMappingPicker.d.ts +2 -1
  17. package/dist/types/pickers/ChannelPicker.d.ts +5 -3
  18. package/dist/types/pickers/LatchPicker.d.ts +5 -3
  19. package/dist/types/pickers/MappingPicker.d.ts +2 -1
  20. package/dist/types/pickers/NotePicker.d.ts +2 -1
  21. package/dist/types/pickers/PolarityPicker.d.ts +6 -4
  22. package/dist/types/pickers/ResponseCurve.d.ts +14 -5
  23. package/dist/types/pickers/ResponseCurvePicker.d.ts +6 -4
  24. package/dist/types/pickers/StatusPicker.d.ts +2 -1
  25. package/dist/types/pickers/ValuePicker.d.ts +2 -1
  26. package/lib/@types/nebiru-react-rotary-knob.d.ts +20 -0
  27. package/lib/@types/react-svgmt.d.ts +74 -0
  28. package/lib/@types/zds-mappings.d.ts +55 -0
  29. package/lib/@types/zds-react-piano.d.ts +17 -0
  30. package/lib/hooks/useComponentSize.ts +52 -0
  31. package/lib/hooks/useStateWithDynamicDefault.ts +13 -0
  32. package/lib/main.ts +34 -0
  33. package/lib/midi/ccValues.ts +139 -0
  34. package/lib/midi/export.ts +54 -0
  35. package/lib/other/DefaultTooltip.ts +14 -0
  36. package/lib/other/OctavePlayer.tsx +141 -0
  37. package/lib/other/SVGText.tsx +221 -0
  38. package/lib/other/SoundFontProvider.tsx +130 -0
  39. package/lib/pickers/CCPicker.tsx +28 -0
  40. package/lib/pickers/ChannelMappingPicker.tsx +135 -0
  41. package/lib/pickers/ChannelPicker.tsx +76 -0
  42. package/lib/pickers/Knob.tsx +93 -0
  43. package/lib/pickers/KnobPicker.tsx +93 -0
  44. package/lib/pickers/LatchPicker.tsx +33 -0
  45. package/lib/pickers/MappingPicker.tsx +68 -0
  46. package/lib/pickers/NotePicker.tsx +95 -0
  47. package/lib/pickers/PianoPicker.tsx +77 -0
  48. package/lib/pickers/PolarityPicker.tsx +39 -0
  49. package/lib/pickers/ResponseCurve.tsx +304 -0
  50. package/lib/pickers/ResponseCurvePicker.tsx +74 -0
  51. package/lib/pickers/Select.tsx +248 -0
  52. package/lib/pickers/StatusPicker.tsx +28 -0
  53. package/lib/pickers/ValuePicker.tsx +67 -0
  54. package/lib/pickers/knobSkin10.tsx +94 -0
  55. package/lib/rotaryKnob/InternalInput.tsx +43 -0
  56. package/lib/rotaryKnob/Types.ts +6 -0
  57. package/lib/rotaryKnob/helpers/DrawCircle.tsx +41 -0
  58. package/lib/rotaryKnob/helpers/DrawLine.tsx +32 -0
  59. package/lib/rotaryKnob/helpers/HelpersOverlay.tsx +43 -0
  60. package/lib/rotaryKnob/helpers/KnobVisualHelpers.tsx +138 -0
  61. package/lib/rotaryKnob/index.tsx +417 -0
  62. package/lib/rotaryKnob/knobdefaultskin.ts +56 -0
  63. package/lib/rotaryKnob/utils.ts +112 -0
  64. package/lib/temp/acoustic_grand_piano-mp3.js +94 -0
  65. package/lib/utils.ts +41 -0
  66. package/package.json +2 -6
  67. package/stories/assets/sampleStyles.css +349 -0
  68. package/stories/assets/utils.js +7 -0
  69. package/stories/picker.stories.tsx +313 -0
  70. package/tsconfig.app.json +26 -0
  71. package/tsconfig.json +34 -0
  72. package/tsconfig.node.json +24 -0
  73. package/vite.config.ts +45 -0
  74. package/dist/index.d.ts +0 -244
@@ -0,0 +1,95 @@
1
+ import { forwardRef, useCallback, useMemo } from 'react'
2
+ import type { GroupBase, SelectInstance } from 'react-select'
3
+ import { Midi } from 'tonal'
4
+ import { type MapItem, type Mapping, emptyMapping } from 'zds-mappings'
5
+ import useStateWithDynamicDefault from '../hooks/useStateWithDynamicDefault'
6
+ import { assertRange } from '../utils'
7
+ import Select from './Select'
8
+ import type { Option, SelectProps } from './Select'
9
+
10
+ const { midiToNoteName } = Midi
11
+
12
+ const formattedMapEntry = ({ note, name }: MapItem) =>
13
+ `${note} ${name.length ? '-' : ''} ${name}`
14
+ const formattedListEntry = (label: string, idx: number) => ({
15
+ label,
16
+ value: idx + 1,
17
+ })
18
+
19
+ type NotePickerProps = SelectProps<number> & {
20
+ channel: number
21
+ disabled?: boolean
22
+ isMelodicMode?: boolean
23
+ mapping?: Mapping
24
+ onChange: (value: number) => void
25
+ }
26
+
27
+ type NotePickerRef = SelectInstance<
28
+ Option<number>,
29
+ false,
30
+ GroupBase<Option<number>>
31
+ >
32
+
33
+ const NotePicker = forwardRef<
34
+ SelectInstance<Option<number>, false, GroupBase<Option<number>>>,
35
+ NotePickerProps
36
+ >((props, ref) => {
37
+ const {
38
+ channel,
39
+ disabled = false,
40
+ isMelodicMode = false,
41
+ mapping,
42
+ onChange,
43
+ value: initialValue,
44
+ ...rest
45
+ } = props
46
+
47
+ const options = useMemo(() => {
48
+ if (isMelodicMode) {
49
+ return emptyMapping()
50
+ .map(({ note }) => {
51
+ const midiNoteName = midiToNoteName(note, { sharps: false })
52
+ .replace('b', '♭')
53
+ .replace('#', '♯')
54
+ return `${midiNoteName} (#${note})`
55
+ })
56
+ .map(formattedListEntry)
57
+ }
58
+
59
+ return (mapping || emptyMapping())
60
+ .map(formattedMapEntry)
61
+ .map(formattedListEntry)
62
+ }, [isMelodicMode, mapping])
63
+
64
+ const [value, setValue] = useStateWithDynamicDefault(initialValue)
65
+
66
+ const handleChange = useCallback(
67
+ (v: number | string) => {
68
+ if (typeof v === 'string') return
69
+
70
+ const possibleNoteNumber = assertRange(v, 128, 0)
71
+
72
+ if (possibleNoteNumber > 0) {
73
+ onChange(possibleNoteNumber)
74
+ }
75
+ setValue(v)
76
+ },
77
+ [onChange, setValue],
78
+ )
79
+
80
+ return (
81
+ <Select
82
+ {...rest}
83
+ disabled={disabled}
84
+ // isDisabled={disabled}
85
+ onChange={handleChange}
86
+ options={options}
87
+ ref={ref}
88
+ value={value}
89
+ />
90
+ )
91
+ })
92
+
93
+ export default NotePicker
94
+
95
+ export type { NotePickerProps, NotePickerRef }
@@ -0,0 +1,77 @@
1
+ import { Piano as ReactPiano } from 'zds-react-piano'
2
+ import type { WithProviderProps } from '../other/OctavePlayer'
3
+ import SoundfontProvider from '../other/SoundFontProvider'
4
+
5
+ const firstNote = 21
6
+ const lastNote = 108
7
+ const numNotes = lastNote - firstNote - 1
8
+
9
+ const whiteNotes = numNotes * 0.7 // approx
10
+
11
+ const audioContext = new window.AudioContext()
12
+
13
+ type PianoProps = {
14
+ disabled: boolean
15
+ height: number
16
+ width: number
17
+ }
18
+
19
+ const PianoPicker = ({ height, width, ...rest }: PianoProps) => {
20
+ const keyWidth = width / whiteNotes + 2
21
+ const keyWidthToHeight = keyWidth / height
22
+
23
+ return (
24
+ <div>
25
+ <ReactPiano
26
+ keyWidthToHeight={keyWidthToHeight}
27
+ noteRange={{ first: firstNote, last: lastNote }}
28
+ width={width}
29
+ {...rest}
30
+ />
31
+ </div>
32
+ )
33
+ }
34
+
35
+ const WithProvider = ({
36
+ format,
37
+ height,
38
+ hostname,
39
+ instrumentName,
40
+ onChange,
41
+ onClick,
42
+ onDoubleClick,
43
+ onKeyMouseEnter,
44
+ onKeyMouseLeave,
45
+ soundfont,
46
+ width,
47
+ }: WithProviderProps) => (
48
+ <SoundfontProvider
49
+ {...{
50
+ audioContext,
51
+ format,
52
+ hostname,
53
+ instrumentName,
54
+ onChange,
55
+ soundfont,
56
+ }}
57
+ render={({ isLoading, playNote, stopNote }) => (
58
+ <PianoPicker
59
+ disabled={isLoading}
60
+ {...{
61
+ height,
62
+ onClick,
63
+ onDoubleClick,
64
+ onKeyMouseEnter,
65
+ onKeyMouseLeave,
66
+ playNote,
67
+ stopNote,
68
+ width,
69
+ }}
70
+ />
71
+ )}
72
+ />
73
+ )
74
+
75
+ export default WithProvider
76
+
77
+ export type { PianoProps }
@@ -0,0 +1,39 @@
1
+ import { forwardRef } from 'react'
2
+ import type { GroupBase, SelectInstance } from 'react-select'
3
+ import Select from './Select'
4
+ import type { Option, SelectProps } from './Select'
5
+
6
+ type Polarity = 0 | 1
7
+
8
+ type PolarityPickerProps = SelectProps<Polarity> & {
9
+ labelOff?: string
10
+ labelOn?: string
11
+ }
12
+
13
+ type PolarityPickerRef = SelectInstance<
14
+ Option<Polarity>,
15
+ false,
16
+ GroupBase<Option<Polarity>>
17
+ >
18
+
19
+ const PolarityPicker = forwardRef<
20
+ SelectInstance<Option<Polarity>, false, GroupBase<Option<Polarity>>>,
21
+ PolarityPickerProps
22
+ >(({ labelOff = 'Normally Off', labelOn = 'Normally On', ...rest }, ref) => {
23
+ const options = [
24
+ { value: 0, label: labelOff },
25
+ { value: 1, label: labelOn },
26
+ ] as Option<Polarity>[]
27
+
28
+ return (
29
+ <Select
30
+ {...rest}
31
+ options={options}
32
+ ref={ref}
33
+ />
34
+ )
35
+ })
36
+
37
+ export default PolarityPicker
38
+
39
+ export type { Polarity, PolarityPickerProps, PolarityPickerRef }
@@ -0,0 +1,304 @@
1
+ import { useRef } from 'react'
2
+ import useComponentSize from '../hooks/useComponentSize'
3
+ import DefaultTooltip, { type TooltipProps } from '../other/DefaultTooltip'
4
+ import SVGText from '../other/SVGText'
5
+ import type { Option } from './Select'
6
+
7
+ const RESPONSE_CURVE_0 = 0
8
+ const RESPONSE_CURVE_1 = 1
9
+ const RESPONSE_CURVE_2 = 2
10
+ const RESPONSE_CURVE_3 = 3
11
+ const RESPONSE_CURVE_4 = 4
12
+ const RESPONSE_CURVE_5 = 5
13
+ const RESPONSE_CURVE_6 = 6
14
+ const RESPONSE_CURVE_7 = 7
15
+
16
+ type Curve =
17
+ | typeof RESPONSE_CURVE_0
18
+ | typeof RESPONSE_CURVE_1
19
+ | typeof RESPONSE_CURVE_2
20
+ | typeof RESPONSE_CURVE_3
21
+ | typeof RESPONSE_CURVE_4
22
+ | typeof RESPONSE_CURVE_5
23
+ | typeof RESPONSE_CURVE_6
24
+ | typeof RESPONSE_CURVE_7
25
+
26
+ type ResponseCurve = {
27
+ value: Curve
28
+ label: React.ReactNode
29
+ c1: number
30
+ c2: number
31
+ x1: number
32
+ y1: number
33
+ x2: number
34
+ y2: number
35
+ labelX: number
36
+ labelY: number
37
+ }
38
+
39
+ const RESPONSE_CURVES: Option<Curve>[] = [
40
+ {
41
+ value: RESPONSE_CURVE_7,
42
+ label: 'Always maxed out.',
43
+ },
44
+ {
45
+ value: RESPONSE_CURVE_6,
46
+ label: 'Most sensitive.',
47
+ },
48
+ {
49
+ value: RESPONSE_CURVE_5,
50
+ label: 'Moderately sensitive.',
51
+ },
52
+ {
53
+ value: RESPONSE_CURVE_4,
54
+ label: 'A little sensitive.',
55
+ },
56
+ {
57
+ value: RESPONSE_CURVE_0,
58
+ label: 'Linear, no change.',
59
+ },
60
+ {
61
+ value: RESPONSE_CURVE_1,
62
+ label: 'A little less sensitive.',
63
+ },
64
+ {
65
+ value: RESPONSE_CURVE_2,
66
+ label: 'Even less sensitive.',
67
+ },
68
+ {
69
+ value: RESPONSE_CURVE_3,
70
+ label: 'Least sensitive.',
71
+ },
72
+ ]
73
+
74
+ // const width = 218
75
+ // const height = 150
76
+ const axisOffset = 20
77
+ const buttonSize = 15
78
+
79
+ const axisX1 = axisOffset
80
+ const axisY1 = axisOffset
81
+
82
+ type ResponseCurveProps = {
83
+ autosize: boolean
84
+ disabled?: boolean
85
+ inverted: boolean
86
+ onChange: (event: { target: { value: Curve } }) => void
87
+ Tooltip: React.FC<TooltipProps>
88
+ value: Curve
89
+ }
90
+
91
+ const ResponseCurve = (props: ResponseCurveProps) => {
92
+ const {
93
+ Tooltip = DefaultTooltip,
94
+ autosize,
95
+ disabled,
96
+ inverted,
97
+ onChange,
98
+ value: responseCurve,
99
+ } = props
100
+
101
+ const ref = useRef(null)
102
+ const svgRef = useRef(null)
103
+ const { height: ourHeight, width: ourWidth } = useComponentSize(ref)
104
+
105
+ const width = autosize ? ourWidth || 218 : 218
106
+ const height = autosize ? ourHeight || 150 : 150
107
+
108
+ const axisX2 = width - axisOffset
109
+ const axisY2 = height - axisOffset
110
+
111
+ const xMid = width / 2
112
+ const yMid = height / 2
113
+
114
+ const svgProps = { ref: svgRef }
115
+
116
+ const responseCurves = RESPONSE_CURVES.map(
117
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: <explanation>
118
+ ({ value, label }): ResponseCurve => {
119
+ let c1 = 0
120
+ let c2 = 0
121
+ let x1 = axisX1
122
+ let y1 = inverted ? axisY1 : axisY2
123
+ let x2 = axisX2
124
+ let y2 = inverted ? axisY2 : axisY1
125
+ let labelX = xMid
126
+ let labelY = yMid
127
+ switch (value) {
128
+ case RESPONSE_CURVE_0:
129
+ c1 = xMid
130
+ c2 = yMid
131
+ labelY += 3
132
+
133
+ break
134
+ case RESPONSE_CURVE_1:
135
+ c1 = inverted ? xMid + width * 0.13 : xMid - width * 0.13
136
+ c2 = yMid - height * 0.13
137
+ labelX = inverted ? xMid + width * 0.1 : xMid - width * 0.1
138
+ labelY = yMid - height * 0.09
139
+ break
140
+
141
+ case RESPONSE_CURVE_2:
142
+ c1 = inverted ? xMid + width * 0.25 : xMid - width * 0.25
143
+ c2 = yMid - height * 0.25
144
+ labelX = inverted ? xMid + width * 0.2 : xMid - width * 0.2
145
+ labelY = yMid - height * 0.17
146
+ break
147
+
148
+ case RESPONSE_CURVE_3:
149
+ c1 = inverted ? xMid + width * 0.35 : xMid - width * 0.35
150
+ c2 = yMid - height * 0.35
151
+ labelX = inverted ? xMid + width * 0.28 : xMid - width * 0.28
152
+ labelY = yMid - height * 0.25
153
+ break
154
+
155
+ case RESPONSE_CURVE_4:
156
+ c1 = inverted ? xMid - width * 0.13 : xMid + width * 0.13
157
+ c2 = yMid + height * 0.13
158
+ labelX = inverted ? xMid - width * 0.1 : xMid + width * 0.1
159
+ labelY = yMid + height * 0.1
160
+ break
161
+
162
+ case RESPONSE_CURVE_5:
163
+ c1 = inverted ? xMid - width * 0.25 : xMid + width * 0.25
164
+ c2 = yMid + height * 0.25
165
+ labelX = inverted ? xMid - width * 0.2 : xMid + width * 0.2
166
+ labelY = yMid + height * 0.18
167
+ break
168
+
169
+ case RESPONSE_CURVE_6:
170
+ c1 = inverted ? xMid - width * 0.35 : xMid + width * 0.35
171
+ c2 = yMid + height * 0.35
172
+ labelX = inverted ? xMid - width * 0.28 : xMid + width * 0.28
173
+ labelY = yMid + height * 0.25
174
+ break
175
+
176
+ case RESPONSE_CURVE_7:
177
+ x1 = inverted ? axisX1 : axisX2
178
+ y1 = axisY1
179
+ x2 = x1
180
+ y2 = axisY2
181
+ c1 = x1
182
+ c2 = yMid
183
+ labelX = x1 - 5
184
+ labelY = height - 30
185
+ break
186
+
187
+ default:
188
+ break
189
+ }
190
+ return {
191
+ value,
192
+ label,
193
+ c1,
194
+ c2,
195
+ x1,
196
+ y1,
197
+ x2,
198
+ y2,
199
+ labelX,
200
+ labelY,
201
+ }
202
+ },
203
+ )
204
+
205
+ return (
206
+ <div
207
+ className="response-curve-picker"
208
+ ref={ref}>
209
+ <svg {...svgProps}>
210
+ <rect
211
+ className="container"
212
+ height={height}
213
+ rx={3}
214
+ ry={3}
215
+ width={width}
216
+ x={0}
217
+ y={0}
218
+ />
219
+
220
+ <SVGText
221
+ className="axis"
222
+ textAnchor="middle"
223
+ transform={`translate(${axisX1 - 14} ${height / 2}) rotate(90)`}
224
+ x={0}
225
+ y={0}
226
+ text="Applied force"
227
+ />
228
+
229
+ <SVGText
230
+ className="axis"
231
+ textAnchor="middle"
232
+ transform={`translate(${width / 2} ${axisY2 + 13})`}
233
+ x={0}
234
+ y={0}
235
+ text="Output (velocity)"
236
+ />
237
+
238
+ <line
239
+ className="axis"
240
+ x1={axisX1}
241
+ x2={axisX1}
242
+ y1={axisY1}
243
+ y2={axisY2}
244
+ />
245
+ <line
246
+ className="axis"
247
+ x1={axisX1}
248
+ x2={axisX2}
249
+ y1={axisY2}
250
+ y2={axisY2}
251
+ />
252
+
253
+ {responseCurves.map(
254
+ ({ value, label, x1, y1, x2, y2, c1, c2, labelX, labelY }) => (
255
+ <g key={value}>
256
+ <path
257
+ className={value === responseCurve ? 'curve-selected' : 'curve'}
258
+ d={`M${x1} ${y1} C ${c1} ${c2}, ${c1} ${c2}, ${x2} ${y2}`}
259
+ />
260
+
261
+ <Tooltip
262
+ placement="left"
263
+ title={label}>
264
+ <rect
265
+ className={
266
+ value === responseCurve ? 'button-selected' : 'button'
267
+ }
268
+ height={buttonSize}
269
+ onClick={
270
+ disabled
271
+ ? f => f
272
+ : () =>
273
+ onChange({
274
+ target: {
275
+ value: Number.parseInt(String(value)) as Curve,
276
+ },
277
+ })
278
+ }
279
+ transform={`translate(${labelX + 3} ${labelY - buttonSize / 1.05}) rotate(45)`}
280
+ width={buttonSize}
281
+ />
282
+ </Tooltip>
283
+
284
+ <text
285
+ className={
286
+ value === responseCurve ? 'button-selected' : 'button'
287
+ }
288
+ transform={`translate(${labelX} ${labelY})`}>
289
+ {value}
290
+ <title>{label}</title>
291
+ </text>
292
+ </g>
293
+ ),
294
+ )}
295
+ </svg>
296
+ </div>
297
+ )
298
+ }
299
+
300
+ export default ResponseCurve
301
+
302
+ export { RESPONSE_CURVES }
303
+
304
+ export type { Curve, ResponseCurve, ResponseCurveProps }
@@ -0,0 +1,74 @@
1
+ import { forwardRef, useCallback } from 'react'
2
+ import type { GroupBase, SelectInstance } from 'react-select'
3
+ import DefaultTooltip, { type TooltipProps } from '../other/DefaultTooltip'
4
+ import { findObj } from '../utils'
5
+ import ResponseCurve, { type Curve, RESPONSE_CURVES } from './ResponseCurve'
6
+ import Select from './Select'
7
+ import type { Option, SelectProps } from './Select'
8
+
9
+ type ResponseCurvePickerProps = SelectProps<Curve> & {
10
+ inverted?: boolean
11
+ Tooltip?: React.FC<TooltipProps>
12
+ }
13
+
14
+ type ResponseCurvePickerRef = SelectInstance<
15
+ Option<Curve>,
16
+ false,
17
+ GroupBase<Option<Curve>>
18
+ >
19
+
20
+ const ResponseCurvePicker = forwardRef<
21
+ SelectInstance<Option<Curve>, false, GroupBase<Option<Curve>>>,
22
+ ResponseCurvePickerProps
23
+ >((props, ref) => {
24
+ const {
25
+ Tooltip = DefaultTooltip,
26
+ inverted = false,
27
+ onChange,
28
+ value,
29
+ ...rest
30
+ } = props
31
+
32
+ const Placeholder = useCallback(
33
+ () => (
34
+ <div className="singleValue">
35
+ {(findObj('value', value)(RESPONSE_CURVES) as Option<Curve>)?.label}
36
+ </div>
37
+ ),
38
+ [value],
39
+ )
40
+
41
+ const MenuList = useCallback(
42
+ ({ setValue }: { setValue: (v: Curve) => void }) => (
43
+ <ResponseCurve
44
+ autosize
45
+ inverted={inverted}
46
+ onChange={({ target: { value: v } }) => {
47
+ setValue(v)
48
+ onChange(v)
49
+ }}
50
+ Tooltip={Tooltip}
51
+ value={Number.parseInt(String(value)) as Curve}
52
+ />
53
+ ),
54
+ [Tooltip, inverted, onChange, value],
55
+ )
56
+
57
+ return (
58
+ <Select
59
+ {...rest}
60
+ components={{
61
+ Placeholder,
62
+ MenuList,
63
+ }}
64
+ isSearchable={false}
65
+ onChange={f => f}
66
+ ref={ref}
67
+ value={value}
68
+ />
69
+ )
70
+ })
71
+
72
+ export default ResponseCurvePicker
73
+
74
+ export type { ResponseCurvePickerProps, ResponseCurvePickerRef }