import React, { useMemo, useState, useRef, useContext, createContext, useEffect, useLayoutEffect } from 'react'; import PropTypes from 'prop-types'; import { Combobox, ComboboxInput, ComboboxList, ComboboxOption, ComboboxPopover } from '@reach/combobox'; import { useId } from '@reach/auto-id'; import '@reach/combobox/styles.css'; import { matchSorter } from 'match-sorter'; import { XIcon } from '@heroicons/react/outline'; import isHotkey from 'is-hotkey'; import invariant from 'tiny-invariant'; import { useDeferredSubmit, useHiddenField } from './shared/hooks'; const Context = createContext(); const optionValueByLabel = (values, options, label) => { const maybeOption = values.includes(label) ? [label, label] : options.find(([optionLabel]) => optionLabel == label); return maybeOption ? maybeOption[1] : undefined; }; const optionLabelByValue = (values, options, value) => { const maybeOption = values.includes(value) ? [value, value] : options.find(([, optionValue]) => optionValue == value); return maybeOption ? maybeOption[0] : undefined; }; function ComboMultiple({ options, id, labelledby, describedby, label, group, name = 'value', selected, acceptNewValues = false }) { invariant(id || label, 'ComboMultiple: `id` or a `label` are required'); invariant(group, 'ComboMultiple: `group` is required'); const inputRef = useRef(); const [term, setTerm] = useState(''); const [selections, setSelections] = useState(selected); const [newValues, setNewValues] = useState([]); const inputId = useId(id); const removedLabelledby = `${inputId}-remove`; const selectedLabelledby = `${inputId}-selected`; const optionsWithLabels = useMemo( () => Array.isArray(options[0]) ? options : options.filter((o) => o).map((o) => [o, o]), [options] ); const extraOptions = useMemo( () => acceptNewValues && term && term.length > 2 && !optionLabelByValue(newValues, optionsWithLabels, term) ? [[term, term]] : [], [acceptNewValues, term, optionsWithLabels, newValues] ); const results = useMemo( () => [ ...extraOptions, ...(term ? matchSorter( optionsWithLabels.filter(([label]) => !label.startsWith('--')), term ) : optionsWithLabels) ].filter(([, value]) => !selections.includes(value)), [term, selections, extraOptions, optionsWithLabels] ); const [, setHiddenFieldValue, hiddenField] = useHiddenField(group, name); const awaitFormSubmit = useDeferredSubmit(hiddenField); const handleChange = (event) => { setTerm(event.target.value); }; const saveSelection = (fn) => { setSelections((selections) => { selections = fn(selections); setHiddenFieldValue(JSON.stringify(selections)); return selections; }); }; const onSelect = (value) => { const maybeValue = [...extraOptions, ...optionsWithLabels].find( ([val]) => val == value ); const selectedValue = maybeValue && maybeValue[1]; if (selectedValue) { if ( acceptNewValues && extraOptions[0] && extraOptions[0][0] == selectedValue ) { setNewValues((newValues) => { const set = new Set(newValues); set.add(selectedValue); return [...set]; }); } saveSelection((selections) => { const set = new Set(selections); set.add(selectedValue); return [...set]; }); } setTerm(''); awaitFormSubmit.done(); hidePopover(); }; const onRemove = (label) => { const optionValue = optionValueByLabel(newValues, options, label); if (optionValue) { saveSelection((selections) => selections.filter((value) => value != optionValue) ); setNewValues((newValues) => newValues.filter((value) => value != optionValue) ); } inputRef.current.focus(); }; const onKeyDown = (event) => { if ( isHotkey('enter', event) || isHotkey(' ', event) || isHotkey(',', event) || isHotkey(';', event) ) { if ( term && [...extraOptions, ...optionsWithLabels] .map(([label]) => label) .includes(term) ) { event.preventDefault(); onSelect(term); } } }; const hidePopover = () => { document .querySelector(`[data-reach-combobox-popover-id="${inputId}"]`) ?.setAttribute('hidden', 'true'); }; const showPopover = () => { document .querySelector(`[data-reach-combobox-popover-id="${inputId}"]`) ?.removeAttribute('hidden'); }; const onBlur = () => { const shouldSelect = term && [...extraOptions, ...optionsWithLabels] .map(([label]) => label) .includes(term); awaitFormSubmit(() => { if (shouldSelect) { onSelect(term); } else { hidePopover(); } }); }; return ( désélectionner {results && (results.length > 0 || !acceptNewValues) && ( {results.length === 0 && (
  • Aucun résultat{' '}
  • )} {results.map(([label], index) => { if (label.startsWith('--')) { return ; } return ; })}
    )}
    ); } function ComboboxTokenLabel({ onRemove, ...props }) { const selectionsRef = useRef([]); useLayoutEffect(() => { selectionsRef.current = []; return () => (selectionsRef.current = []); }); const context = { onRemove, selectionsRef }; return (
    ); } ComboboxTokenLabel.propTypes = { onRemove: PropTypes.func }; function ComboboxSeparator({ value }) { return (
  • {value.slice(2, -2)}
  • ); } ComboboxSeparator.propTypes = { value: PropTypes.string }; function ComboboxToken({ value, describedby, ...props }) { const { selectionsRef, onRemove } = useContext(Context); useEffect(() => { selectionsRef.current.push(value); }); return (
  • ); } ComboboxToken.propTypes = { value: PropTypes.string, describedby: PropTypes.string }; ComboMultiple.propTypes = { options: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf( PropTypes.arrayOf( PropTypes.oneOfType([PropTypes.string, PropTypes.number]) ) ) ]), selected: PropTypes.arrayOf(PropTypes.string), arraySelected: PropTypes.arrayOf(PropTypes.array), acceptNewValues: PropTypes.bool, mandatory: PropTypes.bool, id: PropTypes.string, group: PropTypes.string, name: PropTypes.string, labelledby: PropTypes.string, describedby: PropTypes.string, label: PropTypes.string }; export default ComboMultiple;