Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { useRef } from 'react'
import {
DropdownMenu,
DropdownMenuContent,
Expand Down Expand Up @@ -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<HTMLInputElement | null>
onCreate?: () => void
onCreateFolder?: () => void
onDuplicate?: () => void
Expand Down Expand Up @@ -84,6 +97,7 @@ export function ContextMenu({
onMarkAsUnread,
onTogglePin,
onRename,
renameInputRef,
onCreate,
onCreateFolder,
onDuplicate,
Expand Down Expand Up @@ -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 (
<DropdownMenu open={isOpen} onOpenChange={(open) => !open && onClose()} modal={false}>
<DropdownMenuTrigger asChild>
Expand All @@ -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 && (
<DropdownMenuItem
Expand Down Expand Up @@ -210,6 +240,7 @@ export function ContextMenu({
<DropdownMenuItem
disabled={disableRename}
onSelect={() => {
justSelectedRenameRef.current = true
onRename()
onClose()
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ export const WorkflowItem = memo(function WorkflowItem({
onClose={closeMenu}
onOpenInNewTab={handleOpenInNewTab}
onRename={handleStartEdit}
renameInputRef={inputRef}
onDuplicate={handleDuplicate}
onExport={handleExport}
onDelete={handleOpenDeleteModal}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ function WorkspaceHeaderImpl({
const isContextMenuOpeningRef = useRef(false)
const contextMenuClosedRef = useRef(true)
const hasInputFocusedRef = useRef(false)
const renameInputRef = useRef<HTMLInputElement | null>(null)

const [isMounted, setIsMounted] = useState(false)
useEffect(() => {
Expand Down Expand Up @@ -408,6 +409,7 @@ function WorkspaceHeaderImpl({
)}
<input
ref={(el) => {
renameInputRef.current = el
if (el && !hasInputFocusedRef.current) {
hasInputFocusedRef.current = true
el.focus()
Expand Down Expand Up @@ -643,6 +645,7 @@ function WorkspaceHeaderImpl({
menuRef={contextMenuRef}
onClose={closeContextMenu}
onRename={handleRenameAction}
renameInputRef={renameInputRef}
onDelete={handleDeleteAction}
onLeave={handleLeaveAction}
onUploadLogo={handleUploadLogoAction}
Expand Down
Loading