|
245 | 245 | untrack(() => table.setPageIndex(0)); |
246 | 246 | }); |
247 | 247 |
|
| 248 | + $effect(() => { |
| 249 | + if (selectedCidrs.size === 0) { |
| 250 | + rangeAnchor = null; |
| 251 | + } |
| 252 | + }); |
| 253 | +
|
248 | 254 | $effect(() => { |
249 | 255 | const pageCount = Math.max(1, table.getPageCount()); |
250 | 256 | if (pagination.pageIndex >= pageCount) { |
|
284 | 290 | return () => window.removeEventListener("keydown", selectByKeyboard); |
285 | 291 | }); |
286 | 292 |
|
287 | | - let menu = $state<{ cidr: string; x: number; y: number } | null>(null); |
| 293 | + let menu = $state<{ cidr: string; x: number; y: number; trigger: HTMLElement | null } | null>(null); |
288 | 294 | let rangeAnchor = $state<string | null>(null); |
289 | 295 |
|
290 | 296 | function openMenu(event: MouseEvent, cidr: string) { |
291 | 297 | event.preventDefault(); |
292 | | - menu = { cidr, x: event.clientX, y: event.clientY }; |
| 298 | + menu = { cidr, x: event.clientX, y: event.clientY, trigger: event.currentTarget as HTMLElement }; |
| 299 | + } |
| 300 | +
|
| 301 | + function selectRowByKeyboard(event: KeyboardEvent, cidr: string) { |
| 302 | + if (isInteractive(event.target)) { |
| 303 | + return; |
| 304 | + } |
| 305 | + if (event.key !== "Enter" && event.key !== " ") { |
| 306 | + if (event.key === "ContextMenu" || (event.shiftKey && event.key === "F10")) { |
| 307 | + event.preventDefault(); |
| 308 | + const trigger = event.currentTarget as HTMLElement; |
| 309 | + const bounds = trigger.getBoundingClientRect(); |
| 310 | + menu = { cidr, x: bounds.left + Math.min(24, bounds.width / 2), y: bounds.top + Math.min(24, bounds.height / 2), trigger }; |
| 311 | + } |
| 312 | + return; |
| 313 | + } |
| 314 | + event.preventDefault(); |
| 315 | + const modifier = event.metaKey || event.ctrlKey; |
| 316 | + if (event.shiftKey && rangeAnchor) { |
| 317 | + const start = orderedRows.findIndex((row) => row.original.cidr === rangeAnchor); |
| 318 | + const end = orderedRows.findIndex((row) => row.original.cidr === cidr); |
| 319 | + if (start >= 0 && end >= 0) { |
| 320 | + const range = orderedRows |
| 321 | + .slice(Math.min(start, end), Math.max(start, end) + 1) |
| 322 | + .map((row) => row.original.cidr); |
| 323 | + onSelectionChange(modifier ? new Set([...selectedCidrs, ...range]) : new Set(range)); |
| 324 | + return; |
| 325 | + } |
| 326 | + } |
| 327 | + const next = new Set(selectedCidrs); |
| 328 | + if (modifier || selectedCidrs.has(cidr)) { |
| 329 | + if (next.has(cidr)) { |
| 330 | + next.delete(cidr); |
| 331 | + } else { |
| 332 | + next.add(cidr); |
| 333 | + } |
| 334 | + } else { |
| 335 | + next.clear(); |
| 336 | + next.add(cidr); |
| 337 | + } |
| 338 | + onSelectionChange(next); |
| 339 | + rangeAnchor = cidr; |
293 | 340 | } |
294 | 341 |
|
295 | 342 | function selectRow(event: MouseEvent, cidr: string) { |
|
459 | 506 | <div class="flex h-8 items-center"> |
460 | 507 | {#if header.column.getCanFilter()} |
461 | 508 | <input |
462 | | - class="block h-6 w-full rounded-md bg-raised px-1 text-xs font-normal text-text focus:outline-none focus:ring-1 focus:ring-accent {numericIds.has( |
| 509 | + class="block h-6 w-full rounded-[var(--radius-control)] border border-transparent bg-raised px-1 text-xs font-normal text-text focus:border-line focus:outline-none focus:ring-1 focus:ring-accent {numericIds.has( |
463 | 510 | header.column.id, |
464 | 511 | ) |
465 | 512 | ? 'text-right' |
466 | 513 | : ''}" |
467 | 514 | value={String(header.column.getFilterValue() ?? "")} |
| 515 | + aria-label={`Filter ${String(header.column.columnDef.header)}`} |
468 | 516 | oninput={(event) => { |
469 | 517 | header.column.setFilterValue(event.currentTarget.value); |
470 | 518 | table.setPageIndex(0); |
|
485 | 533 | <tr |
486 | 534 | class="group h-8 border-t border-line {selectedCidrs.has(row.original.cidr) |
487 | 535 | ? 'bg-accent/10 shadow-[inset_3px_0_0_var(--color-accent)] hover:bg-accent/15' |
488 | | - : 'hover:bg-raised'}" |
| 536 | + : 'hover:bg-raised/80'} focus-visible:relative focus-visible:z-10 focus-visible:outline-none focus-visible:shadow-[inset_0_0_0_1px_var(--color-accent)]" |
| 537 | + tabindex="0" |
489 | 538 | aria-selected={selectedCidrs.has(row.original.cidr)} |
490 | 539 | onclick={(event) => selectRow(event, row.original.cidr)} |
491 | 540 | oncontextmenu={(event) => openMenu(event, row.original.cidr)} |
| 541 | + onkeydown={(event) => selectRowByKeyboard(event, row.original.cidr)} |
492 | 542 | > |
493 | 543 | {#each row.getAllCells() as cell (cell.id)} |
494 | 544 | <td |
|
527 | 577 | <RowMenu |
528 | 578 | x={menu.x} |
529 | 579 | y={menu.y} |
| 580 | + trigger={menu.trigger} |
530 | 581 | {locked} |
531 | 582 | onProbe={() => { |
532 | 583 | const cidr = menu?.cidr; |
|
0 commit comments