diff --git a/src/index.keyboardNav.test.js b/src/index.keyboardNav.test.js
index 3cd039a2..f484b908 100644
--- a/src/index.keyboardNav.test.js
+++ b/src/index.keyboardNav.test.js
@@ -1,6 +1,6 @@
import test from 'ava'
import React from 'react'
-import { spy } from 'sinon'
+import { spy, stub } from 'sinon'
import { mount } from 'enzyme'
import DropdownTreeSelect from './index'
@@ -161,3 +161,80 @@ test('should set current focus as selected on tab out for simpleSelect', t => {
triggerOnKeyboardKeyDown(wrapper, ['ArrowDown', 'ArrowRight', 'ArrowRight', 'Tab'])
t.deepEqual(wrapper.state().tags[0].label, 'ccc 1')
})
+
+test('should scroll on keyboard navigation', t => {
+ const largeTree = [...Array(150).keys()].map(i => node(`id${i}`, `label${i}`))
+ const wrapper = mount()
+ const getElementById = stub(document, 'getElementById')
+ const contentNode = wrapper.find('.dropdown-content').getDOMNode()
+
+ t.deepEqual(contentNode.scrollTop, 0)
+
+ triggerOnKeyboardKeyDown(wrapper, ['ArrowUp'])
+ largeTree.forEach((n, index) => {
+ getElementById.withArgs(`${n.id}_li`).returns({ offsetTop: index, clientHeight: 1 })
+ })
+
+ triggerOnKeyboardKeyDown(wrapper, ['ArrowUp'])
+ t.deepEqual(wrapper.find('li.focused').text(), 'label148')
+ t.notDeepEqual(contentNode.scrollTop, 0)
+
+ getElementById.restore()
+})
+
+test('should only scroll on keyboard navigation', t => {
+ const largeTree = [...Array(150).keys()].map(i => node(`id${i}`, `label${i}`))
+ const wrapper = mount()
+ const getElementById = stub(document, 'getElementById')
+ const contentNode = wrapper.find('.dropdown-content').getDOMNode()
+
+ triggerOnKeyboardKeyDown(wrapper, ['ArrowUp'])
+ largeTree.forEach((n, index) => {
+ getElementById.withArgs(`${n.id}_li`).returns({ offsetTop: index, clientHeight: 1 })
+ })
+
+ triggerOnKeyboardKeyDown(wrapper, ['ArrowUp'])
+
+ const scrollTop = contentNode.scrollTop
+
+ // Simulate scroll up and setting new props
+ contentNode.scrollTop -= 20
+ const newTree = largeTree.map(n => {
+ return { checked: true, ...n }
+ })
+ wrapper.setProps({ data: newTree, showDropdown: 'initial' })
+ t.notDeepEqual(contentNode.scrollTop, scrollTop)
+
+ // Verify scroll is restored to previous position after keyboard nav
+ triggerOnKeyboardKeyDown(wrapper, ['ArrowUp', 'ArrowDown'])
+ t.deepEqual(contentNode.scrollTop, scrollTop)
+
+ getElementById.restore()
+})
+
+const keyDownTests = [
+ { keyCode: 13, expected: true }, // Enter
+ { keyCode: 32, expected: true }, // Space
+ { keyCode: 40, expected: true }, // Arrow down
+ { keyCode: 9, expected: false }, // Tab
+ { keyCode: 38, expected: false }, // Up arrow
+]
+
+keyDownTests.forEach(testArgs => {
+ test(`Key code ${testArgs.keyCode} ${testArgs.expected ? 'can' : "can't"} open dropdown on keyDown`, t => {
+ const wrapper = mount()
+ const trigger = wrapper.find('.dropdown-trigger')
+ trigger.instance().focus()
+ trigger.simulate('keyDown', { key: 'mock', keyCode: testArgs.keyCode })
+ t.is(wrapper.state().showDropdown, testArgs.expected)
+ })
+})
+
+test(`Key event should not trigger if not focused/active element`, t => {
+ const wrapper = mount()
+ const trigger = wrapper.find('.dropdown-trigger')
+ const input = wrapper.find('.search')
+ input.instance().focus()
+ trigger.simulate('keyDown', { key: 'mock', keyCode: 13 })
+ t.is(wrapper.state().showDropdown, false)
+})
diff --git a/src/index.test.js b/src/index.test.js
index c79f22d2..1f947880 100644
--- a/src/index.test.js
+++ b/src/index.test.js
@@ -255,25 +255,6 @@ test('detects click outside when other dropdown instance', t => {
t.false(wrapper1.state().showDropdown)
})
-const keyDownTests = [
- { keyCode: 13, expected: true }, // Enter
- { keyCode: 32, expected: true }, // Space
- { keyCode: 40, expected: true }, // Arrow down
- { keyCode: 9, expected: false }, // Tab
- { keyCode: 38, expected: false }, // Up arrow
-]
-
-keyDownTests.forEach(testArgs => {
- test(`Key code ${testArgs.keyCode} ${testArgs.expected ? 'can' : "can't"} open dropdown on keyDown`, t => {
- const { tree } = t.context
- const wrapper = mount()
- const trigger = wrapper.find('.dropdown-trigger')
- trigger.instance().focus()
- trigger.simulate('keyDown', { key: 'mock', keyCode: testArgs.keyCode })
- t.is(wrapper.state().showDropdown, testArgs.expected)
- })
-})
-
test('adds aria-labelledby when label contains # to search input', t => {
const { tree } = t.context
const wrapper = mount()
diff --git a/src/tree/index.js b/src/tree/index.js
index 71dc577f..ac4276ac 100644
--- a/src/tree/index.js
+++ b/src/tree/index.js
@@ -38,7 +38,8 @@ class Tree extends Component {
constructor(props) {
super(props)
- this.computeInstanceProps(props)
+ this.currentPage = 1
+ this.computeInstanceProps(props, true)
this.state = {
items: this.allVisibleNodes.slice(0, this.props.pageSize),
@@ -46,9 +47,11 @@ class Tree extends Component {
}
componentWillReceiveProps = nextProps => {
- this.computeInstanceProps(nextProps)
+ const { activeDescendant } = nextProps
+ const hasSameActiveDescendant = activeDescendant === this.props.activeDescendant
+ this.computeInstanceProps(nextProps, !hasSameActiveDescendant)
this.setState({ items: this.allVisibleNodes.slice(0, this.currentPage * this.props.pageSize) }, () => {
- const { activeDescendant } = nextProps
+ if (hasSameActiveDescendant) return
const { scrollableTarget } = this.state
const activeLi = activeDescendant && document && document.getElementById(activeDescendant)
if (activeLi && scrollableTarget) {
@@ -61,15 +64,13 @@ class Tree extends Component {
this.setState({ scrollableTarget: this.node.parentNode })
}
- computeInstanceProps = props => {
+ computeInstanceProps = (props, checkActiveDescendant) => {
this.allVisibleNodes = this.getNodes(props)
this.totalPages = Math.ceil(this.allVisibleNodes.length / this.props.pageSize)
- if (props.activeDescendant) {
+ if (checkActiveDescendant && props.activeDescendant) {
const currentId = props.activeDescendant.replace(/_li$/, '')
const focusIndex = this.allVisibleNodes.findIndex(n => n.key === currentId) + 1
this.currentPage = focusIndex > 0 ? Math.ceil(focusIndex / this.props.pageSize) : 1
- } else {
- this.currentPage = 1
}
}