fix(spreadsheet): popups show up and hide

This commit is contained in:
Elian Doran 2026-03-11 12:10:46 +02:00
parent dc7b91433b
commit aa2d8af15c
No known key found for this signature in database

View File

@ -43,6 +43,7 @@ function SpreadsheetEditor({ note, noteContext, readOnly }: TypeWidgetProps & {
useDarkMode(apiRef);
usePersistence(note, noteContext, apiRef, containerRef, readOnly);
useSearchIntegration(apiRef);
useFixRadixPortals();
// Focus the spreadsheet when the note is focused.
useTriliumEvent("focusOnDetail", () => {
@ -55,6 +56,53 @@ function SpreadsheetEditor({ note, noteContext, readOnly }: TypeWidgetProps & {
return <div ref={containerRef} className="spreadsheet" />;
}
/**
* Univer's design system uses Radix UI primitives whose DismissableLayer detects
* "outside" clicks/focus via document-level pointerdown/focusin listeners combined
* with a React capture-phase flag. In React, portal events bubble through the
* component tree so onPointerDownCapture fires on the DismissableLayer, setting an
* internal flag that suppresses the "outside" detection. With preact/compat, portal
* events don't bubble through the React tree, so the flag never gets set and Radix
* immediately dismisses popups.
*
* Radix dispatches cancelable custom events ("dismissableLayer.pointerDownOutside"
* and "dismissableLayer.focusOutside") on the original event target before calling
* onDismiss. The dismiss is skipped if defaultPrevented is true. This hook intercepts
* those custom events in the capture phase and prevents default when the target is
* inside a Radix portal, restoring the expected behavior.
*/
function useFixRadixPortals() {
useEffect(() => {
function isInsideUniverPortal(target: HTMLElement): boolean {
// Check for Radix content wrappers (covers most popover/menu portals).
if (target.closest("[data-radix-popper-content-wrapper]") || target.closest("[data-radix-menu-content]")) {
return true;
}
// Some Univer portals (e.g. nested color picker) don't use a Radix
// wrapper. Fall back to checking if the element has Univer classes.
if (target.className?.includes("univer-")) {
return true;
}
return false;
}
function preventDismiss(e: Event) {
if (e.target instanceof HTMLElement && isInsideUniverPortal(e.target)) {
e.preventDefault();
}
}
document.addEventListener("dismissableLayer.pointerDownOutside", preventDismiss, true);
document.addEventListener("dismissableLayer.focusOutside", preventDismiss, true);
return () => {
document.removeEventListener("dismissableLayer.pointerDownOutside", preventDismiss, true);
document.removeEventListener("dismissableLayer.focusOutside", preventDismiss, true);
};
}, []);
}
function useInitializeSpreadsheet(containerRef: MutableRef<HTMLDivElement | null>, apiRef: MutableRef<FUniver | undefined>, readOnly: boolean) {
useEffect(() => {
if (!containerRef.current) return;