Skip to content
2 changes: 1 addition & 1 deletion resources/js/components/ui/Listing/Search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defineExpose({ focus });
<div class="flex-1 max-w-sm" :class="{ 'max-w-60!': activeFilterBadgeCount > 2 }">
<label for="listings-search" class="sr-only">{{ __('Search entries') }}</label>
<Input
autofocus
:focus="true"
ref="input"
icon="magnifying-glass"
id="listings-search"
Expand Down
52 changes: 47 additions & 5 deletions resources/js/pages/layout/Layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import { ConfigProvider } from 'reka-ui';
import SessionExpiry from '@/components/SessionExpiry.vue';
import LicensingAlert from '@/components/LicensingAlert.vue';
import PortalTargets from '@/components/portals/PortalTargets.vue';
import Tooltips from '@/components/Tooltips.vue';
import { provide, watch, ref } from 'vue';
import { provide, watch, ref, onMounted, onUnmounted, nextTick } from 'vue';
import { router } from '@inertiajs/vue3';
import useBodyClasses from './body-classes.js';
import useStatamicPageProps from '@/composables/page-props.js';

useBodyClasses('bg-global-header-bg font-sans leading-normal text-gray-900 dark:text-white');

Expand All @@ -22,6 +21,49 @@ watch(() => props.additionalBreadcrumbs, (newVal) => additionalBreadcrumbs.value
provide('layout', {
additionalBreadcrumbs,
});

// Focus management: focus main element if no input has auto-focus
let navigationListener = null;

function focusMain() {
// Wait for components to mount and autofocus to process
nextTick(() => {
requestAnimationFrame(() => {
setTimeout(() => {
// If an input is already focused, we're done
if (document.activeElement?.matches('input, textarea, select, [contenteditable]')) {
return;
}

// Find any input with autofocus attribute (including nested in UI components)
const autofocusInput = document.querySelector('input[autofocus], textarea[autofocus], select[autofocus]') ||
document.querySelector('[data-ui-input] input[autofocus]');

// If autofocus input exists but isn't focused, focus it manually
if (autofocusInput && document.activeElement !== autofocusInput) {
autofocusInput.focus();
return;
}

// Otherwise, focus the content card
if (!autofocusInput) {
document.querySelector('#content-card')?.focus();
}
}, 100);
});
});
}

onMounted(() => {
navigationListener = router.on('success', focusMain);
focusMain();
});

onUnmounted(() => {
if (navigationListener) {
navigationListener();
}
});
</script>

<template>
Expand All @@ -32,8 +74,8 @@ provide('layout', {

<main id="main" class="flex bg-body-bg dark:border-t dark:border-body-border rounded-t-2xl fixed top-14 inset-x-0 bottom-0 min-h-[calc(100vh-3.5rem)]">
<Nav />
<div id="main-content" class="main-content sm:p-2 h-full flex-1 overflow-y-auto rounded-t-2xl">
<div id="content-card" class="relative content-card grid min-h-full">
<div id="main-content" class="main-content sm:p-2 h-full flex-1 overflow-y-auto focus:outline-none rounded-t-2xl">
<div id="content-card" tabindex="-1" class="focus:outline-none relative content-card grid min-h-full">
<div class="w-full">
<slot />
</div>
Expand Down