Skip to content

Commit

Permalink
Feature: Use Floating UI for tooltip positioning (#276)
Browse files Browse the repository at this point in the history
* Fix tooltip positioning.

* Add tooltip to main frame.

* Fix unknown and hidden frame tooltip.

* Fix arrow overflowing from tooltip.

* Fix tooltip for hidden frames and unknown frames when scrolling.

* Fix scrollIntoView

* Fix angular bottom of tooltip

* Handle error messaging.

* code-quality:remove JSDoc types

* code-quality:add early return and change catch comment

* code-quality:replace bind with arrow functions

---------

Co-authored-by: ayushnirwal <coding.carrots@gmail.com>
  • Loading branch information
amovar18 and ayushnirwal authored Dec 13, 2023
1 parent a5ca8e4 commit 5a5274e
Show file tree
Hide file tree
Showing 8 changed files with 15,584 additions and 285 deletions.
15,457 changes: 15,453 additions & 4 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions packages/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
},
"license": "Apache-2.0",
"scripts": {
"dev": "webpack --config webpack.config.cjs --watch",
"build": "cross-env NODE_ENV=production webpack --config webpack.config.cjs"
},
"dev": "webpack --config webpack.config.cjs --watch",
"build": "cross-env NODE_ENV=production webpack --config webpack.config.cjs"
},
"bugs": {
"url": "https://github.com/GoogleChromeLabs/ps-analysis-tool/issues"
},
"homepage": "https://github.com/GoogleChromeLabs/ps-analysis-tool#readme",
"dependencies": {
"@floating-ui/core": "^1.5.0",
"@floating-ui/dom": "^1.5.3",
"@ps-analysis-tool/common": "*",
"@ps-analysis-tool/design-system": "*",
"classnames": "^2.3.2",
Expand All @@ -33,7 +35,7 @@
"validate.js": "^0.13.1",
"victory": "^36.6.11"
},
"devDependencies": {
"@types/react-copy-to-clipboard": "^5.0.4"
}
"devDependencies": {
"@types/react-copy-to-clipboard": "^5.0.4"
}
}
146 changes: 99 additions & 47 deletions packages/extension/src/contentScript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* External dependencies
*/
import { noop } from '@ps-analysis-tool/common';
import { computePosition, flip, shift } from '@floating-ui/core';
import { autoUpdate, platform, arrow } from '@floating-ui/dom';
/**
* Internal dependencies.
*/
Expand All @@ -23,7 +29,6 @@ import {
addOverlay,
setOverlayPosition,
addTooltip,
setTooltipPosition,
} from './popovers';
import type { ResponseType } from './types';
import { CookieStore } from '../localStore';
Expand All @@ -41,49 +46,49 @@ import './style.css';
*/
class WebpageContentScript {
/**
* @property {chrome.runtime.Port | null} port - The connection port.
* Connection port
*/
port: chrome.runtime.Port | null = null;

/**
* @property {boolean} isInspecting - If true, the page is currently being inspected.
* Cleanup function that needs to run when tooltip is removed from the screen.
*/
cleanup: () => void = noop;

/**
* If true, the page is currently being inspected.
*/
isInspecting = false;

/**
* @property {boolean} isHoveringOverPage - If true, the mouse is currently hovering over the page.
* If true, the mouse is currently hovering over the page.
*/
isHoveringOverPage = false;

/**
* @property {boolean} bodyHoverStateSent - Keeps track if the hover state message has been sent.
* Keeps track if the hover state message has been sent.
*/
bodyHoverStateSent = false;

/**
* @property {Array<() => void>} scrollEventListeners - Array of scroll event listeners.
* Array of scroll event listeners.
*/
scrollEventListeners: Array<() => void> = [];

/**
* @property {HTMLElement} docElement - Document element.
* Document element.
*/
docElement: HTMLElement;

/**
* @property {HTMLElement} hoveredFrame - Frame that is currently being hovered over.
* Frame that is currently being hovered over.
*/
hoveredFrame: HTMLElement | null = null;

/**
* Initialize.
*/
constructor() {
this.handleHoverEvent = this.handleHoverEvent.bind(this);
this.abortInspection = this.abortInspection.bind(this);
this.onMessage = this.onMessage.bind(this);
this.onDisconnect = this.onDisconnect.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.docElement = document.documentElement;

this.listenToConnection();
Expand Down Expand Up @@ -136,7 +141,7 @@ class WebpageContentScript {
* Handles incoming messages from the connected port.
* @param {ResponseType} response - The incoming message/response.
*/
onMessage(response: ResponseType) {
onMessage = (response: ResponseType) => {
this.isInspecting = response.isInspecting;

if (response.isInspecting) {
Expand All @@ -147,7 +152,7 @@ class WebpageContentScript {
} else {
this.abortInspection();
}
}
};

/**
* Inserts overlay
Expand Down Expand Up @@ -188,23 +193,73 @@ class WebpageContentScript {
numberOfVisibleFrames,
numberOfHiddenFrames
);

const isHiddenForFirstTime = frame ? !frame.clientWidth : false;
setTooltipPosition(
tooltip,
frame,
isHiddenForFirstTime,
response.selectedFrame
);

const updatePosition = () => {
if (isElementVisibleInViewport(frame, true)) {
const isHidden = frame ? !frame.clientWidth : false;
setTooltipPosition(tooltip, frame, isHidden, response.selectedFrame);
}
};

this.addEventListerOnScroll(updatePosition);
const arrowElement = document.getElementById('ps-content-tooltip-arrow');
if (
(frame?.tagName === 'BODY' || isFrameHidden(frame) || !frame) &&
arrowElement &&
tooltip
) {
Object.assign(arrowElement.style, {
left: '20px',
top: '10px',
transform: 'rotate(45deg)',
});
Object.assign(tooltip.style, {
top: `${5 + window.scrollY}px`,
});
return tooltip;
}
if (
frame &&
(frame.tagName !== 'BODY' || !isFrameHidden(frame)) &&
tooltip &&
arrowElement
) {
this.cleanup = autoUpdate(frame, tooltip, () => {
computePosition(frame, tooltip, {
platform: platform,
placement: 'top',
middleware: [
shift({
boundary: document.querySelector('body'),
}),
flip({
boundary: document.querySelector('body'),
}),
arrow({
element: arrowElement,
}),
],
}).then(({ x, y, middlewareData, placement }) => {
Object.assign(tooltip.style, {
top: `${y}px`,
left: `${x}px`,
});
const side = placement.split('-')[0];

const staticSide = {
top: 'bottom',
right: 'left',
bottom: 'top',
left: 'right',
}[side];

if (middlewareData.arrow) {
const { x: arrowX, y: arrowY } = middlewareData.arrow;

Object.assign(arrowElement.style, {
left: arrowX ? `${arrowX - 15}px` : '',
top: arrowY ? `${arrowY}px` : '',
right: '',
bottom: '',
[staticSide as string]: `${arrowElement.offsetWidth / 2}px`,
transform: 'rotate(45deg)',
});
}
return tooltip;
});
});
}

return tooltip;
}
Expand All @@ -231,27 +286,29 @@ class WebpageContentScript {
this.scrollEventListeners = [];
}

this.cleanup();

removeAllPopovers();
}

/**
* Handles the port disconnection event.
*/
onDisconnect() {
onDisconnect = () => {
this.port?.onMessage.removeListener(this.onMessage);
this.port = null;
this.abortInspection();
}
};

/**
* Abort inspection and removes all frame popovers and hover event listeners.
*/
abortInspection(): void {
abortInspection = (): void => {
this.removeEventListeners();
removeAllPopovers();
toggleFrameHighlighting(false);
this.isInspecting = false;
}
};

/**
* Insert popovers for all visible frames.
Expand Down Expand Up @@ -385,18 +442,13 @@ class WebpageContentScript {

const firstToolTip = popoverElement['firstToolTip'];
const frameWithTooltip = popoverElement['frameWithTooltip'];

if (
firstToolTip &&
!this.isHoveringOverPage &&
frameToScrollTo.clientWidth &&
!isElementVisibleInViewport(frameWithTooltip)
!isElementVisibleInViewport(firstToolTip)
) {
(firstToolTip as HTMLElement).scrollIntoView({
behavior: 'instant',
block: 'start',
inline: 'nearest',
});
(frameWithTooltip as HTMLElement).scrollIntoView();
}
}

Expand All @@ -405,7 +457,7 @@ class WebpageContentScript {
* @param {MouseEvent} event - The mouse event triggered by user action.
*/
// eslint-disable-next-line complexity
handleHoverEvent(event: MouseEvent): void {
handleHoverEvent = (event: MouseEvent) => {
const target = event.target as HTMLElement;
const isNonIframeElement = target.tagName !== 'IFRAME';
const isTooltipElement =
Expand Down Expand Up @@ -494,13 +546,13 @@ class WebpageContentScript {
} catch (error) {
this.abortInspection();
}
}
};

/**
* Toggle this.isHoveringOverPage state when mouse or visibility is changed.
* @param {MouseEvent} event Mouse event.
*/
handleMouseMove(event: Event) {
handleMouseMove = (event: Event) => {
if (event?.type === 'mouseenter') {
this.isHoveringOverPage = true;
} else if (event?.type === 'mouseleave') {
Expand All @@ -510,7 +562,7 @@ class WebpageContentScript {
if (document.hidden) {
this.isHoveringOverPage = false;
}
}
};

/**
* Set topics to be used in the Topics landing page.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ const createTooltip = (
const tooltipShowButtonContainer = createShowMoreButton();
content.appendChild(tooltipShowButtonContainer);
}

const toolTipArrow = document.createElement('div');
toolTipArrow.classList.add('ps-content-arrow');
toolTipArrow.id = 'ps-content-tooltip-arrow';
tooltip.appendChild(toolTipArrow);
tooltip.appendChild(content);

tooltip.popover = 'manual';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,3 @@
* limitations under the License.
*/
export { default as addTooltip } from './addTooltip';
export { default as setTooltipPosition } from './setTooltipPosition';
Loading

0 comments on commit 5a5274e

Please sign in to comment.