Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: emulate structural heading navigation to workaround problems with screen readers and the dynamic scroll #2978

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ You can also check [on GitHub](https://github.com/nextcloud/news/releases), the
## [25.x.x]
### Changed
- Show red error bubble only if more than 8 updates fail.
- Emulate structural heading navigation in screen reader mode with the jump to previous/next articles keys
- Add `PageDown` and `PageUp` for article heading navigation in screen reader mode

### Fixed

Expand Down
16 changes: 13 additions & 3 deletions src/components/feed-display/FeedItemDisplay.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<template>
<article class="feed-item-display"
<div class="feed-item-display"
:class="{ 'screenreader': screenReaderMode }"
:aria-posinset="itemIndex"
:aria-setsize="itemCount"
:aria-posinset="itemIndex">
@focusin="selectItemOnFocus">
<ShareItem v-if="showShareMenu" :item-id="item.id" @close="closeShareMenu()" />

<div class="action-bar">
Expand Down Expand Up @@ -124,7 +125,7 @@
<div class="body" :dir="item.rtl && 'rtl'" v-html="item.body" />
<!--eslint-enable-->
</div>
</article>
</div>
</template>

<script lang="ts">
Expand Down Expand Up @@ -196,6 +197,15 @@ export default Vue.extend({
clearSelected(): void {
this.$store.commit(MUTATIONS.SET_SELECTED_ITEM, { id: undefined })
},
/**
* Use parent click handler to select item when focused,
* needed by screen reader navigation
*/
selectItemOnFocus(): void {
if (this.screenReaderMode && this.$store.getters.selected !== this.item) {
this.$emit('click-item')
}
},
/**
* Returns locale formatted date string
*
Expand Down
14 changes: 13 additions & 1 deletion src/components/feed-display/FeedItemDisplayList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
<slot name="header" />
</div>

<button v-if="screenReaderMode"
v-shortkey="['pageup']"
class="hidden"
@shortkey="jumpToPreviousItem">
Prev
</button>
<button v-shortkey="['arrowleft']" class="hidden" @shortkey="jumpToPreviousItem">
Prev
</button>
Expand All @@ -14,6 +20,12 @@
<button v-shortkey="['p']" class="hidden" @shortkey="jumpToPreviousItem">
Prev
</button>
<button v-if="screenReaderMode"
v-shortkey="['pagedown']"
class="hidden"
@shortkey="jumpToNextItem">
Next
</button>
<button v-shortkey="['arrowright']" class="hidden" @shortkey="jumpToNextItem">
Next
</button>
Expand Down Expand Up @@ -64,7 +76,7 @@
:item-index="index + 1"
:item="item"
:class="{ 'active': selectedItem && selectedItem.id === item.id }"
@show-details="$emit('show-details')" />
@click-item="clickItem(item)" />
<FeedItemRow v-else
:key="item.id"
:ref="'feedItemRow' + item.id"
Expand Down
20 changes: 17 additions & 3 deletions src/components/feed-display/VirtualScroll.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default Vue.extend({
checkMarkRead: true,
seenItems: new Map(),
lastRendered: null,
elementToFocus: null,
}
},
computed: {
Expand All @@ -45,10 +46,10 @@ export default Vue.extend({
return this.$store.state.items.fetchingItems[this.fetchKey]
},
},
compactMode: {
displayMode: {
cache: false,
get() {
return this.$store.getters.displaymode === '1'
return this.$store.getters.displaymode
},
},
},
Expand Down Expand Up @@ -117,7 +118,7 @@ export default Vue.extend({
let renderedItems = 0
let upperPaddingItems = 0
let lowerPaddingItems = 0
const itemHeight = this.compactMode ? 44 : 111
const itemHeight = this.displayMode === '1' ? 44 : 111
const padding = GRID_ITEM_HEIGHT
if (this.$slots.default && this.$el && this.$el.getBoundingClientRect) {
const childComponents = this.$slots.default.filter(child => !!child.componentOptions)
Expand Down Expand Up @@ -160,11 +161,24 @@ export default Vue.extend({
const scrollTop = this.scrollTop
this.$nextTick(() => {
if (this.elementToShow) {
// Workaround for buggy scroll with screen readers.
// Remember currently selected item to focus on next tick
if (this.displayMode === '2') {
this.elementToFocus = this.elementToShow
}
this.elementToShow.scrollIntoView({ behavior: 'auto', block: 'start' })
this.elementToShow = null
} else {
this.$el.scrollTop = scrollTop
}
// Focus title link in article to emulate structural heading navigation
// with screen readers
if (this.elementToFocus) {
const titleLink = this.elementToFocus.querySelector('a')
if (titleLink) {
titleLink.focus()
}
}
})

return h('div', {
Expand Down
16 changes: 16 additions & 0 deletions src/components/modals/HelpModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@
</tr>
</thead>
<tbody>
<tr>
<td>
<kbd>PageDown</kbd>
</td>
<td>
{{ t('news', 'Jump to next article') }} {{ t('news', '(screenreader mode)') }}
</td>
</tr>
<tr>
<td>
<kbd>PageUp</kbd>
</td>
<td>
{{ t('news', 'Jump to previous article') }} {{ t('news', '(screenreader mode)') }}
</td>
</tr>
<tr>
<td>
<kbd>n</kbd>
Expand Down
Loading