Added support for DOM cards.

This commit is contained in:
2019-07-12 14:33:15 +02:00
parent d04f92ee7f
commit e1b5c45b52
10 changed files with 941 additions and 386 deletions
+198 -127
View File
@@ -4003,6 +4003,7 @@
}
class DOMScatter extends AbstractScatter {
constructor(
element,
@@ -4028,7 +4029,7 @@
width = null, // required
height = null, // required
resizable = false,
clickOnTap = false,
tapDelegate = null,
triggerSVGClicks = false,
allowClickDistance = 44,
verbose = true,
@@ -4085,8 +4086,7 @@
this.height = height;
this.throwVisibility = Math.min(width, height, throwVisibility);
this.container = container;
this.clickOnTap = clickOnTap;
this.triggerSVGClicks = triggerSVGClicks;
this.tapDelegate = tapDelegate;
this.scale = startScale;
this.rotationDegrees = this.startRotationDegrees;
this.transformOrigin = transformOrigin;
@@ -4100,8 +4100,7 @@
transformOrigin: transformOrigin
};
this.tapNodes = new Map();
this.allowClickDistance = allowClickDistance;
// For tweenlite we need initial values in _gsTransform
TweenLite.set(element, this.initialValues);
this.onResize = onResize;
@@ -4133,24 +4132,8 @@
});
this.resizeButton = button;
}
if (clickOnTap) {
/* Since the tap triggers a synthetic click event
we must prevent the original trusted click event which
is also dispatched by the system.
*/
element.addEventListener('click', event => {
/* Currently we cannot send synthesized click events to SVG elements without unwanted side effects.
Therefore we make an exception and let the original click event through.
*/
if (event.target.ownerSVGElement) {
if (this.triggerSVGClicks) {
return
}
}
else if (event.isTrusted) {
Events.stop(event);
}
}, true);
if (tapDelegate) {
tapDelegate.handleClicks();
}
container.add(this);
}
@@ -4312,110 +4295,12 @@
}
onTap(event, interaction, point) {
if (this.clickOnTap) {
let directNode = document.elementFromPoint(event.clientX, event.clientY);
console.log("onTap", event);
if (this.isClickable(directNode)) {
directNode.click();
}
else {
let nearestNode = this.nearestClickable(event);
if (this.isClickable(nearestNode)) {
/* 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 (nearestNode.tagName == 'svg') {
let handler = this.tapNodes.get(nearestNode);
console.log("Clicking near SVG: to be done", handler);
if (this.triggerSVGClicks)
nearestNode.dispatchEvent(new Event('click'));
return
}
console.log("nearestNode clicked");
nearestNode.click();
}
}
if (this.tapDelegate) {
Events.stop(event);
this.tapDelegate.tap(event, "scatter");
}
}
/**
* Adds a click or tap behavior to the node. Uses
* either the scatter clickOnTap version which requires click handlers
* or uses the hammer.js driven tap handler.
*
* @param {*} node
* @param {*} handler
* @memberof DOMScatter
*/
addTapListener(node, handler) {
if (this.clickOnTap) {
node.addEventListener('click', handler);
this.tapNodes.set(node, handler);
}
else {
InteractionMapper$1.on('tap', node, handler);
}
}
isClickable(node) {
if (node == null)
return false
if (node.tagName == 'A')
return true
if (node.hasAttribute("onclick"))
return true
if (this.tapNodes.has(node))
return true
return false
}
/**
* Returns an array of all clickable 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 clickables
* II. Objects that have been attached a click handler by the scatter itself via
*/
clickableNodes() {
let result = [];
for (let node of this.element.querySelectorAll("*")) {
if (this.isClickable(node))
result.push(node);
}
return result
}
nearestClickable(event) {
let element = this.element;
let clickables = this.clickableNodes();
let globalClick = (event.center) ? event.center : { x: event.x, y: event.y };
let localClick = Points.fromPageToNode(element, globalClick);
let clickRects = clickables.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 = clickables[closestClickIndex];
if (distances[closestClickIndex] < this.allowClickDistance) {
return closestClickable
}
return null
}
isDescendant(parent, child) {
let node = child.parentNode;
while (node != null) {
@@ -4735,7 +4620,7 @@
translatable = true,
scalable = true,
rotatable = true,
clickOnTap = false,
tapDelegateFactory = null,
onFront = null,
onBack = null,
onClose = null,
@@ -4755,7 +4640,7 @@
this.translatable = translatable;
this.scalable = scalable;
this.rotatable = rotatable;
this.clickOnTap = clickOnTap;
this.tapDelegateFactory = tapDelegateFactory;
this.onFrontFlipped = onFront;
this.onBackFlipped = onBack;
this.onClose = onClose;
@@ -4811,7 +4696,7 @@
scalable: this.scalable,
rotatable: this.rotatable,
overdoScaling: this.overdoScaling,
clickOnTap: this.clickOnTap
tapDelegate: (this.tapDelegateFactory) ? this.tapDelegateFactory(this.cardWrapper) : null
}
);
@@ -7634,6 +7519,190 @@
}
}
/* 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 => {
console.log('handleClicks', event.isTrusted);
/* Currently we cannot send synthesized click events to SVG elements without unwanted side effects.
Therefore we make an exception and let the original click event through.
*/
if (event.isTrusted) {
Events.stop(event);
if (this.triggerSVGClicks && this.isSVGNode(event.target)) {
this.tap(event, "triggerSVGClicks");
}
}
}, true);
}
handleClicksAsTaps() {
this.domNode.addEventListener('click', event => {
console.log('handleClicksAsTaps', event.isTrusted);
/* Currently we cannot send synthesized click events to SVG elements without unwanted side effects.
Therefore we make an exception and let the original click event through.
*/
if (event.isTrusted) {
Events.stop(event);
}
this.tap(event);
}, true);
}
isClickable(node) {
if (node == null)
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()) {
console.log("selector", this.domNode.querySelectorAll(selector));
for (let obj of this.domNode.querySelectorAll(selector)) {
console.log("selector2", node, obj);
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). */
console.log("simulateClick", node, node.ownerSVGElement);
if (this.isSVGNode(node)) {
if (this.triggerSVGClicks) {
let click = new Event('click');
node.dispatchEvent(click);
}
return
}
node.click();
}
nodeTapped(node, event) {
if (this.isClickable(node)) {
this.simulateClick(node, event);
return true
}
if (this.tapNodes.has(node)) {
handler = this.tapNodes.get(node);
handler(event);
return true
}
for (let [selector, handler] of this.tapHandler.entries()) {
for (let obj of this.domNode.querySelectorAll(selector)) {
if (node == obj) {
handler(event);
return true
}
}
}
return false
}
tap(event, calledBy='unknown') {
console.log("tap", calledBy, event.alreadyTapped, event);
if (event.isTrusted) {
let node = this.nearestActive(event);
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);
}
}
}
window.CardWrapper = CardWrapper;
/* Needed to ensure that rollup.js includes class definitions and the classes
are visible inside doctests.
*/
@@ -7697,4 +7766,6 @@
window.randomInt = randomInt;
window.randomFloat = randomFloat;
window.CardWrapper = CardWrapper;
}());
+12 -127
View File
@@ -7003,6 +7003,7 @@
}
class DOMScatter extends AbstractScatter {
constructor(
element,
@@ -7028,7 +7029,7 @@
width = null, // required
height = null, // required
resizable = false,
clickOnTap = false,
tapDelegate = null,
triggerSVGClicks = false,
allowClickDistance = 44,
verbose = true,
@@ -7085,8 +7086,7 @@
this.height = height;
this.throwVisibility = Math.min(width, height, throwVisibility);
this.container = container;
this.clickOnTap = clickOnTap;
this.triggerSVGClicks = triggerSVGClicks;
this.tapDelegate = tapDelegate;
this.scale = startScale;
this.rotationDegrees = this.startRotationDegrees;
this.transformOrigin = transformOrigin;
@@ -7100,8 +7100,7 @@
transformOrigin: transformOrigin
};
this.tapNodes = new Map();
this.allowClickDistance = allowClickDistance;
// For tweenlite we need initial values in _gsTransform
TweenLite.set(element, this.initialValues);
this.onResize = onResize;
@@ -7133,24 +7132,8 @@
});
this.resizeButton = button;
}
if (clickOnTap) {
/* Since the tap triggers a synthetic click event
we must prevent the original trusted click event which
is also dispatched by the system.
*/
element.addEventListener('click', event => {
/* Currently we cannot send synthesized click events to SVG elements without unwanted side effects.
Therefore we make an exception and let the original click event through.
*/
if (event.target.ownerSVGElement) {
if (this.triggerSVGClicks) {
return
}
}
else if (event.isTrusted) {
Events$1.stop(event);
}
}, true);
if (tapDelegate) {
tapDelegate.handleClicks();
}
container.add(this);
}
@@ -7312,110 +7295,12 @@
}
onTap(event, interaction, point) {
if (this.clickOnTap) {
let directNode = document.elementFromPoint(event.clientX, event.clientY);
console.log("onTap", event);
if (this.isClickable(directNode)) {
directNode.click();
}
else {
let nearestNode = this.nearestClickable(event);
if (this.isClickable(nearestNode)) {
/* 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 (nearestNode.tagName == 'svg') {
let handler = this.tapNodes.get(nearestNode);
console.log("Clicking near SVG: to be done", handler);
if (this.triggerSVGClicks)
nearestNode.dispatchEvent(new Event('click'));
return
}
console.log("nearestNode clicked");
nearestNode.click();
}
}
if (this.tapDelegate) {
Events$1.stop(event);
this.tapDelegate.tap(event, "scatter");
}
}
/**
* Adds a click or tap behavior to the node. Uses
* either the scatter clickOnTap version which requires click handlers
* or uses the hammer.js driven tap handler.
*
* @param {*} node
* @param {*} handler
* @memberof DOMScatter
*/
addTapListener(node, handler) {
if (this.clickOnTap) {
node.addEventListener('click', handler);
this.tapNodes.set(node, handler);
}
else {
InteractionMapper$1.on('tap', node, handler);
}
}
isClickable(node) {
if (node == null)
return false
if (node.tagName == 'A')
return true
if (node.hasAttribute("onclick"))
return true
if (this.tapNodes.has(node))
return true
return false
}
/**
* Returns an array of all clickable 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 clickables
* II. Objects that have been attached a click handler by the scatter itself via
*/
clickableNodes() {
let result = [];
for (let node of this.element.querySelectorAll("*")) {
if (this.isClickable(node))
result.push(node);
}
return result
}
nearestClickable(event) {
let element = this.element;
let clickables = this.clickableNodes();
let globalClick = (event.center) ? event.center : { x: event.x, y: event.y };
let localClick = Points.fromPageToNode(element, globalClick);
let clickRects = clickables.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 = clickables[closestClickIndex];
if (distances[closestClickIndex] < this.allowClickDistance) {
return closestClickable
}
return null
}
isDescendant(parent, child) {
let node = child.parentNode;
while (node != null) {
@@ -7601,7 +7486,7 @@
translatable = true,
scalable = true,
rotatable = true,
clickOnTap = false,
tapDelegateFactory = null,
onFront = null,
onBack = null,
onClose = null,
@@ -7621,7 +7506,7 @@
this.translatable = translatable;
this.scalable = scalable;
this.rotatable = rotatable;
this.clickOnTap = clickOnTap;
this.tapDelegateFactory = tapDelegateFactory;
this.onFrontFlipped = onFront;
this.onBackFlipped = onBack;
this.onClose = onClose;
@@ -7677,7 +7562,7 @@
scalable: this.scalable,
rotatable: this.rotatable,
overdoScaling: this.overdoScaling,
clickOnTap: this.clickOnTap
tapDelegate: (this.tapDelegateFactory) ? this.tapDelegateFactory(this.cardWrapper) : null
}
);