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({ updateData({ ...batchedData, openPopup: "fontFamily" });
openPopup: "fontFamily", setBatchedData({});
});
} else { } else {
// close immediately to avoid racing with other popovers opening // close: clear openPopup if we're still the active popup
const data = { const data = {
openPopup:
appState.openPopup === "fontFamily"
? null
: appState.openPopup,
currentHoveredFontFamily: null, currentHoveredFontFamily: null,
cachedElements: new Map(cachedElementsRef.current), cachedElements: new Map(cachedElementsRef.current),
resetAll: true, resetAll: true,
} as ChangeFontFamilyData; } as ChangeFontFamilyData;
if (isUnmounted.current) { // apply immediately to avoid racing with other popovers opening
updateData({ ...batchedData, ...data }); updateData({ ...batchedData, ...data });
} else { setBatchedData({});
// apply immediately instead of batching
updateData({ ...batchedData, ...data });
setBatchedData({});
}
cachedElementsRef.current.clear(); cachedElementsRef.current.clear();
} }
}} }}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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