Skip to content

Commit d4894b1

Browse files
authored
Merge pull request #58462 from nextcloud/backport/58457/stable33
[stable33] fix(files): fix tab navigation from select all checkbox to batch actions
2 parents 1aa4d13 + 136c5a0 commit d4894b1

File tree

4 files changed

+58
-4
lines changed

4 files changed

+58
-4
lines changed

apps/files/src/components/FilesListTableHeader.vue

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
@keyup.esc.exact="resetSelection">
1010
<NcCheckboxRadioSwitch
1111
v-bind="selectAllBind"
12+
:id="FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID"
1213
data-cy-files-list-selection-checkbox
1314
@update:model-value="onToggleAll" />
1415
</th>
@@ -79,6 +80,7 @@ import { t } from '@nextcloud/l10n'
7980
import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
8081
import { defineComponent } from 'vue'
8182
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
83+
import { FILE_LIST_HEAD_FIRST_BATCH_ACTION_ID } from './FilesListTableHeaderActions.vue'
8284
import FilesListTableHeaderButton from './FilesListTableHeaderButton.vue'
8385
import { useFileListWidth } from '../composables/useFileListWidth.ts'
8486
import { useRouteParameters } from '../composables/useRouteParameters.ts'
@@ -88,6 +90,8 @@ import { useActiveStore } from '../store/active.ts'
8890
import { useFilesStore } from '../store/files.ts'
8991
import { useSelectionStore } from '../store/selection.ts'
9092
93+
export const FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID = 'files-list-header-select-all-checkbox'
94+
9195
export default defineComponent({
9296
name: 'FilesListTableHeader',
9397
@@ -137,6 +141,8 @@ export default defineComponent({
137141
138142
directory,
139143
isNarrow,
144+
145+
FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID,
140146
}
141147
},
142148
@@ -196,6 +202,11 @@ export default defineComponent({
196202
})
197203
},
198204
205+
mounted() {
206+
const selectAllCheckbox = document.getElementById(FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID)
207+
selectAllCheckbox?.addEventListener('keydown', this.onSelectAllCheckboxFocusOut)
208+
},
209+
199210
methods: {
200211
ariaSortForMode(mode: string): 'ascending' | 'descending' | undefined {
201212
if (this.sortingMode === mode) {
@@ -231,6 +242,18 @@ export default defineComponent({
231242
this.selectionStore.reset()
232243
},
233244
245+
onSelectAllCheckboxFocusOut(event: KeyboardEvent) {
246+
// If the user tabbed further and we have a batch action to tab to
247+
const firstBatchActionButton = document.getElementById(FILE_LIST_HEAD_FIRST_BATCH_ACTION_ID)
248+
if (event.code === 'Tab' && !event.shiftKey && !event.metaKey && firstBatchActionButton) {
249+
event.preventDefault()
250+
event.stopPropagation()
251+
252+
firstBatchActionButton.focus()
253+
logger.debug('Focusing first batch action button')
254+
}
255+
},
256+
234257
t,
235258
},
236259
})

apps/files/src/components/FilesListTableHeaderActions.vue

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
@close="openedSubmenu = null">
1717
<!-- Default actions list-->
1818
<NcActionButton
19-
v-for="action in enabledMenuActions"
19+
v-for="(action, idx) in enabledMenuActions"
20+
:id="idx === 0 ? FILE_LIST_HEAD_FIRST_BATCH_ACTION_ID : undefined"
2021
:key="action.id"
2122
:ref="`action-batch-${action.id}`"
2223
:class="{
@@ -84,6 +85,7 @@ import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
8485
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
8586
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
8687
import ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft.vue'
88+
import { FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID } from './FilesListTableHeader.vue'
8789
import { useFileActions } from '../composables/useFileActions.ts'
8890
import { useFileListWidth } from '../composables/useFileListWidth.ts'
8991
import logger from '../logger.ts'
@@ -93,6 +95,8 @@ import { useActiveStore } from '../store/active.ts'
9395
import { useFilesStore } from '../store/files.ts'
9496
import { useSelectionStore } from '../store/selection.ts'
9597
98+
export const FILE_LIST_HEAD_FIRST_BATCH_ACTION_ID = 'files-list-head-first-batch-action'
99+
96100
export default defineComponent({
97101
name: 'FilesListTableHeaderActions',
98102
@@ -149,6 +153,8 @@ export default defineComponent({
149153
150154
boundariesElement,
151155
inlineActions,
156+
157+
FILE_LIST_HEAD_FIRST_BATCH_ACTION_ID,
152158
}
153159
},
154160
@@ -269,6 +275,17 @@ export default defineComponent({
269275
},
270276
},
271277
278+
mounted() {
279+
const firstActionId = this.enabledMenuActions.at(0)?.id
280+
const firstButton = this.$refs.actionsMenu?.$refs?.[`action-batch-${firstActionId}`]
281+
if (firstButton) {
282+
firstButton.$el.focus()
283+
logger.debug('Focusing first batch action button')
284+
285+
firstButton.$el.addEventListener('focusout', this.onFirstButtonFocusOut)
286+
}
287+
},
288+
272289
methods: {
273290
/**
274291
* Get a cached note from the store
@@ -343,6 +360,20 @@ export default defineComponent({
343360
}
344361
},
345362
363+
// When focusing out the first button outside the header actions
364+
// we can return back to the select all checkbox
365+
onFirstButtonFocusOut(event: FocusEvent) {
366+
// If the focus is still within this component, do nothing
367+
if (this.$el.contains(event.relatedTarget)) {
368+
return
369+
}
370+
371+
event.preventDefault()
372+
event.stopPropagation()
373+
document.getElementById(FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID)?.focus()
374+
logger.debug('Focusing select all checkbox again')
375+
},
376+
346377
t: translate,
347378
},
348379
})

dist/files-main.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/files-main.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)