diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx index ac6cd52bac0..13e4dc6ab16 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx @@ -1,5 +1,6 @@ 'use client' +import { useRef } from 'react' import { DropdownMenu, DropdownMenuContent, @@ -34,6 +35,18 @@ interface ContextMenuProps { onMarkAsUnread?: () => void onTogglePin?: () => void onRename?: () => void + /** + * Ref to the rename input rendered by the "Rename" action, if any. Radix's + * FocusScope defers its close-time focus teardown to a `setTimeout(0)`, which + * can run after the rename input's own mount-time `focus()`/`select()` and + * clobber the selection (the "rename deselects the text" bug). Focusing from + * `onCloseAutoFocus` runs synchronously inside that same deferred teardown, so + * it always wins the race regardless of scheduler timing. Only applied when + * this specific close was caused by selecting "Rename" (see + * `justSelectedRenameRef`) — an unrelated action closing the menu while an + * earlier rename is still live must not steal focus back into it. + */ + renameInputRef?: React.RefObject onCreate?: () => void onCreateFolder?: () => void onDuplicate?: () => void @@ -84,6 +97,7 @@ export function ContextMenu({ onMarkAsUnread, onTogglePin, onRename, + renameInputRef, onCreate, onCreateFolder, onDuplicate, @@ -132,6 +146,13 @@ export function ContextMenu({ (showUploadLogo && onUploadLogo) const hasCopySection = (showDuplicate && onDuplicate) || (showExport && onExport) + /** + * Only the "Rename" item should trigger the `onCloseAutoFocus` refocus below — + * an unrelated action (Delete, Duplicate, ...) closing this menu while a rename + * from an earlier interaction is still live must not steal focus back into it. + */ + const justSelectedRenameRef = useRef(false) + return ( !open && onClose()} modal={false}> @@ -152,7 +173,16 @@ export function ContextMenu({ side='bottom' sideOffset={4} className='max-h-[var(--radix-dropdown-menu-content-available-height,400px)]' - onCloseAutoFocus={(e) => e.preventDefault()} + onCloseAutoFocus={(e) => { + e.preventDefault() + const shouldFocusRenameInput = justSelectedRenameRef.current + justSelectedRenameRef.current = false + const input = shouldFocusRenameInput ? renameInputRef?.current : null + if (input) { + input.focus() + input.select() + } + }} > {showOpenInNewTab && onOpenInNewTab && ( { + justSelectedRenameRef.current = true onRename() onClose() }} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx index 377e562eadf..cc29b050118 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx @@ -575,6 +575,7 @@ export const FolderItem = memo(function FolderItem({ workspaceId, folder }: Fold menuRef={menuRef} onClose={closeMenu} onRename={handleStartEdit} + renameInputRef={inputRef} onCreate={handleCreateWorkflowInFolder} onCreateFolder={handleCreateFolderInFolder} onDuplicate={handleDuplicate} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx index d0c4b104b4e..fcc7d8a28b8 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx @@ -487,6 +487,7 @@ export const WorkflowItem = memo(function WorkflowItem({ onClose={closeMenu} onOpenInNewTab={handleOpenInNewTab} onRename={handleStartEdit} + renameInputRef={inputRef} onDuplicate={handleDuplicate} onExport={handleExport} onDelete={handleOpenDeleteModal} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx index 711d96af8db..dcdfefbacb4 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx @@ -113,6 +113,7 @@ function WorkspaceHeaderImpl({ const isContextMenuOpeningRef = useRef(false) const contextMenuClosedRef = useRef(true) const hasInputFocusedRef = useRef(false) + const renameInputRef = useRef(null) const [isMounted, setIsMounted] = useState(false) useEffect(() => { @@ -408,6 +409,7 @@ function WorkspaceHeaderImpl({ )} { + renameInputRef.current = el if (el && !hasInputFocusedRef.current) { hasInputFocusedRef.current = true el.focus() @@ -643,6 +645,7 @@ function WorkspaceHeaderImpl({ menuRef={contextMenuRef} onClose={closeContextMenu} onRename={handleRenameAction} + renameInputRef={renameInputRef} onDelete={handleDeleteAction} onLeave={handleLeaveAction} onUploadLogo={handleUploadLogoAction}