forked from agilgur5/react-signature-canvas
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.tsx
174 lines (146 loc) · 5.42 KB
/
index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import * as PropTypes from 'prop-types'
import { CanvasHTMLAttributes, Component } from 'react'
import { Options as SignaturePadOptions } from 'signature_pad'
import SignaturePad from 'signature_pad'
import trimCanvas from 'trim-canvas'
export interface SignatureCanvasProps extends SignaturePadOptions {
canvasProps?: CanvasHTMLAttributes<HTMLCanvasElement>
clearOnResize?: boolean
}
export class SignatureCanvas extends Component<SignatureCanvasProps> {
static propTypes = {
// signature_pad's props
velocityFilterWeight: PropTypes.number,
minWidth: PropTypes.number,
maxWidth: PropTypes.number,
minDistance: PropTypes.number,
dotSize: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),
penColor: PropTypes.string,
throttle: PropTypes.number,
onEnd: PropTypes.func,
onBegin: PropTypes.func,
// props specific to the React wrapper
canvasProps: PropTypes.object,
clearOnResize: PropTypes.bool
}
static defaultProps: Pick<SignatureCanvasProps, 'clearOnResize'> = {
clearOnResize: true
}
static refNullError = new Error('react-signature-canvas is currently ' +
'mounting or unmounting: React refs are null during this phase.')
// shortcut reference (https://stackoverflow.com/a/29244254/3431180)
private readonly staticThis = this.constructor as typeof SignatureCanvas
_sigPad: SignaturePad | null = null
_canvas: HTMLCanvasElement | null = null
private readonly setRef = (ref: HTMLCanvasElement | null): void => {
this._canvas = ref
// if component is unmounted, set internal references to null
if (this._canvas === null) {
this._sigPad = null
}
}
_excludeOurProps = (): SignaturePadOptions => {
const { canvasProps, clearOnResize, ...sigPadProps } = this.props
return sigPadProps
}
override componentDidMount: Component['componentDidMount'] = () => {
const canvas = this.getCanvas()
this._sigPad = new SignaturePad(canvas, this._excludeOurProps())
this._resizeCanvas()
this.on()
}
override componentWillUnmount: Component['componentWillUnmount'] = () => {
this.off()
}
// propagate prop updates to SignaturePad
override componentDidUpdate: Component['componentDidUpdate'] = () => {
Object.assign(this._sigPad, this._excludeOurProps())
}
// return the canvas ref for operations like toDataURL
getCanvas = (): HTMLCanvasElement => {
if (this._canvas === null) {
throw this.staticThis.refNullError
}
return this._canvas
}
// return a trimmed copy of the canvas
getTrimmedCanvas = (): HTMLCanvasElement => {
// copy the canvas
const canvas = this.getCanvas()
const copy = document.createElement('canvas')
copy.width = canvas.width
copy.height = canvas.height
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
copy.getContext('2d')!.drawImage(canvas, 0, 0)
// then trim it
return trimCanvas(copy)
}
// return the internal SignaturePad reference
getSignaturePad = (): SignaturePad => {
if (this._sigPad === null) {
throw this.staticThis.refNullError
}
return this._sigPad
}
_checkClearOnResize = (): void => {
if (!this.props.clearOnResize) { // eslint-disable-line @typescript-eslint/strict-boolean-expressions -- this is backward compatible with the previous behavior, where null was treated as falsey
return
}
this._resizeCanvas()
}
_resizeCanvas = (): void => {
const canvasProps = this.props.canvasProps ?? {}
const { width, height } = canvasProps
// don't resize if the canvas has fixed width and height
if (typeof width !== 'undefined' && typeof height !== 'undefined') {
return
}
const canvas = this.getCanvas()
/* When zoomed out to less than 100%, for some very strange reason,
some browsers report devicePixelRatio as less than 1
and only part of the canvas is cleared then. */
const ratio = Math.max(window.devicePixelRatio ?? 1, 1)
if (typeof width === 'undefined') {
canvas.width = canvas.offsetWidth * ratio
}
if (typeof height === 'undefined') {
canvas.height = canvas.offsetHeight * ratio
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
canvas.getContext('2d')!.scale(ratio, ratio)
this.clear()
}
override render: Component['render'] = () => {
const { canvasProps } = this.props
return <canvas ref={this.setRef} {...canvasProps} />
}
// all wrapper functions below render
//
on: SignaturePad['on'] = () => {
window.addEventListener('resize', this._checkClearOnResize)
return this.getSignaturePad().on()
}
off: SignaturePad['off'] = () => {
window.removeEventListener('resize', this._checkClearOnResize)
return this.getSignaturePad().off()
}
clear: SignaturePad['clear'] = () => {
return this.getSignaturePad().clear()
}
isEmpty: SignaturePad['isEmpty'] = () => {
return this.getSignaturePad().isEmpty()
}
fromDataURL: SignaturePad['fromDataURL'] = (dataURL, options) => {
return this.getSignaturePad().fromDataURL(dataURL, options)
}
toDataURL: SignaturePad['toDataURL'] = (type, encoderOptions) => {
return this.getSignaturePad().toDataURL(type as string, encoderOptions as number)
}
fromData: SignaturePad['fromData'] = (pointGroups) => {
return this.getSignaturePad().fromData(pointGroups)
}
toData: SignaturePad['toData'] = () => {
return this.getSignaturePad().toData()
}
}
export default SignatureCanvas