From e1b5c45b520cc4437b832e060e3bd624f154a75f Mon Sep 17 00:00:00 2001
From: Uwe Oestermeier
Date: Fri, 12 Jul 2019 14:33:15 +0200
Subject: [PATCH] Added support for DOM cards.
---
dist/iwmlib.js | 325 ++++++++++++++++++++--------------
dist/iwmlib.pixi.js | 139 ++-------------
lib/bundle.js | 3 +
lib/card/highlight.js | 400 ++++++++++++++++++++++++++++++++++++++++++
lib/card/index.html | 107 +++++++++++
lib/card/wrapper.js | 187 ++++++++++++++++++++
lib/flippable.html | 3 +-
lib/flippable.js | 6 +-
lib/scatter.html | 9 +-
lib/scatter.js | 148 +++-------------
10 files changed, 941 insertions(+), 386 deletions(-)
create mode 100644 lib/card/highlight.js
create mode 100644 lib/card/index.html
create mode 100644 lib/card/wrapper.js
diff --git a/dist/iwmlib.js b/dist/iwmlib.js
index 847c842..71bf8fa 100644
--- a/dist/iwmlib.js
+++ b/dist/iwmlib.js
@@ -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;
+
}());
diff --git a/dist/iwmlib.pixi.js b/dist/iwmlib.pixi.js
index 30375bd..ec6b2ba 100644
--- a/dist/iwmlib.pixi.js
+++ b/dist/iwmlib.pixi.js
@@ -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
}
);
diff --git a/lib/bundle.js b/lib/bundle.js
index da94acf..dbbdc35 100755
--- a/lib/bundle.js
+++ b/lib/bundle.js
@@ -19,6 +19,7 @@ import {ResizeEvent, DOMScatterContainer, AbstractScatter, DOMScatter, ScatterEv
import {Cycle, Colors, Elements, Angle, Dates, Points, Polygon, Rect, Sets, Strings, isEmpty, getId, lerp, debounce, randomInt, randomFloat, LowPassFilter} from './utils.js'
import UITest from './uitest.js'
+import {CardWrapper} from './card/wrapper.js'
/* Needed to ensure that rollup.js includes class definitions and the classes
are visible inside doctests.
*/
@@ -81,3 +82,5 @@ window.lerp = lerp
window.debounce = debounce
window.randomInt = randomInt
window.randomFloat = randomFloat
+
+window.CardWrapper = CardWrapper
\ No newline at end of file
diff --git a/lib/card/highlight.js b/lib/card/highlight.js
new file mode 100644
index 0000000..27f8c8f
--- /dev/null
+++ b/lib/card/highlight.js
@@ -0,0 +1,400 @@
+/* eslint-disable no-console */
+/* global TweenLite */
+
+let _HighlightEnabled = true
+let _CircleIds = 0
+
+
+/** Helper method to round values with one digit precision */
+function round(value) {
+ return Math.round(parseFloat(value) * 10) / 10
+}
+
+
+/**
+ * A namespace with static functions to expand and shrink highlighted image regions.
+ * Assumes an SVG image with the following structure:
+ *
+ *
+ *
+ * The SVG root element should use a viewbox with 0 0 100 100 to ensure that the positions and size of the
+ * circles can be represnted in percent.
+ *
+ * @class Highlight
+ * @extends {Object}
+ */
+class Highlight extends Object {
+
+ static disableAnimations() {
+ _HighlightEnabled = false
+ let expanded = document.querySelectorAll('.expanded')
+ for (let obj of expanded) {
+ this.shrink(obj)
+ }
+ }
+
+ static enableAnimations() {
+ _HighlightEnabled = true
+ }
+
+ static removeAnimations(svgRoot) {
+ let expanded = svgRoot.querySelectorAll('.expanded')
+ for (let obj of expanded) {
+ TweenLite.set(obj, { scale: 1 })
+ obj.classList.remove('zooming')
+ obj.classList.remove('expanded')
+ }
+ let defs = svgRoot.querySelector('defs')
+ while (defs.firstChild) {
+ defs.firstChild.remove()
+ }
+ let maskImages = svgRoot.querySelectorAll('.addedImage')
+ for (let m of maskImages) {
+ m.remove()
+ }
+ let circles = svgRoot.querySelectorAll('circle')
+ for (let circle of circles) {
+ if (circle.classList.length == 0) {
+ circle.removeAttribute('class')
+ }
+ if (circle.hasAttribute('id') && circle.getAttribute('id').startsWith('@@')) {
+ circle.removeAttribute('id')
+ }
+ circle.removeAttribute('data-svg-origin')
+ circle.removeAttribute('transform')
+ circle.removeAttribute('style')
+ let cx = circle.getAttribute('cx')
+ let cy = circle.getAttribute('cy')
+ let r = circle.getAttribute('r')
+ circle.setAttribute('cx', round(cx))
+ circle.setAttribute('cy', round(cy))
+ circle.setAttribute('r', round(r))
+ }
+ }
+
+ static expand(obj, { scale = 2, duration = 3, stroke = 2, onComplete = null } = {}) {
+ if (obj == null)
+ return
+ //console.log("expand")
+ obj.classList.add('zooming')
+ TweenLite.to(obj, duration, {
+ scale: scale,
+ onUpdate: () => {
+ let scale = obj._gsTransform.scaleX
+ obj.setAttribute('stroke-width', stroke / scale)
+ },
+ onComplete: () => {
+ console.log('expand complete')
+ obj.classList.remove('zooming')
+ obj.classList.add('expanded')
+ obj.setAttribute('stroke-width', stroke / scale)
+ if (onComplete) onComplete()
+ }
+ })
+ }
+
+ static shrink(obj, { duration = 0.5, stroke = 2 } = {}) {
+ //console.log("shrink")
+ if (obj == null)
+ return
+ obj.classList.add('zooming')
+ TweenLite.to(obj, duration, {
+ scale: 1,
+ onUpdate: () => {
+ let scale = obj._gsTransform.scaleX
+ obj.setAttribute('stroke-width', stroke / scale)
+ },
+ onComplete: () => {
+ //console.log("shrink complete")
+ obj.classList.remove('zooming')
+ obj.classList.remove('expanded')
+ obj.setAttribute('stroke-width', stroke)
+ }
+ })
+ }
+
+ static animateCircle(target, callback) {
+ console.log('ANIMATE CIRCLE', this)
+ // ** DEBUG OUTPUTS **
+
+ let circle = target
+ // We need a unique id to ensure correspondence between circle, mask, and maskImage
+ if (!circle.hasAttribute('id')) {
+ _CircleIds += 1
+ circle.setAttribute('id', '@@' + _CircleIds)
+ }
+ let id = circle.getAttribute('id')
+ TweenLite.set(circle, { transformOrigin: '50% 50%' })
+ /*if (circle.classList.contains('zooming')) {
+ console.log("already zooming")
+ return
+ }*/
+
+ let svgRoot = circle.closest('svg')
+ let circleGroup = circle.parentNode
+ let image = svgRoot.querySelector('image')
+
+
+ let stroke = parseFloat(circleGroup.getAttribute('stroke-width') || 6)
+
+ let defs = svgRoot.querySelector('defs')
+ if (defs == null) {
+ defs = document.createElementNS(svgRoot, 'defs')
+ svgRoot.insertBefore(defs, image)
+ }
+
+ // // We need direct children, therefore we cannot use querySelectorAll
+ let maskImageId = 'maskImage' + id
+ let maskImage = svgRoot.getElementById(maskImageId)
+
+ if (circle.classList.contains('expanded')) {
+ if (!circle.classList.contains('zooming')) {
+ this.shrink(circle, { stroke })
+ this.shrink(maskImage, { stroke })
+ return
+ }
+ //console.log("animate called while zooming out -> expand")
+ }
+ else if (circle.classList.contains('zooming')) {
+ //console.log("animate called while zooming in -> shrink")
+ this.shrink(circle, { stroke })
+ this.shrink(maskImage, { stroke })
+ return
+ }
+ let circles = Array.from(circleGroup.children).filter(e => e.tagName == 'circle')
+ for (let c of circles) {
+ //console.log("shrinking all circles")
+ this.shrink(c, { stroke })
+ }
+ let maskImages = circleGroup.querySelectorAll('.addedImage')
+ for (let m of maskImages) {
+ this.shrink(m, { stroke })
+ }
+
+ Highlight._createSVGMask(svgRoot, image, id)
+
+ // TweenLite.set(maskImage, { transformOrigin: `${tx}% ${ty}%` })
+
+ this.expand(circle, { stroke, onComplete: callback })
+ this.expand(maskImage)
+
+ return false
+ }
+
+ static openHighlight(target, {
+ animation = 0.5,
+ scale = 2,
+ onExpanded = null
+ } = {}) {
+
+ console.log('Open Highlight!')
+
+ if (Highlight._isExpanded(target)) {
+ console.log('Target is already expanded!')
+ return
+ } else {
+
+ let targetId = target.getAttribute('id')
+ console.log(targetId)
+ if(targetId && targetId.startsWith('@@')){
+ let id = targetId.slice(2)
+ const imageId = '#maskImage'+id
+ const parent = target.parentNode
+ if(parent != null){
+ let image = parent.querySelector(imageId)
+ if(image){
+ this._bringToFront(image)
+ }else console.error('Could not find corresponding image element.')
+ }else console.log('Element was no parent:', target)
+ }
+
+ this._bringToFront(target)
+
+ let svgRoot = target.closest('svg')
+ let image = svgRoot.querySelector('image')
+
+ // eslint-disable-next-line no-unused-vars
+ let [mask, maskImage] = Highlight._getSVGMask(target, { svgRoot, image })
+ let center = Highlight._calculateCenterRelativeTo(target, image)
+
+ TweenLite.set(maskImage, { transformOrigin: `${center.x}% ${center.y}%` })
+ TweenLite.set(target, { transformOrigin: '50% 50%' })
+
+ TweenLite.to(target, 2, {
+
+ })
+
+ TweenLite.to([target, maskImage], animation, {
+ scale,
+ onComplete: onExpanded
+ })
+
+ target.classList.add('expanded')
+ }
+
+ return
+ }
+
+ static _bringToFront(target){
+ const parent = target.parentNode
+ if(target && parent){
+ parent.removeChild(target)
+ parent.appendChild(target)
+ }else console.error('Could not bring to front. Either no target or no parent.', target, parent)
+ }
+
+ static _getSVGMask(circle, { svgRoot = null, image = null } = {}) {
+ const id = this._retrieveId(circle)
+ const maskId = 'mask' + id
+ const maskImageId = 'maskImage' + id
+
+ if (!svgRoot) svgRoot = circle.closest('svg')
+
+ let mask = svgRoot.getElementById(maskId)
+ let maskImage = svgRoot.getElementById(maskImageId)
+
+ if (!mask || !maskImage)
+ [mask, maskImage] = Highlight._createSVGMask(circle, { svgRoot, image, id })
+
+ return [mask, maskImage]
+ }
+
+
+
+ /**
+ * Creates an SVG mask for a provided svgElement.
+ *
+ * @static
+ * @param {SVGElement} element - Element that should be masked.
+ * @param {object} opts - Optional parameters to avoid unnecessary fetching of elements.
+ * @param {SVGElement} opts.svgRoot - The root
+
+ Note that the objects can also be actived by clicking nearby and not directly on the DOM node.
+ This solves a major problem on large tabletops with a parallaxis in the display.
+
+
+
+
A Demo Card with onclick
+
+
Lorem ipsum dolor sit amet,
+ consetetur sadipscing elitr.
+
+
+
+
A Demo Card with selectors
+
+
Lorem ipsum dolor sit
+ amet,
+ consetetur sadipscing elitr.
+
+
+
+
+
+
+
+ Using Cards within Scatters
+
+
Cards can be used within scatters. Since the CardWrapper implements the TapDelegate protocol they can simply
+ be attached to a DOMScatter object.
+