import React , { useEffect , useMemo , useRef , useState } from 'react' ; import mergeRefs from 'react-merge-refs' ; import useMeasure from 'react-use-measure' ; import uniqueId from 'lodash.uniqueid' ; import { ResizeObserver } from '@juggle/resize-observer' ; import styles from './SelectedItems.module.scss' ; import { Tag } from '../../components/tag' ; import classNames from '../../utils/classNames' ; import { IconCrossCircle } from '../../icons' ; type SelectedItemsProps < OptionType > = { /**
     * The index of the item that should be active and focused
     */ activeIndex : number ; /**
     * Additional class names to apply to the root element
     */ className ? : string ; /**
     * Flag for whether the clear selections button should be displayed
     */ clearable ? : boolean ; /**
     * The aria-label for the clear button
     */ clearButtonAriaLabel ? : string ; /**
     * Dropdown ID
     */ dropdownId : string ; /**
     * Downshift selected item getter function
     */ getSelectedItemProps : any ; /**
     * Flag for whether selected items that horizontally don't fit the container should be hidden
     */ hideItems : boolean ; /**
     * Callback function fired when the clear button is pressed.
     */ onClear : ( ) => void ; /**
     * Callback function fired when the when an item is removed
     */ onRemove : ( selectedItem : OptionType ) => void ; /**
     * Sets the data item field that represents the item label
     */ optionLabelField : string ; /**
     * The aria-label for the remove button
     */ removeButtonAriaLabel : string ; /**
     * Selected items
     */ selectedItems : OptionType [ ] ; /**
     * Selected items container ref
     */ selectedItemsContainerRef : React . MutableRefObject < HTMLDivElement > ; /**
     * Label for selected items that is only visible to screen readers. Can be used to to give screen reader users additional information about the selected item
     */ selectedItemSrLabel ? : string ; /**
     * Downshift setActiveIndex function
     */ setActiveIndex : ( index : number ) => void ; /**
     * Whether the dropdown toggle button is hidden
     */ toggleButtonHidden ? : boolean ; } ; param string param value const replaceTokenWithValue = ( string , value ) : string | undefined => string ?. replace ( '{value}' , value ) ; param element param property const getHorizontalSpacing = ( element , property ) : number => { if ( ! element ) return 0 ; const longhandProperties = [ `${ property }-left` , `${ property }-right` ] ; const computedStyles = window . getComputedStyle ( element ) ; return ( longhandProperties . map ( ( item ) => computedStyles . getPropertyValue ( item ) . replace ( 'px' , '' ) ) . reduce ( ( spacing , value ) => spacing + parseInt ( value , 10 ) , 0 ) ) ; } ; param childSpacing param containerEl param containerSpacing param hiddenCountEl param hideItems param setHiddenCount const handleItemHiding = ( childSpacing , containerEl , containerSpacing , hiddenCountEl , hideItems , setHiddenCount ) : void => { const visibleItems = [ ] ; const hiddenItems = [ ] ; if ( containerEl && hiddenCountEl ) { const childNodes = [ ... containerEl . childNodes ] . filter ( ( node ) => node . tagName === 'DIV' ) as HTMLDivElement [ ] ; const hiddenCountWidth = hiddenCountEl . offsetWidth + childSpacing ; const containerWidth = containerEl . offsetWidth - hiddenCountWidth - containerSpacing ; [ ... childNodes ] . reduce ( ( combinedChildWidth , child ) => { const childWidth = child . offsetWidth + childSpacing ; combinedChildWidth + childWidth < containerWidth ? visibleItems . push ( child ) : hiddenItems . push ( child ) ; return combinedChildWidth + childWidth ; } , 0 ) ; if ( hideItems ) { visibleItems . forEach ( ( item ) => item . classList . remove ( styles . hidden ) ) ; hiddenItems . forEach ( ( item ) => item . classList . add ( styles . hidden ) ) ; setHiddenCount ( hiddenItems . length ) ; } else { [ ... childNodes ] . forEach ( ( item ) => item . classList . remove ( styles . hidden ) ) ; } } } ; export const SelectedItems = < OptionType > ( { activeIndex , className , clearable = true , clearButtonAriaLabel , dropdownId , getSelectedItemProps , hideItems = false , onClear , onRemove , optionLabelField , removeButtonAriaLabel , selectedItems , selectedItemsContainerRef , selectedItemSrLabel , setActiveIndex , toggleButtonHidden = false , } ) => { const [ ref , { width , height } ] = useMeasure ( { debounce : 0 , scroll : false , polyfill : ResizeObserver } ) ; const [ hiddenCount , setHiddenCount ] = useState ( 0 ) ; const hiddenItemsCountRef = useRef < HTMLSpanElement > ( ) ; const hiddenCountEl = hiddenItemsCountRef . current ; const containerEl = selectedItemsContainerRef . current ; const [ containerSpacing , childSpacing ] = useMemo ( ( ) => { if ( ! containerEl ) return [ 0 , 0 ] ; const childNodes = containerEl . childNodes || [ ] ; const container = getHorizontalSpacing ( containerEl , 'padding' ) ; const child = getHorizontalSpacing ( childNodes [ 0 ] as HTMLDivElement , 'margin' ) ; return [ container , child ] ; } , [ containerEl ] ) ; useEffect ( ( ) => handleItemHiding ( childSpacing , containerEl , containerSpacing , hiddenCountEl , hideItems , setHiddenCount ) , [ childSpacing , containerEl , containerSpacing , height , hiddenCountEl , hideItems , selectedItems , setHiddenCount , width , ] ) ; return ( < > < div ref = { mergeRefs < HTMLDivElement > ( [ ref , selectedItemsContainerRef ] ) } className = { classNames ( styles . selectedItems , hideItems && styles . itemsHidden , toggleButtonHidden && styles . noToggle , className ) } > { selectedItems . map ( ( _selectedItem , index ) => { const selectedItemLabel = _selectedItem [ optionLabelField ] ; const tagId = uniqueId ( 'hds-tag-' ) ; return ( < Tag key = { selectedItemLabel } className = { styles . tag } id = { tagId } labelProps = { { 'aria-labelledby' : `${ dropdownId }-label ${ tagId }-label` } } role = "button" deleteButtonAriaLabel = { replaceTokenWithValue ( removeButtonAriaLabel , selectedItemLabel ) } deleteButtonProps = { { tabIndex : - 1 , } } onDelete = { ( e ) => { e . stopPropagation ( ) ; onRemove ( _selectedItem ) ; } } srOnlyLabel = { replaceTokenWithValue ( selectedItemSrLabel , selectedItemLabel ) } { ... getSelectedItemProps ( { selectedItem : _selectedItem , index , onKeyDown : ( event ) => { if ( event . key === 'Backspace' ) event . preventDefault ( ) ; else if ( event . key === 'Enter' || event . key === ' ' ) { event . preventDefault ( ) ; onRemove ( _selectedItem ) ; } } , onFocus : ( ) => setActiveIndex ( index ) , } ) } > { selectedItemLabel } < /Tag>); } ) } < span ref = { hiddenItemsCountRef } className = { classNames ( styles . hiddenItemsCount , ( ! hideItems || hiddenCount === 0 ) && styles . hidden ) } > + { hiddenCount } < /span> < /div> { } { clearable && ( < button type = "button" className = { classNames ( styles . clearButton , toggleButtonHidden && styles . noToggle ) } onClick = { onClear } aria - label = { clearButtonAriaLabel } onFocus = { ( ) => { if ( activeIndex === - 1 ) { ( containerEl ?. childNodes [ 0 ] as HTMLDivElement ) . setAttribute ( 'tabindex' , '0' ) ; } } } > < IconCrossCircle /> < /button>)} < />); } ;