diff --git a/.changeset/allow-event-bubbling-for-all-controls.md b/.changeset/allow-event-bubbling-for-all-controls.md deleted file mode 100644 index 7c8dab422c..0000000000 --- a/.changeset/allow-event-bubbling-for-all-controls.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'react-select': patch ---- - -Use defaultPrevented to skip duplicate event handler for clicking select. diff --git a/.changeset/serious-cheetahs-give.md b/.changeset/serious-cheetahs-give.md deleted file mode 100644 index 98f8702933..0000000000 --- a/.changeset/serious-cheetahs-give.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'react-select': patch ---- - -Fix type inference for Async's loadOptions prop diff --git a/.circleci/config.yml b/.circleci/config.yml index 4a9568ad3d..4175d429d0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 docker_defaults: &docker_defaults docker: - - image: cypress/browsers:node12.18.0-chrome83-ff77 + - image: cypress/browsers:node16.17.1-chrome106-ff105-edge environment: TERM: xterm working_directory: ~/project/repo diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index 3a9b247319..513e922358 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -1,5 +1,6 @@ { "buildCommand": "build", "packages": ["packages/*"], - "sandboxes": ["nfmxw"] + "sandboxes": ["nfmxw"], + "node": "16" } diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..902ebdae29 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: 'npm' + directory: '/' + schedule: + interval: 'weekly' + ignore: + - dependency-name: '*' + update-types: + ['version-update:semver-minor', 'version-update:semver-patch'] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c666fca674..3444cd9069 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,8 +5,15 @@ on: branches: - master +permissions: + contents: read + jobs: release: + permissions: + # for changesets/action + contents: write + pull-requests: write name: Release runs-on: ubuntu-latest steps: @@ -16,10 +23,10 @@ jobs: # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits fetch-depth: 0 - - name: Setup Node.js 10.x + - name: Setup Node.js 16.x uses: actions/setup-node@master with: - node-version: 10.x + node-version: 16.x - name: Install Yarn run: npm install --global yarn diff --git a/.nvmrc b/.nvmrc index 48082f72f0..b6a7d89c68 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -12 +16 diff --git a/README.md b/README.md index 413d5705d7..438c903d0f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # React-Select -The Select control for [React](https://reactjs.com). Initially built for use in [KeystoneJS](http://www.keystonejs.com). +The Select control for [React](https://reactjs.org). Initially built for use in [KeystoneJS](https://www.keystonejs.com). See [react-select.com](https://www.react-select.com) for live demos and comprehensive docs. @@ -136,8 +136,8 @@ If you don't provide these props, you can set the initial value of the state the React-select exposes two public methods: -- `focus()` - focus the control programatically -- `blur()` - blur the control programatically +- `focus()` - focus the control programmatically +- `blur()` - blur the control programmatically ## Customisation @@ -151,15 +151,15 @@ Check the docs for more information on: - [Advanced use-cases](https://www.react-select.com/advanced) - [TypeScript guide](https://www.react-select.com/typescript) -## Typescript +## TypeScript -The v5 release represents a rewrite from JavaScript to Typescript. The types for v4 and earlier releases are available at [@types](https://www.npmjs.com/package/@types/react-select). See the [TypeScript guide](https://www.react-select.com/typescript) for how to use the types starting with v5. +The v5 release represents a rewrite from JavaScript to TypeScript. The types for v4 and earlier releases are available at [@types](https://www.npmjs.com/package/@types/react-select). See the [TypeScript guide](https://www.react-select.com/typescript) for how to use the types starting with v5. # Thanks Thank you to everyone who has contributed to this project. It's been a wild ride. -If you like React Select, you should [follow me on twitter](https://twitter.com/jedwatson)! +If you like React Select, you should [follow me on Twitter](https://twitter.com/jedwatson)! Shout out to [Joss Mackison](https://github.com/jossmac), [Charles Lee](https://github.com/gwyneplaine), [Ben Conolly](https://github.com/Noviny), [Tom Walker](https://github.com/bladey), [Nathan Bierema](https://github.com/Methuselah96), [Eric Bonow](https://github.com/ebonow), [Mitchell Hamilton](https://github.com/mitchellhamilton), [Dave Brotherstone](https://github.com/bruderstein), [Brian Vaughn](https://github.com/bvaughn), and the [Atlassian Design System](https://atlassian.design) team who along with many other contributors have made this possible ❤️ diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e7bee2cb7e..746697a989 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,14 @@ # @react-select/docs +## 3.1.2 + +### Patch Changes + +- [`0ca2d5ba`](https://github.com/JedWatson/react-select/commit/0ca2d5ba4aa42fb2a1bf033bcee660a293e39e50) [#5431](https://github.com/JedWatson/react-select/pull/5431) Thanks [@nderkim](https://github.com/nderkim)! - Change `class` components to `functional` components + +- Updated dependencies [0ca2d5ba]: + - react-select@undefined + ## 3.1.1 ### Patch Changes @@ -44,7 +53,7 @@ ### Minor Changes -- [2baf5a9d](https://github.com/JedWatson/react-select/commit/2baf5a9df2f4f56f9c9374fcb879cb5259a6d8d0) [#4414](https://github.com/JedWatson/react-select/pull/4414) Thanks [@ebonow](https://github.com/ebonow)! - Add ariaLiveMessages prop for internationalization and other customizations +- [2baf5a9d](https://github.com/JedWatson/react-select/commit/2baf5a9df2f4f56f9c9374fcb879cb5259a6d8d0) [#4414](https://github.com/JedWatson/react-select/pull/4414) Thanks [@ebonow](https://github.com/ebonow)! - Add ariaLiveMessages prop for internationalization and other customisations ### Patch Changes diff --git a/docs/ExampleWrapper.tsx b/docs/ExampleWrapper.tsx index c290c5f973..eb01b177b3 100644 --- a/docs/ExampleWrapper.tsx +++ b/docs/ExampleWrapper.tsx @@ -1,7 +1,7 @@ /** @jsx jsx */ import { jsx } from '@emotion/react'; // eslint-disable-line no-unused-vars import { CSSObject } from '@emotion/serialize'; -import { Component } from 'react'; +import { ReactNode, useState } from 'react'; import CodeSandboxer, { GitInfo } from 'react-codesandboxer'; import { CodeBlock } from './markdown/renderer'; import pkg from '../packages/react-select/package.json'; @@ -20,99 +20,69 @@ const gitInfo: GitInfo = { const sourceUrl = `https://github.com/${gitInfo.account}/react-select/tree/${gitInfo.branch}`; interface Props { + children?: ReactNode; readonly label: string; readonly raw: { readonly default: string }; readonly urlPath: string; readonly isEditable?: boolean; } -interface State { - readonly showCode: boolean; -} - -export default class ExampleWrapper extends Component { - state: State = { showCode: false }; - static defaultProps = { isEditable: true }; - - renderCodeSample = () => { - let { raw } = this.props; - let { showCode } = this.state; - - if (!showCode || !raw) { - return null; - } else { - return ; - } - }; - - renderSourceViewOption = () => { - let { raw } = this.props; - let { showCode } = this.state; - - if (!raw) { - return ( - - - - ); - } else { - return ( - this.setState({ showCode: !showCode })} - title="View Source" - > - - - ); - } - }; - - renderCSBButton = () => { - let { isEditable, raw, urlPath } = this.props; +export default ({ + children, + label, + raw, + urlPath, + isEditable = true, +}: Props) => { + const [showCode, setShowCode] = useState(false); - if (isEditable) { - return ( - - {({ isLoading }) => ( - - {isLoading ? : } + return ( +
+ +

{label}

+ + {raw ? ( + setShowCode((prev) => !prev)} + title="View Source" + > + + ) : ( + + + )} - - ); - } else { - return null; - } - }; - - render() { - return ( -
- -

{this.props.label}

- - {this.renderSourceViewOption()} - {this.renderCSBButton()} - -
- {this.renderCodeSample()} - {this.props.children} -
- ); - } -} + {isEditable ? ( + + {({ isLoading }) => ( + + {isLoading ? : } + + )} + + ) : null} +
+
+ {showCode && raw ? ( + + ) : null} + {children} +
+ ); +}; const ExampleHeading = (props: any) => (
( alignItems: 'center', display: 'flex', justifyContent: 'flex-end', - opacity: 0.2, + opacity: 0.8, transition: 'opacity 140ms', transitionDelay: '140ms', diff --git a/docs/Tests.tsx b/docs/Tests.tsx index cbbcab70f7..58daf08939 100644 --- a/docs/Tests.tsx +++ b/docs/Tests.tsx @@ -1,8 +1,8 @@ import React, { ChangeEventHandler, - Component, ComponentProps, ComponentType, + useState, } from 'react'; import Select, { MenuPlacement } from 'react-select'; @@ -15,14 +15,6 @@ interface SuiteProps { readonly selectComponent: ComponentType>; readonly idSuffix: string; } -interface SuiteState { - readonly isDisabled: boolean; - readonly isFixed: boolean; - readonly isLoading: boolean; - readonly escapeClearsValue: boolean; - readonly blockScroll: boolean; - readonly portalPlacement: MenuPlacement; -} const AnimatedSelect = (props: ComponentProps) => ( - Disabled - - - - Loading - -
+ const menuPortalTarget = !isFixed ? document.body : null; -

Grouped

-
- +
+ ({ ...base, zIndex: 999 }), + }} + isDisabled={isDisabled} + isLoading={isLoading} + options={colourOptions} + /> + + + Disabled + + + -
+ Loading + +
+ +

Grouped

+
+ +
-

Clearable

-
- Clearable +
+ + + - - - escapeClearsValue - -
+ escapeClearsValue + +
-

Portalled

-
-
-
{'overflow: hidden; position: absolute;'}
- Portalled +
+
+
{'overflow: hidden; position: absolute;'}
+ + + + + + + Fixed + + + + Portal + + + - - - - - - Fixed - - - - Portal - - - - Block Scroll - -
-
+ Block Scroll +
+
- ); - } -} +
+ ); +}; export default function Tests() { return ( diff --git a/docs/examples/AccessingInternals.tsx b/docs/examples/AccessingInternals.tsx index f43bf0539d..ef314c85a4 100644 --- a/docs/examples/AccessingInternals.tsx +++ b/docs/examples/AccessingInternals.tsx @@ -1,4 +1,4 @@ -import React, { Component, Fragment } from 'react'; +import React, { Fragment, useRef } from 'react'; import Select, { SelectInstance } from 'react-select'; import AsyncSelect from 'react-select/async'; @@ -20,105 +20,87 @@ const promiseOptions = (inputValue: string) => }, 1000); }); -export default class AccessingInternals extends Component { - selectRef?: SelectInstance | null; - asyncRef?: SelectInstance | null; - creatableRef?: SelectInstance | null; - focus = () => { - console.log(this.selectRef); - this.selectRef!.focus(); +export default function AccessingInternals() { + const selectRef = useRef | null>(null); + const asyncRef = useRef | null>(null); + const creatableRef = useRef | null>(null); + + // Focus handlers + const focus = () => { + console.log(selectRef); + selectRef.current?.focus(); }; - focusCreatable = () => { - console.log(this.creatableRef); - this.creatableRef!.focus(); + const focusAsync = () => { + console.log(asyncRef); + asyncRef.current?.focus(); }; - focusAsync = () => { - console.log(this.asyncRef); - this.asyncRef!.focus(); + const focusCreatable = () => { + console.log(creatableRef); + creatableRef.current?.focus(); }; - blurAsync = () => { - this.asyncRef!.blur(); + + // Blur handlers + const blur = () => { + selectRef.current?.blur(); }; - blurCreatable = () => { - this.creatableRef!.blur(); + const blurAsync = () => { + asyncRef.current?.blur(); }; - blur = () => this.selectRef!.blur(); - onSelectRef = (ref: SelectInstance) => { - console.log(ref); - this.selectRef = ref; + const blurCreatable = () => { + creatableRef.current?.blur(); }; - render() { - return ( - -

Creatable Select

- { - this.creatableRef = ref; - }} - isClearable - options={colourOptions} - /> - - - - - - -

Async Select

- { - this.asyncRef = ref; - }} - cacheOptions - defaultOptions - loadOptions={promiseOptions} - /> - - - - - - -

Select

- + + + + + + +
+ ); } diff --git a/docs/examples/AsyncCallbacks.tsx b/docs/examples/AsyncCallbacks.tsx index c2eabc7363..69f40cbd99 100644 --- a/docs/examples/AsyncCallbacks.tsx +++ b/docs/examples/AsyncCallbacks.tsx @@ -1,12 +1,8 @@ -import React, { Component } from 'react'; +import React from 'react'; import AsyncSelect from 'react-select/async'; import { ColourOption, colourOptions } from '../data'; -interface State { - readonly inputValue: string; -} - const filterColors = (inputValue: string) => { return colourOptions.filter((i) => i.label.toLowerCase().includes(inputValue.toLowerCase()) @@ -22,24 +18,6 @@ const loadOptions = ( }, 1000); }; -export default class WithCallbacks extends Component<{}, State> { - state: State = { inputValue: '' }; - handleInputChange = (newValue: string) => { - const inputValue = newValue.replace(/\W/g, ''); - this.setState({ inputValue }); - return inputValue; - }; - render() { - return ( -
-
inputValue: "{this.state.inputValue}"
- -
- ); - } -} +export default () => ( + +); diff --git a/docs/examples/AsyncCreatable.tsx b/docs/examples/AsyncCreatable.tsx index 3e882b9a6c..50d440c15f 100644 --- a/docs/examples/AsyncCreatable.tsx +++ b/docs/examples/AsyncCreatable.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import AsyncCreatableSelect from 'react-select/async-creatable'; import { ColourOption, colourOptions } from '../data'; @@ -16,14 +16,10 @@ const promiseOptions = (inputValue: string) => }, 1000); }); -export default class WithPromises extends Component { - render() { - return ( - - ); - } -} +export default () => ( + +); diff --git a/docs/examples/AsyncMulti.tsx b/docs/examples/AsyncMulti.tsx index 86b83d3f69..e58d197861 100644 --- a/docs/examples/AsyncMulti.tsx +++ b/docs/examples/AsyncMulti.tsx @@ -1,12 +1,8 @@ -import React, { Component } from 'react'; +import React from 'react'; import AsyncSelect from 'react-select/async'; import { ColourOption, colourOptions } from '../data'; -interface State { - readonly inputValue: string; -} - const filterColors = (inputValue: string) => { return colourOptions.filter((i) => i.label.toLowerCase().includes(inputValue.toLowerCase()) @@ -20,21 +16,11 @@ const promiseOptions = (inputValue: string) => }, 1000); }); -export default class AsyncMulti extends Component<{}, State> { - state: State = { inputValue: '' }; - handleInputChange = (newValue: string) => { - const inputValue = newValue.replace(/\W/g, ''); - this.setState({ inputValue }); - return inputValue; - }; - render() { - return ( - - ); - } -} +export default () => ( + +); diff --git a/docs/examples/AsyncPromises.tsx b/docs/examples/AsyncPromises.tsx index 973fb3365f..afca61dcf3 100644 --- a/docs/examples/AsyncPromises.tsx +++ b/docs/examples/AsyncPromises.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import AsyncSelect from 'react-select/async'; import { ColourOption, colourOptions } from '../data'; @@ -16,10 +16,6 @@ const promiseOptions = (inputValue: string) => }, 1000); }); -export default class WithPromises extends Component { - render() { - return ( - - ); - } -} +export default () => ( + +); diff --git a/docs/examples/BasicSingle.tsx b/docs/examples/BasicSingle.tsx index 6a9f4e14fe..872b56c330 100644 --- a/docs/examples/BasicSingle.tsx +++ b/docs/examples/BasicSingle.tsx @@ -1,4 +1,4 @@ -import React, { Component, Fragment } from 'react'; +import React, { useState } from 'react'; import Select from 'react-select'; import { colourOptions } from '../data'; @@ -10,86 +10,65 @@ const Checkbox = ({ children, ...props }: JSX.IntrinsicElements['input']) => ( ); -interface State { - readonly isClearable: boolean; - readonly isDisabled: boolean; - readonly isLoading: boolean; - readonly isRtl: boolean; - readonly isSearchable: boolean; -} +export default () => { + const [isClearable, setIsClearable] = useState(true); + const [isSearchable, setIsSearchable] = useState(true); + const [isDisabled, setIsDisabled] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [isRtl, setIsRtl] = useState(false); -export default class SingleSelect extends Component<{}, State> { - state: State = { - isClearable: true, - isDisabled: false, - isLoading: false, - isRtl: false, - isSearchable: true, - }; + return ( + <> + - -
+ setIsClearable((state) => !state)} + > + Clearable + + setIsSearchable((state) => !state)} + > + Searchable + + setIsDisabled((state) => !state)} + > + Disabled + + setIsLoading((state) => !state)} > - - Clearable - - - Searchable - - - Disabled - - - Loading - - - RTL - -
- - ); - } -} + Loading + + setIsRtl((state) => !state)}> + RTL + +
+ + ); +}; diff --git a/docs/examples/ControlledMenu.tsx b/docs/examples/ControlledMenu.tsx index 5a34df0779..a17f8095c6 100644 --- a/docs/examples/ControlledMenu.tsx +++ b/docs/examples/ControlledMenu.tsx @@ -1,52 +1,44 @@ -import React, { Component, Fragment } from 'react'; +import React, { useRef, useState } from 'react'; import Select, { SelectInstance } from 'react-select'; -import { ColourOption, colourOptions } from '../data'; +import { colourOptions } from '../data'; import { Note } from '../styled-components'; const Checkbox = (props: JSX.IntrinsicElements['input']) => ( ); -interface State { - readonly menuIsOpen: boolean; -} +export default () => { + const ref = useRef(null); + const [menuIsOpen, setMenuIsOpen] = useState(false); -export default class controlledMenu extends Component<{}, State> { - state: State = { - menuIsOpen: false, + const toggleMenuIsOpen = () => { + setMenuIsOpen((value) => !value); + const selectEl = ref.current; + if (!selectEl) return; + if (menuIsOpen) selectEl.blur(); + else selectEl.focus(); }; - select?: SelectInstance | null; - toggleMenuIsOpen = () => { - this.setState((state) => ({ menuIsOpen: !state.menuIsOpen })); - if (this.select) { - return !this.state.menuIsOpen ? this.select.focus() : this.select.blur(); - } - }; - render() { - const { menuIsOpen } = this.state; - return ( - - ({ ...base, position: 'relative' }) }} + name="color" + options={colourOptions} + /> + + - - - menuIsOpen - - - ); - } -} + menuIsOpen + + + ); +}; diff --git a/docs/examples/CreatableAdvanced.tsx b/docs/examples/CreatableAdvanced.tsx index 94370138de..1deb68aa27 100644 --- a/docs/examples/CreatableAdvanced.tsx +++ b/docs/examples/CreatableAdvanced.tsx @@ -1,19 +1,12 @@ -import React, { Component } from 'react'; +import React, { useState } from 'react'; import CreatableSelect from 'react-select/creatable'; -import { ActionMeta, OnChangeValue } from 'react-select'; interface Option { readonly label: string; readonly value: string; } -interface State { - readonly isLoading: boolean; - readonly options: readonly Option[]; - readonly value: Option | null | undefined; -} - const createOption = (label: string) => ({ label, value: label.toLowerCase().replace(/\W/g, ''), @@ -25,50 +18,30 @@ const defaultOptions = [ createOption('Three'), ]; -export default class CreatableAdvanced extends Component<{}, State> { - state: State = { - isLoading: false, - options: defaultOptions, - value: undefined, - }; - handleChange = ( - newValue: OnChangeValue, - actionMeta: ActionMeta
); -export default class CustomControl extends Component { - render() { - return ( - +); diff --git a/docs/examples/CustomFilterOptions.tsx b/docs/examples/CustomFilterOptions.tsx index dbb98f7c4d..14225da741 100644 --- a/docs/examples/CustomFilterOptions.tsx +++ b/docs/examples/CustomFilterOptions.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import Select from 'react-select'; import { colourOptions } from '../data'; @@ -20,17 +20,13 @@ const customOptions = [ ...colourOptions, ]; -export default class SelectCreateFilter extends Component { - render() { - return ( - +); diff --git a/docs/examples/CustomGetOptionLabel.tsx b/docs/examples/CustomGetOptionLabel.tsx index 04ec3db169..2ddf3783b5 100644 --- a/docs/examples/CustomGetOptionLabel.tsx +++ b/docs/examples/CustomGetOptionLabel.tsx @@ -1,24 +1,20 @@ -import React, { Component, Fragment } from 'react'; +import React from 'react'; import Select from 'react-select'; import { flavourOptions } from '../data'; -export default class CustomGetOptionLabel extends Component { - render() { - return ( - -

- Composing a display label from the label property and rating property - in the options object -

- `${option.label}: ${option.rating}`} + /> + +); diff --git a/docs/examples/CustomIsOptionDisabled.tsx b/docs/examples/CustomIsOptionDisabled.tsx index 5d4e520578..81834db042 100644 --- a/docs/examples/CustomIsOptionDisabled.tsx +++ b/docs/examples/CustomIsOptionDisabled.tsx @@ -1,24 +1,20 @@ -import React, { Component, Fragment } from 'react'; +import React from 'react'; import Select from 'react-select'; import { flavourOptions } from '../data'; -export default class CustomIsOptionDisabled extends Component { - render() { - return ( - -

- Disable all options that do not have a 'safe' rating, via the - isOptionsDisabled fn prop -

- option.rating !== 'safe'} + /> + +); diff --git a/docs/examples/CustomSingleValue.tsx b/docs/examples/CustomSingleValue.tsx index bfdcc8fc56..74bf30bb8b 100644 --- a/docs/examples/CustomSingleValue.tsx +++ b/docs/examples/CustomSingleValue.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import Select, { components, SingleValueProps } from 'react-select'; import { ColourOption, colourOptions } from '../data'; @@ -9,27 +9,23 @@ const SingleValue = ({ {children} ); -export default class CustomControl extends Component { - render() { - return ( - ({ + ...base, + padding: 5, + borderRadius: 5, + background: colourOptions[2].color, + color: 'white', + display: 'flex', + }), + }} + components={{ SingleValue }} + isSearchable + name="color" + options={colourOptions} + /> +); diff --git a/docs/examples/CustomValueContainer.tsx b/docs/examples/CustomValueContainer.tsx index 9ae6e40a1b..f8f908bdd3 100644 --- a/docs/examples/CustomValueContainer.tsx +++ b/docs/examples/CustomValueContainer.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import Select, { components, ValueContainerProps } from 'react-select'; import { ColourOption, colourOptions } from '../data'; @@ -9,26 +9,22 @@ const ValueContainer = ({ {children} ); -export default class CustomControl extends Component { - render() { - return ( - ({ ...base, color: 'white' }), + valueContainer: (base) => ({ + ...base, + background: colourOptions[2].color, + color: 'white', + width: '100%', + }), + }} + components={{ ValueContainer }} + isSearchable + name="color" + options={colourOptions} + /> +); diff --git a/docs/examples/DefaultOptions.tsx b/docs/examples/DefaultOptions.tsx index c02e9271c9..8c0fe86f63 100644 --- a/docs/examples/DefaultOptions.tsx +++ b/docs/examples/DefaultOptions.tsx @@ -1,12 +1,8 @@ -import React, { Component } from 'react'; +import React from 'react'; import AsyncSelect from 'react-select/async'; import { ColourOption, colourOptions } from '../data'; -interface State { - readonly inputValue: string; -} - const filterColors = (inputValue: string) => { return colourOptions.filter((i) => i.label.toLowerCase().includes(inputValue.toLowerCase()) @@ -20,20 +16,10 @@ const promiseOptions = (inputValue: string) => }, 1000); }); -export default class WithPromises extends Component<{}, State> { - state: State = { inputValue: '' }; - handleInputChange = (newValue: string) => { - const inputValue = newValue.replace(/\W/g, ''); - this.setState({ inputValue }); - return inputValue; - }; - render() { - return ( - - ); - } -} +export default () => ( + +); diff --git a/docs/examples/Experimental.tsx b/docs/examples/Experimental.tsx index 1482bda179..552132bcd2 100644 --- a/docs/examples/Experimental.tsx +++ b/docs/examples/Experimental.tsx @@ -1,5 +1,5 @@ /** @jsx jsx */ -import { Component } from 'react'; +import { useState } from 'react'; import { jsx } from '@emotion/react'; import { CSSObject } from '@emotion/serialize'; import moment, { Moment } from 'moment'; @@ -183,73 +183,47 @@ const Option = (props: OptionProps) => { interface DatePickerProps { readonly value: DateOption | null; - readonly onChange: (value: DateOption | null) => void; + readonly onChange: (newValue: DateOption | null) => void; } -interface DatePickerState { - readonly options: readonly (DateOption | CalendarGroup)[]; -} +const DatePicker = (props: DatePickerProps) => { + const [options, setOptions] = useState(defaultOptions); -class DatePicker extends Component { - state: DatePickerState = { - options: defaultOptions, - }; - handleInputChange = (value: string) => { + const handleInputChange = (value: string) => { if (!value) { - this.setState({ options: defaultOptions }); + setOptions(defaultOptions); return; } const date = chrono.parseDate(suggest(value.toLowerCase())); - if (date) { - this.setState({ - options: [createOptionForDate(date), createCalendarOptions(date)], - }); - } else { - this.setState({ - options: [], - }); + if (!date) { + setOptions([]); + return; } + setOptions([createOptionForDate(date), createCalendarOptions(date)]); }; - render() { - const { value } = this.props; - const { options } = this.state; - return ( - - {...this.props} - components={{ Group, Option }} - filterOption={null} - isMulti={false} - isOptionSelected={(o, v) => v.some((i) => i.date.isSame(o.date, 'day'))} - maxMenuHeight={380} - onChange={this.props.onChange} - onInputChange={this.handleInputChange} - options={options} - value={value} - /> - ); - } -} -interface State { - readonly value: DateOption | null; -} + return ( + + {...props} + components={{ Group, Option }} + filterOption={null} + isMulti={false} + isOptionSelected={(o, v) => v.some((i) => i.date.isSame(o.date, 'day'))} + maxMenuHeight={380} + onChange={props.onChange} + onInputChange={handleInputChange} + options={options} + value={props.value} + /> + ); +}; -export default class Experimental extends Component<{}, State> { - state: State = { - value: defaultOptions[0] as DateOption, - }; - handleChange = (value: DateOption | null) => { - this.setState({ value }); - }; - render() { - const { value } = this.state; - const displayValue = - value && 'value' in value ? value.value.toString() : 'null'; - return ( -
-
Value: {displayValue}
- -
- ); - } -} +export default () => { + const [value, setValue] = useState( + defaultOptions[0] as DateOption + ); + + return ( + setValue(newValue)} /> + ); +}; diff --git a/docs/examples/FixedOptions.tsx b/docs/examples/FixedOptions.tsx index f279843f65..e45af75611 100644 --- a/docs/examples/FixedOptions.tsx +++ b/docs/examples/FixedOptions.tsx @@ -1,12 +1,8 @@ -import React, { Component } from 'react'; +import React, { useState } from 'react'; import Select, { ActionMeta, OnChangeValue, StylesConfig } from 'react-select'; import { ColourOption, colourOptions } from '../data'; -interface State { - readonly value: readonly ColourOption[]; -} - const styles: StylesConfig = { multiValue: (base, state) => { return state.data.isFixed ? { ...base, backgroundColor: 'gray' } : base; @@ -27,21 +23,15 @@ const orderOptions = (values: readonly ColourOption[]) => { .concat(values.filter((v) => !v.isFixed)); }; -export default class FixedOptions extends Component<{}, State> { - state = { - value: orderOptions([colourOptions[0], colourOptions[1], colourOptions[3]]), - }; - - constructor(props: {}) { - super(props); +export default () => { + const [value, setValue] = useState( + orderOptions([colourOptions[0], colourOptions[1], colourOptions[3]]) + ); - this.onChange = this.onChange.bind(this); - } - - onChange( - value: OnChangeValue, + const onChange = ( + newValue: OnChangeValue, actionMeta: ActionMeta - ) { + ) => { switch (actionMeta.action) { case 'remove-value': case 'pop-value': @@ -50,27 +40,24 @@ export default class FixedOptions extends Component<{}, State> { } break; case 'clear': - value = colourOptions.filter((v) => v.isFixed); + newValue = colourOptions.filter((v) => v.isFixed); break; } - value = orderOptions(value); - this.setState({ value: value }); - } + setValue(orderOptions(newValue)); + }; - render() { - return ( - !v.isFixed)} + name="colors" + className="basic-multi-select" + classNamePrefix="select" + onChange={onChange} + options={colourOptions} + /> + ); +}; diff --git a/docs/examples/MenuPortal.tsx b/docs/examples/MenuPortal.tsx index 598ab1af54..feabc6453c 100644 --- a/docs/examples/MenuPortal.tsx +++ b/docs/examples/MenuPortal.tsx @@ -1,93 +1,69 @@ -import React, { ChangeEventHandler, Component, Fragment } from 'react'; +import React, { useState } from 'react'; import Modal from '@atlaskit/modal-dialog'; import Button from '@atlaskit/button'; -import Select from 'react-select'; +import Select, { MenuPlacement } from 'react-select'; import { H1, Note } from '../styled-components'; import { colourOptions } from '../data'; -interface State { - readonly isOpen: boolean; - readonly isFixed: boolean; - readonly portalPlacement: 'auto' | 'bottom' | 'top'; -} +export default () => { + const [isOpen, setIsOpen] = useState(false); + const [isFixed, setIsFixed] = useState(false); + const [menuPlacement, setMenuPlacement] = useState('bottom'); -export default class MenuPortal extends Component<{}, State> { - state: State = { - isOpen: false, - isFixed: false, - portalPlacement: 'bottom', - }; - open = () => { - console.log('menuPortal is Open'); - this.setState({ isOpen: true }); - }; - close = () => { - this.setState({ isOpen: false }); - }; - setPlacement: ChangeEventHandler = ({ currentTarget }) => { - const portalPlacement = - currentTarget && (currentTarget.value as 'auto' | 'bottom' | 'top'); - this.setState({ portalPlacement }); - }; - toggleMode = () => { - this.setState((state) => ({ isFixed: !state.isFixed })); - }; - render() { - const { close, open } = this; - const { isOpen, isFixed, portalPlacement } = this.state; - return ( - - - {isOpen ? ( - -

Portaled Menu Element

- ({ ...base, zIndex: 9999 }) }} + menuPortalTarget={document.body} + isSearchable + name="color" + menuPosition={isFixed ? 'fixed' : 'absolute'} + menuPlacement={menuPlacement} + options={colourOptions} + menuShouldScrollIntoView={false} + /> + + + + + setIsFixed((prev) => !prev)} + value="fixed" + checked={isFixed} + id="cypress-portalled__fixed" /> - - - - - - Fixed - - - - Portal - -
- ) : null} -
- ); - } -} + Fixed + + + setIsFixed((prev) => !prev)} + value="portal" + checked={!isFixed} + id="cypress-portalled__portal" + /> + Portal + + + ) : null} + + ); +}; diff --git a/docs/examples/OnSelectResetsInput.tsx b/docs/examples/OnSelectResetsInput.tsx index 446c58a9a7..8f8838b02a 100644 --- a/docs/examples/OnSelectResetsInput.tsx +++ b/docs/examples/OnSelectResetsInput.tsx @@ -1,48 +1,32 @@ -import React, { Component } from 'react'; +import React from 'react'; import Select, { InputActionMeta } from 'react-select'; import { colourOptions } from '../data'; -interface State { - readonly menuIsOpen?: boolean; -} +export default () => { + const [menuIsOpen, setMenuIsOpen] = React.useState(); -export default class OnSelectResetsInput extends Component<{}, State> { - state: State = {}; - onInputChange = ( + const onInputChange = ( inputValue: string, { action, prevInputValue }: InputActionMeta ) => { - console.log(inputValue, action); - switch (action) { - case 'input-change': - return inputValue; - case 'menu-close': - console.log(prevInputValue); - let menuIsOpen = undefined; - if (prevInputValue) { - menuIsOpen = true; - } - this.setState({ - menuIsOpen, - }); - return prevInputValue; - default: - return prevInputValue; + if (action === 'input-change') return inputValue; + if (action === 'menu-close') { + if (prevInputValue) setMenuIsOpen(true); + else setMenuIsOpen(undefined); } + return prevInputValue; }; - render() { - const { menuIsOpen } = this.state; - return ( - + ); +}; diff --git a/docs/examples/Popout.tsx b/docs/examples/Popout.tsx index 88a71f58f6..2823fdf51d 100644 --- a/docs/examples/Popout.tsx +++ b/docs/examples/Popout.tsx @@ -1,9 +1,9 @@ /** @jsx jsx */ -import { Component, FunctionComponent, ReactNode } from 'react'; +import { ReactNode, useState } from 'react'; import { jsx } from '@emotion/react'; import Button from '@atlaskit/button'; -import Select, { OnChangeValue, StylesConfig } from 'react-select'; +import Select, { StylesConfig } from 'react-select'; import { defaultTheme } from 'react-select'; import { StateOption, stateOptions } from '../data'; @@ -18,55 +18,45 @@ const selectStyles: StylesConfig = { menu: () => ({ boxShadow: 'inset 0 1px 0 rgba(0, 0, 0, 0.1)' }), }; -interface State { - readonly isOpen: boolean; - readonly value: StateOption | null | undefined; -} +export default () => { + const [isOpen, setIsOpen] = useState(false); + const [value, setValue] = useState(); -export default class PopoutExample extends Component<{}, State> { - state: State = { isOpen: false, value: undefined }; - toggleOpen = () => { - this.setState((state) => ({ isOpen: !state.isOpen })); - }; - onSelectChange = (value: OnChangeValue) => { - this.toggleOpen(); - this.setState({ value }); - }; - render() { - const { isOpen, value } = this.state; - return ( - } - onClick={this.toggleOpen} - isSelected={isOpen} - > - {value ? `State: ${value.label}` : 'Select a State'} - - } - > - { + setValue(newValue); + setIsOpen(false); + }} + options={stateOptions} + placeholder="Search..." + styles={selectStyles} + tabSelectsValue={false} + value={value} + /> + + ); +}; // styled components @@ -99,16 +89,16 @@ const Blanket = (props: JSX.IntrinsicElements['div']) => ( {...props} /> ); -interface DropdownProps { - readonly isOpen: boolean; - readonly target: ReactNode; - readonly onClose: () => void; -} -const Dropdown: FunctionComponent = ({ +const Dropdown = ({ children, isOpen, target, onClose, +}: { + children?: ReactNode; + readonly isOpen: boolean; + readonly target: ReactNode; + readonly onClose: () => void; }) => (
{target} diff --git a/docs/generate-magical-types/generate/package.json b/docs/generate-magical-types/generate/package.json index fc99df9b47..c8cade72a6 100644 --- a/docs/generate-magical-types/generate/package.json +++ b/docs/generate-magical-types/generate/package.json @@ -1,6 +1,3 @@ { - "main": "dist/generate-magical-types.cjs.js", - "preconstruct": { - "source": "../src/generate" - } + "main": "dist/react-select-generate-magical-types-generate.cjs.js" } diff --git a/docs/generate-magical-types/package.json b/docs/generate-magical-types/package.json index 29b35c8069..9af9564a97 100644 --- a/docs/generate-magical-types/package.json +++ b/docs/generate-magical-types/package.json @@ -12,8 +12,8 @@ }, "preconstruct": { "entrypoints": [ - "generate", - "serialize" + "generate.ts", + "serialize.ts" ] } } diff --git a/docs/generate-magical-types/serialize/package.json b/docs/generate-magical-types/serialize/package.json index edbc5569c0..2b2d88a1f4 100644 --- a/docs/generate-magical-types/serialize/package.json +++ b/docs/generate-magical-types/serialize/package.json @@ -1,6 +1,3 @@ { - "main": "dist/generate-magical-types.cjs.js", - "preconstruct": { - "source": "../src/serialize" - } + "main": "dist/react-select-generate-magical-types-serialize.cjs.js" } diff --git a/docs/package.json b/docs/package.json index e5652bdd08..c48f42357c 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "@react-select/docs", "private": true, - "version": "3.1.1", + "version": "3.1.2", "main": "dist/docs.cjs.js", "author": "Jed Watson", "license": "MIT", @@ -11,29 +11,16 @@ "@atlaskit/modal-dialog": "^11.2.5", "@atlaskit/spinner": "^15.0.6", "@atlaskit/tooltip": "^17.1.2", - "@babel/core": "^7.12.0", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/polyfill": "^7.12.1", - "@babel/preset-env": "^7.12.0", - "@babel/preset-react": "^7.12.1", - "@babel/preset-typescript": "^7.12.7", "@babel/runtime": "^7.12.0", - "@emotion/babel-plugin": "^11.0.0", - "@emotion/react": "^11.1.1", + "@emotion/react": "^11.8.1", "@magical-types/convert-type": "^0.1.4", "@magical-types/pretty": "^0.3.5", "@magical-types/serialization": "^0.2.1", "@magical-types/types": "^0.1.2", "@types/chroma-js": "^1.4.3", - "@types/copy-webpack-plugin": "^5.0.2", - "@types/html-webpack-plugin": "^3.2.4", - "@types/node": "^14.14.22", "@types/node-fetch": "^2.5.8", - "@types/pretty-proptypes": "^1.1.0", "@types/raf-schd": "^4.0.0", - "@types/react": "^16.14.2", "@types/react-codesandboxer": "^3.1.0", - "@types/react-dom": "^16.9.10", "@types/react-helmet": "^5.0.16", "@types/react-markings": "^1.3.0", "@types/react-node-resolver": "^2.0.0", @@ -41,21 +28,15 @@ "@types/react-syntax-highlighter": "^0.0.8", "@types/webpack": "^4.41.26", "@types/webpack-dev-server": "^3.11.1", - "babel-loader": "^8.0.0", "babel-plugin-macros": "^3.0.1", "chroma-js": "^1.3.6", "chrono-node": "^2.1.11", "clean-webpack-plugin": "^3.0.0", "codesandboxer": "^0.1.1", - "copy-webpack-plugin": "^5.0.3", - "cross-env": "^5.1.3", - "css-loader": "^0.28.7", - "dotenv": "^8.2.0", "fork-ts-checker-webpack-plugin": "^6.1.0", "html-webpack-plugin": "^3.2.0", - "moment": "^2.20.1", - "pretty-proptypes": "^0.5.0", - "raf-schd": "^2.1.0", + "moment": "^2.29.4", + "raf-schd": "^4.0.3", "raw-loader": "^2.0.0", "react": "^16.13.0", "react-codesandboxer": "^3.1.5", @@ -63,22 +44,21 @@ "react-helmet": "^5.2.0", "react-markings": "^1.3.0", "react-router-dom": "^4.2.2", - "react-select": "^5.0.0", + "react-select": "^5.5.7", "react-sortable-hoc": "^1.9.1", "react-syntax-highlighter": "^7.0.1", "style-loader": "^0.23.1", - "styled-components": "^3.4.10", + "styled-components": "^4.4.1", "ts-node": "^9.1.1", - "typescript": "^4.1.3", "unfetch": "^3.0.0", "webpack": "^4.30.0", - "webpack-cli": "^3.3.1", + "webpack-cli": "^4.10.0", "webpack-dev-server": "^3.3.1" }, "scripts": { "site-prebuild": "node ./generate-magical-types/generate && node ./generate-magical-types/serialize", - "start": "yarn site-prebuild && webpack-dev-server --progress", - "start:test": "NODE_ENV=test yarn site-prebuild && webpack-dev-server --progress", - "build:docs": "rimraf docs/dist && yarn site-prebuild && webpack --progress -p" + "start": "yarn site-prebuild && webpack serve --progress", + "start:test": "NODE_ENV=test yarn site-prebuild && webpack serve --progress", + "build:docs": "rimraf docs/dist && yarn site-prebuild && webpack --progress --mode production" } } diff --git a/docs/pages/advanced/index.tsx b/docs/pages/advanced/index.tsx index ab9b9c7b7f..2d7f8fc74a 100644 --- a/docs/pages/advanced/index.tsx +++ b/docs/pages/advanced/index.tsx @@ -35,7 +35,7 @@ export default function Advanced() { ## Accessibility Accessibility is important. React-select is committed to providing a custom experience to all users and relies heavily on the aria-live spec to provide - a custom experience for all users. As such, we also provide an api to address internationalization or further customization. + a custom experience for all users. As such, we also provide an api to address internationalization or further customisation. ${( ); diff --git a/netlify.toml b/netlify.toml index fc5ee4c6ba..ca162f9bde 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,3 +1,6 @@ [build] command = "yarn build:docs" publish = "docs/dist" + +[build.environment] + YARN_VERSION = "1.22.19" diff --git a/package.json b/package.json index dcbe874a22..698551a561 100644 --- a/package.json +++ b/package.json @@ -10,105 +10,55 @@ "url": "https://github.com/JedWatson/react-select.git" }, "dependencies": { - "@atlaskit/button": "^15.1.4", - "@atlaskit/icon": "^11.0.1", - "@atlaskit/modal-dialog": "^11.2.5", - "@atlaskit/spinner": "^15.0.6", - "@atlaskit/tooltip": "^17.1.2", - "@babel/core": "^7.12.0", + "@babel/core": "^7.19.6", "@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/plugin-proposal-private-methods": "^7.13.0", "@babel/plugin-transform-runtime": "^7.12.0", "@babel/polyfill": "^7.12.1", - "@babel/preset-env": "^7.12.0", + "@babel/preset-env": "^7.19.4", "@babel/preset-react": "^7.12.1", - "@babel/preset-typescript": "^7.12.7", + "@babel/preset-typescript": "^7.18.6", "@babel/runtime": "^7.12.0", "@changesets/cli": "^2.17.0", "@changesets/get-github-info": "^0.5.0", - "@emotion/babel-plugin": "^11.0.0", - "@emotion/cache": "^11.4.0", + "@emotion/babel-plugin": "^11.10.2", "@emotion/jest": "^11.1.0", - "@emotion/react": "^11.1.1", - "@preconstruct/cli": "^1.0.0", - "@testing-library/dom": "7.0.4", + "@manypkg/cli": "^0.19.2", + "@preconstruct/cli": "^2.2.2", + "@testing-library/dom": "8.19.0", "@testing-library/jest-dom": "5.1.1", - "@testing-library/react": "10.0.1", + "@testing-library/react": "12.1.4", "@testing-library/user-event": "^10.0.0", - "@types/chroma-js": "^1.4.3", "@types/copy-webpack-plugin": "^5.0.2", "@types/html-webpack-plugin": "^3.2.4", - "@types/jest-in-case": "^1.0.3", - "@types/node": "^14.14.22", - "@types/node-fetch": "^2.5.8", + "@types/jest-in-case": "^1.0.6", + "@types/node": "^16.11.68", "@types/pretty-proptypes": "^1.1.0", - "@types/raf-schd": "^4.0.0", "@types/react": "^16.14.2", - "@types/react-codesandboxer": "^3.1.0", "@types/react-dom": "^16.9.10", - "@types/react-helmet": "^5.0.16", - "@types/react-markings": "^1.3.0", - "@types/react-node-resolver": "^2.0.0", - "@types/react-router-dom": "^4.3.5", - "@types/react-syntax-highlighter": "^0.0.8", "@types/react-transition-group": "^4.4.0", - "@types/webpack": "^4.41.26", - "@types/webpack-dev-server": "^3.11.1", "@typescript-eslint/eslint-plugin": "^4.14.0", "@typescript-eslint/parser": "^4.14.0", "babel-core": "^7.0.0-bridge.0", "babel-eslint": "^10.1.0", "babel-jest": "^23.6.0", - "babel-loader": "^8.0.0", - "bolt-check": "^0.3.0", - "chroma-js": "^1.3.6", - "chrono-node": "^2.1.11", - "codesandboxer": "^0.1.1", - "concurrently": "^3.5.1", + "babel-loader": "^8.2.5", + "concurrently": "^7.5.0", "copy-webpack-plugin": "^5.0.3", "coveralls": "^2.11.12", - "cross-env": "^5.1.3", + "cross-env": "^7.0.3", "css-loader": "^0.28.7", "cypress": "^5.0.0", - "dataloader": "^1.4.0", - "dotenv": "^8.2.0", - "enzyme": "^3.8.0", - "enzyme-adapter-react-16": "^1.1.1", - "enzyme-to-json": "^3.3.0", + "dotenv": "^16.0.3", "eslint": "^7.18.0", "eslint-plugin-react": "^7.22.0", "eslint-plugin-react-hooks": "^4.2.0", - "fork-ts-checker-webpack-plugin": "^6.1.0", "gh-pages": "^1.1.0", - "html-webpack-plugin": "^3.2.0", "jest": "^25.1.0", "jest-in-case": "^1.0.2", - "memoize-one": "^5.0.0", - "moment": "^2.20.1", - "node-fetch": "^2.6.1", "prettier": "^2.2.1", - "pretty-proptypes": "^0.5.0", - "prop-types": "^15.6.0", - "raf": "^3.4.0", - "raf-schd": "^2.1.0", - "raw-loader": "^2.0.0", - "react": "^16.13.0", - "react-codesandboxer": "^3.1.5", - "react-dom": "^16.13.0", - "react-helmet": "^5.2.0", - "react-markings": "^1.3.0", - "react-router-dom": "^4.2.2", - "react-sortable-hoc": "^1.9.1", - "react-syntax-highlighter": "^7.0.1", - "react-transition-group": "^4.3.0", "style-loader": "^0.23.1", - "styled-components": "^3.4.10", - "ts-node": "^9.1.1", - "typescript": "^4.1.3", - "unfetch": "^3.0.0", - "webpack": "^4.30.0", - "webpack-cli": "^3.3.1", - "webpack-dev-server": "^3.3.1" + "typescript": "^4.1.3" }, "scripts": { "build": "preconstruct build", @@ -123,10 +73,10 @@ "fresh": "rm -rf node_modules && yarn install", "test": "npm run test:jest && npm run test:cypress", "test:jest": "jest --coverage", - "e2e": "concurrently --kill-others --success=first --names 'SERVER,E2E' 'yarn start:test --progress=false --no-info' 'yarn test:cypress'", + "e2e": "concurrently --kill-others --success=first --names 'SERVER,E2E' 'yarn start:test' 'yarn test:cypress'", "type-check": "tsc --build packages/react-select/tsconfig.json && tsc --build packages/react-select/src/__tests__/tsconfig.json && tsc --build docs/tsconfig.json && tsc --build cypress/tsconfig.json", "precommit": "yarn run type-check", - "postinstall": "preconstruct dev", + "postinstall": "preconstruct dev && manypkg check", "test:cypress": "yarn test:cypress:chrome && yarn test:cypress:firefox", "test:cypress:chrome": "cypress run --browser chrome", "test:cypress:firefox": "cypress run --browser firefox", @@ -163,7 +113,8 @@ }, "workspaces": [ "packages/*", - "docs" + "docs", + "storybook" ], "preconstruct": { "packages": [ diff --git a/packages/react-select/CHANGELOG.md b/packages/react-select/CHANGELOG.md index 1bdf240b0a..57a84ba859 100644 --- a/packages/react-select/CHANGELOG.md +++ b/packages/react-select/CHANGELOG.md @@ -1,5 +1,87 @@ # react-select +## 5.5.7 + +### Patch Changes + +- [`0ca2d5ba`](https://github.com/JedWatson/react-select/commit/0ca2d5ba4aa42fb2a1bf033bcee660a293e39e50) [#5431](https://github.com/JedWatson/react-select/pull/5431) Thanks [@nderkim](https://github.com/nderkim)! - Change `class` components to `functional` components + +## 5.5.6 + +### Patch Changes + +- [`92398939`](https://github.com/JedWatson/react-select/commit/9239893986c6aaaa7105d3f5a91022827e544b10) [#5409](https://github.com/JedWatson/react-select/pull/5409) Thanks [@lukebennett88](https://github.com/lukebennett88)! - Move files around to as to be compatible with version 2 of `@preconstruct/cli` + +## 5.5.5 + +### Patch Changes + +- [`0dd38029`](https://github.com/JedWatson/react-select/commit/0dd3802977e525b4d8ea1eb083f8f13788016c28) [#5246](https://github.com/JedWatson/react-select/pull/5246) Thanks [@Rall3n](https://github.com/Rall3n)! - Fix re-focus of component in Firefox if being disabled while focused + +## 5.5.4 + +### Patch Changes + +- [`ebb0a17a`](https://github.com/JedWatson/react-select/commit/ebb0a17a30b22cb7e7f7467ed8eda4256166e401) [#5404](https://github.com/JedWatson/react-select/pull/5404) Thanks [@Methuselah96](https://github.com/Methuselah96)! - Use ResizeObserver to auto-update menu position if available + +## 5.5.3 + +### Patch Changes + +- [`07656aaa`](https://github.com/JedWatson/react-select/commit/07656aaac7f636129f8d09e723df9fa6e5ff2841) [#5399](https://github.com/JedWatson/react-select/pull/5399) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update memoize-one + +## 5.5.2 + +### Patch Changes + +- [`00238f1a`](https://github.com/JedWatson/react-select/commit/00238f1a65ce9184b99edd6d3b3307f9b5c0c6c1) [#5376](https://github.com/JedWatson/react-select/pull/5376) Thanks [@lukebennett88](https://github.com/lukebennett88)! - Fix bug with animated multi-value select width being too wide + +## 5.5.1 + +### Patch Changes + +- [`0f6ef093`](https://github.com/JedWatson/react-select/commit/0f6ef093282ca7356fb0e7ee7c706681a5a97901) [#5381](https://github.com/JedWatson/react-select/pull/5381) Thanks [@Methuselah96](https://github.com/Methuselah96)! - Disable use of ResizeObserver for menu position auto-updating to avoid potential breaking changes. + +## 5.5.0 + +### Minor Changes + +- [`598f9ee0`](https://github.com/JedWatson/react-select/commit/598f9ee0e641138820ae1b3d2a2121a1c21d3876) [#5256](https://github.com/JedWatson/react-select/pull/5256) Thanks [@Methuselah96](https://github.com/Methuselah96)! - Auto-update menu position when using menu portalling + +## 5.4.0 + +### Minor Changes + +- [`5d49f70a`](https://github.com/JedWatson/react-select/commit/5d49f70aeb9d6c2685b81b361e3dab3e2064292d) [#5249](https://github.com/JedWatson/react-select/pull/5249) Thanks [@Methuselah96](https://github.com/Methuselah96)! - Export `formatOptionLabel` types (i.e., `FormatOptionLabelMeta` and `FormatOptionLabelContext`). + +## 5.3.2 + +### Patch Changes + +- [`1f140e42`](https://github.com/JedWatson/react-select/commit/1f140e423707e9966254050e3234a65ee05977e9) [#5177](https://github.com/JedWatson/react-select/pull/5177) Thanks [@Methuselah96](https://github.com/Methuselah96)! - Fix view height used for menu positioning to be the scroll parent instead of the window + +## 5.3.1 + +### Patch Changes + +- [`03bf7351`](https://github.com/JedWatson/react-select/commit/03bf735127fec6e47de8ae45e7cdc0a39c8b638b) [#5164](https://github.com/JedWatson/react-select/pull/5164) Thanks [@Methuselah96](https://github.com/Methuselah96)! - Bump @emotion/react to ^11.8.1 to avoid `useInsertionEffect` bug + +## 5.3.0 + +### Minor Changes + +- [`c7d8d4b3`](https://github.com/JedWatson/react-select/commit/c7d8d4b3ee01cee63b34adf4a895ef07ce2f3b03) [#5133](https://github.com/JedWatson/react-select/pull/5133) Thanks [@nil4](https://github.com/nil4)! - Update `peerDependencies` to include React 18 + +### Patch Changes + +- [`0aaa9575`](https://github.com/JedWatson/react-select/commit/0aaa9575ed7e817841b9c9b494b4bd4dc2247b26) [#5134](https://github.com/JedWatson/react-select/pull/5134) Thanks [@rkulinski](https://github.com/rkulinski)! - Use defaultPrevented to skip duplicate event handler for clicking select. + +- [`87e14431`](https://github.com/JedWatson/react-select/commit/87e144319f485fba20b46bc71eb8162f88d19430) [#5131](https://github.com/JedWatson/react-select/pull/5131) Thanks [@pcorpet](https://github.com/pcorpet)! - Avoid referencing an ID that is not in the DOM + +- [`7184d538`](https://github.com/JedWatson/react-select/commit/7184d538f587c1dd5a4ca5ad6cc0745fbb8d3809) [#5082](https://github.com/JedWatson/react-select/pull/5082) Thanks [@Methuselah96](https://github.com/Methuselah96)! - Fix type inference for Async's loadOptions prop + +- [`bd4ee8ae`](https://github.com/JedWatson/react-select/commit/bd4ee8ae66e581e8be1679fa4b1838451e3f23b7) [#5057](https://github.com/JedWatson/react-select/pull/5057) Thanks [@Rall3n](https://github.com/Rall3n)! - Prevent transition props from being forwarded to `` element in `DummyInput` component + ## 5.2.2 ### Patch Changes @@ -22,7 +104,7 @@ - [`6c7a3d1e`](https://github.com/JedWatson/react-select/commit/6c7a3d1e07b7d6a8f484a829e69b20eae5a92b91) [#4785](https://github.com/JedWatson/react-select/pull/4785) Thanks [@Rall3n](https://github.com/Rall3n)! - Add `prevInputValue` to action meta -- [`b522ac65`](https://github.com/JedWatson/react-select/commit/b522ac658f85701ecf413436f3cf8d8d49117c82) [#4860](https://github.com/JedWatson/react-select/pull/4860) Thanks [@ebonow](https://github.com/ebonow)! - Fix animated MultiValue transitions when being removed and change method used to generate unqiue keys for Option components. Closes #4844 , closes #4602 +- [`b522ac65`](https://github.com/JedWatson/react-select/commit/b522ac658f85701ecf413436f3cf8d8d49117c82) [#4860](https://github.com/JedWatson/react-select/pull/4860) Thanks [@ebonow](https://github.com/ebonow)! - Fix animated MultiValue transitions when being removed and change method used to generate unique keys for Option components. Closes #4844 , closes #4602 ### Patch Changes @@ -54,7 +136,7 @@ - [ef87c3ac](https://github.com/JedWatson/react-select/commit/ef87c3ac7fd453800595eebebb85f1107f78d34c) [#4683](https://github.com/JedWatson/react-select/pull/4683) Thanks [@JedWatson](https://github.com/JedWatson)! - React-Select has been converted from Flow to TypeScript. - Other changes for v5 include usage of `forwardRef`, new hooks for `stateManager`, `async` and `creatable` components, and more reliable filtering implementaion with new options in the creatable variant. + Other changes for v5 include usage of `forwardRef`, new hooks for `stateManager`, `async` and `creatable` components, and more reliable filtering implementation with new options in the creatable variant. ### Patch Changes @@ -112,7 +194,7 @@ - [ef87c3ac](https://github.com/JedWatson/react-select/commit/ef87c3ac7fd453800595eebebb85f1107f78d34c) [#4489](https://github.com/JedWatson/react-select/pull/4489) Thanks [@Methuselah96](https://github.com/Methuselah96)! - React-Select has been converted from Flow to TypeScript. - Other changes for v5 include usage of `forwardRef`, new hooks for `stateManager`, `async` and `creatable` components, and more reliable filtering implementaion with new options in the creatable variant. + Other changes for v5 include usage of `forwardRef`, new hooks for `stateManager`, `async` and `creatable` components, and more reliable filtering implementation with new options in the creatable variant. - [#4625](https://github.com/JedWatson/react-select/pull/4625) Thanks [@ebonow](https://github.com/ebonow)! - Remove dependency on AutoSizeInput - BREAKING CHANGES: @@ -153,7 +235,7 @@ - [2ffed9c6](https://github.com/JedWatson/react-select/commit/2ffed9c6c40c9d5b81d7c8faf7bfc995976299ec) [#4444](https://github.com/JedWatson/react-select/pull/4444) Thanks [@Rall3n](https://github.com/Rall3n)! - Use accessor props to get value and label in `compareOption` -- [2baf5a9d](https://github.com/JedWatson/react-select/commit/2baf5a9df2f4f56f9c9374fcb879cb5259a6d8d0) [#4414](https://github.com/JedWatson/react-select/pull/4414) Thanks [@ebonow](https://github.com/ebonow)! - Add ariaLiveMessages prop for internationalization and other customizations +- [2baf5a9d](https://github.com/JedWatson/react-select/commit/2baf5a9df2f4f56f9c9374fcb879cb5259a6d8d0) [#4414](https://github.com/JedWatson/react-select/pull/4414) Thanks [@ebonow](https://github.com/ebonow)! - Add ariaLiveMessages prop for internationalization and other customisations - [7cdb8a6b](https://github.com/JedWatson/react-select/commit/7cdb8a6b4d9de89a599b3aee8b6d90a44a931ea6) [#4391](https://github.com/JedWatson/react-select/pull/4391) Thanks [@ebonow](https://github.com/ebonow)! - Pass and sanitize CommonProps passed to Group and Input components @@ -173,7 +255,7 @@ ### Minor Changes -- [b5f9b0c5](https://github.com/JedWatson/react-select/commit/b5f9b0c59d7ac8559f88287ba73f0495b4c8eed2) [#4342](https://github.com/JedWatson/react-select/pull/4342) Thanks [@Methuselah96](https://github.com/Methuselah96)! - Standardized innerProps and className props on customizable components +- [b5f9b0c5](https://github.com/JedWatson/react-select/commit/b5f9b0c59d7ac8559f88287ba73f0495b4c8eed2) [#4342](https://github.com/JedWatson/react-select/pull/4342) Thanks [@Methuselah96](https://github.com/Methuselah96)! - Standardized innerProps and className props on customisable components - [19b76342](https://github.com/JedWatson/react-select/commit/19b763428d6df254f0b9662f18a698dd3c59d83b) [#3911](https://github.com/JedWatson/react-select/pull/3911) Thanks [@eugenet8k](https://github.com/eugenet8k)! - Add `removedValues` to `onChange` event meta when the action is `clear` (when the user clears the value in the Select) @@ -1124,7 +1206,7 @@ was better to do this before declaring 1.0.0 stable. ## v1.0.0-beta14 / 2016-07-17 -- fixed; `react-input-autosize` has been udpated to `1.1.0`, which includes +- fixed; `react-input-autosize` has been updated to `1.1.0`, which includes fixes for the new warnings that React 15.2 logs - fixed; "Unknown prop `inputClassName` on
tag" warning, thanks [Max Stoiber](https://github.com/mxstbr) @@ -1203,7 +1285,7 @@ triaging issues for this release! ## v1.0.0-beta12 / 2016-04-02 -- added; `menuRenderer` method and example for effeciently rendering thousands +- added; `menuRenderer` method and example for efficiently rendering thousands of options, thanks [Brian Vaughn](https://github.com/bvaughn) - added; `optionClassName` prop, thanks [Max Tyler](https://github.com/iam4x) @@ -1582,7 +1664,7 @@ version; sorry about that! [Kevin Burke](https://github.com/kembuco) - added; Support for case-insensitive filtering when `matchPos="start"`, thanks [wesrage](https://github.com/wesrage) -- added; Support for customizable background color, thanks +- added; Support for customisable background color, thanks [John Morales](https://github.com/JohnMorales) - fixed; Updated ESLint and cleared up warnings, thanks [Alexander Shemetovsky](https://github.com/AlexKVal) @@ -1609,7 +1691,7 @@ version; sorry about that! - fixed; clarified dependency documentation and added dependencies for Bower - fixed; Scoping issues in `_bindCloseMenuIfClickedOutside`, thanks [bannaN](https://github.com/bannaN) -- fixed; Doesnt try to set focus afterupdate if component is disabled, thanks +- fixed; Doesn't try to set focus afterupdate if component is disabled, thanks [bannaN](https://github.com/bannaN) ## v0.4.7 / 2015-04-21 @@ -1774,7 +1856,7 @@ version; sorry about that! - improved; Build tasks and docs - added; Working standalone build - added; Minified dist version -- added; Publised to Bower +- added; Published to Bower ## v0.2.2 / 2014-11-15 diff --git a/packages/react-select/README.md b/packages/react-select/README.md index 413d5705d7..e2ca77b1f1 100644 --- a/packages/react-select/README.md +++ b/packages/react-select/README.md @@ -136,8 +136,8 @@ If you don't provide these props, you can set the initial value of the state the React-select exposes two public methods: -- `focus()` - focus the control programatically -- `blur()` - blur the control programatically +- `focus()` - focus the control programmatically +- `blur()` - blur the control programmatically ## Customisation @@ -151,9 +151,9 @@ Check the docs for more information on: - [Advanced use-cases](https://www.react-select.com/advanced) - [TypeScript guide](https://www.react-select.com/typescript) -## Typescript +## TypeScript -The v5 release represents a rewrite from JavaScript to Typescript. The types for v4 and earlier releases are available at [@types](https://www.npmjs.com/package/@types/react-select). See the [TypeScript guide](https://www.react-select.com/typescript) for how to use the types starting with v5. +The v5 release represents a rewrite from JavaScript to TypeScript. The types for v4 and earlier releases are available at [@types](https://www.npmjs.com/package/@types/react-select). See the [TypeScript guide](https://www.react-select.com/typescript) for how to use the types starting with v5. # Thanks diff --git a/packages/react-select/animated/package.json b/packages/react-select/animated/package.json index b2af8543d0..480f66dd01 100644 --- a/packages/react-select/animated/package.json +++ b/packages/react-select/animated/package.json @@ -1,8 +1,5 @@ { - "main": "dist/react-select.cjs.js", - "module": "dist/react-select.esm.js", - "types": "dist/react-select.cjs.d.ts", - "preconstruct": { - "source": "../src/animated" - } + "main": "dist/react-select-animated.cjs.js", + "module": "dist/react-select-animated.esm.js", + "types": "dist/react-select-animated.cjs.d.ts" } diff --git a/packages/react-select/async-creatable/package.json b/packages/react-select/async-creatable/package.json index 3117f827dc..0a3bddde09 100644 --- a/packages/react-select/async-creatable/package.json +++ b/packages/react-select/async-creatable/package.json @@ -1,8 +1,5 @@ { - "main": "dist/react-select.cjs.js", - "module": "dist/react-select.esm.js", - "types": "dist/react-select.cjs.d.ts", - "preconstruct": { - "source": "../src/AsyncCreatable" - } + "main": "dist/react-select-async-creatable.cjs.js", + "module": "dist/react-select-async-creatable.esm.js", + "types": "dist/react-select-async-creatable.cjs.d.ts" } diff --git a/packages/react-select/async/package.json b/packages/react-select/async/package.json index 69034f9131..b58d5d1e27 100644 --- a/packages/react-select/async/package.json +++ b/packages/react-select/async/package.json @@ -1,8 +1,5 @@ { - "main": "dist/react-select.cjs.js", - "module": "dist/react-select.esm.js", - "types": "dist/react-select.cjs.d.ts", - "preconstruct": { - "source": "../src/Async" - } + "main": "dist/react-select-async.cjs.js", + "module": "dist/react-select-async.esm.js", + "types": "dist/react-select-async.cjs.d.ts" } diff --git a/packages/react-select/base/package.json b/packages/react-select/base/package.json index f538122b58..957568d890 100644 --- a/packages/react-select/base/package.json +++ b/packages/react-select/base/package.json @@ -1,8 +1,5 @@ { - "main": "dist/react-select.cjs.js", - "module": "dist/react-select.esm.js", - "types": "dist/react-select.cjs.d.ts", - "preconstruct": { - "source": "../src/Select" - } + "main": "dist/react-select-base.cjs.js", + "module": "dist/react-select-base.esm.js", + "types": "dist/react-select-base.cjs.d.ts" } diff --git a/packages/react-select/creatable/package.json b/packages/react-select/creatable/package.json index 0be16fc266..347a42cf5c 100644 --- a/packages/react-select/creatable/package.json +++ b/packages/react-select/creatable/package.json @@ -1,8 +1,5 @@ { - "main": "dist/react-select.cjs.js", - "module": "dist/react-select.esm.js", - "types": "dist/react-select.cjs.d.ts", - "preconstruct": { - "source": "../src/Creatable" - } + "main": "dist/react-select-creatable.cjs.js", + "module": "dist/react-select-creatable.esm.js", + "types": "dist/react-select-creatable.cjs.d.ts" } diff --git a/packages/react-select/package.json b/packages/react-select/package.json index bd980c4576..7699c52ae2 100644 --- a/packages/react-select/package.json +++ b/packages/react-select/package.json @@ -1,6 +1,6 @@ { "name": "react-select", - "version": "5.2.2", + "version": "5.5.7", "description": "A Select control built with and for ReactJS", "main": "dist/react-select.cjs.js", "module": "dist/react-select.esm.js", @@ -12,14 +12,16 @@ "dependencies": { "@babel/runtime": "^7.12.0", "@emotion/cache": "^11.4.0", - "@emotion/react": "^11.1.1", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", "@types/react-transition-group": "^4.4.0", - "memoize-one": "^5.0.0", + "memoize-one": "^6.0.0", "prop-types": "^15.6.0", - "react-transition-group": "^4.3.0" + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.1.2" }, "devDependencies": { - "@types/jest-in-case": "^1.0.3", + "@types/jest-in-case": "^1.0.6", "enzyme": "^3.8.0", "enzyme-to-json": "^3.3.0", "jest-in-case": "^1.0.2", @@ -27,8 +29,8 @@ "react-dom": "^16.13.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "files": [ "dist", @@ -50,12 +52,12 @@ ], "preconstruct": { "entrypoints": [ - ".", - "base", - "animated", - "async", - "creatable", - "async-creatable" + "index.ts", + "base/index.ts", + "animated/index.ts", + "async/index.ts", + "creatable/index.ts", + "async-creatable/index.ts" ] } } diff --git a/packages/react-select/src/NonceProvider.tsx b/packages/react-select/src/NonceProvider.tsx index 1ba548aa88..5e91a85e49 100644 --- a/packages/react-select/src/NonceProvider.tsx +++ b/packages/react-select/src/NonceProvider.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; -import { Component, ReactNode } from 'react'; +import { useMemo } from 'react'; +import { ReactNode } from 'react'; import { CacheProvider } from '@emotion/react'; import createCache from '@emotion/cache'; -import memoizeOne from 'memoize-one'; interface NonceProviderProps { nonce: string; @@ -10,21 +10,10 @@ interface NonceProviderProps { cacheKey: string; } -export default class NonceProvider extends Component { - constructor(props: NonceProviderProps) { - super(props); - this.createEmotionCache = memoizeOne(this.createEmotionCache); - } - createEmotionCache = (nonce: string, key: string) => { - return createCache({ nonce, key }); - }; - render() { - const emotionCache = this.createEmotionCache( - this.props.nonce, - this.props.cacheKey - ); - return ( - {this.props.children} - ); - } -} +export default ({ nonce, children, cacheKey }: NonceProviderProps) => { + const emotionCache = useMemo( + () => createCache({ key: cacheKey, nonce }), + [cacheKey, nonce] + ); + return {children}; +}; diff --git a/packages/react-select/src/Select.tsx b/packages/react-select/src/Select.tsx index 5814ee871d..699f6119ce 100644 --- a/packages/react-select/src/Select.tsx +++ b/packages/react-select/src/Select.tsx @@ -81,7 +81,7 @@ export interface Props< 'aria-labelledby'?: AriaAttributes['aria-labelledby']; /** Used to set the priority with which screen reader should treat updates to live regions. The possible settings are: off, polite (default) or assertive */ 'aria-live'?: AriaAttributes['aria-live']; - /** Customize the messages used by the aria-live component */ + /** Customise the messages used by the aria-live component */ ariaLiveMessages?: AriaLiveMessages; /** Focus the control when it is mounted */ autoFocus?: boolean; @@ -727,9 +727,18 @@ export default class Select< } if (isFocused && isDisabled && !prevProps.isDisabled) { - // ensure select state gets blurred in case Select is programatically disabled while focused + // ensure select state gets blurred in case Select is programmatically disabled while focused // eslint-disable-next-line react/no-did-update-set-state this.setState({ isFocused: false }, this.onMenuClose); + } else if ( + !isFocused && + !isDisabled && + prevProps.isDisabled && + this.inputRef === document.activeElement + ) { + // ensure select state gets focused in case Select is programatically re-enabled while focused (Firefox) + // eslint-disable-next-line react/no-did-update-set-state + this.setState({ isFocused: true }); } // scroll the focused option into view if necessary @@ -991,7 +1000,7 @@ export default class Select< // ============================== getTheme() { - // Use the default theme if there are no customizations. + // Use the default theme if there are no customisations. if (!this.props.theme) { return defaultTheme; } @@ -1560,13 +1569,15 @@ export default class Select< 'aria-autocomplete': 'list' as const, 'aria-expanded': menuIsOpen, 'aria-haspopup': true, - 'aria-controls': this.getElementId('listbox'), - 'aria-owns': this.getElementId('listbox'), 'aria-errormessage': this.props['aria-errormessage'], 'aria-invalid': this.props['aria-invalid'], 'aria-label': this.props['aria-label'], 'aria-labelledby': this.props['aria-labelledby'], role: 'combobox', + ...(menuIsOpen && { + 'aria-controls': this.getElementId('listbox'), + 'aria-owns': this.getElementId('listbox'), + }), ...(!isSearchable && { 'aria-readonly': true, }), diff --git a/packages/react-select/src/__tests__/Select.test.tsx b/packages/react-select/src/__tests__/Select.test.tsx index a7fe6eaab7..b48171c1a2 100644 --- a/packages/react-select/src/__tests__/Select.test.tsx +++ b/packages/react-select/src/__tests__/Select.test.tsx @@ -1953,7 +1953,7 @@ test('multi select > clicking on X next to option will call onChange with all op }); /** - * TODO: Need to get hightlight a menu option and then match value with aria-activedescendant prop + * TODO: Need to get highlight a menu option and then match value with aria-activedescendant prop */ cases( 'accessibility > aria-activedescendant', diff --git a/packages/react-select/src/__tests__/StateManaged.test.tsx b/packages/react-select/src/__tests__/StateManaged.test.tsx index e61d7b074c..1626020558 100644 --- a/packages/react-select/src/__tests__/StateManaged.test.tsx +++ b/packages/react-select/src/__tests__/StateManaged.test.tsx @@ -68,7 +68,7 @@ cases( 'click on dropdown indicator', ({ props }) => { let { container } = render( snapshot 1`] = ` > ; } -interface CollapseState { - width: Width; -} // wrap each MultiValue with a collapse transition; decreases width until // finally removing from DOM -export class Collapse extends Component { - duration = collapseDuration; - rafID?: number | null; - state: CollapseState = { width: 'auto' }; - transition: { [K in TransitionStatus]?: CSSProperties } = { - exiting: { width: 0, transition: `width ${this.duration}ms ease-out` }, - exited: { width: 0 }, - }; - nodeRef = createRef(); +export const Collapse = ({ children, in: _in, onExited }: CollapseProps) => { + const ref = useRef(null); + const [width, setWidth] = useState('auto'); - componentDidMount() { - const { current: ref } = this.nodeRef; + useEffect(() => { + const el = ref.current; + if (!el) return; /* - A check on existence of ref should not be necessary at this point, - but TypeScript demands it. + Here we're invoking requestAnimationFrame with a callback invoking our + call to getBoundingClientRect and setState in order to resolve an edge case + around portalling. Certain portalling solutions briefly remove children from the DOM + before appending them to the target node. This is to avoid us trying to call getBoundingClientrect + while the Select component is in this state. */ - if (ref) { - /* - Here we're invoking requestAnimationFrame with a callback invoking our - call to getBoundingClientRect and setState in order to resolve an edge case - around portalling. Certain portalling solutions briefly remove children from the DOM - before appending them to the target node. This is to avoid us trying to call getBoundingClientrect - while the Select component is in this state. - */ - // cannot use `offsetWidth` because it is rounded - this.rafID = window.requestAnimationFrame(() => { - const { width } = ref.getBoundingClientRect(); - this.setState({ width }); - }); - } - } + // cannot use `offsetWidth` because it is rounded + const rafId = window.requestAnimationFrame(() => + setWidth(el.getBoundingClientRect().width) + ); - componentWillUnmount() { - if (this.rafID) { - window.cancelAnimationFrame(this.rafID); + return () => window.cancelAnimationFrame(rafId); + }, []); + + const getStyleFromStatus = (status: TransitionStatus) => { + switch (status) { + default: + return { width }; + case 'exiting': + return { width: 0, transition: `width ${collapseDuration}ms ease-out` }; + case 'exited': + return { width: 0 }; } - } - - // get base styles - getStyle = (width: Width): CSSProperties => ({ - overflow: 'hidden', - whiteSpace: 'nowrap', - width, - }); - - // get transition styles - getTransition = (state: TransitionStatus) => this.transition[state]; - - render() { - const { children, in: inProp, onExited } = this.props; - const exitedProp = () => { - if (this.nodeRef.current && onExited) { - onExited(this.nodeRef.current); - } - }; - - const { width } = this.state; + }; - return ( - - {(state) => { - const style = { - ...this.getStyle(width), - ...this.getTransition(state), - }; - return ( -
- {children} -
- ); - }} -
- ); - } -} + return ( + { + const el = ref.current; + if (!el) return; + onExited?.(el); + }} + timeout={collapseDuration} + nodeRef={ref} + > + {(status) => ( +
+ {children} +
+ )} +
+ ); +}; diff --git a/packages/react-select/src/async-creatable/index.ts b/packages/react-select/src/async-creatable/index.ts new file mode 100644 index 0000000000..76ef9d9ee5 --- /dev/null +++ b/packages/react-select/src/async-creatable/index.ts @@ -0,0 +1,2 @@ +export * from '../AsyncCreatable'; +export { default } from '../AsyncCreatable'; diff --git a/packages/react-select/src/async/index.ts b/packages/react-select/src/async/index.ts new file mode 100644 index 0000000000..260c255b2c --- /dev/null +++ b/packages/react-select/src/async/index.ts @@ -0,0 +1,2 @@ +export * from '../Async'; +export { default } from '../Async'; diff --git a/packages/react-select/src/base/index.ts b/packages/react-select/src/base/index.ts new file mode 100644 index 0000000000..7192f7dda0 --- /dev/null +++ b/packages/react-select/src/base/index.ts @@ -0,0 +1,2 @@ +export * from '../Select'; +export { default } from '../Select'; diff --git a/packages/react-select/src/components/Menu.tsx b/packages/react-select/src/components/Menu.tsx index f6846774ec..a1a48da148 100644 --- a/packages/react-select/src/components/Menu.tsx +++ b/packages/react-select/src/components/Menu.tsx @@ -1,20 +1,26 @@ /** @jsx jsx */ import { createContext, - Component, + ReactElement, ReactNode, - RefCallback, - ContextType, + Ref, + useCallback, + useContext, + useMemo, + useRef, + useState, } from 'react'; import { jsx } from '@emotion/react'; import { createPortal } from 'react-dom'; +import { autoUpdate } from '@floating-ui/dom'; +import useLayoutEffect from 'use-isomorphic-layout-effect'; import { animatedScrollTo, getBoundingClientObj, - RectType, getScrollParent, getScrollTop, + normalizedHeight, scrollTo, } from '../utils'; import { @@ -35,8 +41,8 @@ import { // Get Menu Placement // ------------------------------ -interface MenuState { - placement: CoercedMenuPlacement | null; +interface CalculatedMenuPlacementAndHeight { + placement: CoercedMenuPlacement; maxHeight: number; } interface PlacementArgs { @@ -50,17 +56,20 @@ interface PlacementArgs { } export function getMenuPlacement({ - maxHeight, + maxHeight: preferredMaxHeight, menuEl, minHeight, - placement, + placement: preferredPlacement, shouldScroll, isFixedPosition, theme, -}: PlacementArgs): MenuState { +}: PlacementArgs): CalculatedMenuPlacementAndHeight { const { spacing } = theme; const scrollParent = getScrollParent(menuEl!); - const defaultState: MenuState = { placement: 'bottom', maxHeight }; + const defaultState: CalculatedMenuPlacementAndHeight = { + placement: 'bottom', + maxHeight: preferredMaxHeight, + }; // something went wrong, return default state if (!menuEl || !menuEl.offsetParent) return defaultState; @@ -75,7 +84,9 @@ export function getMenuPlacement({ } = menuEl.getBoundingClientRect(); const { top: containerTop } = menuEl.offsetParent.getBoundingClientRect(); - const viewHeight = window.innerHeight; + const viewHeight = isFixedPosition + ? window.innerHeight + : normalizedHeight(scrollParent); const scrollTop = getScrollTop(scrollParent); const marginBottom = parseInt(getComputedStyle(menuEl).marginBottom, 10); @@ -89,12 +100,12 @@ export function getMenuPlacement({ const scrollUp = scrollTop + menuTop - marginTop; const scrollDuration = 160; - switch (placement) { + switch (preferredPlacement) { case 'auto': case 'bottom': // 1: the menu will fit, do nothing if (viewSpaceBelow >= menuHeight) { - return { placement: 'bottom', maxHeight }; + return { placement: 'bottom', maxHeight: preferredMaxHeight }; } // 2: the menu will fit, if scrolled @@ -103,7 +114,7 @@ export function getMenuPlacement({ animatedScrollTo(scrollParent, scrollDown, scrollDuration); } - return { placement: 'bottom', maxHeight }; + return { placement: 'bottom', maxHeight: preferredMaxHeight }; } // 3: the menu will fit, if constrained @@ -130,15 +141,15 @@ export function getMenuPlacement({ // 4. Forked beviour when there isn't enough space below // AUTO: flip the menu, render above - if (placement === 'auto' || isFixedPosition) { + if (preferredPlacement === 'auto' || isFixedPosition) { // may need to be constrained after flipping - let constrainedHeight = maxHeight; + let constrainedHeight = preferredMaxHeight; const spaceAbove = isFixedPosition ? viewSpaceAbove : scrollSpaceAbove; if (spaceAbove >= minHeight) { constrainedHeight = Math.min( spaceAbove - marginBottom - spacing.controlHeight, - maxHeight + preferredMaxHeight ); } @@ -146,17 +157,17 @@ export function getMenuPlacement({ } // BOTTOM: allow browser to increase scrollable area and immediately set scroll - if (placement === 'bottom') { + if (preferredPlacement === 'bottom') { if (shouldScroll) { scrollTo(scrollParent, scrollDown); } - return { placement: 'bottom', maxHeight }; + return { placement: 'bottom', maxHeight: preferredMaxHeight }; } break; case 'top': // 1: the menu will fit, do nothing if (viewSpaceAbove >= menuHeight) { - return { placement: 'top', maxHeight }; + return { placement: 'top', maxHeight: preferredMaxHeight }; } // 2: the menu will fit, if scrolled @@ -165,7 +176,7 @@ export function getMenuPlacement({ animatedScrollTo(scrollParent, scrollUp, scrollDuration); } - return { placement: 'top', maxHeight }; + return { placement: 'top', maxHeight: preferredMaxHeight }; } // 3: the menu will fit, if constrained @@ -173,7 +184,7 @@ export function getMenuPlacement({ (!isFixedPosition && scrollSpaceAbove >= minHeight) || (isFixedPosition && viewSpaceAbove >= minHeight) ) { - let constrainedHeight = maxHeight; + let constrainedHeight = preferredMaxHeight; // we want to provide as much of the menu as possible to the user, // so give them whatever is available below rather than the minHeight. @@ -199,9 +210,9 @@ export function getMenuPlacement({ // 4. not enough space, the browser WILL NOT increase scrollable area when // absolutely positioned element rendered above the viewport (only below). // Flip the menu, render below - return { placement: 'bottom', maxHeight }; + return { placement: 'bottom', maxHeight: preferredMaxHeight }; default: - throw new Error(`Invalid placement provided "${placement}".`); + throw new Error(`Invalid placement provided "${preferredPlacement}".`); } return defaultState; @@ -230,7 +241,7 @@ export interface MenuProps< > extends CommonPropsAndClassName, MenuPlacementProps { /** Reference to the internal element, consumed by the MenuPlacer component */ - innerRef: RefCallback; + innerRef: Ref; innerProps: JSX.IntrinsicElements['div']; isLoading: boolean; placement: CoercedMenuPlacement; @@ -244,7 +255,7 @@ interface PlacerProps { } interface ChildrenProps { - ref: RefCallback; + ref: Ref; placerProps: PlacerProps; } @@ -255,7 +266,7 @@ export interface MenuPlacerProps< > extends CommonProps, MenuPlacementProps { /** The children to be rendered. */ - children: (childrenProps: ChildrenProps) => ReactNode; + children: (childrenProps: ChildrenProps) => ReactElement; } function alignToControl(placement: CoercedMenuPlacement) { @@ -284,34 +295,37 @@ export const menuCSS = < zIndex: 1, }); -const PortalPlacementContext = createContext<{ - getPortalPlacement: ((menuState: MenuState) => void) | null; -}>({ getPortalPlacement: null }); +const PortalPlacementContext = + createContext<{ + setPortalPlacement: (placement: CoercedMenuPlacement) => void; + } | null>(null); // NOTE: internal only -export class MenuPlacer< +export const MenuPlacer = < Option, IsMulti extends boolean, Group extends GroupBase