import React, {useState, useRef} from 'react'; import * as fuzzy from '../data/fuzzy.js'; import * as util from '../util.js'; /** * 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 { list: suggestions, setList: setSuggestions, focus: focusedSuggestion, setFocus: setFocusedSuggestion } = util.useFocusableList(); // 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([]); 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) { // Touche Haut : focus de la suggestion précédente ev.preventDefault(); setFocusedSuggestion(focusedSuggestion - 1); } else if (ev.keyCode === keys.down) { // 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(fuzzy.filterTerms( // Filtre les termes correspondant à la saisie nextValue, // Sélectionne les termes qui n’ont pas déjà été choisis availableTerms.filter(({id: suggestionId}) => !terms.some(({id: termId}) => termId === suggestionId ) ) )); } }; return (
{terms.map((term, index) => {term.name} )}
); }; export default TermInput;