aboutsummaryrefslogtreecommitdiffstats
path: root/apps/theming
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2023-10-30 16:42:28 +0100
committerFerdinand Thiessen <opensource@fthiessen.de>2023-10-31 21:56:13 +0100
commit716164c2850f835e236ad648f9774d5f6af5df07 (patch)
treeb4d892280fa98ff1d343002e50e86dc95f220918 /apps/theming
parentb4e707059def28bf4544ae43272bf62d5662961b (diff)
downloadnextcloud-server-716164c2850f835e236ad648f9774d5f6af5df07.tar.gz
nextcloud-server-716164c2850f835e236ad648f9774d5f6af5df07.zip
fix(theming): Add accessible information for screen readers to the app order selector
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'apps/theming')
-rw-r--r--apps/theming/src/components/AppOrderSelector.vue71
-rw-r--r--apps/theming/src/components/AppOrderSelectorElement.vue22
2 files changed, 80 insertions, 13 deletions
diff --git a/apps/theming/src/components/AppOrderSelector.vue b/apps/theming/src/components/AppOrderSelector.vue
index 392519de25e..d8238ea2282 100644
--- a/apps/theming/src/components/AppOrderSelector.vue
+++ b/apps/theming/src/components/AppOrderSelector.vue
@@ -1,21 +1,34 @@
<template>
- <ol ref="listElement" data-cy-app-order class="order-selector">
- <AppOrderSelectorElement v-for="app,index in appList"
- :key="`${app.id}${renderCount}`"
- ref="selectorElements"
- :app="app"
- :is-first="index === 0 || !!appList[index - 1].default"
- :is-last="index === value.length - 1"
- v-on="app.default ? {} : {
- 'move:up': () => moveUp(index),
- 'move:down': () => moveDown(index),
- }" />
- </ol>
+ <Fragment>
+ <div :id="statusInfoId"
+ aria-live="polite"
+ class="hidden-visually"
+ role="status">
+ {{ statusInfo }}
+ </div>
+ <ol ref="listElement" data-cy-app-order class="order-selector">
+ <AppOrderSelectorElement v-for="app,index in appList"
+ :key="`${app.id}${renderCount}`"
+ ref="selectorElements"
+ :app="app"
+ :aria-details="ariaDetails"
+ :aria-describedby="statusInfoId"
+ :is-first="index === 0 || !!appList[index - 1].default"
+ :is-last="index === value.length - 1"
+ v-on="app.default ? {} : {
+ 'move:up': () => moveUp(index),
+ 'move:down': () => moveDown(index),
+ 'update:focus': () => updateStatusInfo(index),
+ }" />
+ </ol>
+ </Fragment>
</template>
<script lang="ts">
+import { translate as t } from '@nextcloud/l10n'
import { useSortable } from '@vueuse/integrations/useSortable'
import { PropType, computed, defineComponent, onUpdated, ref } from 'vue'
+import { Fragment } from 'vue-frag'
import AppOrderSelectorElement from './AppOrderSelectorElement.vue'
@@ -32,9 +45,17 @@ export default defineComponent({
name: 'AppOrderSelector',
components: {
AppOrderSelectorElement,
+ Fragment,
},
props: {
/**
+ * Details like status information that need to be forwarded to the interactive elements
+ */
+ ariaDetails: {
+ type: String,
+ default: null,
+ },
+ /**
* List of apps to reorder
*/
value: {
@@ -125,6 +146,28 @@ export default defineComponent({
emit('update:value', [...before, props.value[index], ...after])
}
+ /**
+ * Additional status information to show to screen reader users for accessibility
+ */
+ const statusInfo = ref('')
+
+ /**
+ * ID to be used on the status info element
+ */
+ const statusInfoId = `sorting-status-info-${(Math.random() + 1).toString(36).substring(7)}`
+
+ /**
+ * Update the status information for the currently selected app
+ * @param index Index of the app that is currently selected
+ */
+ const updateStatusInfo = (index: number) => {
+ statusInfo.value = t('theming', 'Current selected app: {app}, position {position} of {total}', {
+ app: props.value[index].label,
+ position: index + 1,
+ total: props.value.length,
+ })
+ }
+
return {
appList,
listElement,
@@ -132,6 +175,10 @@ export default defineComponent({
moveDown,
moveUp,
+ statusInfoId,
+ statusInfo,
+ updateStatusInfo,
+
renderCount,
selectorElements,
}
diff --git a/apps/theming/src/components/AppOrderSelectorElement.vue b/apps/theming/src/components/AppOrderSelectorElement.vue
index 4c7266eb798..23e7b0b07c9 100644
--- a/apps/theming/src/components/AppOrderSelectorElement.vue
+++ b/apps/theming/src/components/AppOrderSelectorElement.vue
@@ -3,7 +3,8 @@
:class="{
'order-selector-element': true,
'order-selector-element--disabled': app.default
- }">
+ }"
+ @focusin="$emit('update:focus')">
<svg width="20"
height="20"
viewBox="0 0 20 20"
@@ -25,6 +26,8 @@
<NcButton v-show="!isFirst && !app.default"
ref="buttonUp"
:aria-label="t('settings', 'Move up')"
+ :aria-describedby="ariaDescribedby"
+ :aria-details="ariaDetails"
data-cy-app-order-button="up"
type="tertiary-no-background"
@click="moveUp">
@@ -36,6 +39,8 @@
<NcButton v-show="!isLast && !app.default"
ref="buttonDown"
:aria-label="t('settings', 'Move down')"
+ :aria-describedby="ariaDescribedby"
+ :aria-details="ariaDetails"
data-cy-app-order-button="down"
type="tertiary-no-background"
@click="moveDown">
@@ -73,6 +78,17 @@ export default defineComponent({
NcButton,
},
props: {
+ /**
+ * Needs to be forwarded to the buttons (as interactive elements)
+ */
+ ariaDescribedby: {
+ type: String,
+ default: null,
+ },
+ ariaDetails: {
+ type: String,
+ default: null,
+ },
app: {
type: Object as PropType<IApp>,
required: true,
@@ -89,6 +105,10 @@ export default defineComponent({
emits: {
'move:up': () => true,
'move:down': () => true,
+ /**
+ * We need this as Sortable.js removes all native focus event listeners
+ */
+ 'update:focus': () => true,
},
setup(props, { emit }) {
const buttonUp = ref()