Skip to content

Commit 7dbee61

Browse files
committed
feat: Enhance the renderPath props.
1 parent 06fb363 commit 7dbee61

File tree

6 files changed

+73
-82
lines changed

6 files changed

+73
-82
lines changed

core/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export default function App() {
119119
<Signature
120120
ref={$svg}
121121
defaultPoints={points}
122-
renderPath={(path, keyName, data, index) => {
122+
renderPath={(path, keyName, data, index, container) => {
123123
if (keyName === 'path-1' || index === 0) {
124124
return <path d={path} fill="red" />
125125
}
@@ -198,7 +198,7 @@ export interface SignatureProps extends React.SVGProps<SVGSVGElement> {
198198
options?: StrokeOptions;
199199
readonly?: boolean;
200200
defaultPoints?: Record<string, number[][]>;
201-
renderPath?: (d: string, keyName: string, point: number[][], index: number) => JSX.Element;
201+
renderPath?: (d: string, keyName: string, point: number[][], index: number, container: SVGSVGElement) => JSX.Element;
202202
onPointer?: (points: number[][]) => void;
203203
}
204204
export type SignatureRef = {

core/src/Paths.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useOptionStore } from './options';
55
import { getSvgPathFromStroke } from './utils';
66

77
export const Paths = () => {
8-
const { options, ...data } = useStore();
8+
const data = useStore();
99
return (
1010
<Fragment>
1111
{Object.keys(data).map((key, index) => (
@@ -22,10 +22,10 @@ type CreatePathProps = {
2222
};
2323

2424
const CreatePath = ({ data = [], index, keyName }: CreatePathProps) => {
25-
const { renderPath, ...options } = useOptionStore();
25+
const { renderPath, container, ...options } = useOptionStore();
2626
const stroke = getStroke(data, options);
2727
const pathData = getSvgPathFromStroke(stroke);
28-
const dom = renderPath ? renderPath(pathData, keyName, data, index) : null;
28+
const dom = renderPath ? renderPath(pathData, keyName, data, index, container as unknown as SVGSVGElement) : null;
2929
if (dom) return dom;
3030
return <path d={pathData} />;
3131
};

core/src/Signature.tsx

Lines changed: 65 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,78 @@
11
import React, { useEffect, useRef, useId, forwardRef, useImperativeHandle } from 'react';
2-
import { type StrokeOptions } from 'perfect-freehand';
32
import { getBoundingClientRect, getClinetXY, defaultStyle, useEvent } from './utils';
43

54
import { useDispatch } from './store';
6-
import { SignatureRef } from './';
5+
import { SignatureRef, SignatureProps } from './';
76

8-
export interface SignatureProps extends React.SVGProps<SVGSVGElement> {
9-
prefixCls?: string;
10-
options?: StrokeOptions;
11-
readonly?: boolean;
12-
onPointer?: (points: number[][]) => void;
13-
}
7+
export const Signature = forwardRef<SignatureRef, Omit<SignatureProps, 'defaultPoints' | 'renderPath' | 'options'>>(
8+
(props, ref) => {
9+
const { className, prefixCls = 'w-signature', style, readonly = false, onPointer, children, ...others } = props;
10+
const cls = [className, prefixCls].filter(Boolean).join(' ');
11+
const $svg = useRef<SVGSVGElement>(null);
12+
const $path = useRef<SVGPathElement>();
13+
const pointsRef = useRef<number[][]>();
14+
const pointCount = useRef<number>(0);
15+
const pointId = useId();
16+
const dispatch = useDispatch();
17+
useImperativeHandle<SignatureRef, SignatureRef>(
18+
ref,
19+
() => ({ svg: $svg.current, dispatch, clear: () => dispatch({}) }),
20+
[$svg.current, dispatch],
21+
);
1422

15-
export const Signature = forwardRef<SignatureRef, SignatureProps>((props, ref) => {
16-
const {
17-
className,
18-
prefixCls = 'w-signature',
19-
style,
20-
readonly = false,
21-
onPointer,
22-
options,
23-
children,
24-
...others
25-
} = props;
26-
const cls = [className, prefixCls].filter(Boolean).join(' ');
27-
const $svg = useRef<SVGSVGElement>(null);
28-
const $path = useRef<SVGPathElement>();
29-
const pointsRef = useRef<number[][]>();
30-
const pointCount = useRef<number>(0);
31-
const pointId = useId();
32-
const dispatch = useDispatch();
33-
useImperativeHandle<SignatureRef, SignatureRef>(
34-
ref,
35-
() => ({ svg: $svg.current, dispatch, clear: () => dispatch({}) }),
36-
[$svg.current, dispatch],
37-
);
38-
39-
const handlePointerDown = useEvent((e: React.PointerEvent<SVGSVGElement>) => {
40-
if (readonly) return;
41-
pointCount.current += 1;
42-
const { offsetY, offsetX } = getBoundingClientRect($svg.current);
43-
const clientX = e.clientX || e.nativeEvent.clientX;
44-
const clientY = e.clientY || e.nativeEvent.clientY;
45-
pointsRef.current = [[clientX - offsetX, clientY - offsetY]];
46-
const pathElm = document.createElementNS('http://www.w3.org/2000/svg', 'path');
47-
$path.current = pathElm;
48-
$svg.current!.appendChild(pathElm);
49-
dispatch({
50-
[pointId + pointCount.current]: pointsRef.current,
51-
});
52-
});
53-
54-
const handlePointerMove = useEvent((e: PointerEvent) => {
55-
if ($path.current) {
23+
const handlePointerDown = useEvent((e: React.PointerEvent<SVGSVGElement>) => {
24+
if (readonly) return;
25+
pointCount.current += 1;
5626
const { offsetY, offsetX } = getBoundingClientRect($svg.current);
57-
const { clientX, clientY } = getClinetXY(e);
58-
pointsRef.current = [...pointsRef.current!, [clientX - offsetX, clientY - offsetY]];
27+
const clientX = e.clientX || e.nativeEvent.clientX;
28+
const clientY = e.clientY || e.nativeEvent.clientY;
29+
pointsRef.current = [[clientX - offsetX, clientY - offsetY]];
30+
const pathElm = document.createElementNS('http://www.w3.org/2000/svg', 'path');
31+
$path.current = pathElm;
32+
$svg.current!.appendChild(pathElm);
5933
dispatch({
6034
[pointId + pointCount.current]: pointsRef.current,
6135
});
62-
}
63-
});
36+
});
37+
38+
const handlePointerMove = useEvent((e: PointerEvent) => {
39+
if ($path.current) {
40+
const { offsetY, offsetX } = getBoundingClientRect($svg.current);
41+
const { clientX, clientY } = getClinetXY(e);
42+
pointsRef.current = [...pointsRef.current!, [clientX - offsetX, clientY - offsetY]];
43+
dispatch({
44+
[pointId + pointCount.current]: pointsRef.current,
45+
});
46+
}
47+
});
6448

65-
const handlePointerUp = useEvent(() => {
66-
let result = pointsRef.current || [];
67-
onPointer && props.onPointer!(result);
68-
$path.current = undefined;
69-
pointsRef.current = undefined;
70-
});
49+
const handlePointerUp = useEvent(() => {
50+
let result = pointsRef.current || [];
51+
onPointer && props.onPointer!(result);
52+
$path.current = undefined;
53+
pointsRef.current = undefined;
54+
});
7155

72-
useEffect(() => {
73-
if (readonly) return;
74-
document.addEventListener('pointermove', handlePointerMove);
75-
document.addEventListener('pointerup', handlePointerUp);
76-
return () => {
56+
useEffect(() => {
7757
if (readonly) return;
78-
document.removeEventListener('pointermove', handlePointerMove);
79-
document.removeEventListener('pointerup', handlePointerUp);
80-
};
81-
}, []);
82-
return (
83-
<svg {...others} ref={$svg} className={cls} onPointerDown={handlePointerDown} style={{ ...defaultStyle, ...style }}>
84-
{children}
85-
</svg>
86-
);
87-
});
58+
document.addEventListener('pointermove', handlePointerMove);
59+
document.addEventListener('pointerup', handlePointerUp);
60+
return () => {
61+
if (readonly) return;
62+
document.removeEventListener('pointermove', handlePointerMove);
63+
document.removeEventListener('pointerup', handlePointerUp);
64+
};
65+
}, []);
66+
return (
67+
<svg
68+
{...others}
69+
ref={$svg}
70+
className={cls}
71+
onPointerDown={handlePointerDown}
72+
style={{ ...defaultStyle, ...style }}
73+
>
74+
{children}
75+
</svg>
76+
);
77+
},
78+
);

core/src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface SignatureProps extends React.SVGProps<SVGSVGElement> {
2121
options?: StrokeOptions;
2222
readonly?: boolean;
2323
defaultPoints?: Record<string, number[][]>;
24-
renderPath?: (d: string, keyName: string, point: number[][], index: number) => JSX.Element;
24+
renderPath?: (d: string, keyName: string, point: number[][], index: number, container: SVGSVGElement) => JSX.Element;
2525
onPointer?: (points: number[][]) => void;
2626
}
2727

core/src/options.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const OptionDispatchContext = createContext<Dispatch>(() => {});
2626
type Dispatch = React.Dispatch<InitialOptionState>;
2727
type InitialOptionState = StrokeOptions & {
2828
renderPath?: SignatureProps['renderPath'];
29+
container?: HTMLElement;
2930
};
3031

3132
export function reducerOption(tasks: InitialOptionState, action: InitialOptionState) {

core/src/utils.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { useCallback, useEffect, useRef } from 'react';
2-
import { type StrokeOptions } from 'perfect-freehand';
32

43
/**
54
* Turn the points returned from perfect-freehand into SVG path data.
@@ -19,7 +18,7 @@ export function getSvgPathFromStroke(stroke: number[][]) {
1918
return d.join(' ');
2019
}
2120

22-
export const getBoundingClientRect = (el: SVGSVGElement | null) => {
21+
export const getBoundingClientRect = <T extends Element>(el: T | null) => {
2322
const rect = el?.getBoundingClientRect();
2423
const offsetX = rect?.left || 0;
2524
const offsetY = rect?.top || 0;

0 commit comments

Comments
 (0)