How to customize Material UI Chip component to be editable and copy paste content (comma separated)

Viewed 318

I have the requirement for create the input field which takes the numbers array(comma ','separated ) like [123,34,23,13].

I want when user copy this 123,34,23,13 into input field it should change in this below format based on comma.

enter image description here

Is there anyway to achieve this.

Later on I want to show the red flag also for incorrect number(which will be verify on submission of value).

2 Answers

Does this jsFiddle demo meet your requirements? (Click "Run" to start the demo.) If not, please let me know what is missing.

Notes

  1. The demo accepts only comma-separated integers (or a single integer) as input. No spaces are permitted.

  2. 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.

  3. 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';

/**
 * Sorting and comparator functions adapted from Material-UI "Sorting & selecting"
 *   Table component 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/tables/EnhancedTable.tsx
 *
 */
function descendingComparator( a, b, orderBy ) {
    if ( typeof( a[ orderBy ] ) == 'undefined'  ) {
        a[ orderBy ] = 0;
    }

    if ( typeof( b[ orderBy ] ) == 'undefined' ) {
        b[ orderBy ] = 0;
    }

    if ( parseInt( b[ orderBy ] ) < parseInt( a[ orderBy ] ) ) {
        return -1;
    }
    if ( parseInt( b[ orderBy ] ) > parseInt( a[ orderBy ] ) ) {
        return 1;
    }

    return 0;
}

/**
 * Sorting and comparator functions adapted from Material-UI "Sorting & selecting"
 *   Table component 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/tables/EnhancedTable.tsx
 *
 */
function getComparator( order, orderBy ) {
    return order === 'desc'
        ? ( a, b ) => descendingComparator( a, b, orderBy )
        : ( a, b ) => -descendingComparator( a, b, orderBy );
}

/**
 * Sorting and comparator functions adapted from Material-UI "Sorting & selecting"
 *   Table component 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/tables/EnhancedTable.tsx
 *
 */
function stableSort( array, comparator ) {
    const stabilizedThis = array.map( ( el, index ) => [ el, index ] );
    stabilizedThis.sort( ( a, b ) => {
        const order = comparator( a[ 0 ], b[ 0 ] );
        if ( order !== 0 ) {
            return order;
        }
        return a[ 1 ] - b[ 1 ];
    });
    return stabilizedThis.map( ( el ) => el[ 0 ] );
}

/**
 * Sorting and comparator functions adapted from Material-UI "Sorting & selecting"
 *   Table component 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/tables/EnhancedTable.tsx
 *
 */
const getSortedListAsArray = ( list ) => {
    let comparator = getComparator( 'asc', 'value' );
    let sortedReminders = stableSort( list, comparator );

    return sortedReminders;
}

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;
    }

    /**
     * Sorting and comparator functions adapted from Material-UI "Sorting & selecting"
     *   Table component 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/tables/EnhancedTable.tsx
     *
     */
    function updateList( items ) {
        const selectedValues = list.map( ( item ) => item.value );
        let newSelected = list.slice(); 

        for ( let item of items ) {
            let index = parseInt( item.value );
            let selectedIndex = -1;

            if ( typeof( list ) != 'undefined' && list != null ) {
                selectedIndex = selectedValues.indexOf( index );
            }
        
            if ( selectedIndex === -1 ) {
                newSelected = newSelected.concat( item );
            } else if ( selectedIndex === 0 ) {
                newSelected = newSelected.concat( newSelected.slice( 1 ) );
            } else if ( selectedIndex === list.length - 1 ) {
                newSelected = newSelected.concat( newSelected.slice( 0, -1 ) );
            } else if ( selectedIndex > 0 ) {
                newSelected = newSelected.concat(
                    newSelected.slice( 0, selectedIndex ),
                    newSelected.slice( selectedIndex + 1 )
                );
            }
        }

        newSelected = getSortedListAsArray( newSelected );
        setList( newSelected );
    }

    function validateValue( value, validatedItems ) {
        let isValid = false;
        const selectedValues = list.map( ( item ) => item.value );
        const validatedValues = validatedItems.map( ( item ) => item.value );
        if ( selectedValues.indexOf( value ) === -1 && validatedValues.indexOf( value ) === -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 ) {
                if ( /[^0-9]+/.test( value ) || value.length == 0 ) {
                    continue;
                } else {
                    let isValid = validateValue( value, validatedItems );
                    validatedItems.push( {
                        id: uuidv4(),
                        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={ list.map(( item ) => item.id) }
                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 positive integers (no spaces)"
                    />
                )}
                onChange={ validateInput }
            />

            <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 />
);

Related