]> source.dussan.org Git - nextcloud-server.git/commitdiff
fix(theming): App order settings - ensure the focus is kept on button
authorFerdinand Thiessen <opensource@fthiessen.de>
Fri, 20 Oct 2023 22:34:27 +0000 (00:34 +0200)
committerFerdinand Thiessen <opensource@fthiessen.de>
Sat, 21 Oct 2023 01:52:18 +0000 (03:52 +0200)
When pressing a button for changing the app order that button should keep the focus after reordering the list.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
apps/theming/src/components/AppOrderSelectorElement.vue
cypress/e2e/theming/navigation-bar-settings.cy.ts

index ee795b6272a851951aca1c5cbdf4ea15f5052910..73da7579d59c9a995569046e57ac0aec7f7e2601 100644 (file)
 
                <div class="order-selector-element__actions">
                        <NcButton v-show="!isFirst && !app.default"
+                               ref="buttonUp"
                                :aria-label="t('settings', 'Move up')"
                                data-cy-app-order-button="up"
                                type="tertiary-no-background"
-                               @click="$emit('move:up')">
+                               @click="moveUp">
                                <template #icon>
                                        <IconArrowUp :size="20" />
                                </template>
                        </NcButton>
                        <div v-show="isFirst || !!app.default" aria-hidden="true" class="order-selector-element__placeholder" />
                        <NcButton v-show="!isLast && !app.default"
+                               ref="buttonDown"
                                :aria-label="t('settings', 'Move down')"
                                data-cy-app-order-button="down"
                                type="tertiary-no-background"
-                               @click="$emit('move:down')">
+                               @click="moveDown">
                                <template #icon>
                                        <IconArrowDown :size="20" />
                                </template>
 </template>
 
 <script lang="ts">
+import type { PropType } from 'vue'
+
 import { translate as t } from '@nextcloud/l10n'
-import { PropType, defineComponent } from 'vue'
+import { defineComponent, nextTick, onUpdated, ref } from 'vue'
 
 import IconArrowDown from 'vue-material-design-icons/ArrowDown.vue'
 import IconArrowUp from 'vue-material-design-icons/ArrowUp.vue'
@@ -86,8 +90,55 @@ export default defineComponent({
                'move:up': () => true,
                'move:down': () => true,
        },
-       setup() {
+       setup(props, { emit }) {
+               const buttonUp = ref()
+               const buttonDown = ref()
+
+               /**
+                * Used to decide if we need to trigger focus() an a button on update
+                */
+               let needsFocus = 0
+
+               /**
+                * Handle move up, ensure focus is kept on the button
+                */
+               const moveUp = () => {
+                       emit('move:up')
+                       needsFocus = 1 // request focus on buttonUp
+               }
+
+               /**
+                * Handle move down, ensure focus is kept on the button
+                */
+                const moveDown = () => {
+                       emit('move:down')
+                       needsFocus = -1 // request focus on buttonDown
+               }
+
+               /**
+                * onUpdated hook is used to reset the focus on the last used button (if requested)
+                * If the button is now visible anymore (because this element is the first/last) then the opposite button is focussed
+                */
+               onUpdated(() => {
+                       if (needsFocus !== 0) {
+                               // focus requested
+                               if ((needsFocus === 1 || props.isLast) && !props.isFirst) {
+                                       // either requested to btn up and it is not the first, or it was requested to btn down but it is the last
+                                       nextTick(() => buttonUp.value.$el.focus())
+                               } else {
+                                       nextTick(() => buttonDown.value.$el.focus())
+                               }
+                       }
+                       needsFocus = 0
+               })
+
                return {
+                       buttonUp,
+                       buttonDown,
+
+                       moveUp,
+                       moveDown,
+
                        t,
                }
        },
index 50c48d5ac6db7135194a5a7a45e49843c69ad040..a5657ee5a15728ba3bb29e064e51d36dfecc6d57 100644 (file)
@@ -210,3 +210,44 @@ describe('User theming set app order with default app', () => {
                })
        })
 })
+
+describe('User theming app order list accessibility', () => {
+       let user: User
+
+       before(() => {
+               cy.resetAdminTheming()
+               // Create random user for this test
+               cy.createRandomUser().then(($user) => {
+                       user = $user
+                       cy.login($user)
+               })
+       })
+
+       after(() => {
+               cy.deleteUser(user)
+       })
+
+       it('See the app order settings', () => {
+               cy.visit('/settings/user/theming')
+               cy.get('[data-cy-app-order]').scrollIntoView()
+               cy.get('[data-cy-app-order] [data-cy-app-order-element]').should('have.length', 2)
+       })
+
+       it('click the first button', () => {
+               cy.get('[data-cy-app-order] [data-cy-app-order-element]:first-of-type [data-cy-app-order-button="down"]').should('be.visible').click()
+       })
+
+       it('see the same app kept the focus', () => {
+               cy.get('[data-cy-app-order] [data-cy-app-order-element]:first-of-type [data-cy-app-order-button="down"]').should('not.have.focus')
+               cy.get('[data-cy-app-order] [data-cy-app-order-element]:last-of-type [data-cy-app-order-button="up"]').should('have.focus')
+       })
+
+       it('click the last button', () => {
+               cy.get('[data-cy-app-order] [data-cy-app-order-element]:last-of-type [data-cy-app-order-button="up"]').should('be.visible').click()
+       })
+
+       it('see the same app kept the focus', () => {
+               cy.get('[data-cy-app-order] [data-cy-app-order-element]:first-of-type [data-cy-app-order-button="down"]').should('have.focus')
+               cy.get('[data-cy-app-order] [data-cy-app-order-element]:last-of-type [data-cy-app-order-button="up"]').should('not.have.focus')
+       })
+})