@tremolo-ui/react 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compose-refs.cjs +72 -0
- package/dist/compose-refs.cjs.map +1 -0
- package/dist/compose-refs.d.cts +43 -0
- package/dist/compose-refs.d.cts.map +1 -0
- package/dist/compose-refs.d.ts +43 -0
- package/dist/compose-refs.d.ts.map +1 -0
- package/dist/compose-refs.js +70 -0
- package/dist/compose-refs.js.map +1 -0
- package/dist/index.cjs +2272 -1650
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1461 -475
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1461 -475
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2258 -1648
- package/dist/index.js.map +1 -1
- package/package.json +32 -31
- package/src/components/AnimationCanvas/index.tsx +162 -194
- package/src/components/Knob/ActiveLine.tsx +9 -8
- package/src/components/Knob/InactiveLine.tsx +11 -14
- package/src/components/Knob/SVGRoot.tsx +10 -17
- package/src/components/Knob/Thumb.tsx +33 -11
- package/src/components/Knob/context.tsx +73 -89
- package/src/components/Knob/index.tsx +227 -105
- package/src/components/NumberInput/DecrementStepper.tsx +21 -50
- package/src/components/NumberInput/IncrementStepper.tsx +22 -50
- package/src/components/NumberInput/InputField.tsx +206 -0
- package/src/components/NumberInput/Stepper.tsx +150 -21
- package/src/components/NumberInput/StepperButton.tsx +95 -0
- package/src/components/NumberInput/context.tsx +104 -149
- package/src/components/NumberInput/index.tsx +461 -110
- package/src/components/Piano/index.tsx +413 -263
- package/src/components/Piano/keyboard-shortcuts.ts +61 -0
- package/src/components/PointsEditor/Background.tsx +19 -4
- package/src/components/PointsEditor/Container.tsx +92 -22
- package/src/components/PointsEditor/Point.tsx +293 -53
- package/src/components/PointsEditor/SelectionBox.tsx +54 -0
- package/src/components/PointsEditor/context.tsx +97 -75
- package/src/components/PointsEditor/index.tsx +497 -62
- package/src/components/Slider/Marks.tsx +84 -0
- package/src/components/Slider/MarksOption.tsx +115 -0
- package/src/components/Slider/Thumb.tsx +99 -54
- package/src/components/Slider/Track.tsx +73 -62
- package/src/components/Slider/context.tsx +40 -53
- package/src/components/Slider/index.tsx +237 -190
- package/src/components/Slider/type.ts +27 -9
- package/src/components/XYPad/Area.tsx +52 -33
- package/src/components/XYPad/Thumb.tsx +121 -60
- package/src/components/XYPad/context.tsx +49 -0
- package/src/components/XYPad/index.tsx +338 -246
- package/src/components/_util/Placement.tsx +68 -0
- package/src/components/_util/VisuallyHiddenRangeInput.tsx +24 -0
- package/src/components/_util/css-length.ts +13 -0
- package/src/{components/_util/composeRefs.tsx → compose-refs/index.tsx} +33 -5
- package/src/hooks/{useCallbackRef.ts → _internal/useCallbackRef.ts} +3 -1
- package/src/hooks/_internal/useCheckSteps.ts +170 -0
- package/src/hooks/useAnimationFrame.ts +17 -7
- package/src/hooks/useDrag.ts +39 -9
- package/src/hooks/useDragValue.ts +194 -0
- package/src/hooks/useEventListener.ts +11 -7
- package/src/hooks/useLongPress.ts +42 -8
- package/src/hooks/useMIDIAccess.ts +82 -11
- package/src/hooks/useMIDIInput.ts +45 -13
- package/src/hooks/useMIDIMessage.ts +23 -5
- package/src/hooks/useWheel.ts +29 -9
- package/src/index.ts +50 -27
- package/src/input-event.ts +32 -0
- package/dist/index.css +0 -536
- package/src/Globals.d.ts +0 -3
- package/src/components/AnimationCanvas/canvas.ts +0 -46
- package/src/components/Knob/index.css +0 -77
- package/src/components/NumberInput/InternalInput.tsx +0 -216
- package/src/components/NumberInput/index.css +0 -185
- package/src/components/NumberInput/type.ts +0 -58
- package/src/components/Piano/KeyLabel.tsx +0 -51
- package/src/components/Piano/context.tsx +0 -73
- package/src/components/Piano/index.css +0 -90
- package/src/components/Piano/key.tsx +0 -205
- package/src/components/Piano/keyboardShortcuts.ts +0 -31
- package/src/components/PointsEditor/index.css +0 -30
- package/src/components/Slider/Scale.tsx +0 -52
- package/src/components/Slider/ScaleOption.tsx +0 -103
- package/src/components/Slider/index.css +0 -112
- package/src/components/XYPad/index.css +0 -48
- package/src/components/_util/index.ts +0 -28
- package/src/components/_util/type.ts +0 -1
- package/src/hooks/useDragWithElement.ts +0 -100
- package/src/hooks/usePianoDrag.ts +0 -75
- package/src/hooks/useRefCallbackEvent.ts +0 -34
- package/src/styles/global.css +0 -19
package/package.json
CHANGED
|
@@ -1,55 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tremolo-ui/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "React component library for audio app",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"sideEffects":
|
|
7
|
-
"*.css"
|
|
8
|
-
],
|
|
6
|
+
"sideEffects": false,
|
|
9
7
|
"main": "dist/index.cjs",
|
|
10
8
|
"module": "dist/index.js",
|
|
11
9
|
"types": "dist/index.d.cts",
|
|
12
10
|
"scripts": {
|
|
13
|
-
"sb": "storybook dev
|
|
11
|
+
"sb": "storybook dev",
|
|
14
12
|
"typecheck": "tsc",
|
|
15
|
-
"build:sb": "storybook build",
|
|
13
|
+
"build:sb": "storybook build -o storybook-static/i/storybook-react && cp .storybook/_headers storybook-static/_headers",
|
|
16
14
|
"serve": "http-server ./storybook-static",
|
|
17
15
|
"lint": "eslint --fix --ext .ts,.tsx .",
|
|
18
16
|
"format": "prettier --write .",
|
|
19
|
-
"test": "
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
20
19
|
"build:package": "tsc && tsdown",
|
|
21
20
|
"clean": "npm run clean:build && npm run clean:sb",
|
|
22
21
|
"clean:build": "rimraf ./dist",
|
|
23
22
|
"clean:sb": "rimraf ./storybook-static"
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|
|
26
|
-
"@storybook/addon-a11y": "10.
|
|
27
|
-
"@storybook/addon-
|
|
28
|
-
"@storybook/
|
|
25
|
+
"@storybook/addon-a11y": "10.6.0",
|
|
26
|
+
"@storybook/addon-docs": "10.6.0",
|
|
27
|
+
"@storybook/addon-links": "10.6.0",
|
|
28
|
+
"@storybook/react-vite": "10.6.0",
|
|
29
|
+
"@testing-library/jest-dom": "7.0.1",
|
|
29
30
|
"@testing-library/react": "16.3.3",
|
|
30
|
-
"@types/react": "19.
|
|
31
|
-
"@types/react-dom": "19.
|
|
32
|
-
"jest-environment-jsdom": "29.7.0",
|
|
31
|
+
"@types/react": "19.3.0",
|
|
32
|
+
"@types/react-dom": "19.3.0",
|
|
33
33
|
"jotai": "2.20.3",
|
|
34
|
-
"react": "19.
|
|
35
|
-
"react-dom": "19.
|
|
36
|
-
"storybook": "10.
|
|
37
|
-
"tone": "15.1.22"
|
|
38
|
-
"@storybook/addon-docs": "10.5.10"
|
|
34
|
+
"react": "19.3.0",
|
|
35
|
+
"react-dom": "19.3.0",
|
|
36
|
+
"storybook": "10.6.0",
|
|
37
|
+
"tone": "15.1.22"
|
|
39
38
|
},
|
|
40
39
|
"peerDependencies": {
|
|
41
40
|
"react": "^18 ||^19",
|
|
42
41
|
"react-dom": "^18 || ^19"
|
|
43
42
|
},
|
|
44
43
|
"dependencies": {
|
|
45
|
-
"@tremolo-ui/dom": "^0.
|
|
46
|
-
"@tremolo-ui/functions": "^0.
|
|
47
|
-
"clsx": "^2.1.1",
|
|
48
|
-
"zustand": "^5.0.3"
|
|
44
|
+
"@tremolo-ui/dom": "^0.6.0",
|
|
45
|
+
"@tremolo-ui/functions": "^0.6.0"
|
|
49
46
|
},
|
|
50
47
|
"files": [
|
|
51
48
|
"dist",
|
|
52
|
-
"src"
|
|
49
|
+
"src",
|
|
50
|
+
"!src/**/*.test.{ts,tsx}",
|
|
51
|
+
"!src/**/*.stories.{ts,tsx}"
|
|
53
52
|
],
|
|
54
53
|
"publishConfig": {
|
|
55
54
|
"access": "public"
|
|
@@ -83,13 +82,15 @@
|
|
|
83
82
|
"default": "./dist/index.js"
|
|
84
83
|
}
|
|
85
84
|
},
|
|
86
|
-
"./
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
"./compose-refs": {
|
|
86
|
+
"require": {
|
|
87
|
+
"types": "./dist/compose-refs.d.cts",
|
|
88
|
+
"default": "./dist/compose-refs.cjs"
|
|
89
|
+
},
|
|
90
|
+
"import": {
|
|
91
|
+
"types": "./dist/compose-refs.d.ts",
|
|
92
|
+
"default": "./dist/compose-refs.js"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
94
95
|
}
|
|
95
96
|
}
|
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import clsx from 'clsx'
|
|
2
1
|
import {
|
|
3
2
|
ComponentPropsWithoutRef,
|
|
4
|
-
|
|
5
|
-
RefObject,
|
|
6
|
-
useCallback,
|
|
3
|
+
forwardRef,
|
|
7
4
|
useEffect,
|
|
8
5
|
useRef,
|
|
6
|
+
useState,
|
|
9
7
|
} from 'react'
|
|
10
8
|
|
|
11
9
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
createAnimationCanvas,
|
|
11
|
+
type AnimationCanvasInstance,
|
|
12
|
+
type AnimationFrame,
|
|
13
|
+
} from '@tremolo-ui/dom'
|
|
14
|
+
|
|
15
|
+
import { useComposedRefs } from '../../compose-refs'
|
|
17
16
|
|
|
18
17
|
export type InitFunction = (
|
|
19
18
|
context: CanvasRenderingContext2D,
|
|
@@ -27,216 +26,185 @@ export type InitFunction = (
|
|
|
27
26
|
|
|
28
27
|
export type DrawFunction = (
|
|
29
28
|
context: CanvasRenderingContext2D,
|
|
30
|
-
option:
|
|
31
|
-
/** current canvas width */
|
|
32
|
-
width: number
|
|
33
|
-
/** current canvas height */
|
|
34
|
-
height: number
|
|
35
|
-
/** frame count */
|
|
36
|
-
count: number
|
|
37
|
-
/** delta time (ms) */
|
|
38
|
-
deltaTime: number
|
|
39
|
-
/** elapsed time (ms) */
|
|
40
|
-
elapsedTime: number
|
|
41
|
-
/** frame per second */
|
|
42
|
-
fps: number
|
|
43
|
-
},
|
|
29
|
+
option: AnimationFrame,
|
|
44
30
|
) => void
|
|
45
31
|
|
|
46
|
-
export interface
|
|
32
|
+
export interface AnimationCanvasCommonProps {
|
|
33
|
+
/**
|
|
34
|
+
* Draw one frame. It is given the 2D context and the frame: the size in CSS
|
|
35
|
+
* pixels, `count`, `deltaTime`, `elapsedTime` and `fps`.
|
|
36
|
+
*
|
|
37
|
+
* A new function replaces the old one in place, so writing it inline does
|
|
38
|
+
* not restart anything.
|
|
39
|
+
*/
|
|
47
40
|
draw: DrawFunction
|
|
41
|
+
/**
|
|
42
|
+
* Called once before the first frame, with the context and the size in CSS
|
|
43
|
+
* pixels. Set up what every frame shares here.
|
|
44
|
+
*/
|
|
48
45
|
init?: InitFunction
|
|
46
|
+
/**
|
|
47
|
+
* Redraw on every animation frame. Turn it off to draw only when there is a
|
|
48
|
+
* reason to: when the canvas mounts, when it is resized, and when the
|
|
49
|
+
* component re-renders.
|
|
50
|
+
*
|
|
51
|
+
* @default true
|
|
52
|
+
*/
|
|
49
53
|
animate?: boolean
|
|
50
54
|
/**
|
|
55
|
+
* Read once, when the 2D context is created, so changing it later has no
|
|
56
|
+
* effect. Passing a fresh object on every render is therefore harmless.
|
|
57
|
+
*
|
|
51
58
|
* @see https://developer.mozilla.org/docs/Web/API/HTMLCanvasElement/getContext#contextattributes
|
|
52
59
|
*/
|
|
53
60
|
options?: CanvasRenderingContext2DSettings
|
|
61
|
+
/**
|
|
62
|
+
* Carry the drawing across a resize, so that the canvas does not blank for a
|
|
63
|
+
* frame while the new size is drawn. A fixed canvas is resized too, when its
|
|
64
|
+
* `width` or `height` changes.
|
|
65
|
+
*
|
|
66
|
+
* @default true
|
|
67
|
+
*/
|
|
68
|
+
reduceFlickering?: boolean
|
|
54
69
|
}
|
|
55
70
|
|
|
56
|
-
|
|
71
|
+
/** A canvas of the size given in CSS pixels. This is the default. */
|
|
72
|
+
export interface AnimationCanvasFixedProps {
|
|
73
|
+
/** Leave it out, or pass `false`, for a canvas of the size below. */
|
|
74
|
+
resizable?: false
|
|
75
|
+
/**
|
|
76
|
+
* Width of the canvas in CSS pixels.
|
|
77
|
+
* @default 100
|
|
78
|
+
*/
|
|
57
79
|
width?: number
|
|
80
|
+
/**
|
|
81
|
+
* Height of the canvas in CSS pixels.
|
|
82
|
+
* @default 100
|
|
83
|
+
*/
|
|
58
84
|
height?: number
|
|
59
85
|
}
|
|
60
86
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
87
|
+
/**
|
|
88
|
+
* A canvas that follows the size of its parent element, so the parent needs a
|
|
89
|
+
* size of its own.
|
|
90
|
+
*/
|
|
91
|
+
export interface AnimationCanvasResizableProps {
|
|
92
|
+
/**
|
|
93
|
+
* Follow the size of the parent element instead of `width` and `height`.
|
|
94
|
+
* Switching it rebuilds the canvas.
|
|
95
|
+
*/
|
|
96
|
+
resizable: true
|
|
97
|
+
/** Not accepted: the size comes from the parent element. */
|
|
98
|
+
width?: never
|
|
99
|
+
/** Not accepted: the size comes from the parent element. */
|
|
100
|
+
height?: never
|
|
64
101
|
}
|
|
65
102
|
|
|
66
|
-
export type AnimationCanvasProps =
|
|
67
|
-
|
|
68
|
-
RelativeSizingProps
|
|
103
|
+
export type AnimationCanvasProps = AnimationCanvasCommonProps &
|
|
104
|
+
(AnimationCanvasFixedProps | AnimationCanvasResizableProps)
|
|
69
105
|
|
|
70
|
-
|
|
71
|
-
* A simple animatable canvas with requestAnimationFrame()
|
|
72
|
-
*/
|
|
73
|
-
export function AnimationCanvas(
|
|
74
|
-
props: CommonProps &
|
|
75
|
-
AbsoluteSizingProps &
|
|
76
|
-
Omit<ComponentPropsWithoutRef<'canvas'>, keyof AnimationCanvasProps>,
|
|
77
|
-
): ReactElement
|
|
78
|
-
export function AnimationCanvas(
|
|
79
|
-
pros: CommonProps &
|
|
80
|
-
RelativeSizingProps &
|
|
81
|
-
Omit<ComponentPropsWithoutRef<'canvas'>, keyof AnimationCanvasProps>,
|
|
82
|
-
): ReactElement
|
|
83
|
-
export function AnimationCanvas({
|
|
84
|
-
// common
|
|
85
|
-
draw,
|
|
86
|
-
init,
|
|
87
|
-
animate = true,
|
|
88
|
-
options,
|
|
89
|
-
// absolute
|
|
90
|
-
width: _width = 100,
|
|
91
|
-
height: _height = 100,
|
|
92
|
-
// relative
|
|
93
|
-
relativeSize,
|
|
94
|
-
reduceFlickering = true,
|
|
95
|
-
// canvas props
|
|
96
|
-
className,
|
|
97
|
-
onContextMenu = (event) => event.preventDefault(),
|
|
98
|
-
...props
|
|
99
|
-
}: AnimationCanvasProps &
|
|
106
|
+
type Props = AnimationCanvasProps &
|
|
100
107
|
Omit<
|
|
101
108
|
ComponentPropsWithoutRef<'canvas'>,
|
|
102
|
-
keyof
|
|
103
|
-
>
|
|
104
|
-
const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
105
|
-
const memoCanvasRef = useRef<HTMLCanvasElement>(null)
|
|
106
|
-
const reqIdRef = useRef(-1)
|
|
107
|
-
const widthRef = useRef(0)
|
|
108
|
-
const heightRef = useRef(0)
|
|
109
|
-
const deltaMemoRef = useRef(-1)
|
|
110
|
-
const startTimeRef = useRef(-1)
|
|
111
|
-
|
|
112
|
-
const loop = useCallback(
|
|
113
|
-
(
|
|
114
|
-
context: CanvasRenderingContext2D,
|
|
115
|
-
width: RefObject<number>,
|
|
116
|
-
height: RefObject<number>,
|
|
117
|
-
count: number,
|
|
118
|
-
) => {
|
|
119
|
-
const now = performance.now()
|
|
120
|
-
const deltaTime = now - deltaMemoRef.current
|
|
121
|
-
const elapsedTime = now - startTimeRef.current
|
|
122
|
-
deltaMemoRef.current = now
|
|
123
|
-
if (animate) {
|
|
124
|
-
reqIdRef.current = requestAnimationFrame(() =>
|
|
125
|
-
loop(context, width, height, count + 1),
|
|
126
|
-
)
|
|
127
|
-
}
|
|
128
|
-
draw(context, {
|
|
129
|
-
width: width.current,
|
|
130
|
-
height: height.current,
|
|
131
|
-
count: count + 1,
|
|
132
|
-
deltaTime,
|
|
133
|
-
elapsedTime,
|
|
134
|
-
fps: 1000 / deltaTime,
|
|
135
|
-
})
|
|
136
|
-
},
|
|
137
|
-
[draw, animate],
|
|
138
|
-
)
|
|
109
|
+
keyof AnimationCanvasCommonProps | 'resizable' | 'width' | 'height'
|
|
110
|
+
>
|
|
139
111
|
|
|
112
|
+
/**
|
|
113
|
+
* A simple animatable canvas with requestAnimationFrame()
|
|
114
|
+
*/
|
|
115
|
+
export const AnimationCanvas = /* @__PURE__ */ forwardRef<
|
|
116
|
+
HTMLCanvasElement,
|
|
117
|
+
Props
|
|
118
|
+
>(function AnimationCanvas(
|
|
119
|
+
{
|
|
120
|
+
// common
|
|
121
|
+
draw,
|
|
122
|
+
init,
|
|
123
|
+
animate = true,
|
|
124
|
+
options,
|
|
125
|
+
reduceFlickering = true,
|
|
126
|
+
// fixed
|
|
127
|
+
width = 100,
|
|
128
|
+
height = 100,
|
|
129
|
+
// resizable
|
|
130
|
+
resizable = false,
|
|
131
|
+
// canvas props
|
|
132
|
+
className,
|
|
133
|
+
onContextMenu = (event) => event.preventDefault(),
|
|
134
|
+
...props
|
|
135
|
+
},
|
|
136
|
+
forwardedRef,
|
|
137
|
+
) {
|
|
138
|
+
// See useDrag for why the node is held in state rather than a ref: an inline
|
|
139
|
+
// ref would be re-attached on every render and tear the instance down.
|
|
140
|
+
const [node, setNode] = useState<HTMLCanvasElement | null>(null)
|
|
141
|
+
// The caller's ref gets the same element.
|
|
142
|
+
const composedRef = useComposedRefs<HTMLCanvasElement>(forwardedRef, setNode)
|
|
143
|
+
|
|
144
|
+
// Read when the instance is created. The effect below keeps it current, and
|
|
145
|
+
// runs right after, so a stale handler is replaced within the same commit.
|
|
146
|
+
const latest = useRef({
|
|
147
|
+
draw,
|
|
148
|
+
init,
|
|
149
|
+
animate,
|
|
150
|
+
width,
|
|
151
|
+
height,
|
|
152
|
+
reduceFlickering,
|
|
153
|
+
options,
|
|
154
|
+
})
|
|
155
|
+
const instanceRef = useRef<AnimationCanvasInstance | null>(null)
|
|
156
|
+
|
|
157
|
+
// Runs before the creation effect below. This keeps a rebuilt instance on
|
|
158
|
+
// the current render's size and handlers while updating an existing one in
|
|
159
|
+
// place on ordinary renders.
|
|
140
160
|
useEffect(() => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
widthRef.current = width
|
|
150
|
-
heightRef.current = height
|
|
151
|
-
|
|
152
|
-
if (init) init(context, { width, height })
|
|
153
|
-
const now = performance.now()
|
|
154
|
-
deltaMemoRef.current = now
|
|
155
|
-
startTimeRef.current = now
|
|
156
|
-
loop(context, widthRef, heightRef, -1)
|
|
161
|
+
latest.current = {
|
|
162
|
+
draw,
|
|
163
|
+
init,
|
|
164
|
+
animate,
|
|
165
|
+
width,
|
|
166
|
+
height,
|
|
167
|
+
reduceFlickering,
|
|
168
|
+
options,
|
|
157
169
|
}
|
|
170
|
+
instanceRef.current?.update({
|
|
171
|
+
animate,
|
|
172
|
+
size: { width, height },
|
|
173
|
+
reduceFlickering,
|
|
174
|
+
})
|
|
175
|
+
})
|
|
158
176
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
memoContext.scale(1 / dpr, 1 / dpr)
|
|
178
|
-
if (canvas.width > 0 && canvas.height > 0) {
|
|
179
|
-
memoContext.drawImage(canvas, 0, 0)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
setDprConfig(canvas, context, w, h, dpr)
|
|
184
|
-
widthRef.current = w
|
|
185
|
-
heightRef.current = h
|
|
186
|
-
|
|
187
|
-
if (
|
|
188
|
-
reduceFlickering &&
|
|
189
|
-
memoContext &&
|
|
190
|
-
memoCanvas &&
|
|
191
|
-
memoCanvas.width > 0 &&
|
|
192
|
-
memoCanvas.height > 0
|
|
193
|
-
) {
|
|
194
|
-
for (const prop of drawingState) {
|
|
195
|
-
;(context[prop] as DrawingStateValue) = contextMemo[prop]
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
context.drawImage(memoContext.canvas, 0, 0)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (!animate) {
|
|
202
|
-
// re rendering
|
|
203
|
-
loop(context, widthRef, heightRef, -1)
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
})
|
|
207
|
-
ro.observe(parent)
|
|
208
|
-
|
|
209
|
-
const width = parent.clientWidth
|
|
210
|
-
const height = parent.clientHeight
|
|
211
|
-
firstRendering(width, height)
|
|
212
|
-
|
|
213
|
-
return () => {
|
|
214
|
-
if (reqIdRef.current) cancelAnimationFrame(reqIdRef.current)
|
|
215
|
-
ro.disconnect()
|
|
216
|
-
}
|
|
217
|
-
} else {
|
|
218
|
-
const { width, height } = canvas.getBoundingClientRect()
|
|
219
|
-
firstRendering(width, height)
|
|
220
|
-
|
|
221
|
-
return () => {
|
|
222
|
-
if (reqIdRef.current) cancelAnimationFrame(reqIdRef.current)
|
|
223
|
-
}
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
if (!node) return
|
|
179
|
+
|
|
180
|
+
const current = latest.current
|
|
181
|
+
const instance = createAnimationCanvas(node, {
|
|
182
|
+
draw: (context, frame) => latest.current.draw(context, frame),
|
|
183
|
+
init: (context, size) => latest.current.init?.(context, size),
|
|
184
|
+
animate: current.animate,
|
|
185
|
+
size: { width: current.width, height: current.height },
|
|
186
|
+
reduceFlickering: current.reduceFlickering,
|
|
187
|
+
resizable,
|
|
188
|
+
contextAttributes: current.options,
|
|
189
|
+
})
|
|
190
|
+
instanceRef.current = instance
|
|
191
|
+
|
|
192
|
+
return () => {
|
|
193
|
+
instanceRef.current = null
|
|
194
|
+
instance.destroy()
|
|
224
195
|
}
|
|
225
|
-
|
|
196
|
+
// Only `resizable` decides how the instance is wired, so it is the one
|
|
197
|
+
// setting that rebuilds it. `options` is read from the ref above rather
|
|
198
|
+
// than depended on: it is almost always written inline, and depending on
|
|
199
|
+
// it would tear the canvas down on every render.
|
|
200
|
+
}, [node, resizable])
|
|
226
201
|
|
|
227
202
|
return (
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
onContextMenu={onContextMenu}
|
|
235
|
-
{...props}
|
|
236
|
-
></canvas>
|
|
237
|
-
{relativeSize && reduceFlickering && (
|
|
238
|
-
<canvas style={{ display: 'none' }} ref={memoCanvasRef}></canvas>
|
|
239
|
-
)}
|
|
240
|
-
</>
|
|
203
|
+
<canvas
|
|
204
|
+
className={className}
|
|
205
|
+
ref={composedRef}
|
|
206
|
+
onContextMenu={onContextMenu}
|
|
207
|
+
{...props}
|
|
208
|
+
/>
|
|
241
209
|
)
|
|
242
|
-
}
|
|
210
|
+
})
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import clsx from 'clsx'
|
|
2
1
|
import { SVGProps } from 'react'
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import { useCheckPlacement } from '../_util/Placement'
|
|
4
|
+
|
|
5
|
+
import { arcPath, arcRadius, useKnobContext } from './context'
|
|
5
6
|
|
|
6
7
|
export function ActiveLine({
|
|
7
8
|
stroke = 'currentColor',
|
|
@@ -9,17 +10,17 @@ export function ActiveLine({
|
|
|
9
10
|
className,
|
|
10
11
|
...props
|
|
11
12
|
}: Omit<SVGProps<SVGPathElement>, 'd'>) {
|
|
13
|
+
useCheckPlacement('Knob.ActiveLine', 'Knob.SVGRoot')
|
|
14
|
+
|
|
12
15
|
const r2 = useKnobContext((s) => s.r2)
|
|
13
16
|
const r3 = useKnobContext((s) => s.r3)
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const x3 = useKnobContext((s) => s.x3)
|
|
17
|
-
const y3 = useKnobContext((s) => s.y3)
|
|
17
|
+
|
|
18
|
+
const radius = arcRadius(strokeWidth)
|
|
18
19
|
|
|
19
20
|
return (
|
|
20
21
|
<path
|
|
21
|
-
className={
|
|
22
|
-
d={
|
|
22
|
+
className={className}
|
|
23
|
+
d={arcPath(r2, r3, radius)}
|
|
23
24
|
fill="none"
|
|
24
25
|
stroke={stroke}
|
|
25
26
|
strokeWidth={strokeWidth}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import clsx from 'clsx'
|
|
2
1
|
import { SVGProps } from 'react'
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import { useCheckPlacement } from '../_util/Placement'
|
|
4
|
+
|
|
5
|
+
import { arcPath, arcRadius, useKnobContext } from './context'
|
|
5
6
|
|
|
6
7
|
export function InactiveLine({
|
|
7
8
|
stroke = 'currentColor',
|
|
@@ -9,6 +10,8 @@ export function InactiveLine({
|
|
|
9
10
|
className,
|
|
10
11
|
...props
|
|
11
12
|
}: Omit<SVGProps<SVGPathElement>, 'd'>) {
|
|
13
|
+
useCheckPlacement('Knob.InactiveLine', 'Knob.SVGRoot')
|
|
14
|
+
|
|
12
15
|
const min = useKnobContext((s) => s.min)
|
|
13
16
|
const max = useKnobContext((s) => s.max)
|
|
14
17
|
const startValue = useKnobContext((s) => s.startValue)
|
|
@@ -17,21 +20,15 @@ export function InactiveLine({
|
|
|
17
20
|
const r2 = useKnobContext((s) => s.r2)
|
|
18
21
|
const r3 = useKnobContext((s) => s.r3)
|
|
19
22
|
const r4 = useKnobContext((s) => s.r4)
|
|
20
|
-
|
|
21
|
-
const
|
|
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)
|
|
23
|
+
|
|
24
|
+
const radius = arcRadius(strokeWidth)
|
|
28
25
|
|
|
29
26
|
return (
|
|
30
27
|
<>
|
|
31
28
|
{startValue > min && (
|
|
32
29
|
<path
|
|
33
|
-
className={
|
|
34
|
-
d={
|
|
30
|
+
className={className}
|
|
31
|
+
d={arcPath(r1, r2, radius)}
|
|
35
32
|
fill="none"
|
|
36
33
|
stroke={stroke}
|
|
37
34
|
strokeWidth={strokeWidth}
|
|
@@ -40,8 +37,8 @@ export function InactiveLine({
|
|
|
40
37
|
)}
|
|
41
38
|
{startValue < max && (
|
|
42
39
|
<path
|
|
43
|
-
className={
|
|
44
|
-
d={
|
|
40
|
+
className={className}
|
|
41
|
+
d={arcPath(r3, r4, radius)}
|
|
45
42
|
fill="none"
|
|
46
43
|
stroke={stroke}
|
|
47
44
|
strokeWidth={strokeWidth}
|
|
@@ -1,39 +1,32 @@
|
|
|
1
|
-
import { SVGProps } from 'react'
|
|
1
|
+
import { ReactNode, SVGProps } from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { Placement } from '../_util/Placement'
|
|
4
4
|
|
|
5
|
-
import { ActiveLine } from './ActiveLine'
|
|
6
5
|
import { viewBoxSize } from './context'
|
|
7
|
-
import { InactiveLine } from './InactiveLine'
|
|
8
|
-
import { Thumb } from './Thumb'
|
|
9
6
|
|
|
10
|
-
interface
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
export interface KnobSVGRootProps {
|
|
8
|
+
/**
|
|
9
|
+
* `<Knob.InactiveLine />`, `<Knob.ActiveLine />` and `<Knob.Thumb />` go
|
|
10
|
+
* here, in the order you want them painted.
|
|
11
|
+
*/
|
|
12
|
+
children: ReactNode
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function SVGRoot({
|
|
16
16
|
children,
|
|
17
17
|
style,
|
|
18
18
|
...props
|
|
19
|
-
}:
|
|
19
|
+
}: KnobSVGRootProps & Omit<SVGProps<SVGSVGElement>, keyof KnobSVGRootProps>) {
|
|
20
20
|
return (
|
|
21
21
|
<svg
|
|
22
22
|
viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}
|
|
23
23
|
style={{
|
|
24
24
|
display: 'block',
|
|
25
|
-
overflow: 'visible',
|
|
26
25
|
...style,
|
|
27
26
|
}}
|
|
28
27
|
{...props}
|
|
29
28
|
>
|
|
30
|
-
{children
|
|
31
|
-
<>
|
|
32
|
-
<InactiveLine />
|
|
33
|
-
<ActiveLine />
|
|
34
|
-
<Thumb />
|
|
35
|
-
</>
|
|
36
|
-
)}
|
|
29
|
+
<Placement name="Knob.SVGRoot">{children}</Placement>
|
|
37
30
|
</svg>
|
|
38
31
|
)
|
|
39
32
|
}
|