import React, {useState, useRef} from 'react'; /** * Codes des touches du clavier par nom. */ const keys = Object.assign(Object.create(null), { 'backspace': 8, 'enter': 13, 'left': 37, 'up': 38, 'right': 39, 'down': 40, }); /** * Zone de saisie des termes de recherche. * * @prop terms Ensemble de termes déjà validés. * @param availableTerms Termes pouvant être ajoutés par l’utilisateur. * @prop setTerms Fonction de rappel utilisée pour modifier l’ensemble des * termes validés par l’utilisateur. */ const TermInput = ({terms, availableTerms, setTerms}) => { // Valeur actuellement saisie dans le champ de recherche de termes const [value, setValue] = useState(''); // Liste des termes suggérés par autocomplétion const [suggestions, setSuggestions] = useState([]); // Terme ayant le focus parmi les suggestions const [focusedSuggestion, setFocusedSuggestion] = useState(0); // Référence au champ de saisie const inputRef = useRef(null); /** * Ajoute un terme à l’ensemble des termes sélectionnés. * * @param term Terme à ajouter. */ const addTerm = term => { if (terms.some(({id}) => id === term.id)) { // N’ajoute pas les termes déjà sélectionnés return; } setTerms(terms.concat([term])); setValue(''); setSuggestions([]); setFocusedSuggestion(0); if (inputRef.current !== null) { inputRef.current.focus(); } }; /** * Supprime un terme des termes sélectionnés. * * @param index Indice du terme à supprimer dans la liste. */ const removeTerm = index => { setTerms([ ...terms.slice(0, index), ...terms.slice(index + 1) ]); }; /** * Gère l’appui sur une touche dans le champ de saisie. * * @param ev Informations sur l’événement d’appui. */ const handleInputKeyDown = ev => { if (ev.keyCode === keys.enter && value) { // Touche Entrée : ajout de la suggestion ayant le focus ev.preventDefault(); if (focusedSuggestion < suggestions.length) { addTerm(suggestions[focusedSuggestion]); } } else if ( ev.keyCode === keys.backspace && !value && terms.length !== 0 ) { // Touche Retour alors que le champ de saisie est vide : // retrait du dernier terme ev.preventDefault(); setTerms(terms.slice(0, -1)); } else if (ev.keyCode === keys.up && focusedSuggestion > 0) { // Touche Haut : focus de la suggestion précédente ev.preventDefault(); setFocusedSuggestion(focusedSuggestion - 1); } else if ( ev.keyCode === keys.down && focusedSuggestion < suggestions.length - 1 ) { // Touche Bas : focus de la suggestion suivante ev.preventDefault(); setFocusedSuggestion(focusedSuggestion + 1); } }; /** * Gère la modification du contenu du champ de saisie. * * @param ev Informations sur l’événement de modification. */ const handleInputChange = ev => { const nextValue = ev.target.value; setValue(nextValue); if (nextValue.length === 0) { // On ne fait pas de suggestion si la valeur saisie est trop courte setSuggestions([]); } else { setSuggestions(availableTerms // Sélectionne les termes qui n’ont pas déjà été choisis .filter(({id: suggestionId}) => !terms.some(({id: termId}) => termId === suggestionId ) ) // Filtre ceux dont le nom correspond à la saisie .filter(({name}) => name.toLowerCase().startsWith(nextValue.toLowerCase()) ) ); } }; return (
{terms.map((term, index) => {term.name} )}
); }; export default TermInput;