import { EventBaseObject } from './EventBaseObject'; const commonProps = [ 'altKey', 'bubbles', 'cancelable', 'ctrlKey', 'currentTarget', 'eventPhase', 'metaKey', 'shiftKey', 'target', 'timeStamp', 'view', 'type', ]; function isNullOrUndefined(w) { return w === null || w === undefined; } type eventNormalizerType = { reg: RegExp, props: string[], fix?: (event, nativeEvent) => void } const eventNormalizers: eventNormalizerType[] = [ { reg: /^key/, props: ['char', 'charCode', 'key', 'keyCode', 'which'], fix(event, nativeEvent) { if (isNullOrUndefined(event.which)) { event.which = !isNullOrUndefined(nativeEvent.charCode) ? nativeEvent.charCode : nativeEvent.keyCode; } // add metaKey to non-Mac browsers (use ctrl for PC 's and Meta for Macs) if (event.metaKey === undefined) { event.metaKey = event.ctrlKey; } }, }, { reg: /^touch/, props: ['touches', 'changedTouches', 'targetTouches'], }, { reg: /^hashchange$/, props: ['newURL', 'oldURL'], }, { reg: /^gesturechange$/i, props: ['rotation', 'scale'], }, { reg: /^(mousewheel|DOMMouseScroll)$/, props: [], fix(event, nativeEvent) { let deltaX; let deltaY; let delta; const wheelDelta = nativeEvent.wheelDelta; const axis = nativeEvent.axis; const wheelDeltaY = nativeEvent.wheelDeltaY; const wheelDeltaX = nativeEvent.wheelDeltaX; const detail = nativeEvent.detail; // ie/webkit if (wheelDelta) { delta = wheelDelta / 120; } // gecko if (detail) { // press control e.detail == 1 else e.detail == 3 delta = 0 - (detail % 3 === 0 ? detail / 3 : detail); } // Gecko if (axis !== undefined) { if (axis === event.HORIZONTAL_AXIS) { deltaY = 0; deltaX = 0 - delta; } else if (axis === event.VERTICAL_AXIS) { deltaX = 0; deltaY = delta; } } // Webkit if (wheelDeltaY !== undefined) { deltaY = wheelDeltaY / 120; } if (wheelDeltaX !== undefined) { deltaX = -1 * wheelDeltaX / 120; } // deltaY (ie) if (!deltaX && !deltaY) { deltaY = delta; } if (deltaX !== undefined) { /** * deltaX of mousewheel event * @property deltaX * @member Event.DomEvent.Object */ event.deltaX = deltaX; } if (deltaY !== undefined) { /** * deltaY of mousewheel event * @property deltaY * @member Event.DomEvent.Object */ event.deltaY = deltaY; } if (delta !== undefined) { /** * delta of mousewheel event * @property delta * @member Event.DomEvent.Object */ event.delta = delta; } }, }, { reg: /^mouse|contextmenu|click|mspointer|(^DOMMouseScroll$)/i, props: [ 'buttons', 'clientX', 'clientY', 'button', 'offsetX', 'relatedTarget', 'which', 'fromElement', 'toElement', 'offsetY', 'pageX', 'pageY', 'screenX', 'screenY', ], fix(event, nativeEvent) { let eventDoc; let doc; let body; const target = event.target; const button = nativeEvent.button; // Calculate pageX/Y if missing and clientX/Y available if (target && isNullOrUndefined(event.pageX) && !isNullOrUndefined(nativeEvent.clientX)) { eventDoc = target.ownerDocument || document; doc = eventDoc.documentElement; body = eventDoc.body; event.pageX = nativeEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); event.pageY = nativeEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); } // which for click: 1 === left; 2 === middle; 3 === right // do not use button if (!event.which && button !== undefined) { if (button & 1) { event.which = 1; } else if (button & 2) { event.which = 3; } else if (button & 4) { event.which = 2; } else { event.which = 0; } } // add relatedTarget, if necessary if (!event.relatedTarget && event.fromElement) { event.relatedTarget = (event.fromElement === target) ? event.toElement : event.fromElement; } return event; }, }, ]; export class DomEventObject extends EventBaseObject { private nativeEvent: Event; constructor(nativeEvent: Event) { super(); const type = nativeEvent.type; const isNative = (typeof nativeEvent.stopPropagation === 'function') || (typeof nativeEvent.cancelBubble === 'boolean'); this.nativeEvent = nativeEvent; // in case dom event has been mark as default prevented by lower dom node let isDefaultPrevented = false; if ('defaultPrevented' in nativeEvent) { isDefaultPrevented = nativeEvent.defaultPrevented ? true : false; } else if ('getPreventDefault' in nativeEvent) { // https://bugzilla.mozilla.org/show_bug.cgi?id=691151 isDefaultPrevented = nativeEvent['getPreventDefault']() ? true : false; } else if ('returnValue' in nativeEvent) { isDefaultPrevented = nativeEvent['returnValue'] === false ? true : false; } this._isDefaultPrevented = isDefaultPrevented; const fixFns = []; let fixFn; let l; let prop; let props = commonProps.concat(); eventNormalizers.forEach((normalizer) => { if (type.match(normalizer.reg)) { props = props.concat(normalizer.props); if (normalizer.fix) { fixFns.push(normalizer.fix); } } }); l = props.length; // clone properties of the original event object while (l) { prop = props[--l]; this[prop] = nativeEvent[prop]; } // fix target property, if necessary if (!this.target && isNative) { this.target = nativeEvent.srcElement || document; // srcElement might not be defined either } // check if target is a text node (safari) if (this.target && this.target.nodeType === 3) { this.target = this.target.parentNode; } l = fixFns.length; while (l) { fixFn = fixFns[--l]; fixFn(this, nativeEvent); } this.timeStamp = nativeEvent.timeStamp || Date.now(); } preventDefault() { const e = this.nativeEvent; // if preventDefault exists run it on the original event if (e.preventDefault) { e.preventDefault(); } else { // otherwise set the returnValue property of the original event to FALSE (IE) e.returnValue = false; } super.preventDefault(); } stopPropagation() { const e = this.nativeEvent; // if stopPropagation exists run it on the original event if (e.stopPropagation) { e.stopPropagation(); } else { // otherwise set the cancelBubble property of the original event to TRUE (IE) e.cancelBubble = true; } super.stopPropagation(); } }