fix popover not closable and font search auto focus

This commit is contained in:
Ryan Di
2025-08-28 19:42:48 +10:00
parent 8325f4bd3a
commit 5e395f2027
7 changed files with 75 additions and 31 deletions

View File

@@ -1168,25 +1168,23 @@ export const actionChangeFontFamily = register({
}
}
setBatchedData({
openPopup: "fontFamily",
});
updateData({ ...batchedData, openPopup: "fontFamily" });
setBatchedData({});
} else {
// close immediately to avoid racing with other popovers opening
// close: clear openPopup if we're still the active popup
const data = {
openPopup:
appState.openPopup === "fontFamily"
? null
: appState.openPopup,
currentHoveredFontFamily: null,
cachedElements: new Map(cachedElementsRef.current),
resetAll: true,
} as ChangeFontFamilyData;
if (isUnmounted.current) {
updateData({ ...batchedData, ...data });
} else {
// apply immediately instead of batching
updateData({ ...batchedData, ...data });
setBatchedData({});
}
// apply immediately to avoid racing with other popovers opening
updateData({ ...batchedData, ...data });
setBatchedData({});
cachedElementsRef.current.clear();
}
}}

View File

@@ -413,6 +413,10 @@ export const CompactShapeActions = ({
return next;
});
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{resizeIcon}
</button>
@@ -460,8 +464,8 @@ export const CompactShapeActions = ({
<Popover.Root
open={appState.openPopup === "arrowProperties"}
onOpenChange={(open) => {
setAppState({ openPopup: open ? "arrowProperties" : null });
if (open) {
setAppState({ openPopup: "arrowProperties" });
setStrokePopoverOpen(false);
setOtherActionsPopoverOpen(false);
}
@@ -482,6 +486,10 @@ export const CompactShapeActions = ({
setAppState({ openPopup: "arrowProperties" });
}
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{(() => {
// Show an icon based on the current arrow type
@@ -518,9 +526,11 @@ export const CompactShapeActions = ({
container={container}
style={{ maxWidth: "13rem" }}
onClose={() => {
if (appState.openPopup === "arrowProperties") {
setAppState({ openPopup: null });
}
setAppState((prev: AppState) =>
prev.openPopup === "arrowProperties"
? { openPopup: null }
: null,
);
}}
>
{renderAction("changeArrowProperties")}
@@ -551,8 +561,8 @@ export const CompactShapeActions = ({
<Popover.Root
open={appState.openPopup === "textAlign"}
onOpenChange={(open) => {
setAppState({ openPopup: open ? "textAlign" : null });
if (open) {
setAppState({ openPopup: "textAlign" });
setStrokePopoverOpen(false);
setOtherActionsPopoverOpen(false);
}
@@ -573,6 +583,10 @@ export const CompactShapeActions = ({
setAppState({ openPopup: "textAlign" });
}
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{TextSizeIcon}
</button>
@@ -583,9 +597,11 @@ export const CompactShapeActions = ({
container={container}
style={{ maxWidth: "13rem" }}
onClose={() => {
if (appState.openPopup === "textAlign") {
setAppState({ openPopup: null });
}
setAppState((prev: AppState) =>
prev.openPopup === "textAlign"
? { openPopup: null }
: null,
);
}}
>
<div className="selected-shape-actions">
@@ -651,6 +667,10 @@ export const CompactShapeActions = ({
return next;
});
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{settingsPlusIcon}
</button>

View File

@@ -226,8 +226,14 @@ const ColorPickerTrigger = ({
onPointerDown={(e) => {
// use pointerdown so we run before outside-close logic
e.preventDefault();
e.stopPropagation();
onToggle();
}}
onClick={(e) => {
// suppress Radix default toggle to avoid double-toggle flicker
e.preventDefault();
e.stopPropagation();
}}
>
<div className="color-picker__button-outline">{!color && slashIcon}</div>
{compactMode && color && (
@@ -313,7 +319,7 @@ export const ColorPicker = ({
onToggle={() => {
// atomic switch: if another popup is open, close it first, then open this one next tick
if (appState.openPopup === type) {
// toggle off
// toggle off on same trigger
updateData({ openPopup: null });
} else if (appState.openPopup) {
// switching

View File

@@ -102,16 +102,19 @@ export const FontPicker = React.memo(
</div>
)}
{!compactMode && <ButtonSeparator />}
<Popover.Root open={isOpened} onOpenChange={onPopupChange}>
<Popover.Root open={isOpened} onOpenChange={() => {}}>
<FontPickerTrigger
selectedFontFamily={selectedFontFamily}
onTrigger={() => {
onTrigger={(e) => {
// suppress default to avoid double toggle
if (e && e.preventDefault) {
e.preventDefault();
}
if (isOpened) {
onPopupChange(false);
} else {
// switch from any open popup: close then open next tick
onPopupChange(false);
setTimeout(() => onPopupChange(true), 0);
onPopupChange(true);
}
}}
/>

View File

@@ -285,6 +285,7 @@ export const FontPickerList = React.memo(
onClose={onClose}
onPointerLeave={onLeave}
onKeyDown={onKeyDown}
preventAutoFocusOnTouch={true}
>
<QuickSearch
ref={inputRef}

View File

@@ -26,7 +26,17 @@ export const FontPickerTrigger = ({
return (
<Popover.Trigger asChild>
{/* Empty div as trigger so it's stretched 100% due to different button sizes */}
<div data-openpopup="fontFamily">
<div
data-openpopup="fontFamily"
onPointerDown={(e) => {
e.preventDefault();
onTrigger?.(e);
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<ButtonIcon
standalone
icon={TextIcon}
@@ -35,10 +45,8 @@ export const FontPickerTrigger = ({
testId={"font-family-show-fonts"}
active={isTriggerActive}
onClick={(e) => {
if (onTrigger) {
e.preventDefault();
onTrigger(e);
}
e.preventDefault();
e.stopPropagation();
}}
style={{
border: "none",

View File

@@ -17,6 +17,7 @@ interface PropertiesPopoverProps {
onPointerLeave?: React.PointerEventHandler<HTMLDivElement>;
onFocusOutside?: Popover.PopoverContentProps["onFocusOutside"];
onPointerDownOutside?: Popover.PopoverContentProps["onPointerDownOutside"];
preventAutoFocusOnTouch?: boolean;
}
export const PropertiesPopover = React.forwardRef<
@@ -34,6 +35,7 @@ export const PropertiesPopover = React.forwardRef<
onFocusOutside,
onPointerLeave,
onPointerDownOutside,
preventAutoFocusOnTouch = false,
},
ref,
) => {
@@ -64,6 +66,12 @@ export const PropertiesPopover = React.forwardRef<
onKeyDown={onKeyDown}
onFocusOutside={onFocusOutside}
onPointerDownOutside={onPointerDownOutside}
onOpenAutoFocus={(e) => {
// prevent auto-focus on touch devices to avoid keyboard popup
if (preventAutoFocusOnTouch && device.isTouchScreen) {
e.preventDefault();
}
}}
onCloseAutoFocus={(e) => {
e.stopPropagation();
// prevents focusing the trigger