Does this jsFiddle demo meet your requirements? (Click "Run" to start the demo.) If not, please let me know what is missing.
Notes
The demo accepts only comma-separated integers (or a single integer) as input. No spaces are permitted.
Duplicate values are displayed with a red chip and not included in the result set. So far duplicate values are the only validation criterion causing values to fail where the failure is shown to the user via red chips.
Non numerical input fails silently.
index.html
<!DOCTYPE html>
<html>
<head>
<title>Material-UI Chip Input Field Test</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="testContainer" class="testContainer"></div>
<script src="./index.js"></script>
<noscript> Please enable javascript to view the site </noscript>
</body>
</html>
index.jsx
import * as React from 'react';
import { createRoot } from 'react-dom/client';
const container = document.getElementById( 'testContainer' );
const root = createRoot( container );
import Chip from '@mui/material/Chip';
import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField';
import Stack from '@mui/material/Stack';
import { v4 as uuidv4 } from 'uuid';
let collator = new Intl.Collator( 'en', { numeric: true, sensitivity: 'base' } );
const getSortedListAsArray = ( list ) => {
let sortedList = list.sort( ( a, b ) => {
return collator.compare( a.value, b.value );
} );
return sortedList;
}
export default function Tags() {
const [ list, setList ] = React.useState( [] );
const selectedItems = list.filter( ( item ) => item.isValid );
const selectedLengthIndex = selectedItems.length - 1;
let listById = {};
for ( let item of list ) {
listById[ item.id ] = item;
}
function updateList( items ) {
let newList = [].concat( list, items );
newList = getSortedListAsArray( newList );
setList( newList );
}
function validateValue( value, validatedItems ) {
let isValid = false;
const valueInt = parseInt( value );
const selectedValues = list.map( ( item ) => item.value );
const validatedValues = validatedItems.map( ( item ) => item.value );
if ( selectedValues.indexOf( valueInt ) === -1 &&
validatedValues.indexOf( valueInt ) === -1
) {
isValid = true;
}
return isValid;
}
function validateInput( event, inputs, reason ) {
if ( 'createOption' == reason ) {
let validatedItems = [];
let values = inputs[ inputs.length - 1 ].split( ',' );
for ( let value of values ) {
// Test for positive integers. Fail silently.
if ( /[^0-9]+/.test( value ) || value.length == 0 ) {
continue;
} else {
let isValid = validateValue( value, validatedItems );
validatedItems.push( {
id: uuidv4(),
value: parseInt( value ),
isValid
} );
}
}
updateList( validatedItems );
} else if ( 'removeOption' == reason ) {
let newList = inputs.map( ( id ) => listById[ id ] );
setList( newList );
} else if ( 'clear' == reason ) {
setList( [] );
}
}
/**
* Return call adapted from Material-UI 'Multiple values' Autocomplete demo.
* @see https://mui.com/material-ui/react-table/#sorting-amp-selecting
* Code: @see https://github.com/mui/material-ui/blob/v5.9.2/docs/data/material/components/autocomplete/Tags.tsx
*
*/
return (
<Stack spacing={ 3 } sx={ { width: 500 } }>
<Autocomplete
multiple
id='tags-filled'
filterSelectedOptions={ true }
options={ [] }
value={ list.map( ( item ) => item.id ) }
freeSolo
renderTags={ ( listIds, getTagProps ) =>
listIds.map( ( id, index ) => (
<Chip
key={ index }
variant='outlined'
label={ listById[ id ].value }
sx={ {
color: ( theme ) => {
let chipColor = '#fff';
if ( typeof( listById[ id ] ) == 'object' ) {
chipColor = listById[ id ].isValid
? theme.palette.common.white
: theme.palette.common.white
}
return chipColor;
},
backgroundColor: ( theme ) => {
let chipColor = '#fff';
if ( typeof( listById[ id ] ) == 'object' ) {
chipColor = listById[ id ].isValid
? theme.palette.primary.main
: theme.palette.error.main
}
return chipColor;
},
[`& .MuiSvgIcon-root.MuiSvgIcon-fontSizeMedium.MuiChip-deleteIcon.MuiChip-deleteIconMedium.MuiChip-deleteIconColorDefault.MuiChip-deleteIconOutlinedColorDefault`]: {
fill: ( theme ) => theme.palette.grey[200]
}
} }
{ ...getTagProps( { index } ) }
/>
) )
}
renderInput={ ( params ) => (
<TextField
{ ...params }
variant='filled'
label='Material-UI Chip Input Test'
placeholder='e.g. 12,73,902,23,41'
helperText='Enter comma separated integers (no spaces)'
/>
) }
onChange={ validateInput }
/>
{ /* Display list of unique integers. */ }
<div>
{ selectedItems.map( ( item, index ) => {
let comma = null;
if ( selectedLengthIndex != index ) {
comma = ( <span key={ 'idx' + index }>, </span> );
}
return (
item.isValid
? <span key={ index }>{ item.value }{ comma }</span>
: null
);
} ) }
</div>
</Stack>
);
}
/**
* Inject component into DOM
*/
root.render(
<Tags />
);