Skip to content

Commit

Permalink
feat: emulate structural heading navigation to workaround problems wi…
Browse files Browse the repository at this point in the history
…th screen readers and the dynamic scroll

Signed-off-by: Wolfgang <[email protected]>
  • Loading branch information
wofferl committed Dec 12, 2024
1 parent 4a7c8ac commit fd41750
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 7 deletions.
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
- set correct input focus when opening `AddFeed` or `Share` modals
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 @@ -141,7 +142,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 @@ -213,6 +214,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

0 comments on commit fd41750

Please sign in to comment.