fix(ui): enforce canonical inspector selects

This commit is contained in:
Codex
2026-07-16 18:56:20 +03:00
parent 751d9963f1
commit a1c4ecaa93
19 changed files with 258 additions and 135 deletions
+13 -9
View File
@@ -17,6 +17,7 @@ export interface DropdownTriggerApi {
show: () => void;
close: () => void;
toggle: () => void;
setAnchorRef: (node: HTMLElement | null) => void;
setTriggerRef: (node: HTMLElement | null) => void;
surfaceId: string;
}
@@ -49,6 +50,7 @@ export function Dropdown({
const instanceId = useId();
const surfaceId = `${instanceId.replaceAll(":", "")}-surface`;
const [isOpen, setIsOpen] = useState(false);
const [anchorElement, setAnchorElement] = useState<HTMLElement | null>(null);
const [triggerElement, setTriggerElement] = useState<HTMLElement | null>(null);
const surfaceRef = useRef<HTMLDivElement>(null);
const [surfaceStyle, setSurfaceStyle] = useState<CSSProperties>({ visibility: "hidden" });
@@ -62,8 +64,9 @@ export function Dropdown({
}, [disabled]);
const updatePosition = useCallback(() => {
if (!triggerElement || !surfaceRef.current) return;
const anchor = triggerElement.getBoundingClientRect();
const anchorNode = anchorElement ?? triggerElement;
if (!anchorNode || !surfaceRef.current) return;
const anchor = anchorNode.getBoundingClientRect();
const measured = surfaceRef.current.getBoundingClientRect();
const surfaceWidth = typeof width === "number"
? width
@@ -83,7 +86,7 @@ export function Dropdown({
maxHeight: position.maxHeight,
visibility: "visible",
});
}, [minWidth, offset, placement, triggerElement, width]);
}, [anchorElement, minWidth, offset, placement, triggerElement, width]);
useLayoutEffect(() => {
if (!isOpen) return;
@@ -95,12 +98,14 @@ export function Dropdown({
const handlePointerDown = (event: PointerEvent) => {
const target = event.target;
if (!(target instanceof Node)) return;
if (triggerElement?.contains(target) || surfaceRef.current?.contains(target)) return;
if (anchorElement?.contains(target) || triggerElement?.contains(target) || surfaceRef.current?.contains(target)) return;
close();
};
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
close();
event.preventDefault();
event.stopPropagation();
triggerElement?.focus();
}
};
@@ -112,22 +117,22 @@ export function Dropdown({
window.dispatchEvent(new CustomEvent("nodedc-dropdown-open", { detail: { id: instanceId } }));
document.addEventListener("pointerdown", handlePointerDown);
document.addEventListener("keydown", handleKeyDown);
document.addEventListener("keydown", handleKeyDown, true);
window.addEventListener("nodedc-dropdown-open", handleOtherOpen as EventListener);
window.addEventListener("resize", handleViewportChange);
window.addEventListener("scroll", handleViewportChange, true);
return () => {
document.removeEventListener("pointerdown", handlePointerDown);
document.removeEventListener("keydown", handleKeyDown);
document.removeEventListener("keydown", handleKeyDown, true);
window.removeEventListener("nodedc-dropdown-open", handleOtherOpen as EventListener);
window.removeEventListener("resize", handleViewportChange);
window.removeEventListener("scroll", handleViewportChange, true);
};
}, [close, instanceId, isOpen, triggerElement, updatePosition]);
}, [anchorElement, close, instanceId, isOpen, triggerElement, updatePosition]);
return (
<span className={cn("nodedc-dropdown-anchor", className)}>
{trigger({ open: isOpen, show, close, toggle, setTriggerRef: setTriggerElement, surfaceId })}
{trigger({ open: isOpen, show, close, toggle, setAnchorRef: setAnchorElement, setTriggerRef: setTriggerElement, surfaceId })}
{isOpen && typeof document !== "undefined"
? createPortal(
<div
@@ -145,4 +150,3 @@ export function Dropdown({
</span>
);
}