fix(ui): исправить фокус списка серверов и контраст общих элементов
This commit is contained in:
1 parent
9b0d7bb6e8
commit
3c9096804b
21 files changed
+6952
-58
No files matched your search
@@ -35,6 +35,10 @@ import { LayersIcon, SearchIcon, ServerIcon } from "lucide-react"
|
||||
/** Preview: https://reui.io/preview/base/list-9 · https://reui.io/docs/components/base/icon-tile · https://reui.io/docs/components/base/frame · https://reui.io/docs/components/base/badge */
|
||||
export const ALL_SERVERS_ID = "all"
|
||||
|
||||
// Device URL changes can remount the page. Keep only the current keyboard intent,
|
||||
// and never restore it after the user has focused another control.
|
||||
let pendingKeyboardFocus: { path: string; id: string; source: Element | null; until: number } | null = null
|
||||
|
||||
export interface ServerTileItem {
|
||||
id: string
|
||||
name: string
|
||||
@@ -185,7 +189,15 @@ function ServerTileRail({
|
||||
useEffect(() => {
|
||||
const viewport = listRef.current?.closest<HTMLElement>('[data-slot="scroll-area-viewport"]')
|
||||
viewport?.scrollTo({ top: 0 })
|
||||
listRef.current?.querySelector<HTMLElement>('[aria-selected="true"]')?.scrollIntoView({ block: "nearest" })
|
||||
const selected = listRef.current?.querySelector<HTMLElement>('[aria-selected="true"]')
|
||||
selected?.scrollIntoView({ block: "nearest" })
|
||||
const pending = pendingKeyboardFocus
|
||||
if (pending && (pending.until < performance.now() || pending.path !== window.location.pathname)) {
|
||||
pendingKeyboardFocus = null
|
||||
} else if (pending && pending.id === resolvedId && selected?.getClientRects().length) {
|
||||
if (document.activeElement === document.body || document.activeElement === pending.source) selected.focus({ preventScroll: true })
|
||||
pendingKeyboardFocus = null
|
||||
}
|
||||
}, [pageIndex, resolvedId, q, pageSize])
|
||||
const visibleIds = new Set(table.getRowModel().rows.map(row => row.id))
|
||||
const visibleGroups = groups.map(group => ({ ...group, items: group.items.filter(item => visibleIds.has(item.id)) })).filter(group => group.items.length > 0)
|
||||
@@ -200,6 +212,19 @@ function ServerTileRail({
|
||||
return ids
|
||||
}, [filtered, showAllTile])
|
||||
|
||||
const tabStopId = activeId && selectableIds.includes(activeId)
|
||||
? activeId
|
||||
: showAllTile ? ALL_SERVERS_ID : table.getRowModel().rows.find(row => isSelectable(row.original))?.id
|
||||
|
||||
function selectWithKeyboard(id: string) {
|
||||
if (id === resolvedId) {
|
||||
listRef.current?.querySelector<HTMLElement>('[aria-selected="true"]')?.focus()
|
||||
return
|
||||
}
|
||||
pendingKeyboardFocus = { path: window.location.pathname, id, source: document.activeElement, until: performance.now() + 10000 }
|
||||
onSelect(id)
|
||||
}
|
||||
|
||||
function moveSelection(delta: number) {
|
||||
if (selectableIds.length === 0) return
|
||||
const idx = selectableIds.indexOf(resolvedId)
|
||||
@@ -208,7 +233,7 @@ function ServerTileRail({
|
||||
? delta > 0 ? 0 : selectableIds.length - 1
|
||||
: Math.min(selectableIds.length - 1, Math.max(0, idx + delta))
|
||||
const nextId = selectableIds[nextIdx]
|
||||
if (nextId) onSelect(nextId)
|
||||
if (nextId) selectWithKeyboard(nextId)
|
||||
}
|
||||
|
||||
function handleListKeyDown(event: KeyboardEvent<HTMLDivElement>) {
|
||||
@@ -225,13 +250,13 @@ function ServerTileRail({
|
||||
}
|
||||
if (event.key === "Home") {
|
||||
event.preventDefault()
|
||||
if (selectableIds[0]) onSelect(selectableIds[0])
|
||||
if (selectableIds[0]) selectWithKeyboard(selectableIds[0])
|
||||
return
|
||||
}
|
||||
if (event.key === "End") {
|
||||
event.preventDefault()
|
||||
const last = selectableIds[selectableIds.length - 1]
|
||||
if (last) onSelect(last)
|
||||
if (last) selectWithKeyboard(last)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,8 +308,6 @@ function ServerTileRail({
|
||||
className="flex flex-col gap-0.5 p-1.5"
|
||||
role="listbox"
|
||||
aria-label="Серверы"
|
||||
aria-activedescendant={activeId ? `${railId}-server-tile-${activeId}` : undefined}
|
||||
tabIndex={0}
|
||||
onKeyDown={handleListKeyDown}
|
||||
>
|
||||
{showAllTile ? (
|
||||
@@ -292,6 +315,7 @@ function ServerTileRail({
|
||||
id={`${railId}-server-tile-${ALL_SERVERS_ID}`}
|
||||
item={allItem}
|
||||
selected={resolvedId === ALL_SERVERS_ID}
|
||||
tabStop={tabStopId === ALL_SERVERS_ID}
|
||||
onSelect={() => onSelect(ALL_SERVERS_ID)}
|
||||
showCount={showCount}
|
||||
showType={false}
|
||||
@@ -315,6 +339,7 @@ function ServerTileRail({
|
||||
id={`${railId}-server-tile-${item.id}`}
|
||||
item={item}
|
||||
selected={resolvedId === item.id}
|
||||
tabStop={tabStopId === item.id}
|
||||
onSelect={() => onSelect(item.id)}
|
||||
showCount={showCount}
|
||||
showType={showType}
|
||||
@@ -354,6 +379,7 @@ function ServerTileButton({
|
||||
id,
|
||||
item,
|
||||
selected,
|
||||
tabStop,
|
||||
onSelect,
|
||||
showCount,
|
||||
showType,
|
||||
@@ -363,6 +389,7 @@ function ServerTileButton({
|
||||
id: string
|
||||
item: ServerTileItem
|
||||
selected: boolean
|
||||
tabStop: boolean
|
||||
onSelect: () => void
|
||||
showCount: boolean
|
||||
showType: boolean
|
||||
@@ -385,6 +412,7 @@ function ServerTileButton({
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={selected}
|
||||
tabIndex={tabStop ? 0 : -1}
|
||||
title={item.title ?? hostname}
|
||||
disabled={!selectable}
|
||||
onClick={onSelect}
|
||||
|
||||
Reference in new issue
Block a user