Fix - Breaks the UI when element has fixed or absolute position
Browse files- .eslintrc.json +1 -0
- src/common/constants.js +1 -0
- src/common/utils.js +21 -0
- src/core/element.js +47 -40
- src/driver.scss +3 -0
- types/index.d.ts +10 -6
.eslintrc.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
| 11 |
"func-names": "off",
|
| 12 |
"no-bitwise": "off",
|
| 13 |
"class-methods-use-this": "off",
|
|
|
|
| 14 |
"no-param-reassign": [
|
| 15 |
"off"
|
| 16 |
],
|
|
|
|
| 11 |
"func-names": "off",
|
| 12 |
"no-bitwise": "off",
|
| 13 |
"class-methods-use-this": "off",
|
| 14 |
+
"prefer-destructuring": "off",
|
| 15 |
"no-param-reassign": [
|
| 16 |
"off"
|
| 17 |
],
|
src/common/constants.js
CHANGED
|
@@ -13,6 +13,7 @@ export const ID_STAGE = 'driver-highlighted-element-stage';
|
|
| 13 |
export const ID_POPOVER = 'driver-popover-item';
|
| 14 |
|
| 15 |
export const CLASS_DRIVER_HIGHLIGHTED_ELEMENT = 'driver-highlighted-element';
|
|
|
|
| 16 |
|
| 17 |
export const CLASS_NO_ANIMATION = 'driver-no-animation';
|
| 18 |
export const CLASS_POPOVER_TIP = 'driver-popover-tip';
|
|
|
|
| 13 |
export const ID_POPOVER = 'driver-popover-item';
|
| 14 |
|
| 15 |
export const CLASS_DRIVER_HIGHLIGHTED_ELEMENT = 'driver-highlighted-element';
|
| 16 |
+
export const CLASS_POSITION_RELATIVE = 'driver-position-relative';
|
| 17 |
|
| 18 |
export const CLASS_NO_ANIMATION = 'driver-no-animation';
|
| 19 |
export const CLASS_POPOVER_TIP = 'driver-popover-tip';
|
src/common/utils.js
CHANGED
|
@@ -11,3 +11,24 @@ export const createNodeFromString = (htmlString) => {
|
|
| 11 |
// Change this to div.childNodes to support multiple top-level nodes
|
| 12 |
return div.firstChild;
|
| 13 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
// Change this to div.childNodes to support multiple top-level nodes
|
| 12 |
return div.firstChild;
|
| 13 |
};
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
* Gets the CSS property from the given element
|
| 17 |
+
* @param {HTMLElement|Node} element
|
| 18 |
+
* @param {string} propertyName
|
| 19 |
+
* @return {string}
|
| 20 |
+
*/
|
| 21 |
+
export const getStyleProperty = (element, propertyName) => {
|
| 22 |
+
let propertyValue = '';
|
| 23 |
+
|
| 24 |
+
if (element.currentStyle) {
|
| 25 |
+
propertyValue = element.currentStyle[propertyName];
|
| 26 |
+
} else if (document.defaultView && document.defaultView.getComputedStyle) {
|
| 27 |
+
propertyValue = document.defaultView
|
| 28 |
+
.getComputedStyle(element, null)
|
| 29 |
+
.getPropertyValue(propertyName);
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
return propertyValue && propertyValue.toLowerCase ? propertyValue.toLowerCase() : propertyValue;
|
| 33 |
+
};
|
| 34 |
+
|
src/core/element.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
|
|
|
|
|
| 1 |
import Position from './position';
|
| 2 |
-
import { ANIMATION_DURATION_MS, CLASS_DRIVER_HIGHLIGHTED_ELEMENT } from '../common/constants';
|
| 3 |
|
| 4 |
/**
|
| 5 |
* Wrapper around DOMElements to enrich them
|
|
@@ -36,27 +37,6 @@ export default class Element {
|
|
| 36 |
this.animationTimeout = null;
|
| 37 |
}
|
| 38 |
|
| 39 |
-
/**
|
| 40 |
-
* Gets the screen co-ordinates (x,y) for the current dom element
|
| 41 |
-
* @returns {{x: number, y: number}}
|
| 42 |
-
* @private
|
| 43 |
-
*/
|
| 44 |
-
getScreenCoordinates() {
|
| 45 |
-
let tempNode = this.node;
|
| 46 |
-
|
| 47 |
-
let x = this.document.documentElement.offsetLeft;
|
| 48 |
-
let y = this.document.documentElement.offsetTop;
|
| 49 |
-
|
| 50 |
-
if (tempNode.offsetParent) {
|
| 51 |
-
do {
|
| 52 |
-
x += tempNode.offsetLeft;
|
| 53 |
-
y += tempNode.offsetTop;
|
| 54 |
-
} while (tempNode = tempNode.offsetParent);
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
return { x, y };
|
| 58 |
-
}
|
| 59 |
-
|
| 60 |
/**
|
| 61 |
* Checks if the current element is visible in viewport
|
| 62 |
* @returns {boolean}
|
|
@@ -128,24 +108,20 @@ export default class Element {
|
|
| 128 |
* @public
|
| 129 |
*/
|
| 130 |
getCalculatedPosition() {
|
| 131 |
-
const
|
| 132 |
-
const
|
| 133 |
-
|
| 134 |
-
top: Number.MAX_VALUE,
|
| 135 |
-
right: 0,
|
| 136 |
-
bottom: 0,
|
| 137 |
-
});
|
| 138 |
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
position.left = Math.min(position.left, coordinates.x);
|
| 143 |
-
position.top = Math.min(position.top, coordinates.y);
|
| 144 |
-
position.right = Math.max(position.right, coordinates.x + this.node.offsetWidth);
|
| 145 |
-
position.bottom = Math.max(position.bottom, coordinates.y + this.node.offsetHeight);
|
| 146 |
-
}
|
| 147 |
|
| 148 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
}
|
| 150 |
|
| 151 |
/**
|
|
@@ -160,7 +136,7 @@ export default class Element {
|
|
| 160 |
this.hideStage();
|
| 161 |
}
|
| 162 |
|
| 163 |
-
this.
|
| 164 |
|
| 165 |
// If there was any animation in progress, cancel that
|
| 166 |
this.window.clearTimeout(this.animationTimeout);
|
|
@@ -170,6 +146,10 @@ export default class Element {
|
|
| 170 |
}
|
| 171 |
}
|
| 172 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
/**
|
| 174 |
* Checks if the given element is same as the current element
|
| 175 |
* @param {Element} element
|
|
@@ -202,7 +182,7 @@ export default class Element {
|
|
| 202 |
this.showPopover();
|
| 203 |
this.showStage();
|
| 204 |
|
| 205 |
-
this.
|
| 206 |
|
| 207 |
const highlightedElement = this;
|
| 208 |
const popoverElement = this.popover;
|
|
@@ -220,6 +200,33 @@ export default class Element {
|
|
| 220 |
}
|
| 221 |
}
|
| 222 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
/**
|
| 224 |
* Shows the stage behind the element
|
| 225 |
* @public
|
|
|
|
| 1 |
+
import { ANIMATION_DURATION_MS, CLASS_DRIVER_HIGHLIGHTED_ELEMENT, CLASS_POSITION_RELATIVE } from '../common/constants';
|
| 2 |
+
import { getStyleProperty } from '../common/utils';
|
| 3 |
import Position from './position';
|
|
|
|
| 4 |
|
| 5 |
/**
|
| 6 |
* Wrapper around DOMElements to enrich them
|
|
|
|
| 37 |
this.animationTimeout = null;
|
| 38 |
}
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
/**
|
| 41 |
* Checks if the current element is visible in viewport
|
| 42 |
* @returns {boolean}
|
|
|
|
| 108 |
* @public
|
| 109 |
*/
|
| 110 |
getCalculatedPosition() {
|
| 111 |
+
const body = this.document.body;
|
| 112 |
+
const documentElement = this.document.documentElement;
|
| 113 |
+
const window = this.window;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
+
const scrollTop = this.window.pageYOffset || documentElement.scrollTop || body.scrollTop;
|
| 116 |
+
const scrollLeft = window.pageXOffset || documentElement.scrollLeft || body.scrollLeft;
|
| 117 |
+
const elementRect = this.node.getBoundingClientRect();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
+
return new Position({
|
| 120 |
+
top: elementRect.top + scrollTop,
|
| 121 |
+
left: elementRect.left + scrollLeft,
|
| 122 |
+
right: elementRect.left + scrollLeft + elementRect.width,
|
| 123 |
+
bottom: elementRect.top + scrollTop + elementRect.height,
|
| 124 |
+
});
|
| 125 |
}
|
| 126 |
|
| 127 |
/**
|
|
|
|
| 136 |
this.hideStage();
|
| 137 |
}
|
| 138 |
|
| 139 |
+
this.removeHighlightClasses();
|
| 140 |
|
| 141 |
// If there was any animation in progress, cancel that
|
| 142 |
this.window.clearTimeout(this.animationTimeout);
|
|
|
|
| 146 |
}
|
| 147 |
}
|
| 148 |
|
| 149 |
+
removeHighlightClasses() {
|
| 150 |
+
this.node.classList.remove(CLASS_DRIVER_HIGHLIGHTED_ELEMENT);
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
/**
|
| 154 |
* Checks if the given element is same as the current element
|
| 155 |
* @param {Element} element
|
|
|
|
| 182 |
this.showPopover();
|
| 183 |
this.showStage();
|
| 184 |
|
| 185 |
+
this.addHighlightClasses();
|
| 186 |
|
| 187 |
const highlightedElement = this;
|
| 188 |
const popoverElement = this.popover;
|
|
|
|
| 200 |
}
|
| 201 |
}
|
| 202 |
|
| 203 |
+
addHighlightClasses() {
|
| 204 |
+
this.node.classList.add(CLASS_DRIVER_HIGHLIGHTED_ELEMENT);
|
| 205 |
+
|
| 206 |
+
if (this.canMakeRelative()) {
|
| 207 |
+
this.node.classList.add(CLASS_POSITION_RELATIVE);
|
| 208 |
+
}
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
canMakeRelative() {
|
| 212 |
+
const currentPosition = this.getStyleProperty('position');
|
| 213 |
+
const avoidPositionsList = ['absolute', 'fixed', 'relative'];
|
| 214 |
+
|
| 215 |
+
// Because if the element has any of these positions, making it
|
| 216 |
+
// relative will break the UI
|
| 217 |
+
return !avoidPositionsList.includes(currentPosition);
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
/**
|
| 221 |
+
* Get an element CSS property on the page
|
| 222 |
+
* @param {string} property
|
| 223 |
+
* @returns string
|
| 224 |
+
* @private
|
| 225 |
+
*/
|
| 226 |
+
getStyleProperty(property) {
|
| 227 |
+
return getStyleProperty(this.node, property);
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
/**
|
| 231 |
* Shows the stage behind the element
|
| 232 |
* @public
|
src/driver.scss
CHANGED
|
@@ -172,5 +172,8 @@ div#driver-highlighted-element-stage {
|
|
| 172 |
|
| 173 |
.driver-highlighted-element {
|
| 174 |
z-index: $highlighted-element-zindex !important;
|
|
|
|
|
|
|
|
|
|
| 175 |
position: relative;
|
| 176 |
}
|
|
|
|
| 172 |
|
| 173 |
.driver-highlighted-element {
|
| 174 |
z-index: $highlighted-element-zindex !important;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
.driver-position-relative {
|
| 178 |
position: relative;
|
| 179 |
}
|
types/index.d.ts
CHANGED
|
@@ -199,12 +199,6 @@ declare module 'driver.js' {
|
|
| 199 |
window: Window,
|
| 200 |
document: Document);
|
| 201 |
|
| 202 |
-
/**
|
| 203 |
-
* Gets the screen coordinates for the current DOM Element
|
| 204 |
-
* @return {Driver.ScreenCoordinates}
|
| 205 |
-
*/
|
| 206 |
-
public getScreenCoordinates(): Driver.ScreenCoordinates;
|
| 207 |
-
|
| 208 |
/**
|
| 209 |
* Checks if the give element is in view port or not
|
| 210 |
* @return {boolean}
|
|
@@ -280,6 +274,16 @@ declare module 'driver.js' {
|
|
| 280 |
* @return {Driver.ElementSize}
|
| 281 |
*/
|
| 282 |
public getSize(): Driver.ElementSize;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
}
|
| 284 |
|
| 285 |
class Overlay {
|
|
|
|
| 199 |
window: Window,
|
| 200 |
document: Document);
|
| 201 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
/**
|
| 203 |
* Checks if the give element is in view port or not
|
| 204 |
* @return {boolean}
|
|
|
|
| 274 |
* @return {Driver.ElementSize}
|
| 275 |
*/
|
| 276 |
public getSize(): Driver.ElementSize;
|
| 277 |
+
|
| 278 |
+
/**
|
| 279 |
+
* Removes the highlight classes from current element if any
|
| 280 |
+
*/
|
| 281 |
+
private removeHighlightClasses(): void;
|
| 282 |
+
|
| 283 |
+
/**
|
| 284 |
+
* Adds the highlight classes to current element if required
|
| 285 |
+
*/
|
| 286 |
+
private addHighlightClasses(): void;
|
| 287 |
}
|
| 288 |
|
| 289 |
class Overlay {
|