wikimedica-disease-search/app/src/components/TermInput.js

189 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 lutilisateur.
*/
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 lentrée courante en lajoutant 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 lappui sur une touche dans le champ de saisie.
*
* @param ev Informations sur lévénement dappui.
*/
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 (
<div className="TermInput">
{query.map((term, index) =>
<span
key={term.id}
className="TermInput_queryTerm"
title="Cliquez pour retirer ce terme de la recherche"
onClick={removeQueryTerm.bind(null, index)}
>
{term.name}
</span>
)}
<input
autoFocus={true}
type="text" className="TermInput_input"
placeholder="Rechercher un symptôme, un signe ou une maladie…"
value={value} ref={inputRef}
onChange={handleInputChange}
onKeyDown={handleInputKeyDown} />
<ul className="TermInput_suggestions">
{suggestions.map((suggestion, index) =>
<li
key={suggestion.id}
className={[
'TermInput_suggestion',
index === focusedSuggestion
? 'TermInput_suggestion-focus'
: '',
].join(' ')}
onMouseEnter={setFocusedSuggestion.bind(null, index)}
onClick={finalizeInput.bind(null, suggestion)}
title="Cliquez pour ajouter ce terme à la recherche"
>
{suggestion.name}
{suggestion.alias.length >= 1 ? (
<span
className="TermInput_suggestionAlias"
title={
(suggestion.alias.length >= 2
? 'Autres noms: '
: 'Autre nom: ')
+ suggestion.alias.join(', ')
}
>
{suggestion.alias.join(', ')}
</span>
) : null}
</li>
)}
</ul>
</div>
);
};
TermInput.propTypes = {
query: PropTypes.arrayOf(Term).isRequired,
addQueryTerm: PropTypes.func.isRequired,
removeQueryTerm: PropTypes.func.isRequired,
availableTerms: PropTypes.arrayOf(Term).isRequired,
};
export default TermInput;