import { matchSorter } from "match-sorter"; import * as React from "react"; import { Action, ActionGroup, ActionTree } from "./types"; import useKBar from "./useKBar"; export const NO_GROUP = "none"; export default function useMatches() { const { actions, search, rootActionId } = useKBar((state) => ({ actions: state.actions, search: state.searchQuery, rootActionId: state.currentRootActionId, })); const filtered = React.useMemo(() => { return Object.keys(actions).reduce((acc, actionId) => { const action = actions[actionId]; if (!action.parent && !rootActionId) { acc[actionId] = action; } if (action.parent === rootActionId) { acc[actionId] = action; } return acc; }, {}); }, [actions, rootActionId]); const matches = useInternalMatches(filtered, search); const groups = React.useMemo(() => { const groupMap = matches.reduce((acc, action) => { const section = action.section || NO_GROUP; if (!acc[section]) { acc[section] = []; } acc[section].push(action); return acc; }, {} as Record); let groups: ActionGroup[] = []; Object.keys(groupMap).forEach((name) => { const actions = groupMap[name]; groups.push({ name, actions }); }); return groups; }, [matches]); return groups; } function useInternalMatches(actions: ActionTree, search: string) { // matchSorter returns an unstable array each time it is called. // We throttle here to limit the calls to a reasonable amount. const throttled = useThrottled(search, 100); const list = React.useMemo( () => Object.keys(actions).map((key) => actions[key]), [actions] ); return React.useMemo( () => { const pinnedActions = list.filter((action) => action.pinned); const filteredActions = search.trim() === "" ? list : matchSorter(list, throttled, { keys: ["name", "keywords", "subtitle"], }); return pinnedActions .filter((action) => !filteredActions.find((curr) => curr === action)) .concat(filteredActions); }, // eslint-disable-next-line react-hooks/exhaustive-deps [throttled, list] ); } function useThrottled(value: string, ms: number) { const [throttled, setThrottled] = React.useState(value); const lastRan = React.useRef(Date.now()); React.useEffect(() => { const timeout = setTimeout(() => { setThrottled(value); lastRan.current = Date.now(); }, lastRan.current - (Date.now() - ms)); return () => clearTimeout(timeout); }, [ms, value]); return throttled; }