UNPKG

reka-ui

Version:

Vue port for Radix UI Primitives.

88 lines (86 loc) 3.47 kB
const require_rolldown_runtime = require('../rolldown-runtime.cjs'); const require_shared_getActiveElement = require('./getActiveElement.cjs'); const __vueuse_shared = require_rolldown_runtime.__toESM(require("@vueuse/shared")); //#region src/shared/useTypeahead.ts function useTypeahead(callback) { const search = (0, __vueuse_shared.refAutoReset)("", 1e3); const handleTypeaheadSearch = (key, items) => { search.value = search.value + key; if (callback) callback(key); else { const currentItem = require_shared_getActiveElement.getActiveElement(); const itemsWithTextValue = items.map((item) => ({ ...item, textValue: item.value?.textValue ?? item.ref.textContent?.trim() ?? "" })); const currentMatch = itemsWithTextValue.find((item) => item.ref === currentItem); const values = itemsWithTextValue.map((item) => item.textValue); const nextMatch = getNextMatch(values, search.value, currentMatch?.textValue); const newItem = itemsWithTextValue.find((item) => item.textValue === nextMatch); if (newItem) newItem.ref.focus(); return newItem?.ref; } }; const resetTypeahead = () => { search.value = ""; }; return { search, handleTypeaheadSearch, resetTypeahead }; } /** * Wraps an array around itself at a given start index * Example: `wrapArray(['a', 'b', 'c', 'd'], 2) === ['c', 'd', 'a', 'b']` */ function wrapArray(array, startIndex) { return array.map((_, index) => array[(startIndex + index) % array.length]); } /** * This is the "meat" of the typeahead matching logic. It takes in all the values, * the search and the current match, and returns the next match (or `undefined`). * * We normalize the search because if a user has repeatedly pressed a character, * we want the exact same behavior as if we only had that one character * (ie. cycle through options starting with that character) * * We also reorder the values by wrapping the array around the current match. * This is so we always look forward from the current match, and picking the first * match will always be the correct one. * * Finally, if the normalized search is exactly one character, we exclude the * current match from the values because otherwise it would be the first to match always * and focus would never move. This is as opposed to the regular case, where we * don't want focus to move if the current match still matches. */ function getNextMatch(values, search, currentMatch) { const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]); const normalizedSearch = isRepeated ? search[0] : search; const currentMatchIndex = currentMatch ? values.indexOf(currentMatch) : -1; let wrappedValues = wrapArray(values, Math.max(currentMatchIndex, 0)); const excludeCurrentMatch = normalizedSearch.length === 1; if (excludeCurrentMatch) wrappedValues = wrappedValues.filter((v) => v !== currentMatch); const nextMatch = wrappedValues.find((value) => value.toLowerCase().startsWith(normalizedSearch.toLowerCase())); return nextMatch !== currentMatch ? nextMatch : void 0; } //#endregion Object.defineProperty(exports, 'getNextMatch', { enumerable: true, get: function () { return getNextMatch; } }); Object.defineProperty(exports, 'useTypeahead', { enumerable: true, get: function () { return useTypeahead; } }); Object.defineProperty(exports, 'wrapArray', { enumerable: true, get: function () { return wrapArray; } }); //# sourceMappingURL=useTypeahead.cjs.map