mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-09-19 07:20:21 +02:00
fix popover not closable and font search auto focus
This commit is contained in:
@@ -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();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@@ -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>
|
||||||
|
@@ -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
|
||||||
|
@@ -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);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@@ -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}
|
||||||
|
@@ -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",
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user