diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx index bf43a55ba..b6e7aa4cb 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx @@ -38,7 +38,6 @@ const SCOPE_DESCRIPTIONS: Record = { 'https://www.googleapis.com/auth/gmail.modify': 'View and manage email messages', 'https://www.googleapis.com/auth/drive.file': 'View and manage Google Drive files', 'https://www.googleapis.com/auth/drive': 'Access all Google Drive files', - 'https://www.googleapis.com/auth/drive.readonly': 'View Google Drive files', 'https://www.googleapis.com/auth/calendar': 'View and manage calendar', 'https://www.googleapis.com/auth/userinfo.email': 'View email address', 'https://www.googleapis.com/auth/userinfo.profile': 'View basic profile info', diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/components/keyboard-navigation-handler.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/components/keyboard-navigation-handler.tsx index b0068e19e..aa1fb1df5 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/components/keyboard-navigation-handler.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/components/keyboard-navigation-handler.tsx @@ -29,7 +29,6 @@ const findFolderInNested = ( if (folderId === targetFolderId) { return nestedTag } - // Recursively search in nested children if (nestedTag.nestedChildren) { const found = findFolderInNested(nestedTag.nestedChildren, blockId, targetFolderId) if (found) return found @@ -67,7 +66,6 @@ const findFolderInfoForTag = ( nestedTag, } } - // Recursively search in nested children if (nestedTag.nestedChildren) { const found = findFolderInfoForTag(nestedTag.nestedChildren, targetTag, group) if (found) return found @@ -82,7 +80,6 @@ const findFolderInfoForTag = ( */ const isChildOfAnyFolder = (nestedTags: NestedTag[], tag: string): boolean => { for (const nestedTag of nestedTags) { - // Check in leaf children if (nestedTag.children) { for (const child of nestedTag.children) { if (child.fullTag === tag) { @@ -90,14 +87,12 @@ const isChildOfAnyFolder = (nestedTags: NestedTag[], tag: string): boolean => { } } } - // Check if this is a nested folder's parent tag (should be hidden at root) if (nestedTag.nestedChildren) { for (const nestedChild of nestedTag.nestedChildren) { if (nestedChild.parentTag === tag) { return true } } - // Recursively check deeper nested children if (isChildOfAnyFolder(nestedTag.nestedChildren, tag)) { return true } @@ -122,14 +117,11 @@ export const KeyboardNavigationHandler: React.FC const nestedPath = nestedNav?.nestedPath ?? [] if (isInFolder && currentFolder) { - // Determine the current folder to show based on nested navigation let currentNestedTag: NestedTag | null = null if (nestedPath.length > 0) { - // We're in nested navigation - use the deepest nested tag currentNestedTag = nestedPath[nestedPath.length - 1] } else { - // At base folder level - find the folder from currentFolder ID for (const group of nestedBlockTagGroups) { const folder = findFolderInNested(group.nestedTags, group.blockId, currentFolder) if (folder) { @@ -140,7 +132,6 @@ export const KeyboardNavigationHandler: React.FC } if (currentNestedTag) { - // First, add the parent tag itself (so it's navigable as the first item) if (currentNestedTag.parentTag) { const parentIdx = flatTagList.findIndex( (item) => item.tag === currentNestedTag!.parentTag @@ -149,7 +140,6 @@ export const KeyboardNavigationHandler: React.FC indices.push(parentIdx) } } - // Add all leaf children if (currentNestedTag.children) { for (const child of currentNestedTag.children) { const idx = flatTagList.findIndex((item) => item.tag === child.fullTag) @@ -158,7 +148,6 @@ export const KeyboardNavigationHandler: React.FC } } } - // Add nested children parent tags (for subfolder navigation) if (currentNestedTag.nestedChildren) { for (const nestedChild of currentNestedTag.nestedChildren) { if (nestedChild.parentTag) { @@ -171,12 +160,9 @@ export const KeyboardNavigationHandler: React.FC } } } else { - // We're at root level, show all non-child items - // (variables and parent tags, but not their children) for (let i = 0; i < flatTagList.length; i++) { const tag = flatTagList[i].tag - // Check if this is a child of a parent folder let isChild = false for (const group of nestedBlockTagGroups) { if (isChildOfAnyFolder(group.nestedTags, tag)) { @@ -194,15 +180,11 @@ export const KeyboardNavigationHandler: React.FC return indices }, [isInFolder, currentFolder, flatTagList, nestedBlockTagGroups, nestedNav]) - // Track nested path length for dependency const nestedPathLength = nestedNav?.nestedPath.length ?? 0 - // Auto-select first visible item when entering/exiting folders or navigating nested - // This effect only runs when folder navigation state changes, not on every render useEffect(() => { if (!visible || visibleIndices.length === 0) return - // Select first visible item when entering a folder or navigating nested setSelectedIndex(visibleIndices[0]) // eslint-disable-next-line react-hooks/exhaustive-deps }, [visible, isInFolder, currentFolder, nestedPathLength]) @@ -237,7 +219,6 @@ export const KeyboardNavigationHandler: React.FC } | null = null if (selected) { - // Find if selected tag can be expanded (is a folder) for (const group of nestedBlockTagGroups) { const folderInfo = findFolderInfoForTag(group.nestedTags, selected.tag, group) if (folderInfo) { @@ -278,8 +259,6 @@ export const KeyboardNavigationHandler: React.FC e.preventDefault() e.stopPropagation() if (selected && selectedIndex >= 0 && selectedIndex < flatTagList.length) { - // Always select the tag, even for folders - // Use Arrow Right to navigate into folders handleTagSelect(selected.tag, selected.group) } break @@ -288,10 +267,8 @@ export const KeyboardNavigationHandler: React.FC e.preventDefault() e.stopPropagation() if (isInFolder && nestedNav) { - // Already in a folder - use nested navigation to go deeper nestedNav.navigateIn(currentFolderInfo.nestedTag, currentFolderInfo.group) } else { - // At root level - open the folder normally openFolderWithSelection( currentFolderInfo.id, currentFolderInfo.title, @@ -305,13 +282,9 @@ export const KeyboardNavigationHandler: React.FC if (isInFolder) { e.preventDefault() e.stopPropagation() - // Try to navigate back in nested path first if (nestedNav?.navigateBack()) { - // Successfully navigated back one level in nested navigation - // Selection will be handled by the auto-select effect return } - // At root folder level, close the folder entirely closeFolder() let firstRootIndex = 0 for (let i = 0; i < flatTagList.length; i++) { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx index b8bef1a41..70acdb7fa 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx @@ -340,12 +340,10 @@ interface TagTreeNode { const buildNestedTagTree = (tags: string[], blockName: string): NestedTag[] => { const root: TagTreeNode = { key: 'root', children: new Map() } - // Build tree from tags for (const tag of tags) { const parts = tag.split('.') - if (parts.length < 2) continue // Skip root-only tags + if (parts.length < 2) continue - // Skip the blockname part, start from the first property const pathParts = parts.slice(1) let current = root @@ -359,7 +357,6 @@ const buildNestedTagTree = (tags: string[], blockName: string): NestedTag[] => { } const node = current.children.get(part)! - // If this is the last part, store the full tag if (i === pathParts.length - 1) { node.fullTag = tag } @@ -367,7 +364,6 @@ const buildNestedTagTree = (tags: string[], blockName: string): NestedTag[] => { } } - // Convert tree to NestedTag array const convertToNestedTags = ( node: TagTreeNode, parentPath: string, @@ -380,17 +376,14 @@ const buildNestedTagTree = (tags: string[], blockName: string): NestedTag[] => { const parentTag = `${blockPrefix}.${currentPath}` if (child.children.size === 0) { - // Leaf node result.push({ key: currentPath, display: key, fullTag: child.fullTag || parentTag, }) } else { - // Folder node - may have both leaf value and children const nestedChildren = convertToNestedTags(child, currentPath, blockPrefix) - // Separate leaf children from nested folders const leafChildren: NestedTagChild[] = [] const folders: NestedTag[] = [] @@ -483,10 +476,8 @@ const FolderContentsInner: React.FC = ({ nestedTag, onNavigateIn, }) => { - // Get the current folder based on nested path const currentNestedTag = nestedPath.length > 0 ? nestedPath[nestedPath.length - 1] : nestedTag - // Find parent tag index for highlighting const parentTagIndex = currentNestedTag.parentTag ? flatTagList.findIndex((item) => item.tag === currentNestedTag.parentTag) : -1 @@ -609,7 +600,6 @@ const FolderContents: React.FC = (props) => { const nestedPath = nestedNav?.nestedPath ?? [] - // Register this folder when it becomes active useEffect(() => { if (nestedNav && currentFolder) { const folderId = `${props.group.blockId}-${props.nestedTag.key}` @@ -647,7 +637,6 @@ const NestedTagRenderer: React.FC = ({ const hasChildren = nestedTag.children && nestedTag.children.length > 0 const hasNestedChildren = nestedTag.nestedChildren && nestedTag.nestedChildren.length > 0 - // If this tag has children (leaf or nested), render as a folder if (hasChildren || hasNestedChildren) { const folderId = `${group.blockId}-${nestedTag.key}` @@ -863,7 +852,6 @@ export const TagDropdown: React.FC = ({ const [selectedIndex, setSelectedIndex] = useState(0) const itemRefs = useRef>(new Map()) - // Nested navigation state - supports unlimited nesting depth const [nestedPath, setNestedPath] = useState([]) const baseFolderRef = useRef<{ id: string @@ -1578,31 +1566,25 @@ export const TagDropdown: React.FC = ({ list.push({ tag }) }) - // Recursively flatten nested tags const flattenNestedTag = (nestedTag: NestedTag, group: BlockTagGroup, rootTag: string) => { - // Skip if this is the root tag (already added) if (nestedTag.fullTag === rootTag) { return } - // Add parent tag for folders if (nestedTag.parentTag) { list.push({ tag: nestedTag.parentTag, group }) } - // Add the tag itself if it's a leaf if (nestedTag.fullTag && !nestedTag.children && !nestedTag.nestedChildren) { list.push({ tag: nestedTag.fullTag, group }) } - // Add leaf children if (nestedTag.children) { nestedTag.children.forEach((child) => { list.push({ tag: child.fullTag, group }) }) } - // Recursively process nested children if (nestedTag.nestedChildren) { nestedTag.nestedChildren.forEach((nestedChild) => { flattenNestedTag(nestedChild, group, rootTag) @@ -1727,15 +1709,12 @@ export const TagDropdown: React.FC = ({ [inputValue, cursorPosition, workflowVariables, onSelect, onClose, getMergedSubBlocks] ) - // Keep ref updated for nested navigation handleTagSelectRef.current = handleTagSelect - // Get popover context for nested navigation (will be available inside Popover) const popoverContextRef = useRef<{ openFolder: (id: string, title: string, onLoad?: () => void, onSelect?: () => void) => void } | null>(null) - // Create nested navigation context value const nestedNavigationValue = useMemo( () => ({ nestedPath, @@ -1745,12 +1724,10 @@ export const TagDropdown: React.FC = ({ setNestedPath((prev) => [...prev, tag]) - // Reset scroll to top when navigating into a folder if (scrollAreaRef.current) { scrollAreaRef.current.scrollTop = 0 } - // Update popover's folder title to show current nested folder const selectionCallback = () => { if (tag.parentTag && handleTagSelectRef.current) { handleTagSelectRef.current(tag.parentTag, group) @@ -1772,7 +1749,6 @@ export const TagDropdown: React.FC = ({ setNestedPath(newPath) if (newPath.length === 0) { - // Going back to root folder level const selectionCallback = () => { if (baseFolder.baseTag.parentTag && handleTagSelectRef.current) { handleTagSelectRef.current(baseFolder.baseTag.parentTag, baseFolder.group) @@ -1785,7 +1761,6 @@ export const TagDropdown: React.FC = ({ selectionCallback ) } else { - // Going back to a nested folder const parentTag = newPath[newPath.length - 1] const selectionCallback = () => { if (parentTag.parentTag && handleTagSelectRef.current) { @@ -1803,7 +1778,6 @@ export const TagDropdown: React.FC = ({ }, registerFolder: (folderId, folderTitle, baseTag, group) => { baseFolderRef.current = { id: folderId, title: folderTitle, baseTag, group } - // Reset scroll to top when entering a folder if (scrollAreaRef.current) { scrollAreaRef.current.scrollTop = 0 } @@ -1812,7 +1786,6 @@ export const TagDropdown: React.FC = ({ [nestedPath] ) - // Reset nested path when popover closes useEffect(() => { if (!visible) { setNestedPath([]) 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 56b8948f6..b03138de5 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 @@ -34,16 +34,13 @@ function ColorGrid({ const gridRef = useRef(null) const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]) - // Focus the grid when entering the folder useEffect(() => { if (isInFolder && gridRef.current) { - // Find the currently selected color or default to first const selectedIndex = WORKFLOW_COLORS.findIndex( ({ color }) => color.toLowerCase() === hexInput.toLowerCase() ) const initialIndex = selectedIndex >= 0 ? selectedIndex : 0 setFocusedIndex(initialIndex) - // Small delay to let the folder animation complete setTimeout(() => { buttonRefs.current[initialIndex]?.focus() }, 50) diff --git a/apps/sim/components/emcn/components/popover/popover.tsx b/apps/sim/components/emcn/components/popover/popover.tsx index 9b99ce2df..764c1f5ae 100644 --- a/apps/sim/components/emcn/components/popover/popover.tsx +++ b/apps/sim/components/emcn/components/popover/popover.tsx @@ -433,17 +433,14 @@ const PopoverContent = React.forwardRef< const effectiveSideOffset = sideOffset ?? (side === 'top' ? 20 : 14) - // Switch to mouse mode when mouse moves const handleMouseMove = React.useCallback(() => { if (context?.isKeyboardNav) { context.setKeyboardNav(false) } }, [context]) - // Track menu items for keyboard navigation const contentRef = React.useRef(null) - // Merge refs const mergedRef = React.useCallback( (node: HTMLDivElement | null) => { contentRef.current = node @@ -456,12 +453,10 @@ const PopoverContent = React.forwardRef< [ref] ) - // Keyboard navigation handler - use window listener to ensure events are captured React.useEffect(() => { if (!context) return const handleKeyDown = (e: KeyboardEvent) => { - // Get content element inside handler to ensure it's current const content = contentRef.current if (!content) return @@ -477,7 +472,6 @@ const PopoverContent = React.forwardRef< e.preventDefault() e.stopPropagation() context.setKeyboardNav(true) - // If no selection, start at first item; otherwise move down if (currentIndex < 0) { context.setSelectedIndex(0) } else { @@ -488,7 +482,6 @@ const PopoverContent = React.forwardRef< e.preventDefault() e.stopPropagation() context.setKeyboardNav(true) - // If no selection, start at last item; otherwise move up if (currentIndex < 0) { context.setSelectedIndex(items.length - 1) } else { @@ -509,12 +502,10 @@ const PopoverContent = React.forwardRef< } } - // Use capture phase to ensure we get the event window.addEventListener('keydown', handleKeyDown, true) return () => window.removeEventListener('keydown', handleKeyDown, true) }, [context]) - // Scroll selected item into view React.useEffect(() => { const content = contentRef.current if (!content || !context?.isKeyboardNav || context.selectedIndex < 0) return @@ -684,7 +675,6 @@ const PopoverItem = React.forwardRef( const itemRef = React.useRef(null) const [itemIndex, setItemIndex] = React.useState(-1) - // Merge refs const mergedRef = React.useCallback( (node: HTMLDivElement | null) => { itemRef.current = node @@ -697,7 +687,6 @@ const PopoverItem = React.forwardRef( [ref] ) - // Calculate item index on mount and when siblings change React.useEffect(() => { if (!itemRef.current) return const content = itemRef.current.closest('[data-radix-popper-content-wrapper]') @@ -718,9 +707,7 @@ const PopoverItem = React.forwardRef( } const handleMouseEnter = (e: React.MouseEvent) => { - // Clear last hovered item to close any open hover submenus context?.setLastHoveredItem(null) - // Update selected index to this item's position if (itemIndex >= 0 && context) { context.setSelectedIndex(itemIndex) } @@ -863,10 +850,8 @@ const PopoverFolder = React.forwardRef( const triggerRef = React.useRef(null) const [itemIndex, setItemIndex] = React.useState(-1) - // Submenu is open when this folder is the last hovered item (for expandOnHover mode) const isHoverOpen = expandOnHover && lastHoveredItem === id - // Merge refs const mergedRef = React.useCallback( (node: HTMLDivElement | null) => { triggerRef.current = node @@ -879,7 +864,6 @@ const PopoverFolder = React.forwardRef( [ref] ) - // Calculate item index on mount React.useEffect(() => { if (!triggerRef.current) return const content = triggerRef.current.closest('[data-radix-popper-content-wrapper]') @@ -889,9 +873,7 @@ const PopoverFolder = React.forwardRef( setItemIndex(index) }, []) - // If we're in a folder and this isn't the current one, hide if (isInFolder && currentFolder !== id) return null - // If this folder is open via click (inline mode), render children directly if (currentFolder === id) return <>{children} const handleClickOpen = () => { @@ -901,27 +883,23 @@ const PopoverFolder = React.forwardRef( const handleClick = (e: React.MouseEvent) => { e.stopPropagation() if (expandOnHover) { - // In hover mode, clicking opens inline and clears hover state setLastHoveredItem(null) } handleClickOpen() } const handleMouseEnter = () => { - // Update selected index for keyboard navigation if (itemIndex >= 0) { setSelectedIndex(itemIndex) } if (!expandOnHover) return - // Calculate position for submenu if (triggerRef.current) { const rect = triggerRef.current.getBoundingClientRect() const parentPopover = triggerRef.current.closest('[data-radix-popper-content-wrapper]') const parentRect = parentPopover?.getBoundingClientRect() - // Position to the right of the parent popover with a small gap setSubmenuPosition({ top: rect.top, left: parentRect ? parentRect.right + 4 : rect.right + 4, @@ -932,7 +910,6 @@ const PopoverFolder = React.forwardRef( onOpen?.() } - // Determine if this folder is active (for keyboard navigation highlight) const isActive = active !== undefined ? active : itemIndex >= 0 && selectedIndex === itemIndex // Suppress hover when in keyboard mode to prevent dual highlights