|
215 | 215 | } |
216 | 216 | </script> |
217 | 217 |
|
218 | | -<div |
219 | | - class="full-list" |
220 | | - class:is-focused={isFocused} |
221 | | - bind:this={scrollContainer} |
222 | | - bind:clientHeight={containerHeight} |
223 | | - onscroll={handleScroll} |
224 | | - tabindex="-1" |
225 | | - role="listbox" |
226 | | - aria-activedescendant={cursorIndex >= 0 ? `file-${String(cursorIndex)}` : undefined} |
227 | | -> |
228 | | - <!-- Header row with sortable columns --> |
| 218 | +<div class="full-list-container" class:is-focused={isFocused}> |
| 219 | + <!-- Header row with sortable columns (outside scroll container for correct height calculation) --> |
229 | 220 | <div class="header-row"> |
230 | 221 | <span class="header-icon"></span> |
231 | 222 | <SortableHeader |
|
251 | 242 | onClick={onSortChange ?? (() => {})} |
252 | 243 | /> |
253 | 244 | </div> |
254 | | - <!-- Spacer div provides accurate scrollbar for full list size --> |
255 | | - <div class="virtual-spacer" style="height: {virtualWindow.totalSize}px;"> |
256 | | - <!-- Visible window positioned with translateY --> |
257 | | - <div class="virtual-window" style="transform: translateY({virtualWindow.offset}px);"> |
258 | | - {#each visibleFiles as { file, globalIndex } (file.path)} |
259 | | - {@const syncIcon = getSyncIconPath(syncStatusMap[file.path])} |
260 | | - <!-- svelte-ignore a11y_interactive_supports_focus --> |
261 | | - <div |
262 | | - id={`file-${String(globalIndex)}`} |
263 | | - class="file-entry" |
264 | | - class:is-under-cursor={globalIndex === cursorIndex} |
265 | | - class:is-selected={selectedIndices.has(globalIndex)} |
266 | | - onmousedown={(e: MouseEvent) => { |
267 | | - handleMouseDown(e, globalIndex) |
268 | | - }} |
269 | | - ondblclick={() => { |
270 | | - handleDoubleClick(globalIndex) |
271 | | - }} |
272 | | - oncontextmenu={(e: MouseEvent) => { |
273 | | - e.preventDefault() |
274 | | - onSelect(globalIndex) |
275 | | - onContextMenu?.(file) |
276 | | - }} |
277 | | - role="option" |
278 | | - aria-selected={globalIndex === cursorIndex} |
279 | | - > |
280 | | - <FileIcon {file} {syncIcon} /> |
281 | | - <span class="col-name">{file.name}</span> |
282 | | - <span class="col-size" title={file.size !== undefined ? formatHumanReadable(file.size) : ''}> |
283 | | - {#if file.isDirectory} |
284 | | - <span class="size-dir"><dir></span> |
285 | | - {:else if file.size !== undefined} |
286 | | - {#each formatSizeTriads(file.size) as triad, i (i)} |
287 | | - <span class={triad.tierClass}>{triad.value}</span> |
288 | | - {/each} |
289 | | - {/if} |
290 | | - </span> |
291 | | - <span class="col-date">{formatDateShort(file.modifiedAt)}</span> |
292 | | - </div> |
293 | | - {/each} |
| 245 | + <!-- Scrollable file list --> |
| 246 | + <div |
| 247 | + class="full-list" |
| 248 | + bind:this={scrollContainer} |
| 249 | + bind:clientHeight={containerHeight} |
| 250 | + onscroll={handleScroll} |
| 251 | + tabindex="-1" |
| 252 | + role="listbox" |
| 253 | + aria-activedescendant={cursorIndex >= 0 ? `file-${String(cursorIndex)}` : undefined} |
| 254 | + > |
| 255 | + <!-- Spacer div provides accurate scrollbar for full list size --> |
| 256 | + <div class="virtual-spacer" style="height: {virtualWindow.totalSize}px;"> |
| 257 | + <!-- Visible window positioned with translateY --> |
| 258 | + <div class="virtual-window" style="transform: translateY({virtualWindow.offset}px);"> |
| 259 | + {#each visibleFiles as { file, globalIndex } (file.path)} |
| 260 | + {@const syncIcon = getSyncIconPath(syncStatusMap[file.path])} |
| 261 | + <!-- svelte-ignore a11y_interactive_supports_focus --> |
| 262 | + <div |
| 263 | + id={`file-${String(globalIndex)}`} |
| 264 | + class="file-entry" |
| 265 | + class:is-under-cursor={globalIndex === cursorIndex} |
| 266 | + class:is-selected={selectedIndices.has(globalIndex)} |
| 267 | + onmousedown={(e: MouseEvent) => { |
| 268 | + handleMouseDown(e, globalIndex) |
| 269 | + }} |
| 270 | + ondblclick={() => { |
| 271 | + handleDoubleClick(globalIndex) |
| 272 | + }} |
| 273 | + oncontextmenu={(e: MouseEvent) => { |
| 274 | + e.preventDefault() |
| 275 | + onSelect(globalIndex) |
| 276 | + onContextMenu?.(file) |
| 277 | + }} |
| 278 | + role="option" |
| 279 | + aria-selected={globalIndex === cursorIndex} |
| 280 | + > |
| 281 | + <FileIcon {file} {syncIcon} /> |
| 282 | + <span class="col-name">{file.name}</span> |
| 283 | + <span class="col-size" title={file.size !== undefined ? formatHumanReadable(file.size) : ''}> |
| 284 | + {#if file.isDirectory} |
| 285 | + <span class="size-dir"><dir></span> |
| 286 | + {:else if file.size !== undefined} |
| 287 | + {#each formatSizeTriads(file.size) as triad, i (i)} |
| 288 | + <span class={triad.tierClass}>{triad.value}</span> |
| 289 | + {/each} |
| 290 | + {/if} |
| 291 | + </span> |
| 292 | + <span class="col-date">{formatDateShort(file.modifiedAt)}</span> |
| 293 | + </div> |
| 294 | + {/each} |
| 295 | + </div> |
294 | 296 | </div> |
295 | 297 | </div> |
296 | 298 | </div> |
297 | 299 |
|
298 | 300 | <style> |
| 301 | + .full-list-container { |
| 302 | + display: flex; |
| 303 | + flex-direction: column; |
| 304 | + height: 100%; |
| 305 | + width: 100%; |
| 306 | + } |
| 307 | +
|
299 | 308 | .full-list { |
300 | 309 | overflow-y: auto; |
301 | 310 | overflow-x: hidden; |
|
304 | 313 | line-height: 1; |
305 | 314 | flex: 1; |
306 | 315 | outline: none; |
307 | | - display: flex; |
308 | | - flex-direction: column; |
309 | 316 | } |
310 | 317 |
|
311 | 318 | .header-row { |
|
344 | 351 | background-color: rgba(204, 228, 247, 0.1); |
345 | 352 | } |
346 | 353 |
|
347 | | - .full-list.is-focused .file-entry.is-under-cursor { |
| 354 | + .full-list-container.is-focused .file-entry.is-under-cursor { |
348 | 355 | background-color: var(--color-cursor-focused-bg); |
349 | 356 | } |
350 | 357 |
|
|
398 | 405 | } |
399 | 406 |
|
400 | 407 | /* Selection color is preserved even under cursor */ |
401 | | - .full-list.is-focused .file-entry.is-under-cursor.is-selected .col-name { |
| 408 | + .full-list-container.is-focused .file-entry.is-under-cursor.is-selected .col-name { |
402 | 409 | color: var(--color-selection-fg); |
403 | 410 | } |
404 | 411 |
|
|
0 commit comments