zim-fuzzysearch
Version:
Client side fuzzy search combined with seperate configurable result highlighting.
146 lines (145 loc) • 7.16 kB
JavaScript
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";
};