fix(front): autofocus follows the stacking order of elements

This commit is contained in:
upsiflu 2025-03-22 17:18:03 +01:00
parent 3044a88dbc
commit ef67b38018
4 changed files with 39 additions and 11 deletions

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, useSlots, onMounted } from 'vue' import { ref, computed, useSlots, onMounted, watch, onUnmounted, nextTick } from 'vue'
import { type ColorProps, type VariantProps, type DefaultProps, type RaisedProps, type PastelProps, color } from '~/composables/color' import { type ColorProps, type VariantProps, type DefaultProps, type RaisedProps, type PastelProps, color } from '~/composables/color'
import { type WidthProps, width } from '~/composables/width' import { type WidthProps, width } from '~/composables/width'
@ -65,9 +65,18 @@ const click = async (...args: any[]) => {
internalLoader.value = false internalLoader.value = false
} }
} }
onMounted(() => {
if (props.autofocus) button.value.focus() const previouslyFocusedElement = ref()
})
onMounted(() => props.autofocus && nextTick(() => {
previouslyFocusedElement.value = document.activeElement
previouslyFocusedElement.value?.blur()
button.value.focus()
}))
onUnmounted(() =>
previouslyFocusedElement.value?.focus()
)
</script> </script>
<template> <template>

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref } from 'vue' import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import onKeyboardShortcut from '~/composables/onKeyboardShortcut' import onKeyboardShortcut from '~/composables/onKeyboardShortcut'
import { type ColorProps, type VariantProps, type DefaultProps, type RaisedProps, type PastelProps, color } from '~/composables/color.ts' import { type ColorProps, type VariantProps, type DefaultProps, type RaisedProps, type PastelProps, color } from '~/composables/color.ts'
@ -41,9 +41,17 @@ const { t } = useI18n()
const input = ref() const input = ref()
onMounted(() => { const previouslyFocusedElement = ref()
if (props.autofocus) input.value.focus()
}) onMounted(() => props.autofocus && nextTick(() => {
previouslyFocusedElement.value = document.activeElement
previouslyFocusedElement.value?.blur()
input.value.focus()
}))
onUnmounted(() =>
previouslyFocusedElement.value?.focus()
)
const model = defineModel<string|number>({ required: true }) const model = defineModel<string|number>({ required: true })
</script> </script>

View File

@ -13,7 +13,8 @@ const props = defineProps<{
overPopover?: true, overPopover?: true,
destructive?: true, destructive?: true,
cancel?: string, cancel?: string,
icon? : string icon?: string,
autofocus?: true | 'off'
} &(ColorProps | DefaultProps)>() } &(ColorProps | DefaultProps)>()
const isOpen = defineModel<boolean>({ default: false }) const isOpen = defineModel<boolean>({ default: false })
@ -23,8 +24,11 @@ const previouslyFocusedElement = ref()
// Handle focus and inertness of the elements behind the modal // Handle focus and inertness of the elements behind the modal
watchEffect(() => { watchEffect(() => {
if (isOpen.value) { if (isOpen.value) {
previouslyFocusedElement.value = document.activeElement nextTick(()=>{
document.querySelector('#app')?.setAttribute('inert', 'true') previouslyFocusedElement.value = document.activeElement
previouslyFocusedElement.value?.blur()
document.querySelector('#app')?.setAttribute('inert', 'true')
})
} else { } else {
nextTick(() => previouslyFocusedElement.value?.focus()) nextTick(() => previouslyFocusedElement.value?.focus())
document.querySelector('#app')?.removeAttribute('inert') document.querySelector('#app')?.removeAttribute('inert')
@ -93,6 +97,7 @@ onKeyboardShortcut('escape', () => { isOpen.value = false })
icon="bi-x-lg" icon="bi-x-lg"
ghost ghost
align-self="baseline" align-self="baseline"
:autofocus="props.autofocus === undefined ? ($slots.actions || cancel ? undefined : true) : props.autofocus !== 'off'"
@click="isOpen = false" @click="isOpen = false"
/> />
</Layout> </Layout>

View File

@ -335,6 +335,12 @@ primary
</Layout> </Layout>
## Auto-focus the close button
If there are no action slots and no cancel button, the close button is auto-focused.
The `autofocus` prop, when set to `off`, overrides this behavior.
## Responsivity ## Responsivity
### Designing for small screens ### Designing for small screens