Skip to content

Commit

Permalink
[Autocomplete] Fix Multiple tag delete action (mui#18153)
Browse files Browse the repository at this point in the history
  • Loading branch information
Takeichi Kanzaki Cabrera authored and oliviertassinari committed Nov 9, 2019
1 parent 0050cb8 commit 096bbc0
Show file tree
Hide file tree
Showing 12 changed files with 45 additions and 77 deletions.
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">renderGroup</span> | <span class="prop-type">func</span> | | Render the group.<br><br>**Signature:**<br>`function(option: any) => ReactNode`<br>*option:* The group to render. |
| <span class="prop-name required">renderInput&nbsp;*</span> | <span class="prop-type">func</span> | | Render the input.<br><br>**Signature:**<br>`function(params: object) => ReactNode`<br>*params:* null |
| <span class="prop-name">renderOption</span> | <span class="prop-type">func</span> | | Render the option, use `getOptionLabel` by default.<br><br>**Signature:**<br>`function(option: any, state: object) => ReactNode`<br>*option:* The option to render.<br>*state:* The state of the component. |
| <span class="prop-name">renderTags</span> | <span class="prop-type">func</span> | | Render the selected value.<br><br>**Signature:**<br>`function(value: any) => ReactNode`<br>*value:* The `value` provided to the component. |
| <span class="prop-name">renderTags</span> | <span class="prop-type">func</span> | | Render the selected value.<br><br>**Signature:**<br>`function(value: any, getTagProps: function) => ReactNode`<br>*value:* The `value` provided to the component.<br>*getTagProps:* A tag props getter. |
| <span class="prop-name">value</span> | <span class="prop-type">any</span> | | The value of the autocomplete. |

The `ref` is forwarded to the root element.
Expand Down
8 changes: 1 addition & 7 deletions docs/src/pages/components/autocomplete/CustomizedHook.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,7 @@ export default function CustomizedHook() {
<Label {...getInputLabelProps()}>Customized hook</Label>
<InputWrapper ref={setAnchorEl} className={focused ? 'focused' : ''}>
{value.map((option, index) => (
<Tag
key={index}
data-tag-index={index}
tabIndex={-1}
label={option.title}
{...getTagProps()}
/>
<Tag label={option.title} {...getTagProps({ index })} />
))}

<input {...getInputProps()} />
Expand Down
8 changes: 1 addition & 7 deletions docs/src/pages/components/autocomplete/CustomizedHook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,7 @@ export default function CustomizedHook() {
<Label {...getInputLabelProps()}>Customized hook</Label>
<InputWrapper ref={setAnchorEl} className={focused ? 'focused' : ''}>
{value.map((option: FilmOptionType, index: number) => (
<Tag
key={index}
data-tag-index={index}
tabIndex={-1}
label={option.title}
{...getTagProps()}
/>
<Tag label={option.title} {...getTagProps({ index })} />
))}
<input {...getInputProps()} />
</InputWrapper>
Expand Down
12 changes: 2 additions & 10 deletions docs/src/pages/components/autocomplete/FixedTags.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,9 @@ export default function FixedTags() {
options={top100Films}
getOptionLabel={option => option.title}
defaultValue={[top100Films[6], top100Films[13]]}
renderTags={(value, { className, onDelete }) =>
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
key={index}
disabled={index === 0}
data-tag-index={index}
tabIndex={-1}
label={option.title}
className={className}
onDelete={onDelete}
/>
<Chip disabled={index === 0} label={option.title} {...getTagProps({ index })} />
))
}
style={{ width: 500 }}
Expand Down
12 changes: 2 additions & 10 deletions docs/src/pages/components/autocomplete/FixedTags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,9 @@ export default function FixedTags() {
options={top100Films}
getOptionLabel={(option: FilmOptionType) => option.title}
defaultValue={[top100Films[6], top100Films[13]]}
renderTags={(value: FilmOptionType[], { className, onDelete }) =>
renderTags={(value: FilmOptionType[], getTagProps) =>
value.map((option: FilmOptionType, index: number) => (
<Chip
key={index}
disabled={index === 0}
data-tag-index={index}
tabIndex={-1}
label={option.title}
className={className}
onDelete={onDelete}
/>
<Chip disabled={index === 0} label={option.title} {...getTagProps({ index })} />
))
}
style={{ width: 500 }}
Expand Down
12 changes: 2 additions & 10 deletions docs/src/pages/components/autocomplete/Tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,9 @@ export default function Tags() {
options={top100Films.map(option => option.title)}
defaultValue={[top100Films[13].title]}
freeSolo
renderTags={(value, { className, onDelete }) =>
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
key={index}
variant="outlined"
data-tag-index={index}
tabIndex={-1}
label={option}
className={className}
onDelete={onDelete}
/>
<Chip variant="outlined" label={option} {...getTagProps({ index })} />
))
}
renderInput={params => (
Expand Down
12 changes: 2 additions & 10 deletions docs/src/pages/components/autocomplete/Tags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,9 @@ export default function Tags() {
options={top100Films.map(option => option.title)}
defaultValue={[top100Films[13].title]}
freeSolo
renderTags={(value: string[], { className, onDelete }) =>
renderTags={(value: string[], getTagProps) =>
value.map((option: string, index: number) => (
<Chip
key={index}
variant="outlined"
data-tag-index={index}
tabIndex={-1}
label={option}
className={className}
onDelete={onDelete}
/>
<Chip variant="outlined" label={option} {...getTagProps({ index })} />
))
}
renderInput={params => (
Expand Down
8 changes: 3 additions & 5 deletions packages/material-ui-lab/src/Autocomplete/Autocomplete.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ export interface RenderOptionState {
selected: boolean;
}

export interface RenderValueState {
className: string;
onDelete: () => {};
}
export type GetTagProps = ({ index }: { index: number }) => {};

export interface RenderGroupParams {
key: string;
Expand Down Expand Up @@ -103,9 +100,10 @@ export interface AutocompleteProps
* Render the selected value.
*
* @param {any} value The `value` provided to the component.
* @param {function} getTagProps A tag props getter.
* @returns {ReactNode}
*/
renderTags?: (value: any, state: RenderValueState) => React.ReactNode;
renderTags?: (value: any, getTagProps: GetTagProps) => React.ReactNode;
}

export type AutocompleteClassKey =
Expand Down
17 changes: 6 additions & 11 deletions packages/material-ui-lab/src/Autocomplete/Autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,22 +232,16 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) {
let startAdornment;

if (multiple && value.length > 0) {
const tagProps = {
...getTagProps(),
const getCustomizedTagProps = params => ({
className: classes.tag,
};
...getTagProps(params),
});

if (renderTags) {
startAdornment = renderTags(value, tagProps);
startAdornment = renderTags(value, getCustomizedTagProps);
} else {
startAdornment = value.map((option, index) => (
<Chip
key={index}
data-tag-index={index}
tabIndex={-1}
label={getOptionLabel(option)}
{...tagProps}
/>
<Chip label={getOptionLabel(option)} {...getCustomizedTagProps({ index })} />
));
}
}
Expand Down Expand Up @@ -570,6 +564,7 @@ Autocomplete.propTypes = {
* Render the selected value.
*
* @param {any} value The `value` provided to the component.
* @param {function} getTagProps A tag props getter.
* @returns {ReactNode}
*/
renderTags: PropTypes.func,
Expand Down
17 changes: 17 additions & 0 deletions packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,23 @@ describe('<Autocomplete />', () => {
document.activeElement.blur();
input.focus();
});

it('should remove the last option', () => {
const handleChange = spy();
const options = ['one', 'two'];
const { container } = render(
<Autocomplete
defaultValue={options}
options={options}
onChange={handleChange}
renderInput={params => <TextField {...params} />}
multiple
/>,
);
fireEvent.click(container.querySelectorAll('svg[data-mui-test="CancelIcon"]')[1]);
expect(handleChange.callCount).to.equal(1);
expect(handleChange.args[0][1]).to.deep.equal([options[0]]);
});
});

describe('WAI-ARIA conforming markup', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export default function useAutocomplete(
getInputLabelProps: () => {};
getClearProps: () => {};
getPopupIndicatorProps: () => {};
getTagProps: () => {};
getTagProps: ({ index }: { index: number }) => {};
getListboxProps: () => {};
getOptionProps: ({ option, index }: { option: any; index: number }) => {};
id: string;
Expand Down
12 changes: 7 additions & 5 deletions packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -613,8 +613,7 @@ export default function useAutocomplete(props) {
selectNewValue(event, filteredOptions[highlightedIndexRef.current]);
};

const handleTagDelete = event => {
const index = Number(event.currentTarget.getAttribute('data-tag-index'));
const handleTagDelete = index => event => {
const newValue = [...value];
newValue.splice(index, 1);
handleValue(event, newValue);
Expand Down Expand Up @@ -705,8 +704,11 @@ export default function useAutocomplete(props) {
event.preventDefault();
},
}),
getTagProps: () => ({
onDelete: handleTagDelete,
getTagProps: ({ index }) => ({
key: index,
'data-tag-index': index,
tabIndex: -1,
onDelete: handleTagDelete(index),
}),
getListboxProps: () => ({
role: 'listbox',
Expand All @@ -722,9 +724,9 @@ export default function useAutocomplete(props) {
const disabled = getOptionDisabled ? getOptionDisabled(option) : false;

return {
key: index,
tabIndex: -1,
role: 'option',
key: index,
id: `${id}-option-${index}`,
onMouseOver: handleOptionMouseOver,
onClick: handleOptionClick,
Expand Down

0 comments on commit 096bbc0

Please sign in to comment.