@tremolo-ui/react 0.2.0 → 0.2.1
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.
- package/dist/index.cjs +1203 -1036
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +176 -159
- package/dist/index.d.cts +241 -246
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +241 -246
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1187 -1021
- package/dist/index.js.map +1 -1
- package/package.json +17 -19
- package/src/components/AnimationCanvas/index.tsx +0 -7
- package/src/components/DragObserver/index.tsx +9 -7
- package/src/components/Knob/ActiveLine.tsx +29 -0
- package/src/components/Knob/InactiveLine.tsx +53 -0
- package/src/components/Knob/SVGRoot.tsx +39 -0
- package/src/components/Knob/Thumb.tsx +64 -0
- package/src/components/Knob/context.tsx +120 -0
- package/src/components/Knob/index.css +5 -1
- package/src/components/Knob/index.tsx +110 -164
- package/src/components/NumberInput/DecrementStepper.tsx +0 -2
- package/src/components/NumberInput/IncrementStepper.tsx +0 -2
- package/src/components/NumberInput/InternalInput.tsx +3 -1
- package/src/components/NumberInput/Stepper.tsx +0 -2
- package/src/components/NumberInput/context.tsx +0 -1
- package/src/components/NumberInput/index.tsx +6 -8
- package/src/components/Piano/KeyLabel.tsx +0 -2
- package/src/components/Piano/context.tsx +0 -2
- package/src/components/Piano/index.tsx +7 -11
- package/src/components/Piano/key.tsx +0 -10
- package/src/components/Piano/keyboardShortcuts.ts +0 -2
- package/src/components/PointsEditor/Background.tsx +17 -0
- package/src/components/PointsEditor/Container.tsx +26 -0
- package/src/components/PointsEditor/Point.tsx +137 -0
- package/src/components/PointsEditor/context.tsx +91 -0
- package/src/components/PointsEditor/index.css +30 -0
- package/src/components/PointsEditor/index.tsx +129 -0
- package/src/components/Slider/Scale.tsx +0 -2
- package/src/components/Slider/ScaleOption.tsx +0 -2
- package/src/components/Slider/Thumb.tsx +0 -4
- package/src/components/Slider/Track.tsx +0 -2
- package/src/components/Slider/context.tsx +0 -1
- package/src/components/Slider/index.tsx +42 -14
- package/src/components/WheelObserver/index.tsx +17 -19
- package/src/components/XYPad/Area.tsx +0 -2
- package/src/components/XYPad/Thumb.tsx +0 -3
- package/src/components/XYPad/index.tsx +52 -25
- package/src/components/_util/composeRefs.tsx +63 -0
- package/src/components/_util/index.ts +23 -6
- package/src/components/_util/type.ts +1 -0
- package/src/hooks/useAnimationFrame.ts +0 -3
- package/src/hooks/useCallbackRef.ts +2 -2
- package/src/hooks/useDrag.ts +12 -11
- package/src/hooks/useDragWithElement.ts +12 -17
- package/src/hooks/useEventListener.ts +6 -9
- package/src/hooks/useInterval.ts +0 -3
- package/src/hooks/useLongPress.ts +0 -3
- package/src/hooks/useMIDIAccess.ts +0 -1
- package/src/hooks/useMIDIInput.ts +0 -1
- package/src/hooks/useMIDIMessage.ts +0 -1
- package/src/hooks/usePianoDrag.ts +75 -0
- package/src/index.ts +8 -5
- package/src/styles/global.css +17 -1
- package/dist/index.css.map +0 -1
- package/src/components/AnimationKnob/index.tsx +0 -314
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tremolo-ui/react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "React component library for audio app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -23,30 +23,26 @@
|
|
|
23
23
|
"clean:sb": "rimraf ./storybook-static"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@storybook/addon-a11y": "
|
|
27
|
-
"@storybook/addon-
|
|
28
|
-
"@storybook/
|
|
29
|
-
"@
|
|
30
|
-
"@
|
|
31
|
-
"@
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"react": "^19",
|
|
40
|
-
"react-dom": "^19",
|
|
41
|
-
"storybook": "^8.6.7",
|
|
42
|
-
"tone": "^15"
|
|
26
|
+
"@storybook/addon-a11y": "10.5.10",
|
|
27
|
+
"@storybook/addon-links": "10.5.10",
|
|
28
|
+
"@storybook/react-vite": "10.5.10",
|
|
29
|
+
"@testing-library/react": "16.3.2",
|
|
30
|
+
"@types/react": "19.2.18",
|
|
31
|
+
"@types/react-dom": "19.2.4",
|
|
32
|
+
"jest-environment-jsdom": "29.7.0",
|
|
33
|
+
"jotai": "2.20.2",
|
|
34
|
+
"react": "19.2.8",
|
|
35
|
+
"react-dom": "19.2.8",
|
|
36
|
+
"storybook": "10.5.10",
|
|
37
|
+
"tone": "15.1.22",
|
|
38
|
+
"@storybook/addon-docs": "10.5.10"
|
|
43
39
|
},
|
|
44
40
|
"peerDependencies": {
|
|
45
41
|
"react": "^18 ||^19",
|
|
46
42
|
"react-dom": "^18 || ^19"
|
|
47
43
|
},
|
|
48
44
|
"dependencies": {
|
|
49
|
-
"@tremolo-ui/functions": "^0.1
|
|
45
|
+
"@tremolo-ui/functions": "^0.2.1",
|
|
50
46
|
"clsx": "^2.1.1",
|
|
51
47
|
"zustand": "^5.0.3"
|
|
52
48
|
},
|
|
@@ -91,6 +87,8 @@
|
|
|
91
87
|
"./styles/Knob.css": "./src/components/Knob/index.css",
|
|
92
88
|
"./styles/NumberInput.css": "./src/components/NumberInput/index.css",
|
|
93
89
|
"./styles/Piano.css": "./src/components/Piano/index.css",
|
|
90
|
+
"./styles/PointsEditor.css": "./src/components/PointsEditor/index.css",
|
|
91
|
+
"./styles/Slider.css": "./src/components/Slider/index.css",
|
|
94
92
|
"./styles/XYPad.css": "./src/components/XYPad/index.css"
|
|
95
93
|
}
|
|
96
94
|
}
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
setDprConfig,
|
|
16
16
|
} from './canvas'
|
|
17
17
|
|
|
18
|
-
/** @category AnimationCanvas */
|
|
19
18
|
export type InitFunction = (
|
|
20
19
|
context: CanvasRenderingContext2D,
|
|
21
20
|
option: {
|
|
@@ -26,7 +25,6 @@ export type InitFunction = (
|
|
|
26
25
|
},
|
|
27
26
|
) => void
|
|
28
27
|
|
|
29
|
-
/** @category AnimationCanvas */
|
|
30
28
|
export type DrawFunction = (
|
|
31
29
|
context: CanvasRenderingContext2D,
|
|
32
30
|
option: {
|
|
@@ -45,7 +43,6 @@ export type DrawFunction = (
|
|
|
45
43
|
},
|
|
46
44
|
) => void
|
|
47
45
|
|
|
48
|
-
/** @category AnimationCanvas */
|
|
49
46
|
export interface CommonProps {
|
|
50
47
|
draw: DrawFunction
|
|
51
48
|
init?: InitFunction
|
|
@@ -56,26 +53,22 @@ export interface CommonProps {
|
|
|
56
53
|
options?: CanvasRenderingContext2DSettings
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
/** @category AnimationCanvas */
|
|
60
56
|
export interface AbsoluteSizingProps {
|
|
61
57
|
width?: number
|
|
62
58
|
height?: number
|
|
63
59
|
}
|
|
64
60
|
|
|
65
|
-
/** @category AnimationCanvas */
|
|
66
61
|
export interface RelativeSizingProps {
|
|
67
62
|
relativeSize?: boolean
|
|
68
63
|
reduceFlickering?: boolean
|
|
69
64
|
}
|
|
70
65
|
|
|
71
|
-
/** @category AnimationCanvas */
|
|
72
66
|
export type AnimationCanvasProps = CommonProps &
|
|
73
67
|
AbsoluteSizingProps &
|
|
74
68
|
RelativeSizingProps
|
|
75
69
|
|
|
76
70
|
/**
|
|
77
71
|
* A simple animatable canvas with requestAnimationFrame()
|
|
78
|
-
* @category AnimationCanvas
|
|
79
72
|
*/
|
|
80
73
|
export function AnimationCanvas(
|
|
81
74
|
props: CommonProps &
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react'
|
|
2
2
|
|
|
3
3
|
import { useDrag } from '../../hooks/useDrag'
|
|
4
|
+
import { Override } from '../_util/type'
|
|
4
5
|
|
|
5
|
-
/** @category DragObserver */
|
|
6
6
|
export interface DragObserverProps<T extends ElementType> {
|
|
7
7
|
/**
|
|
8
8
|
* React.ElementType
|
|
@@ -17,23 +17,22 @@ export interface DragObserverProps<T extends ElementType> {
|
|
|
17
17
|
threshold?: number
|
|
18
18
|
|
|
19
19
|
children?: ReactNode
|
|
20
|
-
onDrag
|
|
20
|
+
onDrag?: (x: number, y: number, deltaX: number, deltaY: number) => void
|
|
21
21
|
onDragStart?: () => void
|
|
22
22
|
onDragEnd?: () => void
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/** @category DragObserver */
|
|
26
25
|
export function DragObserver<T extends ElementType = 'div'>(
|
|
27
|
-
props: DragObserverProps<T
|
|
28
|
-
Omit<ComponentPropsWithoutRef<T>, keyof DragObserverProps<T>>,
|
|
26
|
+
props: Override<DragObserverProps<T>, ComponentPropsWithoutRef<T>>,
|
|
29
27
|
) {
|
|
30
28
|
const {
|
|
31
29
|
as: Component = 'div',
|
|
32
30
|
threshold = 1,
|
|
33
|
-
children,
|
|
34
31
|
onDrag,
|
|
35
32
|
onDragStart,
|
|
36
33
|
onDragEnd,
|
|
34
|
+
children,
|
|
35
|
+
onPointerDown,
|
|
37
36
|
...attributes
|
|
38
37
|
} = props
|
|
39
38
|
|
|
@@ -47,7 +46,10 @@ export function DragObserver<T extends ElementType = 'div'>(
|
|
|
47
46
|
return (
|
|
48
47
|
<Component
|
|
49
48
|
ref={refCallback}
|
|
50
|
-
onPointerDown={
|
|
49
|
+
onPointerDown={(e) => {
|
|
50
|
+
pointerDownHandler(e)
|
|
51
|
+
onPointerDown?.(e)
|
|
52
|
+
}}
|
|
51
53
|
{...attributes}
|
|
52
54
|
>
|
|
53
55
|
{children}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
import { SVGProps } from 'react'
|
|
3
|
+
|
|
4
|
+
import { center, useKnobContext } from './context'
|
|
5
|
+
|
|
6
|
+
export function ActiveLine({
|
|
7
|
+
stroke = 'currentColor',
|
|
8
|
+
strokeWidth = 6,
|
|
9
|
+
className,
|
|
10
|
+
...props
|
|
11
|
+
}: Omit<SVGProps<SVGPathElement>, 'd'>) {
|
|
12
|
+
const r2 = useKnobContext((s) => s.r2)
|
|
13
|
+
const r3 = useKnobContext((s) => s.r3)
|
|
14
|
+
const x2 = useKnobContext((s) => s.x2)
|
|
15
|
+
const y2 = useKnobContext((s) => s.y2)
|
|
16
|
+
const x3 = useKnobContext((s) => s.x3)
|
|
17
|
+
const y3 = useKnobContext((s) => s.y3)
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<path
|
|
21
|
+
className={clsx('tremolo-knob-active-line', className)}
|
|
22
|
+
d={`M ${x2} ${y2} A ${center} ${center} -135 ${r3 - r2 > 180 ? 1 : 0} 1 ${x3} ${y3}`}
|
|
23
|
+
fill="none"
|
|
24
|
+
stroke={stroke}
|
|
25
|
+
strokeWidth={strokeWidth}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
import { SVGProps } from 'react'
|
|
3
|
+
|
|
4
|
+
import { center, useKnobContext } from './context'
|
|
5
|
+
|
|
6
|
+
export function InactiveLine({
|
|
7
|
+
stroke = 'currentColor',
|
|
8
|
+
strokeWidth = 6,
|
|
9
|
+
className,
|
|
10
|
+
...props
|
|
11
|
+
}: Omit<SVGProps<SVGPathElement>, 'd'>) {
|
|
12
|
+
const min = useKnobContext((s) => s.min)
|
|
13
|
+
const max = useKnobContext((s) => s.max)
|
|
14
|
+
const startValue = useKnobContext((s) => s.startValue)
|
|
15
|
+
|
|
16
|
+
const r1 = useKnobContext((s) => s.r1)
|
|
17
|
+
const r2 = useKnobContext((s) => s.r2)
|
|
18
|
+
const r3 = useKnobContext((s) => s.r3)
|
|
19
|
+
const r4 = useKnobContext((s) => s.r4)
|
|
20
|
+
const x1 = useKnobContext((s) => s.x1)
|
|
21
|
+
const y1 = useKnobContext((s) => s.y1)
|
|
22
|
+
const x2 = useKnobContext((s) => s.x2)
|
|
23
|
+
const y2 = useKnobContext((s) => s.y2)
|
|
24
|
+
const x3 = useKnobContext((s) => s.x3)
|
|
25
|
+
const y3 = useKnobContext((s) => s.y3)
|
|
26
|
+
const x4 = useKnobContext((s) => s.x4)
|
|
27
|
+
const y4 = useKnobContext((s) => s.y4)
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
{startValue > min && (
|
|
32
|
+
<path
|
|
33
|
+
className={clsx('tremolo-knob-inactive-line', className)}
|
|
34
|
+
d={`M ${x1} ${y1} A ${center} ${center} -135 ${r2 - r1 > 180 ? 1 : 0} 1 ${x2} ${y2}`}
|
|
35
|
+
fill="none"
|
|
36
|
+
stroke={stroke}
|
|
37
|
+
strokeWidth={strokeWidth}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
)}
|
|
41
|
+
{startValue < max && (
|
|
42
|
+
<path
|
|
43
|
+
className={clsx('tremolo-knob-inactive-line', className)}
|
|
44
|
+
d={`M ${x3} ${y3} A ${center} ${center} -135 ${r4 - r3 > 180 ? 1 : 0} 1 ${x4} ${y4}`}
|
|
45
|
+
fill="none"
|
|
46
|
+
stroke={stroke}
|
|
47
|
+
strokeWidth={strokeWidth}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
)}
|
|
51
|
+
</>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { SVGProps } from 'react'
|
|
2
|
+
|
|
3
|
+
import { Override } from '../_util/type'
|
|
4
|
+
|
|
5
|
+
import { ActiveLine } from './ActiveLine'
|
|
6
|
+
import { viewBoxSize } from './context'
|
|
7
|
+
import { InactiveLine } from './InactiveLine'
|
|
8
|
+
import { Thumb } from './Thumb'
|
|
9
|
+
|
|
10
|
+
interface SVGRoot {
|
|
11
|
+
block?: boolean
|
|
12
|
+
overflowVisible?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function SVGRoot({
|
|
16
|
+
children,
|
|
17
|
+
style,
|
|
18
|
+
...props
|
|
19
|
+
}: Override<SVGRoot, SVGProps<SVGSVGElement>>) {
|
|
20
|
+
return (
|
|
21
|
+
<svg
|
|
22
|
+
viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}
|
|
23
|
+
style={{
|
|
24
|
+
display: 'block',
|
|
25
|
+
overflow: 'visible',
|
|
26
|
+
...style,
|
|
27
|
+
}}
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
{children || (
|
|
31
|
+
<>
|
|
32
|
+
<InactiveLine />
|
|
33
|
+
<ActiveLine />
|
|
34
|
+
<Thumb />
|
|
35
|
+
</>
|
|
36
|
+
)}
|
|
37
|
+
</svg>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
import { SVGProps } from 'react'
|
|
3
|
+
|
|
4
|
+
import { clamp } from '@tremolo-ui/functions'
|
|
5
|
+
|
|
6
|
+
import { useKnobContext, viewBoxSize } from './context'
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
/** color */
|
|
10
|
+
thumb?: string
|
|
11
|
+
/** color */
|
|
12
|
+
thumbLine?: string
|
|
13
|
+
/** percent (0-100) */
|
|
14
|
+
thumbSize?: number
|
|
15
|
+
/** percent (0-100) */
|
|
16
|
+
thumbLineWeight?: number
|
|
17
|
+
/** percent (0-100) */
|
|
18
|
+
thumbLineLength?: number
|
|
19
|
+
|
|
20
|
+
classes?: {
|
|
21
|
+
thumb?: string
|
|
22
|
+
thumbLine?: string
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function Thumb({
|
|
27
|
+
className,
|
|
28
|
+
|
|
29
|
+
thumb = 'currentColor',
|
|
30
|
+
thumbLine = 'currentColor',
|
|
31
|
+
thumbSize = 84,
|
|
32
|
+
thumbLineWeight = 6,
|
|
33
|
+
thumbLineLength = 35,
|
|
34
|
+
classes,
|
|
35
|
+
|
|
36
|
+
...props
|
|
37
|
+
}: Props & Omit<SVGProps<SVGSVGElement>, 'd' | keyof Props>) {
|
|
38
|
+
const angleRange = useKnobContext((s) => s.angleRange)
|
|
39
|
+
|
|
40
|
+
const p = useKnobContext((s) => s.p)
|
|
41
|
+
const r1 = useKnobContext((s) => s.r1)
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<svg className={clsx('tremolo-knob-thumb', classes?.thumb)} {...props}>
|
|
45
|
+
<circle cx="50%" cy="50%" r={`${thumbSize / 2}%`} fill={thumb} />
|
|
46
|
+
<line
|
|
47
|
+
className={clsx('tremolo-knob-thumb-line', classes?.thumbLine)}
|
|
48
|
+
x1="50%"
|
|
49
|
+
y1={`${(viewBoxSize - clamp(thumbSize, 0, 100)) / 2}%`}
|
|
50
|
+
x2="50%"
|
|
51
|
+
y2={`${thumbLineLength}%`}
|
|
52
|
+
stroke={thumbLine}
|
|
53
|
+
strokeWidth={`${thumbLineWeight}%`}
|
|
54
|
+
// NOTE
|
|
55
|
+
// https://bugs.webkit.org/show_bug.cgi?id=201854
|
|
56
|
+
// https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/transform-origin#browser_compatibility
|
|
57
|
+
style={{
|
|
58
|
+
transform: `rotate(${r1 + p * angleRange}deg)`,
|
|
59
|
+
transformOrigin: '50% 50%',
|
|
60
|
+
}}
|
|
61
|
+
/>
|
|
62
|
+
</svg>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
PropsWithChildren,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useRef,
|
|
7
|
+
} from 'react'
|
|
8
|
+
import { createStore, useStore } from 'zustand'
|
|
9
|
+
|
|
10
|
+
import { normalizeValue, radian } from '@tremolo-ui/functions'
|
|
11
|
+
|
|
12
|
+
export const viewBoxSize = 100
|
|
13
|
+
export const center = viewBoxSize / 2
|
|
14
|
+
|
|
15
|
+
type State = {
|
|
16
|
+
value: number
|
|
17
|
+
min: number
|
|
18
|
+
max: number
|
|
19
|
+
step: number
|
|
20
|
+
skew: number // | SkewFunction // TODO
|
|
21
|
+
startValue: number
|
|
22
|
+
angleRange: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type DrawingState = {
|
|
26
|
+
p: number // normalized value
|
|
27
|
+
r1: number // ロータリー開始位置
|
|
28
|
+
r2: number // activeLineの開始位置
|
|
29
|
+
r3: number // activeLineの終了位置
|
|
30
|
+
r4: number // ロータリー終了位置
|
|
31
|
+
x1: number
|
|
32
|
+
y1: number
|
|
33
|
+
x2: number
|
|
34
|
+
y2: number
|
|
35
|
+
x3: number
|
|
36
|
+
y3: number
|
|
37
|
+
x4: number
|
|
38
|
+
y4: number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function calcDrawingState({
|
|
42
|
+
value,
|
|
43
|
+
min,
|
|
44
|
+
max,
|
|
45
|
+
skew,
|
|
46
|
+
startValue,
|
|
47
|
+
angleRange,
|
|
48
|
+
}: State) {
|
|
49
|
+
const p = normalizeValue(value, min, max, skew)
|
|
50
|
+
const s = normalizeValue(startValue, min, max, skew)
|
|
51
|
+
|
|
52
|
+
const r1 = -angleRange / 2
|
|
53
|
+
const r2 = r1 + Math.min(p, s) * angleRange
|
|
54
|
+
const r3 = r1 + Math.max(p, s) * angleRange
|
|
55
|
+
const r4 = angleRange / 2
|
|
56
|
+
const x1 = center + center * Math.cos(radian(r1 - 90))
|
|
57
|
+
const y1 = center + center * Math.sin(radian(r1 - 90))
|
|
58
|
+
const x2 = center + center * Math.cos(radian(r2 - 90))
|
|
59
|
+
const y2 = center + center * Math.sin(radian(r2 - 90))
|
|
60
|
+
const x3 = center + center * Math.cos(radian(r3 - 90))
|
|
61
|
+
const y3 = center + center * Math.sin(radian(r3 - 90))
|
|
62
|
+
const x4 = center + center * Math.cos(radian(r4 - 90))
|
|
63
|
+
const y4 = center + center * Math.sin(radian(r4 - 90))
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
p,
|
|
67
|
+
r1,
|
|
68
|
+
r2,
|
|
69
|
+
r3,
|
|
70
|
+
r4,
|
|
71
|
+
x1,
|
|
72
|
+
y1,
|
|
73
|
+
x2,
|
|
74
|
+
y2,
|
|
75
|
+
x3,
|
|
76
|
+
y3,
|
|
77
|
+
x4,
|
|
78
|
+
y4,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type KnobStore = ReturnType<typeof createKnobStore>
|
|
83
|
+
|
|
84
|
+
const createKnobStore = (initProps: State) => {
|
|
85
|
+
return createStore<State & DrawingState>()(() => ({
|
|
86
|
+
...initProps,
|
|
87
|
+
...calcDrawingState(initProps),
|
|
88
|
+
}))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const KnobContext = createContext<KnobStore | null>(null)
|
|
92
|
+
|
|
93
|
+
export function KnobProvider({ children, ...props }: PropsWithChildren<State>) {
|
|
94
|
+
const storeRef = useRef<KnobStore>(null)
|
|
95
|
+
if (!storeRef.current) {
|
|
96
|
+
storeRef.current = createKnobStore(props)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (storeRef.current) {
|
|
101
|
+
storeRef.current.setState({ ...props, ...calcDrawingState(props) })
|
|
102
|
+
} else {
|
|
103
|
+
storeRef.current = createKnobStore(props)
|
|
104
|
+
}
|
|
105
|
+
}, [props])
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<KnobContext.Provider value={storeRef.current}>
|
|
109
|
+
{children}
|
|
110
|
+
</KnobContext.Provider>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function useKnobContext<T>(
|
|
115
|
+
selector: (state: State & DrawingState) => T,
|
|
116
|
+
): T {
|
|
117
|
+
const store = useContext(KnobContext)
|
|
118
|
+
if (!store) throw new Error('Missing KnobContext.Provider in the tree')
|
|
119
|
+
return useStore(store, selector)
|
|
120
|
+
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
.tremolo-knob {
|
|
2
2
|
display: inline-block;
|
|
3
|
-
cursor:
|
|
3
|
+
cursor: grab;
|
|
4
4
|
overflow: visible;
|
|
5
5
|
outline: 0;
|
|
6
6
|
|
|
7
7
|
&[aria-readonly='true'] {
|
|
8
8
|
cursor: not-allowed;
|
|
9
9
|
}
|
|
10
|
+
|
|
11
|
+
&[data-dragging='true'] {
|
|
12
|
+
cursor: inherit;
|
|
13
|
+
}
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
.tremolo-knob-active-line {
|