From 2d33f141f4ea58f9449a0a2031a2a4563ed450d5 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 01:29:40 -0300 Subject: [PATCH 01/18] feat(table): add basic keyboard nav when table has row-clicked handler or is selctable --- .../table/helpers/mixin-tbody-row.js | 74 +++++++++++++------ 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/src/components/table/helpers/mixin-tbody-row.js b/src/components/table/helpers/mixin-tbody-row.js index a53a4e4519e..cf25bc6739c 100644 --- a/src/components/table/helpers/mixin-tbody-row.js +++ b/src/components/table/helpers/mixin-tbody-row.js @@ -1,6 +1,8 @@ import toString from '../../../utils/to-string' import get from '../../../utils/get' import KeyCodes from '../../../utils/key-codes' +import { selectAll } from '../../../utils/dom' +import { arrayIncludes } from '../../../utils/array' import filterEvent from './filter-event' import textSelectionActive from './text-selection-active' @@ -74,6 +76,45 @@ export default { } return value === null || typeof value === 'undefined' ? '' : value }, + tbodyRowKeydown(evt) { + const keyCode = evt.keyCode + const target = evt.target + const trs = this.$refs.itemRows + if (this.stopIfBusy(e)) { + // If table is busy (via provider) then don't propagate + return + } else if (!(target && target.tagName === 'TR' && target === document.activeElement)) { + // Ignore if not the active tr element + return + } else if (trs && trs.length === 0) { + /* istanbul ignore next */ + return + } + const index = trs.indexOf(target) + if (keyCode === KeyCodes.ENTER || keyCode === KeyCodes.SPACE) { + evt.stopPropagation() + evt.preventDefault() + // We also allow enter/space to trigger a click (when row is focused) + evt.target.click() + } else if (arrayIncludes([KeyCode.UP, KeyCode.DOWN, KeyCode.HOME, KeyCode.END], keyCode)) { + evt.stopPropagation() + evt.preventDefault() + const shift = evt.shift + if (keyCode === KeyCode.HOME || (shift && keyCode === KeyCode.UP)) { + // Focus first row + trs[0].focus() + } else if (keyCode === KeyCode.END || (shift && keyCode === KeyCode.DOWN)) { + // Focus last row + trs[trs.length - 1].focus() + } else if (keyCode === KeyCode.UP && index > 1) { + // Focus previous row + trs[index - 1].focus() + } else if (keyCode === KeyCode.DOWN && index < trs.length - 2) { + // Focus previous row + trs[index + 1].focus() + } + } + }, // Row event handlers rowClicked(e, item, index) { if (this.stopIfBusy(e)) { @@ -87,11 +128,6 @@ export default { /* istanbul ignore next: JSDOM doesn't support getSelection() */ return } - if (e.type === 'keydown') { - // If the click was generated by space or enter, stop page scroll - e.stopPropagation() - e.preventDefault() - } this.$emit('row-clicked', item, index, e) }, middleMouseRowClicked(e, item, index) { @@ -236,12 +272,20 @@ export default { ? this.safeId(`_row_${item[primaryKey]}`) : null + const handlers = {} + if (hasRowClickHandler) { + handlers['click'] = evt => { this.rowClicked(evt, item, rowIndex) } + handlers['keydown'] = this.tbodyRowKeydown + } + // Add the item row $rows.push( h( 'tr', { key: `__b-table-row-${rowKey}__`, + ref: 'itemRows', + refInFor: true, class: [ this.rowClasses(item), this.rowSelectedClasses(rowIndex), @@ -260,28 +304,14 @@ export default { role: 'row' }, on: { - // TODO: only instatiate handlers if we have registered listeners (except row-clicked) + ...handlers, + // TODO: instatiate the following handlers only if we have registered + // listeners i.e. this.$listeners['row-middle-clicked'], etc. auxclick: evt => { if (evt.which === 2) { this.middleMouseRowClicked(evt, item, rowIndex) } }, - click: evt => { - this.rowClicked(evt, item, rowIndex) - }, - keydown: evt => { - // We also allow enter/space to trigger a click (when row is focused) - const keyCode = evt.keyCode - if (keyCode === KeyCodes.ENTER || keyCode === KeyCodes.SPACE) { - if ( - evt.target && - evt.target.tagName === 'TR' && - evt.target === document.activeElement - ) { - this.rowClicked(evt, item, rowIndex) - } - } - }, contextmenu: evt => { this.rowContextmenu(evt, item, rowIndex) }, From 18833dea86bb15f4294fa20add64ac3912b3fb76 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 01:34:43 -0300 Subject: [PATCH 02/18] Update mixin-tbody-row.js --- src/components/table/helpers/mixin-tbody-row.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/table/helpers/mixin-tbody-row.js b/src/components/table/helpers/mixin-tbody-row.js index cf25bc6739c..c822a540e78 100644 --- a/src/components/table/helpers/mixin-tbody-row.js +++ b/src/components/table/helpers/mixin-tbody-row.js @@ -80,7 +80,7 @@ export default { const keyCode = evt.keyCode const target = evt.target const trs = this.$refs.itemRows - if (this.stopIfBusy(e)) { + if (this.stopIfBusy(evt)) { // If table is busy (via provider) then don't propagate return } else if (!(target && target.tagName === 'TR' && target === document.activeElement)) { From eab3e9dfbe535d0425719ddd5fa31070706c222e Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 01:38:06 -0300 Subject: [PATCH 03/18] Update mixin-tbody-row.js --- src/components/table/helpers/mixin-tbody-row.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/table/helpers/mixin-tbody-row.js b/src/components/table/helpers/mixin-tbody-row.js index c822a540e78..25d4a0845e6 100644 --- a/src/components/table/helpers/mixin-tbody-row.js +++ b/src/components/table/helpers/mixin-tbody-row.js @@ -1,7 +1,6 @@ import toString from '../../../utils/to-string' import get from '../../../utils/get' import KeyCodes from '../../../utils/key-codes' -import { selectAll } from '../../../utils/dom' import { arrayIncludes } from '../../../utils/array' import filterEvent from './filter-event' import textSelectionActive from './text-selection-active' @@ -96,20 +95,20 @@ export default { evt.preventDefault() // We also allow enter/space to trigger a click (when row is focused) evt.target.click() - } else if (arrayIncludes([KeyCode.UP, KeyCode.DOWN, KeyCode.HOME, KeyCode.END], keyCode)) { + } else if (arrayIncludes([KeyCodes.UP, KeyCodes.DOWN, KeyCodes.HOME, KeyCodes.END], keyCode)) { evt.stopPropagation() evt.preventDefault() const shift = evt.shift - if (keyCode === KeyCode.HOME || (shift && keyCode === KeyCode.UP)) { + if (keyCode === KeyCodes.HOME || (shift && keyCode === KeyCodes.UP)) { // Focus first row trs[0].focus() - } else if (keyCode === KeyCode.END || (shift && keyCode === KeyCode.DOWN)) { + } else if (keyCode === KeyCodes.END || (shift && keyCode === KeyCodes.DOWN)) { // Focus last row trs[trs.length - 1].focus() - } else if (keyCode === KeyCode.UP && index > 1) { + } else if (keyCode === KeyCodes.UP && index > 1) { // Focus previous row trs[index - 1].focus() - } else if (keyCode === KeyCode.DOWN && index < trs.length - 2) { + } else if (keyCode === KeyCodes.DOWN && index < trs.length - 2) { // Focus previous row trs[index + 1].focus() } From 0d1431e25cc803bac7916f5233f8621c0b9bf69c Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 01:41:44 -0300 Subject: [PATCH 04/18] Update mixin-tbody-row.js --- src/components/table/helpers/mixin-tbody-row.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/table/helpers/mixin-tbody-row.js b/src/components/table/helpers/mixin-tbody-row.js index 25d4a0845e6..242c1a7c7de 100644 --- a/src/components/table/helpers/mixin-tbody-row.js +++ b/src/components/table/helpers/mixin-tbody-row.js @@ -95,7 +95,9 @@ export default { evt.preventDefault() // We also allow enter/space to trigger a click (when row is focused) evt.target.click() - } else if (arrayIncludes([KeyCodes.UP, KeyCodes.DOWN, KeyCodes.HOME, KeyCodes.END], keyCode)) { + } else if ( + arrayIncludes([KeyCodes.UP, KeyCodes.DOWN, KeyCodes.HOME, KeyCodes.END], keyCode) + ) { evt.stopPropagation() evt.preventDefault() const shift = evt.shift @@ -273,7 +275,9 @@ export default { const handlers = {} if (hasRowClickHandler) { - handlers['click'] = evt => { this.rowClicked(evt, item, rowIndex) } + handlers['click'] = evt => { + this.rowClicked(evt, item, rowIndex) + } handlers['keydown'] = this.tbodyRowKeydown } From 31d4c3b64e3b601263c3e9b950fbf089c929d3f3 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 01:44:21 -0300 Subject: [PATCH 05/18] Update table-tbody-row-events.spec.js --- .../table/table-tbody-row-events.spec.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/components/table/table-tbody-row-events.spec.js b/src/components/table/table-tbody-row-events.spec.js index 44f8c6c61d3..91016dde0b6 100644 --- a/src/components/table/table-tbody-row-events.spec.js +++ b/src/components/table/table-tbody-row-events.spec.js @@ -10,6 +10,10 @@ describe('table tbody row events', () => { propsData: { fields: testFields, items: testItems + }, + listeners: { + // Row Clicked will only occur if there is a registered listener + 'row-clicked': () => {} } }) expect(wrapper).toBeDefined() @@ -32,6 +36,10 @@ describe('table tbody row events', () => { fields: testFields, items: testItems, busy: true + }, + listeners: { + // Row Clicked will only occur if there is a registered listener + 'row-clicked': () => {} } }) expect(wrapper).toBeDefined() @@ -50,6 +58,10 @@ describe('table tbody row events', () => { propsData: { fields: testFields, items: testItems + }, + listeners: { + // Row Clicked will only occur if there is a registered listener + 'row-clicked': () => {} } }) expect(wrapper).toBeDefined() @@ -295,6 +307,10 @@ describe('table tbody row events', () => { fields: testFields, items: testItems, busy: true + }, + listeners: { + // Row Clicked will only occur if there is a registered listener + 'row-clicked': () => {} } }) expect(wrapper).toBeDefined() @@ -323,6 +339,10 @@ describe('table tbody row events', () => { c: 'link', d: '', e: '' + }, + listeners: { + // Row Clicked will only occur if there is a registered listener + 'row-clicked': () => {} } }) expect(wrapper).toBeDefined() From c89739212dc462501370ca5cbc2b7c70d591c33c Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 01:45:50 -0300 Subject: [PATCH 06/18] Update table-tbody-row-events.spec.js --- src/components/table/table-tbody-row-events.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/table/table-tbody-row-events.spec.js b/src/components/table/table-tbody-row-events.spec.js index 91016dde0b6..6ed1713d5f1 100644 --- a/src/components/table/table-tbody-row-events.spec.js +++ b/src/components/table/table-tbody-row-events.spec.js @@ -295,8 +295,8 @@ describe('table tbody row events', () => { expect(wrapper.emitted('row-clicked').length).toBe(1) expect(wrapper.emitted('row-clicked')[0][0]).toEqual(testItems[1]) /* row item */ expect(wrapper.emitted('row-clicked')[0][1]).toEqual(1) /* row index */ - // Note: the KeyboardEvent is forwarded to the click handler - expect(wrapper.emitted('row-clicked')[0][2]).toBeInstanceOf(KeyboardEvent) /* event */ + // Note: the KeyboardEvent is converted into a MouseEvent + expect(wrapper.emitted('row-clicked')[0][2]).toBeInstanceOf(MouseEvent) /* event */ wrapper.destroy() }) From 078324ce9f6d96543716729c7db519b5b532f6ca Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 01:50:14 -0300 Subject: [PATCH 07/18] Update table.spec.js --- src/components/table/table.spec.js | 159 ----------------------------- 1 file changed, 159 deletions(-) diff --git a/src/components/table/table.spec.js b/src/components/table/table.spec.js index 85c94171e04..ac4cca5f4f7 100644 --- a/src/components/table/table.spec.js +++ b/src/components/table/table.spec.js @@ -390,165 +390,6 @@ describe('table', () => { } }) - it('each data row should emit a row-clicked event when clicked', async () => { - const { - app: { $refs } - } = window - const vm = $refs.table_paginated - - const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY') - expect(tbody).toBeDefined() - if (tbody) { - const trs = [...tbody.children] - expect(trs.length).toBe(vm.perPage) - trs.forEach((tr, idx) => { - const spy = jest.fn() - vm.$on('row-clicked', spy) - tr.click() - vm.$off('row-clicked', spy) - expect(spy).toHaveBeenCalled() - }) - } - }) - - /* - * This test needs a polyfill for getSelection and createRange - * - it('row-clicked event should not happen when textSelection is active', async () => { - const { - app: { $refs } - } = window - const vm = $refs.table_paginated - - const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY') - expect(tbody).toBeDefined() - const trs = [...tbody.children] - expect(trs.length).toBe(vm.perPage) - - // Clear selection if any current - let selection = window.getSelection() - if (selection.rangeCount > 0) { - selection.removeAllRanges() - } - - const spy = jest.fn() - vm.$on('row-clicked', spy) - expect(spy).not.toHaveBeenCalled() - - // Select text in first TR - const range = document.createRange() - range.selectNode(trs[0]) - selection.addRange(range) - - // Click row - trs[0].click() - expect(spy).not.toHaveBeenCalled() - - // Clear selection - if (selection.rangeCount > 0) { - selection.removeAllRanges() - } - - // Click row - trs[0].click() - expect(spy).toHaveBeenCalled() - }) - */ - - it('each data row should emit a row-contextmenu event when right clicked', async () => { - const { - app: { $refs } - } = window - const vm = $refs.table_paginated - - const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY') - expect(tbody).toBeDefined() - if (tbody) { - const trs = [...tbody.children] - expect(trs.length).toBe(vm.perPage) - trs.forEach((tr, idx) => { - const spy = jest.fn() - vm.$on('row-contextmenu', spy) - tr.dispatchEvent(new MouseEvent('contextmenu', { button: 2 })) - vm.$off('row-contextmenu', spy) - expect(spy).toHaveBeenCalled() - }) - } - }) - - it('each data row should emit a row-middle-clicked event when middle clicked', async () => { - const { - app: { $refs } - } = window - const vm = $refs.table_paginated - - const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY') - expect(tbody).toBeDefined() - if (tbody) { - const trs = [...tbody.children] - expect(trs.length).toBe(vm.perPage) - trs.forEach((tr, idx) => { - const spy = jest.fn() - vm.$on('row-middle-clicked', spy) - tr.dispatchEvent(new MouseEvent('auxclick', { button: 1, which: 2 })) - vm.$off('row-middle-clicked', spy) - expect(spy).toHaveBeenCalled() - }) - } - }) - - it('each header th should emit a head-clicked event when clicked', async () => { - const { - app: { $refs } - } = window - const vm = $refs.table_paginated - const fieldKeys = Object.keys(vm.fields) - - const thead = [...vm.$el.children].find(el => el && el.tagName === 'THEAD') - expect(thead).toBeDefined() - if (thead) { - const tr = [...thead.children].find(el => el && el.tagName === 'TR') - expect(tr).toBeDefined() - if (tr) { - const ths = [...tr.children] - expect(ths.length).toBe(fieldKeys.length) - ths.forEach((th, idx) => { - const spy = jest.fn() - vm.$on('head-clicked', spy) - th.click() - vm.$off('head-clicked', spy) - expect(spy).toHaveBeenCalled() - }) - } - } - }) - - it('each footer th should emit a head-clicked event when clicked', async () => { - const { - app: { $refs } - } = window - const vm = $refs.table_paginated - const fieldKeys = Object.keys(vm.fields) - - const tfoot = [...vm.$el.children].find(el => el && el.tagName === 'TFOOT') - expect(tfoot).toBeDefined() - if (tfoot) { - const tr = [...tfoot.children].find(el => el && el.tagName === 'TR') - expect(tr).toBeDefined() - if (tr) { - const ths = [...tr.children] - expect(ths.length).toBe(fieldKeys.length) - ths.forEach((th, idx) => { - const spy = jest.fn() - vm.$on('head-clicked', spy) - th.click() - vm.$off('head-clicked', spy) - expect(spy).toHaveBeenCalled() - }) - } - } - }) - it('sortable header th should emit a sort-changed event with context when clicked and sort changed', async () => { const { app: { $refs } From 1922b27e30e7b7497cf5699b42821366f18c460b Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 02:05:49 -0300 Subject: [PATCH 08/18] Update mixin-tbody-row.js --- src/components/table/helpers/mixin-tbody-row.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/table/helpers/mixin-tbody-row.js b/src/components/table/helpers/mixin-tbody-row.js index 242c1a7c7de..6f1caa22557 100644 --- a/src/components/table/helpers/mixin-tbody-row.js +++ b/src/components/table/helpers/mixin-tbody-row.js @@ -75,7 +75,7 @@ export default { } return value === null || typeof value === 'undefined' ? '' : value }, - tbodyRowKeydown(evt) { + tbodyRowKeydown(evt, item, index) { const keyCode = evt.keyCode const target = evt.target const trs = this.$refs.itemRows @@ -85,6 +85,10 @@ export default { } else if (!(target && target.tagName === 'TR' && target === document.activeElement)) { // Ignore if not the active tr element return + } else if (target.tabIndex !== 0) { + // Ignore if not focusable + /* istanbul ignore next */ + return } else if (trs && trs.length === 0) { /* istanbul ignore next */ return @@ -94,7 +98,8 @@ export default { evt.stopPropagation() evt.preventDefault() // We also allow enter/space to trigger a click (when row is focused) - evt.target.click() + // We translate to a row-clicked event + this.rowClicked(evt, item, index) } else if ( arrayIncludes([KeyCodes.UP, KeyCodes.DOWN, KeyCodes.HOME, KeyCodes.END], keyCode) ) { @@ -278,7 +283,9 @@ export default { handlers['click'] = evt => { this.rowClicked(evt, item, rowIndex) } - handlers['keydown'] = this.tbodyRowKeydown + handlers['keydown'] = evt => { + this.tbodyRowKeydown(evt, item, rowIndex) + } } // Add the item row From d2044a1b8fffbfdeab83e777ba426c89b5a2e13d Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 02:06:44 -0300 Subject: [PATCH 09/18] Update table-tbody-row-events.spec.js --- src/components/table/table-tbody-row-events.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/table/table-tbody-row-events.spec.js b/src/components/table/table-tbody-row-events.spec.js index 6ed1713d5f1..2fc849cc168 100644 --- a/src/components/table/table-tbody-row-events.spec.js +++ b/src/components/table/table-tbody-row-events.spec.js @@ -295,8 +295,8 @@ describe('table tbody row events', () => { expect(wrapper.emitted('row-clicked').length).toBe(1) expect(wrapper.emitted('row-clicked')[0][0]).toEqual(testItems[1]) /* row item */ expect(wrapper.emitted('row-clicked')[0][1]).toEqual(1) /* row index */ - // Note: the KeyboardEvent is converted into a MouseEvent - expect(wrapper.emitted('row-clicked')[0][2]).toBeInstanceOf(MouseEvent) /* event */ + // Note: the KeyboardEvent is passed to the row-clicked handler + expect(wrapper.emitted('row-clicked')[0][2]).toBeInstanceOf(KeyboardEvent) /* event */ wrapper.destroy() }) From 685fa4702f29100c8daef35bd30eb81906eef671 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 02:08:45 -0300 Subject: [PATCH 10/18] Update mixin-tbody-row.js --- src/components/table/helpers/mixin-tbody-row.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/table/helpers/mixin-tbody-row.js b/src/components/table/helpers/mixin-tbody-row.js index 6f1caa22557..aa098ceabe0 100644 --- a/src/components/table/helpers/mixin-tbody-row.js +++ b/src/components/table/helpers/mixin-tbody-row.js @@ -75,7 +75,7 @@ export default { } return value === null || typeof value === 'undefined' ? '' : value }, - tbodyRowKeydown(evt, item, index) { + tbodyRowKeydown(evt, item, rowIndex) { const keyCode = evt.keyCode const target = evt.target const trs = this.$refs.itemRows @@ -99,7 +99,7 @@ export default { evt.preventDefault() // We also allow enter/space to trigger a click (when row is focused) // We translate to a row-clicked event - this.rowClicked(evt, item, index) + this.rowClicked(evt, item, rowIndex) } else if ( arrayIncludes([KeyCodes.UP, KeyCodes.DOWN, KeyCodes.HOME, KeyCodes.END], keyCode) ) { From eea795c562511dd7cfea96f5dde380833518e361 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 02:16:39 -0300 Subject: [PATCH 11/18] Update mixin-tbody-row.js --- src/components/table/helpers/mixin-tbody-row.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/table/helpers/mixin-tbody-row.js b/src/components/table/helpers/mixin-tbody-row.js index aa098ceabe0..6d90d252830 100644 --- a/src/components/table/helpers/mixin-tbody-row.js +++ b/src/components/table/helpers/mixin-tbody-row.js @@ -112,11 +112,11 @@ export default { } else if (keyCode === KeyCodes.END || (shift && keyCode === KeyCodes.DOWN)) { // Focus last row trs[trs.length - 1].focus() - } else if (keyCode === KeyCodes.UP && index > 1) { + } else if (keyCode === KeyCodes.UP && index > 0) { // Focus previous row trs[index - 1].focus() - } else if (keyCode === KeyCodes.DOWN && index < trs.length - 2) { - // Focus previous row + } else if (keyCode === KeyCodes.DOWN && index < trs.length - 1) { + // Focus next row trs[index + 1].focus() } } From fd9c07d04ee103133192c2f3d29e9c630301c51c Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 10:01:40 -0300 Subject: [PATCH 12/18] Update table-tbody-row-events.spec.js --- .../table/table-tbody-row-events.spec.js | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/components/table/table-tbody-row-events.spec.js b/src/components/table/table-tbody-row-events.spec.js index 2fc849cc168..6ee409c26f5 100644 --- a/src/components/table/table-tbody-row-events.spec.js +++ b/src/components/table/table-tbody-row-events.spec.js @@ -378,4 +378,46 @@ describe('table tbody row events', () => { wrapper.destroy() }) + + it('keyboard events moves focus to apropriate rows', async () => { + const wrapper = mount(Table, { + propsData: { + fields: testFields, + items: testItems + }, + listeners: { + // Tabindex will only be set if htere is a row-clicked listener + 'row-clicked': () => {} + } + }) + expect(wrapper).toBeDefined() + const $rows = wrapper.findAll('tbody > tr') + expect($rows.length).toBe(3) + expect(document.activeElement).not.toBe($rows.at(0)) + expect(document.activeElement).not.toBe($rows.at(1)) + expect(document.activeElement).not.toBe($rows.at(2)) + + $rows.at(0).trigger('focus') + expect(document.activeElement).toBe($rows.at(0)) + + $rows.at(0).trigger('keydown.end') + expect(document.activeElement).toBe($rows.at(2)) + + $rows.at(2).trigger('keydown.home') + expect(document.activeElement).toBe($rows.at(0)) + + $rows.at(0).trigger('keydown.down') + expect(document.activeElement).toBe($rows.at(1)) + + $rows.at(1).trigger('keydown.up') + expect(document.activeElement).toBe($rows.at(0)) + + $rows.at(0).trigger('keydown.down', { shiftKey: true }) + expect(document.activeElement).toBe($rows.at(2)) + + $rows.at(2).trigger('keydown.up', { shiftKey: true }) + expect(document.activeElement).toBe($rows.at(0)) + + wrapper.destroy() + }) }) From 2b20753b0a1927ace3dd9636db5e4e4b08356256 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 10:02:12 -0300 Subject: [PATCH 13/18] Update mixin-tbody-row.js --- src/components/table/helpers/mixin-tbody-row.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/table/helpers/mixin-tbody-row.js b/src/components/table/helpers/mixin-tbody-row.js index 6d90d252830..30c07607306 100644 --- a/src/components/table/helpers/mixin-tbody-row.js +++ b/src/components/table/helpers/mixin-tbody-row.js @@ -105,7 +105,7 @@ export default { ) { evt.stopPropagation() evt.preventDefault() - const shift = evt.shift + const shift = evt.shiftKey if (keyCode === KeyCodes.HOME || (shift && keyCode === KeyCodes.UP)) { // Focus first row trs[0].focus() From 7287ccfae5a101e5a1305de5e7194177d3d873d2 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 10:10:24 -0300 Subject: [PATCH 14/18] Update table-tbody-row-events.spec.js --- .../table/table-tbody-row-events.spec.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/table/table-tbody-row-events.spec.js b/src/components/table/table-tbody-row-events.spec.js index 6ee409c26f5..933056c6dc7 100644 --- a/src/components/table/table-tbody-row-events.spec.js +++ b/src/components/table/table-tbody-row-events.spec.js @@ -393,30 +393,30 @@ describe('table tbody row events', () => { expect(wrapper).toBeDefined() const $rows = wrapper.findAll('tbody > tr') expect($rows.length).toBe(3) - expect(document.activeElement).not.toBe($rows.at(0)) - expect(document.activeElement).not.toBe($rows.at(1)) - expect(document.activeElement).not.toBe($rows.at(2)) + expect(document.activeElement).not.toEqual($rows.at(0)) + expect(document.activeElement).not.toEqual($rows.at(1)) + expect(document.activeElement).not.toEqual($rows.at(2)) $rows.at(0).trigger('focus') - expect(document.activeElement).toBe($rows.at(0)) + expect(document.activeElement).toEqual($rows.at(0)) $rows.at(0).trigger('keydown.end') - expect(document.activeElement).toBe($rows.at(2)) + expect(document.activeElement).toEqual($rows.at(2)) $rows.at(2).trigger('keydown.home') - expect(document.activeElement).toBe($rows.at(0)) + expect(document.activeElement).toEqual($rows.at(0)) $rows.at(0).trigger('keydown.down') - expect(document.activeElement).toBe($rows.at(1)) + expect(document.activeElement).toEqual($rows.at(1)) $rows.at(1).trigger('keydown.up') - expect(document.activeElement).toBe($rows.at(0)) + expect(document.activeElement).toEqual($rows.at(0)) $rows.at(0).trigger('keydown.down', { shiftKey: true }) - expect(document.activeElement).toBe($rows.at(2)) + expect(document.activeElement).toEqual($rows.at(2)) $rows.at(2).trigger('keydown.up', { shiftKey: true }) - expect(document.activeElement).toBe($rows.at(0)) + expect(document.activeElement).toEqual($rows.at(0)) wrapper.destroy() }) From 311826cb8a10cd39beae13cbb13c80f18efd288c Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 10:12:24 -0300 Subject: [PATCH 15/18] Update table-tbody-row-events.spec.js --- .../table/table-tbody-row-events.spec.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/table/table-tbody-row-events.spec.js b/src/components/table/table-tbody-row-events.spec.js index 933056c6dc7..1b2b7e720bf 100644 --- a/src/components/table/table-tbody-row-events.spec.js +++ b/src/components/table/table-tbody-row-events.spec.js @@ -393,30 +393,30 @@ describe('table tbody row events', () => { expect(wrapper).toBeDefined() const $rows = wrapper.findAll('tbody > tr') expect($rows.length).toBe(3) - expect(document.activeElement).not.toEqual($rows.at(0)) - expect(document.activeElement).not.toEqual($rows.at(1)) - expect(document.activeElement).not.toEqual($rows.at(2)) + expect(document.activeElement).not.toBe($rows.at(0).element) + expect(document.activeElement).not.toBe($rows.at(1).element) + expect(document.activeElement).not.toBe($rows.at(2).element) $rows.at(0).trigger('focus') - expect(document.activeElement).toEqual($rows.at(0)) + expect(document.activeElement).toBe($rows.at(0).element) $rows.at(0).trigger('keydown.end') - expect(document.activeElement).toEqual($rows.at(2)) + expect(document.activeElement).toBe($rows.at(2).element) $rows.at(2).trigger('keydown.home') - expect(document.activeElement).toEqual($rows.at(0)) + expect(document.activeElement).toBe($rows.at(0).element) $rows.at(0).trigger('keydown.down') - expect(document.activeElement).toEqual($rows.at(1)) + expect(document.activeElement).toBe($rows.at(1).element) $rows.at(1).trigger('keydown.up') - expect(document.activeElement).toEqual($rows.at(0)) + expect(document.activeElement).toBe($rows.at(0).element) $rows.at(0).trigger('keydown.down', { shiftKey: true }) - expect(document.activeElement).toEqual($rows.at(2)) + expect(document.activeElement).toBe($rows.at(2).element) $rows.at(2).trigger('keydown.up', { shiftKey: true }) - expect(document.activeElement).toEqual($rows.at(0)) + expect(document.activeElement).toBe($rows.at(0).element) wrapper.destroy() }) From e07a66b703dc175f9bd2fd8157b17b344f981725 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 10:17:15 -0300 Subject: [PATCH 16/18] Update table-tbody-row-events.spec.js --- src/components/table/table-tbody-row-events.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/table/table-tbody-row-events.spec.js b/src/components/table/table-tbody-row-events.spec.js index 1b2b7e720bf..03776891b3e 100644 --- a/src/components/table/table-tbody-row-events.spec.js +++ b/src/components/table/table-tbody-row-events.spec.js @@ -397,7 +397,7 @@ describe('table tbody row events', () => { expect(document.activeElement).not.toBe($rows.at(1).element) expect(document.activeElement).not.toBe($rows.at(2).element) - $rows.at(0).trigger('focus') + $rows.at(0).element.focus() expect(document.activeElement).toBe($rows.at(0).element) $rows.at(0).trigger('keydown.end') From 61ac9a041f02115e12a3e4945eceb28f9e84bb23 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 10:22:17 -0300 Subject: [PATCH 17/18] Update table-tbody-row-events.spec.js --- src/components/table/table-tbody-row-events.spec.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/table/table-tbody-row-events.spec.js b/src/components/table/table-tbody-row-events.spec.js index 03776891b3e..4ea42025199 100644 --- a/src/components/table/table-tbody-row-events.spec.js +++ b/src/components/table/table-tbody-row-events.spec.js @@ -418,6 +418,13 @@ describe('table tbody row events', () => { $rows.at(2).trigger('keydown.up', { shiftKey: true }) expect(document.activeElement).toBe($rows.at(0).element) + // SHould only move focus if TR was target + $rows + .at(0) + .find('td') + .trigger('keydown.down') + expect(document.activeElement).toBe($rows.at(0).element) + wrapper.destroy() }) }) From 5561f6ecd7351288a1cc928f760b347bc69fea41 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 11:01:38 -0300 Subject: [PATCH 18/18] Update README.md --- src/components/table/README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/table/README.md b/src/components/table/README.md index 74aef89bd59..aa955a98020 100644 --- a/src/components/table/README.md +++ b/src/components/table/README.md @@ -1818,12 +1818,22 @@ When `` is mounted in the document, it will automatically trigger a pro ## Table accessibility notes +When a column (field) is sortable, the header (and footer) heading cells will also be placed into +the document tab sequence for accessibility. + When the table is in `selectable` mode, or if there is a `row-clicked` event listener registered, all data item rows (`` elements) will be placed into the document tab sequence (via `tabindex="0"`) to allow keyboard-only and screen reader users the ability to click the rows. -When a column (field) is sortable, the header (and footer) heading cells will also be placed into -the document tab sequence for accessibility. +When the table items rows are in the tabl sequence, they will also support basic keyboard navigation +when focused: + +- DOWN will move to the next row +- UP will move to the previous row +- END or DOWN+SHIFT will move to the last row +- HOME or UP+SHIFT will move to the first row +- ENTER or SPACE to click the row. SHIFT and CTRL + modifiers will also work (depending on the table selectable mode). Note the following row based events/actions are not considered accessible, and should only be used if the functionality is non critical or can be provided via other means: