UNPKG

zim-fuzzysearch

Version:

Client side fuzzy search combined with seperate configurable result highlighting.

146 lines (145 loc) 7.16 kB
import React from "react"; import { useFuse } from "./hooks/useFuse"; import { FuzzyInputProvider } from "./hooks/useFuzzyInput"; import { FuzzyResultProvider } from "./hooks/useFuzzyResult"; import FuzzyGui from "./FuzzyGui"; // fuse specific default option var optionsDefault = { //includeMatches: true, ignoreLocation: true, ignoreFieldNorm: false, includeScore: true, keys: ["label", "txt", "tags"] }; //default options for mark.js var markOptionsDefault = { element: "b", accuracy: "complementary", exclude: ["h5"], }; var ZIMFuzzySearch = function (_a) { var data = _a.data, tagConceptMap = _a.tagConceptMap, onSelect = _a.onSelect, onTagSelect = _a.onTagSelect, onSearch = _a.onSearch, onFuseKeySelect = _a.onFuseKeySelect, _b = _a.onFuseScoreToCssClass, onFuseScoreToCssClass = _b === void 0 ? mapFuzzyInterval : _b, searchInput = _a.searchInput, _c = _a.placeHolder, placeHolder = _c === void 0 ? "Search..." : _c, onInput = _a.onInput, _d = _a.fuseOptions, fuseOptions = _d === void 0 ? optionsDefault : _d, _e = _a.markOptions, markOptions = _e === void 0 ? markOptionsDefault : _e, _f = _a.resultCountLabel, resultCountLabel = _f === void 0 ? "Matches: " : _f, _g = _a.searchOnClick, searchOnClick = _g === void 0 ? false : _g, _h = _a.showResultGrid, showResultGrid = _h === void 0 ? false : _h, _j = _a.fuseKeyMap, fuseKeyMap = _j === void 0 ? null : _j, _k = _a.fuseKeyAllLabel, fuseKeyAllLabel = _k === void 0 ? "All" : _k, suggestions = _a.suggestions, children = _a.children; var _l = React.useState(function () { // make sure that scores are always included fuseOptions.includeScore = true; return fuseOptions; }), fuseStateOptions = _l[0], setFuseStateOptions = _l[1]; var _m = React.useState(searchInput ? searchInput : null), inputVal = _m[0], setInputVal = _m[1]; var fuseSearchInputDelay = React.useRef(undefined); // states needed for additional filtering of the fuseResults var _o = React.useState(undefined), filteredFuseResult = _o[0], setFilteredFuseResult = _o[1]; var _p = React.useState([]), selTag = _p[0], setSelTag = _p[1]; var _q = useFuse(fuseStateOptions, data), fuseResult = _q.fuseResult, doFuseSearch = _q.doFuseSearch, groupBy = _q.groupBy, resetFuse = _q.resetFuse; // two way binding React.useEffect(function () { if (!searchInput) return; setInputVal(searchInput); }, [searchInput, data]); // when value set from outside -> do a fuse serach once React.useEffect(function () { if (!searchInput) return; // when value set from outside -> trigger search once. if (searchOnClick && !fuseResult && data) { doFuseSearch(searchInput); if (onSearch) onSearch(searchInput); } }, [data]); // when input change do search when not in clickmode. // timer delays init auf fusesearch(e.g. if multiple inputs) React.useEffect(function () { if (fuseSearchInputDelay.current) clearTimeout(fuseSearchInputDelay.current); if (!searchOnClick && inputVal) { fuseSearchInputDelay.current = setTimeout(function () { if (fuseSearchInputDelay.current) { doFuseSearch(inputVal); if (onSearch) onSearch(inputVal); } }, 300); } else if (inputVal === null) { resetFuse(); } }, [searchInput, data, inputVal]); var handleInput = function (val) { setInputVal(val); // ensure two way binding if (onInput) onInput(val ? val : ""); }; var toggleFuseKey = function (fuseKeys) { var copy = JSON.parse(JSON.stringify(fuseStateOptions)); copy.keys = fuseKeys; setFuseStateOptions(copy); if (onFuseKeySelect) onFuseKeySelect(fuseKeys); }; /** * Trigger fuse search whenever fuse options change. */ React.useEffect(function () { //do only fire if (fuseOptions === fuseStateOptions) return; if (!inputVal) return; doFuseSearch(inputVal); }, [fuseStateOptions]); var checkIncludes = function (activeKeys, fuseKeys) { return activeKeys.some(function (key) { return fuseKeys.includes(key); }); }; // everytime when fuseResult changes -> filter according to this useEffect. // Result component will only visualize the filtered result. // React.useEffect(function () { if (!fuseResult) return; if (!inputVal) return setFilteredFuseResult(undefined); // set filtered result to fuse result if no tagMap was given. if (!tagConceptMap || selTag.length === 0) return setFilteredFuseResult(function () { return fuseResult; }); var filtered = fuseResult.filter(function (item) { if (!item.item.tags) return false; //if(selTag[0] === "")return true; return item.item.tags.some(function (tag) { return selTag.includes(tag); }); //return item.item.tags.includes(selTag) }); setFilteredFuseResult(function () { return filtered; }); }, [fuseResult, selTag, inputVal]); return (React.createElement(FuzzyInputProvider, { fuseStateOptions: fuseStateOptions, fuseKeyMap: fuseKeyMap, fuseKeyAllLabel: fuseKeyAllLabel, toggleFuseKey: toggleFuseKey, clickMode: searchOnClick, doSearch: doFuseSearch, onSearch: onSearch, handleInput: handleInput, inputVal: inputVal, placeHolder: placeHolder, data: data, setSelTag: setSelTag, suggestions: suggestions }, React.createElement(FuzzyResultProvider, { fuzzyResult: filteredFuseResult, onSelect: onSelect, resultCountLabel: resultCountLabel, showResultGrid: showResultGrid, tagConceptMap: tagConceptMap, onTagSelect: onTagSelect, onFuseScoreToCssClass: onFuseScoreToCssClass }, children ? children : React.createElement(FuzzyGui, { markOptions: markOptions })))); }; export default ZIMFuzzySearch; /** * Used as default method. Runs when CSS names are calculated for each result div. * Defines intervals and returns different css classnames according to fuse.js scores. * @param score Floating point value for Fuse score. * @returns Different css classnames to indicate fuse scoring. */ var mapFuzzyInterval = function (score) { if (score < 0.0001) { return "zim-fuzzy-score-highest"; } else if (0.0001 < score && score <= 0.05) { return "zim-fuzzy-score-higher"; } else if (0.05 < score && score <= 0.1) { return "zim-fuzzy-score-high"; } else if (0.1 < score && score <= 0.5) { return "zim-fuzzy-score-medium"; } else if (0.5 < score && score <= 1) { return "zim-fuzzy-score-low"; } else if (score > 1) { return "zim-fuzzy-score-lowest"; } return "zim-fuzzy-score-error"; };