This commit is contained in:
waleed
2026-01-18 12:41:19 -08:00
parent 508807465c
commit 3554d7f744
5 changed files with 1 additions and 82 deletions

View File

@@ -38,7 +38,6 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
'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',

View File

@@ -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<KeyboardNavigationHandlerProps>
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<KeyboardNavigationHandlerProps>
}
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<KeyboardNavigationHandlerProps>
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<KeyboardNavigationHandlerProps>
}
}
}
// 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<KeyboardNavigationHandlerProps>
}
}
} 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<KeyboardNavigationHandlerProps>
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<KeyboardNavigationHandlerProps>
} | 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<KeyboardNavigationHandlerProps>
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<KeyboardNavigationHandlerProps>
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<KeyboardNavigationHandlerProps>
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++) {

View File

@@ -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<FolderContentsProps> = ({
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<NestedTagRendererProps> = (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<NestedTagRendererProps> = ({
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<TagDropdownProps> = ({
const [selectedIndex, setSelectedIndex] = useState(0)
const itemRefs = useRef<Map<number, HTMLElement>>(new Map())
// Nested navigation state - supports unlimited nesting depth
const [nestedPath, setNestedPath] = useState<NestedTag[]>([])
const baseFolderRef = useRef<{
id: string
@@ -1578,31 +1566,25 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
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<TagDropdownProps> = ({
[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<NestedNavigationContextValue>(
() => ({
nestedPath,
@@ -1745,12 +1724,10 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
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<TagDropdownProps> = ({
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<TagDropdownProps> = ({
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<TagDropdownProps> = ({
},
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<TagDropdownProps> = ({
[nestedPath]
)
// Reset nested path when popover closes
useEffect(() => {
if (!visible) {
setNestedPath([])

View File

@@ -34,16 +34,13 @@ function ColorGrid({
const gridRef = useRef<HTMLDivElement>(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)

View File

@@ -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<HTMLDivElement>(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<HTMLDivElement, PopoverItemProps>(
const itemRef = React.useRef<HTMLDivElement>(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<HTMLDivElement, PopoverItemProps>(
[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<HTMLDivElement, PopoverItemProps>(
}
const handleMouseEnter = (e: React.MouseEvent<HTMLDivElement>) => {
// 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<HTMLDivElement, PopoverFolderProps>(
const triggerRef = React.useRef<HTMLDivElement>(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<HTMLDivElement, PopoverFolderProps>(
[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<HTMLDivElement, PopoverFolderProps>(
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<HTMLDivElement, PopoverFolderProps>(
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<HTMLDivElement, PopoverFolderProps>(
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