zds-pickers 4.0.3 → 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 (101) 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.cjs.js +15 -32
  14. package/dist/index.es.js +4144 -10129
  15. package/dist/types/hooks/useComponentSize.d.ts +5 -0
  16. package/dist/types/hooks/useStateWithDynamicDefault.d.ts +2 -0
  17. package/dist/types/index.d.ts +266 -2
  18. package/dist/types/main.d.ts +19 -0
  19. package/dist/types/midi/ccValues.d.ts +3 -0
  20. package/dist/types/midi/export.d.ts +22 -0
  21. package/dist/types/other/DefaultTooltip.d.ts +10 -0
  22. package/dist/types/other/OctavePlayer.d.ts +20 -0
  23. package/dist/types/other/SVGText.d.ts +17 -0
  24. package/dist/types/other/SoundFontProvider.d.ts +17 -0
  25. package/dist/types/pickers/CCPicker.d.ts +7 -0
  26. package/dist/types/pickers/ChannelMappingPicker.d.ts +14 -0
  27. package/dist/types/pickers/ChannelPicker.d.ts +8 -0
  28. package/dist/types/pickers/Knob.d.ts +12 -0
  29. package/dist/types/pickers/KnobPicker.d.ts +27 -0
  30. package/dist/types/pickers/LatchPicker.d.ts +8 -0
  31. package/dist/types/pickers/MappingPicker.d.ts +11 -0
  32. package/dist/types/pickers/NotePicker.d.ts +20 -0
  33. package/dist/types/pickers/PianoPicker.d.ts +9 -0
  34. package/dist/types/pickers/PolarityPicker.d.ts +14 -0
  35. package/dist/types/pickers/ResponseCurve.d.ts +40 -0
  36. package/dist/types/pickers/ResponseCurvePicker.d.ts +15 -0
  37. package/dist/types/pickers/Select.d.ts +29 -0
  38. package/dist/types/pickers/StatusPicker.d.ts +12 -0
  39. package/dist/types/pickers/ValuePicker.d.ts +15 -0
  40. package/dist/types/pickers/knobSkin10.d.ts +14 -0
  41. package/dist/types/rotaryKnob/InternalInput.d.ts +18 -0
  42. package/dist/types/rotaryKnob/Types.d.ts +5 -0
  43. package/dist/types/rotaryKnob/helpers/DrawCircle.d.ts +18 -0
  44. package/dist/types/rotaryKnob/helpers/DrawLine.d.ts +15 -0
  45. package/dist/types/rotaryKnob/helpers/HelpersOverlay.d.ts +11 -0
  46. package/dist/types/rotaryKnob/helpers/KnobVisualHelpers.d.ts +30 -0
  47. package/dist/types/rotaryKnob/index.d.ts +49 -0
  48. package/dist/types/rotaryKnob/knobdefaultskin.d.ts +6 -0
  49. package/dist/types/rotaryKnob/utils.d.ts +49 -0
  50. package/dist/types/utils.d.ts +9 -0
  51. package/lib/@types/nebiru-react-rotary-knob.d.ts +20 -0
  52. package/lib/@types/react-svgmt.d.ts +74 -0
  53. package/lib/@types/zds-mappings.d.ts +55 -0
  54. package/lib/@types/zds-react-piano.d.ts +17 -0
  55. package/lib/hooks/useComponentSize.ts +52 -0
  56. package/lib/hooks/useStateWithDynamicDefault.ts +13 -0
  57. package/lib/main.ts +34 -0
  58. package/lib/midi/ccValues.ts +139 -0
  59. package/lib/midi/export.ts +54 -0
  60. package/lib/other/DefaultTooltip.ts +14 -0
  61. package/lib/other/OctavePlayer.tsx +141 -0
  62. package/lib/other/SVGText.tsx +221 -0
  63. package/lib/other/SoundFontProvider.tsx +130 -0
  64. package/lib/pickers/CCPicker.tsx +28 -0
  65. package/lib/pickers/ChannelMappingPicker.tsx +135 -0
  66. package/lib/pickers/ChannelPicker.tsx +76 -0
  67. package/lib/pickers/Knob.tsx +93 -0
  68. package/lib/pickers/KnobPicker.tsx +93 -0
  69. package/lib/pickers/LatchPicker.tsx +33 -0
  70. package/lib/pickers/MappingPicker.tsx +68 -0
  71. package/lib/pickers/NotePicker.tsx +95 -0
  72. package/lib/pickers/PianoPicker.tsx +77 -0
  73. package/lib/pickers/PolarityPicker.tsx +39 -0
  74. package/lib/pickers/ResponseCurve.tsx +304 -0
  75. package/lib/pickers/ResponseCurvePicker.tsx +74 -0
  76. package/lib/pickers/Select.tsx +248 -0
  77. package/lib/pickers/StatusPicker.tsx +28 -0
  78. package/lib/pickers/ValuePicker.tsx +67 -0
  79. package/lib/pickers/knobSkin10.tsx +94 -0
  80. package/lib/rotaryKnob/InternalInput.tsx +43 -0
  81. package/lib/rotaryKnob/Types.ts +6 -0
  82. package/lib/rotaryKnob/helpers/DrawCircle.tsx +41 -0
  83. package/lib/rotaryKnob/helpers/DrawLine.tsx +32 -0
  84. package/lib/rotaryKnob/helpers/HelpersOverlay.tsx +43 -0
  85. package/lib/rotaryKnob/helpers/KnobVisualHelpers.tsx +138 -0
  86. package/lib/rotaryKnob/index.tsx +417 -0
  87. package/lib/rotaryKnob/knobdefaultskin.ts +56 -0
  88. package/lib/rotaryKnob/utils.ts +112 -0
  89. package/lib/temp/acoustic_grand_piano-mp3.js +94 -0
  90. package/lib/utils.ts +41 -0
  91. package/package.json +3 -7
  92. package/stories/assets/sampleStyles.css +349 -0
  93. package/stories/assets/utils.js +7 -0
  94. package/stories/picker.stories.tsx +313 -0
  95. package/tsconfig.app.json +26 -0
  96. package/tsconfig.json +34 -0
  97. package/tsconfig.node.json +24 -0
  98. package/vite.config.ts +45 -0
  99. package/dist/index.cjs.js.map +0 -1
  100. package/dist/index.d.ts +0 -244
  101. package/dist/index.es.js.map +0 -1
@@ -0,0 +1,417 @@
1
+ /**
2
+ * Show the rotation circle and marker
3
+ * dispatches drag events
4
+ */
5
+
6
+ import { type D3DragEvent, drag } from 'd3-drag'
7
+ import { type ScaleLinear, scaleLinear } from 'd3-scale'
8
+ import { select } from 'd3-selection'
9
+ import type { BaseType, Selection } from 'd3-selection'
10
+ import { useEffect, useRef, useState } from 'react'
11
+ import { SvgLoader, SvgProxy } from 'react-svgmt'
12
+ import InternalInput from './InternalInput'
13
+ import type { Point } from './Types'
14
+ import { KnobVisualHelpers } from './helpers/KnobVisualHelpers'
15
+ import defaultSkin from './knobdefaultskin'
16
+ import utils from './utils'
17
+
18
+ /**
19
+ * type definition for the skin system attribute modification element
20
+ */
21
+ type AttributeSetValue = {
22
+ name: string
23
+ value: (props: unknown, value: unknown) => string
24
+ }
25
+
26
+ /**
27
+ * Type definition for the skin element manipulation block
28
+ */
29
+
30
+ interface UpdateElement {
31
+ element: string
32
+ content: (_props: React.CSSProperties, value: number) => string
33
+ attrs: AttributeSetValue[]
34
+ }
35
+
36
+ interface Skin {
37
+ svg: string
38
+ knobX: number
39
+ knobY: number
40
+ updateAttributes: UpdateElement[]
41
+ }
42
+
43
+ type KnobProps = Omit<React.ComponentProps<'div'>, 'onChange'> & {
44
+ clampMax?: number
45
+ clampMin?: number
46
+ defaultValue?: number
47
+ disabled?: boolean
48
+ format?: (val: number) => string
49
+ max?: number
50
+ min?: number
51
+ onChange?: (val: number) => void
52
+ onEnd?: () => void
53
+ onStart?: () => void
54
+ preciseMode?: boolean
55
+ rotateDegrees?: number
56
+ skin?: Skin
57
+ step?: number
58
+ style: React.CSSProperties
59
+ unlockDistance?: number
60
+ value?: number
61
+ }
62
+
63
+ type KnobState = {
64
+ svgRef?: SVGSVGElement
65
+ dragging: boolean
66
+ dragDistance: number
67
+ mousePos: Point
68
+ valueAngle: number
69
+ uncontrolledValue?: number
70
+ }
71
+
72
+ // function printDebugValues(obj) {
73
+ // console.log('------------')
74
+ // Object.keys(obj).forEach(key => {
75
+ // const value =
76
+ // typeof obj[key] === 'object' ? JSON.stringify(obj[key]) : obj[key]
77
+ // console.log(`${key}: ${value}`)
78
+ // })
79
+ // console.log('------------')
80
+ // }
81
+
82
+ /**
83
+ * Generic knob component
84
+ */
85
+ const RotaryKnob = (props: KnobProps) => {
86
+ const {
87
+ clampMax = 360,
88
+ clampMin = 0,
89
+ defaultValue = 0,
90
+ disabled = false,
91
+ format = (val: number) => val.toFixed(0),
92
+ max = 100,
93
+ min = 0,
94
+ onChange = () => {},
95
+ onEnd = () => {},
96
+ onStart = () => {},
97
+ preciseMode = true,
98
+ rotateDegrees = 0,
99
+ skin = defaultSkin as Skin,
100
+ step = 1,
101
+ style,
102
+ unlockDistance = 100,
103
+ value,
104
+ ...rest
105
+ } = props
106
+
107
+ const [isControlled, setIsControlled] = useState(value !== undefined)
108
+
109
+ const container = useRef<HTMLDivElement>(null)
110
+ const inputRef = useRef<HTMLInputElement>(null)
111
+
112
+ let scale: ScaleLinear<number, number>
113
+ let scaleProps: {
114
+ min: number
115
+ max: number
116
+ clampMin: number
117
+ clampMax: number
118
+ }
119
+
120
+ const [state, setState] = useState<KnobState>({
121
+ svgRef: undefined,
122
+ dragging: false,
123
+ dragDistance: 0,
124
+ mousePos: { x: 0, y: 0 },
125
+ valueAngle: 0,
126
+ uncontrolledValue: 0,
127
+ })
128
+
129
+ // returns angles between 0 and 360 for values lower 0 or greater 360
130
+ const normalizeAngle = (angle: number): number => {
131
+ if (angle < 0) {
132
+ //E.g -1 turns 359
133
+ return angle + 360
134
+ }
135
+ if (angle > 360) {
136
+ return angle - 360
137
+ }
138
+ return angle
139
+ }
140
+
141
+ // converts a value to an angle for the knob respecting the additional rotateDegrees prop
142
+ const convertValueToAngle = (value: number): number => {
143
+ const angle = (getScale()(value) + rotateDegrees) % 360
144
+ return normalizeAngle(angle)
145
+ }
146
+
147
+ // converts an angle to a value for the knob respecting the additional rotateDegrees prop
148
+ const convertAngleToValue = (angle: number): number => {
149
+ const angle2 = angle - rotateDegrees
150
+ return getScale().invert(normalizeAngle(angle2))
151
+ }
152
+
153
+ const getScale = () => {
154
+ // Memoize scale so it's not recalculated every time
155
+ if (
156
+ !scaleProps ||
157
+ scaleProps.min !== min ||
158
+ scaleProps.max !== max ||
159
+ scaleProps.clampMin !== clampMin ||
160
+ scaleProps.clampMax !== clampMax
161
+ ) {
162
+ if (rotateDegrees < 270 && rotateDegrees > 90) {
163
+ scale = scaleLinear().domain([min, max]).range([clampMin, clampMax])
164
+ } else {
165
+ scale = scaleLinear().domain([max, min]).range([clampMax, clampMin])
166
+ }
167
+ scale.clamp(true)
168
+ scaleProps = {
169
+ min: min,
170
+ max: max,
171
+ clampMin: clampMin,
172
+ clampMax: clampMax,
173
+ }
174
+ }
175
+ return scale
176
+ }
177
+
178
+ useEffect(() => {
179
+ setIsControlled(value !== undefined)
180
+
181
+ setState(prevState => ({
182
+ ...prevState,
183
+ uncontrolledValue: value ?? defaultValue ?? 0,
184
+ }))
185
+
186
+ if (container.current) {
187
+ // Remove existing drag behavior
188
+ select(container.current).on('.drag', null)
189
+ // Only setup new drag if not disabled
190
+ if (!disabled) {
191
+ setupDragging(select(container.current))
192
+ }
193
+ }
194
+ }, [value, defaultValue, disabled])
195
+
196
+ const saveRef = (elem: SVGElement) => {
197
+ if (!state.svgRef) {
198
+ setState({ ...state, svgRef: elem as SVGSVGElement })
199
+ }
200
+ }
201
+
202
+ const onAngleChanged = (angle: number) => {
203
+ //Calculate domain value
204
+ const domainValue = convertAngleToValue(angle)
205
+ if (!isControlled) {
206
+ /**
207
+ * If the mode is 'uncontrolled' we need to update our local state
208
+ */
209
+ setState(st => {
210
+ return { ...st, uncontrolledValue: domainValue }
211
+ })
212
+ }
213
+ if (!disabled) onChange(domainValue)
214
+ }
215
+
216
+ /**
217
+ * Returns the current value (depending on controlled/uncontrolled mode)
218
+ */
219
+ const getValue = (): number =>
220
+ isControlled ? (value ?? 0) : (state.uncontrolledValue ?? 0)
221
+
222
+ const setupDragging = (
223
+ elem: Selection<HTMLDivElement, unknown, BaseType, unknown>,
224
+ ) => {
225
+ if (!elem?.node()) return
226
+ const node = elem.node()
227
+ if (!node) return
228
+
229
+ let vbox = node.getBoundingClientRect()
230
+ const cx = vbox.width / 2
231
+ const cy = vbox.height / 2
232
+
233
+ // Shared state via closure
234
+ let startPos: Point
235
+ let startAngle: number
236
+ let initialAngle: number
237
+ let monitoring = false
238
+
239
+ function started(event: D3DragEvent<HTMLDivElement, unknown, unknown>) {
240
+ if (!event?.sourceEvent) return
241
+ if (!node) return
242
+
243
+ const currentValue = getValue()
244
+
245
+ vbox = node.getBoundingClientRect()
246
+ elem.classed('dragging', true)
247
+ onStart()
248
+
249
+ startPos = { x: event.x - cx, y: event.y - cy }
250
+ startAngle = utils.getAngleForPoint(startPos.x, startPos.y)
251
+ initialAngle = convertValueToAngle(currentValue)
252
+ monitoring = false
253
+
254
+ // ...existing setState code...
255
+ }
256
+
257
+ function dragged(event: D3DragEvent<HTMLDivElement, unknown, unknown>) {
258
+ const currentPos = { x: event.x - cx, y: event.y - cy }
259
+ const distanceFromCenter = Math.sqrt(
260
+ currentPos.x ** 2 + currentPos.y ** 2,
261
+ )
262
+
263
+ if (preciseMode) {
264
+ if (!monitoring && distanceFromCenter >= unlockDistance) {
265
+ startPos = currentPos
266
+ startAngle = utils.getAngleForPoint(currentPos.x, currentPos.y)
267
+ monitoring = true
268
+ }
269
+ } else {
270
+ monitoring = true
271
+ }
272
+
273
+ const currentAngle = utils.getAngleForPoint(currentPos.x, currentPos.y)
274
+ const deltaAngle = currentAngle - startAngle
275
+ let finalAngle = initialAngle + deltaAngle
276
+ finalAngle = normalizeAngle(finalAngle)
277
+
278
+ if (monitoring) {
279
+ onAngleChanged(finalAngle)
280
+ }
281
+
282
+ setState(prevState => ({
283
+ ...prevState,
284
+ dragDistance: distanceFromCenter,
285
+ mousePos: {
286
+ x: event.sourceEvent.clientX,
287
+ y: event.sourceEvent.clientY,
288
+ },
289
+ valueAngle: monitoring ? finalAngle : initialAngle,
290
+ }))
291
+ }
292
+
293
+ function ended() {
294
+ elem.classed('dragging', false)
295
+ setState(prevState => ({ ...prevState, dragging: false }))
296
+ onEnd()
297
+ inputRef.current?.focus()
298
+ }
299
+
300
+ const dragInstance = drag<HTMLDivElement, unknown>()
301
+ .container(() => node)
302
+ .on('start', started)
303
+ .on('drag', dragged)
304
+ .on('end', ended)
305
+
306
+ elem.call(dragInstance)
307
+ }
308
+
309
+ const onFormControlChange = (newVal: number) => {
310
+ if (!isControlled) {
311
+ /**
312
+ * If the mode is 'uncontrolled' we need to update our local state
313
+ */
314
+ setState(prevState => ({ ...prevState, uncontrolledValue: newVal }))
315
+ }
316
+
317
+ if (!disabled) onChange(newVal)
318
+ }
319
+
320
+ const currentValue: number = getValue()
321
+ const angle = convertValueToAngle(currentValue)
322
+
323
+ const styles: {
324
+ container: React.CSSProperties
325
+ input: React.CSSProperties
326
+ } = {
327
+ container: Object.assign(
328
+ {},
329
+ {
330
+ width: '50px',
331
+ height: '50px',
332
+ overflow: 'hidden',
333
+ position: 'relative',
334
+ userSelect: 'none',
335
+ cursor: 'crosshair',
336
+ },
337
+ style,
338
+ ),
339
+ input: {
340
+ width: '50%',
341
+ position: 'absolute',
342
+ top: '0',
343
+ left: '-100%',
344
+ },
345
+ }
346
+
347
+ //Get custom updates defined by the skin
348
+ //Transform the updateAttributes array into a SvgProxy array
349
+ const updateAttrs = skin.updateAttributes
350
+ const skinElemUpdates = updateAttrs
351
+ ? updateAttrs.map((elemUpdate, ix: number) => {
352
+ let elemContent: string | null = null
353
+ if (elemUpdate.content) {
354
+ elemContent = elemUpdate.content(props, currentValue)
355
+ }
356
+ const attributes: Record<string, string> = {}
357
+ //Call value function
358
+ //TODO: support string values
359
+ for (const attr of elemUpdate.attrs || []) {
360
+ attributes[attr.name] = attr.value(props, currentValue)
361
+ }
362
+
363
+ return (
364
+ <SvgProxy
365
+ key={ix}
366
+ selector={elemUpdate.element}
367
+ {...attributes}>
368
+ {elemContent}
369
+ </SvgProxy>
370
+ )
371
+ })
372
+ : null
373
+
374
+ return (
375
+ <>
376
+ <div
377
+ ref={container}
378
+ style={styles.container}
379
+ {...rest}>
380
+ <InternalInput
381
+ disabled={disabled}
382
+ inputRef={inputRef}
383
+ style={styles.input}
384
+ value={currentValue}
385
+ min={min}
386
+ max={max}
387
+ step={step}
388
+ onChange={onFormControlChange}
389
+ />
390
+
391
+ <SvgLoader
392
+ height="100%"
393
+ svgXML={skin.svg}
394
+ width="100%"
395
+ onSVGReady={saveRef}>
396
+ <SvgProxy
397
+ selector="#knob"
398
+ transform={`$ORIGINAL rotate(${angle}, ${skin.knobX}, ${skin.knobY})`}
399
+ />
400
+ {skinElemUpdates}
401
+ </SvgLoader>
402
+ </div>
403
+
404
+ {state.dragging && preciseMode && (
405
+ <KnobVisualHelpers
406
+ svgRef={state.svgRef}
407
+ radius={state.dragDistance}
408
+ mousePos={state.mousePos}
409
+ minimumDragDistance={unlockDistance}
410
+ valueAngle={state.valueAngle}
411
+ />
412
+ )}
413
+ </>
414
+ )
415
+ }
416
+
417
+ export default RotaryKnob
@@ -0,0 +1,56 @@
1
+ export default {
2
+ knobX: 71,
3
+ knobY: 71,
4
+ svg: `
5
+
6
+ <svg width="204px" height="204px" viewBox="0 0 204 204" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
7
+ <!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
8
+ <desc>Created with Sketch.</desc>
9
+ <defs>
10
+ <linearGradient x1="50%" y1="50%" x2="50%" y2="100%" id="linearGradient-1">
11
+ <stop stop-color="#444040" stop-opacity="0.51098279" offset="0%"></stop>
12
+ <stop stop-color="#131111" stop-opacity="0.893200861" offset="100%"></stop>
13
+ </linearGradient>
14
+ <circle id="path-2" cx="98.0400009" cy="98.0400009" r="98.0400009"></circle>
15
+ <filter x="-3.3%" y="-3.3%" width="106.6%" height="106.6%" filterUnits="objectBoundingBox" id="filter-3">
16
+ <feMorphology radius="0.5" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
17
+ <feOffset dx="0" dy="0" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
18
+ <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
19
+ <feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"></feComposite>
20
+ <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
21
+ </filter>
22
+ <linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-4">
23
+ <stop stop-color="#FFFFFF" stop-opacity="0.5" offset="0%"></stop>
24
+ <stop stop-color="#000000" stop-opacity="0.5" offset="100%"></stop>
25
+ </linearGradient>
26
+ <circle id="path-5" cx="98" cy="98" r="86"></circle>
27
+ <filter x="-4.1%" y="-3.5%" width="108.1%" height="108.1%" filterUnits="objectBoundingBox" id="filter-6">
28
+ <feMorphology radius="0.5" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
29
+ <feOffset dx="0" dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
30
+ <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
31
+ <feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"></feComposite>
32
+ <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
33
+ </filter>
34
+ </defs>
35
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
36
+ <g id="s13" transform="translate(4.000000, 4.000000)">
37
+ <g id="container">
38
+ <g id="Oval-2">
39
+ <use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use>
40
+ <use stroke="#979797" stroke-width="1" fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use>
41
+ </g>
42
+ <g id="Oval-2">
43
+ <use fill="black" fill-opacity="1" filter="url(#filter-6)" xlink:href="#path-5"></use>
44
+ <use fill="" fill-rule="evenodd" xlink:href="#path-5"></use>
45
+ <use stroke="#4A4A4A" stroke-width="1" fill="url(#linearGradient-4)" fill-rule="evenodd" xlink:href="#path-5"></use>
46
+ </g>
47
+ <g id="knob" transform="translate(27.431373, 27.431373)">
48
+ <circle id="Oval-5" fill="#322E2E" cx="71" cy="71" r="71"></circle>
49
+ <path d="M70.0686275,4.17063061 L86.5686275,65.2689929 C81.3642501,66.811221 75.8642501,67.5735686 70.0686275,67.5560356 C64.2730048,67.5385027 58.7730048,66.7761551 53.5686275,65.2689929 L70.0686275,4.17063061 Z" id="Rectangle-Copy" fill="#E6D7D7" transform="translate(70.068627, 35.863481) scale(1, -1) translate(-70.068627, -35.863481) "></path>
50
+ </g>
51
+ </g>
52
+ </g>
53
+ </g>
54
+ </svg>
55
+ `,
56
+ }
@@ -0,0 +1,112 @@
1
+ /** Note on viewbox/width
2
+ * This methods only work when the pixels match the svg units.
3
+ * when the viewbox is changed, or width is set they won't return
4
+ * the correct results
5
+ */
6
+ /**
7
+ * Returns the global coordinates of a position given
8
+ * for a specific coordinate system
9
+ * @param {*} svgdoc
10
+ * @param {*} elem
11
+ * @param {*} x
12
+ * @param {*} y
13
+ */
14
+ // function toGlobalCoordinates(svgdoc: any, elem: any, x: number, y: number) {
15
+ // var offset = svgdoc.getBoundingClientRect()
16
+ // var matrix = elem.getScreenCTM()
17
+ // return {
18
+ // x: matrix.a * x + matrix.c * y + matrix.e - offset.left,
19
+ // y: matrix.b * x + matrix.d * y + matrix.f - offset.top,
20
+ // }
21
+ // }
22
+
23
+ /**
24
+ * Converts a global position to a local one
25
+ * @param {*} svgdoc
26
+ * @param {*} elem
27
+ * @param {*} x
28
+ * @param {*} y
29
+ */
30
+ // function toLocalCoordinates(svgdoc: any, elem: any, x: number, y: number) {
31
+ // const offset = svgdoc.getBoundingClientRect()
32
+ // const matrix = elem.getScreenCTM().inverse()
33
+ // return {
34
+ // x: matrix.a * x + matrix.c * y + matrix.e - offset.left,
35
+ // y: matrix.b * x + matrix.d * y + matrix.f - offset.top,
36
+ // }
37
+ // }
38
+
39
+ function toRadians(angle: number) {
40
+ return (angle * Math.PI) / 180
41
+ }
42
+
43
+ function getQuadrant(x: number, y: number) {
44
+ if (x >= 0 && y >= 0) return 1
45
+ if (x <= 0 && y >= 0) return 2
46
+ if (x <= 0 && y <= 0) return 3
47
+ if (x >= 0 && y <= 0) return 4
48
+ return 1
49
+ }
50
+
51
+ /**
52
+ * Returns the angle (degrees) given a point.
53
+ * Assumes 0,0 as the center of the circle
54
+ * @param {number} x
55
+ * @param {number} y
56
+ */
57
+ function getAngleForPoint(x: number, y: number) {
58
+ if (x === 0 && y === 0) return 0
59
+
60
+ const angle = Math.atan(x / y)
61
+ let angleDeg = (angle * 180) / Math.PI
62
+ //The final value depends on the quadrant
63
+ const quadrant = getQuadrant(x, y)
64
+
65
+ if (quadrant === 1) {
66
+ angleDeg = 90 - angleDeg
67
+ }
68
+ if (quadrant === 2) {
69
+ angleDeg = 90 - angleDeg
70
+ }
71
+ if (quadrant === 3) {
72
+ angleDeg = 270 - angleDeg
73
+ }
74
+ if (quadrant === 4) {
75
+ angleDeg = 270 - angleDeg
76
+ }
77
+ return angleDeg
78
+ }
79
+
80
+ /**
81
+ * Transforms the top/left variables of the rectangle
82
+ * returned by getBoundingClientRect to document based coordinates
83
+ * without the scroll.
84
+ * UPDATE
85
+ * @param {Rect} box
86
+ */
87
+ function transformBoundingClientRectToDocument(box: DOMRect) {
88
+ let t: HTMLElement
89
+ t = document.documentElement || document.body.parentNode
90
+ const scrollX = (typeof t.scrollLeft === 'number' ? t : document.body)
91
+ .scrollLeft
92
+
93
+ t = document.documentElement || document.body.parentNode
94
+ const scrollY = (typeof t.scrollTop === 'number' ? t : document.body)
95
+ .scrollTop
96
+
97
+ //This assumes width == height
98
+ const ttop = box.top - scrollY
99
+ const tleft = box.left - scrollX
100
+ //info needed to draw line from center to mousepos
101
+
102
+ return { top: ttop, left: tleft, width: box.width, height: box.height }
103
+ }
104
+
105
+ export default {
106
+ // toGlobalCoordinates,
107
+ // toLocalCoordinates,
108
+ toRadians,
109
+ getQuadrant,
110
+ getAngleForPoint,
111
+ transformBoundingClientRectToDocument,
112
+ }