File size: 5,312 Bytes
59d1882 f3edffc 22c7264 8755ee2 a47eab3 8755ee2 a174130 5d08a61 0aebd1a 27c03db c7fb12b 5829a93 22c7264 37f5ac4 3155c68 22c7264 8755ee2 3ce1b66 8755ee2 ad0769b bdfd65a 22c7264 bdfd65a 9eea97b 0797fb7 59d1882 0797fb7 22c7264 8755ee2 a47eab3 3ce1b66 8755ee2 f3edffc 5aa283f 27c03db 22c7264 a174130 3a2c734 a174130 c7fb12b ede8cb9 a174130 ede8cb9 22c7264 8755ee2 bcf9be5 22c7264 27c03db bcf9be5 fe79342 0797fb7 c7fb12b 3ce1b66 0797fb7 ad0769b 9eea97b c7fb12b 22fca24 3ce1b66 22fca24 27c03db 3ce1b66 27c03db 8755ee2 3ce1b66 8755ee2 3155c68 864b5fe 140aae9 864b5fe 4be3ef9 5d08a61 3155c68 5d08a61 f898881 27c03db 33909e6 4be3ef9 3155c68 4be3ef9 3155c68 4be3ef9 3ce1b66 4be3ef9 37f5ac4 8755ee2 3ce1b66 8755ee2 c7fb12b a174130 f898881 a174130 22c7264 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
import { ANIMATION_DURATION_MS, ID_OVERLAY, OVERLAY_HTML } from '../common/constants';
import { createNodeFromString } from '../common/utils';
/**
* Responsible for overlay creation and manipulation i.e.
* cutting out the visible part, animating between the sections etc
*/
export default class Overlay {
/**
* @param {Object} options
* @param {Window} window
* @param {Document} document
*/
constructor(options, window, document) {
this.options = options;
this.highlightedElement = null; // currently highlighted dom element (instance of Element)
this.lastHighlightedElement = null; // element that was highlighted before current one
this.hideTimer = null;
this.window = window;
this.document = document;
this.removeNode = this.removeNode.bind(this);
}
/**
* Prepares the overlay
* @private
*/
attachNode() {
let pageOverlay = this.document.getElementById(ID_OVERLAY);
if (!pageOverlay) {
pageOverlay = createNodeFromString(OVERLAY_HTML);
document.body.appendChild(pageOverlay);
}
this.node = pageOverlay;
this.node.style.opacity = '0';
if (!this.options.animate) {
// For non-animation cases remove the overlay because we achieve this overlay by having
// a higher box-shadow on the stage. Why are we doing it that way? Because the stage that
// is shown "behind" the highlighted element to make it pop out of the screen, it introduces
// some stacking contexts issues. To avoid those issues we just make the stage background
// transparent and achieve the overlay using the shadow so to make the element below it visible
// through the stage even if there are stacking issues.
if (this.node.parentElement) {
this.node.parentElement.removeChild(this.node);
}
}
}
/**
* Highlights the dom element on the screen
* @param {Element} element
* @public
*/
highlight(element) {
if (!element || !element.node) {
console.warn('Invalid element to highlight. Must be an instance of `Element`');
return;
}
// If highlighted element is not changed from last time
if (element.isSame(this.highlightedElement)) {
return;
}
// There might be hide timer from last time
// which might be getting triggered
this.window.clearTimeout(this.hideTimer);
// Trigger the hook for highlight started
element.onHighlightStarted();
// Old element has been deselected
if (this.highlightedElement && !this.highlightedElement.isSame(this.lastHighlightedElement)) {
this.highlightedElement.onDeselected();
}
// get the position of element around which we need to draw
const position = element.getCalculatedPosition();
if (!position.canHighlight()) {
return;
}
this.lastHighlightedElement = this.highlightedElement;
this.highlightedElement = element;
this.show();
// Element has been highlighted
this.highlightedElement.onHighlighted();
}
/**
* Shows the overlay on whole screen
* @public
*/
show() {
if (this.node && this.node.parentElement) {
return;
}
this.attachNode();
window.setTimeout(() => {
this.node.style.opacity = `${this.options.opacity}`;
this.node.style.position = 'fixed';
this.node.style.left = '0';
this.node.style.top = '0';
this.node.style.bottom = '0';
this.node.style.right = '0';
});
}
/**
* Returns the currently selected element
* @returns {null|*}
* @public
*/
getHighlightedElement() {
return this.highlightedElement;
}
/**
* Gets the element that was highlighted before current element
* @returns {null|*}
* @public
*/
getLastHighlightedElement() {
return this.lastHighlightedElement;
}
/**
* Removes the overlay and cancel any listeners
* @public
*/
clear(immediate = false) {
// Callback for when overlay is about to be reset
if (this.options.onReset) {
this.options.onReset(this.highlightedElement);
}
// Deselect the highlighted element if any
if (this.highlightedElement) {
const hideStage = true;
this.highlightedElement.onDeselected(hideStage);
}
this.highlightedElement = null;
this.lastHighlightedElement = null;
if (!this.node) {
return;
}
// Clear any existing timers and remove node
this.window.clearTimeout(this.hideTimer);
if (this.options.animate && !immediate) {
this.node.style.opacity = '0';
this.hideTimer = this.window.setTimeout(this.removeNode, ANIMATION_DURATION_MS);
} else {
this.removeNode();
}
}
/**
* Removes the overlay node if it exists
* @private
*/
removeNode() {
if (this.node && this.node.parentElement) {
this.node.parentElement.removeChild(this.node);
}
}
/**
* Refreshes the overlay i.e. sets the size according to current window size
* And moves the highlight around if necessary
* @public
*/
refresh() {
// If no highlighted element, cancel the refresh
if (!this.highlightedElement) {
return;
}
// Reposition the stage and show popover
this.highlightedElement.showPopover();
this.highlightedElement.showStage();
}
}
|