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

downshift v6 #1104

Merged
merged 7 commits into from
Jul 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
<p align="center" style="font-size: 1.2rem;">Primitives to build simple, flexible, WAI-ARIA compliant React
autocomplete, combobox or select dropdown components.</p>

> [Read the docs](https://downshift-js.com)
> |
> [Read the docs](https://downshift-js.com) |
> [See the intro blog post](https://kentcdodds.com/blog/introducing-downshift-for-react)
> |
> [Listen to the Episode 79 of the Full Stack Radio podcast](https://simplecast.com/s/f2e65eaf)
Expand Down Expand Up @@ -109,7 +108,6 @@ and `side-effect free`.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Installation](#installation)
- [Usage](#usage)
- [Basic Props](#basic-props)
Expand Down Expand Up @@ -972,9 +970,9 @@ described below.
work normally). See below for customizing the handlers.

- `Escape`: will clear downshift's state. This means that `highlightedIndex`
will be set to the `defaultHighlightedIndex`, the `inputValue` will be set to
empty string, `selectedItem` will be set to `null`, and the `isOpen` state
will be set to the `defaultIsOpen`.
will be set to the `defaultHighlightedIndex` and the `isOpen` state will be
set to the `defaultIsOpen`. If `isOpen` is already false, the `inputValue`
will be set to an empty string and `selectedItem` will be set to `null`

### customizing handlers

Expand Down Expand Up @@ -1426,6 +1424,7 @@ Thanks goes to these people ([emoji key][emojis]):

<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors][all-contributors] specification.
Expand Down
25 changes: 18 additions & 7 deletions src/__tests__/downshift.get-input-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -403,12 +403,14 @@ test('enter on an input with an open menu and a highlightedIndex but with IME co

// now it behaves normally
expect(childrenSpy).toHaveBeenCalledTimes(1)
expect(childrenSpy).toHaveBeenCalledWith(expect.objectContaining({
selectedItem: colors[0],
inputValue: colors[0],
isOpen: false,
highlightedIndex: null,
}))
expect(childrenSpy).toHaveBeenCalledWith(
expect.objectContaining({
selectedItem: colors[0],
inputValue: colors[0],
isOpen: false,
highlightedIndex: null,
}),
)
})

test('enter on an input with an open menu and a highlightedIndex selects that item', () => {
Expand Down Expand Up @@ -451,9 +453,18 @@ test('escape on an input without a selection should reset downshift and close th
)
})

test('escape on an input with a selection should reset downshift, clear input and close the menu', () => {
test('escape on an input with a selection and open should only reset downshift', () => {
const {escapeOnInput, childrenSpy} = renderDownshift()
escapeOnInput()
expect(childrenSpy).toHaveBeenLastCalledWith(
expect.objectContaining({isOpen: false}),
)
})

test('escape on an input with a selection and closed menu should reset downshift, clear input and close the menu', () => {
const {escapeOnInput, childrenSpy} = setupDownshiftWithState()
escapeOnInput()
escapeOnInput()
expect(childrenSpy).toHaveBeenLastCalledWith(
expect.objectContaining({
isOpen: false,
Expand Down
5 changes: 2 additions & 3 deletions src/downshift.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
getNextNonDisabledIndex,
getState,
isControlledProp,
validateControlledUnchanged
validateControlledUnchanged,
} from './utils'

class Downshift extends Component {
Expand Down Expand Up @@ -597,8 +597,7 @@ class Downshift extends Component {
event.preventDefault()
this.reset({
type: stateChangeTypes.keyDownEscape,
selectedItem: null,
inputValue: '',
...(!this.state.isOpen && {selectedItem: null, inputValue: ''}),
})
},
}
Expand Down
17 changes: 10 additions & 7 deletions src/hooks/useCombobox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -865,13 +865,16 @@ described below.
- `Home`: Moves `highlightedIndex` to first position.
- `Enter`: If there is a highlighted option, it will select it and close the
menu.
- `Escape`: It will close the menu if open and will clear selection: the value
in the `input` and the item stored as `selectedItem`.
- `Blur(Tab, Shift+Tab, MouseClick outside)`: It will close the menu select the
highlighted item if any. In the case of `(Shift+)Tab` the focus will move
naturally.

#### Menu
- `Escape`: It will close the menu if open. If the menu is closed, it will clear
selection: the value in the `input` will become an empty string and the item
stored as `selectedItem` will become `null`.
- `Blur(Tab, Shift+Tab)`: It will close the menu and select the highlighted
item, if any. The focus will move naturally to the next/previous element in
the Tab order.
- `Blur(mouse click outside)`: It will close the menu without selecting any
element, even if there is one highlighted.

#### Menu

- `MouseLeave`: Will clear the value of the `highlightedIndex` if it was set.

Expand Down
58 changes: 50 additions & 8 deletions src/hooks/useCombobox/__tests__/getInputProps.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ describe('getInputProps', () => {
)
})

test('escape it has the menu closed, item removed and focused kept on input', () => {
test('escape with menu open has the menu closed and focused kept on input', () => {
const {keyDownOnInput, input, getItems} = renderCombobox({
initialIsOpen: true,
initialHighlightedIndex: 2,
Expand All @@ -671,7 +671,21 @@ describe('getInputProps', () => {
keyDownOnInput('Escape')

expect(getItems()).toHaveLength(0)
expect(input.value).toBe('')
expect(input).toHaveValue(items[0])
expect(input).toHaveFocus()
})

test('escape with closed menu has item removed and focused kept on input', () => {
const {keyDownOnInput, input, getItems} = renderCombobox({
initialHighlightedIndex: 2,
initialSelectedItem: items[0],
})

input.focus()
keyDownOnInput('Escape')

expect(getItems()).toHaveLength(0)
expect(input).toHaveValue('')
expect(input).toHaveFocus()
})

Expand Down Expand Up @@ -845,13 +859,14 @@ describe('getInputProps', () => {
await changeInputValue(inputValue)
blurInput()

expect(input.value).toBe(inputValue)
expect(input).toHaveValue(inputValue)
})

test('by mouse is not triggered if target is within downshift', () => {
const stateReducer = jest.fn().mockImplementation(s => s)
const {input, container} = renderCombobox({
isOpen: true,
highlightedIndex: 0,
stateReducer,
})
document.body.appendChild(container)
Expand All @@ -866,15 +881,29 @@ describe('getInputProps', () => {

expect(stateReducer).toHaveBeenCalledTimes(1)
expect(stateReducer).toHaveBeenCalledWith(
expect.objectContaining({}),
expect.objectContaining({type: stateChangeTypes.InputBlur}),
{
highlightedIndex: 0,
inputValue: '',
isOpen: true,
selectedItem: null,
},
expect.objectContaining({
type: stateChangeTypes.InputBlur,
changes: {
highlightedIndex: -1,
inputValue: '',
isOpen: false,
selectedItem: null,
},
}),
)
})

test('by touch is not triggered if target is within downshift', () => {
const stateReducer = jest.fn().mockImplementation(s => s)
const {container, input} = renderCombobox({
isOpen: true,
highlightedIndex: 0,
stateReducer,
})
document.body.appendChild(container)
Expand All @@ -890,8 +919,21 @@ describe('getInputProps', () => {

expect(stateReducer).toHaveBeenCalledTimes(1)
expect(stateReducer).toHaveBeenCalledWith(
expect.objectContaining({}),
expect.objectContaining({type: stateChangeTypes.InputBlur}),
{
highlightedIndex: 0,
inputValue: '',
isOpen: true,
selectedItem: null,
},
expect.objectContaining({
type: stateChangeTypes.InputBlur,
changes: {
highlightedIndex: -1,
inputValue: '',
isOpen: false,
selectedItem: null,
},
}),
)
})
})
Expand Down Expand Up @@ -919,7 +961,7 @@ describe('getInputProps', () => {
})
getMenuProps({}, {suppressRefError: true})
getComboboxProps({}, {suppressRefError: true})

if (firstRender) {
firstRender = false
getInputProps({}, {suppressRefError: true})
Expand Down
3 changes: 3 additions & 0 deletions src/hooks/useCombobox/__tests__/props.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,7 @@ describe('props', () => {
expect(onSelectedItemChange).toHaveBeenCalledWith(
expect.objectContaining({
selectedItem: items[itemIndex],
type: stateChangeTypes.ItemClick,
}),
)
})
Expand Down Expand Up @@ -981,6 +982,7 @@ describe('props', () => {
expect(onHighlightedIndexChange).toHaveBeenCalledWith(
expect.objectContaining({
highlightedIndex: 0,
type: stateChangeTypes.InputKeyDownArrowDown,
}),
)
})
Expand Down Expand Up @@ -1058,6 +1060,7 @@ describe('props', () => {
expect(onIsOpenChange).toHaveBeenCalledWith(
expect.objectContaining({
isOpen: false,
type: stateChangeTypes.InputKeyDownEscape,
}),
)
})
Expand Down
2 changes: 2 additions & 0 deletions src/hooks/useCombobox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ function useCombobox(userProps = {}) {
() => {
dispatch({
type: stateChangeTypes.InputBlur,
selectItem: false,
})
},
)
Expand Down Expand Up @@ -425,6 +426,7 @@ function useCombobox(userProps = {}) {
if (!mouseAndTouchTrackersRef.current.isMouseDown) {
dispatch({
type: stateChangeTypes.InputBlur,
selectItem: true,
})
}
}
Expand Down
19 changes: 11 additions & 8 deletions src/hooks/useCombobox/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,11 @@ export default function downshiftUseComboboxReducer(state, action) {
case stateChangeTypes.InputKeyDownEscape:
changes = {
isOpen: false,
selectedItem: null,
highlightedIndex: -1,
inputValue: '',
...(!state.isOpen && {
selectedItem: null,
inputValue: '',
}),
}
break
case stateChangeTypes.InputKeyDownHome:
Expand Down Expand Up @@ -110,11 +112,12 @@ export default function downshiftUseComboboxReducer(state, action) {
case stateChangeTypes.InputBlur:
changes = {
isOpen: false,
...(state.highlightedIndex >= 0 && {
selectedItem: props.items[state.highlightedIndex],
inputValue: props.itemToString(props.items[state.highlightedIndex]),
highlightedIndex: -1,
}),
highlightedIndex: -1,
...(state.highlightedIndex >= 0 &&
action.selectItem && {
selectedItem: props.items[state.highlightedIndex],
inputValue: props.itemToString(props.items[state.highlightedIndex]),
}),
}
break
case stateChangeTypes.InputChange:
Expand Down Expand Up @@ -157,7 +160,7 @@ export default function downshiftUseComboboxReducer(state, action) {
case stateChangeTypes.FunctionSelectItem:
changes = {
selectedItem: action.selectedItem,
inputValue: props.itemToString(action.selectedItem)
inputValue: props.itemToString(action.selectedItem),
}
break
case stateChangeTypes.ControlledPropUpdatedSelectedItem:
Expand Down
3 changes: 3 additions & 0 deletions src/hooks/useSelect/__tests__/props.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,7 @@ describe('props', () => {
expect(onSelectedItemChange).toHaveBeenCalledWith(
expect.objectContaining({
selectedItem: items[index],
type: stateChangeTypes.ItemClick,
}),
)
})
Expand Down Expand Up @@ -994,6 +995,7 @@ describe('props', () => {
expect(onHighlightedIndexChange).toHaveBeenCalledWith(
expect.objectContaining({
highlightedIndex: 0,
type: stateChangeTypes.ToggleButtonKeyDownArrowDown,
}),
)
})
Expand Down Expand Up @@ -1066,6 +1068,7 @@ describe('props', () => {
expect(onIsOpenChange).toHaveBeenCalledWith(
expect.objectContaining({
isOpen: false,
type: stateChangeTypes.MenuKeyDownEscape,
}),
)
})
Expand Down
7 changes: 4 additions & 3 deletions src/hooks/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function callOnChangeProps(action, state, newState) {
const changes = {}

Object.keys(state).forEach(key => {
invokeOnChangeHandler(key, props, state, newState)
invokeOnChangeHandler(key, action, state, newState)

if (newState[key] !== state[key]) {
changes[key] = newState[key]
Expand All @@ -34,14 +34,15 @@ function callOnChangeProps(action, state, newState) {
}
}

function invokeOnChangeHandler(key, props, state, newState) {
function invokeOnChangeHandler(key, action, state, newState) {
const {props, type} = action
const handler = `on${capitalizeString(key)}Change`
if (
props[handler] &&
newState[key] !== undefined &&
newState[key] !== state[key]
) {
props[handler](newState)
props[handler]({type, ...newState})
}
}

Expand Down
Loading