UNPKG

nervjs

Version:

A react-like framework based on virtual-dom

201 lines (188 loc) 5.07 kB
import { isFunction } from '~' import SimpleMap from '~/simple-map' const delegatedEvents = new SimpleMap() const unbubbleEvents = { onmousemove: 1, ontouchmove: 1, onmouseleave: 1, onmouseenter: 1, onload: 1, onunload: 1, onscroll: 1, onfocus: 1, onblur: 1, onrowexit: 1, onbeforeunload: 1, onstop: 1, ondragdrop: 1, ondragenter: 1, ondragexit: 1, ondraggesture: 1, ondragover: 1, oncontextmenu: 1, onerror: 1, onabort: 1, oncanplay: 1, oncanplaythrough: 1, ondurationchange: 1, onemptied: 1, onended: 1, onloadeddata: 1, onloadedmetadata: 1, onloadstart: 1, onencrypted: 1, onpause: 1, onplay: 1, onplaying: 1, onprogress: 1, onratechange: 1, onseeking: 1, onseeked: 1, onstalled: 1, onsuspend: 1, ontimeupdate: 1, onvolumechange: 1, onwaiting: 1 } class EventHook { type = 'EventHook' constructor (eventName, handler) { this.eventName = getEventName(eventName) this.handler = handler } hook (node, prop, prev) { if (prev && prev.type === 'EventHook' && prev.handler === this.handler && prev.eventName === this.eventName) { return } const eventName = this.eventName let delegatedRoots = delegatedEvents.get(eventName) if (unbubbleEvents[eventName] === 1) { if (!delegatedRoots) { delegatedRoots = new SimpleMap() } const event = attachEventToNode(node, eventName, delegatedRoots) delegatedEvents.set(eventName, delegatedRoots) if (isFunction(this.handler)) { delegatedRoots.set(node, { eventHandler: this.handler, event }) } } else { if (!delegatedRoots) { delegatedRoots = { items: new SimpleMap() } delegatedRoots.event = attachEventToDocument(document, eventName, delegatedRoots) delegatedEvents.set(eventName, delegatedRoots) } if (isFunction(this.handler)) { delegatedRoots.items.set(node, this.handler) } } } unhook (node, prop, next) { if (next && next.type === 'EventHook' && next.handler === this.handler && next.eventName === next.eventName) { return } const eventName = this.eventName let delegatedRoots = delegatedEvents.get(eventName) if (unbubbleEvents[eventName] === 1 && delegatedRoots) { let event = delegatedRoots.get(node) node.removeEventListener(parseEventName(eventName), event.event, false) delegatedRoots.remove(node) if (delegatedRoots.size() === 0) { delegatedEvents.remove(eventName) } } else if (delegatedRoots && delegatedRoots.items) { let items = delegatedRoots.items if (items.remove(node) && items.size() === 0) { document.removeEventListener(parseEventName(eventName), delegatedRoots.event, false) delegatedEvents.remove(eventName) } } } } function getEventName (name) { if (name === 'onDoubleClick') { name = 'ondblclick' } else if (name === 'onTouchTap') { name = 'onclick' } return name.toLowerCase() } function parseEventName (name) { return name.substr(2) } function stopPropagation () { this.cancelBubble = true this.stopImmediatePropagation() } function dispatchEvent (event, target, items, count, eventData) { const eventsToTrigger = items.get(target) if (eventsToTrigger) { count-- eventData.currentTarget = target eventsToTrigger(event) if (event.cancelBubble) { return } } if (count > 0) { const parentDom = target.parentNode if (parentDom === null || (event.type === 'click' && parentDom.nodeType === 1 && parentDom.disabled)) { return } dispatchEvent(event, parentDom, items, count, eventData) } } function attachEventToDocument (doc, eventName, delegatedRoots) { const eventHandler = (event) => { const count = delegatedRoots.items.size() if (count > 0) { const eventData = { currentTarget: event.target } Object.defineProperties(event, { currentTarget: { configurable: true, get () { return eventData.currentTarget } }, stopPropagation: { value: stopPropagation } }) dispatchEvent(event, event.target, delegatedRoots.items, count, eventData) } } doc.addEventListener(parseEventName(eventName), eventHandler, false) return eventHandler } function attachEventToNode (node, eventName, delegatedRoots) { const eventHandler = (event) => { const eventToTrigger = delegatedRoots.get(node) if (eventToTrigger && eventToTrigger.eventHandler) { const eventData = { currentTarget: node } Object.defineProperties(event, { currentTarget: { configurable: true, get () { return eventData.currentTarget } } }) eventToTrigger.eventHandler(event) } } node.addEventListener(parseEventName(eventName), eventHandler, false) return eventHandler } export default EventHook