diff --git a/src/index.keyboardNav.test.js b/src/index.keyboardNav.test.js
new file mode 100644
index 00000000..86243e36
--- /dev/null
+++ b/src/index.keyboardNav.test.js
@@ -0,0 +1,163 @@
+import test from 'ava'
+import React from 'react'
+import { spy } from 'sinon'
+import { mount } from 'enzyme'
+import DropdownTreeSelect from './index'
+
+const node = (id, label) => ({ id, label, value: label })
+
+const tree = {
+ ...node('c1', 'ccc 1'),
+ children: [
+ {
+ ...node('c2', 'ccc 2'),
+ children: [node('a1', 'aaa 1'), node('a2', 'aaa 2')],
+ },
+ {
+ ...node('c3', 'ccc 3'),
+ children: [node('a3', 'aaa 3'), node('b1', 'bbb 1'), node('b2', 'bbb 2')],
+ },
+ ],
+}
+
+const triggerOnKeyboardKeyDown = (wrapper, keys) => {
+ const elements = [].concat(keys || [])
+ elements.forEach(key => wrapper.find('.search').simulate('keyDown', { key }))
+}
+
+test('some key presses opens dropdown on keyboardNavigation', t => {
+ ;['ArrowUp', 'ArrowDown', 'Home', 'PageUp', 'End', 'PageDown', 'a', 'B'].forEach(key => {
+ const wrapper = mount(
)
+ triggerOnKeyboardKeyDown(wrapper, key)
+ t.true(wrapper.state().showDropdown)
+ })
+})
+
+test('esc closes dropdown on keyboardNavigation', async t => {
+ const wrapper = mount(
)
+ triggerOnKeyboardKeyDown(wrapper, ['ArrowDown', 'Escape'])
+ const showDropdown = await new Promise(resolve =>
+ setTimeout(() => {
+ resolve(wrapper.state().showDropdown)
+ }, 1)
+ )
+ t.false(showDropdown)
+})
+
+test('other key presses does not open dropdown on keyboardNavigation', t => {
+ ;['Enter', 'ArrowLeft', 'ArrowRight'].forEach(key => {
+ const wrapper = mount(
)
+ triggerOnKeyboardKeyDown(wrapper, key)
+ t.false(wrapper.state().showDropdown)
+ })
+})
+
+test('can navigate and focus child on keyboardNavigation', t => {
+ const wrapper = mount(
)
+ triggerOnKeyboardKeyDown(wrapper, [
+ 'ArrowDown',
+ 'ArrowRight',
+ 'ArrowRight',
+ 'ArrowDown',
+ 'ArrowRight',
+ 'ArrowRight',
+ 'ArrowDown',
+ ])
+ t.deepEqual(wrapper.find('li.focused').text(), 'bbb 1')
+})
+
+test('can navigate circular on keyboardNavigation', t => {
+ const wrapper = mount(
)
+ triggerOnKeyboardKeyDown(wrapper, ['ArrowDown', 'ArrowRight', 'ArrowRight', 'ArrowDown', 'ArrowDown'])
+ t.deepEqual(wrapper.find('li.focused').text(), 'ccc 1')
+ triggerOnKeyboardKeyDown(wrapper, 'ArrowUp')
+ t.deepEqual(wrapper.find('li.focused').text(), 'ccc 3')
+})
+
+test('can navigate to edge on keyboardNavigation', t => {
+ const wrapper = mount(
)
+ triggerOnKeyboardKeyDown(wrapper, ['ArrowDown', 'ArrowRight', 'ArrowRight', 'ArrowRight'])
+ ;['PageDown', 'PageUp', 'End', 'Home'].forEach((key, index) => {
+ const expected = index % 2 === 0 ? 'ccc 3' : 'ccc 1'
+ triggerOnKeyboardKeyDown(wrapper, key)
+ t.deepEqual(wrapper.find('li.focused').text(), expected)
+ })
+})
+
+test('can collapse on keyboardNavigation', t => {
+ const wrapper = mount(
)
+ triggerOnKeyboardKeyDown(wrapper, ['ArrowDown', 'ArrowRight', 'ArrowRight', 'ArrowDown'])
+ t.deepEqual(wrapper.find('li.focused').text(), 'ccc 3')
+
+ triggerOnKeyboardKeyDown(wrapper, 'ArrowLeft')
+ t.deepEqual(wrapper.find('li.focused').text(), 'ccc 1')
+ t.true(wrapper.find('#c3').exists())
+
+ triggerOnKeyboardKeyDown(wrapper, 'ArrowLeft')
+ t.false(wrapper.find('#c3').exists())
+})
+
+test('can navigate searchresult on keyboardNavigation', t => {
+ const wrapper = mount(
)
+ wrapper.instance().onInputChange('bb')
+ triggerOnKeyboardKeyDown(wrapper, ['b', 'ArrowDown', 'ArrowDown', 'ArrowDown'])
+ t.deepEqual(wrapper.find('li.focused').text(), 'bbb 1')
+})
+
+test('can navigate with keepTreOnSearch on keyboardNavigation', t => {
+ const wrapper = mount(
)
+ wrapper.instance().onInputChange('bb')
+ triggerOnKeyboardKeyDown(wrapper, ['b', 'ArrowDown', 'ArrowDown', 'ArrowDown'])
+ t.deepEqual(wrapper.find('li.focused').text(), 'bbb 1')
+ t.true(wrapper.find('#c1').exists())
+})
+
+test('can delete tags on empty search input with backspace on keyboardNavigation', t => {
+ const data = [{ ...node('a', 'a'), checked: true }, { ...node('b', 'b'), checked: true }]
+ const wrapper = mount(
)
+ wrapper.instance().searchInput.value = 'x'
+ triggerOnKeyboardKeyDown(wrapper, 'Backspace')
+ t.deepEqual(wrapper.state().tags.length, 2)
+
+ wrapper.instance().searchInput.value = ''
+ triggerOnKeyboardKeyDown(wrapper, 'Backspace')
+ t.deepEqual(wrapper.state().tags.length, 1)
+ triggerOnKeyboardKeyDown(wrapper, 'Backspace')
+ t.deepEqual(wrapper.state().tags.length, 0)
+})
+
+test('can select with enter on keyboardNavigation', t => {
+ const wrapper = mount(
)
+ triggerOnKeyboardKeyDown(wrapper, ['ArrowDown', 'ArrowDown', 'Enter'])
+ t.deepEqual(wrapper.state().tags.length, 1)
+})
+
+test('can delete tags with backspace/delete on keyboardNavigation', t => {
+ const data = [{ ...node('a', 'a'), checked: true }, { ...node('b', 'b'), checked: true }]
+ const wrapper = mount(
)
+ const event = {
+ type: 'keydown',
+ stopPropagation: spy(),
+ nativeEvent: { stopImmediatePropagation: spy() },
+ }
+ wrapper.find('#a_tag > .tag-remove').simulate('keyDown', { ...event, key: 'Backspace' })
+ t.deepEqual(wrapper.state().tags.length, 1)
+ wrapper.find('#b_tag > .tag-remove').simulate('keyUp', { ...event, key: 'Delete' })
+ t.deepEqual(wrapper.state().tags.length, 0)
+})
+
+test('remembers current focus between prop updates', t => {
+ const wrapper = mount(
)
+ t.false(wrapper.find('li.focused').exists())
+ triggerOnKeyboardKeyDown(wrapper, ['ArrowDown'])
+ t.deepEqual(wrapper.find('li.focused').text(), 'ccc 1')
+ wrapper.setProps({ data: tree })
+ wrapper.instance().handleClick()
+ t.deepEqual(wrapper.find('li.focused').text(), 'ccc 1')
+})
+
+test('should set current focus as selected on tab out for simpleSelect', t => {
+ const wrapper = mount(
)
+ triggerOnKeyboardKeyDown(wrapper, ['ArrowDown', 'ArrowRight', 'ArrowRight', 'Tab'])
+ t.deepEqual(wrapper.state().tags[0].label, 'ccc 1')
+})
diff --git a/src/index.test.js b/src/index.test.js
index 625549ad..fd620cb8 100644
--- a/src/index.test.js
+++ b/src/index.test.js
@@ -244,3 +244,17 @@ test('detects click outside when other dropdown instance', t => {
t.false(wrapper1.state().showDropdown)
})
+
+test('adds aria-labelledby when label contains # to search input', t => {
+ const { tree } = t.context
+ const wrapper = mount(
)
+ t.deepEqual(wrapper.find('.search').prop('aria-labelledby'), 'hello world')
+ t.deepEqual(wrapper.find('.search').prop('aria-label'), undefined)
+})
+
+test('adds aria-label when having label on search input', t => {
+ const { tree } = t.context
+ const wrapper = mount(
)
+ t.deepEqual(wrapper.find('.search').prop('aria-labelledby'), undefined)
+ t.deepEqual(wrapper.find('.search').prop('aria-label'), 'hello world')
+})
diff --git a/src/input/index.js b/src/input/index.js
index 32edf9bb..49d9c261 100644
--- a/src/input/index.js
+++ b/src/input/index.js
@@ -4,15 +4,23 @@ import cn from 'classnames/bind'
import Tag from '../tag'
import styles from './index.css'
import { getDataset, debounce } from '../utils'
+import { getAriaLabel } from '../a11y'
const cx = cn.bind(styles)
-const getTags = (tags = [], onDelete, readOnly, disabled) =>
+const getTags = (tags = [], onDelete, readOnly, disabled, labelRemove) =>
tags.map(tag => {
const { _id, label, tagClassName, dataset } = tag
return (
-
+
)
})
@@ -25,9 +33,13 @@ class Input extends PureComponent {
onFocus: PropTypes.func,
onBlur: PropTypes.func,
onTagRemove: PropTypes.func,
+ onKeyDown: PropTypes.func,
inputRef: PropTypes.func,
disabled: PropTypes.bool,
readOnly: PropTypes.bool,
+ activeDescendant: PropTypes.string,
+ label: PropTypes.string,
+ labelRemove: PropTypes.string,
}
constructor(props) {
@@ -50,11 +62,15 @@ class Input extends PureComponent {
onBlur,
disabled,
readOnly,
+ onKeyDown,
+ activeDescendant,
+ label,
+ labelRemove,
} = this.props
return (
diff --git a/src/tag/index.css b/src/tag/index.css
index 98395e88..e11f5231 100644
--- a/src/tag/index.css
+++ b/src/tag/index.css
@@ -4,6 +4,11 @@
padding: 2px 0 2px 2px;
border-radius: 2px;
display: inline-block;
+
+ &:focus-within {
+ background-color: #e9e9e9;
+ border-color: #a0a0a0;
+ }
}
.tag-remove {
@@ -19,4 +24,8 @@
&.disabled {
cursor: not-allowed;
}
+
+ &:focus {
+ color: #3c3c3c;
+ }
}
diff --git a/src/tag/index.js b/src/tag/index.js
index 34f24ac4..343c1f66 100644
--- a/src/tag/index.js
+++ b/src/tag/index.js
@@ -13,25 +13,52 @@ class Tag extends PureComponent {
onDelete: PropTypes.func,
readOnly: PropTypes.bool,
disabled: PropTypes.bool,
+ labelRemove: PropTypes.string,
}
handleClick = e => {
const { id, onDelete } = this.props
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
- onDelete(id)
+ onDelete(id, (e.key || e.keyCode) !== undefined)
+ }
+
+ onKeyDown = e => {
+ if (e.key === 'Backspace') {
+ this.handleClick(e)
+ e.preventDefault()
+ }
+ }
+
+ onKeyUp = e => {
+ if (e.keyCode === 32 || ['Delete', 'Enter'].indexOf(e.key) > -1) {
+ this.handleClick(e)
+ e.preventDefault()
+ }
}
render() {
- const { label, readOnly, disabled } = this.props
+ const { id, label, labelRemove = 'Remove', readOnly, disabled } = this.props
+
+ const tagId = `${id}_tag`
+ const className = cx('tag-remove', { readOnly }, { disabled })
+ const isDisabled = readOnly || disabled
+ const onClick = !isDisabled ? this.handleClick : undefined
+ const onKeyDown = !isDisabled ? this.onKeyDown : undefined
+ const onKeyUp = !isDisabled ? this.onKeyUp : undefined
return (
-
+
{label}
x
diff --git a/src/tree-manager/index.js b/src/tree-manager/index.js
index 14f99a80..20416f1a 100644
--- a/src/tree-manager/index.js
+++ b/src/tree-manager/index.js
@@ -1,7 +1,8 @@
import getPartialState from './getPartialState'
-
import { isEmpty } from '../utils'
import flattenTree from './flatten-tree'
+import nodeVisitor from './nodeVisitor'
+import keyboardNavigation, { FocusActionNames } from './keyboardNavigation'
class TreeManager {
constructor({ data, simpleSelect, radioSelect, showPartiallySelected, hierarchical, rootPrefixId }) {
@@ -47,20 +48,17 @@ class TreeManager {
const matches = []
+ const addOnMatch = node => {
+ if (node.label.toLowerCase().indexOf(searchTerm) >= 0) {
+ matches.push(node._id)
+ }
+ }
+
if (closestMatch !== searchTerm) {
const superMatches = this.searchMaps.get(closestMatch)
- superMatches.forEach(key => {
- const node = this.getNodeById(key)
- if (node.label.toLowerCase().indexOf(searchTerm) >= 0) {
- matches.push(node._id)
- }
- })
+ superMatches.forEach(key => addOnMatch(this.getNodeById(key)))
} else {
- this.tree.forEach(node => {
- if (node.label.toLowerCase().indexOf(searchTerm) >= 0) {
- matches.push(node._id)
- }
- })
+ this.tree.forEach(addOnMatch)
}
this.searchMaps.set(searchTerm, matches)
@@ -235,43 +233,49 @@ class TreeManager {
}
}
- getTags() {
+ get tags() {
if (this.radioSelect || this.simpleSelect) {
- return this._getTagsForSingleSelect()
- }
-
- const tags = []
- const visited = {}
- const markSubTreeVisited = node => {
- visited[node._id] = true
- if (!isEmpty(node._children)) node._children.forEach(c => markSubTreeVisited(this.getNodeById(c)))
+ if (this.currentChecked) {
+ return [this.getNodeById(this.currentChecked)]
+ }
+ return []
}
- this.tree.forEach((node, key) => {
- if (visited[key]) return
-
- if (node.checked) {
- tags.push(node)
- } else {
- visited[key] = true
- }
+ return nodeVisitor.getNodesMatching(this.tree, (node, key, visited) => {
if (node.checked && !this.hierarchical) {
// Parent node, so no need to walk children
- markSubTreeVisited(node)
+ nodeVisitor.markSubTreeVisited(node, visited, id => this.getNodeById(id))
}
+ return node.checked
})
- return tags
}
getTreeAndTags() {
- return { tree: this.tree, tags: this.getTags() }
+ return { tree: this.tree, tags: this.tags }
}
- _getTagsForSingleSelect() {
- if (this.currentChecked) {
- return [this.getNodeById(this.currentChecked)]
+ handleNavigationKey(currentFocus, tree, key, readOnly, markSubTreeOnNonExpanded, onToggleChecked, onToggleExpanded) {
+ const prevFocus = currentFocus && this.getNodeById(currentFocus)
+ const getNodeById = id => this.getNodeById(id)
+ const action = keyboardNavigation.getAction(prevFocus, key)
+
+ if (FocusActionNames.has(action)) {
+ const newFocus = keyboardNavigation.handleFocusNavigationkey(
+ tree,
+ action,
+ prevFocus,
+ getNodeById,
+ markSubTreeOnNonExpanded
+ )
+ return newFocus
+ }
+
+ if (!prevFocus || !tree.has(prevFocus._id)) {
+ // No current focus or not visible
+ return currentFocus
}
- return []
+
+ return keyboardNavigation.handleToggleNavigationkey(action, prevFocus, readOnly, onToggleChecked, onToggleExpanded)
}
}
diff --git a/src/tree-manager/keyboardNavigation.js b/src/tree-manager/keyboardNavigation.js
new file mode 100644
index 00000000..9798456e
--- /dev/null
+++ b/src/tree-manager/keyboardNavigation.js
@@ -0,0 +1,190 @@
+import nodeVisitor from './nodeVisitor'
+
+const Keys = {
+ Up: 'ArrowUp',
+ Down: 'ArrowDown',
+ Left: 'ArrowLeft',
+ Right: 'ArrowRight',
+ Enter: 'Enter',
+ Home: 'Home',
+ PageUp: 'PageUp',
+ End: 'End',
+ PageDown: 'PageDown',
+}
+
+export const NavActions = {
+ None: 'None',
+ FocusPrevious: 'FocusPrevious',
+ FocusNext: 'FocusNext',
+ FocusParent: 'FocusParent',
+ FocusFirst: 'FocusFirst',
+ FocusLast: 'FocusLast',
+ ToggleExpanded: 'ToggleExpanded',
+ ToggleChecked: 'ToggleChecked',
+}
+
+export const FocusActionNames = new Set([
+ NavActions.FocusPrevious,
+ NavActions.FocusNext,
+ NavActions.FocusParent,
+ NavActions.FocusFirst,
+ NavActions.FocusLast,
+])
+
+const validTriggerOpenKeys = [Keys.Up, Keys.Down, Keys.Home, Keys.PageUp, Keys.End, Keys.PageDown]
+const validKeys = validTriggerOpenKeys.concat([Keys.Left, Keys.Right, Keys.Enter])
+
+const isValidKey = (key, isOpen) => {
+ const keysToCheck = isOpen ? validKeys : validTriggerOpenKeys
+ return keysToCheck.indexOf(key) > -1
+}
+
+const isMatchingEvent = (key, keys, currentFocus, nonFocusKey) =>
+ keys.indexOf(key) > -1 || (!currentFocus && key === nonFocusKey)
+
+const isFocusFirstEvent = (key, currentFocus) => isMatchingEvent(key, [Keys.Home, Keys.PageUp], currentFocus, Keys.Down)
+
+const isFocusLastEvent = (key, currentFocus) => isMatchingEvent(key, [Keys.End, Keys.PageDown], currentFocus, Keys.Up)
+
+const isReverseTraverseAction = action =>
+ isMatchingEvent(action, [NavActions.FocusPrevious, NavActions.FocusLast], true)
+
+const isEdgeTraverseAction = action => isMatchingEvent(action, [NavActions.FocusFirst, NavActions.FocusLast], true)
+
+const getLeftNavAction = (currentFocus, key) => {
+ if (!currentFocus || key !== Keys.Left) return NavActions.None
+
+ if (currentFocus.expanded === true) {
+ return NavActions.ToggleExpanded
+ }
+ if (currentFocus._parent) {
+ return NavActions.FocusParent
+ }
+
+ return NavActions.None
+}
+
+const getRightNavAction = (currentFocus, key) => {
+ if (!currentFocus || !currentFocus._children || key !== Keys.Right) {
+ return NavActions.None
+ }
+
+ return currentFocus.expanded !== true ? NavActions.ToggleExpanded : NavActions.FocusNext
+}
+
+const getRelativeAction = (currentFocus, key) => {
+ if (!currentFocus) return NavActions.None
+ switch (key) {
+ case Keys.Up:
+ return NavActions.FocusPrevious
+ case Keys.Down:
+ return NavActions.FocusNext
+ case Keys.Enter:
+ return NavActions.ToggleChecked
+ default:
+ return NavActions.None
+ }
+}
+
+const getAction = (currentFocus, key) => {
+ let action
+ if (key === Keys.Left) {
+ action = getLeftNavAction(currentFocus, key)
+ } else if (key === Keys.Right) {
+ action = getRightNavAction(currentFocus, key)
+ } else if (isFocusFirstEvent(key, currentFocus)) {
+ action = NavActions.FocusFirst
+ } else if (isFocusLastEvent(key, currentFocus)) {
+ action = NavActions.FocusLast
+ } else {
+ action = getRelativeAction(currentFocus, key)
+ }
+ return action
+}
+
+const getParentFocus = (prevFocus, getNodeById) =>
+ prevFocus && prevFocus._parent ? getNodeById(prevFocus._parent) : prevFocus
+
+const getRelativeNeighborsFocus = (sortedNodes, prevFocus) => {
+ const nextIndex = sortedNodes.indexOf(prevFocus) + 1
+ if (nextIndex % sortedNodes.length === 0) {
+ return sortedNodes[0]
+ }
+ return sortedNodes[nextIndex]
+}
+
+const getRelativeFocus = (sortedNodes, prevFocus, action) => {
+ if (!sortedNodes || sortedNodes.length === 0) {
+ return prevFocus
+ }
+
+ let focus = prevFocus
+ if (isEdgeTraverseAction(action)) {
+ ;[focus] = sortedNodes
+ } else if ([NavActions.FocusPrevious, NavActions.FocusNext].indexOf(action) > -1) {
+ focus = getRelativeNeighborsFocus(sortedNodes, prevFocus)
+ }
+ return focus
+}
+
+const getNextFocus = (tree, prevFocus, action, getNodeById, markSubTreeOnNonExpanded) => {
+ if (action === NavActions.FocusParent) {
+ return getParentFocus(prevFocus, getNodeById)
+ }
+ if (!FocusActionNames.has(action)) {
+ return prevFocus
+ }
+
+ let nodes = nodeVisitor.getVisibleNodes(tree, getNodeById, markSubTreeOnNonExpanded)
+ if (isReverseTraverseAction(action)) {
+ nodes = nodes.reverse()
+ }
+
+ return getRelativeFocus(nodes, prevFocus, action)
+}
+
+const getNextFocusAfterTagDelete = (deletedId, prevTags, tags, fallback) => {
+ // Sets new focus to next tag or returns fallback
+ let index = prevTags && prevTags.findIndex(t => t._id === deletedId)
+ if (index < 0 || !tags.length) return fallback
+
+ index = tags.length > index ? index : tags.length - 1
+ const newFocusId = tags[index]._id
+ const focusNode = document.getElementById(`${newFocusId}_tag`)
+ if (focusNode) {
+ return focusNode.firstElementChild || fallback
+ }
+ return fallback
+}
+
+const handleFocusNavigationkey = (tree, action, prevFocus, getNodeById, markSubTreeOnNonExpanded) => {
+ const newFocus = keyboardNavigation.getNextFocus(tree, prevFocus, action, getNodeById, markSubTreeOnNonExpanded)
+ if (prevFocus && newFocus && prevFocus._id !== newFocus._id) {
+ prevFocus._focused = false
+ }
+ if (newFocus) {
+ newFocus._focused = true
+ return newFocus._id
+ }
+ return prevFocus && prevFocus._id
+}
+
+const handleToggleNavigationkey = (action, prevFocus, readOnly, onToggleChecked, onToggleExpanded) => {
+ if (action === NavActions.ToggleChecked && !readOnly && !(prevFocus.readOnly || prevFocus.disabled)) {
+ onToggleChecked(prevFocus._id, prevFocus.checked !== true)
+ } else if (action === NavActions.ToggleExpanded) {
+ onToggleExpanded(prevFocus._id)
+ }
+ return prevFocus && prevFocus._id
+}
+
+const keyboardNavigation = {
+ isValidKey,
+ getAction,
+ getNextFocus,
+ getNextFocusAfterTagDelete,
+ handleFocusNavigationkey,
+ handleToggleNavigationkey,
+}
+
+export default keyboardNavigation
diff --git a/src/tree-manager/nodeVisitor.js b/src/tree-manager/nodeVisitor.js
new file mode 100644
index 00000000..7b36700c
--- /dev/null
+++ b/src/tree-manager/nodeVisitor.js
@@ -0,0 +1,40 @@
+import { isEmpty } from '../utils'
+
+const markSubTreeVisited = (node, visited, getItemById) => {
+ visited[node._id] = true
+ if (!isEmpty(node._children)) {
+ node._children.forEach(c => markSubTreeVisited(getItemById(c), visited, getItemById))
+ }
+}
+
+const getNodesMatching = (tree, nodePredicate) => {
+ const nodes = []
+ const visited = {}
+
+ tree.forEach((node, key) => {
+ if (visited[key]) return
+
+ if (nodePredicate(node, key, visited)) {
+ nodes.push(node)
+ }
+
+ visited[key] = true
+ })
+
+ return nodes
+}
+
+const getVisibleNodes = (tree, getItemById, markSubTreeOnNonExpanded) =>
+ getNodesMatching(tree, (node, key, visited) => {
+ if (markSubTreeOnNonExpanded && node._children && node._children.length && node.expanded !== true) {
+ markSubTreeVisited(node, visited, getItemById)
+ }
+ return !node.hide
+ })
+
+const nodeVisitor = {
+ getNodesMatching,
+ getVisibleNodes,
+ markSubTreeVisited,
+}
+export default nodeVisitor
diff --git a/src/tree-manager/tests/index.test.js b/src/tree-manager/tests/index.test.js
index 8be6a8d3..29acc514 100644
--- a/src/tree-manager/tests/index.test.js
+++ b/src/tree-manager/tests/index.test.js
@@ -96,7 +96,7 @@ test('should get tags based on children check state', t => {
],
}
const manager = new TreeManager({ data: tree })
- t.deepEqual(manager.getTags().map(t => t.label), ['l1c1'])
+ t.deepEqual(manager.tags.map(t => t.label), ['l1c1'])
})
test('should get tags based on parent check state', t => {
@@ -113,7 +113,7 @@ test('should get tags based on parent check state', t => {
],
}
const manager = new TreeManager({ data: tree })
- t.deepEqual(manager.getTags().map(t => t.label), ['l1'])
+ t.deepEqual(manager.tags.map(t => t.label), ['l1'])
})
test('should get tags based on multiple parent check state', t => {
@@ -142,7 +142,7 @@ test('should get tags based on multiple parent check state', t => {
},
]
const manager = new TreeManager({ data: tree })
- t.deepEqual(manager.getTags().map(t => t.label), ['l1', 'l2'])
+ t.deepEqual(manager.tags.map(t => t.label), ['l1', 'l2'])
})
test('should get tags based on multiple parent/child check state', t => {
@@ -171,7 +171,7 @@ test('should get tags based on multiple parent/child check state', t => {
},
]
const manager = new TreeManager({ data: tree })
- t.deepEqual(manager.getTags().map(t => t.label), ['l1', 'l2c2'])
+ t.deepEqual(manager.tags.map(t => t.label), ['l1', 'l2c2'])
})
test('should toggle children when checked', t => {
diff --git a/src/tree-node/index.css b/src/tree-node/index.css
index 4f5c7bb8..5e23d5b8 100644
--- a/src/tree-node/index.css
+++ b/src/tree-node/index.css
@@ -21,6 +21,10 @@
}
}
}
+
+ &.focused {
+ background-color: #f4f4f4;
+ }
}
.toggle {
diff --git a/src/tree-node/index.js b/src/tree-node/index.js
index 324fce7b..e76f807a 100644
--- a/src/tree-node/index.js
+++ b/src/tree-node/index.js
@@ -27,6 +27,7 @@ const getNodeCx = props => {
showPartiallySelected,
readOnly,
checked,
+ _focused: focused,
} = props
return cx(
@@ -41,6 +42,7 @@ const getNodeCx = props => {
partial: showPartiallySelected && partial,
readOnly,
checked,
+ focused,
},
className
)
@@ -102,8 +104,10 @@ class TreeNode extends PureComponent {
const liCx = getNodeCx(this.props)
const style = keepTreeOnSearch || !searchModeOn ? { paddingLeft: `${(_depth || 0) * 20}px` } : {}
+ const liId = `${_id}_li`
+
return (
-
+
{
label: 'item0-0-0',
value: 'value0-0-0',
className: 'cn0-0-0',
+ _children: [{ label: 'item0-0-1', value: 'value0-0-1' }, { label: 'item0-0-2', value: 'value0-0-2' }],
}
const onChange = spy()
@@ -53,6 +54,24 @@ test('notifies node toggle changes', t => {
t.true(onChange.calledWith('0-0-0'))
})
+test('can toggle with enter and space', t => {
+ const node = {
+ _id: '0-0-0',
+ _parent: '0-0',
+ label: 'item0-0-0',
+ value: 'value0-0-0',
+ className: 'cn0-0-0',
+ _children: [{ label: 'item0-0-1', value: 'value0-0-1' }, { label: 'item0-0-2', value: 'value0-0-2' }],
+ }
+
+ ;[{ key: 'Enter' }, { keyCode: 32 }].forEach(event => {
+ const onChange = spy()
+ const wrapper = mount( )
+ wrapper.find('.toggle').simulate('keydown', event)
+ t.true(onChange.calledWith('0-0-0'))
+ })
+})
+
test('remove gap during search', t => {
const node = {
_id: '0-0-0',
diff --git a/src/tree-node/node-label.js b/src/tree-node/node-label.js
index 89bac4be..26474962 100644
--- a/src/tree-node/node-label.js
+++ b/src/tree-node/node-label.js
@@ -17,7 +17,6 @@ class NodeLabel extends PureComponent {
value: PropTypes.string.isRequired,
checked: PropTypes.bool,
partial: PropTypes.bool,
- expanded: PropTypes.bool,
disabled: PropTypes.bool,
dataset: PropTypes.object,
simpleSelect: PropTypes.bool,
@@ -56,7 +55,7 @@ class NodeLabel extends PureComponent {
nodeLabelProps.onClick = this.handleCheckboxChange
}
- const sharedProps = { id, value, checked, disabled, readOnly }
+ const sharedProps = { id, value, checked, disabled, readOnly, tabIndex: -1 }
return (
diff --git a/src/tree-node/toggle.js b/src/tree-node/toggle.js
index d98c428a..2c39b748 100644
--- a/src/tree-node/toggle.js
+++ b/src/tree-node/toggle.js
@@ -19,10 +19,28 @@ class Toggle extends PureComponent {
this.props.onNodeToggle(this.props.id)
}
+ onKeyDown = e => {
+ if (e.key === 'Enter' || e.keyCode === 32) {
+ this.props.onNodeToggle(this.props.id)
+ e.preventDefault()
+ }
+ }
+
render() {
const { expanded, isLeaf } = this.props
- const toggleCx = cx('toggle', { expanded: !isLeaf && expanded, collapsed: !isLeaf && !expanded })
- return
+ if (isLeaf) return null
+
+ const toggleCx = cx('toggle', { expanded, collapsed: !expanded })
+ return (
+
+ )
}
}
diff --git a/src/tree/index.js b/src/tree/index.js
index 52fb3283..b4a9497c 100644
--- a/src/tree/index.js
+++ b/src/tree/index.js
@@ -29,6 +29,7 @@ class Tree extends Component {
pageSize: PropTypes.number,
readOnly: PropTypes.bool,
clientId: PropTypes.string,
+ activeDescendant: PropTypes.string,
}
static defaultProps = {
@@ -47,7 +48,14 @@ class Tree extends Component {
componentWillReceiveProps = nextProps => {
this.computeInstanceProps(nextProps)
- this.setState({ items: this.allVisibleNodes.slice(0, this.props.pageSize) })
+ this.setState({ items: this.allVisibleNodes.slice(0, this.currentPage * this.props.pageSize) }, () => {
+ const { activeDescendant } = nextProps
+ const { scrollableTarget } = this.state
+ const activeLi = activeDescendant && document && document.getElementById(activeDescendant)
+ if (activeLi && scrollableTarget) {
+ scrollableTarget.scrollTop = activeLi.offsetTop - (scrollableTarget.clientHeight - activeLi.clientHeight) / 2
+ }
+ })
}
componentDidMount = () => {
@@ -57,7 +65,13 @@ class Tree extends Component {
computeInstanceProps = props => {
this.allVisibleNodes = this.getNodes(props)
this.totalPages = Math.ceil(this.allVisibleNodes.length / this.props.pageSize)
- this.currentPage = 1
+ if (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
+ }
}
getNodes = props => {
@@ -74,6 +88,7 @@ class Tree extends Component {
onChange,
onCheckboxChange,
onNodeToggle,
+ activeDescendant,
clientId,
} = props
const items = []
@@ -95,6 +110,7 @@ class Tree extends Component {
showPartiallySelected={showPartiallySelected}
readOnly={readOnly}
clientId={clientId}
+ activeDescendant={activeDescendant}
/>
)
}
@@ -102,7 +118,7 @@ class Tree extends Component {
return items
}
- hasMore = () => this.currentPage <= this.totalPages
+ hasMore = () => this.currentPage < this.totalPages
loadMore = () => {
this.currentPage = this.currentPage + 1
diff --git a/types/react-dropdown-tree-select.d.ts b/types/react-dropdown-tree-select.d.ts
index 40f10ad3..4622b12a 100644
--- a/types/react-dropdown-tree-select.d.ts
+++ b/types/react-dropdown-tree-select.d.ts
@@ -81,6 +81,10 @@ declare module "react-dropdown-tree-select" {
* Use to ensure a own unique id when a simple counter is not sufficient, e.g in a partial server render (SSR)
*/
id?: string;
+ /** Adds `aria-labelledby` to search input when input starts with `#`, adds `aria-label` to search input when label has value (not containing '#') */
+ label?: string;
+ /** The text to display for `aria-label` on tag delete buttons which is combined with `aria-labelledby` pointing to the node label. Defaults to `Remove */
+ labelRemove?: string;
}
export interface DropdownTreeSelectState {
diff --git a/yarn.lock b/yarn.lock
index 2ad8250a..c1879be9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -387,10 +387,10 @@
version "7.0.0-beta.49"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.0.0-beta.49.tgz#944d0c5ba2812bb159edbd226743afd265179bdc"
-"@babel/parser@^7.0.0", "@babel/parser@^7.4.0":
- version "7.4.2"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.2.tgz#b4521a400cb5a871eab3890787b4bc1326d38d91"
- integrity sha512-9fJTDipQFvlfSVdD/JBtkiY0br9BtfvW2R8wo6CX/Ej2eMuV0gWPk1M67Mt3eggQvBqYW1FCEk8BN7WvGm/g5g==
+"@babel/parser@^7.0.0", "@babel/parser@^7.4.0", "@babel/parser@^7.4.3":
+ version "7.4.3"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.3.tgz#eb3ac80f64aa101c907d4ce5406360fe75b7895b"
+ integrity sha512-gxpEUhTS1sGA63EGQGuA+WESPR/6tz6ng7tSHFCmaTJK/cGK8y37cBTspX+U2xCAue2IQVvF6Z0oigmjwD8YGQ==
"@babel/plugin-proposal-async-generator-functions@7.0.0-beta.44":
version "7.0.0-beta.44"
@@ -633,15 +633,15 @@
lodash "^4.17.5"
"@babel/traverse@^7.0.0":
- version "7.4.0"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.0.tgz#14006967dd1d2b3494cdd650c686db9daf0ddada"
- integrity sha512-/DtIHKfyg2bBKnIN+BItaIlEg5pjAnzHOIQe5w+rHAw/rg9g0V7T4rqPX8BJPfW11kt3koyjAnTNwCzb28Y1PA==
+ version "7.4.3"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.3.tgz#1a01f078fc575d589ff30c0f71bf3c3d9ccbad84"
+ integrity sha512-HmA01qrtaCwwJWpSKpA948cBvU5BrmviAief/b3AVw936DtcdsTexlbyzNuDnthwhOQ37xshn7hvQaEQk7ISYQ==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/generator" "^7.4.0"
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-split-export-declaration" "^7.4.0"
- "@babel/parser" "^7.4.0"
+ "@babel/parser" "^7.4.3"
"@babel/types" "^7.4.0"
debug "^4.1.0"
globals "^11.1.0"
@@ -957,6 +957,22 @@ add-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa"
+airbnb-prop-types@^2.12.0:
+ version "2.13.2"
+ resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.13.2.tgz#43147a5062dd2a4a5600e748a47b64004cc5f7fc"
+ integrity sha512-2FN6DlHr6JCSxPPi25EnqGaXC4OC3/B3k1lCd6MMYrZ51/Gf/1qDfaR+JElzWa+Tl7cY2aYOlsYJGFeQyVHIeQ==
+ dependencies:
+ array.prototype.find "^2.0.4"
+ function.prototype.name "^1.1.0"
+ has "^1.0.3"
+ is-regex "^1.0.4"
+ object-is "^1.0.1"
+ object.assign "^4.1.0"
+ object.entries "^1.1.0"
+ prop-types "^15.7.2"
+ prop-types-exact "^1.2.0"
+ react-is "^16.8.6"
+
ajv-errors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59"
@@ -1229,6 +1245,14 @@ array.partial@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/array.partial/-/array.partial-1.0.4.tgz#a88c8372953d8a8d495b24b8c94bdb285084d2a6"
+array.prototype.find@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.0.4.tgz#556a5c5362c08648323ddaeb9de9d14bc1864c90"
+ integrity sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=
+ dependencies:
+ define-properties "^1.1.2"
+ es-abstract "^1.7.0"
+
array.prototype.flat@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz#812db8f02cad24d3fab65dd67eabe3b8903494a4"
@@ -1279,6 +1303,7 @@ assign-symbols@^1.0.0:
ast-types-flow@0.0.7, ast-types-flow@^0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
+ integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0=
async-each@^1.0.0:
version "1.0.1"
@@ -1454,6 +1479,11 @@ aws4@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
+axe-core@^3.2.2:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-3.2.2.tgz#b06d6e9ae4636d706068843272bfaeed3fe97362"
+ integrity sha512-gAy4kMSPpuRJV3mwictJqlg5LhE84Vw2CydKdC4tvrLhR6+G3KW51zbL/vYujcLA2jvWOq3HMHrVeNuw+mrLVA==
+
axobject-query@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9"
@@ -2064,6 +2094,13 @@ babel-plugin-transform-regenerator@^6.24.1:
dependencies:
regenerator-transform "^0.10.0"
+babel-plugin-transform-runtime@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz#88490d446502ea9b8e7efb0fe09ec4d99479b1ee"
+ integrity sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=
+ dependencies:
+ babel-runtime "^6.22.0"
+
babel-plugin-transform-strict-mode@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758"
@@ -2297,9 +2334,9 @@ bluebird@^3.5.1:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.2.tgz#1be0908e054a751754549c270489c1505d4ab15a"
bluebird@^3.5.3:
- version "3.5.3"
- resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
- integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
+ version "3.5.4"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.4.tgz#d6cc661595de30d5b3af5fcedd3c0b3ef6ec5714"
+ integrity sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
version "4.11.8"
@@ -3117,10 +3154,15 @@ commander@^2.11.0:
version "2.15.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.0.tgz#ad2a23a1c3b036e392469b8012cec6b33b4c1322"
-commander@^2.13.0, commander@^2.14.1, commander@^2.9.0:
+commander@^2.13.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
+commander@^2.14.1, commander@^2.9.0:
+ version "2.20.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
+ integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
+
commander@~2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
@@ -4221,23 +4263,24 @@ entities@^1.1.1, entities@~1.1.1:
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
enzyme-adapter-react-16@^1.11.2:
- version "1.11.2"
- resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.11.2.tgz#8efeafb27e96873a5492fdef3f423693182eb9d4"
- integrity sha512-2ruTTCPRb0lPuw/vKTXGVZVBZqh83MNDnakMhzxhpJcIbneEwNy2Cv0KvL97pl57/GOazJHflWNLjwWhex5AAA==
+ version "1.12.1"
+ resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.12.1.tgz#6a2d74c80559d35ac0a91ca162fa45f4186290cf"
+ integrity sha512-GB61gvY97XvrA6qljExGY+lgI6BBwz+ASLaRKct9VQ3ozu0EraqcNn3CcrUckSGIqFGa1+CxO5gj5is5t3lwrw==
dependencies:
- enzyme-adapter-utils "^1.10.1"
+ enzyme-adapter-utils "^1.11.0"
object.assign "^4.1.0"
object.values "^1.1.0"
prop-types "^15.7.2"
- react-is "^16.8.4"
+ react-is "^16.8.6"
react-test-renderer "^16.0.0-0"
semver "^5.6.0"
-enzyme-adapter-utils@^1.10.1:
- version "1.10.1"
- resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.10.1.tgz#58264efa19a7befdbf964fb7981a108a5452ac96"
- integrity sha512-oasinhhLoBuZsIkTe8mx0HiudtfErUtG0Ooe1FOplu/t4c9rOmyG5gtrBASK6u4whHIRWvv0cbZMElzNTR21SA==
+enzyme-adapter-utils@^1.11.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.11.0.tgz#6ffff782b1b57dd46c72a845a91fc4103956a117"
+ integrity sha512-0VZeoE9MNx+QjTfsjmO1Mo+lMfunucYB4wt5ficU85WB/LoetTJrbuujmHP3PJx6pSoaAuLA+Mq877x4LoxdNg==
dependencies:
+ airbnb-prop-types "^2.12.0"
function.prototype.name "^1.1.0"
object.assign "^4.1.0"
object.fromentries "^2.0.0"
@@ -4416,9 +4459,9 @@ eslint-import-resolver-node@^0.3.2:
resolve "^1.5.0"
eslint-module-utils@^2.3.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.3.0.tgz#546178dab5e046c8b562bbb50705e2456d7bda49"
- integrity sha512-lmDJgeOOjk8hObTysjqH7wyMi+nsHwwvfBykwfhjR1LNdd7C2uFJBvx4OpWYpXOw4df1yE1cDEVd1yLHitk34w==
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz#8b93499e9b00eab80ccb6614e69f03678e84e09a"
+ integrity sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==
dependencies:
debug "^2.6.8"
pkg-dir "^2.0.0"
@@ -4483,6 +4526,7 @@ eslint-plugin-react@7.12.4:
eslint-scope@3.7.1, eslint-scope@^3.7.1:
version "3.7.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
+ integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=
dependencies:
esrecurse "^4.1.0"
estraverse "^4.1.1"
@@ -4551,7 +4595,6 @@ eslint@5.6.0:
eslint@^4.0.0, eslint@^4.5.0:
version "4.19.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300"
- integrity sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==
dependencies:
ajv "^5.3.0"
babel-code-frame "^6.22.0"
@@ -6687,9 +6730,9 @@ js-yaml@^3.10.0:
esprima "^4.0.0"
js-yaml@^3.12.0, js-yaml@^3.13.0:
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.0.tgz#38ee7178ac0eea2c97ff6d96fff4b18c7d8cf98e"
- integrity sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==
+ version "3.13.1"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
+ integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
@@ -7296,6 +7339,7 @@ log-symbols@^1.0.2:
log-symbols@^2.0.0, log-symbols@^2.1.0, log-symbols@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
+ integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
dependencies:
chalk "^2.0.1"
@@ -8223,6 +8267,16 @@ object.entries@^1.0.4:
function-bind "^1.1.0"
has "^1.0.1"
+object.entries@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.0.tgz#2024fc6d6ba246aee38bdb0ffd5cfbcf371b7519"
+ integrity sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==
+ dependencies:
+ define-properties "^1.1.3"
+ es-abstract "^1.12.0"
+ function-bind "^1.1.1"
+ has "^1.0.3"
+
object.fromentries@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.0.tgz#49a543d92151f8277b3ac9600f1e930b189d30ab"
@@ -8418,9 +8472,9 @@ p-map@^1.1.1:
integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==
p-map@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.0.0.tgz#be18c5a5adeb8e156460651421aceca56c213a50"
- integrity sha512-GO107XdrSUmtHxVoi60qc9tUl/KkNKm+X2CF4P9amalpGxv5YqVPJNfSb0wcA+syCopkZvYYIzW8OVTQW59x/w==
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
+ integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
p-try@^1.0.0:
version "1.0.0"
@@ -9466,6 +9520,15 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
+prop-types-exact@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/prop-types-exact/-/prop-types-exact-1.2.0.tgz#825d6be46094663848237e3925a98c6e944e9869"
+ integrity sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==
+ dependencies:
+ has "^1.0.3"
+ object.assign "^4.1.0"
+ reflect.ownkeys "^0.2.0"
+
prop-types@^15.5.10, prop-types@^15.5.4:
version "15.6.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca"
@@ -9693,12 +9756,7 @@ react-infinite-scroll-component@^4.0.2:
version "4.2.0"
resolved "https://registry.yarnpkg.com/react-infinite-scroll-component/-/react-infinite-scroll-component-4.2.0.tgz#605227dd213987c8d785157a87178acc26d465e1"
-react-is@^16.8.1:
- version "16.8.5"
- resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.5.tgz#c54ac229dd66b5afe0de5acbe47647c3da692ff8"
- integrity sha512-sudt2uq5P/2TznPV4Wtdi+Lnq3yaYW8LfvPKLM9BKD8jJNBkxMVyB0C9/GmVhLw7Jbdmndk/73n7XQGeN9A3QQ==
-
-react-is@^16.8.4:
+react-is@^16.8.1, react-is@^16.8.6:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
@@ -9889,6 +9947,11 @@ reduce-function-call@^1.0.1, reduce-function-call@^1.0.2:
dependencies:
balanced-match "^0.4.2"
+reflect.ownkeys@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460"
+ integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=
+
regenerate-unicode-properties@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c"
@@ -11241,9 +11304,9 @@ symbol-tree@^3.2.2:
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
synchronous-promise@^2.0.5:
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.6.tgz#de76e0ea2b3558c1e673942e47e714a930fa64aa"
- integrity sha512-TyOuWLwkmtPL49LHCX1caIwHjRzcVd62+GF6h8W/jHOeZUFHpnd2XJDVuUlaTaLPH1nuu2M69mfHr5XbQJnf/g==
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.7.tgz#3574b3d2fae86b145356a4b89103e1577f646fe3"
+ integrity sha512-16GbgwTmFMYFyQMLvtQjvNWh30dsFe1cAW5Fg1wm5+dg84L9Pe36mftsIRU95/W2YsISxsz/xq4VB23sqpgb/A==
table@4.0.2:
version "4.0.2"