@@ -14,6 +14,12 @@ import SwiftUI
1414
1515struct WelcomeWindowView : View {
1616 private static let logger = Logger ( subsystem: " com.TablePro " , category: " WelcomeWindowView " )
17+
18+ private enum FocusField {
19+ case search
20+ case connectionList
21+ }
22+
1723 private let storage = ConnectionStorage . shared
1824 private let groupStorage = GroupStorage . shared
1925 private let dbManager = DatabaseManager . shared
@@ -26,7 +32,8 @@ struct WelcomeWindowView: View {
2632 @State private var connectionToDelete : DatabaseConnection ?
2733 @State private var showDeleteConfirmation = false
2834 @State private var hoveredConnectionId : UUID ?
29- @State private var selectedConnectionId : UUID ? // For keyboard navigation
35+ @State private var selectedConnectionId : UUID ?
36+ @FocusState private var focus : FocusField ?
3037 @State private var showOnboarding = !AppSettingsStorage. shared. hasCompletedOnboarding ( )
3138 @State private var groups : [ ConnectionGroup ] = [ ]
3239 @State private var collapsedGroupIds : Set < UUID > = {
@@ -271,6 +278,37 @@ struct WelcomeWindowView: View {
271278 TextField ( " Search for connection... " , text: $searchText)
272279 . textFieldStyle ( . plain)
273280 . font ( . system( size: ThemeEngine . shared. activeTheme. typography. body) )
281+ . focused ( $focus, equals: . search)
282+ . onKeyPress ( . return) {
283+ if let id = selectedConnectionId,
284+ let connection = connections. first ( where: { $0. id == id } )
285+ {
286+ connectToDatabase ( connection)
287+ }
288+ return . handled
289+ }
290+ . onKeyPress ( characters: . init( charactersIn: " jn " ) , phases: [ . down, . repeat ] ) { keyPress in
291+ guard keyPress. modifiers. contains ( . control) else { return . ignored }
292+ moveToNextConnection ( )
293+ focus = . connectionList
294+ return . handled
295+ }
296+ . onKeyPress ( characters: . init( charactersIn: " kp " ) , phases: [ . down, . repeat ] ) { keyPress in
297+ guard keyPress. modifiers. contains ( . control) else { return . ignored }
298+ moveToPreviousConnection ( )
299+ focus = . connectionList
300+ return . handled
301+ }
302+ . onKeyPress ( . downArrow) {
303+ moveToNextConnection ( )
304+ focus = . connectionList
305+ return . handled
306+ }
307+ . onKeyPress ( . upArrow) {
308+ moveToPreviousConnection ( )
309+ focus = . connectionList
310+ return . handled
311+ }
274312 }
275313 . padding ( . horizontal, ThemeEngine . shared. activeTheme. spacing. sm)
276314 . padding ( . vertical, 6 )
@@ -311,57 +349,62 @@ struct WelcomeWindowView: View {
311349 /// - Return key: connects to selected row
312350 /// - Arrow keys: native keyboard navigation
313351 private var connectionList : some View {
314- List ( selection: $selectedConnectionId) {
315- ForEach ( ungroupedConnections) { connection in
316- connectionRow ( for: connection)
317- }
318- . onMove { from, to in
319- guard searchText. isEmpty else { return }
320- moveUngroupedConnections ( from: from, to: to)
321- }
352+ ScrollViewReader { proxy in
353+ List ( selection: $selectedConnectionId) {
354+ ForEach ( ungroupedConnections) { connection in
355+ connectionRow ( for: connection)
356+ }
357+ . onMove { from, to in
358+ guard searchText. isEmpty else { return }
359+ moveUngroupedConnections ( from: from, to: to)
360+ }
322361
323- ForEach ( activeGroups) { group in
324- Section {
325- if !collapsedGroupIds. contains ( group. id) {
326- ForEach ( connections ( in: group) ) { connection in
327- connectionRow ( for: connection)
362+ ForEach ( activeGroups) { group in
363+ Section {
364+ if !collapsedGroupIds. contains ( group. id) {
365+ ForEach ( connections ( in: group) ) { connection in
366+ connectionRow ( for: connection)
367+ }
328368 }
369+ } header: {
370+ groupHeader ( for: group)
329371 }
330- } header: {
331- groupHeader ( for: group)
332372 }
333373 }
334- }
335- . listStyle ( . inset)
336- . scrollContentBackground ( . hidden)
337- . environment ( \. defaultMinListRowHeight, 44 )
338- . onKeyPress ( . return) {
339- if let id = selectedConnectionId,
340- let connection = connections. first ( where: { $0. id == id } )
341- {
342- connectToDatabase ( connection)
374+ . listStyle ( . inset)
375+ . scrollContentBackground ( . hidden)
376+ . focused ( $focus, equals: . connectionList)
377+ . environment ( \. defaultMinListRowHeight, 44 )
378+ . onKeyPress ( . return) {
379+ if let id = selectedConnectionId,
380+ let connection = connections. first ( where: { $0. id == id } )
381+ {
382+ connectToDatabase ( connection)
383+ }
384+ return . handled
385+ }
386+ . onKeyPress ( characters: . init( charactersIn: " jn " ) , phases: [ . down, . repeat ] ) { keyPress in
387+ guard keyPress. modifiers. contains ( . control) else { return . ignored }
388+ moveToNextConnection ( )
389+ scrollToSelection ( proxy)
390+ return . handled
391+ }
392+ . onKeyPress ( characters: . init( charactersIn: " kp " ) , phases: [ . down, . repeat ] ) { keyPress in
393+ guard keyPress. modifiers. contains ( . control) else { return . ignored }
394+ moveToPreviousConnection ( )
395+ scrollToSelection ( proxy)
396+ return . handled
397+ }
398+ . onKeyPress ( characters: . init( charactersIn: " h " ) , phases: . down) { keyPress in
399+ guard keyPress. modifiers. contains ( . control) else { return . ignored }
400+ collapseSelectedGroup ( )
401+ return . handled
402+ }
403+ . onKeyPress ( characters: . init( charactersIn: " l " ) , phases: . down) { keyPress in
404+ guard keyPress. modifiers. contains ( . control) else { return . ignored }
405+ expandSelectedGroup ( )
406+ return . handled
343407 }
344- return . handled
345- }
346- . onKeyPress ( characters: . init( charactersIn: " j " ) , phases: . down) { keyPress in
347- guard keyPress. modifiers. contains ( . control) else { return . ignored }
348- moveToNextConnection ( )
349- return . handled
350- }
351- . onKeyPress ( characters: . init( charactersIn: " k " ) , phases: . down) { keyPress in
352- guard keyPress. modifiers. contains ( . control) else { return . ignored }
353- moveToPreviousConnection ( )
354- return . handled
355- }
356- . onKeyPress ( characters: . init( charactersIn: " h " ) , phases: . down) { keyPress in
357- guard keyPress. modifiers. contains ( . control) else { return . ignored }
358- collapseSelectedGroup ( )
359- return . handled
360- }
361- . onKeyPress ( characters: . init( charactersIn: " l " ) , phases: . down) { keyPress in
362- guard keyPress. modifiers. contains ( . control) else { return . ignored }
363- expandSelectedGroup ( )
364- return . handled
365408 }
366409 }
367410
@@ -657,6 +700,12 @@ struct WelcomeWindowView: View {
657700 selectedConnectionId = visible [ prev] . id
658701 }
659702
703+ private func scrollToSelection( _ proxy: ScrollViewProxy ) {
704+ if let id = selectedConnectionId {
705+ proxy. scrollTo ( id, anchor: . center)
706+ }
707+ }
708+
660709 private func collapseSelectedGroup( ) {
661710 guard let id = selectedConnectionId,
662711 let connection = connections. first ( where: { $0. id == id } ) ,
0 commit comments