ra-core
Version:
Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React
187 lines • 7.31 kB
JavaScript
import { useCallback, isValidElement } from 'react';
import set from 'lodash/set.js';
import { useChoices, } from "./choices/useChoices.js";
import { useTranslate } from "../i18n/index.js";
/*
* Returns helper functions for suggestions handling.
*
* @param allowDuplicates A boolean indicating whether a suggestion can be added several times
* @param choices An array of available choices
* @param limitChoicesToValue A boolean indicating whether the initial suggestions should be limited to the currently selected one(s)
* @param matchSuggestion Optional unless `optionText` is a React element. Function which check whether a choice matches a filter. Must return a boolean.
* @param optionText Either a string defining the property to use to get the choice text, a function or a React element
* @param optionValue The property to use to get the choice value
* @param selectedItem The currently selected item. Maybe an array of selected items
* @param suggestionLimit The maximum number of suggestions returned
* @param translateChoice A boolean indicating whether to option text should be translated
*
* @returns An object with helper functions:
* - getChoiceText: Returns the choice text or a React element
* - getChoiceValue: Returns the choice value
* - getSuggestions: A function taking a filter value (string) and returning the matching suggestions
*/
export const useSuggestions = ({ allowCreate, choices, createText = 'ra.action.create', createValue = '@@create', createHintValue = '@@ra-create-hint', limitChoicesToValue, matchSuggestion, optionText, optionValue, selectedItem, suggestionLimit = 0, translateChoice, }) => {
const translate = useTranslate();
const { getChoiceText, getChoiceValue } = useChoices({
optionText,
optionValue,
translateChoice,
createValue,
createHintValue,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
const getSuggestions = useCallback(getSuggestionsFactory({
allowCreate,
choices,
createText,
createValue,
getChoiceText,
getChoiceValue,
limitChoicesToValue,
matchSuggestion,
optionText,
optionValue,
selectedItem,
suggestionLimit,
}), [
allowCreate,
choices,
createText,
createValue,
getChoiceText,
getChoiceValue,
limitChoicesToValue,
matchSuggestion,
optionText,
optionValue,
selectedItem,
suggestionLimit,
translate,
]);
return {
getChoiceText,
getChoiceValue,
getSuggestions,
};
};
const escapeRegExp = value => value ? value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : ''; // $& means the whole matched string
/**
* Default matcher implementation which check whether the suggestion text matches the filter.
*/
const defaultMatchSuggestion = getChoiceText => (filter, suggestion, exact = false) => {
const suggestionText = getChoiceText(suggestion);
const isReactElement = isValidElement(suggestionText);
const regex = escapeRegExp(filter);
return isReactElement
? false
: suggestionText &&
!!suggestionText.match(
// We must escape any RegExp reserved characters to avoid errors
// For example, the filter might contain * which must be escaped as \*
new RegExp(exact ? `^${regex}$` : regex, 'i'));
};
/**
* Get the suggestions to display after applying a fuzzy search on the available choices
*
* @example
*
* getSuggestions({
* choices: [{ id: 1, name: 'admin' }, { id: 2, name: 'publisher' }],
* optionText: 'name',
* optionValue: 'id',
* getSuggestionText: choice => choice[optionText],
* })('pub')
*
* // Will return [{ id: 2, name: 'publisher' }]
* getSuggestions({
* choices: [{ id: 1, name: 'admin' }, { id: 2, name: 'publisher' }],
* optionText: 'name',
* optionValue: 'id',
* getSuggestionText: choice => choice[optionText],
* })('pub')
*
* // Will return [{ id: 2, name: 'publisher' }]
*/
export const getSuggestionsFactory = ({ allowCreate = false, choices = [], createText = 'ra.action.create', createValue = '@@create', optionText = 'name', optionValue = 'id', getChoiceText, getChoiceValue, limitChoicesToValue = false, matchSuggestion = defaultMatchSuggestion(getChoiceText), selectedItem, suggestionLimit = 0, }) => filter => {
let suggestions = [];
// if an item is selected and matches the filter
if (selectedItem &&
!Array.isArray(selectedItem) &&
matchSuggestion(filter, selectedItem)) {
if (limitChoicesToValue) {
// display only the selected item
suggestions = choices.filter(choice => getChoiceValue(choice) === getChoiceValue(selectedItem));
}
else {
suggestions = [...choices];
}
}
else {
suggestions = choices.filter(choice => matchSuggestion(filter, choice) ||
(selectedItem != null &&
(!Array.isArray(selectedItem)
? getChoiceValue(choice) ===
getChoiceValue(selectedItem)
: selectedItem.some(selected => getChoiceValue(choice) ===
getChoiceValue(selected)))));
}
suggestions = limitSuggestions(suggestions, suggestionLimit);
const hasExactMatch = suggestions.some(suggestion => matchSuggestion(filter, suggestion, true));
if (allowCreate) {
const filterIsSelectedItem =
// If the selectedItem is an array (for example AutocompleteArrayInput)
// we shouldn't try to match
!!selectedItem && !Array.isArray(selectedItem)
? matchSuggestion(filter, selectedItem, true)
: false;
if (!hasExactMatch && !filterIsSelectedItem) {
suggestions.push(getSuggestion({
optionText,
optionValue,
text: createText,
value: createValue,
}));
}
}
// Only keep unique items. Necessary because we might have fetched
// the currently selected choice in addition of the possible choices
// that may also contain it
const result = suggestions.filter((suggestion, index) => suggestions.indexOf(suggestion) === index);
return result;
};
/**
* @example
*
* limitSuggestions(
* [{ id: 1, name: 'foo'}, { id: 2, name: 'bar' }],
* 1
* );
*
* // Will return [{ id: 1, name: 'foo' }]
*
* @param suggestions List of suggestions
* @param limit
*/
const limitSuggestions = (suggestions, limit = 0) => Number.isInteger(limit) && limit > 0
? suggestions.slice(0, limit)
: suggestions;
/**
* addSuggestion(
* [{ id: 1, name: 'foo'}, { id: 2, name: 'bar' }],
* );
*
* // Will return [{ id: null, name: '' }, { id: 1, name: 'foo' }, { id: 2, name: 'bar' }]
*
* @param suggestions List of suggestions
* @param options
* @param options.optionText
*/
const getSuggestion = ({ optionText = 'name', optionValue = 'id', text = '', value = null, }) => {
const suggestion = {};
set(suggestion, optionValue, value);
if (typeof optionText === 'string') {
set(suggestion, optionText, text);
}
return suggestion;
};
//# sourceMappingURL=useSuggestions.js.map