Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Autocomplete] Call onInputChange before onChange #18897

Merged
merged 3 commits into from
Dec 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/pages/api/autocomplete.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ You can learn more about the difference by [reading this guide](/guides/minimizi
| <span class="prop-name">noOptionsText</span> | <span class="prop-type">node</span> | <span class="prop-default">'No options'</span> | Text to display when there are no options.<br>For localization purposes, you can use the provided [translations](/guides/localization/). |
| <span class="prop-name">onChange</span> | <span class="prop-type">func</span> | | Callback fired when the value changes.<br><br>**Signature:**<br>`function(event: object, value: any) => void`<br>*event:* The event source of the callback<br>*value:* null |
| <span class="prop-name">onClose</span> | <span class="prop-type">func</span> | | Callback fired when the popup requests to be closed. Use in controlled mode (see open).<br><br>**Signature:**<br>`function(event: object) => void`<br>*event:* The event source of the callback. |
| <span class="prop-name">onInputChange</span> | <span class="prop-type">func</span> | | Callback fired when the input value changes.<br><br>**Signature:**<br>`function(event: object, value: string, reason: string) => void`<br>*event:* The event source of the callback.<br>*value:* The new value of the text input<br>*reason:* One of "input" (user input) or "reset" (programmatic change) |
| <span class="prop-name">onInputChange</span> | <span class="prop-type">func</span> | | Callback fired when the input value changes.<br><br>**Signature:**<br>`function(event: object, value: string, reason: string) => void`<br>*event:* The event source of the callback.<br>*value:* The new value of the text input<br>*reason:* Can be: "input" (user input), "reset" (programmatic change), `"clear"`. |
| <span class="prop-name">onOpen</span> | <span class="prop-type">func</span> | | Callback fired when the popup requests to be opened. Use in controlled mode (see open).<br><br>**Signature:**<br>`function(event: object) => void`<br>*event:* The event source of the callback. |
| <span class="prop-name">open</span> | <span class="prop-type">bool</span> | | Control the popup` open state. |
| <span class="prop-name">openText</span> | <span class="prop-type">string</span> | <span class="prop-default">'Open'</span> | Override the default text for the *open popup* icon button.<br>For localization purposes, you can use the provided [translations](/guides/localization/). |
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/api/dialog.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Dialogs are overlaid modal paper based components with a backdrop.
| <span class="prop-name">fullWidth</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the dialog stretches to `maxWidth`.<br>Notice that the dialog width grow is limited by the default margin. |
| <span class="prop-name">maxWidth</span> | <span class="prop-type">'xs'<br>&#124;&nbsp;'sm'<br>&#124;&nbsp;'md'<br>&#124;&nbsp;'lg'<br>&#124;&nbsp;'xl'<br>&#124;&nbsp;false</span> | <span class="prop-default">'sm'</span> | Determine the max-width of the dialog. The dialog width grows with the size of the screen. Set to `false` to disable `maxWidth`. |
| <span class="prop-name">onBackdropClick</span> | <span class="prop-type">func</span> | | Callback fired when the backdrop is clicked. |
| <span class="prop-name">onClose</span> | <span class="prop-type">func</span> | | Callback fired when the component requests to be closed.<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be:`"escapeKeyDown"`, `"backdropClick"`. |
| <span class="prop-name">onClose</span> | <span class="prop-type">func</span> | | Callback fired when the component requests to be closed.<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be: `"escapeKeyDown"`, `"backdropClick"`. |
| <span class="prop-name">onEnter</span> | <span class="prop-type">func</span> | | Callback fired before the dialog enters. |
| <span class="prop-name">onEntered</span> | <span class="prop-type">func</span> | | Callback fired when the dialog has entered. |
| <span class="prop-name">onEntering</span> | <span class="prop-type">func</span> | | Callback fired when the dialog is entering. |
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/api/menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ You can learn more about the difference by [reading this guide](/guides/minimizi
| <span class="prop-name">classes</span> | <span class="prop-type">object</span> | | Override or extend the styles applied to the component. See [CSS API](#css) below for more details. |
| <span class="prop-name">disableAutoFocusItem</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | When opening the menu will not focus the active item but the `[role="menu"]` unless `autoFocus` is also set to `false`. Not using the default means not following WAI-ARIA authoring practices. Please be considerate about possible accessibility implications. |
| <span class="prop-name">MenuListProps</span> | <span class="prop-type">object</span> | <span class="prop-default">{}</span> | Props applied to the [`MenuList`](/api/menu-list/) element. |
| <span class="prop-name">onClose</span> | <span class="prop-type">func</span> | | Callback fired when the component requests to be closed.<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be:`"escapeKeyDown"`, `"backdropClick"`, `"tabKeyDown"`. |
| <span class="prop-name">onClose</span> | <span class="prop-type">func</span> | | Callback fired when the component requests to be closed.<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be: `"escapeKeyDown"`, `"backdropClick"`, `"tabKeyDown"`. |
| <span class="prop-name">onEnter</span> | <span class="prop-type">func</span> | | Callback fired before the Menu enters. |
| <span class="prop-name">onEntered</span> | <span class="prop-type">func</span> | | Callback fired when the Menu has entered. |
| <span class="prop-name">onEntering</span> | <span class="prop-type">func</span> | | Callback fired when the Menu is entering. |
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/api/modal.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ This component shares many concepts with [react-overlays](https://react-bootstra
| <span class="prop-name">hideBackdrop</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the backdrop is not rendered. |
| <span class="prop-name">keepMounted</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | Always keep the children in the DOM. This prop can be useful in SEO situation or when you want to maximize the responsiveness of the Modal. |
| <span class="prop-name">onBackdropClick</span> | <span class="prop-type">func</span> | | Callback fired when the backdrop is clicked. |
| <span class="prop-name">onClose</span> | <span class="prop-type">func</span> | | Callback fired when the component requests to be closed. The `reason` parameter can optionally be used to control the response to `onClose`.<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be:`"escapeKeyDown"`, `"backdropClick"`. |
| <span class="prop-name">onClose</span> | <span class="prop-type">func</span> | | Callback fired when the component requests to be closed. The `reason` parameter can optionally be used to control the response to `onClose`.<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be: `"escapeKeyDown"`, `"backdropClick"`. |
| <span class="prop-name">onEscapeKeyDown</span> | <span class="prop-type">func</span> | | Callback fired when the escape key is pressed, `disableEscapeKeyDown` is false and the modal is in focus. |
| <span class="prop-name">onRendered</span> | <span class="prop-type">func</span> | | Callback fired once the children has been mounted into the `container`. It signals that the `open={true}` prop took effect.<br>This prop will be deprecated and removed in v5, the ref can be used instead. |
| <span class="prop-name required">open&nbsp;*</span> | <span class="prop-type">bool</span> | | If `true`, the modal is open. |
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/api/popover.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ You can learn more about the difference by [reading this guide](/guides/minimizi
| <span class="prop-name">elevation</span> | <span class="prop-type">number</span> | <span class="prop-default">8</span> | The elevation of the popover. |
| <span class="prop-name">getContentAnchorEl</span> | <span class="prop-type">func</span> | | This function is called in order to retrieve the content anchor element. It's the opposite of the `anchorEl` prop. The content anchor element should be an element inside the popover. It's used to correctly scroll and set the position of the popover. The positioning strategy tries to make the content anchor element just above the anchor element. |
| <span class="prop-name">marginThreshold</span> | <span class="prop-type">number</span> | <span class="prop-default">16</span> | Specifies how close to the edge of the window the popover can appear. |
| <span class="prop-name">onClose</span> | <span class="prop-type">func</span> | | Callback fired when the component requests to be closed.<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be:`"escapeKeyDown"`, `"backdropClick"` |
| <span class="prop-name">onClose</span> | <span class="prop-type">func</span> | | Callback fired when the component requests to be closed.<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be: `"escapeKeyDown"`, `"backdropClick"`. |
| <span class="prop-name">onEnter</span> | <span class="prop-type">func</span> | | Callback fired before the component is entering. |
| <span class="prop-name">onEntered</span> | <span class="prop-type">func</span> | | Callback fired when the component has entered. |
| <span class="prop-name">onEntering</span> | <span class="prop-type">func</span> | | Callback fired when the component is entering. |
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/api/snackbar.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ You can learn more about the difference by [reading this guide](/guides/minimizi
| <span class="prop-name">disableWindowBlurListener</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the `autoHideDuration` timer will expire even if the window is not focused. |
| <span class="prop-name">key</span> | <span class="prop-type">any</span> | | When displaying multiple consecutive Snackbars from a parent rendering a single &lt;Snackbar/>, add the key prop to ensure independent treatment of each message. e.g. &lt;Snackbar key={message} />, otherwise, the message may update-in-place and features such as autoHideDuration may be canceled. |
| <span class="prop-name">message</span> | <span class="prop-type">node</span> | | The message to display. |
| <span class="prop-name">onClose</span> | <span class="prop-type">func</span> | | Callback fired when the component requests to be closed. Typically `onClose` is used to set state in the parent component, which is used to control the `Snackbar` `open` prop. The `reason` parameter can optionally be used to control the response to `onClose`, for example ignoring `clickaway`.<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be:`"timeout"` (`autoHideDuration` expired) or: `"clickaway"`. |
| <span class="prop-name">onClose</span> | <span class="prop-type">func</span> | | Callback fired when the component requests to be closed. Typically `onClose` is used to set state in the parent component, which is used to control the `Snackbar` `open` prop. The `reason` parameter can optionally be used to control the response to `onClose`, for example ignoring `clickaway`.<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be: `"timeout"` (`autoHideDuration` expired), `"clickaway"`. |
| <span class="prop-name">onEnter</span> | <span class="prop-type">func</span> | | Callback fired before the transition is entering. |
| <span class="prop-name">onEntered</span> | <span class="prop-type">func</span> | | Callback fired when the transition has entered. |
| <span class="prop-name">onEntering</span> | <span class="prop-type">func</span> | | Callback fired when the transition is entering. |
Expand Down
4 changes: 2 additions & 2 deletions docs/pages/api/speed-dial.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ You can learn more about the difference by [reading this guide](/guides/minimizi
| <span class="prop-name">FabProps</span> | <span class="prop-type">object</span> | <span class="prop-default">{}</span> | Props applied to the [`Fab`](/api/fab/) element. |
| <span class="prop-name">hidden</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the SpeedDial will be hidden. |
| <span class="prop-name">icon</span> | <span class="prop-type">node</span> | | The icon to display in the SpeedDial Fab. The `SpeedDialIcon` component provides a default Icon with animation. |
| <span class="prop-name">onClose</span> | <span class="prop-type">func</span> | | Callback fired when the component requests to be closed.<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be:`"toggle"`, `"blur"`, `"mouseLeave"`, `"escapeKeyDown"`. |
| <span class="prop-name">onOpen</span> | <span class="prop-type">func</span> | | Callback fired when the component requests to be open.<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be:`"toggle"`, `"focus"`, `"mouseEnter"`. |
| <span class="prop-name">onClose</span> | <span class="prop-type">func</span> | | Callback fired when the component requests to be closed.<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be: `"toggle"`, `"blur"`, `"mouseLeave"`, `"escapeKeyDown"`. |
| <span class="prop-name">onOpen</span> | <span class="prop-type">func</span> | | Callback fired when the component requests to be open.<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be: `"toggle"`, `"focus"`, `"mouseEnter"`. |
| <span class="prop-name required">open&nbsp;*</span> | <span class="prop-type">bool</span> | | If `true`, the SpeedDial is open. |
| <span class="prop-name">openIcon</span> | <span class="prop-type">node</span> | | The icon to display in the SpeedDial Fab when the SpeedDial is open. |
| <span class="prop-name">TransitionComponent</span> | <span class="prop-type">elementType</span> | <span class="prop-default">Zoom</span> | The component used for the transition. |
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui-lab/src/Autocomplete/Autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ Autocomplete.propTypes = {
*
* @param {object} event The event source of the callback.
* @param {string} value The new value of the text input
* @param {string} reason One of "input" (user input) or "reset" (programmatic change)
* @param {string} reason Can be: "input" (user input), "reset" (programmatic change), `"clear"`.
*/
onInputChange: PropTypes.func,
/**
Expand Down
18 changes: 17 additions & 1 deletion packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,7 @@ describe('<Autocomplete />', () => {
});
});

describe('controlled input', () => {
describe('controlled', () => {
it('controls the input value', () => {
const handleChange = spy();
function MyComponent() {
Expand All @@ -708,6 +708,22 @@ describe('<Autocomplete />', () => {
expect(handleChange.args[0][0]).to.equal('a');
expect(document.activeElement.value).to.equal('');
});

it('should fire the input change event before the change event', () => {
const handleChange = spy();
const handleInputChange = spy();
render(
<Autocomplete
onChange={handleChange}
onInputChange={handleInputChange}
options={['foo']}
renderInput={params => <TextField {...params} autoFocus />}
/>,
);
fireEvent.keyDown(document.activeElement, { key: 'ArrowDown' });
fireEvent.keyDown(document.activeElement, { key: 'Enter' });
expect(handleInputChange.calledBefore(handleChange)).to.equal(true);
});
});

describe('prop: filterOptions', () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/material-ui-lab/src/SpeedDial/SpeedDial.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ export interface SpeedDialProps
* Callback fired when the component requests to be closed.
*
* @param {object} event The event source of the callback.
* @param {string} reason Can be:`"toggle"`, `"blur"`, `"mouseLeave"`, `"escapeKeyDown"`.
* @param {string} reason Can be: `"toggle"`, `"blur"`, `"mouseLeave"`, `"escapeKeyDown"`.
*/
onClose?: (event: React.SyntheticEvent<{}>, reason: CloseReason) => void;
/**
* Callback fired when the component requests to be open.
*
* @param {object} event The event source of the callback.
* @param {string} reason Can be:`"toggle"`, `"focus"`, `"mouseEnter"`.
* @param {string} reason Can be: `"toggle"`, `"focus"`, `"mouseEnter"`.
*/
onOpen?: (event: React.SyntheticEvent<{}>, reason: OpenReason) => void;
/**
Expand Down
4 changes: 2 additions & 2 deletions packages/material-ui-lab/src/SpeedDial/SpeedDial.js
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ SpeedDial.propTypes = {
* Callback fired when the component requests to be closed.
*
* @param {object} event The event source of the callback.
* @param {string} reason Can be:`"toggle"`, `"blur"`, `"mouseLeave"`, `"escapeKeyDown"`.
* @param {string} reason Can be: `"toggle"`, `"blur"`, `"mouseLeave"`, `"escapeKeyDown"`.
*/
onClose: PropTypes.func,
/**
Expand All @@ -425,7 +425,7 @@ SpeedDial.propTypes = {
* Callback fired when the component requests to be open.
*
* @param {object} event The event source of the callback.
* @param {string} reason Can be:`"toggle"`, `"focus"`, `"mouseEnter"`.
* @param {string} reason Can be: `"toggle"`, `"focus"`, `"mouseEnter"`.
*/
onOpen: PropTypes.func,
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export interface UseAutocompleteProps {
*
* @param {object} event The event source of the callback.
* @param {string} value The new value of the text input
* @param {string} reason One of "input" (user input) or "reset" (programmatic change)
* @param {string} reason Can be: "input" (user input), "reset" (programmatic change), `"clear"`.
*/
onInputChange?: (event: React.ChangeEvent<{}>, value: any, reason: 'input' | 'reset') => void;
/**
Expand Down
30 changes: 17 additions & 13 deletions packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,13 +436,14 @@ export default function useAutocomplete(props) {
newValue.splice(itemIndex, 1);
}
}

resetInputValue(event, newValue);

handleValue(event, newValue);
if (!disableCloseOnSelect) {
handleClose(event);
}

resetInputValue(event, newValue);

selectedIndexRef.current = -1;
};

Expand Down Expand Up @@ -511,8 +512,13 @@ export default function useAutocomplete(props) {

const handleClear = event => {
ignoreFocus.current = true;
handleValue(event, multiple ? [] : null);
setInputValue('');

if (onInputChange) {
onInputChange(event, '', 'clear');
}

handleValue(event, multiple ? [] : null);
};

const handleKeyDown = event => {
Expand Down Expand Up @@ -639,6 +645,14 @@ export default function useAutocomplete(props) {
const handleInputChange = event => {
const newValue = event.target.value;

if (inputValue !== newValue) {
setInputValue(newValue);

if (onInputChange) {
onInputChange(event, newValue, 'input');
}
}

if (newValue === '') {
if (disableOpenOnFocus) {
handleClose(event);
Expand All @@ -650,16 +664,6 @@ export default function useAutocomplete(props) {
} else {
handleOpen(event);
}

if (inputValue === newValue) {
return;
}

setInputValue(newValue);

if (onInputChange) {
onInputChange(event, newValue, 'input');
}
};

const handleOptionMouseOver = event => {
Expand Down
Loading