/* eslint-disable no-console */ /* global TweenLite */ import Events from '../events.js' import { Points } from '../utils.js' export default class CardWrapper extends Object { constructor(domNode, { triggerSVGClicks = true, allowClickDistance = 44 } = {}) { super() this.domNode = domNode this.triggerSVGClicks = triggerSVGClicks this.allowClickDistance = allowClickDistance this.tapNodes = new Map() this.tapHandler = new Map() } handleClicks() { this.domNode.addEventListener( 'click', event => { if (event.isTrusted) { Events.stop(event) if (this.triggerSVGClicks && this.isSVGNode(event.target)) { this.tap(event, 'triggerSVGClicks') } } }, true ) } handleClicksAsTaps() { this.domNode.addEventListener( 'click', event => { if (event.isTrusted) { Events.stop(event) } this.tap(event) }, true ) } isClickPrevented(node) { if (node == null) { return false } if (node.style && node.style.pointerEvents == 'none') { return true } return this.isClickPrevented(node.parentNode) } isClickable(node) { if (node == null) return false // console.log("isClickable", node, this.isClickPrevented(node)) if (this.isClickPrevented(node)) { return false } if (node.tagName == 'A' && node.hasAttribute('href')) return true if (node.hasAttribute('onclick')) return true return false } hasClickHandler(node) { if (node == null) return false if (this.tapNodes.has(node)) return true for (let [selector, handler] of this.tapHandler.entries()) { for (let obj of this.domNode.querySelectorAll(selector)) { if (node == obj) { return true } } } return false } /** * Returns an array of all active nodes. * Unfortunately we cannot search for all nodes with an attached 'click' event listener * See https://stackoverflow.com/questions/11455515/how-to-check-whether-dynamically-attached-event-listener-exists-or-not * Therefore we can only detect the following standard cases: * I. All clickable objects like activeNodes * II. Objects that have been attached a click handler by the scatter itself via */ activeNodes() { let result = [] for (let node of this.domNode.querySelectorAll('*')) { if (this.isClickable(node)) result.push(node) if (this.hasClickHandler(node)) result.push(node) } return result } nearestActive(event) { let element = this.domNode let activeNodes = this.activeNodes() let globalClick = event.center ? event.center : { x: event.x, y: event.y } let localClick = Points.fromPageToNode(element, globalClick) let clickRects = activeNodes.map(link => { let rect = link.getBoundingClientRect() // Since the getBoundingClientRect is untransformed we cannot rely on it's size // We need a transformed bottom right to calculate local width and height let bottomRight = Points.fromPageToNode(element, { x: rect.x + rect.width, y: rect.y + rect.height }) let topLeft = Points.fromPageToNode(element, rect) let width = Math.abs(bottomRight.x - topLeft.x) let height = Math.abs(bottomRight.y - topLeft.y) let center = Points.fromPageToNode(element, { x: rect.x + width / 2, y: rect.y + height / 2 }) return { x: topLeft.x, y: topLeft.y, width, height, center, link } }) let distances = [] clickRects.forEach(rect => { let distance = Points.distanceToRect(localClick, rect) distances.push(parseInt(distance)) }) let closestClickIndex = distances.indexOf(Math.min(...distances)) let closestClickable = activeNodes[closestClickIndex] if (distances[closestClickIndex] < this.allowClickDistance) { return closestClickable } return null } isSVGNode(node) { return node.ownerSVGElement || node.tagName == 'svg' } simulateClick(node, event) { /* https://stackoverflow.com/questions/49564905/is-it-possible-to-use-click-function-on-svg-tags-i-tried-element-click-on-a proposes the dispatchEvent solution. But this leads to problems in flippable.html hiding the back page. Therefore we use the original click event (see constructor). */ if (this.isSVGNode(node)) { if (this.triggerSVGClicks) { let click = new Event('click') node.dispatchEvent(click) } return } node.click() } nodeTapped(node, event) { console.log('nodeTapped', node, this.isClickable(node)) if (this.isClickable(node)) { this.simulateClick(node, event) return true } if (this.tapNodes.has(node)) { let handler = this.tapNodes.get(node) handler(event, node) return true } for (let [selector, handler] of this.tapHandler.entries()) { console.log('nodeTapped', selector) for (let obj of this.domNode.querySelectorAll(selector)) { if (node == obj) { handler(event, node) return true } } } return false } tap(event, calledBy = 'unknown') { if (event.isTrusted) { let node = this.nearestActive(event) console.log('tap', node) this.nodeTapped(node, event) /* let node = document.elementFromPoint(event.clientX, event.clientY) if (!this.nodeTapped(node, event)) { node = this.nearestActive(event) this.nodeTapped(node, event) } */ } } onTap(objOrSelector, handler) { if (typeof objOrSelector == 'string') { this.tapHandler.set(objOrSelector, handler) } else { this.tapNodes.set(objOrSelector, handler) } } }