diff --git a/dist/iwmlib.js b/dist/iwmlib.js index dee8568..cf4786b 100644 --- a/dist/iwmlib.js +++ b/dist/iwmlib.js @@ -3789,12 +3789,13 @@ } onEnd(event, interaction) { - //console.log("Scatter.onEnd", this.dragging) + console.log("Scatter.onEnd", this.dragging); if (interaction.isFinished()) { this.endGesture(interaction); this.dragging = false; for (let key of interaction.ended.keys()) { if (interaction.isTap(key)) { + console.log("Scatter.isTap"); let point = interaction.ended.get(key); this.onTap(event, interaction, point); } @@ -3819,7 +3820,16 @@ } } - onTap(event, interaction, point) {} + //onTap(event, interaction, point) {} + + onTap(event, interaction, point) { + console.log("AbstractScatter.onTap", this.tapDelegate, interaction); + if (this.tapDelegate) { + Events.stop(event); + this.tapDelegate.tap(event, 'scatter'); + } + } + onDragUpdate(delta) { if (this.onTransform != null) { @@ -4366,13 +4376,6 @@ TweenLite.set(this.element, { zIndex: DOMScatter$1.zIndex++ }); } - onTap(event, interaction, point) { - if (this.tapDelegate) { - Events.stop(event); - this.tapDelegate.tap(event, 'scatter'); - } - } - isDescendant(parent, child) { let node = child.parentNode; while (node != null) { @@ -4513,6 +4516,198 @@ DOMScatter$1.zIndex = 1000; + /* eslint-disable no-console */ + + 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$1.fromPageToNode(element, globalClick); + + let clickRects = activeNodes.map(link => { + let rect = link.getBoundingClientRect(); + let topLeft = Points$1.fromPageToNode(element, rect); + let center = Points$1.fromPageToNode(element, { + x: rect.x + rect.width / 2, + y: rect.y + rect.height / 2 + }); + return { + x: topLeft.x, + y: topLeft.y, + width: rect.width, + height: rect.height, + center, + link + } + }); + + let distances = []; + clickRects.forEach(rect => { + let distance = Points$1.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); + } + } + } + /* eslint-disable no-unused-vars */ class CardLoader { @@ -4915,20 +5110,23 @@ this.backBtn = element.querySelector('.backBtn'); this.closeBtn = element.querySelector('.closeBtn'); /* Buttons are not guaranteed to exist. */ - + if (scatter.tapDelegate == null) { + let tapDelegate = new CardWrapper(element); + scatter.tapDelegate = tapDelegate; + } if (this.infoBtn) { - scatter.addTapListener(this.infoBtn, event => { + scatter.tapDelegate.onTap(this.infoBtn, event => { this.flip.start(); }); this.enable(this.infoBtn); } if (this.backBtn) { - scatter.addTapListener(this.backBtn, event => { + scatter.tapDelegate.onTap(this.backBtn, event => { this.start(); }); } if (this.closeBtn) { - scatter.addTapListener(this.closeBtn, event => { + scatter.tapDelegate.onTap(this.closeBtn, event => { this.close(); }); this.enable(this.closeBtn); @@ -10088,198 +10286,6 @@ zoomable: 0.5 }; - /* eslint-disable no-console */ - - 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$1.fromPageToNode(element, globalClick); - - let clickRects = activeNodes.map(link => { - let rect = link.getBoundingClientRect(); - let topLeft = Points$1.fromPageToNode(element, rect); - let center = Points$1.fromPageToNode(element, { - x: rect.x + rect.width / 2, - y: rect.y + rect.height / 2 - }); - return { - x: topLeft.x, - y: topLeft.y, - width: rect.width, - height: rect.height, - center, - link - } - }); - - let distances = []; - clickRects.forEach(rect => { - let distance = Points$1.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)) { - 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); - } - } - } - /** * Extends the card with scatter functionality. * @@ -10972,6 +10978,21 @@ } speak() { + /** + * This is a little bit ugly, but imho the most elegant of all dirty solutions. + * + 5ht * Within the plugins we have no knowledge of other cards and such. But must differentiate the + * clicks by their corresponding owner. The SpeechUtterance just takes a text and has no knowledge + * about the node that is currently read to the user. + * + * This means, that we can identify same text, but not differentiate same text on different nodes. + * To account for that, we add the node to the speechSynthesis object (#benefitsOfJavaScript) and + * have access to the node, by - let's say - expanding the functionality of the SpeechSynthesis object. + * + * SO -17.07.19 + */ + + let activeNode = window.speechSynthesis['speechPluginNode']; this._updateText(); } diff --git a/dist/iwmlib.pixi.js b/dist/iwmlib.pixi.js index 492bdc5..f8f00aa 100644 --- a/dist/iwmlib.pixi.js +++ b/dist/iwmlib.pixi.js @@ -7409,12 +7409,13 @@ } onEnd(event, interaction) { - //console.log("Scatter.onEnd", this.dragging) + console.log("Scatter.onEnd", this.dragging); if (interaction.isFinished()) { this.endGesture(interaction); this.dragging = false; for (let key of interaction.ended.keys()) { if (interaction.isTap(key)) { + console.log("Scatter.isTap"); let point = interaction.ended.get(key); this.onTap(event, interaction, point); } @@ -7439,7 +7440,16 @@ } } - onTap(event, interaction, point) {} + //onTap(event, interaction, point) {} + + onTap(event, interaction, point) { + console.log("AbstractScatter.onTap", this.tapDelegate, interaction); + if (this.tapDelegate) { + Events$1.stop(event); + this.tapDelegate.tap(event, 'scatter'); + } + } + onDragUpdate(delta) { if (this.onTransform != null) { @@ -7806,13 +7816,6 @@ TweenLite.set(this.element, { zIndex: DOMScatter.zIndex++ }); } - onTap(event, interaction, point) { - if (this.tapDelegate) { - Events$1.stop(event); - this.tapDelegate.tap(event, 'scatter'); - } - } - isDescendant(parent, child) { let node = child.parentNode; while (node != null) { @@ -7953,6 +7956,198 @@ DOMScatter.zIndex = 1000; + /* eslint-disable no-console */ + + 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$1.stop(event); + if (this.triggerSVGClicks && this.isSVGNode(event.target)) { + this.tap(event, 'triggerSVGClicks'); + } + } + }, + true + ); + } + + handleClicksAsTaps() { + this.domNode.addEventListener( + 'click', + event => { + if (event.isTrusted) { + Events$1.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(); + let topLeft = Points.fromPageToNode(element, rect); + let center = Points.fromPageToNode(element, { + x: rect.x + rect.width / 2, + y: rect.y + rect.height / 2 + }); + return { + x: topLeft.x, + y: topLeft.y, + width: rect.width, + height: rect.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); + } + } + } + /* eslint-disable no-unused-vars */ class CardLoader { @@ -8213,20 +8408,23 @@ this.backBtn = element.querySelector('.backBtn'); this.closeBtn = element.querySelector('.closeBtn'); /* Buttons are not guaranteed to exist. */ - + if (scatter.tapDelegate == null) { + let tapDelegate = new CardWrapper(element); + scatter.tapDelegate = tapDelegate; + } if (this.infoBtn) { - scatter.addTapListener(this.infoBtn, event => { + scatter.tapDelegate.onTap(this.infoBtn, event => { this.flip.start(); }); this.enable(this.infoBtn); } if (this.backBtn) { - scatter.addTapListener(this.backBtn, event => { + scatter.tapDelegate.onTap(this.backBtn, event => { this.start(); }); } if (this.closeBtn) { - scatter.addTapListener(this.closeBtn, event => { + scatter.tapDelegate.onTap(this.closeBtn, event => { this.close(); }); this.enable(this.closeBtn); diff --git a/lib/card/wrapper.js b/lib/card/wrapper.js index e40fd71..4adadcf 100644 --- a/lib/card/wrapper.js +++ b/lib/card/wrapper.js @@ -155,7 +155,7 @@ export default class CardWrapper extends Object { return true } if (this.tapNodes.has(node)) { - handler = this.tapNodes.get(node) + let handler = this.tapNodes.get(node) handler(event, node) return true } diff --git a/lib/flippable.js b/lib/flippable.js index 20f6b36..fd03c16 100644 --- a/lib/flippable.js +++ b/lib/flippable.js @@ -2,6 +2,7 @@ /* global PDFJS Power1 */ import { getId } from './utils.js' import { DOMScatter } from './scatter.js' +import CardWrapper from './card/wrapper.js' export class CardLoader { constructor( @@ -403,20 +404,23 @@ export class DOMFlippable { this.backBtn = element.querySelector('.backBtn') this.closeBtn = element.querySelector('.closeBtn') /* Buttons are not guaranteed to exist. */ - + if (scatter.tapDelegate == null) { + let tapDelegate = new CardWrapper(element) + scatter.tapDelegate = tapDelegate + } if (this.infoBtn) { - scatter.addTapListener(this.infoBtn, event => { + scatter.tapDelegate.onTap(this.infoBtn, event => { this.flip.start() }) this.enable(this.infoBtn) } if (this.backBtn) { - scatter.addTapListener(this.backBtn, event => { + scatter.tapDelegate.onTap(this.backBtn, event => { this.start() }) } if (this.closeBtn) { - scatter.addTapListener(this.closeBtn, event => { + scatter.tapDelegate.onTap(this.closeBtn, event => { this.close() }) this.enable(this.closeBtn) diff --git a/lib/scatter.js b/lib/scatter.js index 671c877..f6c01c2 100644 --- a/lib/scatter.js +++ b/lib/scatter.js @@ -774,12 +774,13 @@ export class AbstractScatter extends Throwable { } onEnd(event, interaction) { - //console.log("Scatter.onEnd", this.dragging) + console.log("Scatter.onEnd", this.dragging) if (interaction.isFinished()) { this.endGesture(interaction) this.dragging = false for (let key of interaction.ended.keys()) { if (interaction.isTap(key)) { + console.log("Scatter.isTap") let point = interaction.ended.get(key) this.onTap(event, interaction, point) } @@ -804,7 +805,16 @@ export class AbstractScatter extends Throwable { } } - onTap(event, interaction, point) {} + //onTap(event, interaction, point) {} + + onTap(event, interaction, point) { + console.log("AbstractScatter.onTap", this.tapDelegate, interaction) + if (this.tapDelegate) { + Events.stop(event) + this.tapDelegate.tap(event, 'scatter') + } + } + onDragUpdate(delta) { if (this.onTransform != null) { @@ -1351,13 +1361,6 @@ export class DOMScatter extends AbstractScatter { TweenLite.set(this.element, { zIndex: DOMScatter.zIndex++ }) } - onTap(event, interaction, point) { - if (this.tapDelegate) { - Events.stop(event) - this.tapDelegate.tap(event, 'scatter') - } - } - isDescendant(parent, child) { let node = child.parentNode while (node != null) {