react-native-signature-canvas 5.0.2 → 5.1.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/CHANGELOG.md +6 -0
- package/h5/js/app.js +34 -18
- package/index.d.ts +2 -6
- package/index.js +45 -47
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
# [5.1.0](https://github.com/YanYuanFE/react-native-signature-canvas/compare/v5.0.2...v5.1.0) (2026-07-05)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* harden string injection, prop handling, and WebView bridge ([98f158d](https://github.com/YanYuanFE/react-native-signature-canvas/commit/98f158d4f883981052b88d07f1b7c3c703cd0401))
|
|
6
|
+
|
|
1
7
|
## [5.0.2](https://github.com/YanYuanFE/react-native-signature-canvas/compare/v5.0.1...v5.0.2) (2025-12-28)
|
|
2
8
|
|
|
3
9
|
|
package/h5/js/app.js
CHANGED
|
@@ -5,6 +5,17 @@ export default `
|
|
|
5
5
|
canvas = wrapper && wrapper.querySelector("canvas"),
|
|
6
6
|
signaturePad;
|
|
7
7
|
|
|
8
|
+
// Single bridge entry point. Guards the rare window where the RN WebView
|
|
9
|
+
// bridge is missing (injection race / WebView torn down) and surfaces the
|
|
10
|
+
// dropped message instead of swallowing it.
|
|
11
|
+
function postMessage(data) {
|
|
12
|
+
if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) {
|
|
13
|
+
window.ReactNativeWebView.postMessage(data);
|
|
14
|
+
} else {
|
|
15
|
+
console.warn("[signature-canvas] RN bridge unavailable, dropped message: " + data);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
8
19
|
function resizeCanvas() {
|
|
9
20
|
if (!canvas || !canvas.getContext || !signaturePad) {
|
|
10
21
|
return;
|
|
@@ -35,8 +46,8 @@ export default `
|
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
signaturePad = new SignaturePad(canvas, {
|
|
38
|
-
onBegin: () =>
|
|
39
|
-
onEnd: () =>
|
|
49
|
+
onBegin: () => postMessage("BEGIN"),
|
|
50
|
+
onEnd: () => postMessage("END"),
|
|
40
51
|
penColor: '<%penColor%>',
|
|
41
52
|
backgroundColor: '<%backgroundColor%>',
|
|
42
53
|
dotSize: <%dotSize%>,
|
|
@@ -46,22 +57,26 @@ export default `
|
|
|
46
57
|
});
|
|
47
58
|
|
|
48
59
|
// Initial canvas setup
|
|
49
|
-
|
|
60
|
+
const observer = new ResizeObserver(() => {
|
|
61
|
+
resizeCanvas();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
observer.observe(canvas);
|
|
50
65
|
|
|
51
66
|
function clearSignature () {
|
|
52
67
|
signaturePad.clear();
|
|
53
68
|
dataURL='';
|
|
54
|
-
|
|
69
|
+
postMessage("CLEAR");
|
|
55
70
|
}
|
|
56
71
|
|
|
57
72
|
function undo() {
|
|
58
73
|
signaturePad.undo();
|
|
59
|
-
|
|
74
|
+
postMessage("UNDO");
|
|
60
75
|
}
|
|
61
76
|
|
|
62
77
|
function redo() {
|
|
63
78
|
signaturePad.redo();
|
|
64
|
-
|
|
79
|
+
postMessage("REDO");
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
function changePenColor(color) {
|
|
@@ -69,7 +84,7 @@ export default `
|
|
|
69
84
|
return;
|
|
70
85
|
}
|
|
71
86
|
signaturePad.penColor = color;
|
|
72
|
-
|
|
87
|
+
postMessage("CHANGE_PEN");
|
|
73
88
|
}
|
|
74
89
|
|
|
75
90
|
function changePenSize(minW, maxW) {
|
|
@@ -81,34 +96,34 @@ export default `
|
|
|
81
96
|
}
|
|
82
97
|
signaturePad.minWidth = minW;
|
|
83
98
|
signaturePad.maxWidth = maxW;
|
|
84
|
-
|
|
99
|
+
postMessage("CHANGE_PEN_SIZE");
|
|
85
100
|
}
|
|
86
101
|
|
|
87
102
|
function getData () {
|
|
88
103
|
var data = signaturePad.toData();
|
|
89
|
-
|
|
104
|
+
postMessage(JSON.stringify(data));
|
|
90
105
|
}
|
|
91
106
|
|
|
92
|
-
function fromData (pointGroups) {
|
|
93
|
-
signaturePad.fromData(pointGroups);
|
|
94
|
-
|
|
107
|
+
function fromData (pointGroups, suppressClear) {
|
|
108
|
+
signaturePad.fromData(pointGroups, suppressClear);
|
|
109
|
+
postMessage(JSON.stringify(pointGroups));
|
|
95
110
|
}
|
|
96
111
|
|
|
97
112
|
function draw() {
|
|
98
113
|
signaturePad.draw();
|
|
99
|
-
|
|
114
|
+
postMessage("DRAW");
|
|
100
115
|
}
|
|
101
116
|
|
|
102
117
|
function erase() {
|
|
103
118
|
signaturePad.erase();
|
|
104
|
-
|
|
119
|
+
postMessage("ERASE");
|
|
105
120
|
}
|
|
106
121
|
|
|
107
122
|
function cropWhitespace(url) {
|
|
108
123
|
var myImage = new Image();
|
|
109
124
|
myImage.crossOrigin = "Anonymous";
|
|
110
125
|
myImage.onload = function(){
|
|
111
|
-
|
|
126
|
+
postMessage(removeImageBlanks(myImage));
|
|
112
127
|
}
|
|
113
128
|
myImage.src = url;
|
|
114
129
|
|
|
@@ -194,7 +209,7 @@ export default `
|
|
|
194
209
|
}
|
|
195
210
|
|
|
196
211
|
if (signaturePad.isEmpty()) {
|
|
197
|
-
|
|
212
|
+
postMessage("EMPTY");
|
|
198
213
|
} else {
|
|
199
214
|
var imageType = '<%imageType%>' || 'image/png';
|
|
200
215
|
var url = signaturePad.toDataURL(imageType);
|
|
@@ -202,7 +217,7 @@ export default `
|
|
|
202
217
|
if (trimWhitespace === true) {
|
|
203
218
|
cropWhitespace(url);
|
|
204
219
|
} else {
|
|
205
|
-
|
|
220
|
+
postMessage(url);
|
|
206
221
|
}
|
|
207
222
|
|
|
208
223
|
if (autoClear === true && signaturePad) {
|
|
@@ -215,7 +230,8 @@ export default `
|
|
|
215
230
|
|
|
216
231
|
var trimWhitespace = <%trimWhitespace%>;
|
|
217
232
|
|
|
218
|
-
|
|
233
|
+
// <%dataURL%> is injected as an already-quoted JSON string literal
|
|
234
|
+
var dataURL = <%dataURL%>;
|
|
219
235
|
|
|
220
236
|
if (dataURL) signaturePad.fromDataURL(dataURL);
|
|
221
237
|
|
package/index.d.ts
CHANGED
|
@@ -5,9 +5,9 @@ declare module "react-native-signature-canvas" {
|
|
|
5
5
|
|
|
6
6
|
// Enhanced type definitions with better error handling
|
|
7
7
|
|
|
8
|
-
type ImageType = "image/png" | "image/jpeg" | "image/svg+xml";
|
|
8
|
+
export type ImageType = "image/png" | "image/jpeg" | "image/svg+xml";
|
|
9
9
|
|
|
10
|
-
type DataURL = string; // Simplified - should be base64 data URL
|
|
10
|
+
export type DataURL = string; // Simplified - should be base64 data URL
|
|
11
11
|
|
|
12
12
|
type ForwardRef<T, P> = React.ForwardRefExoticComponent<
|
|
13
13
|
React.PropsWithoutRef<P> & React.RefAttributes<T>
|
|
@@ -51,7 +51,6 @@ declare module "react-native-signature-canvas" {
|
|
|
51
51
|
onBegin?: EmptyCallback;
|
|
52
52
|
onEnd?: EmptyCallback;
|
|
53
53
|
onLoadEnd?: EmptyCallback;
|
|
54
|
-
onError?: ErrorCallback; // Added missing error callback
|
|
55
54
|
overlayHeight?: number;
|
|
56
55
|
overlayWidth?: number;
|
|
57
56
|
overlaySrc?: string;
|
|
@@ -91,7 +90,4 @@ declare module "react-native-signature-canvas" {
|
|
|
91
90
|
|
|
92
91
|
const SignatureView: SignatureCanvasComponent;
|
|
93
92
|
export default SignatureView;
|
|
94
|
-
|
|
95
|
-
// Export additional types for external use
|
|
96
|
-
export { SignatureViewProps, SignatureViewRef, ImageType, DataURL };
|
|
97
93
|
}
|
package/index.js
CHANGED
|
@@ -46,16 +46,11 @@ const styles = StyleSheet.create({
|
|
|
46
46
|
},
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return JSON.stringify(param)
|
|
55
|
-
default:
|
|
56
|
-
return param
|
|
57
|
-
}
|
|
58
|
-
}
|
|
49
|
+
// JSON.stringify produces a valid JS literal with quotes/backslashes/newlines escaped
|
|
50
|
+
const getParamForInjection = (param) =>
|
|
51
|
+
typeof param === "string" || typeof param === "object"
|
|
52
|
+
? JSON.stringify(param)
|
|
53
|
+
: param;
|
|
59
54
|
|
|
60
55
|
const SignatureView = forwardRef(
|
|
61
56
|
(
|
|
@@ -125,18 +120,21 @@ const SignatureView = forwardRef(
|
|
|
125
120
|
// Split source generation for better performance
|
|
126
121
|
// Include webViewKey to regenerate script when WebView needs remounting
|
|
127
122
|
const injectedScript = useMemo(() => {
|
|
123
|
+
// String values go through a function replacer so "$&"-style patterns
|
|
124
|
+
// in user input are inserted literally instead of being expanded
|
|
128
125
|
let script = injectedSignaturePad + injectedApplication;
|
|
129
126
|
script = script.replace(/<%autoClear%>/g, autoClear);
|
|
130
127
|
script = script.replace(/<%trimWhitespace%>/g, trimWhitespace);
|
|
131
|
-
script = script.replace(/<%imageType%>/g, imageType || "image/png");
|
|
132
|
-
// Use currentDataURLRef to get the latest dataURL value
|
|
133
|
-
|
|
134
|
-
script = script.replace(/<%
|
|
135
|
-
script = script.replace(/<%
|
|
136
|
-
script = script.replace(/<%
|
|
137
|
-
script = script.replace(/<%
|
|
138
|
-
script = script.replace(/<%
|
|
139
|
-
script = script.replace(/<%
|
|
128
|
+
script = script.replace(/<%imageType%>/g, () => imageType || "image/png");
|
|
129
|
+
// Use currentDataURLRef to get the latest dataURL value;
|
|
130
|
+
// JSON.stringify emits a quoted JS string literal with escaping
|
|
131
|
+
script = script.replace(/<%dataURL%>/g, () => JSON.stringify(currentDataURLRef.current || ""));
|
|
132
|
+
script = script.replace(/<%penColor%>/g, () => penColor || "black");
|
|
133
|
+
script = script.replace(/<%backgroundColor%>/g, () => backgroundColor || "rgba(255,255,255,0)");
|
|
134
|
+
script = script.replace(/<%dotSize%>/g, dotSize ?? "null");
|
|
135
|
+
script = script.replace(/<%minWidth%>/g, minWidth ?? 0.5);
|
|
136
|
+
script = script.replace(/<%maxWidth%>/g, maxWidth ?? 2.5);
|
|
137
|
+
script = script.replace(/<%minDistance%>/g, minDistance ?? 5);
|
|
140
138
|
script = script.replace(/<%orientation%>/g, rotated || false);
|
|
141
139
|
return script;
|
|
142
140
|
}, [autoClear, trimWhitespace, imageType, penColor, backgroundColor, dotSize, minWidth, maxWidth, minDistance, rotated, webViewKey]);
|
|
@@ -144,16 +142,16 @@ const SignatureView = forwardRef(
|
|
|
144
142
|
const source = useMemo(() => {
|
|
145
143
|
const htmlContentValue = customHtml || htmlContent;
|
|
146
144
|
let html = htmlContentValue(injectedScript);
|
|
147
|
-
html = html.replace(/<%bgWidth%>/g, bgWidth
|
|
148
|
-
html = html.replace(/<%bgHeight%>/g, bgHeight
|
|
149
|
-
html = html.replace(/<%bgSrc%>/g, bgSrc || "null");
|
|
150
|
-
html = html.replace(/<%overlayWidth%>/g, overlayWidth
|
|
151
|
-
html = html.replace(/<%overlayHeight%>/g, overlayHeight
|
|
152
|
-
html = html.replace(/<%overlaySrc%>/g, overlaySrc || "null");
|
|
153
|
-
html = html.replace(/<%style%>/g, webStyle || "");
|
|
154
|
-
html = html.replace(/<%description%>/g, descriptionText || "Sign above");
|
|
155
|
-
html = html.replace(/<%confirm%>/g, confirmText || "Confirm");
|
|
156
|
-
html = html.replace(/<%clear%>/g, clearText || "Clear");
|
|
145
|
+
html = html.replace(/<%bgWidth%>/g, bgWidth ?? 0);
|
|
146
|
+
html = html.replace(/<%bgHeight%>/g, bgHeight ?? 0);
|
|
147
|
+
html = html.replace(/<%bgSrc%>/g, () => bgSrc || "null");
|
|
148
|
+
html = html.replace(/<%overlayWidth%>/g, overlayWidth ?? 0);
|
|
149
|
+
html = html.replace(/<%overlayHeight%>/g, overlayHeight ?? 0);
|
|
150
|
+
html = html.replace(/<%overlaySrc%>/g, () => overlaySrc || "null");
|
|
151
|
+
html = html.replace(/<%style%>/g, () => webStyle || "");
|
|
152
|
+
html = html.replace(/<%description%>/g, () => descriptionText || "Sign above");
|
|
153
|
+
html = html.replace(/<%confirm%>/g, () => confirmText || "Confirm");
|
|
154
|
+
html = html.replace(/<%clear%>/g, () => clearText || "Clear");
|
|
157
155
|
html = html.replace(/<%orientation%>/g, rotated || false);
|
|
158
156
|
|
|
159
157
|
return { html };
|
|
@@ -173,7 +171,7 @@ const SignatureView = forwardRef(
|
|
|
173
171
|
// Update dataURL in WebView without full reload
|
|
174
172
|
if (dataURL) {
|
|
175
173
|
const script = `
|
|
176
|
-
dataURL =
|
|
174
|
+
dataURL = ${JSON.stringify(dataURL)};
|
|
177
175
|
if (signaturePad && signaturePad.isEmpty()) {
|
|
178
176
|
signaturePad.fromDataURL(dataURL);
|
|
179
177
|
}
|
|
@@ -292,12 +290,12 @@ const SignatureView = forwardRef(
|
|
|
292
290
|
executeWebViewMethod('changePenSize', [minW, maxW]);
|
|
293
291
|
},
|
|
294
292
|
getData: () => executeWebViewMethod('getData'),
|
|
295
|
-
fromData: (pointGroups) => {
|
|
293
|
+
fromData: (pointGroups, suppressClear = false) => {
|
|
296
294
|
if (!pointGroups) {
|
|
297
295
|
console.warn('fromData: pointGroups must be an array');
|
|
298
296
|
return;
|
|
299
297
|
}
|
|
300
|
-
executeWebViewMethod('fromData', [pointGroups,
|
|
298
|
+
executeWebViewMethod('fromData', [pointGroups, suppressClear]);
|
|
301
299
|
},
|
|
302
300
|
// New method to set dataURL without causing WebView reload
|
|
303
301
|
setDataURL: (url) => {
|
|
@@ -310,7 +308,7 @@ const SignatureView = forwardRef(
|
|
|
310
308
|
return;
|
|
311
309
|
}
|
|
312
310
|
const script = `
|
|
313
|
-
dataURL =
|
|
311
|
+
dataURL = ${JSON.stringify(url)};
|
|
314
312
|
if (signaturePad) {
|
|
315
313
|
signaturePad.clear();
|
|
316
314
|
if (dataURL) {
|
|
@@ -397,20 +395,6 @@ const SignatureView = forwardRef(
|
|
|
397
395
|
return (
|
|
398
396
|
<View style={[styles.webBg, style]}>
|
|
399
397
|
<WebView
|
|
400
|
-
// Key for forcing remount when WebView needs reinitialization
|
|
401
|
-
key={`signature-webview-${webViewKey}`}
|
|
402
|
-
// Core functionality props (cannot be overridden)
|
|
403
|
-
ref={webViewRef}
|
|
404
|
-
source={source}
|
|
405
|
-
onMessage={getSignature}
|
|
406
|
-
onError={renderError}
|
|
407
|
-
onLoadEnd={handleLoadEnd}
|
|
408
|
-
onLoadStart={handleLoadStart}
|
|
409
|
-
onLoadProgress={handleLoadProgress}
|
|
410
|
-
// Handle iOS WKWebView content process termination (crucial for bottom sheets/modals)
|
|
411
|
-
onContentProcessDidTerminate={handleContentProcessDidTerminate}
|
|
412
|
-
javaScriptEnabled={true}
|
|
413
|
-
useWebKit={true}
|
|
414
398
|
// Default component props (can be overridden by webviewProps)
|
|
415
399
|
bounces={false}
|
|
416
400
|
style={[{ flex: 1 }, webviewContainerStyle]}
|
|
@@ -437,6 +421,20 @@ const SignatureView = forwardRef(
|
|
|
437
421
|
startInLoadingState={true}
|
|
438
422
|
// User-provided WebView props (can override defaults but not core functionality)
|
|
439
423
|
{...webviewProps}
|
|
424
|
+
// Core functionality props (cannot be overridden — placed after the spread)
|
|
425
|
+
// Key for forcing remount when WebView needs reinitialization
|
|
426
|
+
key={`signature-webview-${webViewKey}`}
|
|
427
|
+
ref={webViewRef}
|
|
428
|
+
source={source}
|
|
429
|
+
onMessage={getSignature}
|
|
430
|
+
onError={renderError}
|
|
431
|
+
onLoadEnd={handleLoadEnd}
|
|
432
|
+
onLoadStart={handleLoadStart}
|
|
433
|
+
onLoadProgress={handleLoadProgress}
|
|
434
|
+
// Handle iOS WKWebView content process termination (crucial for bottom sheets/modals)
|
|
435
|
+
onContentProcessDidTerminate={handleContentProcessDidTerminate}
|
|
436
|
+
javaScriptEnabled={true}
|
|
437
|
+
useWebKit={true}
|
|
440
438
|
/>
|
|
441
439
|
{(loading || hasError) && (
|
|
442
440
|
<View style={styles.loadingOverlayContainer}>
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-signature-canvas",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "A performant, customizable React Native signature canvas with advanced error handling, WebView optimization, and TypeScript support for iOS, Android, and Expo",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"genlog": "conventional-changelog -p angular -i CHANGELOG.md
|
|
8
|
-
"
|
|
7
|
+
"genlog": "npx -y -p conventional-changelog -p conventional-changelog-angular conventional-changelog -p angular -i CHANGELOG.md",
|
|
8
|
+
"preversion": "npm whoami",
|
|
9
|
+
"version": "npm run genlog && git add CHANGELOG.md",
|
|
10
|
+
"postversion": "git push --follow-tags && npm publish"
|
|
9
11
|
},
|
|
10
12
|
"repository": {
|
|
11
13
|
"type": "git",
|