mirror of https://github.com/vector-im/riot-web
Merge pull request #3228 from matrix-org/jryans/tooltip-safe-area-alt
Improve interactive tooltip safe mousing areapull/21833/head
commit
35ba489a2b
|
@ -36,12 +36,13 @@ limitations under the License.
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
// tooltip overhang + action bar + action bar offset from event
|
// tooltip safe mousing area + tooltip overhang +
|
||||||
width: calc(48px + 100% + 8px);
|
// action bar + action bar offset from event
|
||||||
|
width: calc(10px + 48px + 100% + 8px);
|
||||||
// safe area + action bar
|
// safe area + action bar
|
||||||
height: calc(20px + 100%);
|
height: calc(20px + 100%);
|
||||||
top: -20px;
|
top: -20px;
|
||||||
left: -48px;
|
left: -58px;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
cursor: initial;
|
cursor: initial;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,12 +37,55 @@ function getOrCreateContainer() {
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInRect(x, y, rect, buffer = 10) {
|
function isInRect(x, y, rect, { buffer = 0 } = {}) {
|
||||||
const { top, right, bottom, left } = rect;
|
const { top, right, bottom, left } = rect;
|
||||||
return x >= (left - buffer) && x <= (right + buffer)
|
return x >= (left - buffer) && x <= (right + buffer)
|
||||||
&& y >= (top - buffer) && y <= (bottom + buffer);
|
&& y >= (top - buffer) && y <= (bottom + buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the positive slope of the diagonal of the rect.
|
||||||
|
*
|
||||||
|
* @param {DOMRect} rect
|
||||||
|
* @return {integer}
|
||||||
|
*/
|
||||||
|
function getDiagonalSlope(rect) {
|
||||||
|
const { top, right, bottom, left } = rect;
|
||||||
|
return (bottom - top) / (right - left);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInUpperLeftHalf(x, y, rect) {
|
||||||
|
const { bottom, left } = rect;
|
||||||
|
// Negative slope because Y values grow downwards and for this case, the
|
||||||
|
// diagonal goes from larger to smaller Y values.
|
||||||
|
const diagonalSlope = getDiagonalSlope(rect) * -1;
|
||||||
|
return isInRect(x, y, rect) && (y <= bottom + diagonalSlope * (x - left));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInLowerRightHalf(x, y, rect) {
|
||||||
|
const { bottom, left } = rect;
|
||||||
|
// Negative slope because Y values grow downwards and for this case, the
|
||||||
|
// diagonal goes from larger to smaller Y values.
|
||||||
|
const diagonalSlope = getDiagonalSlope(rect) * -1;
|
||||||
|
return isInRect(x, y, rect) && (y >= bottom + diagonalSlope * (x - left));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInUpperRightHalf(x, y, rect) {
|
||||||
|
const { top, left } = rect;
|
||||||
|
// Positive slope because Y values grow downwards and for this case, the
|
||||||
|
// diagonal goes from smaller to larger Y values.
|
||||||
|
const diagonalSlope = getDiagonalSlope(rect) * 1;
|
||||||
|
return isInRect(x, y, rect) && (y <= top + diagonalSlope * (x - left));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInLowerLeftHalf(x, y, rect) {
|
||||||
|
const { top, left } = rect;
|
||||||
|
// Positive slope because Y values grow downwards and for this case, the
|
||||||
|
// diagonal goes from smaller to larger Y values.
|
||||||
|
const diagonalSlope = getDiagonalSlope(rect) * 1;
|
||||||
|
return isInRect(x, y, rect) && (y >= top + diagonalSlope * (x - left));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This style of tooltip takes a "target" element as its child and centers the
|
* This style of tooltip takes a "target" element as its child and centers the
|
||||||
* tooltip along one edge of the target.
|
* tooltip along one edge of the target.
|
||||||
|
@ -91,15 +134,99 @@ export default class InteractiveTooltip extends React.Component {
|
||||||
this.target = element;
|
this.target = element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canTooltipFitAboveTarget() {
|
||||||
|
const { contentRect } = this.state;
|
||||||
|
const targetRect = this.target.getBoundingClientRect();
|
||||||
|
const targetTop = targetRect.top + window.pageYOffset;
|
||||||
|
return (
|
||||||
|
!contentRect ||
|
||||||
|
(targetTop - contentRect.height > MIN_SAFE_DISTANCE_TO_WINDOW_EDGE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onMouseMove = (ev) => {
|
onMouseMove = (ev) => {
|
||||||
const { clientX: x, clientY: y } = ev;
|
const { clientX: x, clientY: y } = ev;
|
||||||
const { contentRect } = this.state;
|
const { contentRect } = this.state;
|
||||||
const targetRect = this.target.getBoundingClientRect();
|
const targetRect = this.target.getBoundingClientRect();
|
||||||
|
|
||||||
if (!isInRect(x, y, contentRect) && !isInRect(x, y, targetRect)) {
|
// When moving the mouse from the target to the tooltip, we create a
|
||||||
this.hideTooltip();
|
// safe area that includes the tooltip, the target, and the trapezoid
|
||||||
|
// ABCD between them:
|
||||||
|
// ┌───────────┐
|
||||||
|
// │ │
|
||||||
|
// │ │
|
||||||
|
// A └───E───F───┘ B
|
||||||
|
// V
|
||||||
|
// ┌─┐
|
||||||
|
// │ │
|
||||||
|
// C└─┘D
|
||||||
|
//
|
||||||
|
// As long as the mouse remains inside the safe area, the tooltip will
|
||||||
|
// stay open.
|
||||||
|
const buffer = 10;
|
||||||
|
if (
|
||||||
|
isInRect(x, y, contentRect, { buffer }) ||
|
||||||
|
isInRect(x, y, targetRect)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.canTooltipFitAboveTarget()) {
|
||||||
|
const trapezoidLeft = {
|
||||||
|
top: contentRect.bottom,
|
||||||
|
right: targetRect.left,
|
||||||
|
bottom: targetRect.bottom,
|
||||||
|
left: contentRect.left - buffer,
|
||||||
|
};
|
||||||
|
const trapezoidCenter = {
|
||||||
|
top: contentRect.bottom,
|
||||||
|
right: targetRect.right,
|
||||||
|
bottom: targetRect.bottom,
|
||||||
|
left: targetRect.left,
|
||||||
|
};
|
||||||
|
const trapezoidRight = {
|
||||||
|
top: contentRect.bottom,
|
||||||
|
right: contentRect.right + buffer,
|
||||||
|
bottom: targetRect.bottom,
|
||||||
|
left: targetRect.right,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
isInUpperRightHalf(x, y, trapezoidLeft) ||
|
||||||
|
isInRect(x, y, trapezoidCenter) ||
|
||||||
|
isInUpperLeftHalf(x, y, trapezoidRight)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const trapezoidLeft = {
|
||||||
|
top: targetRect.top,
|
||||||
|
right: targetRect.left,
|
||||||
|
bottom: contentRect.top,
|
||||||
|
left: contentRect.left - buffer,
|
||||||
|
};
|
||||||
|
const trapezoidCenter = {
|
||||||
|
top: targetRect.top,
|
||||||
|
right: targetRect.right,
|
||||||
|
bottom: contentRect.top,
|
||||||
|
left: targetRect.left,
|
||||||
|
};
|
||||||
|
const trapezoidRight = {
|
||||||
|
top: targetRect.top,
|
||||||
|
right: contentRect.right + buffer,
|
||||||
|
bottom: contentRect.top,
|
||||||
|
left: targetRect.right,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
isInLowerRightHalf(x, y, trapezoidLeft) ||
|
||||||
|
isInRect(x, y, trapezoidCenter) ||
|
||||||
|
isInLowerLeftHalf(x, y, trapezoidRight)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hideTooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
onTargetMouseOver = (ev) => {
|
onTargetMouseOver = (ev) => {
|
||||||
|
@ -149,12 +276,12 @@ export default class InteractiveTooltip extends React.Component {
|
||||||
// edge, flip around to below the target.
|
// edge, flip around to below the target.
|
||||||
const position = {};
|
const position = {};
|
||||||
let chevronFace = null;
|
let chevronFace = null;
|
||||||
if (contentRect && (targetTop - contentRect.height <= MIN_SAFE_DISTANCE_TO_WINDOW_EDGE)) {
|
if (this.canTooltipFitAboveTarget()) {
|
||||||
position.top = targetBottom;
|
|
||||||
chevronFace = "top";
|
|
||||||
} else {
|
|
||||||
position.bottom = window.innerHeight - targetTop;
|
position.bottom = window.innerHeight - targetTop;
|
||||||
chevronFace = "bottom";
|
chevronFace = "bottom";
|
||||||
|
} else {
|
||||||
|
position.top = targetBottom;
|
||||||
|
chevronFace = "top";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Center the tooltip horizontally with the target's center.
|
// Center the tooltip horizontally with the target's center.
|
||||||
|
|
Loading…
Reference in New Issue