-
Notifications
You must be signed in to change notification settings - Fork 100
Add keyboard navigation to alphabetical and changes lists #2023
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
Changes from all commits
b7ab056
e1849ed
858efd4
15da09d
0ce114d
cb5710d
d821334
e986760
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -191,40 +191,101 @@ function startChangesApp () { | |
|
|
||
| tabChangesApp.component('tab-changes', { | ||
| props: ['changedConcepts', 'selectedConcept', 'loadingConcepts', 'loadingMoreConcepts', 'loadingMessage', 'toConceptPageAriaMessage', 'listStyle'], | ||
| data () { | ||
| return { | ||
| conceptInFocus: 0, | ||
| changedConceptsLength: 0 | ||
| } | ||
| }, | ||
| inject: ['partialPageLoad', 'getConceptURL'], | ||
| emits: ['selectConcept'], | ||
| methods: { | ||
| loadConcept (event, uri) { | ||
| loadConcept (event, uri, i) { | ||
| this.conceptInFocus = i | ||
| partialPageLoad(event, getConceptURL(uri)) | ||
| this.$emit('selectConcept', uri) | ||
| }, | ||
| getIndexedConcepts () { | ||
| // Give each uri and replacedBy in changedConcepts a unique index for keyboard navigation | ||
| let counter = 0 | ||
|
|
||
| const indexed = new Map( | ||
| [...this.changedConcepts].map(([month, entries]) => [ | ||
| month, | ||
| entries.map(entry => { | ||
| const result = { ...entry, index: counter++ } | ||
| if (entry.replacedBy) result.replacedByIndex = counter++ | ||
| return result | ||
| }) | ||
| ]) | ||
| ) | ||
| this.changedConceptsLength = counter | ||
| return indexed | ||
| }, | ||
| handleKeydownEvent (e) { | ||
| if (e.key === ' ') { | ||
| // Click on link currently in focus | ||
| e.preventDefault() | ||
| this.$refs['concept' + this.conceptInFocus][0].click() | ||
| } else if (e.key === 'ArrowDown') { | ||
| // On last element move focus to first list item, otherwise next list item | ||
| e.preventDefault() | ||
| this.conceptInFocus = (this.conceptInFocus + 1) % this.changedConceptsLength | ||
| this.$refs['concept' + this.conceptInFocus][0].focus() | ||
| } else if (e.key === 'ArrowUp') { | ||
| // On first element move focus to last list item, otherwise to previous list item | ||
| e.preventDefault() | ||
| if (this.conceptInFocus === 0) { | ||
| this.conceptInFocus = this.changedConceptsLength - 1 | ||
| } else { | ||
| this.conceptInFocus -= 1 | ||
| } | ||
| this.$refs['concept' + this.conceptInFocus][0].focus() | ||
| } else if (e.key === 'End') { | ||
| // Move focus to last list item | ||
| e.preventDefault() | ||
| this.conceptInFocus = this.changedConceptsLength - 1 | ||
| this.$refs['concept' + this.conceptInFocus][0].focus() | ||
| } else if (e.key === 'Home') { | ||
| // Move focus to first list item | ||
| e.preventDefault() | ||
| this.conceptInFocus = 0 | ||
| this.$refs['concept' + this.conceptInFocus][0].focus() | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
|
Comment on lines
+225
to
255
|
||
| }, | ||
| template: ` | ||
| <div class="sidebar-list pt-3" :style="listStyle" ref="list"> | ||
| <div class="sidebar-list pt-3" tabindex="-1" :style="listStyle" ref="list"> | ||
| <template v-if="loadingConcepts"> | ||
| <div> | ||
| {{ loadingMessage }} <i class="fa-solid fa-spinner fa-spin-pulse"></i> | ||
| </div> | ||
| </template> | ||
| <template v-else> | ||
| <ul class="list-group" v-if="changedConcepts.length !== 0"> | ||
| <template v-for="[month, concepts] in changedConcepts"> | ||
| <template v-for="[month, concepts] in getIndexedConcepts()"> | ||
| <li class="list-group-item py-1 px-2"> | ||
| <h2 class="pb-1">{{ month }}</h2> | ||
| </li> | ||
| <li v-for="concept in concepts" class="list-group-item py-1 px-2"> | ||
| <template v-if="concept.replacedBy"> | ||
| <a :class="{ 'selected': selectedConcept === concept.uri }" | ||
| :href="getConceptURL(concept.uri)" | ||
| @click="loadConcept($event, concept.uri)" | ||
| :tabindex="concept.index === conceptInFocus ? 0 : -1" | ||
| :ref="'concept' + concept.index" | ||
| @click="loadConcept($event, concept.uri, concept.index)" | ||
| @keydown="handleKeydownEvent($event)" | ||
| > | ||
| <s>{{ concept.prefLabel }}</s> | ||
| <span class="visually-hidden">{{ toConceptPageAriaMessage }}</span> | ||
| </a> | ||
| <i class="fa-solid fa-arrow-right"></i> | ||
| <a :class="{ 'selected': selectedConcept === concept.replacedBy }" | ||
| :href="getConceptURL(concept.replacedBy)" | ||
| @click="loadConcept($event, concept.replacedBy)" | ||
| :tabindex="concept.replacedByIndex === conceptInFocus ? 0 : -1" | ||
| :ref="'concept' + concept.replacedByIndex" | ||
| @click="loadConcept($event, concept.replacedBy, concept.replacedByIndex)" | ||
| @keydown="handleKeydownEvent($event)" | ||
| > | ||
| {{ concept.replacingLabel }} | ||
| <span class="visually-hidden">{{ toConceptPageAriaMessage }}</span> | ||
|
|
@@ -233,7 +294,10 @@ function startChangesApp () { | |
| <template v-else> | ||
| <a :class="{ 'selected': selectedConcept === concept.uri }" | ||
| :href="getConceptURL(concept.uri)" | ||
| @click="loadConcept($event, concept.uri)" | ||
| :tabindex="concept.index === conceptInFocus ? 0 : -1" | ||
| :ref="'concept' + concept.index" | ||
| @click="loadConcept($event, concept.uri, concept.index)" | ||
| @keydown="handleKeydownEvent($event)" | ||
| > | ||
| {{ concept.prefLabel }} | ||
| <span class="visually-hidden">{{ toConceptPageAriaMessage }}</span> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -144,5 +144,21 @@ describe('Alphabetical index', () => { | |
| cy.get('.aria-live-message').invoke('text').should('equal', 'Concepts loaded for letter B') | ||
| // Check that new concepts are loaded | ||
| cy.get('#tab-alphabetical').find('.sidebar-list li').first().invoke('text').should('contain', 'birch bark manuscripts') | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test only verifies moving between entries, but not opening them (with space and/or enter). I think that's pretty crucial functionality so it should be tested as well. |
||
| // Press tab | ||
| cy.press(Cypress.Keyboard.Keys.TAB) | ||
| // Check that first list item has focus | ||
| cy.get('#tab-alphabetical').find('.sidebar-list li a').eq(0).should('have.focus') | ||
| // Press down arrow key | ||
| cy.press(Cypress.Keyboard.Keys.DOWN) | ||
| // Check that second list item has focus | ||
| cy.get('#tab-alphabetical').find('.sidebar-list li a').eq(1).should('have.focus') | ||
| // Press up arrow key | ||
| cy.press(Cypress.Keyboard.Keys.UP) | ||
| // Check that first list item has focus again | ||
| cy.get('#tab-alphabetical').find('.sidebar-list li a').eq(0).should('have.focus') | ||
| // Check that pressing space opens concept page | ||
| cy.press(Cypress.Keyboard.Keys.SPACE) | ||
| cy.get('#concept-heading h1', {'timeout': 15000}).invoke('text').should('equal', 'birch bark manuscripts') | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -109,5 +109,27 @@ describe('New and removed view', () => { | |
| // Check that concepts have correct Aria labels | ||
| cy.get('#tab-changes').find('.sidebar-list li a span').eq(0).invoke('text').should('contain', 'Gå till begreppssidan') | ||
|
|
||
| }) | ||
| }) | ||
| it('Keyboard navigation', () => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto, test opening (with space/enter) as well. |
||
| // go to YSO vocab front page in English | ||
| cy.visit('/yso/en/') | ||
| // Click on changes tab and wait until list has loaded | ||
| cy.get('#changes').click() | ||
| cy.get('#tab-changes').find('.sidebar-list li a span').eq(0) | ||
| // Press tab key | ||
| cy.press(Cypress.Keyboard.Keys.TAB) | ||
| // Check that first list item has focus | ||
| cy.get('#tab-changes').find('.sidebar-list a').eq(0).should('have.focus') | ||
| // Press down arrow key | ||
| cy.press(Cypress.Keyboard.Keys.DOWN) | ||
| // Check that second list item has focus | ||
| cy.get('#tab-changes').find('.sidebar-list a').eq(1).should('have.focus') | ||
| // Press up arrow key | ||
| cy.press(Cypress.Keyboard.Keys.UP) | ||
| // Check that first list item has focus again | ||
| cy.get('#tab-changes').find('.sidebar-list a').eq(0).should('have.focus') | ||
| // Check that pressing space opens concept page | ||
| cy.press(Cypress.Keyboard.Keys.SPACE) | ||
| cy.get('#concept-heading h1', {'timeout': 15000}).invoke('text').should('equal', 'Bell beaker culture') | ||
| }) | ||
| }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Function with high complexity (count = 7): handleKeydownEvent [qlty:function-complexity]