Correction in HoverPopup.
This commit is contained in:
parent
c3720df294
commit
57bbcd4b45
@ -1,14 +1,14 @@
|
|||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {
|
import { closePopup, openPopup, togglePopup } from "../../slices/localVariablesSlice";
|
||||||
closePopup,
|
|
||||||
openPopup,
|
|
||||||
togglePopup,
|
|
||||||
} from "../../slices/localVariablesSlice";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* align: "auto" | "left" | "right"
|
* Props:
|
||||||
* boundaryRef: optional ref to the drawer/container element to use as boundary
|
* id, title, content, children
|
||||||
|
* className = ""
|
||||||
|
* Mode = "hover" | "click"
|
||||||
|
* align = "auto" | "left" | "right"
|
||||||
|
* boundaryRef = optional ref to DOM element to constrain popup within (getBoundingClientRect used)
|
||||||
*/
|
*/
|
||||||
const HoverPopup = ({
|
const HoverPopup = ({
|
||||||
id,
|
id,
|
||||||
@ -21,7 +21,7 @@ const HoverPopup = ({
|
|||||||
boundaryRef = null,
|
boundaryRef = null,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const visible = useSelector((s) => s.localVariables.popups[id] || false);
|
const visible = useSelector((s) => s.localVariables.popups?.[id] || false);
|
||||||
|
|
||||||
const triggerRef = useRef(null);
|
const triggerRef = useRef(null);
|
||||||
const popupRef = useRef(null);
|
const popupRef = useRef(null);
|
||||||
@ -39,7 +39,7 @@ const HoverPopup = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Close on outside click when using click mode
|
// Close on outside click when in click mode
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Mode !== "click" || !visible) return;
|
if (Mode !== "click" || !visible) return;
|
||||||
|
|
||||||
@ -58,38 +58,61 @@ const HoverPopup = ({
|
|||||||
return () => document.removeEventListener("click", handler);
|
return () => document.removeEventListener("click", handler);
|
||||||
}, [Mode, visible, dispatch, id]);
|
}, [Mode, visible, dispatch, id]);
|
||||||
|
|
||||||
|
// Positioning: compute left/top relative to popup.offsetParent, but clamp using boundaryRef (if provided)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!visible || !popupRef.current || !triggerRef.current) return;
|
if (!visible || !popupRef.current || !triggerRef.current) return;
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const popup = popupRef.current;
|
const popup = popupRef.current;
|
||||||
const boundaryEl = (boundaryRef && boundaryRef.current) || popup.parentElement;
|
const trigger = triggerRef.current;
|
||||||
if (!boundaryEl) return;
|
|
||||||
|
|
||||||
|
const triggerRect = trigger.getBoundingClientRect();
|
||||||
|
const popupRect = popup.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Find offsetParent (element the absolute popup is positioned relative to)
|
||||||
|
const offsetParent = popup.offsetParent || document.documentElement;
|
||||||
|
const offsetParentRect = offsetParent.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Global positions we want to clamp against (in viewport coordinates)
|
||||||
|
let desiredLeftGlobal;
|
||||||
|
if (align === "left") {
|
||||||
|
desiredLeftGlobal = triggerRect.left;
|
||||||
|
} else if (align === "right") {
|
||||||
|
desiredLeftGlobal = triggerRect.right - popupRect.width;
|
||||||
|
} else {
|
||||||
|
// auto / center: center popup under trigger
|
||||||
|
desiredLeftGlobal = triggerRect.left + triggerRect.width / 2 - popupRect.width / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute boundaries in global coordinates (use boundaryRef if provided, else offsetParent)
|
||||||
|
const boundaryEl = (boundaryRef && boundaryRef.current) || offsetParent;
|
||||||
const boundaryRect = boundaryEl.getBoundingClientRect();
|
const boundaryRect = boundaryEl.getBoundingClientRect();
|
||||||
const triggerRect = triggerRef.current.getBoundingClientRect();
|
|
||||||
popup.style.left = "";
|
// Clamp desiredLeftGlobal to boundaryRect
|
||||||
|
const minLeftGlobal = boundaryRect.left;
|
||||||
|
const maxLeftGlobal = boundaryRect.right - popupRect.width;
|
||||||
|
let clampedLeftGlobal = Math.min(Math.max(desiredLeftGlobal, minLeftGlobal), maxLeftGlobal);
|
||||||
|
|
||||||
|
// Convert to coordinates relative to offsetParent
|
||||||
|
const leftRelativeToOffsetParent = clampedLeftGlobal - offsetParentRect.left;
|
||||||
|
|
||||||
|
// Compute top: place popup just below trigger with small gap
|
||||||
|
const gap = 8;
|
||||||
|
const desiredTopGlobal = triggerRect.bottom + gap;
|
||||||
|
// Convert to offsetParent-relative top
|
||||||
|
const topRelativeToOffsetParent = desiredTopGlobal - offsetParentRect.top;
|
||||||
|
|
||||||
|
// Apply styles
|
||||||
|
popup.style.left = `${Math.round(leftRelativeToOffsetParent)}px`;
|
||||||
|
popup.style.top = `${Math.round(topRelativeToOffsetParent)}px`;
|
||||||
popup.style.right = "";
|
popup.style.right = "";
|
||||||
popup.style.transform = "";
|
popup.style.transform = "";
|
||||||
popup.style.top = "";
|
|
||||||
|
|
||||||
const popupRect = popup.getBoundingClientRect();
|
|
||||||
const triggerCenterX = triggerRect.left + triggerRect.width / 2 - boundaryRect.left;
|
|
||||||
let left = triggerCenterX - popupRect.width / 2;
|
|
||||||
|
|
||||||
// Clamp to boundaries
|
|
||||||
left = Math.max(0, Math.min(left, boundaryRect.width - popupRect.width));
|
|
||||||
popup.style.left = `${left}px`;
|
|
||||||
});
|
});
|
||||||
}, [visible, align, boundaryRef]);
|
}, [visible, align, boundaryRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="d-inline-block position-relative" style={{ overflow: "visible" }}>
|
||||||
className="d-inline-block position-relative" // <-- ADD THIS !!
|
|
||||||
style={{ overflow: "visible" }}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
|
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
@ -103,12 +126,12 @@ const HoverPopup = ({
|
|||||||
<div
|
<div
|
||||||
ref={popupRef}
|
ref={popupRef}
|
||||||
className={`hover-popup bg-white border rounded shadow-sm p-3 position-absolute mt-2 ${className}`}
|
className={`hover-popup bg-white border rounded shadow-sm p-3 position-absolute mt-2 ${className}`}
|
||||||
style={{
|
style={{
|
||||||
zIndex: 2000,
|
zIndex: 2000,
|
||||||
top: "100%",
|
|
||||||
minWidth: "200px",
|
minWidth: "200px",
|
||||||
maxWidth: "300px",
|
maxWidth: "300px",
|
||||||
wordWrap: "break-word",
|
wordWrap: "break-word",
|
||||||
|
// we set left/top from JS; ensure positionAbsolute context exists via offsetParent
|
||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
@ -118,7 +141,6 @@ const HoverPopup = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HoverPopup;
|
export default HoverPopup;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user