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

Add arrow key and tab/enter table editor navigation #1965

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
69d8d23
add all tab/enter/arrow controls
deundrewilliams Jan 26, 2022
d77b3ea
update tests to work with all arrows and tab
deundrewilliams Jan 26, 2022
f0a25e1
Merge branch 'dev/27-pyrite' into issue/726-table-nav
deundrewilliams Jan 26, 2022
09be908
Merge branch 'dev/27-pyrite' into issue/726-table-nav
deundrewilliams Jan 27, 2022
e03153d
add right navigation from rightmost cell
deundrewilliams Jan 31, 2022
74c18ca
add shift+tab left navigation
deundrewilliams Feb 1, 2022
fa4f54c
add left nav when in leftmost column
deundrewilliams Feb 1, 2022
f693f19
update tests to use common editor object
deundrewilliams Feb 3, 2022
ccc8584
update totalCols reference
deundrewilliams Feb 3, 2022
6455b0b
add tabbing to table option buttons
deundrewilliams Feb 8, 2022
e3219a6
change dropdown cell colors on focus
deundrewilliams Feb 9, 2022
c3465d5
highlight dropdown cells when using keyboard, add tests
deundrewilliams Feb 14, 2022
bab7faf
add deselect functionality
deundrewilliams Feb 17, 2022
9b0a5ac
run prettier
deundrewilliams Feb 17, 2022
56a92be
remove commented code
deundrewilliams Feb 17, 2022
e0db51d
add ability to navigate out of table with up/down arrow keys
deundrewilliams Feb 22, 2022
7a5c54a
shorten numCols retrieval
deundrewilliams Feb 28, 2022
7efaec0
fix arrowup from nodes in editor
deundrewilliams Mar 3, 2022
02eb2ad
fix shift+tab bug on bottom cell control option
deundrewilliams Mar 3, 2022
1c3192d
fix selection when exiting table with arrow keys
deundrewilliams Mar 3, 2022
57ebde4
add comments
deundrewilliams Mar 17, 2022
f66df77
clean up code
deundrewilliams Aug 23, 2022
ac9dde4
Merge branch 'dev/30-howlite' into issue/726-table-nav
FrenjaminBanklin Jan 10, 2023
a8b4b7b
726 Merged changes from current dev branch, fixed breaking tests.
FrenjaminBanklin Jan 10, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -1272,6 +1272,122 @@ describe('VisualEditor', () => {
expect(onKeyDown).toHaveBeenCalled()
})

describe('moving to node above', () => {
const editorDefaults = {
children: [
{ type: 'mock node 1', children: [{ text: 'one' }] },
{ type: 'mock node 2', children: [{ text: 'one' }, { text: 'two' }] },
{ type: 'mock node 3', children: [{ text: 'one' }] }
],
apply: jest.fn()
}

const visualEditorProps = {
page: {
attributes: { children: [] },
get: jest.fn(),
toJSON: () => ({ children: [{}] }),
set: jest.fn(),
children: []
},
model: {
title: 'Mock Title',
flatJSON: () => ({ content: {} }),
children: []
},
draft: { accessLevel: FULL }
}

test('onKeyDown handles ArrowUp if node above has one child', () => {
jest.spyOn(Array, 'from').mockReturnValue([])
const spySetSelection = jest.spyOn(Transforms, 'setSelection')

const editor = {
...editorDefaults,
selection: {
anchor: { path: [1], offset: 0 },
focus: { path: [1], offset: 0 }
}
}

const component = mount(<VisualEditor {...visualEditorProps} />)
const instance = component.instance()
instance.editor = editor

instance.onKeyDown({
preventDefault: jest.fn(),
key: 'ArrowUp',
defaultPrevented: false
})

expect(spySetSelection).toHaveBeenCalled()
expect(spySetSelection.mock.calls[0].length).toEqual(2)

const focusSelection = spySetSelection.mock.calls[0][1].focus.path
const anchorSelection = spySetSelection.mock.calls[0][1].anchor.path

expect(focusSelection).toEqual([0, 0])
expect(anchorSelection).toEqual([0, 0])
})

test('onKeyDown handles ArrowUp if node above has multiple children', () => {
jest.spyOn(Array, 'from').mockReturnValue([])
const spySetSelection = jest.spyOn(Transforms, 'setSelection')

const editor = {
...editorDefaults,
selection: {
anchor: { path: [2], offset: 0 },
focus: { path: [2], offset: 0 }
}
}

const component = mount(<VisualEditor {...visualEditorProps} />)
const instance = component.instance()
instance.editor = editor

instance.onKeyDown({
preventDefault: jest.fn(),
key: 'ArrowUp',
defaultPrevented: false
})

expect(spySetSelection).toHaveBeenCalled()
expect(spySetSelection.mock.calls[0].length).toEqual(2)

const focusSelection = spySetSelection.mock.calls[0][1].focus.path
const anchorSelection = spySetSelection.mock.calls[0][1].anchor.path

expect(focusSelection).toEqual([1, 1])
expect(anchorSelection).toEqual([1, 1])
})

test('onKeyDown handles ArrowUp if no node above', () => {
jest.spyOn(Array, 'from').mockReturnValue([])
const spySetSelection = jest.spyOn(Transforms, 'setSelection')

const editor = {
...editorDefaults,
selection: {
anchor: { path: [0], offset: 0 },
focus: { path: [0], offset: 0 }
}
}

const component = mount(<VisualEditor {...visualEditorProps} />)
const instance = component.instance()
instance.editor = editor

instance.onKeyDown({
preventDefault: jest.fn(),
key: 'ArrowUp',
defaultPrevented: false
})

expect(spySetSelection).not.toHaveBeenCalled()
})
})

test('reload disables event listener and calls location.reload', () => {
jest.spyOn(window, 'removeEventListener').mockReturnValueOnce()
Object.defineProperty(window, 'location', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,36 @@ class VisualEditor extends React.Component {
item.plugins.onKeyDown(entry, this.editor, event)
}
}

// Handle ArrowUp from a node - this is default behavior
// in Chrome and Safari but not Firefox
if (event.key === 'ArrowUp' && !event.defaultPrevented) {
event.preventDefault()

const currentNode = this.editor.selection.anchor.path[0]

// Break out if already at top node
if (currentNode === 0) return

const aboveNode = currentNode - 1
const numChildrenAbove = this.editor.children[aboveNode].children.length

let abovePath = [aboveNode]

// If entering a node with multiple children (a table),
// go to the last child (bottom row)
if (numChildrenAbove > 1) {
abovePath = [aboveNode, numChildrenAbove - 1]
}

const focus = Editor.start(this.editor, abovePath)
const anchor = Editor.start(this.editor, abovePath)

Transforms.setSelection(this.editor, {
focus,
anchor
})
}
}

// Generates any necessary decorations, such as place holders
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ exports[`Cell Editor Node Cell component as selected header 1`] = `
<div
className="dropdown-cell"
contentEditable={false}
onKeyDown={[Function]}
>
<button
className=" is-not-open"
Expand All @@ -21,6 +22,8 @@ exports[`Cell Editor Node Cell component as selected header 1`] = `
</button>
<div
className="drop-content-cell is-not-open"
onBlur={[Function]}
onFocus={[Function]}
>
<button
onClick={[Function]}
Expand Down Expand Up @@ -66,6 +69,7 @@ exports[`Cell Editor Node Cell component selected 1`] = `
<div
className="dropdown-cell"
contentEditable={false}
onKeyDown={[Function]}
>
<button
className=" is-not-open"
Expand All @@ -78,6 +82,8 @@ exports[`Cell Editor Node Cell component selected 1`] = `
</button>
<div
className="drop-content-cell is-not-open"
onBlur={[Function]}
onFocus={[Function]}
>
<button
onClick={[Function]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ class Cell extends React.Component {
super(props)
this.state = {
isOpen: false,
isShowingDropDownMenu: false
isShowingDropDownMenu: false,
focusedDropdownSelection: null
}

this.toggleOpen = this.toggleOpen.bind(this)
Expand All @@ -27,6 +28,7 @@ class Cell extends React.Component {
this.deleteRow = this.deleteRow.bind(this)
this.deleteCol = this.deleteCol.bind(this)
this.returnFocusOnShiftTab = this.returnFocusOnShiftTab.bind(this)
this.onFocus = this.onFocus.bind(this)
}

toggleOpen() {
Expand Down Expand Up @@ -288,17 +290,66 @@ class Cell extends React.Component {
}
}

onFocus(event) {
event.target.classList.add('focused')
}

onEndFocus(event) {
event.target.classList.remove('focused')
}

onKeyDown(event) {
const cellControls = Array.from(
document.getElementsByClassName('dropdown-cell')[0].getElementsByTagName('button')
)
const currentIndex = cellControls.findIndex(e => event.target.innerHTML === e.innerHTML)

switch (event.key) {
case 'ArrowDown':
// If not at bottommost option, move to option below
event.preventDefault()

if (currentIndex === cellControls.length - 1) break

cellControls[currentIndex + 1].focus()
break

case 'ArrowUp':
// If not at topmost option, move to option above
event.preventDefault()
if (currentIndex === 0) break

cellControls[currentIndex - 1].focus()
break
case 'Tab':
// If at bottommost option and shift key not pressed, close dropdown menu
if (currentIndex === cellControls.length - 1 && !event.shiftKey) {
cellControls[0].click()
}

break
case 'Enter':
break
default:
event.preventDefault()
}
}

renderDropdown() {
return (
<div className="dropdown-cell" contentEditable={false}>
<div className="dropdown-cell" contentEditable={false} onKeyDown={this.onKeyDown}>
<button
className={isOrNot(this.state.isOpen, 'open')}
onClick={this.toggleOpen}
onKeyDown={this.returnFocusOnShiftTab}
>
<div className="table-options-icon"></div>
</button>
<div className={'drop-content-cell ' + isOrNot(this.state.isOpen, 'open')}>
<div
className={'drop-content-cell ' + isOrNot(this.state.isOpen, 'open')}
onFocus={this.onFocus}
onBlur={this.onEndFocus}
>
<button onClick={this.addRowAbove}>Insert Row Above</button>
<button onClick={this.addRowBelow}>Insert Row Below</button>
<button onClick={this.addColLeft}>Insert Column Left</button>
Expand Down
Loading