diff --git a/src/signature_pad.ts b/src/signature_pad.ts index c7a01af0..5991cd96 100644 --- a/src/signature_pad.ts +++ b/src/signature_pad.ts @@ -62,7 +62,7 @@ export default class SignaturePad extends SignatureEventTarget { // Private stuff /* tslint:disable: variable-name */ private _ctx: CanvasRenderingContext2D; - private _drawningStroke = false; + private _drawingStroke = false; private _isEmpty = true; private _lastPoints: Point[] = []; // Stores up to 4 most recent points; used to generate a new curve private _data: PointGroup[] = []; // Stores all points in groups (one group per line or dot) @@ -76,7 +76,7 @@ export default class SignaturePad extends SignatureEventTarget { this.velocityFilterWeight = options.velocityFilterWeight || 0.7; this.minWidth = options.minWidth || 0.5; this.maxWidth = options.maxWidth || 2.5; - this.throttle = ('throttle' in options ? options.throttle : 16) as number; // in milisecondss + this.throttle = ('throttle' in options ? options.throttle : 16) as number; // in milliseconds this.minDistance = ( 'minDistance' in options ? options.minDistance : 5 ) as number; // in pixels @@ -195,23 +195,30 @@ export default class SignaturePad extends SignatureEventTarget { this.canvas.style.msTouchAction = 'auto'; this.canvas.style.userSelect = 'auto'; - this.canvas.removeEventListener('pointerdown', this._handlePointerStart); + this.canvas.removeEventListener('pointerdown', this._handlePointerDown); this.canvas.removeEventListener('pointermove', this._handlePointerMove); + this.canvas.removeEventListener('pointerleave', this._handlePointerLeave); + this.canvas.removeEventListener('pointerenter', this._handlePointerEnter); this.canvas.ownerDocument.removeEventListener( 'pointerup', - this._handlePointerEnd, + this._handlePointerUp, ); + window.removeEventListener('pointerup', this._handlePointerUp); this.canvas.removeEventListener('mousedown', this._handleMouseDown); this.canvas.removeEventListener('mousemove', this._handleMouseMove); + this.canvas.removeEventListener('mouseleave', this._handleMouseLeave); + this.canvas.removeEventListener('mouseenter', this._handleMouseEnter); this.canvas.ownerDocument.removeEventListener( 'mouseup', this._handleMouseUp, ); + window.removeEventListener('mouseup', this._handleMouseUp); this.canvas.removeEventListener('touchstart', this._handleTouchStart); this.canvas.removeEventListener('touchmove', this._handleTouchMove); this.canvas.removeEventListener('touchend', this._handleTouchEnd); + window.removeEventListener('touchend', this._handleTouchEnd); } public isEmpty(): boolean { @@ -241,77 +248,169 @@ export default class SignaturePad extends SignatureEventTarget { // Event handlers private _handleMouseDown = (event: MouseEvent): void => { - if (event.buttons === 1) { - this._drawningStroke = true; - this._strokeBegin(event); + if (event.buttons !== 1 || this._drawingStroke) { + return; + } + + this._drawingStroke = true; + this.canvas.addEventListener('mousemove', this._handleMouseMove); + this.canvas.addEventListener('mouseleave', this._handleMouseLeave); + this.canvas.addEventListener('mouseenter', this._handleMouseEnter); + this._strokeBegin(event); + }; + + private _handleMouseEnter = (event: MouseEvent): void => { + if (event.buttons !== 1 || this._drawingStroke) { + return; } + + this._drawingStroke = true; + this._strokeBegin(event); }; private _handleMouseMove = (event: MouseEvent): void => { - if (this._drawningStroke) { - this._strokeMoveUpdate(event); + if (event.buttons !== 1 || !this._drawingStroke) { + // Stop when not pressing primary button or pressing multiple buttons + this._strokeEnd(event, false); + return; + } + + this._strokeMoveUpdate(event); + }; + + private _handleMouseLeave = (event: MouseEvent): void => { + if (!this._drawingStroke) { + return; } + + this._drawingStroke = false; + this._strokeEnd(event); }; private _handleMouseUp = (event: MouseEvent): void => { - if (event.buttons === 1 && this._drawningStroke) { - this._drawningStroke = false; - this._strokeEnd(event); + this.canvas.removeEventListener('mousemove', this._handleMouseMove); + this.canvas.removeEventListener('mouseenter', this._handleMouseEnter); + this.canvas.removeEventListener('mouseleave', this._handleMouseLeave); + + if (!this._drawingStroke) { + return; } + + this._strokeEnd(event); }; private _handleTouchStart = (event: TouchEvent): void => { + if (this._drawingStroke) { + return; + } + + const touch = event.targetTouches.item(0); + if (!touch) { + return; + } + // Prevent scrolling. if (event.cancelable) { event.preventDefault(); } - if (event.targetTouches.length === 1) { - const touch = event.changedTouches[0]; - this._strokeBegin(touch); - } + this._drawingStroke = true; + this.canvas.addEventListener('touchmove', this._handleTouchMove); + this._strokeBegin(touch); }; private _handleTouchMove = (event: TouchEvent): void => { + const touch = event.targetTouches.item(0); + if (!touch) { + return; + } + + if (!this._drawingStroke) { + this._strokeEnd(touch, false); + return; + } + // Prevent scrolling. if (event.cancelable) { event.preventDefault(); } - const touch = event.targetTouches[0]; this._strokeMoveUpdate(touch); }; private _handleTouchEnd = (event: TouchEvent): void => { - const wasCanvasTouched = event.target === this.canvas; - if (wasCanvasTouched) { - if (event.cancelable) { - event.preventDefault(); - } - const touch = event.changedTouches[0]; - this._strokeEnd(touch); + this.canvas.removeEventListener('touchmove', this._handleTouchMove); + + if (!this._drawingStroke) { + return; } + const touch = event.changedTouches.item(0); + if (!touch) { + return; + } + + if (event.cancelable) { + event.preventDefault(); + } + + this._strokeEnd(touch); + }; + + private _handlePointerDown = (event: PointerEvent): void => { + if (event.buttons !== 1 || this._drawingStroke) { + return; + } + + event.preventDefault(); + this._drawingStroke = true; + this.canvas.addEventListener('pointermove', this._handlePointerMove); + this.canvas.addEventListener('pointerenter', this._handlePointerEnter); + this.canvas.addEventListener('pointerleave', this._handlePointerLeave); + this._strokeBegin(event); }; - private _handlePointerStart = (event: PointerEvent): void => { - this._drawningStroke = true; + private _handlePointerEnter = (event: PointerEvent): void => { + if (event.buttons !== 1 || this._drawingStroke) { + return; + } + event.preventDefault(); + this._drawingStroke = true; this._strokeBegin(event); }; private _handlePointerMove = (event: PointerEvent): void => { - if (this._drawningStroke) { - event.preventDefault(); - this._strokeMoveUpdate(event); + if (event.buttons !== 1 || !this._drawingStroke) { + // Stop when primary button not pressed or multiple buttons pressed + this._strokeEnd(event, false); + return; } + + event.preventDefault(); + this._strokeMoveUpdate(event); }; - private _handlePointerEnd = (event: PointerEvent): void => { - if (this._drawningStroke) { - event.preventDefault(); - this._drawningStroke = false; - this._strokeEnd(event); + private _handlePointerLeave = (event: PointerEvent): void => { + if (!this._drawingStroke) { + return; + } + + event.preventDefault(); + this._drawingStroke = false; + this._strokeEnd(event); + }; + + private _handlePointerUp = (event: PointerEvent): void => { + this.canvas.removeEventListener('pointermove', this._handlePointerMove); + this.canvas.removeEventListener('pointerenter', this._handlePointerEnter); + this.canvas.removeEventListener('pointerleave', this._handlePointerLeave); + + if (!this._drawingStroke) { + return; } + + event.preventDefault(); + this._strokeEnd(event); }; private _getPointGroupOptions(group?: PointGroup) { @@ -395,35 +494,41 @@ export default class SignaturePad extends SignatureEventTarget { this.dispatchEvent(new CustomEvent('afterUpdateStroke', { detail: event })); } - private _strokeEnd(event: SignatureEvent): void { - this._strokeUpdate(event); + private _strokeEnd(event: SignatureEvent, shouldUpdate = true): void { + this._drawingStroke = false; + // touchend doesn't always have a type + if (shouldUpdate) { + this._strokeUpdate(event); + } this.dispatchEvent(new CustomEvent('endStroke', { detail: event })); } private _handlePointerEvents(): void { - this._drawningStroke = false; + this._drawingStroke = false; - this.canvas.addEventListener('pointerdown', this._handlePointerStart); - this.canvas.addEventListener('pointermove', this._handlePointerMove); + this.canvas.addEventListener('pointerdown', this._handlePointerDown); this.canvas.ownerDocument.addEventListener( 'pointerup', - this._handlePointerEnd, + this._handlePointerUp, ); + window.addEventListener('pointerup', this._handlePointerUp); } private _handleMouseEvents(): void { - this._drawningStroke = false; + this._drawingStroke = false; this.canvas.addEventListener('mousedown', this._handleMouseDown); - this.canvas.addEventListener('mousemove', this._handleMouseMove); this.canvas.ownerDocument.addEventListener('mouseup', this._handleMouseUp); + window.addEventListener('mouseup', this._handleMouseUp); } private _handleTouchEvents(): void { + this._drawingStroke = false; + this.canvas.addEventListener('touchstart', this._handleTouchStart); - this.canvas.addEventListener('touchmove', this._handleTouchMove); this.canvas.addEventListener('touchend', this._handleTouchEnd); + window.addEventListener('touchend', this._handleTouchEnd); } // Called when a new line is started