|
21 | 21 | } from '$lib/network-store.svelte' |
22 | 22 | import type { NetworkHost } from './types' |
23 | 23 | import { updateLeftPaneState, updateRightPaneState, type PaneState, type PaneFileEntry } from '$lib/tauri-commands' |
| 24 | + import { handleNavigationShortcut } from './keyboard-shortcuts' |
| 25 | +
|
| 26 | + /** Row height for host list (matches Full list) */ |
| 27 | + const HOST_ROW_HEIGHT = 20 |
24 | 28 |
|
25 | 29 | interface Props { |
26 | 30 | paneId?: 'left' | 'right' |
|
38 | 42 | // Local cursor state |
39 | 43 | let cursorIndex = $state(0) |
40 | 44 |
|
| 45 | + // Container tracking for PageUp/PageDown |
| 46 | + let listContainer: HTMLDivElement | undefined = $state() |
| 47 | + let containerHeight = $state(0) |
| 48 | +
|
41 | 49 | // Refresh stale shares when component mounts (entering network view) |
42 | 50 | onMount(() => { |
43 | 51 | refreshAllStaleShares() |
|
89 | 97 | } |
90 | 98 | } |
91 | 99 |
|
| 100 | + /** Scrolls to make the cursor visible */ |
| 101 | + function scrollToIndex(index: number) { |
| 102 | + if (!listContainer) return |
| 103 | + const targetTop = index * HOST_ROW_HEIGHT |
| 104 | + const targetBottom = targetTop + HOST_ROW_HEIGHT |
| 105 | + const scrollTop = listContainer.scrollTop |
| 106 | + const viewportBottom = scrollTop + containerHeight |
| 107 | +
|
| 108 | + if (targetTop < scrollTop) { |
| 109 | + listContainer.scrollTop = targetTop |
| 110 | + } else if (targetBottom > viewportBottom) { |
| 111 | + listContainer.scrollTop = targetBottom - containerHeight |
| 112 | + } |
| 113 | + } |
| 114 | +
|
92 | 115 | // Handle keyboard navigation |
93 | 116 | export function handleKeyDown(e: KeyboardEvent): boolean { |
94 | 117 | if (hosts.length === 0) return false |
95 | 118 |
|
| 119 | + // Try centralized navigation shortcuts first (PageUp, PageDown, Home, End, Option+arrows) |
| 120 | + const visibleItems = Math.max(1, Math.floor(containerHeight / HOST_ROW_HEIGHT)) |
| 121 | + const navResult = handleNavigationShortcut(e, { |
| 122 | + currentIndex: cursorIndex, |
| 123 | + totalCount: hosts.length, |
| 124 | + visibleItems, |
| 125 | + }) |
| 126 | + if (navResult?.handled) { |
| 127 | + e.preventDefault() |
| 128 | + cursorIndex = navResult.newIndex |
| 129 | + scrollToIndex(cursorIndex) |
| 130 | + return true |
| 131 | + } |
| 132 | +
|
96 | 133 | switch (e.key) { |
97 | 134 | case 'ArrowDown': |
98 | 135 | e.preventDefault() |
99 | 136 | cursorIndex = Math.min(cursorIndex + 1, hosts.length - 1) |
| 137 | + scrollToIndex(cursorIndex) |
100 | 138 | return true |
101 | 139 | case 'ArrowUp': |
102 | 140 | e.preventDefault() |
103 | 141 | cursorIndex = Math.max(cursorIndex - 1, 0) |
| 142 | + scrollToIndex(cursorIndex) |
104 | 143 | return true |
105 | | - case 'Home': |
| 144 | + case 'ArrowLeft': |
106 | 145 | e.preventDefault() |
107 | 146 | cursorIndex = 0 |
| 147 | + scrollToIndex(cursorIndex) |
108 | 148 | return true |
109 | | - case 'End': |
| 149 | + case 'ArrowRight': |
110 | 150 | e.preventDefault() |
111 | 151 | cursorIndex = hosts.length - 1 |
| 152 | + scrollToIndex(cursorIndex) |
112 | 153 | return true |
113 | 154 | case 'Enter': |
114 | 155 | e.preventDefault() |
|
279 | 320 | <span class="col-shares">Shares</span> |
280 | 321 | <span class="col-status">Status</span> |
281 | 322 | </div> |
282 | | - <div class="host-list"> |
| 323 | + <div class="host-list" bind:this={listContainer} bind:clientHeight={containerHeight}> |
283 | 324 | {#each hosts as host, index (host.id)} |
284 | 325 | <!-- svelte-ignore a11y_no_noninteractive_element_interactions --> |
285 | 326 | <div |
|
359 | 400 |
|
360 | 401 | .host-row { |
361 | 402 | display: flex; |
362 | | - padding: 4px 8px; |
| 403 | + height: 20px; |
| 404 | + padding: var(--spacing-xxs) var(--spacing-sm); |
363 | 405 | cursor: default; |
364 | | - border-bottom: 1px solid var(--color-border-secondary); |
365 | | - } |
366 | | -
|
367 | | - .host-row:hover { |
368 | | - background-color: var(--color-bg-hover); |
369 | 406 | } |
370 | 407 |
|
371 | 408 | .host-row.is-under-cursor { |
|
0 commit comments