Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
1ed0092
fix: Add TypeScript definitions (#223)
ellinge Mar 31, 2019
f8eb1c3
fix: Typing error for children (#232)
ellinge Apr 2, 2019
cb38633
fix: Prevent dropdown close on expand in Firefox (#231)
ellinge Apr 3, 2019
bdf47bc
docs: Add types friendly badge 📚 (#229)
mrchief Apr 13, 2019
7ae71bb
style: Auto format code using prettier before commit (#224)
mrchief Apr 13, 2019
b3e7ec9
feat: Added support for single select in tree dropdown (#217)
ellinge Apr 18, 2019
ef6e584
Add Breaking change to allowed prefixes
mrchief Apr 22, 2019
4e1d5af
feat: Ability to keep the dropdown open always (#241) ✨
moonjy1993 Apr 23, 2019
a4fb879
feat: Add keyboard navigation (#225)
ellinge May 1, 2019
3e5381e
feat: Add ARIA roles and attributes (#233)
gazab May 3, 2019
4806c8e
docs: add moonjy1993 as a contributor (#246)
allcontributors[bot] May 4, 2019
cbd256d
docs: add gazab as a contributor (#247)
allcontributors[bot] May 4, 2019
1f24344
fix: Remove babel-runtime from distribution bundle (#248) 🔥
mrchief May 6, 2019
aee6e7a
fix: Updated onAction to bubble up custom props (#230)
ellinge May 9, 2019
e12bda2
test: Update ava to produce consistent snapshots across OSes 🚨 (#250)
mrchief May 9, 2019
6b2f76f
feat: Group logically related props together (#242) ⚡️
ellinge May 9, 2019
8a9d148
refactor: Update mode to include hierarchical (#251)
mrchief May 12, 2019
a21bd11
Update PULL_REQUEST_TEMPLATE.md
mrchief May 21, 2019
874fd48
fix: Only adjust scroll on active descendant change
ellinge May 22, 2019
71d6e37
fix: Add test
ellinge May 22, 2019
8f2c7cf
fix: Moved keyboard tests and added active focus test
ellinge May 23, 2019
f7b7dd5
fix: Added test for only scroll on keyNav
ellinge May 23, 2019
c685edc
fix: Fixes jarring
ellinge May 23, 2019
5ec2fdc
fix: Check activeDescent only on change
ellinge May 23, 2019
3e255dd
style: Consistent formatting of code in example comment
Jun 4, 2019
9cdb943
Merge branch 'develop' into fix/FocusScrollAfterKeyboardSelection
ellinge Jun 6, 2019
297ed42
refactor: Consolidate showDropdown props (#253) 🔨
mrchief Jun 10, 2019
1fb6f91
Merge branch 'develop' into fix/FocusScrollAfterKeyboardSelection
mrchief Jun 10, 2019
fb5beb3
Merge branch 'master' into develop
mrchief Apr 18, 2019
764a002
fix: Updated onAction to bubble up custom props (#230)
ellinge May 9, 2019
1657711
test: Update ava to produce consistent snapshots across OSes 🚨 (#250)
mrchief May 9, 2019
007602b
feat: Group logically related props together (#242) ⚡️
ellinge May 9, 2019
637fe66
refactor: Update mode to include hierarchical (#251)
mrchief May 12, 2019
60624a4
chore: Update PULL_REQUEST_TEMPLATE.md
mrchief May 21, 2019
a943bea
style: Consistent formatting of code in example comment
Jun 4, 2019
655c45a
refactor: Consolidate showDropdown props (#253) 🔨️
mrchief Jun 10, 2019
77573a7
Merge branch 'develop' into fix/FocusScrollAfterKeyboardSelection
ellinge Jun 10, 2019
9669297
Merge branch develop
Jun 10, 2019
2682a05
Update index.js
ellinge Jun 10, 2019
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
79 changes: 78 additions & 1 deletion src/index.keyboardNav.test.js
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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(<DropdownTreeSelect data={largeTree} showDropdown="initial" />)
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(<DropdownTreeSelect data={largeTree} showDropdown="initial" />)
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(<DropdownTreeSelect data={tree} />)
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(<DropdownTreeSelect data={tree} />)
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)
})
19 changes: 0 additions & 19 deletions src/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(<DropdownTreeSelect data={tree} />)
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(<DropdownTreeSelect data={tree} texts={{ label: '#hello #world' }} />)
Expand Down
15 changes: 8 additions & 7 deletions src/tree/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,20 @@ 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),
}
}

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) {
Expand All @@ -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
}
}

Expand Down