@yext/search-ui-react
Version:
A library of React Components for powering Yext Search integrations
1,351 lines (1,309 loc) • 185 kB
JavaScript
// src/components/SearchBar.tsx
import {
QuerySource,
SearchTypeEnum as SearchTypeEnum2,
useSearchActions as useSearchActions2,
useSearchState as useSearchState2,
useSearchUtilities
} from "@yext/search-headless-react";
import classNames from "classnames";
import React14, { Fragment, isValidElement as isValidElement3, useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo3 } from "react";
// src/hooks/useEntityPreviews.tsx
import { useState } from "react";
// src/hooks/useComponentMountStatus.tsx
import { useEffect, useRef } from "react";
function useComponentMountStatus() {
const isMountedRef = useRef(false);
useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
return isMountedRef;
}
// src/hooks/useDebouncedFunction.ts
import { useRef as useRef2 } from "react";
function useDebouncedFunction(func, milliseconds) {
const timeoutIdRef = useRef2();
if (!func) {
return void 0;
}
const debounced = (...args) => {
return new Promise((resolve) => {
if (timeoutIdRef.current !== void 0) {
clearTimeout(timeoutIdRef.current);
}
timeoutIdRef.current = window.setTimeout(() => {
resolve(func(...args));
timeoutIdRef.current = void 0;
}, milliseconds);
});
};
return debounced;
}
// src/hooks/useEntityPreviews.tsx
function useEntityPreviews(entityPreviewSearcher, debounceTime) {
const isMountedRef = useComponentMountStatus();
const [
verticalKeyToResults,
setVerticalKeyToResults
] = useState({});
const debouncedUniversalSearch = useDebouncedFunction(async () => {
if (!entityPreviewSearcher) {
return;
}
await entityPreviewSearcher.executeUniversalQuery();
if (!isMountedRef.current) {
return;
}
const results = entityPreviewSearcher.state.universal.verticals || [];
setVerticalKeyToResults(getVerticalKeyToResults(results));
setLoadingState(false);
}, debounceTime);
const [isLoading, setLoadingState] = useState(false);
function executeEntityPreviewsQuery(query, universalLimit, restrictVerticals) {
if (!entityPreviewSearcher) {
return;
}
if (query === entityPreviewSearcher.state.query.input) {
return;
}
setLoadingState(true);
entityPreviewSearcher.setQuery(query);
entityPreviewSearcher.setRestrictVerticals(restrictVerticals);
entityPreviewSearcher.setUniversalLimit(universalLimit);
debouncedUniversalSearch?.();
}
return [{ verticalKeyToResults, isLoading }, executeEntityPreviewsQuery];
}
function getVerticalKeyToResults(verticalResultsArray) {
return verticalResultsArray.reduce((prev, current) => {
prev[current.verticalKey] = current;
return prev;
}, {});
}
// src/hooks/useRecentSearches.ts
import { useCallback, useEffect as useEffect2, useState as useState2 } from "react";
import { RecentSearches } from "recent-searches";
function useRecentSearches(recentSearchesLimit, verticalKey) {
const recentSearchesKey = getRecentSearchesKey(verticalKey);
const [recentSearches, setRecentSeaches] = useState2(
new RecentSearches({
limit: recentSearchesLimit,
namespace: recentSearchesKey
})
);
const clearRecentSearches = useCallback(() => {
localStorage.removeItem(recentSearchesKey);
setRecentSeaches(new RecentSearches({
limit: recentSearchesLimit,
namespace: recentSearchesKey
}));
localStorage.removeItem(recentSearchesKey);
}, [recentSearchesKey, recentSearchesLimit]);
const setRecentSearch = useCallback((input) => {
recentSearches.setRecentSearch(input);
}, [recentSearches]);
useEffect2(() => {
setRecentSeaches(new RecentSearches({
limit: recentSearchesLimit,
namespace: recentSearchesKey
}));
}, [recentSearchesKey, recentSearchesLimit]);
return [recentSearches?.getRecentSearches(), setRecentSearch, clearRecentSearches];
}
function getRecentSearchesKey(verticalKey) {
if (verticalKey) {
return `__yxt_recent_searches_${verticalKey}__`;
} else {
return "__yxt_recent_searches_universal__";
}
}
// src/hooks/useSearchWithNearMeHandling.ts
import { useSearchActions } from "@yext/search-headless-react";
// src/utils/search-operations.ts
import {
SearchTypeEnum
} from "@yext/search-headless-react";
async function executeSearch(searchActions) {
const isVertical = searchActions.state.meta.searchType === SearchTypeEnum.Vertical;
try {
isVertical ? searchActions.executeVerticalQuery() : searchActions.executeUniversalQuery();
} catch (e) {
console.error(`Error occured executing a ${isVertical ? "vertical" : "universal"} search.
`, e);
}
}
async function executeAutocomplete(searchActions) {
const isVertical = searchActions.state.meta.searchType === SearchTypeEnum.Vertical;
try {
return isVertical ? searchActions.executeVerticalAutocomplete() : searchActions.executeUniversalAutocomplete();
} catch (e) {
console.error(`Error occured executing a ${isVertical ? "vertical" : "universal"} autocomplete search.
`, e);
}
}
async function getSearchIntents(searchActions) {
const results = await executeAutocomplete(searchActions);
return results?.inputIntents;
}
async function executeGenerativeDirectAnswer(searchActions) {
try {
return await searchActions.executeGenerativeDirectAnswer();
} catch (e) {
console.error(`Error occured executing generative direct answer.
`, e);
}
}
// src/utils/location-operations.ts
import {
SearchIntent as SearchIntent2
} from "@yext/search-headless-react";
var defaultGeolocationOptions = {
enableHighAccuracy: false,
timeout: 6e3,
maximumAge: 3e5
};
async function updateLocationIfNeeded(searchActions, intents, geolocationOptions) {
if (intents.includes(SearchIntent2.NearMe) && !searchActions.state.location.userLocation) {
try {
const position = await getUserLocation(geolocationOptions);
searchActions.setUserLocation({
latitude: position.coords.latitude,
longitude: position.coords.longitude
});
} catch (e) {
console.error(e);
}
}
}
async function getUserLocation(geolocationOptions) {
return new Promise((resolve, reject) => {
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(
(position) => resolve(position),
(err) => {
console.error("Error occured using geolocation API. Unable to determine user's location.");
reject(err);
},
{ ...defaultGeolocationOptions, ...geolocationOptions }
);
} else {
reject("No access to geolocation API. Unable to determine user's location.");
}
});
}
// src/hooks/useSearchWithNearMeHandling.ts
import { useRef as useRef3 } from "react";
function useSearchWithNearMeHandling(geolocationOptions, onSearch) {
const autocompletePromiseRef = useRef3();
const searchActions = useSearchActions();
async function executeQuery() {
try {
let intents = [];
if (!searchActions.state.location.userLocation) {
if (!autocompletePromiseRef.current) {
autocompletePromiseRef.current = executeAutocomplete(searchActions);
}
const autocompleteResponseBeforeSearch = await autocompletePromiseRef.current;
intents = autocompleteResponseBeforeSearch?.inputIntents || [];
await updateLocationIfNeeded(searchActions, intents, geolocationOptions);
}
} catch (e) {
console.error("Error executing autocomplete before search:", e);
await updateLocationIfNeeded(searchActions, [], geolocationOptions);
}
const verticalKey = searchActions.state.vertical.verticalKey ?? "";
const query = searchActions.state.query.input ?? "";
onSearch ? onSearch({ verticalKey, query }) : executeSearch(searchActions);
}
return [executeQuery, autocompletePromiseRef];
}
// src/hooks/useSynchronizedRequest.tsx
import { useRef as useRef4, useState as useState3, useCallback as useCallback2, useEffect as useEffect3 } from "react";
function useSynchronizedRequest(executeRequest, handleRejectedPromise) {
const executeRequestRef = useRef4(executeRequest);
const handleRejectedPromiseRef = useRef4(handleRejectedPromise);
const isMountedRef = useComponentMountStatus();
const networkIds = useRef4({ latestRequest: 0, responseInState: 0 });
const [synchronizedResponse, setSynchronizedResponse] = useState3();
const executeSynchronizedRequest = useCallback2(async (data) => {
const requestId = ++networkIds.current.latestRequest;
return new Promise(async (resolve) => {
let response = void 0;
try {
response = await executeRequestRef.current(data);
} catch (e) {
handleRejectedPromiseRef.current ? handleRejectedPromiseRef.current(e) : console.error(e);
}
if (requestId >= networkIds.current.responseInState) {
if (!isMountedRef.current) {
return;
}
setSynchronizedResponse(response);
networkIds.current.responseInState = requestId;
}
resolve(response);
});
}, [isMountedRef]);
const clearResponseData = useCallback2(() => {
setSynchronizedResponse(void 0);
}, [setSynchronizedResponse]);
useEffect3(() => {
executeRequestRef.current = executeRequest;
handleRejectedPromiseRef.current = handleRejectedPromise;
});
return [synchronizedResponse, executeSynchronizedRequest, clearResponseData];
}
// src/icons/VerticalDividerIcon.tsx
import React from "react";
function VerticalDividerIcon({ className }) {
return /* @__PURE__ */ React.createElement(
"svg",
{
className,
width: "1",
height: "24",
viewBox: "0 0 1 24",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
"aria-hidden": "true"
},
/* @__PURE__ */ React.createElement("rect", { width: "1", height: "24", rx: "0.5", fill: "#E1E5E8" })
);
}
// src/icons/HistoryIcon.tsx
import React2 from "react";
function HistoryIcon() {
return /* @__PURE__ */ React2.createElement("svg", { viewBox: "0 0 14 15", fill: "currentColor", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true" }, /* @__PURE__ */ React2.createElement("path", { d: "M13.7813 7.75C13.7539 4.00391 10.7188 0.96875 7 0.96875C5.11328 0.96875 3.39063 1.76172 2.16016 2.99219L0.929688 1.76172C0.738281 1.57031 0.382813 1.70703 0.382813 2.00781L0.382813 5.45312C0.382813 5.64453 0.519531 5.78125 0.710938 5.78125L4.21094 5.78125C4.51172 5.78125 4.64844 5.42578 4.45703 5.23437L3.11719 3.92188C4.10156 2.91016 5.46875 2.28125 7 2.28125C10.0078 2.28125 12.4688 4.74219 12.4688 7.75C12.4688 10.7852 10.0078 13.2187 7 13.2188C5.57813 13.2188 4.32031 12.6992 3.33594 11.8516C3.22656 11.7422 3.00781 11.7422 2.89844 11.8516L2.43359 12.3164C2.29688 12.4531 2.29688 12.6719 2.43359 12.8086C3.63672 13.875 5.25 14.5586 7 14.5312C10.7188 14.5312 13.7813 11.4961 13.7813 7.75ZM9.1875 10.2109L9.59766 9.69141C9.67969 9.52734 9.65234 9.33594 9.51563 9.22656L7.65625 7.85937V3.92187C7.65625 3.75781 7.49219 3.59375 7.32813 3.59375H6.67188C6.48047 3.59375 6.34375 3.75781 6.34375 3.92187V8.54297L8.75 10.293C8.88672 10.4023 9.10547 10.375 9.1875 10.2109Z" }));
}
// src/icons/CloseIcon.tsx
import React3 from "react";
function CloseIcon() {
return /* @__PURE__ */ React3.createElement("svg", { viewBox: "0 0 18 18", xmlns: "http://www.w3.org/2000/svg", fill: "currentColor", "aria-hidden": "true" }, /* @__PURE__ */ React3.createElement("path", { d: "M10.9095 9.00028L16.6786 3.2311L17.8684 2.04138C18.0439 1.86587 18.0439 1.58067 17.8684 1.40517L16.5954 0.132192C16.4199 -0.0433137 16.1347 -0.0433137 15.9592 0.132192L9.00028 7.0911L2.04138 0.131629C1.86587 -0.0438764 1.58067 -0.0438764 1.40517 0.131629L0.131629 1.40461C-0.0438764 1.58011 -0.0438764 1.86531 0.131629 2.04081L7.0911 9.00028L0.131629 15.9592C-0.0438764 16.1347 -0.0438764 16.4199 0.131629 16.5954L1.40461 17.8684C1.58011 18.0439 1.86531 18.0439 2.04081 17.8684L9.00028 10.9095L14.7695 16.6786L15.9592 17.8684C16.1347 18.0439 16.4199 18.0439 16.5954 17.8684L17.8684 16.5954C18.0439 16.4199 18.0439 16.1347 17.8684 15.9592L10.9095 9.00028Z", fill: "#6b7280" }));
}
// src/icons/MagnifyingGlassIcon.tsx
import React4 from "react";
function MagnifyingGlassIcon() {
return /* @__PURE__ */ React4.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true" }, /* @__PURE__ */ React4.createElement("path", { d: "M0 0h24v24H0V0z", fill: "none" }), /* @__PURE__ */ React4.createElement(
"path",
{
d: "M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
}
));
}
// src/components/Dropdown/Dropdown.tsx
import React8, {
createElement,
isValidElement as isValidElement2,
useEffect as useEffect5,
useMemo,
useRef as useRef5,
useState as useState5
} from "react";
// src/components/Dropdown/DropdownContext.ts
import { createContext, useContext } from "react";
var DropdownContext = createContext(null);
function useDropdownContext() {
const dropdownContextInstance = useContext(DropdownContext);
if (dropdownContextInstance === null) {
throw new Error("Tried to use DropdownContext when none exists.");
}
return dropdownContextInstance;
}
// src/components/Dropdown/InputContext.ts
import { useContext as useContext2, createContext as createContext2 } from "react";
var InputContext = createContext2(null);
function useInputContext() {
const inputContextInstance = useContext2(InputContext);
if (inputContextInstance === null) {
throw new Error("Tried to use InputContext when none exists.");
}
return inputContextInstance;
}
// src/components/Dropdown/Dropdown.tsx
import useRootClosePkg from "@restart/ui/useRootClose";
// src/components/Dropdown/FocusContext.ts
import { createContext as createContext3, useContext as useContext3 } from "react";
var FocusContext = createContext3(null);
function useFocusContext() {
const focusContextInstance = useContext3(FocusContext);
if (focusContextInstance === null) {
throw new Error("Tried to use FocusContext when none exists.");
}
return focusContextInstance;
}
// src/components/ScreenReader.tsx
import React5 from "react";
function ScreenReader({
instructionsId,
instructions,
announcementKey,
announcementText
}) {
return /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(
"div",
{
id: instructionsId,
className: "hidden"
},
instructions
), /* @__PURE__ */ React5.createElement(
"div",
{
className: "sr-only",
key: announcementKey,
"aria-live": "assertive"
},
announcementText
));
}
// src/components/utils/recursivelyMapChildren.ts
import { Children, cloneElement, isValidElement } from "react";
function recursivelyMapChildren(children, elementReplacer) {
return Children.map(children, (c, index) => {
if (!isValidElement(c)) {
return c;
}
const replacedElement = elementReplacer(c, index);
if (!replacedElement || !isValidElement(replacedElement)) {
return replacedElement;
}
const grandchildren = replacedElement.props.children;
if (!grandchildren) {
return replacedElement;
}
const replacedGrandchildren = recursivelyMapChildren(grandchildren, elementReplacer);
return cloneElement(replacedElement, {}, [replacedGrandchildren]);
});
}
// src/components/Dropdown/DropdownItem.tsx
import React6, { useCallback as useCallback3 } from "react";
// src/components/Dropdown/generateDropdownId.ts
function generateDropdownId(screenReaderUUID, index) {
if (!screenReaderUUID) return "";
return screenReaderUUID + "_" + index;
}
// src/components/Dropdown/DropdownItem.tsx
function DropdownItem(_props) {
return null;
}
function DropdownItemWithIndex(props) {
const {
children,
value,
index,
className,
focusedClassName,
itemData,
onClick,
ariaLabel
} = props;
const { toggleDropdown, onSelect, screenReaderUUID } = useDropdownContext();
const { focusedIndex, updateFocusedItem } = useFocusContext();
const { setValue, setLastTypedOrSubmittedValue } = useInputContext();
const isFocused = focusedIndex === index;
const handleClick = useCallback3(() => {
toggleDropdown(false);
updateFocusedItem(-1);
setLastTypedOrSubmittedValue(value);
setValue(value);
onSelect?.(value, index, itemData);
onClick?.(value, index, itemData);
}, [
index,
itemData,
onClick,
onSelect,
setLastTypedOrSubmittedValue,
setValue,
toggleDropdown,
updateFocusedItem,
value
]);
return /* @__PURE__ */ React6.createElement(
"div",
{
id: generateDropdownId(screenReaderUUID, index),
tabIndex: 0,
className: isFocused ? focusedClassName : className,
onClick: handleClick,
"aria-label": typeof ariaLabel === "function" ? ariaLabel(value) : ariaLabel
},
children
);
}
// src/hooks/useLayoutEffect.ts
import useIsomorphicLayoutEffect from "use-isomorphic-layout-effect";
var useLayoutEffect = typeof useIsomorphicLayoutEffect === "function" ? useIsomorphicLayoutEffect : useIsomorphicLayoutEffect["default"];
// src/hooks/useId.ts
import React7, { useEffect as useEffect4, useState as useState4 } from "react";
var serverHandoffComplete = false;
var id = 0;
function genId(baseName) {
++id;
return baseName + "-" + id.toString();
}
var maybeReactUseId = React7["useId".toString()];
function useId(baseName) {
if (maybeReactUseId !== void 0) {
return maybeReactUseId();
}
const initialId = serverHandoffComplete ? genId(baseName) : "";
const [id2, setId] = useState4(initialId);
useLayoutEffect(() => {
if (id2 === "") {
setId(genId(baseName));
}
}, [id2]);
useEffect4(() => {
if (serverHandoffComplete === false) {
serverHandoffComplete = true;
}
}, []);
return id2;
}
// src/components/Dropdown/Dropdown.tsx
var useRootClose = typeof useRootClosePkg === "function" ? useRootClosePkg : useRootClosePkg["default"];
function Dropdown(props) {
const {
children,
screenReaderText,
screenReaderInstructions = "When autocomplete results are available, use up and down arrows to review and enter to select.",
onSelect,
onToggle,
className,
activeClassName,
parentQuery,
alwaysSelectOption = false
} = props;
const containerRef = useRef5(null);
const screenReaderUUID = useId("dropdown");
const [screenReaderKey, setScreenReaderKey] = useState5(0);
const [hasTyped, setHasTyped] = useState5(false);
const [childrenWithDropdownItemsTransformed, items] = useMemo(() => {
return getTransformedChildrenAndItemData(children);
}, [children]);
const inputContext = useInputContextInstance();
const { value, setValue, lastTypedOrSubmittedValue, setLastTypedOrSubmittedValue } = inputContext;
const focusContext = useFocusContextInstance(
items,
lastTypedOrSubmittedValue,
setValue,
screenReaderKey,
setScreenReaderKey,
alwaysSelectOption
);
const { focusedIndex, focusedItemData, updateFocusedItem } = focusContext;
const dropdownContext = useDropdownContextInstance(
lastTypedOrSubmittedValue,
value,
focusedIndex,
focusedItemData,
screenReaderUUID,
setHasTyped,
onToggle,
onSelect
);
const { toggleDropdown, isActive } = dropdownContext;
useLayoutEffect(() => {
if (parentQuery !== void 0 && parentQuery !== lastTypedOrSubmittedValue) {
setLastTypedOrSubmittedValue(parentQuery);
updateFocusedItem(-1, parentQuery);
}
}, [
parentQuery,
lastTypedOrSubmittedValue,
updateFocusedItem,
setLastTypedOrSubmittedValue
]);
useRootClose(containerRef, () => {
toggleDropdown(false);
}, { disabled: !isActive });
function handleKeyDown(e) {
if (!isActive) {
return;
}
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
e.preventDefault();
}
if (e.key === "ArrowDown") {
if (alwaysSelectOption && focusedIndex === items.length - 1) {
updateFocusedItem(0);
} else {
updateFocusedItem(focusedIndex + 1);
}
} else if (e.key === "ArrowUp") {
if (alwaysSelectOption && focusedIndex === 0) {
updateFocusedItem(items.length - 1);
} else {
updateFocusedItem(focusedIndex - 1);
}
} else if (e.key === "Tab" && !e.shiftKey) {
if (items.length !== 0) {
if (focusedIndex >= items.length - 1) {
updateFocusedItem(-1);
toggleDropdown(false);
} else {
updateFocusedItem(focusedIndex + 1);
e.preventDefault();
}
}
} else if (e.key === "Tab" && e.shiftKey) {
if (focusedIndex > 0 || !alwaysSelectOption && focusedIndex === 0) {
updateFocusedItem(focusedIndex - 1);
e.preventDefault();
} else {
updateFocusedItem(-1);
toggleDropdown(false);
}
} else if (!hasTyped) {
setHasTyped(true);
}
}
return /* @__PURE__ */ React8.createElement("div", { ref: containerRef, className: isActive ? activeClassName : className, onKeyDown: handleKeyDown }, /* @__PURE__ */ React8.createElement(DropdownContext.Provider, { value: dropdownContext }, /* @__PURE__ */ React8.createElement(InputContext.Provider, { value: inputContext }, /* @__PURE__ */ React8.createElement(FocusContext.Provider, { value: focusContext }, childrenWithDropdownItemsTransformed))), /* @__PURE__ */ React8.createElement(
ScreenReader,
{
announcementKey: screenReaderKey,
announcementText: isActive && (hasTyped || items.length || value) ? screenReaderText : "",
instructionsId: screenReaderUUID,
instructions: screenReaderInstructions
}
));
}
function useInputContextInstance() {
const [value, setValue] = useState5("");
const [lastTypedOrSubmittedValue, setLastTypedOrSubmittedValue] = useState5("");
return {
value,
setValue,
lastTypedOrSubmittedValue,
setLastTypedOrSubmittedValue
};
}
function useFocusContextInstance(items, lastTypedOrSubmittedValue, setValue, screenReaderKey, setScreenReaderKey, alwaysSelectOption) {
const [focusedIndex, setFocusedIndex] = useState5(-1);
const [focusedValue, setFocusedValue] = useState5(null);
const [focusedItemData, setFocusedItemData] = useState5(void 0);
useEffect5(() => {
if (alwaysSelectOption) {
if (items.length > 0) {
const index = focusedIndex === -1 || focusedIndex >= items.length ? 0 : focusedIndex;
setFocusedIndex(index);
setFocusedValue(items[index].value);
setFocusedItemData(items[index].itemData);
} else {
setFocusedIndex(-1);
setFocusedValue(null);
setFocusedItemData(void 0);
}
}
}, [alwaysSelectOption, focusedIndex, items]);
function updateFocusedItem(updatedFocusedIndex, value) {
const numItems = items.length;
let updatedValue;
if (updatedFocusedIndex === -1 || updatedFocusedIndex >= numItems || numItems === 0) {
updatedValue = value ?? lastTypedOrSubmittedValue;
if (alwaysSelectOption && numItems !== 0) {
setFocusedIndex(0);
setFocusedItemData(items[0].itemData);
setScreenReaderKey(screenReaderKey + 1);
} else {
setFocusedIndex(-1);
setFocusedItemData(void 0);
setScreenReaderKey(screenReaderKey + 1);
}
} else if (updatedFocusedIndex < -1) {
const loopedAroundIndex = (numItems + updatedFocusedIndex + 1) % numItems;
updatedValue = value ?? items[loopedAroundIndex].value;
setFocusedIndex(loopedAroundIndex);
setFocusedItemData(items[loopedAroundIndex].itemData);
} else {
updatedValue = value ?? items[updatedFocusedIndex].value;
setFocusedIndex(updatedFocusedIndex);
setFocusedItemData(items[updatedFocusedIndex].itemData);
}
setFocusedValue(updatedValue);
setValue(alwaysSelectOption ? value ?? lastTypedOrSubmittedValue : updatedValue);
}
return {
focusedIndex,
focusedValue,
focusedItemData,
updateFocusedItem
};
}
function useDropdownContextInstance(prevValue, value, index, focusedItemData, screenReaderUUID, setHasTyped, onToggle, onSelect) {
const [isActive, _toggleDropdown] = useState5(false);
const toggleDropdown = (willBeOpen) => {
if (!willBeOpen) {
setHasTyped(false);
}
_toggleDropdown(willBeOpen);
onToggle?.(willBeOpen, prevValue, value, index, focusedItemData);
};
return {
isActive,
toggleDropdown,
onSelect,
screenReaderUUID
};
}
function getTransformedChildrenAndItemData(children) {
const items = [];
const childrenWithDropdownItemsTransformed = recursivelyMapChildren(children, (child) => {
if (!(isValidElement2(child) && child.type === DropdownItem)) {
return child;
}
const props = child.props;
items.push({
value: props.value,
itemData: props.itemData
});
return createElement(DropdownItemWithIndex, { ...props, index: items.length - 1 });
});
return [childrenWithDropdownItemsTransformed, items];
}
// src/components/Dropdown/DropdownInput.tsx
import React9, { useCallback as useCallback4, useRef as useRef6, useState as useState6 } from "react";
function DropdownInput(props) {
const {
className,
placeholder,
ariaLabel,
onSubmit,
onFocus,
onChange,
submitCriteria
} = props;
const inputRef = useRef6(null);
const { toggleDropdown, onSelect, screenReaderUUID } = useDropdownContext();
const { value = "", setLastTypedOrSubmittedValue } = useInputContext();
const {
focusedIndex = -1,
focusedItemData,
focusedValue,
updateFocusedItem
} = useFocusContext();
const [isTyping, setIsTyping] = useState6(true);
const handleChange = useCallback4((e) => {
setIsTyping(true);
toggleDropdown(true);
onChange?.(e.target.value);
updateFocusedItem(-1, e.target.value);
setLastTypedOrSubmittedValue(e.target.value);
}, [onChange, setLastTypedOrSubmittedValue, toggleDropdown, updateFocusedItem]);
const handleKeyDown = useCallback4((e) => {
if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Tab") {
setIsTyping(false);
}
if (e.key === "Enter" && (!submitCriteria || submitCriteria(focusedIndex))) {
updateFocusedItem(focusedIndex);
toggleDropdown(false);
inputRef.current?.blur();
onSubmit?.(value, focusedIndex, focusedItemData);
if (focusedIndex >= 0) {
onSelect?.(value, focusedIndex, focusedItemData);
}
updateFocusedItem(-1, focusedValue ?? void 0);
}
}, [
focusedIndex,
focusedValue,
focusedItemData,
onSelect,
onSubmit,
submitCriteria,
toggleDropdown,
updateFocusedItem,
value
]);
const handleFocus = useCallback4(() => {
toggleDropdown(true);
updateFocusedItem(-1);
onFocus?.(value);
}, [onFocus, toggleDropdown, updateFocusedItem, value]);
return /* @__PURE__ */ React9.createElement(
"input",
{
ref: inputRef,
className,
placeholder,
value,
onChange: handleChange,
onKeyDown: handleKeyDown,
onFocus: handleFocus,
id: generateDropdownId(screenReaderUUID, -1),
autoComplete: "off",
"aria-describedby": screenReaderUUID,
"aria-activedescendant": isTyping ? "" : generateDropdownId(screenReaderUUID, focusedIndex),
"aria-label": ariaLabel
}
);
}
// src/components/Dropdown/DropdownMenu.tsx
import React10 from "react";
function DropdownMenu({ children }) {
const { isActive } = useDropdownContext();
if (!isActive) {
return null;
}
return /* @__PURE__ */ React10.createElement(React10.Fragment, null, children);
}
// src/hooks/useComposedCssClasses.tsx
import { useMemo as useMemo2 } from "react";
import { extendTailwindMerge } from "tailwind-merge";
var twMerge = extendTailwindMerge({
classGroups: {
form: ["input", "checkbox", "textarea", "select", "multiselect", "radio"].map((v) => "form-" + v)
}
});
function useComposedCssClasses(builtInClasses, customClasses, disableBuiltInClasses = false) {
return useMemo2(() => {
if (disableBuiltInClasses && customClasses) {
return customClasses;
}
const mergedCssClasses = { ...builtInClasses };
if (!customClasses) {
return mergedCssClasses;
}
Object.keys(customClasses).forEach((key) => {
const builtIn = builtInClasses[key];
const custom = customClasses[key];
if (!builtIn || !custom) {
mergedCssClasses[key] = custom || builtIn;
} else {
mergedCssClasses[key] = twMerge(builtIn, custom);
}
});
return mergedCssClasses;
}, [builtInClasses, customClasses, disableBuiltInClasses]);
}
// src/components/SearchButton.tsx
import React11 from "react";
function SearchButton({ handleClick, className }) {
return /* @__PURE__ */ React11.createElement(
"button",
{
className,
onClick: handleClick,
"aria-label": "Submit Search"
},
/* @__PURE__ */ React11.createElement(MagnifyingGlassIcon, null)
);
}
// src/components/utils/processTranslation.ts
function processTranslation(args) {
if (args.count != null && args.pluralForm && args.count !== 1) {
return args.pluralForm;
} else {
return args.phrase;
}
}
// src/components/utils/renderHighlightedValue.tsx
import React12 from "react";
var defaultCssClasses = {
highlighted: "font-normal",
nonHighlighted: "font-semibold"
};
function renderHighlightedValue(highlightedValueOrString, customCssClasses) {
const { value = "", matchedSubstrings } = typeof highlightedValueOrString === "string" ? { value: highlightedValueOrString, matchedSubstrings: [] } : highlightedValueOrString;
const cssClasses = { ...defaultCssClasses, ...customCssClasses };
if (!matchedSubstrings || matchedSubstrings.length === 0) {
return /* @__PURE__ */ React12.createElement("span", null, value);
}
const substrings = [...matchedSubstrings];
substrings.sort((a, b) => a.offset - b.offset);
const highlightedJSX = [];
let curr = 0;
for (const { offset, length } of substrings) {
if (offset > curr) {
highlightedJSX.push(
/* @__PURE__ */ React12.createElement("span", { key: curr, className: cssClasses.nonHighlighted }, value.substring(curr, offset))
);
}
highlightedJSX.push(
/* @__PURE__ */ React12.createElement("span", { key: offset, className: cssClasses.highlighted }, value.substring(offset, offset + length))
);
curr = offset + length;
}
if (curr < value.length) {
highlightedJSX.push(
/* @__PURE__ */ React12.createElement("span", { key: curr, className: cssClasses.nonHighlighted }, value.substring(curr))
);
}
return /* @__PURE__ */ React12.createElement(React12.Fragment, null, highlightedJSX);
}
// src/components/utils/renderAutocompleteResult.tsx
import React13 from "react";
var builtInCssClasses = {
option: "whitespace-no-wrap max-w-full px-3 text-neutral-dark truncate",
icon: "w-6 h-full flex-shrink-0 text-gray-400"
};
function renderAutocompleteResult(result, cssClasses = {}, Icon, ariaLabel) {
return /* @__PURE__ */ React13.createElement(React13.Fragment, null, Icon && /* @__PURE__ */ React13.createElement("div", { className: cssClasses.icon }, /* @__PURE__ */ React13.createElement(Icon, null)), /* @__PURE__ */ React13.createElement("div", { "aria-label": ariaLabel || "", className: cssClasses.option }, renderHighlightedValue(result, cssClasses)));
}
// src/hooks/useAnalytics.ts
import { createContext as createContext4, useContext as useContext4 } from "react";
var AnalyticsContext = createContext4(null);
function useAnalytics() {
return useContext4(AnalyticsContext);
}
// src/hooks/useSearchBarAnalytics.ts
import { useSearchState } from "@yext/search-headless-react";
function useSearchBarAnalytics() {
const analytics = useAnalytics();
const verticalKey = useSearchState((state) => state.vertical.verticalKey);
const queryId = useSearchState((state) => state.query.queryId);
const reportAutocompleteEvent = (suggestedSearchText) => {
analytics?.report({
type: "AUTO_COMPLETE_SELECTION",
...queryId && { queryId },
suggestedSearchText
});
};
const reportSearchClearEvent = () => {
if (!queryId) {
console.error("Unable to report a search clear event. Missing field: queryId.");
return;
}
analytics?.report({
type: "SEARCH_CLEAR_BUTTON",
queryId,
verticalKey
});
};
const reportAnalyticsEvent = (analyticsEventType, suggestedSearchText) => {
if (!analytics) {
return;
}
analyticsEventType === "AUTO_COMPLETE_SELECTION" ? reportAutocompleteEvent(suggestedSearchText || "") : reportSearchClearEvent();
};
return reportAnalyticsEvent;
}
// src/models/verticalLink.ts
var isVerticalLink = (obj) => {
return typeof obj === "object" && !!obj && "verticalKey" in obj;
};
// src/utils/filterutils.tsx
import { Matcher as Matcher2 } from "@yext/search-headless-react";
import isEqual from "lodash/isEqual.js";
// src/models/NumberRangeFilter.ts
import { Matcher } from "@yext/search-headless-react";
function isNumberRangeFilter(unknownFilter = {}) {
const filter = unknownFilter;
return filter.matcher === Matcher.Between && isNumberRangeValue(filter.value);
}
// src/utils/filterutils.tsx
function isNearFilterValue(obj) {
return typeof obj === "object" && !!obj && "radius" in obj && "lat" in obj && "long" in obj;
}
function isNumberRangeValue(obj) {
return typeof obj === "object" && !!obj && ("start" in obj || "end" in obj);
}
function isStringFacet(facet) {
return facet.options.length > 0 && typeof facet.options[0].value === "string";
}
function isNumericalFacet(facet) {
return facet.options.length > 0 && facet.options.some((option) => isNumberRangeFilter(option));
}
function isDuplicateFieldValueFilter(thisFilter, otherFilter) {
if (thisFilter.fieldId !== otherFilter.fieldId) {
return false;
}
if (thisFilter.matcher !== otherFilter.matcher) {
return false;
}
return isEqual(thisFilter.value, otherFilter.value);
}
function isDuplicateStaticFilter(thisFilter, otherFilter) {
if (thisFilter.kind === "fieldValue") {
return otherFilter.kind === "fieldValue" ? isDuplicateFieldValueFilter(thisFilter, otherFilter) : false;
}
if (otherFilter.kind === "fieldValue") {
return false;
}
return thisFilter.combinator === otherFilter.combinator && thisFilter.filters.length === otherFilter.filters.length && thisFilter.filters.every((t) => otherFilter.filters.some((o) => isDuplicateStaticFilter(t, o))) && otherFilter.filters.every((o) => thisFilter.filters.some((t) => isDuplicateStaticFilter(o, t)));
}
function findSelectableFieldValueFilter(filter, selectableFilters) {
return selectableFilters.find((selectableFilter) => {
const { displayName: _2, ...storedFilter } = selectableFilter;
return isDuplicateFieldValueFilter(storedFilter, filter);
});
}
function parseNumberRangeInput(minRangeInput, maxRangeInput) {
const minRange = parseNumber(minRangeInput);
const maxRange = parseNumber(maxRangeInput);
return {
...minRange !== void 0 && {
start: {
matcher: Matcher2.GreaterThanOrEqualTo,
value: minRange
}
},
...maxRange !== void 0 && {
end: {
matcher: Matcher2.LessThanOrEqualTo,
value: maxRange
}
}
};
}
function parseNumber(num) {
const parsedNum = parseFloat(num);
if (isNaN(parsedNum)) {
return void 0;
}
return parsedNum;
}
function clearStaticRangeFilters(searchActions, fieldIds) {
const selectedStaticRangeFilters = searchActions.state?.filters?.static?.filter(
(filter) => isNumberRangeFilter(filter) && filter.selected === true && (!fieldIds || fieldIds.has(filter.fieldId))
);
selectedStaticRangeFilters?.forEach((filter) => {
searchActions.setFilterOption({
...filter,
selected: false
});
});
}
function getSelectedNumericalFacetFields(searchActions) {
const selectedNumericalFacets = searchActions.state.filters.facets?.filter(
(f) => isNumericalFacet(f) && f.options.some((o) => o.selected)
) ?? [];
return new Set(selectedNumericalFacets.map((f) => f.fieldId));
}
function getSelectableFieldValueFilters(staticFilters) {
return staticFilters.map((s) => {
const { filter: { kind, ...filterFields }, ...displayFields } = s;
if (kind === "fieldValue") {
return {
...displayFields,
...filterFields
};
}
return void 0;
}).filter((s) => !!s);
}
function getDefaultFilterDisplayName(numberRange) {
const start = numberRange.start;
const end = numberRange.end;
if (start && end) {
return `${start.value} - ${end.value}`;
} else if (start && !end) {
return `Over ${start.value}`;
} else if (end && !start) {
return `Up to ${end.value}`;
}
return "";
}
// src/components/SearchBar.tsx
var builtInCssClasses2 = {
searchBarContainer: "h-12 mb-6",
inputDivider: "border-t border-gray-200 mx-2.5",
inputElement: "outline-none flex-grow border-none h-11 pl-5 pr-2 text-neutral-dark text-base placeholder:text-neutral-light",
searchButtonContainer: " w-8 h-full mx-2 flex flex-col justify-center items-center",
searchButton: "h-7 w-7",
focusedOption: "bg-gray-100",
clearButton: "h-3 w-3 mr-3.5",
verticalDivider: "mr-0.5",
recentSearchesIcon: "w-5 mr-1 flex-shrink-0 h-full text-gray-400",
recentSearchesOption: "whitespace-no-wrap max-w-full px-3 text-neutral-dark truncate",
recentSearchesNonHighlighted: "font-normal",
// Swap this to semibold once we apply highlighting to recent searches
verticalLink: "ml-12 pl-1 text-neutral italic",
entityPreviewsDivider: "h-px bg-gray-200 mt-1 mb-4 mx-3.5",
...builtInCssClasses
};
function SearchBar({
placeholder,
geolocationOptions,
hideRecentSearches,
visualAutocompleteConfig,
showVerticalLinks = false,
onSelectVerticalLink,
verticalKeyToLabel,
recentSearchesLimit = 5,
customCssClasses,
onSearch
}) {
const {
entityPreviewSearcher,
renderEntityPreviews,
includedVerticals,
universalLimit,
entityPreviewsDebouncingTime = 500
} = visualAutocompleteConfig ?? {};
const searchActions = useSearchActions2();
const searchUtilities = useSearchUtilities();
const reportAnalyticsEvent = useSearchBarAnalytics();
const query = useSearchState2((state) => state.query.input) ?? "";
const cssClasses = useComposedCssClasses(builtInCssClasses2, customCssClasses);
const isVertical = useSearchState2((state) => state.meta.searchType) === SearchTypeEnum2.Vertical;
const verticalKey = useSearchState2((state) => state.vertical.verticalKey);
const debouncedExecuteAutocompleteSearch = useDebouncedFunction(() => executeAutocomplete(searchActions), 200);
const [autocompleteResponse, executeAutocomplete2, clearAutocompleteData] = useSynchronizedRequest(
async () => {
return debouncedExecuteAutocompleteSearch ? debouncedExecuteAutocompleteSearch() : void 0;
}
);
const [
executeQueryWithNearMeHandling,
autocompletePromiseRef
] = useSearchWithNearMeHandling(geolocationOptions, onSearch);
const [
recentSearches,
setRecentSearch,
clearRecentSearches
] = useRecentSearches(recentSearchesLimit, verticalKey);
const filteredRecentSearches = recentSearches?.filter(
(search) => searchUtilities.isCloseMatch(search.query, query)
);
useEffect6(() => {
if (hideRecentSearches) {
clearRecentSearches();
}
}, [clearRecentSearches, hideRecentSearches]);
const clearAutocomplete = useCallback5(() => {
clearAutocompleteData();
autocompletePromiseRef.current = void 0;
}, [autocompletePromiseRef, clearAutocompleteData]);
const executeQuery = useCallback5(() => {
if (!hideRecentSearches) {
const input = searchActions.state.query.input;
input && setRecentSearch(input);
}
executeQueryWithNearMeHandling();
}, [
searchActions.state.query.input,
executeQueryWithNearMeHandling,
hideRecentSearches,
setRecentSearch
]);
const handleSubmit = useCallback5((value, index, itemData) => {
value !== void 0 && searchActions.setQuery(value);
searchActions.setOffset(0);
searchActions.setFacets([]);
clearStaticRangeFilters(searchActions);
if (itemData && isVerticalLink(itemData.verticalLink) && onSelectVerticalLink) {
onSelectVerticalLink({ verticalLink: itemData.verticalLink, querySource: QuerySource.Autocomplete });
} else {
executeQuery();
}
if (typeof index === "number" && index >= 0 && !itemData?.isEntityPreview) {
reportAnalyticsEvent("AUTO_COMPLETE_SELECTION", value);
}
}, [searchActions, executeQuery, onSelectVerticalLink, reportAnalyticsEvent]);
const [
entityPreviewsState,
executeEntityPreviewsQuery
] = useEntityPreviews(entityPreviewSearcher, entityPreviewsDebouncingTime);
const { verticalKeyToResults, isLoading: entityPreviewsLoading } = entityPreviewsState;
const entityPreviews = renderEntityPreviews?.(
entityPreviewsLoading,
verticalKeyToResults,
{ onClick: handleSubmit, ariaLabel: getAriaLabel }
);
const updateEntityPreviews = useCallback5((query2) => {
if (!renderEntityPreviews || !includedVerticals) {
return;
}
executeEntityPreviewsQuery(query2, universalLimit ?? {}, includedVerticals);
}, [executeEntityPreviewsQuery, renderEntityPreviews, includedVerticals, universalLimit]);
const handleInputFocus = useCallback5((value = "") => {
searchActions.setQuery(value);
updateEntityPreviews(value);
autocompletePromiseRef.current = executeAutocomplete2();
}, [searchActions, autocompletePromiseRef, executeAutocomplete2, updateEntityPreviews]);
const handleInputChange = useCallback5((value = "") => {
searchActions.setQuery(value);
updateEntityPreviews(value);
autocompletePromiseRef.current = executeAutocomplete2();
}, [searchActions, autocompletePromiseRef, executeAutocomplete2, updateEntityPreviews]);
const handleClickClearButton = useCallback5(() => {
updateEntityPreviews("");
searchActions.setQuery("");
reportAnalyticsEvent("SEARCH_CLEAR_BUTTON");
}, [handleSubmit, reportAnalyticsEvent, updateEntityPreviews]);
function renderInput() {
return /* @__PURE__ */ React14.createElement(
DropdownInput,
{
className: cssClasses.inputElement,
placeholder,
onSubmit: handleSubmit,
onFocus: handleInputFocus,
onChange: handleInputChange,
ariaLabel: "Conduct a search"
}
);
}
function renderRecentSearches() {
const recentSearchesCssClasses = {
icon: cssClasses.recentSearchesIcon,
option: cssClasses.recentSearchesOption,
nonHighlighted: cssClasses.recentSearchesNonHighlighted
};
return filteredRecentSearches?.map((result, i) => /* @__PURE__ */ React14.createElement(
DropdownItem,
{
className: "flex items-center h-6.5 px-3.5 py-1.5 cursor-pointer hover:bg-gray-100",
focusedClassName: twMerge("flex items-center h-6.5 px-3.5 py-1.5 cursor-pointer hover:bg-gray-100", cssClasses.focusedOption),
key: i,
value: result.query,
onClick: handleSubmit
},
renderAutocompleteResult(
{ value: result.query, inputIntents: [] },
recentSearchesCssClasses,
HistoryIcon,
`recent search: ${result.query}`
)
));
}
const itemDataMatrix = useMemo3(() => {
return autocompleteResponse?.results.map((result) => {
return result.verticalKeys?.map((verticalKey2) => ({
verticalLink: { verticalKey: verticalKey2, query: result.value }
})) ?? [];
}) ?? [];
}, [autocompleteResponse?.results]);
function renderQuerySuggestions() {
return autocompleteResponse?.results.map((result, i) => /* @__PURE__ */ React14.createElement(Fragment, { key: i }, /* @__PURE__ */ React14.createElement(
DropdownItem,
{
className: "flex items-stretch py-1.5 px-3.5 cursor-pointer hover:bg-gray-100",
focusedClassName: twMerge("flex items-stretch py-1.5 px-3.5 cursor-pointer hover:bg-gray-100", cssClasses.focusedOption),
value: result.value,
onClick: handleSubmit
},
renderAutocompleteResult(
result,
cssClasses,
MagnifyingGlassIcon,
`autocomplete suggestion: ${result.value}`
)
), showVerticalLinks && !isVertical && result.verticalKeys?.map((verticalKey2, j) => /* @__PURE__ */ React14.createElement(
DropdownItem,
{
key: j,
className: "flex items-stretch py-1.5 px-3.5 cursor-pointer hover:bg-gray-100",
focusedClassName: twMerge("flex items-stretch py-1.5 px-3.5 cursor-pointer hover:bg-gray-100", cssClasses.focusedOption),
value: result.value,
itemData: itemDataMatrix[i][j],
onClick: handleSubmit
},
renderAutocompleteResult(
{
value: `in ${verticalKeyToLabel ? verticalKeyToLabel(verticalKey2) : verticalKey2}`,
inputIntents: []
},
{ ...cssClasses, option: cssClasses.verticalLink }
)
))));
}
function renderClearButton() {
return /* @__PURE__ */ React14.createElement(React14.Fragment, null, /* @__PURE__ */ React14.createElement(
"button",
{
"aria-label": "Clear the search bar",
className: cssClasses.clearButton,
onClick: handleClickClearButton
},
/* @__PURE__ */ React14.createElement(CloseIcon, null)
), /* @__PURE__ */ React14.createElement(VerticalDividerIcon, { className: cssClasses.verticalDivider }));
}
const entityPreviewsCount = calculateEntityPreviewsCount(entityPreviews);
const showEntityPreviewsDivider = entityPreviews && !!(autocompleteResponse?.results.length || filteredRecentSearches?.length);
const hasItems = !!(autocompleteResponse?.results.length || filteredRecentSearches?.length || entityPreviews);
const screenReaderText = getScreenReaderText(
autocompleteResponse?.results.length,
filteredRecentSearches?.length,
entityPreviewsCount
);
const activeClassName = classNames("relative z-10 bg-white border rounded-3xl border-gray-200 w-full overflow-hidden", {
["shadow-lg"]: hasItems
});
const handleToggleDropdown = useCallback5((isActive) => {
if (!isActive) {
clearAutocomplete();
}
}, [clearAutocomplete]);
return /* @__PURE__ */ React14.createElement("div", { className: cssClasses.searchBarContainer }, /* @__PURE__ */ React14.createElement(
Dropdown,
{
className: "relative bg-white border rounded-3xl border-gray-200 w-full overflow-hidden",
activeClassName,
screenReaderText,
parentQuery: query,
onToggle: handleToggleDropdown
},
/* @__PURE__ */ React14.createElement("div", { className: "inline-flex items-center justify-between w-full" }, renderInput(), query && renderClearButton(), /* @__PURE__ */ React14.createElement(
DropdownSearchButton,
{
handleSubmit,
cssClasses
}
)),
hasItems && /* @__PURE__ */ React14.createElement(StyledDropdownMenu, { cssClasses }, renderRecentSearches(), renderQuerySuggestions(), showEntityPreviewsDivider && /* @__PURE__ */ React14.createElement("div", { className: cssClasses.entityPreviewsDivider }), entityPreviews)
));
}
function StyledDropdownMenu({ cssClasses, children }) {
return /* @__PURE__ */ React14.createElement(DropdownMenu, null, /* @__PURE__ */ React14.createElement("div", { className: cssClasses.inputDivider }), /* @__PURE__ */ React14.createElement("div", { className: "bg-white py-4" }, children));
}
function getScreenReaderText(autocompleteOptions = 0, recentSearchesOptions = 0, entityPreviewsCount = 0) {
const recentSearchesText = recentSearchesOptions > 0 ? processTranslation({
phrase: `${recentSearchesOptions} recent search found.`,
pluralForm: `${recentSearchesOptions} recent searches found.`,
count: recentSearchesOptions
}) : "";
const entityPreviewsText = entityPreviewsCount > 0 ? " " + processTranslation({
phrase: `${entityPreviewsCount} result preview found.`,
pluralForm: `${entityPreviewsCount} result previews found.`,
count: entityPreviewsCount
}) : "";
const autocompleteText = autocompleteOptions > 0 ? " " + processTranslation({
phrase: `${autocompleteOptions} autocomplete suggestion found.`,
pluralForm: `${autocompleteOptions} autocomplete suggestions found.`,
count: autocompleteOptions
}) : "";
const text = recentSearchesText + autocompleteText + entityPreviewsText;
if (text === "") {
return processTranslation({
phrase: "0 autocomplete suggestion found.",
pluralForm: "0 autocomplete suggestions found.",
count: 0
});
}
return text.trim();
}
function DropdownSearchButton({ handleSubmit, cssClasses }) {
const { toggleDropdown } = useDropdownContext();
const handleClick = useCallback5(() => {
handleSubmit();
toggleDropdown(false);
}, [handleSubmit, toggleDropdown]);
return /* @__PURE__ */ React14.createElement("div", { className: cssClasses.searchButtonContainer }, /* @__PURE__ */ React14.createElement(
SearchButton,
{
className: cssClasses.searchButton,
handleClick
}
));
}
function getAriaLabel(value) {
return "result preview: " + value;
}
function calculateEntityPreviewsCount(children) {
let count = 0;
recursivelyMapChildren(children, (c) => {
if (isValidElement3(c) && c.type === DropdownItem) {
count++;
}
return c;
});
return count;
}
// src/components/SpellCheck.tsx
import { useSearchState as useSearchState4, useSearchAct