Touch tooltips, more tooltips
This commit is contained in:
+189
-15
@@ -25,39 +25,213 @@ import React from 'react';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
|
||||
export interface ToolTipExProps extends React.ComponentProps<typeof Tooltip> {
|
||||
valueTooltip?: React.ReactNode; // For use with value prop, to show a tooltip on the value.
|
||||
}
|
||||
|
||||
/* ToolTipEx: a reimplementation of the MUI Tooltip that works with pointer events and long presses. */
|
||||
|
||||
function ToolTipEx(props: ToolTipExProps) {
|
||||
let [disableToolTip, setDisableToolTip] = React.useState(false);
|
||||
let {title, ...extras} = props;
|
||||
function handleMouseDownCapture(event: React.PointerEvent<HTMLDivElement>) {
|
||||
if ((event as any).pointerType === "mouse") {
|
||||
setDisableToolTip(true);
|
||||
let {title, valueTooltip, ...extras} = props;
|
||||
let [open,setOpen]= React.useState(false);
|
||||
let [isLongPress, setIsLongPress] = React.useState(false);
|
||||
let [longPressLeaving, setLongPressLeaving] = React.useState(false);
|
||||
let [timeout, setTimeout] = React.useState<number>(0);
|
||||
let [timeoutInstance, setTimeoutInstance] = React.useState<number>(0);
|
||||
let [pointerDownPoint, setPointerdownPoint] = React.useState<{x: number, y: number} | null>(null);
|
||||
|
||||
const hoverTimeout = 1250;
|
||||
const longpressTimeout = 500;
|
||||
const longpressLeaveTimeout = 1500;
|
||||
|
||||
function startTimeout(timeout: number) {
|
||||
setTimeout(timeout);
|
||||
setTimeoutInstance(timeoutInstance + 1); // make useeffect run.
|
||||
}
|
||||
function stopTimeout() {
|
||||
if (timeout > 0) {
|
||||
startTimeout(0);
|
||||
}
|
||||
}
|
||||
function handleMouseEnter(event: React.MouseEvent<HTMLDivElement>) {
|
||||
setOpen(false);
|
||||
startTimeout(hoverTimeout);
|
||||
}
|
||||
|
||||
function handleMouseLeave(event: React.MouseEvent<HTMLDivElement>) {
|
||||
setOpen(false);
|
||||
stopTimeout();
|
||||
}
|
||||
|
||||
function handlePointerDownCapture(event: React.PointerEvent<HTMLDivElement>) {
|
||||
let pointerType = (event as any).pointerType || "n/a";
|
||||
setIsLongPress(false);
|
||||
if (pointerType === "mouse") {
|
||||
setOpen(false);
|
||||
stopTimeout(); // Reset hover timeout for mouse
|
||||
} else { // pen, or touch.
|
||||
setTimeout(longpressTimeout);
|
||||
setOpen(false);
|
||||
setPointerdownPoint({x: event.clientX, y: event.clientY});
|
||||
}
|
||||
}
|
||||
|
||||
function handlePointerMoveCapture(event: React.PointerEvent<HTMLDivElement>) {
|
||||
if ((event as any).pointerType !== "mouse") {
|
||||
setDisableToolTip(false);
|
||||
React.useEffect(()=> {
|
||||
let t = timeout;
|
||||
let handle: number | null = null;
|
||||
if (valueTooltip === undefined) // no timeout if there's a value tooltip
|
||||
{
|
||||
if (t > 0) {
|
||||
console.log("ToolTipEx: starting timeout for ", t);
|
||||
handle = window.setTimeout(() => {
|
||||
setOpen(true);
|
||||
},t);
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
if (handle !== null) {
|
||||
console.log("ToolTipEx: clearing timeout for ", t);
|
||||
window.clearTimeout(handle);
|
||||
}
|
||||
};
|
||||
},[timeoutInstance]);
|
||||
|
||||
React.useEffect(()=> {
|
||||
if (longPressLeaving) {
|
||||
let handle: number | null = null;
|
||||
handle = window.setTimeout(() => {
|
||||
setIsLongPress(false);
|
||||
setLongPressLeaving(false);
|
||||
}, longpressLeaveTimeout);
|
||||
return () => {
|
||||
if (handle !== null) {
|
||||
window.clearTimeout(handle);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return () => { };
|
||||
}
|
||||
},[longPressLeaving]);
|
||||
|
||||
function handlePointerCancel(event: React.PointerEvent<HTMLDivElement>) {
|
||||
setOpen(false);
|
||||
setIsLongPress(false);
|
||||
stopTimeout();
|
||||
setPointerdownPoint(null); // Reset the mouse down point
|
||||
}
|
||||
function handlePointerMoveCapture(event: React.PointerEvent<HTMLDivElement>) {
|
||||
let pointerType = (event as any).pointerType || "n/a";
|
||||
if (pointerType === "mouse") {
|
||||
setOpen(false);
|
||||
startTimeout(hoverTimeout); // Reset hover timeout for mouse
|
||||
} else { // pen, or touch.
|
||||
if (pointerDownPoint) {
|
||||
const dx = event.clientX - pointerDownPoint.x;
|
||||
const dy = event.clientY - pointerDownPoint.y;
|
||||
if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
|
||||
// If the mouse has moved more than 5 pixels, re-enable the tooltip
|
||||
setOpen(false);
|
||||
setIsLongPress(false);
|
||||
stopTimeout();
|
||||
setPointerdownPoint(null); // Reset the mouse down point
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleConextMenu(event: React.MouseEvent<HTMLDivElement>) {
|
||||
event.preventDefault(); // Prevent the default context menu from appearing
|
||||
event.stopPropagation(); // Prevent the default context menu from appearing
|
||||
if (pointerDownPoint) // touch sequence. This is a long press.
|
||||
{
|
||||
setIsLongPress(true);
|
||||
setOpen(true);
|
||||
stopTimeout();
|
||||
}
|
||||
} // Reset hover timeout for context menu
|
||||
function handlePointerUpCapture(event: React.PointerEvent<HTMLDivElement>) {
|
||||
let pointerType = (event as any).pointerType || "n/a";
|
||||
if (pointerType === "mouse") {
|
||||
setOpen(false);
|
||||
stopTimeout(); // Reset hover timeout for mouse
|
||||
} else { // pen, or touch.
|
||||
stopTimeout();
|
||||
setOpen(false);
|
||||
if (isLongPress) {
|
||||
// If this is a long press, we want to leave the tooltip open for a while.
|
||||
setLongPressLeaving(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
let effectiveTitle: React.ReactNode | null = null;
|
||||
let placement: "top-start" | "right" = "top-start";
|
||||
if (valueTooltip !== undefined && !isLongPress) {
|
||||
effectiveTitle = valueTooltip; // Show value tooltip if available
|
||||
placement = "right";
|
||||
}
|
||||
else {
|
||||
if (open || isLongPress) {
|
||||
effectiveTitle = title; // Show regular title if no value tooltip
|
||||
}
|
||||
}
|
||||
function handleClickCapture(event: React.MouseEvent<HTMLDivElement>) {
|
||||
setLongPressLeaving(false);
|
||||
setIsLongPress(false);
|
||||
setOpen(false);
|
||||
stopTimeout(); // Reset hover timeout on click
|
||||
}
|
||||
return (
|
||||
<div
|
||||
onClickCapture={(e) => {
|
||||
console.log("ToolTipEx: onClickCapture");
|
||||
handleClickCapture(e);
|
||||
}}
|
||||
|
||||
onMouseEnter={(e)=> {
|
||||
console.log("ToolTipEx: onMouseEnter");
|
||||
|
||||
handleMouseEnter(e);
|
||||
}}
|
||||
onMouseLeave={(e)=> {
|
||||
console.log("ToolTipEx: onMouseLeave");
|
||||
handleMouseLeave(e);
|
||||
}}
|
||||
|
||||
onPointerCancelCapture={(e) => {
|
||||
console.log("ToolTipEx: onPointerCancelCapture");
|
||||
handlePointerCancel(e);
|
||||
}}
|
||||
onContextMenuCapture={(e) => {
|
||||
console.log("ToolTipEx: onContextMenuCapture");
|
||||
handleConextMenu(e);
|
||||
return false;
|
||||
}}
|
||||
onPointerDownCapture={(e) => {
|
||||
handleMouseDownCapture(e);
|
||||
console.log("ToolTipEx: onPointerDownCapture");
|
||||
handlePointerDownCapture(e);
|
||||
}}
|
||||
onPointerMoveCapture={(e) => {
|
||||
console.log("ToolTipEx: onPointerMoveCapture");
|
||||
handlePointerMoveCapture(e);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setDisableToolTip(false);
|
||||
}
|
||||
}
|
||||
onPointerUpCapture={(e) => {
|
||||
console.log("ToolTipEx: onPointerUpCapture");
|
||||
handlePointerUpCapture(e);
|
||||
}}
|
||||
>
|
||||
<Tooltip {...extras}
|
||||
title={disableToolTip? undefined: title}
|
||||
placement="top-start" arrow enterDelay={1500} enterNextDelay={1500}
|
||||
open={open || isLongPress || valueTooltip !== undefined}
|
||||
disableInteractive={true}
|
||||
disableTouchListener={true}
|
||||
disableHoverListener={true}
|
||||
disableFocusListener={true}
|
||||
|
||||
title={effectiveTitle}
|
||||
placement={ placement}
|
||||
arrow enterDelay={1500} enterNextDelay={1500}
|
||||
slotProps={{
|
||||
transition: {
|
||||
timeout: 0 // Disable transition for the tooltip
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user