Skip to content

Commit e73a615

Browse files
committed
fix: fix onPointer props issue.
1 parent 25b1522 commit e73a615

File tree

5 files changed

+290
-10
lines changed

5 files changed

+290
-10
lines changed

core/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ react-signature
33

44
[![Build & Deploy](https://github.com/uiwjs/react-signature/actions/workflows/ci.yml/badge.svg)](https://github.com/uiwjs/react-signature/actions/workflows/ci.yml)
55
[![Coverage Status](https://uiwjs.github.io/react-signature/badges.svg)](https://uiwjs.github.io/react-signature/lcov-report/)
6+
[![npm version](https://img.shields.io/npm/v/@uiw/react-signature.svg)](https://www.npmjs.com/package/@uiw/react-signature)
67

78
A signature board component for react.
89

@@ -82,6 +83,40 @@ export default function App() {
8283
}
8384
```
8485

86+
## Create Points
87+
88+
```jsx mdx:preview
89+
import React, { useRef, useState, useCallback, useEffect } from "react";
90+
import Signature from '@uiw/react-signature';
91+
92+
export default function App() {
93+
const $svg = useRef(null);
94+
const [points, setPoints] = useState([])
95+
const handle = (evn) => {
96+
const parentElement = $svg.current
97+
while (parentElement.firstChild) {
98+
parentElement.removeChild(parentElement.firstChild);
99+
}
100+
setPoints([])
101+
}
102+
const handlePoints = (data) => {
103+
if (data.length > 0) {
104+
setPoints([ ...points, JSON.stringify(data) ]);
105+
}
106+
}
107+
return (
108+
<>
109+
<Signature ref={$svg} onPointer={handlePoints} />
110+
<button onClick={handle}>Clear</button>
111+
{points.map((item, idx) => {
112+
return <pre key={idx} style={{ whiteSpace: 'pre-wrap' }}>{item}</pre>
113+
})}
114+
</>
115+
);
116+
}
117+
```
118+
119+
85120
## Props
86121

87122
```ts

core/src/index.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
22
import { getStroke, type StrokeOptions } from 'perfect-freehand';
3-
import { getSvgPathFromStroke, getBoundingClientRect, getClinetXY, defaultOptions, defaultStyle } from './utils';
3+
import {
4+
getSvgPathFromStroke,
5+
getBoundingClientRect,
6+
getClinetXY,
7+
defaultOptions,
8+
defaultStyle,
9+
useEvent,
10+
} from './utils';
411

512
export * from 'perfect-freehand';
613
export * from './utils';
@@ -30,33 +37,35 @@ const Signature = forwardRef<SVGSVGElement, SignatureProps>((props, ref) => {
3037

3138
useImperativeHandle<SVGSVGElement | null, SVGSVGElement | null>(ref, () => $svg.current, [$svg.current]);
3239

33-
const handlePointerDown = (e: React.PointerEvent<SVGSVGElement>) => {
40+
const handlePointerDown = useEvent((e: React.PointerEvent<SVGSVGElement>) => {
3441
const { offsetY, offsetX } = getBoundingClientRect($svg.current);
3542
const clientX = e.clientX || e.nativeEvent.clientX;
3643
const clientY = e.clientY || e.nativeEvent.clientY;
3744
pointsRef.current = [[clientX - offsetX, clientY - offsetY]];
3845
const pathElm = document.createElementNS('http://www.w3.org/2000/svg', 'path');
3946
$path.current = pathElm;
4047
$svg.current!.appendChild(pathElm);
41-
};
48+
});
4249

43-
const handlePointerMove = (e: PointerEvent) => {
50+
const handlePointerMove = useEvent((e: PointerEvent) => {
4451
if ($path.current) {
52+
const resultOptions = { ...defaultOptions, ...options };
53+
console.log(resultOptions);
4554
const { offsetY, offsetX } = getBoundingClientRect($svg.current);
4655
const { clientX, clientY } = getClinetXY(e);
4756
pointsRef.current = [...pointsRef.current!, [clientX - offsetX, clientY - offsetY]];
48-
const stroke = getStroke(pointsRef.current!, { ...defaultOptions, ...options });
57+
const stroke = getStroke(pointsRef.current!, resultOptions);
4958
const pathData = getSvgPathFromStroke(stroke);
5059
$path.current?.setAttribute('d', pathData);
5160
}
52-
};
61+
});
5362

54-
const handlePointerUp = () => {
63+
const handlePointerUp = useEvent(() => {
5564
let result = pointsRef.current || [];
56-
onPointer && onPointer(result);
65+
onPointer && props.onPointer!(result);
5766
$path.current = undefined;
5867
pointsRef.current = undefined;
59-
};
68+
});
6069

6170
useEffect(() => {
6271
if (readonly) return;

core/src/utils.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useCallback, useEffect, useRef } from 'react';
12
import { type StrokeOptions } from 'perfect-freehand';
23

34
/**
@@ -44,7 +45,6 @@ export const getBoundingClientRect = (el: SVGSVGElement | null) => {
4445
};
4546

4647
export const getClinetXY = ({ clientX, clientY }: PointerEvent) => {
47-
// const { clientX, clientY } = e.type === 'touchmove' ? (e as TouchEvent).touches[0] : (e as MouseEvent);
4848
return { clientX, clientY };
4949
};
5050

@@ -56,3 +56,14 @@ export const defaultStyle: React.CSSProperties = {
5656
height: '100%',
5757
backgroundColor: 'var(--w-signature-background)',
5858
} as React.CSSProperties;
59+
60+
// Saves incoming handler to the ref in order to avoid "useCallback hell"
61+
export function useEvent<K>(handler?: (event: K) => void): (event: K) => void {
62+
const callbackRef = useRef(handler);
63+
64+
useEffect(() => {
65+
callbackRef.current = handler;
66+
});
67+
68+
return useCallback((event: K) => callbackRef.current && callbackRef.current(event), []);
69+
}

website/src/Example.tsx

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import Signature, { defaultOptions, StrokeOptions, getStroke, getSvgPathFromStroke } from '@uiw/react-signature';
2+
import { useRef, useState } from 'react';
3+
import styled from 'styled-components';
4+
5+
const Wrapper = styled.div`
6+
border-radius: 4px;
7+
flex: 1;
8+
`;
9+
10+
const points1 = [
11+
[79.51953125, 53.6640625],
12+
[80.234375, 53.08984375],
13+
[82.796875, 51.59375],
14+
[86.5, 50.140625],
15+
[90.828125, 49.328125],
16+
[97.3203125, 48.8671875],
17+
[104.25390625, 49.52734375],
18+
[112.109375, 52.39453125],
19+
[120.70703125, 58.33203125],
20+
[126.0234375, 66.8828125],
21+
[126.30859375, 80.859375],
22+
[121.109375, 98.12109375],
23+
[111.484375, 112.98046875],
24+
[101.30078125, 122.02734375],
25+
[91.9765625, 125.265625],
26+
[85.08203125, 125.6796875],
27+
[81.6640625, 124.00390625],
28+
[80.19921875, 120.87890625],
29+
[80.6953125, 115.671875],
30+
[84.8125, 106.984375],
31+
[92.9921875, 97.24609375],
32+
[105.1640625, 90.71875],
33+
[120.99609375, 87.29296875],
34+
[136.98046875, 87.63671875],
35+
[149.97265625, 90.87109375],
36+
[155.30078125, 94.56640625],
37+
[155.94921875, 99.23828125],
38+
[155.328125, 104.59765625],
39+
[151.8046875, 111.31640625],
40+
[146.5625, 117.6015625],
41+
[140.90234375, 121.62890625],
42+
[137.8515625, 123.453125],
43+
[137.328125, 123.40625],
44+
[136.95703125, 122.9453125],
45+
[137.13671875, 121.32421875],
46+
[140.44921875, 115.890625],
47+
[148.14453125, 106.15234375],
48+
[157.15625, 96.91796875],
49+
[173.70703125, 83.3203125],
50+
[191.55859375, 70.1015625],
51+
[199.70703125, 65.35546875],
52+
[203.46875, 63.515625],
53+
[204.6484375, 63.234375],
54+
[204.1796875, 64.6171875],
55+
[202.94921875, 67.875],
56+
[199.71875, 74.88671875],
57+
[192.64453125, 88.46875],
58+
[184.30078125, 105.28125],
59+
[178.7421875, 116.85546875],
60+
[175.46484375, 121.59765625],
61+
[173.140625, 122.93359375],
62+
[171.39453125, 122.44140625],
63+
[170.09765625, 119.9375],
64+
[169.9375, 112.65234375],
65+
[173.2265625, 99.640625],
66+
[182.1953125, 82.4453125],
67+
[193.19140625, 67.78515625],
68+
[203.48046875, 58.6953125],
69+
[212.25, 53.43359375],
70+
[219.55078125, 51.18359375],
71+
[224.8046875, 50.40234375],
72+
[227.41015625, 50.87890625],
73+
[229.0859375, 52.3984375],
74+
[230.19140625, 55.34765625],
75+
[231.05078125, 60.60546875],
76+
[231.3515625, 67.671875],
77+
[230.57421875, 73.42578125],
78+
[228.1953125, 78.00390625],
79+
[224.86328125, 80.7265625],
80+
[221.46875, 81.40234375],
81+
[218.390625, 79.51953125],
82+
[212.5859375, 72.31640625],
83+
[202.6640625, 61.04296875],
84+
[190.8125, 47.4296875],
85+
[182.90234375, 37.1953125],
86+
[180.26171875, 32.00390625],
87+
[179.12890625, 29.2421875],
88+
[179.1796875, 28.84765625],
89+
[179.921875, 29.28125],
90+
[181.41015625, 30.5234375],
91+
[185.00390625, 33.3671875],
92+
[193.6015625, 39.0859375],
93+
[213.0859375, 51.1328125],
94+
[249.11328125, 72.74609375],
95+
[296.953125, 102.0859375],
96+
[342.8203125, 130.1328125],
97+
[381.08984375, 152.51171875],
98+
[408, 167.734375],
99+
[419.70703125, 173.66796875],
100+
[422.6171875, 174.94921875],
101+
[423.01953125, 175.1484375],
102+
];
103+
const points2 = [
104+
[277.48828125, 62.6015625],
105+
[277.4453125, 62.64453125],
106+
[276.41015625, 64.51953125],
107+
[269.54296875, 74.9921875],
108+
[253.75, 96.70703125],
109+
[232.1171875, 122.76171875],
110+
[214.55859375, 140.99609375],
111+
[206.01171875, 147.85546875],
112+
[201.65625, 150.41015625],
113+
[200.0078125, 151.3125],
114+
[200.0078125, 151.2265625],
115+
[200.15625, 151.0234375],
116+
];
117+
118+
export const ExampleSignature = () => {
119+
const $svg = useRef<SVGSVGElement>(null);
120+
const [options, setOptions] = useState<StrokeOptions>(defaultOptions);
121+
122+
const handle = (evn: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
123+
const parentElement = $svg.current as SVGSVGElement;
124+
while (parentElement.firstChild) {
125+
parentElement.removeChild(parentElement.firstChild);
126+
}
127+
};
128+
129+
const pathData = getSvgPathFromStroke(getStroke(points1, options));
130+
const pathData2 = getSvgPathFromStroke(getStroke(points2, options));
131+
132+
const resetOption = () => {
133+
setOptions(defaultOptions);
134+
};
135+
136+
return (
137+
<Wrapper>
138+
<Signature
139+
style={{ borderRadius: 5, height: 210, border: '1px solid var(--color-border-default, #30363d)' }}
140+
ref={$svg}
141+
options={options}
142+
onPointer={(points) => {
143+
// console.log(JSON.stringify(points));
144+
}}
145+
>
146+
<path d={pathData} />
147+
<path d={pathData2} />
148+
</Signature>
149+
<button onClick={handle}>Clear</button>
150+
<button onClick={resetOption}>Reset Options</button>
151+
<button onClick={handle}>Copy Options</button>
152+
<button onClick={handle}>Copy to SVG</button>
153+
<div>
154+
<label>
155+
<div>Size: {options.size}</div>
156+
<input
157+
type="range"
158+
value={options.size}
159+
max={50}
160+
min={1}
161+
onChange={(evn) => {
162+
setOptions({
163+
...options,
164+
size: Number(evn.target.value),
165+
});
166+
}}
167+
/>
168+
</label>
169+
<label>
170+
<div>Smoothing: {options.smoothing}</div>
171+
<input
172+
type="range"
173+
value={options.smoothing}
174+
max={0.99}
175+
min={-0.99}
176+
step={0.01}
177+
onChange={(evn) => {
178+
setOptions({
179+
...options,
180+
smoothing: Number(evn.target.value),
181+
});
182+
}}
183+
/>
184+
</label>
185+
<label>
186+
<div>Thinning: {options.thinning}</div>
187+
<input
188+
type="range"
189+
value={options.thinning}
190+
max={0.99}
191+
min={-0.99}
192+
step={0.01}
193+
onChange={(evn) => {
194+
setOptions({
195+
...options,
196+
thinning: Number(evn.target.value),
197+
});
198+
}}
199+
/>
200+
</label>
201+
<label>
202+
<div>Streamline: {options.streamline}</div>
203+
<input
204+
type="range"
205+
value={options.streamline}
206+
max={0.99}
207+
min={0.01}
208+
step={0.01}
209+
onChange={(evn) => {
210+
setOptions({
211+
...options,
212+
streamline: Number(evn.target.value),
213+
});
214+
}}
215+
/>
216+
</label>
217+
</div>
218+
</Wrapper>
219+
);
220+
};

website/src/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { createRoot } from 'react-dom/client';
22
import MarkdownPreviewExample from '@uiw/react-markdown-preview-example';
33
import pkg from '@uiw/react-signature/package.json';
44
import data from '@uiw/react-signature/README.md';
5+
import { ExampleSignature } from './Example';
56

67
const Github = MarkdownPreviewExample.Github;
8+
const Example = MarkdownPreviewExample.Example;
79

810
const container = document.getElementById('root');
911
const root = createRoot(container!);
@@ -17,5 +19,8 @@ root.render(
1719
version={`v${VERSION}`}
1820
>
1921
<Github href="https://github.com/uiwjs/react-signature" />
22+
<Example>
23+
<ExampleSignature />
24+
</Example>
2025
</MarkdownPreviewExample>,
2126
);

0 commit comments

Comments
 (0)