import React, {useState, useRef} from 'react'; import PropTypes from 'prop-types'; import {Term} from '../data/model.js'; 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 query Ensemble de termes déjà dans la requête. * @prop addQueryTerm Fonction de rappel pour ajouter un terme à la requête. * @prop removeQueryTerm Fonction de rappel pour ôter un terme de la requête. * @prop availableTerms Termes pouvant être ajoutés par l’utilisateur. */ const TermInput = ({ query, addQueryTerm, removeQueryTerm, availableTerms, }) => { // 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); /** * Valide l’entrée courante en l’ajoutant comme terme de la requête. * * @param term Terme à ajouter dans la requête. */ const finalizeInput = term => { addQueryTerm(term); setValue(''); setSuggestions([]); if (inputRef.current !== null) { inputRef.current.focus(); } }; /** * 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) { finalizeInput(suggestions[focusedSuggestion]); } } else if (ev.keyCode === keys.backspace && !value) { // Touche Retour alors que le champ de saisie est vide : // retrait du dernier terme de la requête ev.preventDefault(); removeQueryTerm(query.length - 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 { // Filtre les termes correspondant à la saisie setSuggestions(fuzzy.filterTerms(nextValue, availableTerms)); } }; return (
{query.map((term, index) => {term.name} )}
); }; TermInput.propTypes = { query: PropTypes.arrayOf(Term).isRequired, addQueryTerm: PropTypes.func.isRequired, removeQueryTerm: PropTypes.func.isRequired, availableTerms: PropTypes.arrayOf(Term).isRequired, }; export default TermInput;