Skip to content

Commit f85def7

Browse files
committed
remove smooth scroll in tag drop
1 parent b300192 commit f85def7

File tree

2 files changed

+180
-54
lines changed

2 files changed

+180
-54
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/components/keyboard-navigation-handler.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ const findFolderInfoForTag = (
5353
nestedTag: NestedTag
5454
} | null => {
5555
for (const nestedTag of nestedTags) {
56-
// Check if this tag is a folder (has children or nestedChildren)
5756
if (
5857
nestedTag.parentTag === targetTag &&
5958
(nestedTag.children?.length || nestedTag.nestedChildren?.length)
@@ -228,18 +227,34 @@ export const KeyboardNavigationHandler: React.FC<KeyboardNavigationHandlerProps>
228227
}
229228
}
230229

230+
const scrollIntoView = () => {
231+
setTimeout(() => {
232+
const selectedItem = document.querySelector<HTMLElement>(
233+
'[data-radix-popper-content-wrapper] [aria-selected="true"]'
234+
)
235+
if (selectedItem) {
236+
selectedItem.scrollIntoView({ behavior: 'auto', block: 'nearest' })
237+
}
238+
}, 0)
239+
}
240+
231241
switch (e.key) {
232242
case 'ArrowDown':
233243
e.preventDefault()
234244
e.stopPropagation()
235245
setKeyboardNav(true)
236246
if (visibleIndices.length > 0) {
237247
const currentVisibleIndex = visibleIndices.indexOf(selectedIndex)
248+
let newIndex: number
238249
if (currentVisibleIndex === -1) {
239-
setSelectedIndex(visibleIndices[0])
250+
newIndex = visibleIndices[0]
240251
} else if (currentVisibleIndex < visibleIndices.length - 1) {
241-
setSelectedIndex(visibleIndices[currentVisibleIndex + 1])
252+
newIndex = visibleIndices[currentVisibleIndex + 1]
253+
} else {
254+
newIndex = visibleIndices[0]
242255
}
256+
setSelectedIndex(newIndex)
257+
scrollIntoView()
243258
}
244259
break
245260
case 'ArrowUp':
@@ -248,11 +263,16 @@ export const KeyboardNavigationHandler: React.FC<KeyboardNavigationHandlerProps>
248263
setKeyboardNav(true)
249264
if (visibleIndices.length > 0) {
250265
const currentVisibleIndex = visibleIndices.indexOf(selectedIndex)
266+
let newIndex: number
251267
if (currentVisibleIndex === -1) {
252-
setSelectedIndex(visibleIndices[0])
268+
newIndex = visibleIndices[visibleIndices.length - 1]
253269
} else if (currentVisibleIndex > 0) {
254-
setSelectedIndex(visibleIndices[currentVisibleIndex - 1])
270+
newIndex = visibleIndices[currentVisibleIndex - 1]
271+
} else {
272+
newIndex = visibleIndices[visibleIndices.length - 1]
255273
}
274+
setSelectedIndex(newIndex)
275+
scrollIntoView()
256276
}
257277
break
258278
case 'Enter':

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx

Lines changed: 155 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ const FolderContentsInner: React.FC<FolderContentsProps> = ({
476476
nestedTag,
477477
onNavigateIn,
478478
}) => {
479+
const { isKeyboardNav, setKeyboardNav } = usePopoverContext()
479480
const currentNestedTag = nestedPath.length > 0 ? nestedPath[nestedPath.length - 1] : nestedTag
480481

481482
const parentTagIndex = currentNestedTag.parentTag
@@ -489,6 +490,9 @@ const FolderContentsInner: React.FC<FolderContentsProps> = ({
489490
<PopoverItem
490491
active={parentTagIndex === selectedIndex && parentTagIndex >= 0}
491492
onMouseEnter={() => {
493+
// Skip selection update during keyboard navigation to prevent scroll-triggered selection changes
494+
if (isKeyboardNav) return
495+
setKeyboardNav(false)
492496
if (parentTagIndex >= 0) setSelectedIndex(parentTagIndex)
493497
}}
494498
onMouseDown={(e) => {
@@ -533,6 +537,8 @@ const FolderContentsInner: React.FC<FolderContentsProps> = ({
533537
key={child.key}
534538
active={childGlobalIndex === selectedIndex && childGlobalIndex >= 0}
535539
onMouseEnter={() => {
540+
if (isKeyboardNav) return
541+
setKeyboardNav(false)
536542
if (childGlobalIndex >= 0) setSelectedIndex(childGlobalIndex)
537543
}}
538544
onMouseDown={(e) => {
@@ -567,6 +573,8 @@ const FolderContentsInner: React.FC<FolderContentsProps> = ({
567573
key={`${group.blockId}-${nestedChild.key}`}
568574
active={parentGlobalIndex === selectedIndex && parentGlobalIndex >= 0}
569575
onMouseEnter={() => {
576+
if (isKeyboardNav) return
577+
setKeyboardNav(false)
570578
if (parentGlobalIndex >= 0) setSelectedIndex(parentGlobalIndex)
571579
}}
572580
onMouseDown={(e) => {
@@ -634,6 +642,7 @@ const NestedTagRenderer: React.FC<NestedTagRendererProps> = ({
634642
blocks,
635643
getMergedSubBlocks,
636644
}) => {
645+
const { isKeyboardNav, setKeyboardNav } = usePopoverContext()
637646
const hasChildren = nestedTag.children && nestedTag.children.length > 0
638647
const hasNestedChildren = nestedTag.nestedChildren && nestedTag.nestedChildren.length > 0
639648

@@ -656,6 +665,8 @@ const NestedTagRenderer: React.FC<NestedTagRendererProps> = ({
656665
}
657666
}}
658667
onMouseEnter={() => {
668+
if (isKeyboardNav) return
669+
setKeyboardNav(false)
659670
if (parentGlobalIndex >= 0) {
660671
setSelectedIndex(parentGlobalIndex)
661672
}
@@ -725,6 +736,8 @@ const NestedTagRenderer: React.FC<NestedTagRendererProps> = ({
725736
rootOnly
726737
active={globalIndex === selectedIndex && globalIndex >= 0}
727738
onMouseEnter={() => {
739+
if (isKeyboardNav) return
740+
setKeyboardNav(false)
728741
if (globalIndex >= 0) setSelectedIndex(globalIndex)
729742
}}
730743
onMouseDown={(e) => {
@@ -750,6 +763,126 @@ const NestedTagRenderer: React.FC<NestedTagRendererProps> = ({
750763
)
751764
}
752765

766+
/**
767+
* Hook to get mouse enter handler that respects keyboard navigation mode.
768+
* Returns a handler that only updates selection if not in keyboard mode.
769+
*/
770+
const useKeyboardAwareMouseEnter = (
771+
setSelectedIndex: (index: number) => void
772+
): ((index: number) => void) => {
773+
const { isKeyboardNav, setKeyboardNav } = usePopoverContext()
774+
775+
return useCallback(
776+
(index: number) => {
777+
if (isKeyboardNav) return
778+
setKeyboardNav(false)
779+
if (index >= 0) setSelectedIndex(index)
780+
},
781+
[isKeyboardNav, setKeyboardNav, setSelectedIndex]
782+
)
783+
}
784+
785+
/**
786+
* Wrapper for variable tag items that has access to popover context
787+
*/
788+
const VariableTagItem: React.FC<{
789+
tag: string
790+
globalIndex: number
791+
selectedIndex: number
792+
setSelectedIndex: (index: number) => void
793+
handleTagSelect: (tag: string) => void
794+
itemRefs: React.RefObject<Map<number, HTMLElement>>
795+
variableInfo: { type: string; id: string } | null
796+
}> = ({
797+
tag,
798+
globalIndex,
799+
selectedIndex,
800+
setSelectedIndex,
801+
handleTagSelect,
802+
itemRefs,
803+
variableInfo,
804+
}) => {
805+
const handleMouseEnter = useKeyboardAwareMouseEnter(setSelectedIndex)
806+
807+
return (
808+
<PopoverItem
809+
key={tag}
810+
rootOnly
811+
active={globalIndex === selectedIndex && globalIndex >= 0}
812+
onMouseEnter={() => handleMouseEnter(globalIndex)}
813+
onMouseDown={(e) => {
814+
e.preventDefault()
815+
e.stopPropagation()
816+
handleTagSelect(tag)
817+
}}
818+
ref={(el) => {
819+
if (el && globalIndex >= 0) {
820+
itemRefs.current?.set(globalIndex, el)
821+
}
822+
}}
823+
>
824+
<span className='flex-1 truncate'>
825+
{tag.startsWith(TAG_PREFIXES.VARIABLE) ? tag.substring(TAG_PREFIXES.VARIABLE.length) : tag}
826+
</span>
827+
{variableInfo && (
828+
<span className='ml-auto text-[10px] text-[var(--text-muted-inverse)]'>
829+
{variableInfo.type}
830+
</span>
831+
)}
832+
</PopoverItem>
833+
)
834+
}
835+
836+
/**
837+
* Wrapper for block root tag items that has access to popover context
838+
*/
839+
const BlockRootTagItem: React.FC<{
840+
rootTag: string
841+
rootTagGlobalIndex: number
842+
selectedIndex: number
843+
setSelectedIndex: (index: number) => void
844+
handleTagSelect: (tag: string, group?: BlockTagGroup) => void
845+
itemRefs: React.RefObject<Map<number, HTMLElement>>
846+
group: BlockTagGroup
847+
tagIcon: string | React.ComponentType<{ className?: string }>
848+
blockColor: string
849+
blockName: string
850+
}> = ({
851+
rootTag,
852+
rootTagGlobalIndex,
853+
selectedIndex,
854+
setSelectedIndex,
855+
handleTagSelect,
856+
itemRefs,
857+
group,
858+
tagIcon,
859+
blockColor,
860+
blockName,
861+
}) => {
862+
const handleMouseEnter = useKeyboardAwareMouseEnter(setSelectedIndex)
863+
864+
return (
865+
<PopoverItem
866+
rootOnly
867+
active={rootTagGlobalIndex === selectedIndex && rootTagGlobalIndex >= 0}
868+
onMouseEnter={() => handleMouseEnter(rootTagGlobalIndex)}
869+
onMouseDown={(e) => {
870+
e.preventDefault()
871+
e.stopPropagation()
872+
handleTagSelect(rootTag, group)
873+
}}
874+
ref={(el) => {
875+
if (el && rootTagGlobalIndex >= 0) {
876+
itemRefs.current?.set(rootTagGlobalIndex, el)
877+
}
878+
}}
879+
>
880+
<TagIcon icon={tagIcon} color={blockColor} />
881+
<span className='flex-1 truncate font-medium'>{blockName}</span>
882+
</PopoverItem>
883+
)
884+
}
885+
753886
/**
754887
* Helper component to capture popover context for nested navigation
755888
*/
@@ -1613,7 +1746,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
16131746
const element = itemRefs.current.get(selectedIndex)
16141747
if (element) {
16151748
element.scrollIntoView({
1616-
behavior: 'smooth',
1749+
behavior: 'auto',
16171750
block: 'nearest',
16181751
})
16191752
}
@@ -1888,35 +2021,16 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
18882021
const globalIndex = flatTagList.findIndex((item) => item.tag === tag)
18892022

18902023
return (
1891-
<PopoverItem
2024+
<VariableTagItem
18922025
key={tag}
1893-
rootOnly
1894-
active={globalIndex === selectedIndex && globalIndex >= 0}
1895-
onMouseEnter={() => {
1896-
if (globalIndex >= 0) setSelectedIndex(globalIndex)
1897-
}}
1898-
onMouseDown={(e) => {
1899-
e.preventDefault()
1900-
e.stopPropagation()
1901-
handleTagSelect(tag)
1902-
}}
1903-
ref={(el) => {
1904-
if (el && globalIndex >= 0) {
1905-
itemRefs.current.set(globalIndex, el)
1906-
}
1907-
}}
1908-
>
1909-
<span className='flex-1 truncate'>
1910-
{tag.startsWith(TAG_PREFIXES.VARIABLE)
1911-
? tag.substring(TAG_PREFIXES.VARIABLE.length)
1912-
: tag}
1913-
</span>
1914-
{variableInfo && (
1915-
<span className='ml-auto text-[10px] text-[var(--text-muted-inverse)]'>
1916-
{variableInfo.type}
1917-
</span>
1918-
)}
1919-
</PopoverItem>
2026+
tag={tag}
2027+
globalIndex={globalIndex}
2028+
selectedIndex={selectedIndex}
2029+
setSelectedIndex={setSelectedIndex}
2030+
handleTagSelect={handleTagSelect}
2031+
itemRefs={itemRefs}
2032+
variableInfo={variableInfo}
2033+
/>
19202034
)
19212035
})}
19222036
{nestedBlockTagGroups.length > 0 && <PopoverDivider rootOnly />}
@@ -1951,26 +2065,18 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
19512065

19522066
return (
19532067
<div key={group.blockId}>
1954-
<PopoverItem
1955-
rootOnly
1956-
active={rootTagGlobalIndex === selectedIndex && rootTagGlobalIndex >= 0}
1957-
onMouseEnter={() => {
1958-
if (rootTagGlobalIndex >= 0) setSelectedIndex(rootTagGlobalIndex)
1959-
}}
1960-
onMouseDown={(e) => {
1961-
e.preventDefault()
1962-
e.stopPropagation()
1963-
handleTagSelect(rootTag, group)
1964-
}}
1965-
ref={(el) => {
1966-
if (el && rootTagGlobalIndex >= 0) {
1967-
itemRefs.current.set(rootTagGlobalIndex, el)
1968-
}
1969-
}}
1970-
>
1971-
<TagIcon icon={tagIcon} color={blockColor} />
1972-
<span className='flex-1 truncate font-medium'>{group.blockName}</span>
1973-
</PopoverItem>
2068+
<BlockRootTagItem
2069+
rootTag={rootTag}
2070+
rootTagGlobalIndex={rootTagGlobalIndex}
2071+
selectedIndex={selectedIndex}
2072+
setSelectedIndex={setSelectedIndex}
2073+
handleTagSelect={handleTagSelect}
2074+
itemRefs={itemRefs}
2075+
group={group}
2076+
tagIcon={tagIcon}
2077+
blockColor={blockColor}
2078+
blockName={group.blockName}
2079+
/>
19742080
{group.nestedTags.map((nestedTag) => {
19752081
if (nestedTag.fullTag === rootTag) {
19762082
return null

0 commit comments

Comments
 (0)