react-native-signature-input 1.0.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/README.md +147 -0
- package/index.d.ts +38 -0
- package/index.js +162 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# react-native-signature-input ✍️
|
|
2
|
+
|
|
3
|
+
A lightweight, **pure JavaScript** signature pad for React Native.
|
|
4
|
+
Built on top of **react-native-svg** for smooth, vector-based drawing without the performance cost or complexity of WebViews.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## ✨ Features
|
|
9
|
+
|
|
10
|
+
- 🚀 **Pure JS** – No native linking required (except `react-native-svg`)
|
|
11
|
+
- ⚡ **High Performance** – Uses `PanResponder` for smooth, lag-free drawing
|
|
12
|
+
- 📐 **Vector Output** – Returns SVG XML strings or raw coordinate paths
|
|
13
|
+
- 🎨 **Customizable** – Control stroke color, width, and background
|
|
14
|
+
- 📱 **Scroll Compatible** – Handles gestures correctly inside `ScrollView`
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 📦 Installation
|
|
19
|
+
|
|
20
|
+
### Install the package
|
|
21
|
+
```bash
|
|
22
|
+
npm install react-native-signature-input
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Install peer dependency
|
|
26
|
+
```bash
|
|
27
|
+
npm install react-native-svg
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
> If you are using **bare React Native**, run:
|
|
31
|
+
```bash
|
|
32
|
+
cd ios && pod install && cd ..
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 💻 Usage
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import React, { useRef } from 'react';
|
|
41
|
+
import { View, Button, Alert, StyleSheet } from 'react-native';
|
|
42
|
+
import SignatureInput from 'react-native-signature-input';
|
|
43
|
+
|
|
44
|
+
export default function App() {
|
|
45
|
+
const signatureRef = useRef(null);
|
|
46
|
+
|
|
47
|
+
const handleClear = () => {
|
|
48
|
+
signatureRef.current.clear();
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleSave = () => {
|
|
52
|
+
if (signatureRef.current.isEmpty()) {
|
|
53
|
+
Alert.alert('Error', 'Please sign first!');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const svgString = signatureRef.current.getSVG();
|
|
58
|
+
console.log(svgString);
|
|
59
|
+
Alert.alert('Success', 'SVG captured! Check console.');
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<View style={styles.container}>
|
|
64
|
+
<View style={styles.signatureContainer}>
|
|
65
|
+
<SignatureInput
|
|
66
|
+
ref={signatureRef}
|
|
67
|
+
height={200}
|
|
68
|
+
color="#000080"
|
|
69
|
+
strokeWidth={4}
|
|
70
|
+
backgroundColor="#fff"
|
|
71
|
+
onEnd={() => console.log('Stroke finished')}
|
|
72
|
+
/>
|
|
73
|
+
</View>
|
|
74
|
+
|
|
75
|
+
<View style={styles.buttonRow}>
|
|
76
|
+
<Button title="Clear" onPress={handleClear} color="red" />
|
|
77
|
+
<Button title="Save SVG" onPress={handleSave} />
|
|
78
|
+
</View>
|
|
79
|
+
</View>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const styles = StyleSheet.create({
|
|
84
|
+
container: {
|
|
85
|
+
flex: 1,
|
|
86
|
+
padding: 20,
|
|
87
|
+
justifyContent: 'center',
|
|
88
|
+
backgroundColor: '#f0f0f0',
|
|
89
|
+
},
|
|
90
|
+
signatureContainer: {
|
|
91
|
+
borderWidth: 1,
|
|
92
|
+
borderColor: '#ccc',
|
|
93
|
+
borderRadius: 10,
|
|
94
|
+
overflow: 'hidden',
|
|
95
|
+
marginBottom: 20,
|
|
96
|
+
elevation: 4,
|
|
97
|
+
backgroundColor: 'white',
|
|
98
|
+
},
|
|
99
|
+
buttonRow: {
|
|
100
|
+
flexDirection: 'row',
|
|
101
|
+
justifyContent: 'space-around',
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 📖 API Documentation
|
|
109
|
+
|
|
110
|
+
### Props
|
|
111
|
+
|
|
112
|
+
| Prop | Type | Default | Description |
|
|
113
|
+
|-----|-----|---------|-------------|
|
|
114
|
+
| ref | React.Ref | `null` | Required to access methods |
|
|
115
|
+
| height | number | `200` | Height of drawing area |
|
|
116
|
+
| color | string | `"black"` | Ink color |
|
|
117
|
+
| strokeWidth | number | `3` | Thickness of line |
|
|
118
|
+
| backgroundColor | string | `"#f5f5f5"` | Background color |
|
|
119
|
+
| onEnd | function | `undefined` | Called when stroke ends |
|
|
120
|
+
| onChange | function | `undefined` | Called with `(paths[])` on update |
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### Methods (via Ref)
|
|
125
|
+
|
|
126
|
+
| Method | Returns | Description |
|
|
127
|
+
|------|--------|-------------|
|
|
128
|
+
| `getSVG()` | `string` | Full SVG XML string |
|
|
129
|
+
| `getPaths()` | `string[]` | Array of SVG path strings |
|
|
130
|
+
| `clear()` | `void` | Clears the canvas |
|
|
131
|
+
| `isEmpty()` | `boolean` | Returns true if no strokes |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 🤝 Contributing
|
|
136
|
+
|
|
137
|
+
Pull requests are welcome!
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 📄 License
|
|
142
|
+
|
|
143
|
+
MIT
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
**Author:** Vasanth
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface SignatureInputProps {
|
|
5
|
+
/** Color of the signature pen. Default: "black" */
|
|
6
|
+
color?: string;
|
|
7
|
+
/** Thickness of the pen stroke. Default: 3 */
|
|
8
|
+
strokeWidth?: number;
|
|
9
|
+
/** Height of the drawing area. Default: 200 */
|
|
10
|
+
height?: number | string;
|
|
11
|
+
/** Width of the drawing area. Default: "100%" */
|
|
12
|
+
width?: number | string;
|
|
13
|
+
/** Background color of the pad. Default: "#f5f5f5" */
|
|
14
|
+
backgroundColor?: string;
|
|
15
|
+
/** Custom container style */
|
|
16
|
+
style?: ViewStyle;
|
|
17
|
+
/** Callback when the user lifts their finger */
|
|
18
|
+
onEnd?: () => void;
|
|
19
|
+
/** Callback when the paths array changes */
|
|
20
|
+
onChange?: (paths: string[]) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SignatureInputRef {
|
|
24
|
+
/** Clears the signature pad */
|
|
25
|
+
clear: () => void;
|
|
26
|
+
/** Returns the array of SVG path strings */
|
|
27
|
+
getPaths: () => string[];
|
|
28
|
+
/** Returns the complete SVG XML string */
|
|
29
|
+
getSVG: () => string;
|
|
30
|
+
/** Returns true if no signature has been drawn */
|
|
31
|
+
isEmpty: () => boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
declare const SignatureInput: React.ForwardRefExoticComponent<
|
|
35
|
+
SignatureInputProps & React.RefAttributes<SignatureInputRef>
|
|
36
|
+
>;
|
|
37
|
+
|
|
38
|
+
export default SignatureInput;
|
package/index.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useState,
|
|
3
|
+
useRef,
|
|
4
|
+
useImperativeHandle,
|
|
5
|
+
forwardRef,
|
|
6
|
+
} from "react";
|
|
7
|
+
import { View, PanResponder, StyleSheet } from "react-native";
|
|
8
|
+
import Svg, { Path } from "react-native-svg";
|
|
9
|
+
|
|
10
|
+
const SignatureInput = forwardRef(
|
|
11
|
+
(
|
|
12
|
+
{
|
|
13
|
+
color = "black",
|
|
14
|
+
strokeWidth = 3,
|
|
15
|
+
height = 200,
|
|
16
|
+
width = "100%",
|
|
17
|
+
backgroundColor = "#f5f5f5",
|
|
18
|
+
style,
|
|
19
|
+
onEnd,
|
|
20
|
+
onChange,
|
|
21
|
+
},
|
|
22
|
+
ref
|
|
23
|
+
) => {
|
|
24
|
+
const [paths, setPaths] = useState([]);
|
|
25
|
+
const [currentPath, setCurrentPath] = useState("");
|
|
26
|
+
const currentPathRef = useRef(""); // Ref to track current stroke without lag
|
|
27
|
+
|
|
28
|
+
useImperativeHandle(
|
|
29
|
+
ref,
|
|
30
|
+
() => ({
|
|
31
|
+
clear: () => {
|
|
32
|
+
setPaths([]);
|
|
33
|
+
setCurrentPath("");
|
|
34
|
+
currentPathRef.current = "";
|
|
35
|
+
if (onChange) onChange([]);
|
|
36
|
+
},
|
|
37
|
+
getPaths: () => paths,
|
|
38
|
+
isEmpty: () => paths.length === 0 && currentPathRef.current === "",
|
|
39
|
+
|
|
40
|
+
getSVG: () => {
|
|
41
|
+
// Ensure we use the latest 'paths' from state
|
|
42
|
+
const bgRect =
|
|
43
|
+
backgroundColor !== "transparent"
|
|
44
|
+
? `<rect width="100%" height="100%" fill="${backgroundColor}" />`
|
|
45
|
+
: "";
|
|
46
|
+
|
|
47
|
+
const pathElements = paths
|
|
48
|
+
.map(
|
|
49
|
+
(p) =>
|
|
50
|
+
`<path d="${p}" stroke="${color}" stroke-width="${strokeWidth}" fill="none" stroke-linecap="round" stroke-linejoin="round"/>`
|
|
51
|
+
)
|
|
52
|
+
.join("");
|
|
53
|
+
|
|
54
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
|
|
55
|
+
${bgRect}
|
|
56
|
+
${pathElements}
|
|
57
|
+
</svg>`;
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
[paths, backgroundColor, color, strokeWidth, height, width]
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const panResponder = useRef(
|
|
64
|
+
PanResponder.create({
|
|
65
|
+
onStartShouldSetPanResponder: () => true,
|
|
66
|
+
onStartShouldSetPanResponderCapture: () => true,
|
|
67
|
+
onMoveShouldSetPanResponder: () => true,
|
|
68
|
+
onMoveShouldSetPanResponderCapture: () => true,
|
|
69
|
+
|
|
70
|
+
onPanResponderGrant: (evt) => {
|
|
71
|
+
const { locationX, locationY } = evt.nativeEvent;
|
|
72
|
+
// Start a new path "Move To"
|
|
73
|
+
const startPath = `M${locationX.toFixed(1)},${locationY.toFixed(1)}`;
|
|
74
|
+
|
|
75
|
+
currentPathRef.current = startPath;
|
|
76
|
+
setCurrentPath(startPath);
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
onPanResponderMove: (evt) => {
|
|
80
|
+
const { locationX, locationY } = evt.nativeEvent;
|
|
81
|
+
// Add "Line To"
|
|
82
|
+
const newPoint = `L${locationX.toFixed(1)},${locationY.toFixed(1)}`;
|
|
83
|
+
|
|
84
|
+
currentPathRef.current += newPoint;
|
|
85
|
+
setCurrentPath(currentPathRef.current);
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
onPanResponderRelease: () => {
|
|
89
|
+
// 1. CAPTURE the full string into a local variable FIRST
|
|
90
|
+
const finishedPath = currentPathRef.current;
|
|
91
|
+
|
|
92
|
+
if (finishedPath) {
|
|
93
|
+
// 2. Use the local variable to update state (Safe from resets)
|
|
94
|
+
setPaths((prev) => {
|
|
95
|
+
const newPaths = [...prev, finishedPath];
|
|
96
|
+
if (onChange) onChange(newPaths);
|
|
97
|
+
return newPaths;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// 3. NOW it is safe to reset
|
|
101
|
+
currentPathRef.current = "";
|
|
102
|
+
setCurrentPath("");
|
|
103
|
+
if (onEnd) onEnd();
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
).current;
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<View
|
|
111
|
+
style={[styles.container, style, { height, width, backgroundColor }]}
|
|
112
|
+
>
|
|
113
|
+
<View style={styles.touchArea} {...panResponder.panHandlers}>
|
|
114
|
+
<Svg style={styles.svgContainer}>
|
|
115
|
+
{/* Render Saved Paths */}
|
|
116
|
+
{paths.map((p, i) => (
|
|
117
|
+
<Path
|
|
118
|
+
key={i}
|
|
119
|
+
d={p}
|
|
120
|
+
stroke={color}
|
|
121
|
+
strokeWidth={strokeWidth}
|
|
122
|
+
fill="none"
|
|
123
|
+
strokeLinecap="round"
|
|
124
|
+
strokeLinejoin="round"
|
|
125
|
+
/>
|
|
126
|
+
))}
|
|
127
|
+
{/* Render Current Stroke being drawn */}
|
|
128
|
+
{currentPath ? (
|
|
129
|
+
<Path
|
|
130
|
+
d={currentPath}
|
|
131
|
+
stroke={color}
|
|
132
|
+
strokeWidth={strokeWidth}
|
|
133
|
+
fill="none"
|
|
134
|
+
strokeLinecap="round"
|
|
135
|
+
strokeLinejoin="round"
|
|
136
|
+
/>
|
|
137
|
+
) : null}
|
|
138
|
+
</Svg>
|
|
139
|
+
</View>
|
|
140
|
+
</View>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const styles = StyleSheet.create({
|
|
146
|
+
container: {
|
|
147
|
+
overflow: "hidden",
|
|
148
|
+
borderRadius: 8,
|
|
149
|
+
},
|
|
150
|
+
touchArea: {
|
|
151
|
+
flex: 1,
|
|
152
|
+
width: "100%",
|
|
153
|
+
height: "100%",
|
|
154
|
+
},
|
|
155
|
+
svgContainer: {
|
|
156
|
+
flex: 1,
|
|
157
|
+
width: "100%",
|
|
158
|
+
height: "100%",
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
export default SignatureInput;
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-signature-input",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight, pure JS signature capture component for React Native using SVG",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.js",
|
|
9
|
+
"index.d.ts"
|
|
10
|
+
],
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"react": "*",
|
|
13
|
+
"react-native": "*",
|
|
14
|
+
"react-native-svg": ">=12.0.0"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/VASANTH3105/react-native-signature-input.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/VASANTH3105/react-native-signature-input/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/VASANTH3105/react-native-signature-input#readme",
|
|
24
|
+
"author": "Vasanth",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"keywords": [
|
|
27
|
+
"react-native",
|
|
28
|
+
"signature",
|
|
29
|
+
"handwriting",
|
|
30
|
+
"svg",
|
|
31
|
+
"canvas",
|
|
32
|
+
"drawing"
|
|
33
|
+
]
|
|
34
|
+
}
|