zds-pickers 4.0.7 → 4.0.9

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 (55) hide show
  1. package/dist/index.cjs.js +11 -11
  2. package/dist/index.cjs.js.map +1 -1
  3. package/dist/index.es.js +1194 -1818
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/types/index.d.ts +2 -0
  6. package/dist/types/midi/export.d.ts +8 -9
  7. package/dist/types/other/OctavePlayer.d.ts +7 -1
  8. package/dist/types/other/SVGText.d.ts +11 -0
  9. package/dist/types/other/SoundFontProvider.d.ts +1 -0
  10. package/dist/types/pickers/ChannelMappingPicker.d.ts +2 -1
  11. package/dist/types/pickers/StatusPicker.d.ts +2 -2
  12. package/lib/hooks/useComponentSize.js +40 -0
  13. package/lib/hooks/useStateWithDynamicDefault.js +9 -0
  14. package/lib/index.js +21 -0
  15. package/lib/index.ts +2 -0
  16. package/lib/midi/ccValues.js +134 -0
  17. package/lib/midi/export.js +27 -0
  18. package/lib/midi/export.ts +2 -3
  19. package/lib/other/DefaultTooltip.js +3 -0
  20. package/lib/other/OctavePlayer.js +48 -0
  21. package/lib/other/OctavePlayer.tsx +1 -1
  22. package/lib/other/SVGText.js +109 -0
  23. package/lib/other/SVGText.tsx +2 -0
  24. package/lib/other/SoundFontProvider.js +87 -0
  25. package/lib/other/SoundFontProvider.tsx +2 -0
  26. package/lib/pickers/CCPicker.js +6 -0
  27. package/lib/pickers/ChannelMappingPicker.js +50 -0
  28. package/lib/pickers/ChannelMappingPicker.tsx +5 -1
  29. package/lib/pickers/ChannelPicker.js +21 -0
  30. package/lib/pickers/Knob.js +49 -0
  31. package/lib/pickers/KnobPicker.js +29 -0
  32. package/lib/pickers/LatchPicker.js +9 -0
  33. package/lib/pickers/MappingPicker.js +37 -0
  34. package/lib/pickers/NotePicker.js +45 -0
  35. package/lib/pickers/PianoPicker.js +27 -0
  36. package/lib/pickers/PolarityPicker.js +11 -0
  37. package/lib/pickers/ResponseCurve.js +154 -0
  38. package/lib/pickers/ResponseCurvePicker.js +19 -0
  39. package/lib/pickers/Select.js +79 -0
  40. package/lib/pickers/StatusPicker.js +5 -0
  41. package/lib/pickers/ValuePicker.js +23 -0
  42. package/lib/pickers/knobSkin10.js +93 -0
  43. package/lib/rotaryKnob/InternalInput.js +22 -0
  44. package/lib/rotaryKnob/Types.js +1 -0
  45. package/lib/rotaryKnob/helpers/DrawCircle.js +9 -0
  46. package/lib/rotaryKnob/helpers/DrawLine.js +9 -0
  47. package/lib/rotaryKnob/helpers/HelpersOverlay.js +26 -0
  48. package/lib/rotaryKnob/helpers/KnobVisualHelpers.js +52 -0
  49. package/lib/rotaryKnob/index.js +244 -0
  50. package/lib/rotaryKnob/knobdefaultskin.js +56 -0
  51. package/lib/rotaryKnob/utils.js +106 -0
  52. package/lib/utils.js +21 -0
  53. package/package.json +2 -3
  54. package/tsconfig.json +1 -1
  55. package/vite.config.ts +3 -19
@@ -0,0 +1,93 @@
1
+ const knobSkin10 = {
2
+ knobX: 71.44,
3
+ knobY: 71.44,
4
+ svg: `
5
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="207px" height="207px" viewBox="0 0 207 207" version="1.1">
6
+ <!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
7
+ <desc>Created with Sketch.</desc>
8
+ <defs>
9
+ <linearGradient x1="50%" y1="50%" x2="50%" y2="100%" id="27c61b0f-392f-962d-de12-cb9da898274b">
10
+ <stop stop-color="#444040" stop-opacity="0.51098279" offset="0%"/>
11
+ <stop stop-color="#131111" stop-opacity="0.893200861" offset="100%"/>
12
+ </linearGradient>
13
+ <circle id="e966567b-3869-4bcd-76b6-2cf084e45390" cx="100.497487" cy="100.497487" r="99.4974874"/>
14
+ <filter x="-3.3%" y="-3.3%" width="106.5%" height="106.5%" filterUnits="objectBoundingBox" id="e2e6829f-0086-96b2-e92a-6f3809b8ac61">
15
+ <feMorphology radius="0.5" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"/>
16
+ <feOffset dx="0" dy="0" in="shadowSpreadOuter1" result="shadowOffsetOuter1"/>
17
+ <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/>
18
+ <feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/>
19
+ <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"/>
20
+ </filter>
21
+ <linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="0facd5c9-8e04-e83f-4842-8ba16f949942">
22
+ <stop stop-color="#FFFFFF" stop-opacity="0.5" offset="0%"/>
23
+ <stop stop-color="#000000" stop-opacity="0.5" offset="100%"/>
24
+ </linearGradient>
25
+ <circle id="c54189ed-7ea3-956c-f040-f0207548b268" cx="100.432161" cy="100.432161" r="86.4321608"/>
26
+ <filter x="-4.0%" y="-3.5%" width="108.1%" height="108.1%" filterUnits="objectBoundingBox" id="dedf2e7e-f008-c315-f682-e424d670f210">
27
+ <feMorphology radius="0.5" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"/>
28
+ <feOffset dx="0" dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1"/>
29
+ <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/>
30
+ <feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/>
31
+ <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"/>
32
+ </filter>
33
+
34
+ <filter x="-5.3%" y="-11.3%" width="110.7%" height="122.6%" filterUnits="objectBoundingBox" id="12f618f7-e258-b597-3ed6-242484ddad8f">
35
+ <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"/>
36
+ <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/>
37
+ <feColorMatrix values="0 0 0 0 0.915577168 0 0 0 0 0.797591325 0 0 0 0 0.601479722 0 0 0 1 0" type="matrix" in="shadowBlurOuter1"/>
38
+ </filter>
39
+ <pattern id="3cc0e5b8-1f0a-886b-f635-8fcb19212870" width="8.04411765" height="8.04411765" x="21.9558824" y="21.9558824" patternUnits="userSpaceOnUse">
40
+ <use xlink:href="#2b8719c1-286b-ebf8-a488-ac66a6598fc5" transform="scale(0.167585784,0.167585784)"/>
41
+ </pattern>
42
+ <image id="2b8719c1-286b-ebf8-a488-ac66a6598fc5" width="48" height="48" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAEGWlDQ1BrQ0dDb2xvclNwYWNlR2VuZXJpY1JHQgAAOI2NVV1oHFUUPrtzZyMkzlNsNIV0qD8NJQ2TVjShtLp/3d02bpZJNtoi6GT27s6Yyc44M7v9oU9FUHwx6psUxL+3gCAo9Q/bPrQvlQol2tQgKD60+INQ6Ium65k7M5lpurHeZe58853vnnvuuWfvBei5qliWkRQBFpquLRcy4nOHj4g9K5CEh6AXBqFXUR0rXalMAjZPC3e1W99Dwntf2dXd/p+tt0YdFSBxH2Kz5qgLiI8B8KdVy3YBevqRHz/qWh72Yui3MUDEL3q44WPXw3M+fo1pZuQs4tOIBVVTaoiXEI/MxfhGDPsxsNZfoE1q66ro5aJim3XdoLFw72H+n23BaIXzbcOnz5mfPoTvYVz7KzUl5+FRxEuqkp9G/Ajia219thzg25abkRE/BpDc3pqvphHvRFys2weqvp+krbWKIX7nhDbzLOItiM8358pTwdirqpPFnMF2xLc1WvLyOwTAibpbmvHHcvttU57y5+XqNZrLe3lE/Pq8eUj2fXKfOe3pfOjzhJYtB/yll5SDFcSDiH+hRkH25+L+sdxKEAMZahrlSX8ukqMOWy/jXW2m6M9LDBc31B9LFuv6gVKg/0Szi3KAr1kGq1GMjU/aLbnq6/lRxc4XfJ98hTargX++DbMJBSiYMIe9Ck1YAxFkKEAG3xbYaKmDDgYyFK0UGYpfoWYXG+fAPPI6tJnNwb7ClP7IyF+D+bjOtCpkhz6CFrIa/I6sFtNl8auFXGMTP34sNwI/JhkgEtmDz14ySfaRcTIBInmKPE32kxyyE2Tv+thKbEVePDfW/byMM1Kmm0XdObS7oGD/MypMXFPXrCwOtoYjyyn7BV29/MZfsVzpLDdRtuIZnbpXzvlf+ev8MvYr/Gqk4H/kV/G3csdazLuyTMPsbFhzd1UabQbjFvDRmcWJxR3zcfHkVw9GfpbJmeev9F08WW8uDkaslwX6avlWGU6NRKz0g/SHtCy9J30o/ca9zX3Kfc19zn3BXQKRO8ud477hLnAfc1/G9mrzGlrfexZ5GLdn6ZZrrEohI2wVHhZywjbhUWEy8icMCGNCUdiBlq3r+xafL549HQ5jH+an+1y+LlYBifuxAvRN/lVVVOlwlCkdVm9NOL5BE4wkQ2SMlDZU97hX86EilU/lUmkQUztTE6mx1EEPh7OmdqBtAvv8HdWpbrJS6tJj3n0CWdM6busNzRV3S9KTYhqvNiqWmuroiKgYhshMjmhTh9ptWhsF7970j/SbMrsPE1suR5z7DMC+P/Hs+y7ijrQAlhyAgccjbhjPygfeBTjzhNqy28EdkUh8C+DU9+z2v/oyeH791OncxHOs5y2AtTc7nb/f73TWPkD/qwBnjX8BoJ98VQNcC+8AAAFFSURBVGgF7djbDYMwDAXQBjEMYqyqY1WMxTppLiJIQIA8ariWkp8IPuweO60am67r7MstY8wwjuPH7dMz3uUua63p+/7r9rePIRW/9QmQzCV9ub0YgSIgzhxvQkjFb1EZBAdEKolkfJTqtnb7bv/zOJm58moRE0AzYgFoRawAGhE7gDZEEKAJcQjQgjgFaEBcAtgRUQBmRDSAFZEEYEQkA9gQWQAmRDaABVEEYEAUAx5H4EaGOyw+SMl66mbXzNOD4k6gCPNUY/CFwF1YOn5zRxJJRINqaUbUuZA/79sd3wm3xIdbdS60rXzo2XVCbO60/HxKJgFKKv4CkEziuyKBWAE0InYAbYggQBPiEKAFcQrQgLgEsCOiAMyIaAArIgnAiEgGsCGyAEyIbAALogjAgCgGPI7AX9w6F3JtqHMhnMWLFSrSD9jOnakVHpZYAAAAAElFTkSuQmCC"/>
43
+ </defs>
44
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
45
+ <g id="s10" transform="translate(3.000000, 3.000000)">
46
+ <g id="container">
47
+ <g id="Oval-2">
48
+ <use fill="black" fill-opacity="1" filter="url(#e2e6829f-0086-96b2-e92a-6f3809b8ac61)" xlink:href="#e966567b-3869-4bcd-76b6-2cf084e45390"/>
49
+ <use stroke="#979797" stroke-width="1" fill="url(#27c61b0f-392f-962d-de12-cb9da898274b)" fill-rule="evenodd" xlink:href="#e966567b-3869-4bcd-76b6-2cf084e45390"/>
50
+ </g>
51
+ <g id="Oval-2">
52
+ <use fill="black" fill-opacity="1" filter="url(#dedf2e7e-f008-c315-f682-e424d670f210)" xlink:href="#c54189ed-7ea3-956c-f040-f0207548b268"/>
53
+ <use fill="" fill-rule="evenodd" xlink:href="#c54189ed-7ea3-956c-f040-f0207548b268"/>
54
+ <use stroke="#4A4A4A" stroke-width="1" fill="url(#0facd5c9-8e04-e83f-4842-8ba16f949942)" fill-rule="evenodd" xlink:href="#c54189ed-7ea3-956c-f040-f0207548b268"/>
55
+ </g>
56
+ <g id="knob" transform="translate(28.574244, 28.574244)">
57
+ <circle id="Oval-5" fill="#322E2E" cx="71.4356094" cy="71.4356094" r="71.4356094"/>
58
+ <path d="M72.4209282,3.63066376 L88.6786876,16.869789 C81.585496,18.1548295 76.1662428,18.7973498 72.4209282,18.7973498 C68.6756135,18.7973498 63.2563604,18.1548295 56.1631688,16.869789 L72.4209282,3.63066376 Z" id="Rectangle" fill="#E6D7D7" transform="translate(72.420928, 11.214007) scale(1, -1) translate(-72.420928, -11.214007) "/>
59
+ </g>
60
+ <g id="label">
61
+ <g id="labeltext" fill-opacity="1" fill="#E6D7D7">
62
+ <text id="534f4e02-5541-3607-0966-af0b025d80e0" font-family="Helvetica" font-size="44" font-weight="normal" fill="#E6D7D7" class="text-7" filter="url(#12f618f7-e258-b597-3ed6-242484ddad8f)">
63
+ <tspan x="45.3246625" y="117.884225">10.10</tspan>
64
+ </text>
65
+ <text id="92ebe866-c281-70c7-7b0e-a373cee013a9" font-family="Helvetica" font-size="44" font-weight="normal" fill="#E6D7D7" class="text-7">
66
+ <tspan x="45.3246625" y="117.884225">10.10</tspan>
67
+ </text>
68
+ </g>
69
+ <circle id="Oval-6" stroke="#979797" fill-opacity="0.730000019" fill="url(#3cc0e5b8-1f0a-886b-f635-8fcb19212870)" transform="translate(100.351759, 100.351759) rotate(-45.000000) translate(-100.351759, -100.351759) " cx="100.351759" cy="100.351759" r="70.3517588"/>
70
+ </g>
71
+ </g>
72
+ </g>
73
+ </g>
74
+ </svg>
75
+ `,
76
+ updateAttributes: [
77
+ {
78
+ element: '#labeltext text tspan',
79
+ content: (_props, value) => value.toFixed(0),
80
+ attrs: [
81
+ {
82
+ name: 'text-anchor',
83
+ value: ( /* props, value */) => 'middle',
84
+ },
85
+ {
86
+ name: 'x',
87
+ value: ( /* props, value */) => '100',
88
+ },
89
+ ],
90
+ },
91
+ ],
92
+ };
93
+ export default knobSkin10;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ class InternalInput extends React.Component {
4
+ render() {
5
+ const props = this.props;
6
+ const hideStyle = {};
7
+ function onValChange(event) {
8
+ props.onChange(Number(event.target.value));
9
+ }
10
+ const { value, min, max, step, onChange, inputRef, ...rest } = props;
11
+ return (_jsx("input", { ref: inputRef, value: value, step: step, onChange: onValChange, style: hideStyle, type: "range", min: min, max: max, ...rest }));
12
+ }
13
+ }
14
+ Object.defineProperty(InternalInput, "defaultProps", {
15
+ enumerable: true,
16
+ configurable: true,
17
+ writable: true,
18
+ value: {
19
+ step: 1,
20
+ }
21
+ });
22
+ export default InternalInput;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ class DrawCircle extends React.Component {
4
+ render() {
5
+ const { r, borderColor = 'black', fillColor = 'transparent', borderWidth = 2, fillOpacity = 1, ...rest } = this.props;
6
+ return (_jsx("circle", { stroke: borderColor, fill: fillColor, fillOpacity: fillOpacity, r: r, ...rest }));
7
+ }
8
+ }
9
+ export default DrawCircle;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ class DrawLine extends React.Component {
4
+ render() {
5
+ const { width = 2, color = 'black', p1, p2, ...rest } = this.props;
6
+ return (_jsx("line", { ...rest, stroke: color, strokeWidth: width, x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }));
7
+ }
8
+ }
9
+ export default DrawLine;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import ReactDOM from 'react-dom';
4
+ /**
5
+ * Draws the overlay and the children
6
+ */
7
+ class HelpersOverlay extends React.Component {
8
+ render() {
9
+ const { children, ...rest } = this.props;
10
+ const styles = {
11
+ overlay: Object.assign({
12
+ width: '100%',
13
+ height: '100vh',
14
+ top: 0,
15
+ left: 0,
16
+ zIndex: 2147483647,
17
+ margin: '0',
18
+ padding: '0',
19
+ boxSizing: 'border-box',
20
+ position: 'fixed',
21
+ }, this.props.overlayStyle),
22
+ };
23
+ return ReactDOM.createPortal(_jsx("div", { style: styles.overlay, ...rest, children: children }), document.body);
24
+ }
25
+ }
26
+ export default HelpersOverlay;
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import utils from '../utils';
4
+ import DrawCircle from './DrawCircle';
5
+ import DrawLine from './DrawLine';
6
+ import HelpersOverlay from './HelpersOverlay';
7
+ class KnobVisualHelpers extends React.Component {
8
+ constructor() {
9
+ super(...arguments);
10
+ Object.defineProperty(this, "state", {
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true,
14
+ value: {
15
+ centerX: 0,
16
+ centerY: 0,
17
+ valueMarkerX: 0,
18
+ valueMarkerY: 0,
19
+ }
20
+ });
21
+ }
22
+ static getDerivedStateFromProps(props, state) {
23
+ //Calculate position
24
+ if (!props.svgRef)
25
+ return state;
26
+ const vbox = props.svgRef.getBoundingClientRect();
27
+ const halfWidth = vbox.width / 2;
28
+ //Calculate current angle segment end point
29
+ //Note: Not sure why we need to subtract 90 here
30
+ const valueMarkerX = props.radius * Math.cos(utils.toRadians(props.valueAngle - 90));
31
+ const valueMarkerY = props.radius * Math.sin(utils.toRadians(props.valueAngle - 90));
32
+ return {
33
+ ...state,
34
+ centerX: vbox.left + halfWidth,
35
+ centerY: vbox.top + halfWidth,
36
+ valueMarkerX,
37
+ valueMarkerY,
38
+ };
39
+ }
40
+ render() {
41
+ const markCircleColor = this.props.minimumDragDistance <= this.props.radius ? 'green' : 'grey';
42
+ const fillColor = this.props.minimumDragDistance <= this.props.radius
43
+ ? '#88E22D'
44
+ : '#D8D8D8';
45
+ const minDistanceCueVisible = this.props.minimumDragDistance >= this.props.radius;
46
+ return (_jsx(HelpersOverlay, { children: _jsxs("svg", { style: { position: 'absolute', top: '0', left: '0' }, width: "100%", height: "100%", children: [_jsx("defs", { children: _jsx("marker", { id: "Triangle", viewBox: "0 0 10 10", refX: "1", refY: "5", markerWidth: "6", markerHeight: "6", orient: "auto", children: _jsx("path", { d: "M 0 0 L 10 5 L 0 10 z" }) }) }), _jsx(DrawCircle, { borderColor: markCircleColor, fillColor: fillColor, fillOpacity: 0.02, r: this.props.radius, cx: this.state.centerX, cy: this.state.centerY }), minDistanceCueVisible && (_jsx(DrawCircle, { borderColor: markCircleColor, fillColor: fillColor, fillOpacity: 0, r: this.props.minimumDragDistance, cx: this.state.centerX, cy: this.state.centerY })), _jsx(DrawLine, { p1: { x: this.state.centerX, y: this.state.centerY }, p2: {
47
+ x: this.state.valueMarkerX + this.state.centerX,
48
+ y: this.state.valueMarkerY + this.state.centerY,
49
+ }, markerEnd: "url(#Triangle)" })] }) }));
50
+ }
51
+ }
52
+ export { KnobVisualHelpers };
@@ -0,0 +1,244 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * Show the rotation circle and marker
4
+ * dispatches drag events
5
+ */
6
+ import { drag } from 'd3-drag';
7
+ import { scaleLinear } from 'd3-scale';
8
+ import { select } from 'd3-selection';
9
+ import { useEffect, useRef, useState } from 'react';
10
+ import { SvgLoader, SvgProxy } from 'react-svgmt';
11
+ import InternalInput from './InternalInput';
12
+ import { KnobVisualHelpers } from './helpers/KnobVisualHelpers';
13
+ import defaultSkin from './knobdefaultskin';
14
+ import utils from './utils';
15
+ // function printDebugValues(obj) {
16
+ // console.log('------------')
17
+ // Object.keys(obj).forEach(key => {
18
+ // const value =
19
+ // typeof obj[key] === 'object' ? JSON.stringify(obj[key]) : obj[key]
20
+ // console.log(`${key}: ${value}`)
21
+ // })
22
+ // console.log('------------')
23
+ // }
24
+ /**
25
+ * Generic knob component
26
+ */
27
+ const RotaryKnob = (props) => {
28
+ const { clampMax = 360, clampMin = 0, defaultValue = 0, disabled = false, format = (val) => val.toFixed(0), max = 100, min = 0, onChange = () => { }, onEnd = () => { }, onStart = () => { }, preciseMode = true, rotateDegrees = 0, skin = defaultSkin, step = 1, style, unlockDistance = 100, value, ...rest } = props;
29
+ const [isControlled, setIsControlled] = useState(value !== undefined);
30
+ const container = useRef(null);
31
+ const inputRef = useRef(null);
32
+ let scale;
33
+ let scaleProps;
34
+ const [state, setState] = useState({
35
+ svgRef: undefined,
36
+ dragging: false,
37
+ dragDistance: 0,
38
+ mousePos: { x: 0, y: 0 },
39
+ valueAngle: 0,
40
+ uncontrolledValue: 0,
41
+ });
42
+ // returns angles between 0 and 360 for values lower 0 or greater 360
43
+ const normalizeAngle = (angle) => {
44
+ if (angle < 0) {
45
+ //E.g -1 turns 359
46
+ return angle + 360;
47
+ }
48
+ if (angle > 360) {
49
+ return angle - 360;
50
+ }
51
+ return angle;
52
+ };
53
+ // converts a value to an angle for the knob respecting the additional rotateDegrees prop
54
+ const convertValueToAngle = (value) => {
55
+ const angle = (getScale()(value) + rotateDegrees) % 360;
56
+ return normalizeAngle(angle);
57
+ };
58
+ // converts an angle to a value for the knob respecting the additional rotateDegrees prop
59
+ const convertAngleToValue = (angle) => {
60
+ const angle2 = angle - rotateDegrees;
61
+ return getScale().invert(normalizeAngle(angle2));
62
+ };
63
+ const getScale = () => {
64
+ // Memoize scale so it's not recalculated every time
65
+ if (!scaleProps ||
66
+ scaleProps.min !== min ||
67
+ scaleProps.max !== max ||
68
+ scaleProps.clampMin !== clampMin ||
69
+ scaleProps.clampMax !== clampMax) {
70
+ if (rotateDegrees < 270 && rotateDegrees > 90) {
71
+ scale = scaleLinear().domain([min, max]).range([clampMin, clampMax]);
72
+ }
73
+ else {
74
+ scale = scaleLinear().domain([max, min]).range([clampMax, clampMin]);
75
+ }
76
+ scale.clamp(true);
77
+ scaleProps = {
78
+ min: min,
79
+ max: max,
80
+ clampMin: clampMin,
81
+ clampMax: clampMax,
82
+ };
83
+ }
84
+ return scale;
85
+ };
86
+ useEffect(() => {
87
+ setIsControlled(value !== undefined);
88
+ setState(prevState => ({
89
+ ...prevState,
90
+ uncontrolledValue: value ?? defaultValue ?? 0,
91
+ }));
92
+ if (container.current) {
93
+ // Remove existing drag behavior
94
+ select(container.current).on('.drag', null);
95
+ // Only setup new drag if not disabled
96
+ if (!disabled) {
97
+ setupDragging(select(container.current));
98
+ }
99
+ }
100
+ }, [value, defaultValue, disabled]);
101
+ const saveRef = (elem) => {
102
+ if (!state.svgRef) {
103
+ setState({ ...state, svgRef: elem });
104
+ }
105
+ };
106
+ const onAngleChanged = (angle) => {
107
+ //Calculate domain value
108
+ const domainValue = convertAngleToValue(angle);
109
+ if (!isControlled) {
110
+ /**
111
+ * If the mode is 'uncontrolled' we need to update our local state
112
+ */
113
+ setState(st => {
114
+ return { ...st, uncontrolledValue: domainValue };
115
+ });
116
+ }
117
+ if (!disabled)
118
+ onChange(domainValue);
119
+ };
120
+ /**
121
+ * Returns the current value (depending on controlled/uncontrolled mode)
122
+ */
123
+ const getValue = () => isControlled ? (value ?? 0) : (state.uncontrolledValue ?? 0);
124
+ const setupDragging = (elem) => {
125
+ if (!elem?.node())
126
+ return;
127
+ const node = elem.node();
128
+ if (!node)
129
+ return;
130
+ let vbox = node.getBoundingClientRect();
131
+ const cx = vbox.width / 2;
132
+ const cy = vbox.height / 2;
133
+ // Shared state via closure
134
+ let startPos;
135
+ let startAngle;
136
+ let initialAngle;
137
+ let monitoring = false;
138
+ function started(event) {
139
+ if (!event?.sourceEvent)
140
+ return;
141
+ if (!node)
142
+ return;
143
+ const currentValue = getValue();
144
+ vbox = node.getBoundingClientRect();
145
+ elem.classed('dragging', true);
146
+ onStart();
147
+ startPos = { x: event.x - cx, y: event.y - cy };
148
+ startAngle = utils.getAngleForPoint(startPos.x, startPos.y);
149
+ initialAngle = convertValueToAngle(currentValue);
150
+ monitoring = false;
151
+ // ...existing setState code...
152
+ }
153
+ function dragged(event) {
154
+ const currentPos = { x: event.x - cx, y: event.y - cy };
155
+ const distanceFromCenter = Math.sqrt(currentPos.x ** 2 + currentPos.y ** 2);
156
+ if (preciseMode) {
157
+ if (!monitoring && distanceFromCenter >= unlockDistance) {
158
+ startPos = currentPos;
159
+ startAngle = utils.getAngleForPoint(currentPos.x, currentPos.y);
160
+ monitoring = true;
161
+ }
162
+ }
163
+ else {
164
+ monitoring = true;
165
+ }
166
+ const currentAngle = utils.getAngleForPoint(currentPos.x, currentPos.y);
167
+ const deltaAngle = currentAngle - startAngle;
168
+ let finalAngle = initialAngle + deltaAngle;
169
+ finalAngle = normalizeAngle(finalAngle);
170
+ if (monitoring) {
171
+ onAngleChanged(finalAngle);
172
+ }
173
+ setState(prevState => ({
174
+ ...prevState,
175
+ dragDistance: distanceFromCenter,
176
+ mousePos: {
177
+ x: event.sourceEvent.clientX,
178
+ y: event.sourceEvent.clientY,
179
+ },
180
+ valueAngle: monitoring ? finalAngle : initialAngle,
181
+ }));
182
+ }
183
+ function ended() {
184
+ elem.classed('dragging', false);
185
+ setState(prevState => ({ ...prevState, dragging: false }));
186
+ onEnd();
187
+ inputRef.current?.focus();
188
+ }
189
+ const dragInstance = drag()
190
+ .container(() => node)
191
+ .on('start', started)
192
+ .on('drag', dragged)
193
+ .on('end', ended);
194
+ elem.call(dragInstance);
195
+ };
196
+ const onFormControlChange = (newVal) => {
197
+ if (!isControlled) {
198
+ /**
199
+ * If the mode is 'uncontrolled' we need to update our local state
200
+ */
201
+ setState(prevState => ({ ...prevState, uncontrolledValue: newVal }));
202
+ }
203
+ if (!disabled)
204
+ onChange(newVal);
205
+ };
206
+ const currentValue = getValue();
207
+ const angle = convertValueToAngle(currentValue);
208
+ const styles = {
209
+ container: Object.assign({}, {
210
+ width: '50px',
211
+ height: '50px',
212
+ overflow: 'hidden',
213
+ position: 'relative',
214
+ userSelect: 'none',
215
+ cursor: 'crosshair',
216
+ }, style),
217
+ input: {
218
+ width: '50%',
219
+ position: 'absolute',
220
+ top: '0',
221
+ left: '-100%',
222
+ },
223
+ };
224
+ //Get custom updates defined by the skin
225
+ //Transform the updateAttributes array into a SvgProxy array
226
+ const updateAttrs = skin.updateAttributes;
227
+ const skinElemUpdates = updateAttrs
228
+ ? updateAttrs.map((elemUpdate, ix) => {
229
+ let elemContent = null;
230
+ if (elemUpdate.content) {
231
+ elemContent = elemUpdate.content(props, currentValue);
232
+ }
233
+ const attributes = {};
234
+ //Call value function
235
+ //TODO: support string values
236
+ for (const attr of elemUpdate.attrs || []) {
237
+ attributes[attr.name] = attr.value(props, currentValue);
238
+ }
239
+ return (_jsx(SvgProxy, { selector: elemUpdate.element, ...attributes, children: elemContent }, ix));
240
+ })
241
+ : null;
242
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { ref: container, style: styles.container, ...rest, children: [_jsx(InternalInput, { disabled: disabled, inputRef: inputRef, style: styles.input, value: currentValue, min: min, max: max, step: step, onChange: onFormControlChange }), _jsxs(SvgLoader, { height: "100%", svgXML: skin.svg, width: "100%", onSVGReady: saveRef, children: [_jsx(SvgProxy, { selector: "#knob", transform: `$ORIGINAL rotate(${angle}, ${skin.knobX}, ${skin.knobY})` }), skinElemUpdates] })] }), state.dragging && preciseMode && (_jsx(KnobVisualHelpers, { svgRef: state.svgRef, radius: state.dragDistance, mousePos: state.mousePos, minimumDragDistance: unlockDistance, valueAngle: state.valueAngle }))] }));
243
+ };
244
+ 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,106 @@
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
+ * Converts a global position to a local one
24
+ * @param {*} svgdoc
25
+ * @param {*} elem
26
+ * @param {*} x
27
+ * @param {*} y
28
+ */
29
+ // function toLocalCoordinates(svgdoc: any, elem: any, x: number, y: number) {
30
+ // const offset = svgdoc.getBoundingClientRect()
31
+ // const matrix = elem.getScreenCTM().inverse()
32
+ // return {
33
+ // x: matrix.a * x + matrix.c * y + matrix.e - offset.left,
34
+ // y: matrix.b * x + matrix.d * y + matrix.f - offset.top,
35
+ // }
36
+ // }
37
+ function toRadians(angle) {
38
+ return (angle * Math.PI) / 180;
39
+ }
40
+ function getQuadrant(x, y) {
41
+ if (x >= 0 && y >= 0)
42
+ return 1;
43
+ if (x <= 0 && y >= 0)
44
+ return 2;
45
+ if (x <= 0 && y <= 0)
46
+ return 3;
47
+ if (x >= 0 && y <= 0)
48
+ return 4;
49
+ return 1;
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, y) {
58
+ if (x === 0 && y === 0)
59
+ return 0;
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
+ if (quadrant === 1) {
65
+ angleDeg = 90 - angleDeg;
66
+ }
67
+ if (quadrant === 2) {
68
+ angleDeg = 90 - angleDeg;
69
+ }
70
+ if (quadrant === 3) {
71
+ angleDeg = 270 - angleDeg;
72
+ }
73
+ if (quadrant === 4) {
74
+ angleDeg = 270 - angleDeg;
75
+ }
76
+ return angleDeg;
77
+ }
78
+ /**
79
+ * Transforms the top/left variables of the rectangle
80
+ * returned by getBoundingClientRect to document based coordinates
81
+ * without the scroll.
82
+ * UPDATE
83
+ * @param {Rect} box
84
+ */
85
+ function transformBoundingClientRectToDocument(box) {
86
+ let t;
87
+ t = document.documentElement || document.body.parentNode;
88
+ const scrollX = (typeof t.scrollLeft === 'number' ? t : document.body)
89
+ .scrollLeft;
90
+ t = document.documentElement || document.body.parentNode;
91
+ const scrollY = (typeof t.scrollTop === 'number' ? t : document.body)
92
+ .scrollTop;
93
+ //This assumes width == height
94
+ const ttop = box.top - scrollY;
95
+ const tleft = box.left - scrollX;
96
+ //info needed to draw line from center to mousepos
97
+ return { top: ttop, left: tleft, width: box.width, height: box.height };
98
+ }
99
+ export default {
100
+ // toGlobalCoordinates,
101
+ // toLocalCoordinates,
102
+ toRadians,
103
+ getQuadrant,
104
+ getAngleForPoint,
105
+ transformBoundingClientRectToDocument,
106
+ };
package/lib/utils.js ADDED
@@ -0,0 +1,21 @@
1
+ // this just creates and fills a zero-indexed array array; 0, 1, 2, 3, etc
2
+ const arraySequence = (numberOfElements) => Array.from(new Array(numberOfElements).keys());
3
+ const assertRange = (value, max = 127, min = 0) => {
4
+ let result = typeof value === 'string' ? Number.parseInt(value, 10) : value;
5
+ if (Number.isNaN(result) || result === null) {
6
+ result = min;
7
+ }
8
+ return result >= min ? (result <= max ? result : max) : min;
9
+ };
10
+ const findObj = (key, value) => (arr) => arr?.find(item => item?.[key] === value);
11
+ const omit = (...keys) => (obj) => Object.entries(obj)
12
+ .filter(([key]) => !keys.flat().map(String).includes(key))
13
+ .reduce((_obj, [key, val]) => Object.assign(_obj, { [key]: val }), {});
14
+ const pick = (...keys) => (obj) => Object.entries(obj)
15
+ .filter(([key]) => keys.flat().map(String).includes(key))
16
+ .reduce((_obj, [key, val]) => Object.assign(_obj, { [key]: val }), {});
17
+ const set = (key, value) => (obj) => ({
18
+ ...obj,
19
+ [key]: value,
20
+ });
21
+ export { arraySequence, assertRange, findObj, omit, pick, set };