diff --git a/frontend/auth/admin/AdminApp.vue b/frontend/auth/admin/AdminApp.vue index ab436ca..6d8a42a 100644 --- a/frontend/auth/admin/AdminApp.vue +++ b/frontend/auth/admin/AdminApp.vue @@ -13,6 +13,7 @@ import AdminDialogs from '@/admin/AdminDialogs.vue' import { useAuthStore } from '@/stores/auth' import { getSettings, adminUiPath, makeUiHref } from '@/utils/settings' import { apiJson } from '@/utils/api' +import { getDirection } from '@/utils/keynav' const info = ref(null) const loading = ref(true) @@ -37,6 +38,15 @@ const renameDisplayValue = ref('') const dialog = ref({ type: null, data: null, busy: false, error: '' }) const safeIdRegex = /[^A-Za-z0-9:._~-]/g +// Template refs for navigation +const breadcrumbsRef = ref(null) +const adminOverviewRef = ref(null) +const adminOrgDetailRef = ref(null) +const adminUserDetailRef = ref(null) + +// Check if any modal/dialog is open (blocks arrow key navigation) +const hasActiveModal = computed(() => dialog.value.type !== null || showRegModal.value) + function sanitizeRenameId() { if (renameIdValue.value) renameIdValue.value = renameIdValue.value.replace(safeIdRegex, '') } function handleGlobalClick(e) { @@ -397,6 +407,36 @@ async function toggleOrgPermission(org, permId, checked) { function openDialog(type, data) { dialog.value = { type, data, busy: false, error: '' } } function closeDialog() { dialog.value = { type: null, data: null, busy: false, error: '' } } +// Keyboard navigation handlers +function handleBreadcrumbKeydown(event) { + if (hasActiveModal.value) return + + const direction = getDirection(event) + if (!direction) return + + // Left/right handled internally by Breadcrumbs component + if (direction === 'down') { + event.preventDefault() + // Move to admin panel content + if (adminOverviewRef.value) { + adminOverviewRef.value.focusFirstElement?.() + } else if (adminOrgDetailRef.value) { + adminOrgDetailRef.value.focusFirstElement?.() + } else if (adminUserDetailRef.value) { + adminUserDetailRef.value.focusFirstElement?.() + } + } +} + +function handlePanelNavigateOut(direction) { + if (hasActiveModal.value) return + + if (direction === 'up') { + // Focus breadcrumbs - focus the current page's crumb + breadcrumbsRef.value?.focusCurrent?.() + } +} + async function refreshUserDetail() { await loadOrgs() if (selectedUser.value) { @@ -483,7 +523,7 @@ async function submitDialog() {

{{ pageHeading }}

- +
@@ -492,9 +532,11 @@ async function submitDialog() {
diff --git a/frontend/src/admin/AdminOrgDetail.vue b/frontend/src/admin/AdminOrgDetail.vue index 8efbbe7..c619775 100644 --- a/frontend/src/admin/AdminOrgDetail.vue +++ b/frontend/src/admin/AdminOrgDetail.vue @@ -1,12 +1,19 @@