33 - SPDX-License-Identifier: AGPL-3.0-or-later
44-->
55<template >
6- <tr class =" files-list__row-head" >
6+ <tr
7+ class =" files-list__row-head"
8+ :class =" { 'files-list__row-head--selected': !isNoneSelected }" >
79 <th
810 class =" files-list__column files-list__row-checkbox"
911 @keyup.esc.exact =" resetSelection" >
1012 <NcCheckboxRadioSwitch
1113 v-bind =" selectAllBind "
12- :id =" FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID "
1314 data-cy-files-list-selection-checkbox
1415 @update :model-value =" onToggleAll " />
16+
17+ <!-- Batch selection UI lives in the select-all cell so column headers
18+ stay valid and Tab order stays in document flow
19+ (select-all → actions → rows). -->
20+ <template v-if =" ! isNoneSelected " >
21+ <span class =" files-list__selected" >
22+ {{ n('files', '{count} selected', '{count} selected', selectedNodes.length, { count: selectedNodes.length }) }}
23+ </span >
24+ <FilesListTableHeaderActions
25+ :current-view =" currentView "
26+ :selected-nodes =" selectedNodes " />
27+ </template >
1528 </th >
1629
17- <!-- Columns display -->
30+ <!-- Columns display (always present so headers match body cells;
31+ visually hidden while a selection is active) -->
1832
1933 <!-- Link to file -->
2034 <th
2135 class =" files-list__column files-list__row-name files-list__column--sortable"
22- :aria-sort =" ariaSortForMode('basename')" >
36+ :class =" { 'hidden-visually': !isNoneSelected }"
37+ :aria-sort =" ariaSortForMode('basename')"
38+ :inert =" !isNoneSelected" >
2339 <!-- Icon or preview -->
2440 <span class =" files-list__row-icon" />
2541
2844 </th >
2945
3046 <!-- Actions -->
31- <th class =" files-list__row-actions" >
47+ <th
48+ class =" files-list__row-actions"
49+ :class =" { 'hidden-visually': !isNoneSelected }"
50+ :inert =" !isNoneSelected" >
3251 <span class =" hidden-visually" >
3352 {{ t('files', 'Actions') }}
3453 </span >
3857 <th
3958 v-if =" isMimeAvailable"
4059 class =" files-list__column files-list__row-mime"
41- :class =" { 'files-list__column--sortable': isMimeAvailable }"
42- :aria-sort =" ariaSortForMode('mime')" >
60+ :class =" {
61+ 'files-list__column--sortable': isMimeAvailable,
62+ 'hidden-visually': !isNoneSelected,
63+ }"
64+ :aria-sort =" ariaSortForMode('mime')"
65+ :inert =" !isNoneSelected" >
4366 <FilesListTableHeaderButton :name =" t (' files' , ' File type' )" mode="mime" />
4467 </th >
4568
4669 <!-- Size -->
4770 <th
4871 v-if =" isSizeAvailable"
4972 class =" files-list__column files-list__row-size"
50- :class =" { 'files-list__column--sortable': isSizeAvailable }"
51- :aria-sort =" ariaSortForMode('size')" >
73+ :class =" {
74+ 'files-list__column--sortable': isSizeAvailable,
75+ 'hidden-visually': !isNoneSelected,
76+ }"
77+ :aria-sort =" ariaSortForMode('size')"
78+ :inert =" !isNoneSelected" >
5279 <FilesListTableHeaderButton :name =" t (' files' , ' Size' )" mode="size" />
5380 </th >
5481
5582 <!-- Mtime -->
5683 <th
5784 v-if =" isMtimeAvailable"
5885 class =" files-list__column files-list__row-mtime"
59- :class =" { 'files-list__column--sortable': isMtimeAvailable }"
60- :aria-sort =" ariaSortForMode('mtime')" >
86+ :class =" {
87+ 'files-list__column--sortable': isMtimeAvailable,
88+ 'hidden-visually': !isNoneSelected,
89+ }"
90+ :aria-sort =" ariaSortForMode('mtime')"
91+ :inert =" !isNoneSelected" >
6192 <FilesListTableHeaderButton :name =" t (' files' , ' Modified' )" mode="mtime" />
6293 </th >
6394
6697 v-for =" column in columns"
6798 :key =" column.id"
6899 :class =" classForColumn(column)"
69- :aria-sort =" ariaSortForMode(column.id)" >
100+ :aria-sort =" ariaSortForMode(column.id)"
101+ :inert =" !isNoneSelected" >
70102 <FilesListTableHeaderButton v-if =" !! column .sort " :name =" column .title " :mode =" column .id " />
71103 <span v-else >
72104 {{ column.title }}
76108</template >
77109
78110<script lang="ts">
79- import type { Node } from ' @nextcloud/files'
111+ import type { Node , View } from ' @nextcloud/files'
80112import type { PropType } from ' vue'
81113import type { FileSource } from ' ../types.ts'
82114
83- import { t } from ' @nextcloud/l10n'
115+ import { n , t } from ' @nextcloud/l10n'
84116import { useHotKey } from ' @nextcloud/vue/composables/useHotKey'
85117import { defineComponent } from ' vue'
86118import NcCheckboxRadioSwitch from ' @nextcloud/vue/components/NcCheckboxRadioSwitch'
87- import { FILE_LIST_HEAD_FIRST_BATCH_ACTION_ID } from ' ./FilesListTableHeaderActions.vue'
119+ import FilesListTableHeaderActions from ' ./FilesListTableHeaderActions.vue'
88120import FilesListTableHeaderButton from ' ./FilesListTableHeaderButton.vue'
89121import { useFileListWidth } from ' ../composables/useFileListWidth.ts'
90122import { useRouteParameters } from ' ../composables/useRouteParameters.ts'
@@ -94,12 +126,11 @@ import { useFilesStore } from '../store/files.ts'
94126import { useSelectionStore } from ' ../store/selection.ts'
95127import { logger } from ' ../utils/logger.ts'
96128
97- export const FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID = ' files-list-header-select-all-checkbox'
98-
99129export default defineComponent ({
100130 name: ' FilesListTableHeader' ,
101131
102132 components: {
133+ FilesListTableHeaderActions ,
103134 FilesListTableHeaderButton ,
104135 NcCheckboxRadioSwitch ,
105136 },
@@ -109,6 +140,11 @@ export default defineComponent({
109140 ],
110141
111142 props: {
143+ currentView: {
144+ type: Object as PropType <View >,
145+ required: true ,
146+ },
147+
112148 isMimeAvailable: {
113149 type: Boolean ,
114150 default: false ,
@@ -145,8 +181,6 @@ export default defineComponent({
145181
146182 directory ,
147183 isNarrow ,
148-
149- FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID ,
150184 }
151185 },
152186
@@ -206,11 +240,6 @@ export default defineComponent({
206240 })
207241 },
208242
209- mounted() {
210- const selectAllCheckbox = document .getElementById (FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID )
211- selectAllCheckbox ?.addEventListener (' keydown' , this .onSelectAllCheckboxFocusOut )
212- },
213-
214243 methods: {
215244 ariaSortForMode(mode : string ): ' ascending' | ' descending' | undefined {
216245 if (this .sortingMode === mode ) {
@@ -224,6 +253,7 @@ export default defineComponent({
224253 ' files-list__column--sortable' : !! column .sort ,
225254 ' files-list__row-column-custom' : true ,
226255 [` files-list__row-${this .activeStore .activeView ?.id }-${column .id } ` ]: true ,
256+ ' hidden-visually' : ! this .isNoneSelected ,
227257 }
228258 },
229259
@@ -246,18 +276,7 @@ export default defineComponent({
246276 this .selectionStore .reset ()
247277 },
248278
249- onSelectAllCheckboxFocusOut(event : KeyboardEvent ) {
250- // If the user tabbed further and we have a batch action to tab to
251- const firstBatchActionButton = document .getElementById (FILE_LIST_HEAD_FIRST_BATCH_ACTION_ID )
252- if (event .code === ' Tab' && ! event .shiftKey && ! event .metaKey && firstBatchActionButton ) {
253- event .preventDefault ()
254- event .stopPropagation ()
255-
256- firstBatchActionButton .focus ()
257- logger .debug (' Focusing first batch action button' )
258- }
259- },
260-
279+ n ,
261280 t ,
262281 },
263282})
@@ -274,4 +293,11 @@ export default defineComponent({
274293 }
275294}
276295
296+ .files-list__selected {
297+ padding-inline-end : 12px ;
298+ white-space : nowrap ;
299+ font-variant-numeric : tabular-nums ;
300+ flex-shrink : 0 ;
301+ color : var (--color-main-text );
302+ }
277303 </style >
0 commit comments