Skip to content

Commit

Permalink
Merge branch 'master' into tab-index-as-number-type
Browse files Browse the repository at this point in the history
  • Loading branch information
JedWatson authored Mar 4, 2021
2 parents ec7c072 + 1e73e5c commit 8543862
Show file tree
Hide file tree
Showing 23 changed files with 889 additions and 375 deletions.
5 changes: 5 additions & 0 deletions .changeset/bright-tigers-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-select': minor
---

Use accessor props to get value and label in `compareOption`
5 changes: 5 additions & 0 deletions .changeset/calm-pianos-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-select': patch
---

Set event listeners to be non-passive to remove Chrome console warnings
7 changes: 7 additions & 0 deletions .changeset/kind-yaks-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'react-select': patch
---

Memoize stripDiacritics in createFilter for the input with memoize-one so that stripDiacritics is not called for the same string as many times as there are options every time the input changes

Inspired by https://blog.johnnyreilly.com/2019/04/react-select-with-less-typing-lag.html
6 changes: 6 additions & 0 deletions .changeset/shaggy-chairs-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@react-select/docs': minor
'react-select': minor
---

Add ariaLiveMessages prop for internationalization and other customizations
58 changes: 58 additions & 0 deletions docs/examples/CustomAriaLive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// @flow
import React, { useState } from 'react';

import Select from 'react-select';
import { colourOptions } from '../data';

export default function CustomAriaLive() {
const [ariaFocusMessage, setAriaFocusMessage] = useState('');
const [isMenuOpen, setIsMenuOpen] = useState(false);

const style = {
blockquote: {
fontStyle: 'italic',
fontSize: '.75rem',
margin: '1rem 0',
},
label: {
fontSize: '.75rem',
fontWeight: 'bold',
lineHeight: 2,
},
};

const onFocus = ({ focused, isDisabled }) => {
const msg = `You are currently focused on option ${focused.label}${
isDisabled ? ', disabled' : ''
}`;
setAriaFocusMessage(msg);
return msg;
};

const onMenuOpen = () => setIsMenuOpen(true);
const onMenuClose = () => setIsMenuOpen(false);

return (
<form>
<label style={style.label} id="aria-label" htmlFor="aria-example-input">
Select a color
</label>

{!!ariaFocusMessage && !!isMenuOpen && (
<blockquote style={style.blockquote}>"{ariaFocusMessage}"</blockquote>
)}

<Select
aria-labelledby="aria-label"
ariaLiveMessages={{
onFocus,
}}
inputId="aria-example-input"
name="aria-live-color"
onMenuOpen={onMenuOpen}
onMenuClose={onMenuClose}
options={colourOptions}
/>
</form>
);
}
1 change: 1 addition & 0 deletions docs/examples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { default as AsyncPromises } from './AsyncPromises';
export { default as BasicGrouped } from './BasicGrouped';
export { default as BasicMulti } from './BasicMulti';
export { default as BasicSingle } from './BasicSingle';
export { default as CustomAriaLive } from './CustomAriaLive';
export { default as CustomControl } from './CustomControl';
export { default as CreatableAdvanced } from './CreatableAdvanced';
export { default as CreatableInputOnly } from './CreatableInputOnly';
Expand Down
21 changes: 18 additions & 3 deletions docs/pages/advanced/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ import md from '../../markdown/renderer';
import ExampleWrapper from '../../ExampleWrapper';
import {
AccessingInternals,
ControlledMenu,
OnSelectResetsInput,
BasicGrouped,
CreateFilter,
ControlledMenu,
CustomAriaLive,
CustomFilterOptions,
CustomGetOptionLabel,
CustomGetOptionValue,
CustomIsOptionDisabled,
Experimental,
Popout,
MenuBuffer,
MenuPortal,
MultiSelectSort,
Popout,
OnSelectResetsInput,
} from '../../examples';

export default function Advanced() {
Expand All @@ -33,6 +34,20 @@ export default function Advanced() {
{md`
# 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.
${(
<ExampleWrapper
label="Custom aria live example"
urlPath="docs/examples/CustomAriaLive.js"
raw={require('!!raw-loader!../../examples/CustomAriaLive.js')}
>
<CustomAriaLive />
</ExampleWrapper>
)}
## Sortable MultiSelect
Using the [react-sortable-hoc](https://www.npmjs.com/package/react-sortable-hoc) package we can easily allow sorting of MultiSelect values by drag and drop.
Expand Down
6 changes: 6 additions & 0 deletions docs/pages/home/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ export default function Home() {
yarn add react-select
~~~
or
~~~bash
npm i --save react-select
~~~
Import the default export and render in your component:
~~~jsx
Expand Down
38 changes: 30 additions & 8 deletions packages/react-select/src/Creatable.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ import Select, { type Props as SelectProps } from './Select';
import type { OptionType, OptionsType, ValueType, ActionMeta } from './types';
import { cleanValue } from './utils';
import manageState from './stateManager';
import {
getOptionValue as baseGetOptionValue,
getOptionLabel as baseGetOptionLabel,
} from './builtins';

type AccessorType = {|
getOptionValue: typeof baseGetOptionValue,
getOptionLabel: typeof baseGetOptionLabel,
|};

export type DefaultCreatableProps = {|
/* Allow options to be created while the `isLoading` prop is true. Useful to
Expand All @@ -25,10 +34,11 @@ export type DefaultCreatableProps = {|
formatCreateLabel: string => Node,
/* Determines whether the "create new ..." option should be displayed based on
the current input value, select value and options array. */
isValidNewOption: (string, OptionsType, OptionsType) => boolean,
isValidNewOption: (string, OptionsType, OptionsType, AccessorType) => boolean,
/* Returns the data for the new option when it is created. Used to display the
value, and is passed to `onChange`. */
getNewOptionData: (string, Node) => OptionType,
...AccessorType,
|};
export type CreatableProps = {
...DefaultCreatableProps,
Expand All @@ -48,10 +58,10 @@ export type CreatableProps = {

export type Props = SelectProps & CreatableProps;

const compareOption = (inputValue = '', option) => {
const compareOption = (inputValue = '', option, accessors) => {
const candidate = String(inputValue).toLowerCase();
const optionValue = String(option.value).toLowerCase();
const optionLabel = String(option.label).toLowerCase();
const optionValue = String(accessors.getOptionValue(option)).toLowerCase();
const optionLabel = String(accessors.getOptionLabel(option)).toLowerCase();
return optionValue === candidate || optionLabel === candidate;
};

Expand All @@ -60,18 +70,23 @@ const builtins = {
isValidNewOption: (
inputValue: string,
selectValue: OptionsType,
selectOptions: OptionsType
selectOptions: OptionsType,
accessors: AccessorType
) =>
!(
!inputValue ||
selectValue.some(option => compareOption(inputValue, option)) ||
selectOptions.some(option => compareOption(inputValue, option))
selectValue.some(option =>
compareOption(inputValue, option, accessors)
) ||
selectOptions.some(option => compareOption(inputValue, option, accessors))
),
getNewOptionData: (inputValue: string, optionLabel: Node) => ({
label: optionLabel,
value: inputValue,
__isNew__: true,
}),
getOptionValue: baseGetOptionValue,
getOptionLabel: baseGetOptionLabel,
};

export const defaultProps: DefaultCreatableProps = {
Expand Down Expand Up @@ -109,10 +124,17 @@ export const makeCreatableSelect = <C: {}>(
isLoading,
isValidNewOption,
value,
getOptionValue,
getOptionLabel,
} = props;
const options = props.options || [];
let { newOption } = state;
if (isValidNewOption(inputValue, cleanValue(value), options)) {
if (
isValidNewOption(inputValue, cleanValue(value), options, {
getOptionValue,
getOptionLabel,
})
) {
newOption = getNewOptionData(inputValue, formatCreateLabel(inputValue));
} else {
newOption = undefined;
Expand Down
Loading

0 comments on commit 8543862

Please sign in to comment.