@@ -30,6 +30,7 @@ type HomeTab = "local" | "servers"
3030
3131interface FolderSelectionViewProps {
3232 onSelectFolder : ( folder : string , binaryPath ?: string , options ?: { forceNew ?: boolean } ) => void
33+ onSelectExistingInstance : ( instanceId : string , recentPath : string , binaryPath : string ) => void
3334 onOpenSidecar ?: ( ) => void
3435 isLoading ?: boolean
3536 onClose ?: ( ) => void
@@ -50,6 +51,8 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
5051 } = useConfig ( )
5152 const { t, locale } = useI18n ( )
5253 const [ selectedIndex , setSelectedIndex ] = createSignal ( 0 )
54+ const [ hoveredRecentActionPath , setHoveredRecentActionPath ] = createSignal < string | null > ( null )
55+ const [ focusedRecentActionPath , setFocusedRecentActionPath ] = createSignal < string | null > ( null )
5356 const [ focusMode , setFocusMode ] = createSignal < "recent" | "new" | null > ( "recent" )
5457 const [ selectedBinary , setSelectedBinary ] = createSignal ( serverSettings ( ) . opencodeBinary || "opencode" )
5558 const [ isFolderBrowserOpen , setIsFolderBrowserOpen ] = createSignal ( false )
@@ -133,6 +136,7 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
133136 const isEditingField =
134137 activeElement &&
135138 ( [ "INPUT" , "TEXTAREA" , "SELECT" ] . includes ( activeElement . tagName ) || activeElement . isContentEditable || Boolean ( insideModal ) )
139+ const isInteractiveControl = activeElement && [ "BUTTON" , "A" ] . includes ( activeElement . tagName )
136140
137141 if ( isEditingField ) {
138142 return
@@ -155,6 +159,8 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
155159 return
156160 }
157161
162+ if ( isInteractiveControl && ( e . key === "Enter" || e . key === " " ) ) return
163+
158164 const listLength = getActiveListLength ( )
159165 if ( listLength === 0 ) return
160166
@@ -209,7 +215,7 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
209215 if ( activeTab ( ) === "local" ) {
210216 const folder = folders ( ) [ index ]
211217 if ( folder ) {
212- handleFolderSelect ( folder . path )
218+ handleFolderSelect ( folder . path , true )
213219 }
214220 return
215221 }
@@ -309,9 +315,27 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
309315 return t ( "time.relative.justNow" )
310316 }
311317
312- function handleFolderSelect ( path : string ) {
318+ function handleFolderSelect ( path : string , forceNew = false ) {
319+ if ( isLoading ( ) ) return
320+ props . onSelectFolder ( path , selectedBinary ( ) , forceNew ? { forceNew : true } : undefined )
321+ }
322+
323+ function handleExistingInstanceSelect ( instanceId : string , recentPath : string ) {
313324 if ( isLoading ( ) ) return
314- props . onSelectFolder ( path , selectedBinary ( ) )
325+ props . onSelectExistingInstance ( instanceId , recentPath , selectedBinary ( ) )
326+ }
327+
328+ function setRecentActionHovered ( path : string , active : boolean ) {
329+ setHoveredRecentActionPath ( ( current ) => active ? path : current === path ? null : current )
330+ }
331+
332+ function setRecentActionFocused ( path : string , active : boolean ) {
333+ setFocusedRecentActionPath ( ( current ) => active ? path : current === path ? null : current )
334+ }
335+
336+ function clearRecentActionState ( path : string ) {
337+ setRecentActionHovered ( path , false )
338+ setRecentActionFocused ( path , false )
315339 }
316340
317341 function resetCloneDialog ( ) {
@@ -924,37 +948,82 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
924948 < For each = { folders ( ) } >
925949 { ( folder , index ) => {
926950 const existingInstance = ( ) => getExistingInstanceForFolder ( folder . path )
951+ const projectName = ( ) => getProjectDisplayName ( folder . path , folder . projectName )
952+ const projectLabelId = ( ) => `recent-folder-${ index ( ) } -name`
953+ const openActionLabelId = ( ) => `recent-folder-${ index ( ) } -open-action`
927954
928955 return < div
929- class = "panel-list-item"
956+ class = "panel-list-item folder-home-recent-item "
930957 classList = { {
931958 "panel-list-item-highlight" : focusMode ( ) === "recent" && selectedIndex ( ) === index ( ) ,
932959 "panel-list-item-disabled" : isLoading ( ) ,
960+ "folder-home-recent-item-action-active" :
961+ hoveredRecentActionPath ( ) === folder . path || focusedRecentActionPath ( ) === folder . path ,
962+ } }
963+ onMouseEnter = { ( ) => {
964+ if ( isLoading ( ) ) return
965+ setFocusMode ( "recent" )
966+ setSelectedIndex ( index ( ) )
933967 } }
934968 >
935969 < div class = "flex items-center gap-2 w-full px-1" >
936- < button
937- data-list-index = { index ( ) }
938- class = "panel-list-item-content flex-1"
939- disabled = { isLoading ( ) }
940- onClick = { ( ) => handleFolderSelect ( folder . path ) }
941- onMouseEnter = { ( ) => {
942- if ( isLoading ( ) ) return
943- setFocusMode ( "recent" )
944- setSelectedIndex ( index ( ) )
945- } }
946- >
947- < div class = "flex items-center justify-between gap-3 w-full" >
970+ < div class = "panel-list-item-content relative flex-1" >
971+ < button
972+ data-list-index = { index ( ) }
973+ type = "button"
974+ class = "folder-home-recent-primary-action"
975+ disabled = { isLoading ( ) }
976+ aria-labelledby = { projectLabelId ( ) }
977+ onClick = { ( ) => handleFolderSelect ( folder . path , true ) }
978+ onFocus = { ( ) => {
979+ setFocusMode ( "recent" )
980+ setSelectedIndex ( index ( ) )
981+ } }
982+ />
983+ < div class = "relative z-[1] pointer-events-none flex items-center justify-between gap-3 w-full" >
948984 < div class = "flex-1 min-w-0" >
949985 < div class = "flex items-center gap-2 mb-1" >
950986 < Folder class = "w-4 h-4 flex-shrink-0 icon-muted" />
951- < span class = "text-sm font-medium truncate text-primary" >
952- { getProjectDisplayName ( folder . path , folder . projectName ) }
987+ < span id = { projectLabelId ( ) } class = "text-sm font-medium truncate text-primary" >
988+ { projectName ( ) }
953989 </ span >
954990 < Show when = { existingInstance ( ) } >
955- < span class = "rounded-full border border-base px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-secondary" >
956- { t ( "folderSelection.recent.openBadge" ) }
957- </ span >
991+ { ( instance ) => {
992+ let ownsHoverState = false
993+ let ownsFocusState = false
994+ onCleanup ( ( ) => {
995+ if ( ownsHoverState ) setRecentActionHovered ( folder . path , false )
996+ if ( ownsFocusState ) setRecentActionFocused ( folder . path , false )
997+ } )
998+ return (
999+ < button
1000+ type = "button"
1001+ class = "folder-home-open-instance-button folder-home-row-action pointer-events-auto"
1002+ disabled = { isLoading ( ) }
1003+ aria-labelledby = { `${ openActionLabelId ( ) } ${ projectLabelId ( ) } ` }
1004+ title = { t ( "folderSelection.recent.switchToOpenProject" ) }
1005+ onClick = { ( ) => handleExistingInstanceSelect ( instance ( ) . id , folder . path ) }
1006+ onMouseEnter = { ( ) => {
1007+ ownsHoverState = true
1008+ setRecentActionHovered ( folder . path , true )
1009+ } }
1010+ onMouseLeave = { ( ) => {
1011+ ownsHoverState = false
1012+ setRecentActionHovered ( folder . path , false )
1013+ } }
1014+ onFocus = { ( ) => {
1015+ ownsFocusState = true
1016+ setRecentActionFocused ( folder . path , true )
1017+ } }
1018+ onBlur = { ( ) => {
1019+ ownsFocusState = false
1020+ setRecentActionFocused ( folder . path , false )
1021+ } }
1022+ >
1023+ < span id = { openActionLabelId ( ) } > { t ( "folderSelection.recent.openBadge" ) } </ span >
1024+ </ button >
1025+ )
1026+ } }
9581027 </ Show >
9591028 </ div >
9601029 < div class = "flex items-center gap-2 pl-6 text-xs text-muted min-w-0" >
@@ -968,20 +1037,31 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
9681037 < kbd class = "kbd" > ↵</ kbd >
9691038 </ Show >
9701039 </ div >
971- </ button >
1040+ </ div >
9721041 < button
9731042 onClick = { ( e ) => openProjectRename ( folder . path , folder . projectName , e ) }
9741043 disabled = { isLoading ( ) }
975- class = "p-2 transition-all hover:bg-surface-hover opacity-70 hover:opacity-100 rounded"
1044+ class = "folder-home-row-action p-2 transition-all hover:bg-surface-hover opacity-70 hover:opacity-100 rounded"
9761045 title = { t ( "folderSelection.recent.rename" ) }
1046+ onMouseEnter = { ( ) => setRecentActionHovered ( folder . path , true ) }
1047+ onMouseLeave = { ( ) => setRecentActionHovered ( folder . path , false ) }
1048+ onFocus = { ( ) => setRecentActionFocused ( folder . path , true ) }
1049+ onBlur = { ( ) => setRecentActionFocused ( folder . path , false ) }
9771050 >
9781051 < Pencil class = "w-3.5 h-3.5 transition-colors icon-muted" />
9791052 </ button >
9801053 < button
981- onClick = { ( e ) => handleRemove ( folder . path , e ) }
1054+ onClick = { ( e ) => {
1055+ clearRecentActionState ( folder . path )
1056+ handleRemove ( folder . path , e )
1057+ } }
9821058 disabled = { isLoading ( ) }
983- class = "p-2 transition-all hover:bg-red-100 dark:hover:bg-red-900/30 opacity-70 hover:opacity-100 rounded"
1059+ class = "folder-home-row-action p-2 transition-all hover:bg-red-100 dark:hover:bg-red-900/30 opacity-70 hover:opacity-100 rounded"
9841060 title = { t ( "folderSelection.recent.remove" ) }
1061+ onMouseEnter = { ( ) => setRecentActionHovered ( folder . path , true ) }
1062+ onMouseLeave = { ( ) => setRecentActionHovered ( folder . path , false ) }
1063+ onFocus = { ( ) => setRecentActionFocused ( folder . path , true ) }
1064+ onBlur = { ( ) => setRecentActionFocused ( folder . path , false ) }
9851065 >
9861066 < Trash2 class = "w-3.5 h-3.5 transition-colors icon-muted hover:text-red-600 dark:hover:text-red-400" />
9871067 </ button >
0 commit comments