react-native-signature-canvas 5.0.0 → 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 CHANGED
@@ -1,4 +1,13 @@
1
- # [5.0.0](https://github.com/YanYuanFE/react-native-signature-canvas/compare/v4.5.1...v5.0.0) (2025-06-28)
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.6.x to v4.7.x
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
- ### v4.7.x (Latest)
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
- canvas {
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: 10%;
129
+ margin: 0;
129
130
  }
130
131
  }
131
132
 
package/h5/js/app.js CHANGED
@@ -1,57 +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
- return;
12
- }
13
-
14
- // Enhanced canvas resize with debouncing
15
- function debounce(func, wait) {
16
- var timeout;
17
- return function executedFunction() {
18
- var later = function() {
19
- clearTimeout(timeout);
20
- func.apply(this, arguments);
21
- };
22
- clearTimeout(timeout);
23
- timeout = setTimeout(later, wait);
24
- };
25
- }
26
-
7
+
27
8
  function resizeCanvas() {
28
- if (!canvas || !canvas.getContext) {
29
- console.warn('Canvas not available for resize');
9
+ if (!canvas || !canvas.getContext || !signaturePad) {
30
10
  return;
31
11
  }
32
-
33
- try {
34
- var context = canvas.getContext("2d");
35
- var imgData = signaturePad ? signaturePad.toData() : null;
36
- var ratio = Math.max(window.devicePixelRatio || 1, 1);
37
-
38
- canvas.width = canvas.offsetWidth * ratio;
39
- canvas.height = canvas.offsetHeight * ratio;
40
- context.scale(ratio, ratio);
41
-
42
- if (imgData && signaturePad) {
43
- signaturePad.fromData(imgData);
44
- }
45
- } catch (error) {
46
- console.error('Error resizing canvas:', error);
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);
47
34
  }
48
35
  }
49
-
50
- // Use debounced resize handler
51
- var debouncedResize = debounce(resizeCanvas, 100);
52
- window.addEventListener('resize', debouncedResize);
53
- resizeCanvas();
54
-
36
+
55
37
  signaturePad = new SignaturePad(canvas, {
56
38
  onBegin: () => window.ReactNativeWebView.postMessage("BEGIN"),
57
39
  onEnd: () => window.ReactNativeWebView.postMessage("END"),
@@ -63,16 +45,20 @@ export default `
63
45
  minDistance: <%minDistance%>,
64
46
  });
65
47
 
48
+ // Initial canvas setup
49
+ resizeCanvas();
50
+
66
51
  function clearSignature () {
67
52
  signaturePad.clear();
53
+ dataURL='';
68
54
  window.ReactNativeWebView.postMessage("CLEAR");
69
55
  }
70
-
56
+
71
57
  function undo() {
72
58
  signaturePad.undo();
73
59
  window.ReactNativeWebView.postMessage("UNDO");
74
60
  }
75
-
61
+
76
62
  function redo() {
77
63
  signaturePad.redo();
78
64
  window.ReactNativeWebView.postMessage("REDO");
@@ -80,37 +66,24 @@ export default `
80
66
 
81
67
  function changePenColor(color) {
82
68
  if (!signaturePad) {
83
- console.warn('SignaturePad not initialized');
84
- return;
85
- }
86
-
87
- // Validate color format
88
- if (typeof color !== 'string' || (!color.match(/^#[0-9A-F]{6}$/i) && !color.match(/^rgba?\(/))) {
89
- console.warn('Invalid color format:', color);
90
69
  return;
91
70
  }
92
-
93
71
  signaturePad.penColor = color;
94
72
  window.ReactNativeWebView && window.ReactNativeWebView.postMessage("CHANGE_PEN");
95
73
  }
96
74
 
97
75
  function changePenSize(minW, maxW) {
98
76
  if (!signaturePad) {
99
- console.warn('SignaturePad not initialized');
100
77
  return;
101
78
  }
102
-
103
- // Validate numeric values
104
79
  if (typeof minW !== 'number' || typeof maxW !== 'number' || minW < 0 || maxW < minW) {
105
- console.warn('Invalid pen size values:', minW, maxW);
106
80
  return;
107
81
  }
108
-
109
82
  signaturePad.minWidth = minW;
110
83
  signaturePad.maxWidth = maxW;
111
84
  window.ReactNativeWebView && window.ReactNativeWebView.postMessage("CHANGE_PEN_SIZE");
112
85
  }
113
-
86
+
114
87
  function getData () {
115
88
  var data = signaturePad.toData();
116
89
  window.ReactNativeWebView.postMessage(JSON.stringify(data));
@@ -135,11 +108,10 @@ export default `
135
108
  var myImage = new Image();
136
109
  myImage.crossOrigin = "Anonymous";
137
110
  myImage.onload = function(){
138
- window.ReactNativeWebView.postMessage(removeImageBlanks(myImage)); //Will return cropped image data
111
+ window.ReactNativeWebView.postMessage(removeImageBlanks(myImage));
139
112
  }
140
113
  myImage.src = url;
141
114
 
142
- //-----------------------------------------//
143
115
  function removeImageBlanks(imageObject) {
144
116
  var imgWidth = imageObject.width;
145
117
  var imgHeight = imageObject.height;
@@ -164,49 +136,40 @@ export default `
164
136
  };
165
137
  },
166
138
  isWhite = function (rgb) {
167
- // many images contain noise, as the white is not a pure #fff white
168
139
  return !rgb.opacity || (rgb.red > 200 && rgb.green > 200 && rgb.blue > 200);
169
140
  },
170
- scanY = function (fromTop) {
171
- var offset = fromTop ? 1 : -1;
172
-
173
- // loop through each row
174
- for(var y = fromTop ? 0 : imgHeight - 1; fromTop ? (y < imgHeight) : (y > -1); y += offset) {
175
-
176
- // loop through each column
177
- for(var x = 0; x < imgWidth; x++) {
178
- var rgb = getRGB(x, y);
179
- if (!isWhite(rgb)) {
180
- if (fromTop) {
181
- return y;
182
- } else {
183
- 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
+ }
184
152
  }
185
153
  }
186
154
  }
187
- }
188
- return null; // all image is white
189
- },
190
- scanX = function (fromLeft) {
191
- var offset = fromLeft? 1 : -1;
192
-
193
- // loop through each column
194
- for(var x = fromLeft ? 0 : imgWidth - 1; fromLeft ? (x < imgWidth) : (x > -1); x += offset) {
195
-
196
- // loop through each row
197
- for(var y = 0; y < imgHeight; y++) {
198
- var rgb = getRGB(x, y);
199
- if (!isWhite(rgb)) {
200
- if (fromLeft) {
201
- return x;
202
- } else {
203
- 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
+ }
204
168
  }
205
- }
169
+ }
206
170
  }
207
- }
208
- return null; // all image is white
209
- };
171
+ return null;
172
+ };
210
173
 
211
174
  var cropTop = scanY(true),
212
175
  cropBottom = scanY(false),
@@ -217,7 +180,6 @@ export default `
217
180
 
218
181
  canvas.setAttribute("width", cropWidth);
219
182
  canvas.setAttribute("height", cropHeight);
220
- // finally crop the guy
221
183
  canvas.getContext("2d").drawImage(imageObject,
222
184
  cropLeft, cropTop, cropWidth, cropHeight,
223
185
  0, 0, cropWidth, cropHeight);
@@ -228,34 +190,29 @@ export default `
228
190
 
229
191
  function readSignature() {
230
192
  if (!signaturePad) {
231
- console.warn('SignaturePad not initialized');
232
193
  return;
233
194
  }
234
-
235
- try {
236
- if (signaturePad.isEmpty()) {
237
- window.ReactNativeWebView && window.ReactNativeWebView.postMessage("EMPTY");
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);
238
204
  } else {
239
- var imageType = '<%imageType%>' || 'image/png';
240
- var url = signaturePad.toDataURL(imageType);
241
-
242
- if (trimWhitespace === true) {
243
- cropWhitespace(url);
244
- } else {
245
- window.ReactNativeWebView && window.ReactNativeWebView.postMessage(url);
246
- }
247
-
248
- if (autoClear === true && signaturePad) {
249
- signaturePad.clear();
250
- }
205
+ window.ReactNativeWebView && window.ReactNativeWebView.postMessage(url);
206
+ }
207
+
208
+ if (autoClear === true && signaturePad) {
209
+ signaturePad.clear();
251
210
  }
252
- } catch (error) {
253
- console.error('Error reading signature:', error);
254
211
  }
255
212
  }
256
213
 
257
214
  var autoClear = <%autoClear%>;
258
-
215
+
259
216
  var trimWhitespace = <%trimWhitespace%>;
260
217
 
261
218
  var dataURL = '<%dataURL%>';
@@ -266,18 +223,12 @@ export default `
266
223
  clearButton.addEventListener("click", clearSignature);
267
224
  }
268
225
 
269
- // Prevent race conditions by sequencing operations
270
226
  if (saveButton) {
271
227
  saveButton.addEventListener("click", function() {
272
- try {
273
- readSignature();
274
- // Small delay to prevent race condition
275
- setTimeout(function() {
276
- getData();
277
- }, 10);
278
- } catch (error) {
279
- console.error('Error in save button click:', error);
280
- }
228
+ readSignature();
229
+ setTimeout(function() {
230
+ getData();
231
+ }, 10);
281
232
  });
282
233
  }
283
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 = false) => void;
80
- // Removed cropWhitespace as it's not exposed in the component
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
- script = script.replace(/<%dataURL%>/g, dataURL || "");
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, dataURL, penColor, backgroundColor, dotSize, minWidth, maxWidth, minDistance, rotated]);
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
- // Optimize WebView reload to prevent excessive reloads
141
- const [shouldReload, setShouldReload] = useState(false);
162
+ // Handle dataURL changes dynamically without reloading WebView
163
+ const prevDataURLRef = useRef(dataURL);
142
164
 
143
165
  useEffect(() => {
144
- setShouldReload(true);
145
- }, [source]);
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
- useEffect(() => {
148
- if (shouldReload && webViewRef.current) {
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.reload();
151
- setShouldReload(false);
183
+ webViewRef.current.injectJavaScript(script);
152
184
  } catch (error) {
153
- console.warn("WebView reload failed:", error);
185
+ console.warn("Failed to update dataURL:", error);
154
186
  }
155
187
  }
156
- }, [shouldReload]);
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(p => typeof p === 'string' ? `'${p}'` : p).join(',')});true;`
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.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 -r 0",
7
+ "genlog": "conventional-changelog -p angular -i CHANGELOG.md -s",
8
8
  "postversion": "git push --follow-tags"
9
9
  },
10
10
  "repository": {
package/CLAUDE.md DELETED
@@ -1,88 +0,0 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## Project Overview
6
-
7
- This is `react-native-signature-canvas`, a React Native library for capturing signatures and drawing on a canvas. The library provides a WebView-based signature pad component with smooth drawing capabilities across iOS, Android, and Expo.
8
-
9
- ## Core Architecture
10
-
11
- The library uses a hybrid architecture combining React Native and web technologies:
12
-
13
- - **Main Component** (`index.js`): The React Native component that wraps a WebView
14
- - **WebView Content** (`h5/`): HTML, CSS, and JavaScript that runs inside the WebView
15
- - `html.js`: HTML template with placeholder variables for configuration
16
- - `js/signature_pad.js`: Core signature pad functionality
17
- - `js/app.js`: Application logic for handling signature interactions
18
- - `css/signature-pad.css`: Styling for the signature pad interface
19
- - **TypeScript Definitions** (`index.d.ts`): Type definitions for props and ref methods
20
-
21
- ### Key Technical Details
22
-
23
- 1. **Communication**: React Native communicates with the WebView through:
24
- - `injectedJavaScript`: JavaScript code injected into the WebView
25
- - `onMessage`: Messages sent from WebView to React Native
26
- - Template variables in HTML (e.g., `<%penColor%>`, `<%bgSrc%>`)
27
-
28
- 2. **Signature Export**: Signatures are captured as base64 data URLs and passed back to React Native via the WebView message bridge
29
-
30
- 3. **Customization**: The component supports extensive customization through props that modify the injected HTML/CSS/JS
31
-
32
- ## Common Development Commands
33
-
34
- ### Example Apps
35
- The repository contains multiple example applications for different use cases:
36
-
37
- ```bash
38
- # React Native CLI app with TypeScript
39
- cd example/signapp && npm run android
40
- cd example/signapp && npm run ios
41
-
42
- # Expo app with modern Expo Router
43
- cd example/expo-app && npm start
44
- cd example/expo-app && npm run android
45
- cd example/expo-app && npm run ios
46
-
47
- # Basic React Native CLI app
48
- cd example/exampleApp && npm start
49
- ```
50
-
51
- ### Testing
52
- ```bash
53
- # Run tests in example apps
54
- cd example/signapp && npm test
55
- cd example/expo-app && npm test
56
- ```
57
-
58
- ### Linting
59
- ```bash
60
- # Lint example apps
61
- cd example/signapp && npm run lint
62
- cd example/expo-app && npm run lint
63
- ```
64
-
65
- ## Development Notes
66
-
67
- ### Modifying the Core Component
68
- - The main component logic is in `index.js`
69
- - WebView HTML template is in `h5/html.js`
70
- - JavaScript functionality is split between `h5/js/signature_pad.js` and `h5/js/app.js`
71
-
72
- ### Adding New Features
73
- 1. Add props to the main component interface
74
- 2. Update TypeScript definitions in `index.d.ts`
75
- 3. Modify the HTML template in `h5/html.js` if UI changes are needed
76
- 4. Update the injected JavaScript in `h5/js/app.js` for new functionality
77
- 5. Test across multiple example apps to ensure compatibility
78
-
79
- ### Example App Structure
80
- - `signapp/`: Full React Native CLI app with TypeScript and Jest testing
81
- - `expo-app/`: Modern Expo app with Expo Router and TypeScript
82
- - `exampleApp/`: Basic React Native CLI app
83
- - `expo-app1/`, `signapp1/`, `sign-app/`, `ts-expo/`: Various other example configurations
84
-
85
- ### Dependencies
86
- - Main library only has peer dependency on `react-native-webview`
87
- - Example apps include additional dependencies for testing and development
88
- - No build process required for the main library (it's a pure React Native component)