react-native-signature-canvas 5.0.1 → 5.0.2
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 +10 -1
- package/README.md +2 -8
- package/h5/html.js +22 -21
- package/h5/js/app.js +79 -121
- package/index.d.ts +5 -2
- package/index.js +93 -15
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
## [5.0.2](https://github.com/YanYuanFE/react-native-signature-canvas/compare/v5.0.1...v5.0.2) (2025-12-28)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* enhance signature canvas with bottom sheet integration and WebView improvements ([69f84a1](https://github.com/YanYuanFE/react-native-signature-canvas/commit/69f84a1))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [5.0.1](https://github.com/YanYuanFE/react-native-signature-canvas/compare/v4.5.1...v5.0.1) (2025-12-28)
|
|
2
11
|
|
|
3
12
|
|
|
4
13
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -401,12 +401,6 @@ const styles = StyleSheet.create({
|
|
|
401
401
|
|
|
402
402
|
## Performance & Reliability
|
|
403
403
|
|
|
404
|
-
### Automatic Error Recovery
|
|
405
|
-
- **Smart retry logic** with exponential backoff
|
|
406
|
-
- **Circuit breaker pattern** to prevent cascading failures
|
|
407
|
-
- **Memory leak prevention** with automatic cleanup
|
|
408
|
-
- **Performance monitoring** with automatic optimization
|
|
409
|
-
|
|
410
404
|
### Performance Features
|
|
411
405
|
- **Debounced resize handling** for smooth interaction
|
|
412
406
|
- **Memory pressure detection** with adaptive optimization
|
|
@@ -421,7 +415,7 @@ const styles = StyleSheet.create({
|
|
|
421
415
|
|
|
422
416
|
## Migration Guide
|
|
423
417
|
|
|
424
|
-
### From v4.
|
|
418
|
+
### From v4.x to v5.x
|
|
425
419
|
|
|
426
420
|
This version is fully backward compatible. New features:
|
|
427
421
|
|
|
@@ -515,7 +509,7 @@ npm install && npm start
|
|
|
515
509
|
|
|
516
510
|
## Changelog
|
|
517
511
|
|
|
518
|
-
###
|
|
512
|
+
### v5.0.1 (Latest)
|
|
519
513
|
- 🆕 Added `webviewProps` for WebView customization
|
|
520
514
|
- 🆕 Enhanced error handling with automatic recovery
|
|
521
515
|
- 🆕 Performance monitoring and optimization
|
package/h5/html.js
CHANGED
|
@@ -12,36 +12,36 @@ export default (script) =>
|
|
|
12
12
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
|
13
13
|
|
|
14
14
|
<style>
|
|
15
|
+
* {
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
margin: 0;
|
|
18
|
+
padding: 0;
|
|
19
|
+
}
|
|
20
|
+
html, body {
|
|
21
|
+
width: 100%;
|
|
22
|
+
height: 100%;
|
|
23
|
+
margin: 0;
|
|
24
|
+
padding: 0;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
}
|
|
15
27
|
body {
|
|
16
28
|
font-family: Helvetica, Sans-Serif;
|
|
17
|
-
|
|
18
29
|
-moz-user-select: none;
|
|
19
30
|
-webkit-user-select: none;
|
|
20
31
|
-ms-user-select: none;
|
|
21
|
-
margin:0;
|
|
22
|
-
overflow:hidden;
|
|
23
|
-
}
|
|
24
|
-
body,html {
|
|
25
|
-
width: 100%;
|
|
26
|
-
height: 300px;
|
|
27
32
|
}
|
|
28
|
-
|
|
29
|
-
box-sizing: border-box;
|
|
30
|
-
margin: 0;
|
|
31
|
-
padding: 0;
|
|
32
|
-
}
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
.rotated-true {
|
|
35
35
|
transform: rotate(90deg);
|
|
36
36
|
transform-origin:bottom left;
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
position:absolute;
|
|
39
39
|
top: -100vw;
|
|
40
40
|
left: 0;
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
height:100vw;
|
|
43
43
|
width:100vh;
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
overflow:auto;
|
|
46
46
|
}
|
|
47
47
|
.rotated-false {
|
|
@@ -56,15 +56,16 @@ export default (script) =>
|
|
|
56
56
|
background-color: #fff;
|
|
57
57
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.27), 0 0 40px rgba(0, 0, 0, 0.08) inset;
|
|
58
58
|
}
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
.m-signature-pad--body {
|
|
61
61
|
position: relative;
|
|
62
|
+
width: 100%;
|
|
62
63
|
height: 100%;
|
|
63
64
|
border: 1px solid #f4f4f4;
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
.m-signature-pad--body
|
|
67
|
-
|
|
66
|
+
|
|
67
|
+
.m-signature-pad--body canvas {
|
|
68
|
+
display: block;
|
|
68
69
|
position: absolute;
|
|
69
70
|
left: 0;
|
|
70
71
|
top: 0;
|
|
@@ -125,7 +126,7 @@ export default (script) =>
|
|
|
125
126
|
|
|
126
127
|
@media screen and (min-device-width: 768px) and (max-device-width: 1024px) {
|
|
127
128
|
.m-signature-pad {
|
|
128
|
-
margin:
|
|
129
|
+
margin: 0;
|
|
129
130
|
}
|
|
130
131
|
}
|
|
131
132
|
|
package/h5/js/app.js
CHANGED
|
@@ -1,56 +1,39 @@
|
|
|
1
1
|
export default `
|
|
2
|
-
// Enhanced error handling and validation
|
|
3
2
|
var wrapper = document.getElementById("signature-pad"),
|
|
4
3
|
clearButton = wrapper && wrapper.querySelector("[data-action=clear]"),
|
|
5
4
|
saveButton = wrapper && wrapper.querySelector("[data-action=save]"),
|
|
6
5
|
canvas = wrapper && wrapper.querySelector("canvas"),
|
|
7
6
|
signaturePad;
|
|
8
|
-
|
|
9
|
-
if (!wrapper || !canvas) {
|
|
10
|
-
console.error('Required DOM elements not found');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Enhanced canvas resize with debouncing
|
|
14
|
-
function debounce(func, wait) {
|
|
15
|
-
var timeout;
|
|
16
|
-
return function executedFunction() {
|
|
17
|
-
var later = function() {
|
|
18
|
-
clearTimeout(timeout);
|
|
19
|
-
func.apply(this, arguments);
|
|
20
|
-
};
|
|
21
|
-
clearTimeout(timeout);
|
|
22
|
-
timeout = setTimeout(later, wait);
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
7
|
+
|
|
26
8
|
function resizeCanvas() {
|
|
27
|
-
if (!canvas || !canvas.getContext) {
|
|
28
|
-
console.warn('Canvas not available for resize');
|
|
9
|
+
if (!canvas || !canvas.getContext || !signaturePad) {
|
|
29
10
|
return;
|
|
30
11
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
12
|
+
|
|
13
|
+
var context = canvas.getContext("2d");
|
|
14
|
+
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
|
15
|
+
|
|
16
|
+
// Save current signature data before resizing
|
|
17
|
+
var imgData = signaturePad.toData();
|
|
18
|
+
var hasDrawnContent = imgData && imgData.length > 0;
|
|
19
|
+
|
|
20
|
+
// Use canvas client dimensions
|
|
21
|
+
var width = canvas.clientWidth;
|
|
22
|
+
var height = canvas.clientHeight;
|
|
23
|
+
|
|
24
|
+
// Resize canvas (this clears the canvas)
|
|
25
|
+
canvas.width = width * ratio;
|
|
26
|
+
canvas.height = height * ratio;
|
|
27
|
+
context.scale(ratio, ratio);
|
|
28
|
+
|
|
29
|
+
// Restore signature content
|
|
30
|
+
if (hasDrawnContent) {
|
|
31
|
+
signaturePad.fromData(imgData);
|
|
32
|
+
} else if (dataURL) {
|
|
33
|
+
signaturePad.fromDataURL(dataURL);
|
|
46
34
|
}
|
|
47
35
|
}
|
|
48
|
-
|
|
49
|
-
// Use debounced resize handler
|
|
50
|
-
var debouncedResize = debounce(resizeCanvas, 100);
|
|
51
|
-
window.addEventListener('resize', debouncedResize);
|
|
52
|
-
resizeCanvas();
|
|
53
|
-
|
|
36
|
+
|
|
54
37
|
signaturePad = new SignaturePad(canvas, {
|
|
55
38
|
onBegin: () => window.ReactNativeWebView.postMessage("BEGIN"),
|
|
56
39
|
onEnd: () => window.ReactNativeWebView.postMessage("END"),
|
|
@@ -62,16 +45,20 @@ export default `
|
|
|
62
45
|
minDistance: <%minDistance%>,
|
|
63
46
|
});
|
|
64
47
|
|
|
48
|
+
// Initial canvas setup
|
|
49
|
+
resizeCanvas();
|
|
50
|
+
|
|
65
51
|
function clearSignature () {
|
|
66
52
|
signaturePad.clear();
|
|
53
|
+
dataURL='';
|
|
67
54
|
window.ReactNativeWebView.postMessage("CLEAR");
|
|
68
55
|
}
|
|
69
|
-
|
|
56
|
+
|
|
70
57
|
function undo() {
|
|
71
58
|
signaturePad.undo();
|
|
72
59
|
window.ReactNativeWebView.postMessage("UNDO");
|
|
73
60
|
}
|
|
74
|
-
|
|
61
|
+
|
|
75
62
|
function redo() {
|
|
76
63
|
signaturePad.redo();
|
|
77
64
|
window.ReactNativeWebView.postMessage("REDO");
|
|
@@ -79,31 +66,24 @@ export default `
|
|
|
79
66
|
|
|
80
67
|
function changePenColor(color) {
|
|
81
68
|
if (!signaturePad) {
|
|
82
|
-
console.warn('SignaturePad not initialized');
|
|
83
69
|
return;
|
|
84
70
|
}
|
|
85
|
-
|
|
86
71
|
signaturePad.penColor = color;
|
|
87
72
|
window.ReactNativeWebView && window.ReactNativeWebView.postMessage("CHANGE_PEN");
|
|
88
73
|
}
|
|
89
74
|
|
|
90
75
|
function changePenSize(minW, maxW) {
|
|
91
76
|
if (!signaturePad) {
|
|
92
|
-
console.warn('SignaturePad not initialized');
|
|
93
77
|
return;
|
|
94
78
|
}
|
|
95
|
-
|
|
96
|
-
// Validate numeric values
|
|
97
79
|
if (typeof minW !== 'number' || typeof maxW !== 'number' || minW < 0 || maxW < minW) {
|
|
98
|
-
console.warn('Invalid pen size values:', minW, maxW);
|
|
99
80
|
return;
|
|
100
81
|
}
|
|
101
|
-
|
|
102
82
|
signaturePad.minWidth = minW;
|
|
103
83
|
signaturePad.maxWidth = maxW;
|
|
104
84
|
window.ReactNativeWebView && window.ReactNativeWebView.postMessage("CHANGE_PEN_SIZE");
|
|
105
85
|
}
|
|
106
|
-
|
|
86
|
+
|
|
107
87
|
function getData () {
|
|
108
88
|
var data = signaturePad.toData();
|
|
109
89
|
window.ReactNativeWebView.postMessage(JSON.stringify(data));
|
|
@@ -128,11 +108,10 @@ export default `
|
|
|
128
108
|
var myImage = new Image();
|
|
129
109
|
myImage.crossOrigin = "Anonymous";
|
|
130
110
|
myImage.onload = function(){
|
|
131
|
-
window.ReactNativeWebView.postMessage(removeImageBlanks(myImage));
|
|
111
|
+
window.ReactNativeWebView.postMessage(removeImageBlanks(myImage));
|
|
132
112
|
}
|
|
133
113
|
myImage.src = url;
|
|
134
114
|
|
|
135
|
-
//-----------------------------------------//
|
|
136
115
|
function removeImageBlanks(imageObject) {
|
|
137
116
|
var imgWidth = imageObject.width;
|
|
138
117
|
var imgHeight = imageObject.height;
|
|
@@ -157,49 +136,40 @@ export default `
|
|
|
157
136
|
};
|
|
158
137
|
},
|
|
159
138
|
isWhite = function (rgb) {
|
|
160
|
-
// many images contain noise, as the white is not a pure #fff white
|
|
161
139
|
return !rgb.opacity || (rgb.red > 200 && rgb.green > 200 && rgb.blue > 200);
|
|
162
140
|
},
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return y;
|
|
175
|
-
} else {
|
|
176
|
-
return Math.min(y + 1, imgHeight);
|
|
141
|
+
scanY = function (fromTop) {
|
|
142
|
+
var offset = fromTop ? 1 : -1;
|
|
143
|
+
for(var y = fromTop ? 0 : imgHeight - 1; fromTop ? (y < imgHeight) : (y > -1); y += offset) {
|
|
144
|
+
for(var x = 0; x < imgWidth; x++) {
|
|
145
|
+
var rgb = getRGB(x, y);
|
|
146
|
+
if (!isWhite(rgb)) {
|
|
147
|
+
if (fromTop) {
|
|
148
|
+
return y;
|
|
149
|
+
} else {
|
|
150
|
+
return Math.min(y + 1, imgHeight);
|
|
151
|
+
}
|
|
177
152
|
}
|
|
178
153
|
}
|
|
179
154
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (fromLeft) {
|
|
194
|
-
return x;
|
|
195
|
-
} else {
|
|
196
|
-
return Math.min(x + 1, imgWidth);
|
|
155
|
+
return null;
|
|
156
|
+
},
|
|
157
|
+
scanX = function (fromLeft) {
|
|
158
|
+
var offset = fromLeft? 1 : -1;
|
|
159
|
+
for(var x = fromLeft ? 0 : imgWidth - 1; fromLeft ? (x < imgWidth) : (x > -1); x += offset) {
|
|
160
|
+
for(var y = 0; y < imgHeight; y++) {
|
|
161
|
+
var rgb = getRGB(x, y);
|
|
162
|
+
if (!isWhite(rgb)) {
|
|
163
|
+
if (fromLeft) {
|
|
164
|
+
return x;
|
|
165
|
+
} else {
|
|
166
|
+
return Math.min(x + 1, imgWidth);
|
|
167
|
+
}
|
|
197
168
|
}
|
|
198
|
-
}
|
|
169
|
+
}
|
|
199
170
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
};
|
|
171
|
+
return null;
|
|
172
|
+
};
|
|
203
173
|
|
|
204
174
|
var cropTop = scanY(true),
|
|
205
175
|
cropBottom = scanY(false),
|
|
@@ -210,7 +180,6 @@ export default `
|
|
|
210
180
|
|
|
211
181
|
canvas.setAttribute("width", cropWidth);
|
|
212
182
|
canvas.setAttribute("height", cropHeight);
|
|
213
|
-
// finally crop the guy
|
|
214
183
|
canvas.getContext("2d").drawImage(imageObject,
|
|
215
184
|
cropLeft, cropTop, cropWidth, cropHeight,
|
|
216
185
|
0, 0, cropWidth, cropHeight);
|
|
@@ -221,34 +190,29 @@ export default `
|
|
|
221
190
|
|
|
222
191
|
function readSignature() {
|
|
223
192
|
if (!signaturePad) {
|
|
224
|
-
console.warn('SignaturePad not initialized');
|
|
225
193
|
return;
|
|
226
194
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
195
|
+
|
|
196
|
+
if (signaturePad.isEmpty()) {
|
|
197
|
+
window.ReactNativeWebView && window.ReactNativeWebView.postMessage("EMPTY");
|
|
198
|
+
} else {
|
|
199
|
+
var imageType = '<%imageType%>' || 'image/png';
|
|
200
|
+
var url = signaturePad.toDataURL(imageType);
|
|
201
|
+
|
|
202
|
+
if (trimWhitespace === true) {
|
|
203
|
+
cropWhitespace(url);
|
|
231
204
|
} else {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
} else {
|
|
238
|
-
window.ReactNativeWebView && window.ReactNativeWebView.postMessage(url);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (autoClear === true && signaturePad) {
|
|
242
|
-
signaturePad.clear();
|
|
243
|
-
}
|
|
205
|
+
window.ReactNativeWebView && window.ReactNativeWebView.postMessage(url);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (autoClear === true && signaturePad) {
|
|
209
|
+
signaturePad.clear();
|
|
244
210
|
}
|
|
245
|
-
} catch (error) {
|
|
246
|
-
console.error('Error reading signature:', error);
|
|
247
211
|
}
|
|
248
212
|
}
|
|
249
213
|
|
|
250
214
|
var autoClear = <%autoClear%>;
|
|
251
|
-
|
|
215
|
+
|
|
252
216
|
var trimWhitespace = <%trimWhitespace%>;
|
|
253
217
|
|
|
254
218
|
var dataURL = '<%dataURL%>';
|
|
@@ -259,18 +223,12 @@ export default `
|
|
|
259
223
|
clearButton.addEventListener("click", clearSignature);
|
|
260
224
|
}
|
|
261
225
|
|
|
262
|
-
// Prevent race conditions by sequencing operations
|
|
263
226
|
if (saveButton) {
|
|
264
227
|
saveButton.addEventListener("click", function() {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
getData();
|
|
270
|
-
}, 10);
|
|
271
|
-
} catch (error) {
|
|
272
|
-
console.error('Error in save button click:', error);
|
|
273
|
-
}
|
|
228
|
+
readSignature();
|
|
229
|
+
setTimeout(function() {
|
|
230
|
+
getData();
|
|
231
|
+
}, 10);
|
|
274
232
|
});
|
|
275
233
|
}
|
|
276
234
|
`;
|
package/index.d.ts
CHANGED
|
@@ -76,8 +76,11 @@ declare module "react-native-signature-canvas" {
|
|
|
76
76
|
readSignature: () => void;
|
|
77
77
|
undo: () => void;
|
|
78
78
|
redo: () => void;
|
|
79
|
-
fromData: (pointGroups, suppressClear
|
|
80
|
-
|
|
79
|
+
fromData: (pointGroups: any[], suppressClear?: boolean) => void;
|
|
80
|
+
/** Set dataURL without causing WebView reload - useful for restoring signatures */
|
|
81
|
+
setDataURL: (url: string) => void;
|
|
82
|
+
/** Force reinitialize WebView - useful for bottom sheets/modals where WebView state is lost */
|
|
83
|
+
reinitialize: () => void;
|
|
81
84
|
};
|
|
82
85
|
|
|
83
86
|
// Enhanced component interface with better type safety
|
package/index.js
CHANGED
|
@@ -46,6 +46,17 @@ const styles = StyleSheet.create({
|
|
|
46
46
|
},
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
const getParamForInjection = (param) => {
|
|
50
|
+
switch (typeof param) {
|
|
51
|
+
case "string":
|
|
52
|
+
return `'${param}'`
|
|
53
|
+
case "object":
|
|
54
|
+
return JSON.stringify(param)
|
|
55
|
+
default:
|
|
56
|
+
return param
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
49
60
|
const SignatureView = forwardRef(
|
|
50
61
|
(
|
|
51
62
|
{
|
|
@@ -97,18 +108,29 @@ const SignatureView = forwardRef(
|
|
|
97
108
|
ref
|
|
98
109
|
) => {
|
|
99
110
|
const [loading, setLoading] = useState(true);
|
|
100
|
-
|
|
101
111
|
const [hasError, setHasError] = useState(false);
|
|
102
112
|
const [retryCount, setRetryCount] = useState(0);
|
|
113
|
+
// Key to force WebView remount when needed (e.g., after content process termination)
|
|
114
|
+
const [webViewKey, setWebViewKey] = useState(0);
|
|
103
115
|
const maxRetries = 3;
|
|
104
116
|
const webViewRef = useRef();
|
|
117
|
+
// Store dataURL for injection - updates when dataURL prop changes
|
|
118
|
+
const currentDataURLRef = useRef(dataURL);
|
|
119
|
+
|
|
120
|
+
// Update ref when dataURL prop changes
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
currentDataURLRef.current = dataURL;
|
|
123
|
+
}, [dataURL]);
|
|
124
|
+
|
|
105
125
|
// Split source generation for better performance
|
|
126
|
+
// Include webViewKey to regenerate script when WebView needs remounting
|
|
106
127
|
const injectedScript = useMemo(() => {
|
|
107
128
|
let script = injectedSignaturePad + injectedApplication;
|
|
108
129
|
script = script.replace(/<%autoClear%>/g, autoClear);
|
|
109
130
|
script = script.replace(/<%trimWhitespace%>/g, trimWhitespace);
|
|
110
131
|
script = script.replace(/<%imageType%>/g, imageType || "image/png");
|
|
111
|
-
|
|
132
|
+
// Use currentDataURLRef to get the latest dataURL value
|
|
133
|
+
script = script.replace(/<%dataURL%>/g, currentDataURLRef.current || "");
|
|
112
134
|
script = script.replace(/<%penColor%>/g, penColor || "black");
|
|
113
135
|
script = script.replace(/<%backgroundColor%>/g, backgroundColor || "rgba(255,255,255,0)");
|
|
114
136
|
script = script.replace(/<%dotSize%>/g, dotSize || "null");
|
|
@@ -117,7 +139,7 @@ const SignatureView = forwardRef(
|
|
|
117
139
|
script = script.replace(/<%minDistance%>/g, minDistance || 5);
|
|
118
140
|
script = script.replace(/<%orientation%>/g, rotated || false);
|
|
119
141
|
return script;
|
|
120
|
-
}, [autoClear, trimWhitespace, imageType,
|
|
142
|
+
}, [autoClear, trimWhitespace, imageType, penColor, backgroundColor, dotSize, minWidth, maxWidth, minDistance, rotated, webViewKey]);
|
|
121
143
|
|
|
122
144
|
const source = useMemo(() => {
|
|
123
145
|
const htmlContentValue = customHtml || htmlContent;
|
|
@@ -137,23 +159,33 @@ const SignatureView = forwardRef(
|
|
|
137
159
|
return { html };
|
|
138
160
|
}, [injectedScript, customHtml, bgWidth, bgHeight, bgSrc, overlayWidth, overlayHeight, overlaySrc, webStyle, descriptionText, confirmText, clearText, rotated]);
|
|
139
161
|
|
|
140
|
-
//
|
|
141
|
-
const
|
|
162
|
+
// Handle dataURL changes dynamically without reloading WebView
|
|
163
|
+
const prevDataURLRef = useRef(dataURL);
|
|
142
164
|
|
|
143
165
|
useEffect(() => {
|
|
144
|
-
|
|
145
|
-
|
|
166
|
+
// Skip if dataURL hasn't changed or WebView isn't ready
|
|
167
|
+
if (prevDataURLRef.current === dataURL || !webViewRef.current || loading) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
146
170
|
|
|
147
|
-
|
|
148
|
-
|
|
171
|
+
prevDataURLRef.current = dataURL;
|
|
172
|
+
|
|
173
|
+
// Update dataURL in WebView without full reload
|
|
174
|
+
if (dataURL) {
|
|
175
|
+
const script = `
|
|
176
|
+
dataURL = '${dataURL}';
|
|
177
|
+
if (signaturePad && signaturePad.isEmpty()) {
|
|
178
|
+
signaturePad.fromDataURL(dataURL);
|
|
179
|
+
}
|
|
180
|
+
true;
|
|
181
|
+
`;
|
|
149
182
|
try {
|
|
150
|
-
webViewRef.current.
|
|
151
|
-
setShouldReload(false);
|
|
183
|
+
webViewRef.current.injectJavaScript(script);
|
|
152
184
|
} catch (error) {
|
|
153
|
-
console.warn("
|
|
185
|
+
console.warn("Failed to update dataURL:", error);
|
|
154
186
|
}
|
|
155
187
|
}
|
|
156
|
-
}, [
|
|
188
|
+
}, [dataURL, loading]);
|
|
157
189
|
|
|
158
190
|
const isJson = (str) => {
|
|
159
191
|
try {
|
|
@@ -228,7 +260,7 @@ const SignatureView = forwardRef(
|
|
|
228
260
|
|
|
229
261
|
try {
|
|
230
262
|
const script = params.length > 0
|
|
231
|
-
? `${method}(${params.map(
|
|
263
|
+
? `${method}(${params.map(getParamForInjection).join(',')});true;`
|
|
232
264
|
: `${method}();true;`;
|
|
233
265
|
webViewRef.current.injectJavaScript(script);
|
|
234
266
|
} catch (error) {
|
|
@@ -267,6 +299,38 @@ const SignatureView = forwardRef(
|
|
|
267
299
|
}
|
|
268
300
|
executeWebViewMethod('fromData', [pointGroups, false]);
|
|
269
301
|
},
|
|
302
|
+
// New method to set dataURL without causing WebView reload
|
|
303
|
+
setDataURL: (url) => {
|
|
304
|
+
if (typeof url !== 'string') {
|
|
305
|
+
console.warn('setDataURL: url must be a string');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (!webViewRef.current) {
|
|
309
|
+
console.warn('WebView ref is null when calling setDataURL');
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const script = `
|
|
313
|
+
dataURL = '${url}';
|
|
314
|
+
if (signaturePad) {
|
|
315
|
+
signaturePad.clear();
|
|
316
|
+
if (dataURL) {
|
|
317
|
+
signaturePad.fromDataURL(dataURL);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
true;
|
|
321
|
+
`;
|
|
322
|
+
try {
|
|
323
|
+
webViewRef.current.injectJavaScript(script);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.error('Error executing setDataURL:', error);
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
// Force reinitialize WebView - useful for bottom sheets/modals where WebView state is lost
|
|
329
|
+
reinitialize: () => {
|
|
330
|
+
setLoading(true);
|
|
331
|
+
setHasError(false);
|
|
332
|
+
setWebViewKey(prev => prev + 1);
|
|
333
|
+
},
|
|
270
334
|
}),
|
|
271
335
|
[executeWebViewMethod]
|
|
272
336
|
);
|
|
@@ -296,6 +360,16 @@ const SignatureView = forwardRef(
|
|
|
296
360
|
}
|
|
297
361
|
}, [onError, retryCount, maxRetries]);
|
|
298
362
|
|
|
363
|
+
// Handle iOS WebView content process termination (WKWebView can be killed when app is backgrounded)
|
|
364
|
+
// This is crucial for bottom sheets and modals where the component stays mounted but WebView is killed
|
|
365
|
+
const handleContentProcessDidTerminate = useCallback(() => {
|
|
366
|
+
console.warn("WebView content process terminated, reinitializing...");
|
|
367
|
+
setLoading(true);
|
|
368
|
+
setHasError(false);
|
|
369
|
+
// Increment key to force WebView remount with fresh JavaScript context
|
|
370
|
+
setWebViewKey(prev => prev + 1);
|
|
371
|
+
}, []);
|
|
372
|
+
|
|
299
373
|
const handleLoadEnd = useCallback(() => {
|
|
300
374
|
setLoading(false);
|
|
301
375
|
setHasError(false);
|
|
@@ -323,6 +397,8 @@ const SignatureView = forwardRef(
|
|
|
323
397
|
return (
|
|
324
398
|
<View style={[styles.webBg, style]}>
|
|
325
399
|
<WebView
|
|
400
|
+
// Key for forcing remount when WebView needs reinitialization
|
|
401
|
+
key={`signature-webview-${webViewKey}`}
|
|
326
402
|
// Core functionality props (cannot be overridden)
|
|
327
403
|
ref={webViewRef}
|
|
328
404
|
source={source}
|
|
@@ -331,11 +407,13 @@ const SignatureView = forwardRef(
|
|
|
331
407
|
onLoadEnd={handleLoadEnd}
|
|
332
408
|
onLoadStart={handleLoadStart}
|
|
333
409
|
onLoadProgress={handleLoadProgress}
|
|
410
|
+
// Handle iOS WKWebView content process termination (crucial for bottom sheets/modals)
|
|
411
|
+
onContentProcessDidTerminate={handleContentProcessDidTerminate}
|
|
334
412
|
javaScriptEnabled={true}
|
|
335
413
|
useWebKit={true}
|
|
336
414
|
// Default component props (can be overridden by webviewProps)
|
|
337
415
|
bounces={false}
|
|
338
|
-
style={[webviewContainerStyle]}
|
|
416
|
+
style={[{ flex: 1 }, webviewContainerStyle]}
|
|
339
417
|
scrollEnabled={scrollable}
|
|
340
418
|
androidLayerType={androidLayerType}
|
|
341
419
|
androidHardwareAccelerationDisabled={
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-signature-canvas",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.2",
|
|
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 -s
|
|
7
|
+
"genlog": "conventional-changelog -p angular -i CHANGELOG.md -s",
|
|
8
8
|
"postversion": "git push --follow-tags"
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|