Merge pull request #58462 from nextcloud/backport/58457/stable33

[stable33] fix(files): fix tab navigation from select all checkbox to batch actions
This commit is contained in:
John Molakvoæ 2026-03-18 22:26:03 +01:00 committed by GitHub
commit d4894b1af1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 58 additions and 4 deletions

View file

@ -9,6 +9,7 @@
@keyup.esc.exact="resetSelection">
<NcCheckboxRadioSwitch
v-bind="selectAllBind"
:id="FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID"
data-cy-files-list-selection-checkbox
@update:model-value="onToggleAll" />
</th>
@ -79,6 +80,7 @@ import { t } from '@nextcloud/l10n'
import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
import { defineComponent } from 'vue'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import { FILE_LIST_HEAD_FIRST_BATCH_ACTION_ID } from './FilesListTableHeaderActions.vue'
import FilesListTableHeaderButton from './FilesListTableHeaderButton.vue'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import { useRouteParameters } from '../composables/useRouteParameters.ts'
@ -88,6 +90,8 @@ import { useActiveStore } from '../store/active.ts'
import { useFilesStore } from '../store/files.ts'
import { useSelectionStore } from '../store/selection.ts'
export const FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID = 'files-list-header-select-all-checkbox'
export default defineComponent({
name: 'FilesListTableHeader',
@ -137,6 +141,8 @@ export default defineComponent({
directory,
isNarrow,
FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID,
}
},
@ -196,6 +202,11 @@ export default defineComponent({
})
},
mounted() {
const selectAllCheckbox = document.getElementById(FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID)
selectAllCheckbox?.addEventListener('keydown', this.onSelectAllCheckboxFocusOut)
},
methods: {
ariaSortForMode(mode: string): 'ascending' | 'descending' | undefined {
if (this.sortingMode === mode) {
@ -231,6 +242,18 @@ export default defineComponent({
this.selectionStore.reset()
},
onSelectAllCheckboxFocusOut(event: KeyboardEvent) {
// If the user tabbed further and we have a batch action to tab to
const firstBatchActionButton = document.getElementById(FILE_LIST_HEAD_FIRST_BATCH_ACTION_ID)
if (event.code === 'Tab' && !event.shiftKey && !event.metaKey && firstBatchActionButton) {
event.preventDefault()
event.stopPropagation()
firstBatchActionButton.focus()
logger.debug('Focusing first batch action button')
}
},
t,
},
})

View file

@ -16,7 +16,8 @@
@close="openedSubmenu = null">
<!-- Default actions list-->
<NcActionButton
v-for="action in enabledMenuActions"
v-for="(action, idx) in enabledMenuActions"
:id="idx === 0 ? FILE_LIST_HEAD_FIRST_BATCH_ACTION_ID : undefined"
:key="action.id"
:ref="`action-batch-${action.id}`"
:class="{
@ -84,6 +85,7 @@ import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft.vue'
import { FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID } from './FilesListTableHeader.vue'
import { useFileActions } from '../composables/useFileActions.ts'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import logger from '../logger.ts'
@ -93,6 +95,8 @@ import { useActiveStore } from '../store/active.ts'
import { useFilesStore } from '../store/files.ts'
import { useSelectionStore } from '../store/selection.ts'
export const FILE_LIST_HEAD_FIRST_BATCH_ACTION_ID = 'files-list-head-first-batch-action'
export default defineComponent({
name: 'FilesListTableHeaderActions',
@ -149,6 +153,8 @@ export default defineComponent({
boundariesElement,
inlineActions,
FILE_LIST_HEAD_FIRST_BATCH_ACTION_ID,
}
},
@ -269,6 +275,17 @@ export default defineComponent({
},
},
mounted() {
const firstActionId = this.enabledMenuActions.at(0)?.id
const firstButton = this.$refs.actionsMenu?.$refs?.[`action-batch-${firstActionId}`]
if (firstButton) {
firstButton.$el.focus()
logger.debug('Focusing first batch action button')
firstButton.$el.addEventListener('focusout', this.onFirstButtonFocusOut)
}
},
methods: {
/**
* Get a cached note from the store
@ -343,6 +360,20 @@ export default defineComponent({
}
},
// When focusing out the first button outside the header actions
// we can return back to the select all checkbox
onFirstButtonFocusOut(event: FocusEvent) {
// If the focus is still within this component, do nothing
if (this.$el.contains(event.relatedTarget)) {
return
}
event.preventDefault()
event.stopPropagation()
document.getElementById(FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID)?.focus()
logger.debug('Focusing select all checkbox again')
},
t: translate,
},
})

4
dist/files-main.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long