Skip to content

Commit

Permalink
feat: V9 (#1582)
Browse files Browse the repository at this point in the history
<!--
Thanks for your interest in the project. Bugs filed and PRs submitted are appreciated!

Please make sure that you are familiar with and follow the Code of Conduct for
this project (found in the CODE_OF_CONDUCT.md file).

Also, please make sure you're familiar with and follow the instructions in the
contributing guidelines (found in the CONTRIBUTING.md file).

If you're new to contributing to open source projects, you might find this free
video course helpful: http://kcd.im/pull-request

Please fill out the information below to expedite the review and (hopefully)
merge of your pull request!
-->

<!-- What changes are being made? (What feature/bug is being fixed here?) -->

**What**:
BREAKING CHANGE: Release Downshift v9.

<!-- Why are these changes necessary? -->

**Why**:
Release the changes in:
- #1580
- #1579
- #1583

Closes #1322.
Closes #1244.
Closes #1227
Closes #1225.
<!-- How were these changes implemented? -->

**How**:
Merged the PRs in this branch.
<!-- Have you done all of these things?  -->

**Checklist**:

<!-- add "N/A" to the end of each line that's irrelevant to your changes -->

<!-- to check an item, place an "x" in the box like so: "- [x] Documentation" -->

- [x] Documentation
- [x] Tests
- [x] TypeScript Types
- [ ] Flow Types
- [x] Ready to be merged
      <!-- In your opinion, is this ready to be merged as soon as it's reviewed? -->

<!-- feel free to add additional comments -->
  • Loading branch information
silviuaavram committed Mar 20, 2024
1 parent 7997f76 commit 5b0d503
Show file tree
Hide file tree
Showing 23 changed files with 549 additions and 1,017 deletions.
2 changes: 1 addition & 1 deletion src/__mocks__/set-a11y-status.js
@@ -1 +1 @@
module.exports = jest.fn()
module.exports = {setStatus: jest.fn(), cleanupStatusDiv: jest.fn()}
8 changes: 4 additions & 4 deletions src/__tests__/downshift.lifecycle.js
@@ -1,7 +1,7 @@
import * as React from 'react'
import {act, fireEvent, render, screen} from '@testing-library/react'
import Downshift from '../'
import setA11yStatus from '../set-a11y-status'
import {setStatus} from '../set-a11y-status'
import * as utils from '../utils'

jest.useFakeTimers()
Expand Down Expand Up @@ -124,7 +124,7 @@ test('handles state change for touchevent events', () => {
})

test('props update causes the a11y status to be updated', () => {
setA11yStatus.mockReset()
setStatus.mockReset()
const MyComponent = () => (
<Downshift isOpen={false}>
{({getInputProps, getItemProps, isOpen}) => (
Expand All @@ -139,11 +139,11 @@ test('props update causes the a11y status to be updated', () => {
const {container, unmount} = render(<MyComponent />)
render(<MyComponent isOpen={true} />, {container})
jest.runAllTimers()
expect(setA11yStatus).toHaveBeenCalledTimes(1)
expect(setStatus).toHaveBeenCalledTimes(1)
render(<MyComponent isOpen={false} />, {container})
unmount()
jest.runAllTimers()
expect(setA11yStatus).toHaveBeenCalledTimes(1)
expect(setStatus).toHaveBeenCalledTimes(1)
})

test('inputValue initializes properly if the selectedItem is controlled and set', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/set-a11y-status.js
Expand Up @@ -80,5 +80,5 @@ test('creates no status div if there is no document', () => {

function setup() {
jest.resetModules()
return require('../set-a11y-status').default
return require('../set-a11y-status').setStatus
}
4 changes: 2 additions & 2 deletions src/downshift.js
Expand Up @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
import {Component, cloneElement} from 'react'
import {isForwardRef} from 'react-is'
import {isPreact, isReactNative, isReactNativeWeb} from './is.macro'
import setA11yStatus from './set-a11y-status'
import {setStatus} from './set-a11y-status'
import * as stateChangeTypes from './stateChangeTypes'
import {
handleRefs,
Expand Down Expand Up @@ -1057,7 +1057,7 @@ class Downshift extends Component {
})
this.previousResultCount = resultCount

setA11yStatus(status, this.props.environment.document)
setStatus(status, this.props.environment.document)
}, 200)

componentDidMount() {
Expand Down
94 changes: 94 additions & 0 deletions src/hooks/MIGRATION_V9.md
@@ -0,0 +1,94 @@
# Migration from v8 to v9

Downshift v8 receives a list of breaking changes, which are necessary to improve
both the user and the developer experience. The changes are only affecting the
hooks and are detailed below.

## Table of Contents

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [onChange Typescript Improvements](#onchange-typescript-improvements)
- [getA11ySelectionMessage](#geta11yselectionmessage)
- [getA11yRemovalMessage](#geta11yremovalmessage)
- [getA11yStatusMessage](#geta11ystatusmessage)
- [selectedItemChanged](#selecteditemchanged)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## onChange Typescript Improvements

The handlers below have their types improved to reflect that they will always
get called with their corresponding state prop:

- useCombobox

- onSelectedItemChange: selectedItem is non optional
- onIsOpenChange: isOpen is non optional
- onHighlightedIndexChange: highlightedIndex is non optional

- useSelect

- onSelectedItemChange: selectedItem is non optional
- onIsOpenChange: isOpen is non optional
- onHighlightedIndexChange: highlightedIndex is non optional
- onInputValueChange: inputValue is non optional

- useMultipleSelection
- onActiveIndexChange: activeIndex is non optional
- onSelectedItemsChange: selectedItems is non optional

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## getA11ySelectionMessage

The prop has been removed from useSelect and useCombobox. If you still need an
a11y selection message, use either `getA11yStatusMessage` or your own aria-live
implementation inside a `onStateChange` callback.

## getA11yRemovalMessage

The prop has been removed from useMultipleSelection. If you still need an a11y
removal message, use either `getA11yStatusMessage` or your own aria-live
implementation inside a `onStateChange` callback.

## getA11yStatusMessage

The prop has been also added to useMultipleSelection, but has some changes
reflected in each of the hook's readme.

- there is no default function provided, so you will not get any aria-live
message anymore if you don't provide the prop directly to the hooks.
- the function is called only with the hook's state, and you should already have
access to the props, such as items or itemToString. Values such as
highlightedItem or resultsCount have been removed, so you need to compute them
yourself if needed.
- `Downshift` is not affected, it has the same `getA11yStatusMessage` as before,
no changes there at all.

The HTML markup with the ARIA attributes we provide through the getter props
should be enough for screen readers to report:

- results count.
- highlighted item.
- item selection.
- what actions the user can take.

If you need anything more specific as part of an aria-live region, please use
the new version of `getA11yStatusMessage` or your own aria-live implementation.

References:

- [useCombobox docs](https://github.com/downshift-js/downshift/blob/master/src/hooks/useCombobox/README.md#geta11ystatusmessage)
- [useSelect docs](https://github.com/downshift-js/downshift/blob/master/src/hooks/useSelect/README.md#geta11ystatusmessage)
- [useMultipleSelection docs](https://github.com/downshift-js/downshift/blob/master/src/hooks/useMultipleSelection/README.md#geta11ystatusmessage)

## selectedItemChanged

This prop has been removed from `useCombobox`. You should use `itemToKey`
instead.

Reference:

[itemToKey docs](https://github.com/downshift-js/downshift/blob/master/src/hooks/useCombobox/README.md#itemtokey)
4 changes: 2 additions & 2 deletions src/hooks/testUtils.js
Expand Up @@ -48,8 +48,8 @@ export const defaultIds = {
inputId: 'downshift-test-id-input',
}

export const waitForDebouncedA11yStatusUpdate = () =>
act(() => jest.advanceTimersByTime(200))
export const waitForDebouncedA11yStatusUpdate = (shouldBeCleared = false) =>
act(() => jest.advanceTimersByTime(shouldBeCleared ? 700 : 200))

export const MemoizedItem = React.memo(function Item({
index,
Expand Down
93 changes: 41 additions & 52 deletions src/hooks/useCombobox/README.md
Expand Up @@ -53,12 +53,14 @@ follows:
- inline autocomplete based on the highlighted item in the menu is also
performed by the consumer.

## Migration to v7
## Migration through breaking changes

`useCombobox` received some changes related to how it works in version 7, as a
conequence of adapting it to the ARIA 1.2 combobox pattern. If you were using
_useCombobox_ previous to 7.0.0, check the [migration guide][migration-guide-v7]
and update if necessary.
The hook received breaking changes related to how it works, as well as the API,
starting with v7. They are documented here:

- [v7 migration guide][migration-guide-v7]
- [v8 migration guide][migration-guide-v8]
- [v9 migration guide][migration-guide-v9]

## Table of Contents

Expand All @@ -82,9 +84,7 @@ and update if necessary.
- [defaultHighlightedIndex](#defaulthighlightedindex)
- [defaultInputValue](#defaultinputvalue)
- [itemToKey](#itemtokey)
- [selectedItemChanged](#selecteditemchanged)
- [getA11yStatusMessage](#geta11ystatusmessage)
- [getA11ySelectionMessage](#geta11yselectionmessage)
- [onHighlightedIndexChange](#onhighlightedindexchange)
- [onIsOpenChange](#onisopenchange)
- [onInputValueChange](#oninputvaluechange)
Expand Down Expand Up @@ -412,8 +412,8 @@ function itemToKey(item) {
```

> This deprecates the "selectedItemChanged" prop. If you are using the prop
> already, make sure you change to "itemToKey" as the former will be removed in
> the next Breaking Change update. A migration example:
> already, make sure you change to "itemToKey" as the former is removed in v9. A
> migration example:
```js
// initial items.
Expand All @@ -439,60 +439,45 @@ function itemToKey(item) {
}
```

### selectedItemChanged

> DEPRECATED. Please use "itemToKey".
> `function(prevItem: any, item: any)` | defaults to:
> `(prevItem, item) => (prevItem !== item)`
Used to determine if the new `selectedItem` has changed compared to the previous
`selectedItem` and properly update Downshift's internal state.

### getA11yStatusMessage

> `function({/* see below */})` | default messages provided in English
This function is passed as props to a status updating function nested within
that allows you to create your own ARIA statuses. It is called when one of the
following props change: `items`, `highlightedIndex`, `inputValue` or `isOpen`.

A default `getA11yStatusMessage` function is provided that will check
`resultCount` and return "No results are available." or if there are results ,
"`resultCount` results are available, use up and down arrow keys to navigate.
Press Enter key to select."

> Note: `resultCount` is `items.length` in our default version of the function.
### getA11ySelectionMessage
that allows you to create your own ARIA statuses. It is called when the state
changes: `selectedItem`, `highlightedIndex`, `inputValue` or `isOpen`.

> `function({/* see below */})` | default messages provided in English
There is no default function provided anymore since v9, so if there's no prop
passed, no aria live status message is created. An implementation that resembles
the previous default is written below, should you want to keep pre v9 behaviour.

This function is similar to the `getA11yStatusMessage` but it is generating a
message when an item is selected. It is passed as props to a status updating
function nested within that allows you to create your own ARIA statuses. It is
called when `selectedItem` changes.
We don't provide this as a default anymore since we consider that screen readers
have been significantly improved and they can convey information about items
count, possible actions and highlighted items only from the HTML markup, without
the need for aria-live regions.

A default `getA11ySelectionMessage` function is provided. When an item is
selected, the message is a selection related one, narrating
"`itemToString(selectedItem)` has been selected".
```js
function getA11yStatusMessage(state) {
if (!state.isOpen) {
return ''
}
// you need to get resultCount and previousResultCount yourself now, since we don't pass them as arguments anymore
const resultCount = items.length
const previousResultCount = previousResultCountRef.current

The object you are passed to generate your status message, for both
`getA11yStatusMessage` and `getA11ySelectionMessage`, has the following
properties:
if (!resultCount) {
return 'No results are available.'
}

<!-- This table was generated via http://www.tablesgenerator.com/markdown_tables -->
if (resultCount !== previousResultCount) {
return `${resultCount} result${
resultCount === 1 ? ' is' : 's are'
} available, use up and down arrow keys to navigate. Press Enter key to select.`
}

| property | type | description |
| --------------------- | --------------- | -------------------------------------------------------------------------------------------- |
| `highlightedIndex` | `number` | The currently highlighted index |
| `highlightedItem` | `any` | The value of the highlighted item |
| `isOpen` | `boolean` | The `isOpen` state |
| `inputValue` | `string` | The value in the text input. |
| `itemToString` | `function(any)` | The `itemToString` function (see props) for getting the string value from one of the options |
| `previousResultCount` | `number` | The total items showing in the dropdown the last time the status was updated |
| `resultCount` | `number` | The total items showing in the dropdown |
| `selectedItem` | `any` | The value of the currently selected item |
return ''
}
```

### onHighlightedIndexChange

Expand Down Expand Up @@ -1168,3 +1153,7 @@ suggestion and the Codesandbox for it, and we will take it from there.
https://github.com/downshift-js/downshift#advanced-react-component-patterns-course
[migration-guide-v7]:
https://github.com/downshift-js/downshift/tree/master/src/hooks/MIGRATION_V7.md#usecombobox
[migration-guide-v8]:
https://github.com/downshift-js/downshift/tree/master/src/hooks/MIGRATION_V8.md
[migration-guide-v9]:
https://github.com/downshift-js/downshift/tree/master/src/hooks/MIGRATION_V9.md

0 comments on commit 5b0d503

Please sign in to comment.