react-native-signature-canvas 4.7.2 → 5.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.
@@ -0,0 +1,166 @@
1
+ # WebView Props Configuration
2
+
3
+ The `react-native-signature-canvas` component now supports a `webviewProps` parameter that allows you to customize the underlying WebView behavior while maintaining the core signature functionality.
4
+
5
+ ## Usage
6
+
7
+ ```jsx
8
+ import SignatureCanvas from 'react-native-signature-canvas';
9
+
10
+ <SignatureCanvas
11
+ // ... other props
12
+ webviewProps={{
13
+ // Any WebView props can be passed here
14
+ cacheEnabled: false,
15
+ allowsFullscreenVideo: false,
16
+ decelerationRate: 'fast',
17
+ // ... more WebView props
18
+ }}
19
+ />
20
+ ```
21
+
22
+ ## Core vs Customizable Props
23
+
24
+ ### Core Props (Cannot be overridden)
25
+ These props are essential for the signature functionality and cannot be overridden via `webviewProps`:
26
+
27
+ - `ref` - Internal WebView reference
28
+ - `source` - HTML content for signature pad
29
+ - `onMessage` - Message handler for signature events
30
+ - `onError` - Error handler (enhanced with retry logic)
31
+ - `onLoadEnd` - Load completion handler
32
+ - `onLoadStart` - Load start handler
33
+ - `onLoadProgress` - Load progress handler
34
+ - `javaScriptEnabled` - Must be true for signature pad to work
35
+ - `useWebKit` - Uses modern WebKit engine
36
+
37
+ ### Default Props (Can be overridden)
38
+ These props have sensible defaults but can be customized via `webviewProps`:
39
+
40
+ #### Performance Optimizations
41
+ ```jsx
42
+ webviewProps={{
43
+ cacheEnabled: true, // Enable/disable WebView cache
44
+ allowsInlineMediaPlayback: false, // Disable media playback
45
+ mediaPlaybackRequiresUserAction: true, // Require user action for media
46
+ allowsBackForwardNavigationGestures: false, // Disable navigation gestures
47
+ }}
48
+ ```
49
+
50
+ #### Security Enhancements
51
+ ```jsx
52
+ webviewProps={{
53
+ allowsLinkPreview: false, // Disable link previews
54
+ allowFileAccess: false, // Disable file access
55
+ allowFileAccessFromFileURLs: false, // Disable file URL access
56
+ allowUniversalAccessFromFileURLs: false, // Disable universal access
57
+ mixedContentMode: "never", // Block mixed content
58
+ originWhitelist: ['*'], // Allow all origins
59
+ }}
60
+ ```
61
+
62
+ #### UI/UX Customization
63
+ ```jsx
64
+ webviewProps={{
65
+ bounces: false, // Disable bounce effect
66
+ scrollEnabled: false, // Disable scrolling (use scrollable prop instead)
67
+ decelerationRate: 'fast', // Scroll deceleration rate
68
+ showsHorizontalScrollIndicator: false, // Hide horizontal scroll
69
+ showsVerticalScrollIndicator: false, // Hide vertical scroll
70
+ }}
71
+ ```
72
+
73
+ #### Android-Specific
74
+ ```jsx
75
+ webviewProps={{
76
+ androidLayerType: "hardware", // Use hardware acceleration
77
+ androidHardwareAccelerationDisabled: false, // Enable hardware acceleration
78
+ }}
79
+ ```
80
+
81
+ ## Common Use Cases
82
+
83
+ ### High Performance Mode
84
+ ```jsx
85
+ <SignatureCanvas
86
+ webviewProps={{
87
+ cacheEnabled: true,
88
+ androidLayerType: "hardware",
89
+ androidHardwareAccelerationDisabled: false,
90
+ }}
91
+ />
92
+ ```
93
+
94
+ ### Low Memory Mode
95
+ ```jsx
96
+ <SignatureCanvas
97
+ webviewProps={{
98
+ cacheEnabled: false,
99
+ androidLayerType: "software",
100
+ androidHardwareAccelerationDisabled: true,
101
+ }}
102
+ />
103
+ ```
104
+
105
+ ### Enhanced Security
106
+ ```jsx
107
+ <SignatureCanvas
108
+ webviewProps={{
109
+ allowFileAccess: false,
110
+ allowFileAccessFromFileURLs: false,
111
+ allowUniversalAccessFromFileURLs: false,
112
+ mixedContentMode: "never",
113
+ originWhitelist: [], // Block all external origins
114
+ }}
115
+ />
116
+ ```
117
+
118
+ ### Custom Scrolling Behavior
119
+ ```jsx
120
+ <SignatureCanvas
121
+ webviewProps={{
122
+ decelerationRate: 'normal',
123
+ alwaysBounceVertical: false,
124
+ alwaysBounceHorizontal: false,
125
+ directionalLockEnabled: true,
126
+ }}
127
+ />
128
+ ```
129
+
130
+ ## Important Notes
131
+
132
+ 1. **Core Functionality**: The `webviewProps` cannot override core functionality props. These are protected to ensure the signature canvas works correctly.
133
+
134
+ 2. **Prop Priority**: User-provided `webviewProps` take precedence over default props but not core props.
135
+
136
+ 3. **TypeScript Support**: The `webviewProps` parameter is fully typed with `Partial<WebViewProps>` from `react-native-webview`.
137
+
138
+ 4. **Performance Impact**: Some WebView props can significantly impact performance. Test thoroughly when customizing performance-related settings.
139
+
140
+ 5. **Platform Differences**: Some props may behave differently on iOS vs Android. Refer to the `react-native-webview` documentation for platform-specific behavior.
141
+
142
+ ## Migration from Previous Versions
143
+
144
+ If you were previously using individual WebView-related props, you can now consolidate them under `webviewProps`:
145
+
146
+ ### Before
147
+ ```jsx
148
+ <SignatureCanvas
149
+ androidLayerType="hardware"
150
+ androidHardwareAccelerationDisabled={false}
151
+ // other props...
152
+ />
153
+ ```
154
+
155
+ ### After
156
+ ```jsx
157
+ <SignatureCanvas
158
+ webviewProps={{
159
+ androidLayerType: "hardware",
160
+ androidHardwareAccelerationDisabled: false,
161
+ }}
162
+ // other props...
163
+ />
164
+ ```
165
+
166
+ Note: The old individual props are still supported for backward compatibility.
package/h5/js/app.js CHANGED
@@ -1,28 +1,55 @@
1
1
  export default `
2
+ // Enhanced error handling and validation
2
3
  var wrapper = document.getElementById("signature-pad"),
3
- clearButton = wrapper.querySelector("[data-action=clear]"),
4
- saveButton = wrapper.querySelector("[data-action=save]"),
5
- canvas = wrapper.querySelector("canvas"),
4
+ clearButton = wrapper && wrapper.querySelector("[data-action=clear]"),
5
+ saveButton = wrapper && wrapper.querySelector("[data-action=save]"),
6
+ canvas = wrapper && wrapper.querySelector("canvas"),
6
7
  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
+ }
7
26
 
8
- // Adjust canvas coordinate space taking into account pixel ratio,
9
- // to make it look crisp on mobile devices.
10
- // This also causes canvas to be cleared.
11
27
  function resizeCanvas() {
12
- // When zoomed out to less than 100%, for some very strange reason,
13
- // some browsers report devicePixelRatio as less than 1
14
- // and only part of the canvas is cleared then.
15
- var context = canvas.getContext("2d"); //context.getImageData(0,0,canvas.width,canvas.height)
16
- var imgData = signaturePad ? signaturePad.toData() : null;
17
- var ratio = Math.max(window.devicePixelRatio || 1, 1);
18
- canvas.width = canvas.offsetWidth * ratio;
19
- canvas.height = canvas.offsetHeight * ratio;
20
- context.scale(ratio, ratio);
21
- // context.putImageData(imgData,0,0);
22
- imgData && signaturePad.fromData(imgData);
28
+ if (!canvas || !canvas.getContext) {
29
+ console.warn('Canvas not available for resize');
30
+ return;
31
+ }
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);
47
+ }
23
48
  }
24
49
 
25
- window.onresize = resizeCanvas;
50
+ // Use debounced resize handler
51
+ var debouncedResize = debounce(resizeCanvas, 100);
52
+ window.addEventListener('resize', debouncedResize);
26
53
  resizeCanvas();
27
54
 
28
55
  signaturePad = new SignaturePad(canvas, {
@@ -33,6 +60,7 @@ export default `
33
60
  dotSize: <%dotSize%>,
34
61
  minWidth: <%minWidth%>,
35
62
  maxWidth: <%maxWidth%>,
63
+ minDistance: <%minDistance%>,
36
64
  });
37
65
 
38
66
  function clearSignature () {
@@ -51,14 +79,36 @@ export default `
51
79
  }
52
80
 
53
81
  function changePenColor(color) {
82
+ 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
+ return;
91
+ }
92
+
54
93
  signaturePad.penColor = color;
55
- window.ReactNativeWebView.postMessage("CHANGE_PEN");
94
+ window.ReactNativeWebView && window.ReactNativeWebView.postMessage("CHANGE_PEN");
56
95
  }
57
96
 
58
97
  function changePenSize(minW, maxW) {
59
- signaturePad.minWidth = minW;
60
- signaturePad.maxWidth = maxW;
61
- window.ReactNativeWebView.postMessage("CHANGE_PEN_SIZE");
98
+ if (!signaturePad) {
99
+ console.warn('SignaturePad not initialized');
100
+ return;
101
+ }
102
+
103
+ // Validate numeric values
104
+ if (typeof minW !== 'number' || typeof maxW !== 'number' || minW < 0 || maxW < minW) {
105
+ console.warn('Invalid pen size values:', minW, maxW);
106
+ return;
107
+ }
108
+
109
+ signaturePad.minWidth = minW;
110
+ signaturePad.maxWidth = maxW;
111
+ window.ReactNativeWebView && window.ReactNativeWebView.postMessage("CHANGE_PEN_SIZE");
62
112
  }
63
113
 
64
114
  function getData () {
@@ -66,6 +116,11 @@ export default `
66
116
  window.ReactNativeWebView.postMessage(JSON.stringify(data));
67
117
  }
68
118
 
119
+ function fromData (pointGroups) {
120
+ signaturePad.fromData(pointGroups);
121
+ window.ReactNativeWebView.postMessage(JSON.stringify(pointGroups));
122
+ }
123
+
69
124
  function draw() {
70
125
  signaturePad.draw();
71
126
  window.ReactNativeWebView.postMessage("DRAW");
@@ -86,8 +141,8 @@ export default `
86
141
 
87
142
  //-----------------------------------------//
88
143
  function removeImageBlanks(imageObject) {
89
- imgWidth = imageObject.width;
90
- imgHeight = imageObject.height;
144
+ var imgWidth = imageObject.width;
145
+ var imgHeight = imageObject.height;
91
146
  var canvas = document.createElement('canvas');
92
147
  canvas.setAttribute("width", imgWidth);
93
148
  canvas.setAttribute("height", imgHeight);
@@ -96,7 +151,10 @@ export default `
96
151
 
97
152
  var imageData = context.getImageData(0, 0, imgWidth, imgHeight),
98
153
  data = imageData.data,
99
- getRBG = function(x, y) {
154
+ getRGB = function(x, y) {
155
+ if (x < 0 || x >= imgWidth || y < 0 || y >= imgHeight) {
156
+ return { red: 255, green: 255, blue: 255, opacity: 255 };
157
+ }
100
158
  var offset = imgWidth * y + x;
101
159
  return {
102
160
  red: data[offset * 4],
@@ -117,7 +175,7 @@ export default `
117
175
 
118
176
  // loop through each column
119
177
  for(var x = 0; x < imgWidth; x++) {
120
- var rgb = getRBG(x, y);
178
+ var rgb = getRGB(x, y);
121
179
  if (!isWhite(rgb)) {
122
180
  if (fromTop) {
123
181
  return y;
@@ -137,7 +195,7 @@ export default `
137
195
 
138
196
  // loop through each row
139
197
  for(var y = 0; y < imgHeight; y++) {
140
- var rgb = getRBG(x, y);
198
+ var rgb = getRGB(x, y);
141
199
  if (!isWhite(rgb)) {
142
200
  if (fromLeft) {
143
201
  return x;
@@ -168,13 +226,31 @@ export default `
168
226
  }
169
227
  }
170
228
 
171
- function readSignature() {
172
- if (signaturePad.isEmpty()) {
173
- window.ReactNativeWebView.postMessage("EMPTY");
174
- } else {
175
- var url = signaturePad.toDataURL('<%imageType%>');
176
- trimWhitespace? cropWhitespace(url): window.ReactNativeWebView.postMessage(url);
177
- if (autoClear) signaturePad.clear();
229
+ function readSignature() {
230
+ if (!signaturePad) {
231
+ console.warn('SignaturePad not initialized');
232
+ return;
233
+ }
234
+
235
+ try {
236
+ if (signaturePad.isEmpty()) {
237
+ window.ReactNativeWebView && window.ReactNativeWebView.postMessage("EMPTY");
238
+ } 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
+ }
251
+ }
252
+ } catch (error) {
253
+ console.error('Error reading signature:', error);
178
254
  }
179
255
  }
180
256
 
@@ -186,10 +262,22 @@ export default `
186
262
 
187
263
  if (dataURL) signaturePad.fromDataURL(dataURL);
188
264
 
189
- clearButton.addEventListener("click", clearSignature );
265
+ if (clearButton) {
266
+ clearButton.addEventListener("click", clearSignature);
267
+ }
190
268
 
191
- saveButton.addEventListener("click", () => {
192
- readSignature();
193
- getData();
194
- });
269
+ // Prevent race conditions by sequencing operations
270
+ if (saveButton) {
271
+ 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
+ }
281
+ });
282
+ }
195
283
  `;
package/index.d.ts CHANGED
@@ -1,14 +1,24 @@
1
1
  declare module "react-native-signature-canvas" {
2
2
  import React from "react";
3
- import {StyleProp, ViewStyle} from "react-native";
3
+ import { StyleProp, ViewStyle } from "react-native";
4
+ import { WebViewProps } from "react-native-webview";
5
+
6
+ // Enhanced type definitions with better error handling
4
7
 
5
8
  type ImageType = "image/png" | "image/jpeg" | "image/svg+xml";
6
9
 
7
- type DataURL = "Base64" | string;
10
+ type DataURL = string; // Simplified - should be base64 data URL
11
+
12
+ type ForwardRef<T, P> = React.ForwardRefExoticComponent<
13
+ React.PropsWithoutRef<P> & React.RefAttributes<T>
14
+ >;
8
15
 
9
- type ForwardRef<T, P> = React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<T>>;
16
+ // Enhanced callback types
17
+ type SignatureCallback = (signature: string) => void;
18
+ type EmptyCallback = () => void;
19
+ type ErrorCallback = (error: Error) => void;
10
20
 
11
- type SignatureViewProps = {
21
+ export type SignatureViewProps = {
12
22
  androidHardwareAccelerationDisabled?: boolean;
13
23
  autoClear?: boolean;
14
24
  backgroundColor?: string;
@@ -24,21 +34,24 @@ declare module "react-native-signature-canvas" {
24
34
  imageType?: ImageType;
25
35
  minWidth?: number;
26
36
  maxWidth?: number;
37
+ minDistance?: number;
38
+ onError?: ErrorCallback; // Added missing prop
27
39
  nestedScrollEnabled?: boolean;
28
40
  showsVerticalScrollIndicator?: boolean;
29
- onOK?: (signature: string) => void;
30
- onEmpty?: () => void;
31
- onClear?: () => void;
32
- onUndo?: () => void;
33
- onRedo?: () => void;
34
- onDraw?: () => void;
35
- onErase?: () => void;
36
- onGetData?: (data: any) => void;
37
- onChangePenColor?: () => void;
38
- onChangePenSize?: () => void;
39
- onBegin?: () => void;
40
- onEnd?: () => void;
41
- onLoadEnd?: () => void;
41
+ onOK?: SignatureCallback;
42
+ onEmpty?: EmptyCallback;
43
+ onClear?: EmptyCallback;
44
+ onUndo?: EmptyCallback;
45
+ onRedo?: EmptyCallback;
46
+ onDraw?: EmptyCallback;
47
+ onErase?: EmptyCallback;
48
+ onGetData?: (data: string) => void; // Should be JSON string
49
+ onChangePenColor?: EmptyCallback;
50
+ onChangePenSize?: EmptyCallback;
51
+ onBegin?: EmptyCallback;
52
+ onEnd?: EmptyCallback;
53
+ onLoadEnd?: EmptyCallback;
54
+ onError?: ErrorCallback; // Added missing error callback
42
55
  overlayHeight?: number;
43
56
  overlayWidth?: number;
44
57
  overlaySrc?: string;
@@ -50,21 +63,32 @@ declare module "react-native-signature-canvas" {
50
63
  webStyle?: string;
51
64
  webviewContainerStyle?: StyleProp<ViewStyle>;
52
65
  androidLayerType?: "none" | "software" | "hardware";
53
- }
66
+ webviewProps?: Partial<WebViewProps>;
67
+ };
54
68
 
55
69
  export type SignatureViewRef = {
56
70
  changePenColor: (color: string) => void;
57
71
  changePenSize: (minW: number, maxW: number) => void;
58
72
  clearSignature: () => void;
59
- cropWhitespace: (url: string) => void;
60
73
  draw: () => void;
61
74
  erase: () => void;
62
75
  getData: () => void;
63
76
  readSignature: () => void;
64
77
  undo: () => void;
65
78
  redo: () => void;
79
+ fromData: (pointGroups, suppressClear = false) => void;
80
+ // Removed cropWhitespace as it's not exposed in the component
81
+ };
82
+
83
+ // Enhanced component interface with better type safety
84
+ interface SignatureCanvasComponent
85
+ extends ForwardRef<SignatureViewRef, SignatureViewProps> {
86
+ displayName?: string;
66
87
  }
67
88
 
68
- const SignatureView: ForwardRef<SignatureViewRef, SignatureViewProps>
89
+ const SignatureView: SignatureCanvasComponent;
69
90
  export default SignatureView;
91
+
92
+ // Export additional types for external use
93
+ export { SignatureViewProps, SignatureViewRef, ImageType, DataURL };
70
94
  }