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 {
|