UNPKG

@yext/search-ui-react

Version:

A library of React Components for powering Yext Search integrations

1,286 lines (1,243 loc) 200 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { AlternativeVerticals: () => AlternativeVerticals, AnalyticsProvider: () => AnalyticsProvider, AppliedFilters: () => AppliedFilters, ApplyFiltersButton: () => ApplyFiltersButton, ComponentsContentPath: () => ComponentsContentPath, DirectAnswer: () => DirectAnswer, DropdownItem: () => DropdownItem, Facets: () => Facets, FilterDivider: () => FilterDivider, FilterSearch: () => FilterSearch, GenerativeDirectAnswer: () => GenerativeDirectAnswer, Geolocation: () => Geolocation, HierarchicalFacet: () => HierarchicalFacet, HierarchicalFacets: () => HierarchicalFacets, LocationBias: () => LocationBias, MapboxMap: () => MapboxMap, NumericalFacet: () => NumericalFacet, NumericalFacets: () => NumericalFacets, Pagination: () => Pagination, ResultsCount: () => ResultsCount, SearchBar: () => SearchBar, SpellCheck: () => SpellCheck, StandardCard: () => StandardCard, StandardFacet: () => StandardFacet, StandardFacets: () => StandardFacets, StandardSection: () => StandardSection, StaticFilters: () => StaticFilters, ThumbsFeedback: () => ThumbsFeedback, UniversalResults: () => UniversalResults, VerticalResults: () => VerticalResults, executeAutocomplete: () => executeAutocomplete, executeGenerativeDirectAnswer: () => executeGenerativeDirectAnswer, executeSearch: () => executeSearch, getSearchIntents: () => getSearchIntents, getUserLocation: () => getUserLocation, isCtaData: () => isCtaData, renderHighlightedValue: () => renderHighlightedValue, updateLocationIfNeeded: () => updateLocationIfNeeded, useAnalytics: () => useAnalytics, useCardAnalyticsCallback: () => useCardAnalyticsCallback, useCardFeedbackCallback: () => useCardFeedbackCallback, useComposedCssClasses: () => useComposedCssClasses }); module.exports = __toCommonJS(index_exports); // src/components/SearchBar.tsx var import_search_headless_react7 = require("@yext/search-headless-react"); var import_classnames = __toESM(require("classnames")); var import_react26 = __toESM(require("react")); // src/hooks/useEntityPreviews.tsx var import_react3 = require("react"); // src/hooks/useComponentMountStatus.tsx var import_react = require("react"); function useComponentMountStatus() { const isMountedRef = (0, import_react.useRef)(false); (0, import_react.useEffect)(() => { isMountedRef.current = true; return () => { isMountedRef.current = false; }; }, []); return isMountedRef; } // src/hooks/useDebouncedFunction.ts var import_react2 = require("react"); function useDebouncedFunction(func, milliseconds) { const timeoutIdRef = (0, import_react2.useRef)(); 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 ] = (0, import_react3.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] = (0, import_react3.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 var import_react4 = require("react"); var import_recent_searches = require("recent-searches"); function useRecentSearches(recentSearchesLimit, verticalKey) { const recentSearchesKey = getRecentSearchesKey(verticalKey); const [recentSearches, setRecentSeaches] = (0, import_react4.useState)( new import_recent_searches.RecentSearches({ limit: recentSearchesLimit, namespace: recentSearchesKey }) ); const clearRecentSearches = (0, import_react4.useCallback)(() => { localStorage.removeItem(recentSearchesKey); setRecentSeaches(new import_recent_searches.RecentSearches({ limit: recentSearchesLimit, namespace: recentSearchesKey })); localStorage.removeItem(recentSearchesKey); }, [recentSearchesKey, recentSearchesLimit]); const setRecentSearch = (0, import_react4.useCallback)((input) => { recentSearches.setRecentSearch(input); }, [recentSearches]); (0, import_react4.useEffect)(() => { setRecentSeaches(new import_recent_searches.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 var import_search_headless_react3 = require("@yext/search-headless-react"); // src/utils/search-operations.ts var import_search_headless_react = require("@yext/search-headless-react"); async function executeSearch(searchActions) { const isVertical = searchActions.state.meta.searchType === import_search_headless_react.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 === import_search_headless_react.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 var import_search_headless_react2 = require("@yext/search-headless-react"); var defaultGeolocationOptions = { enableHighAccuracy: false, timeout: 6e3, maximumAge: 3e5 }; async function updateLocationIfNeeded(searchActions, intents, geolocationOptions) { if (intents.includes(import_search_headless_react2.SearchIntent.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 var import_react5 = require("react"); function useSearchWithNearMeHandling(geolocationOptions, onSearch) { const autocompletePromiseRef = (0, import_react5.useRef)(); const searchActions = (0, import_search_headless_react3.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 var import_react6 = require("react"); function useSynchronizedRequest(executeRequest, handleRejectedPromise) { const executeRequestRef = (0, import_react6.useRef)(executeRequest); const handleRejectedPromiseRef = (0, import_react6.useRef)(handleRejectedPromise); const isMountedRef = useComponentMountStatus(); const networkIds = (0, import_react6.useRef)({ latestRequest: 0, responseInState: 0 }); const [synchronizedResponse, setSynchronizedResponse] = (0, import_react6.useState)(); const executeSynchronizedRequest = (0, import_react6.useCallback)(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 = (0, import_react6.useCallback)(() => { setSynchronizedResponse(void 0); }, [setSynchronizedResponse]); (0, import_react6.useEffect)(() => { executeRequestRef.current = executeRequest; handleRejectedPromiseRef.current = handleRejectedPromise; }); return [synchronizedResponse, executeSynchronizedRequest, clearResponseData]; } // src/icons/VerticalDividerIcon.tsx var import_react7 = __toESM(require("react")); function VerticalDividerIcon({ className }) { return /* @__PURE__ */ import_react7.default.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__ */ import_react7.default.createElement("rect", { width: "1", height: "24", rx: "0.5", fill: "#E1E5E8" }) ); } // src/icons/HistoryIcon.tsx var import_react8 = __toESM(require("react")); function HistoryIcon() { return /* @__PURE__ */ import_react8.default.createElement("svg", { viewBox: "0 0 14 15", fill: "currentColor", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true" }, /* @__PURE__ */ import_react8.default.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 var import_react9 = __toESM(require("react")); function CloseIcon() { return /* @__PURE__ */ import_react9.default.createElement("svg", { viewBox: "0 0 18 18", xmlns: "http://www.w3.org/2000/svg", fill: "currentColor", "aria-hidden": "true" }, /* @__PURE__ */ import_react9.default.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 var import_react10 = __toESM(require("react")); function MagnifyingGlassIcon() { return /* @__PURE__ */ import_react10.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true" }, /* @__PURE__ */ import_react10.default.createElement("path", { d: "M0 0h24v24H0V0z", fill: "none" }), /* @__PURE__ */ import_react10.default.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 var import_react18 = __toESM(require("react")); // src/components/Dropdown/DropdownContext.ts var import_react11 = require("react"); var DropdownContext = (0, import_react11.createContext)(null); function useDropdownContext() { const dropdownContextInstance = (0, import_react11.useContext)(DropdownContext); if (dropdownContextInstance === null) { throw new Error("Tried to use DropdownContext when none exists."); } return dropdownContextInstance; } // src/components/Dropdown/InputContext.ts var import_react12 = require("react"); var InputContext = (0, import_react12.createContext)(null); function useInputContext() { const inputContextInstance = (0, import_react12.useContext)(InputContext); if (inputContextInstance === null) { throw new Error("Tried to use InputContext when none exists."); } return inputContextInstance; } // src/components/Dropdown/Dropdown.tsx var import_useRootClose = __toESM(require("@restart/ui/useRootClose")); // src/components/Dropdown/FocusContext.ts var import_react13 = require("react"); var FocusContext = (0, import_react13.createContext)(null); function useFocusContext() { const focusContextInstance = (0, import_react13.useContext)(FocusContext); if (focusContextInstance === null) { throw new Error("Tried to use FocusContext when none exists."); } return focusContextInstance; } // src/components/ScreenReader.tsx var import_react14 = __toESM(require("react")); function ScreenReader({ instructionsId, instructions, announcementKey, announcementText }) { return /* @__PURE__ */ import_react14.default.createElement(import_react14.default.Fragment, null, /* @__PURE__ */ import_react14.default.createElement( "div", { id: instructionsId, className: "hidden" }, instructions ), /* @__PURE__ */ import_react14.default.createElement( "div", { className: "sr-only", key: announcementKey, "aria-live": "assertive" }, announcementText )); } // src/components/utils/recursivelyMapChildren.ts var import_react15 = require("react"); function recursivelyMapChildren(children, elementReplacer) { return import_react15.Children.map(children, (c, index) => { if (!(0, import_react15.isValidElement)(c)) { return c; } const replacedElement = elementReplacer(c, index); if (!replacedElement || !(0, import_react15.isValidElement)(replacedElement)) { return replacedElement; } const grandchildren = replacedElement.props.children; if (!grandchildren) { return replacedElement; } const replacedGrandchildren = recursivelyMapChildren(grandchildren, elementReplacer); return (0, import_react15.cloneElement)(replacedElement, {}, [replacedGrandchildren]); }); } // src/components/Dropdown/DropdownItem.tsx var import_react16 = __toESM(require("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 = (0, import_react16.useCallback)(() => { 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__ */ import_react16.default.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 var import_use_isomorphic_layout_effect = __toESM(require("use-isomorphic-layout-effect")); var useLayoutEffect = typeof import_use_isomorphic_layout_effect.default === "function" ? import_use_isomorphic_layout_effect.default : import_use_isomorphic_layout_effect.default["default"]; // src/hooks/useId.ts var import_react17 = __toESM(require("react")); var serverHandoffComplete = false; var id = 0; function genId(baseName) { ++id; return baseName + "-" + id.toString(); } var maybeReactUseId = import_react17.default["useId".toString()]; function useId(baseName) { if (maybeReactUseId !== void 0) { return maybeReactUseId(); } const initialId = serverHandoffComplete ? genId(baseName) : ""; const [id2, setId] = (0, import_react17.useState)(initialId); useLayoutEffect(() => { if (id2 === "") { setId(genId(baseName)); } }, [id2]); (0, import_react17.useEffect)(() => { if (serverHandoffComplete === false) { serverHandoffComplete = true; } }, []); return id2; } // src/components/Dropdown/Dropdown.tsx var useRootClose = typeof import_useRootClose.default === "function" ? import_useRootClose.default : import_useRootClose.default["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 = (0, import_react18.useRef)(null); const screenReaderUUID = useId("dropdown"); const [screenReaderKey, setScreenReaderKey] = (0, import_react18.useState)(0); const [hasTyped, setHasTyped] = (0, import_react18.useState)(false); const [childrenWithDropdownItemsTransformed, items] = (0, import_react18.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__ */ import_react18.default.createElement("div", { ref: containerRef, className: isActive ? activeClassName : className, onKeyDown: handleKeyDown }, /* @__PURE__ */ import_react18.default.createElement(DropdownContext.Provider, { value: dropdownContext }, /* @__PURE__ */ import_react18.default.createElement(InputContext.Provider, { value: inputContext }, /* @__PURE__ */ import_react18.default.createElement(FocusContext.Provider, { value: focusContext }, childrenWithDropdownItemsTransformed))), /* @__PURE__ */ import_react18.default.createElement( ScreenReader, { announcementKey: screenReaderKey, announcementText: isActive && (hasTyped || items.length || value) ? screenReaderText : "", instructionsId: screenReaderUUID, instructions: screenReaderInstructions } )); } function useInputContextInstance() { const [value, setValue] = (0, import_react18.useState)(""); const [lastTypedOrSubmittedValue, setLastTypedOrSubmittedValue] = (0, import_react18.useState)(""); return { value, setValue, lastTypedOrSubmittedValue, setLastTypedOrSubmittedValue }; } function useFocusContextInstance(items, lastTypedOrSubmittedValue, setValue, screenReaderKey, setScreenReaderKey, alwaysSelectOption) { const [focusedIndex, setFocusedIndex] = (0, import_react18.useState)(-1); const [focusedValue, setFocusedValue] = (0, import_react18.useState)(null); const [focusedItemData, setFocusedItemData] = (0, import_react18.useState)(void 0); (0, import_react18.useEffect)(() => { 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] = (0, import_react18.useState)(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 (!((0, import_react18.isValidElement)(child) && child.type === DropdownItem)) { return child; } const props = child.props; items.push({ value: props.value, itemData: props.itemData }); return (0, import_react18.createElement)(DropdownItemWithIndex, { ...props, index: items.length - 1 }); }); return [childrenWithDropdownItemsTransformed, items]; } // src/components/Dropdown/DropdownInput.tsx var import_react19 = __toESM(require("react")); function DropdownInput(props) { const { className, placeholder, ariaLabel, onSubmit, onFocus, onChange, submitCriteria } = props; const inputRef = (0, import_react19.useRef)(null); const { toggleDropdown, onSelect, screenReaderUUID } = useDropdownContext(); const { value = "", setLastTypedOrSubmittedValue } = useInputContext(); const { focusedIndex = -1, focusedItemData, focusedValue, updateFocusedItem } = useFocusContext(); const [isTyping, setIsTyping] = (0, import_react19.useState)(true); const handleChange = (0, import_react19.useCallback)((e) => { setIsTyping(true); toggleDropdown(true); onChange?.(e.target.value); updateFocusedItem(-1, e.target.value); setLastTypedOrSubmittedValue(e.target.value); }, [onChange, setLastTypedOrSubmittedValue, toggleDropdown, updateFocusedItem]); const handleKeyDown = (0, import_react19.useCallback)((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 = (0, import_react19.useCallback)(() => { toggleDropdown(true); updateFocusedItem(-1); onFocus?.(value); }, [onFocus, toggleDropdown, updateFocusedItem, value]); return /* @__PURE__ */ import_react19.default.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 var import_react20 = __toESM(require("react")); function DropdownMenu({ children }) { const { isActive } = useDropdownContext(); if (!isActive) { return null; } return /* @__PURE__ */ import_react20.default.createElement(import_react20.default.Fragment, null, children); } // src/hooks/useComposedCssClasses.tsx var import_react21 = require("react"); var import_tailwind_merge = require("tailwind-merge"); var twMerge = (0, import_tailwind_merge.extendTailwindMerge)({ classGroups: { form: ["input", "checkbox", "textarea", "select", "multiselect", "radio"].map((v) => "form-" + v) } }); function useComposedCssClasses(builtInClasses, customClasses, disableBuiltInClasses = false) { return (0, import_react21.useMemo)(() => { 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 var import_react22 = __toESM(require("react")); function SearchButton({ handleClick, className }) { return /* @__PURE__ */ import_react22.default.createElement( "button", { className, onClick: handleClick, "aria-label": "Submit Search" }, /* @__PURE__ */ import_react22.default.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 var import_react23 = __toESM(require("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__ */ import_react23.default.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__ */ import_react23.default.createElement("span", { key: curr, className: cssClasses.nonHighlighted }, value.substring(curr, offset)) ); } highlightedJSX.push( /* @__PURE__ */ import_react23.default.createElement("span", { key: offset, className: cssClasses.highlighted }, value.substring(offset, offset + length)) ); curr = offset + length; } if (curr < value.length) { highlightedJSX.push( /* @__PURE__ */ import_react23.default.createElement("span", { key: curr, className: cssClasses.nonHighlighted }, value.substring(curr)) ); } return /* @__PURE__ */ import_react23.default.createElement(import_react23.default.Fragment, null, highlightedJSX); } // src/components/utils/renderAutocompleteResult.tsx var import_react24 = __toESM(require("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__ */ import_react24.default.createElement(import_react24.default.Fragment, null, Icon && /* @__PURE__ */ import_react24.default.createElement("div", { className: cssClasses.icon }, /* @__PURE__ */ import_react24.default.createElement(Icon, null)), /* @__PURE__ */ import_react24.default.createElement("div", { "aria-label": ariaLabel || "", className: cssClasses.option }, renderHighlightedValue(result, cssClasses))); } // src/hooks/useAnalytics.ts var import_react25 = require("react"); var AnalyticsContext = (0, import_react25.createContext)(null); function useAnalytics() { return (0, import_react25.useContext)(AnalyticsContext); } // src/hooks/useSearchBarAnalytics.ts var import_search_headless_react4 = require("@yext/search-headless-react"); function useSearchBarAnalytics() { const analytics = useAnalytics(); const verticalKey = (0, import_search_headless_react4.useSearchState)((state) => state.vertical.verticalKey); const queryId = (0, import_search_headless_react4.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 var import_search_headless_react6 = require("@yext/search-headless-react"); var import_isEqual = __toESM(require("lodash/isEqual.js")); // src/models/NumberRangeFilter.ts var import_search_headless_react5 = require("@yext/search-headless-react"); function isNumberRangeFilter(unknownFilter = {}) { const filter = unknownFilter; return filter.matcher === import_search_headless_react5.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 (0, import_isEqual.default)(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: import_search_headless_react6.Matcher.GreaterThanOrEqualTo, value: minRange } }, ...maxRange !== void 0 && { end: { matcher: import_search_headless_react6.Matcher.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 = (0, import_search_headless_react7.useSearchActions)(); const searchUtilities = (0, import_search_headless_react7.useSearchUtilities)(); const reportAnalyticsEvent = useSearchBarAnalytics(); const query = (0, import_search_headless_react7.useSearchState)((state) => state.query.input) ?? ""; const cssClasses = useComposedCssClasses(builtInCssClasses2, customCssClasses); const isVertical = (0, import_search_headless_react7.useSearchState)((state) => state.meta.searchType) === import_search_headless_react7.SearchTypeEnum.Vertical; const verticalKey = (0, import_search_headless_react7.useSearchState)((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) ); (0, import_react26.useEffect)(() => { if (hideRecentSearches) { clearRecentSearches(); } }, [clearRecentSearches, hideRecentSearches]); const clearAutocomplete = (0, import_react26.useCallback)(() => { clearAutocompleteData(); autocompletePromiseRef.current = void 0; }, [autocompletePromiseRef, clearAutocompleteData]); const executeQuery = (0, import_react26.useCallback)(() => { if (!hideRecentSearches) { const input = searchActions.state.query.input; input && setRecentSearch(input); } executeQueryWithNearMeHandling(); }, [ searchActions.state.query.input, executeQueryWithNearMeHandling, hideRecentSearches, setRecentSearch ]); const handleSubmit = (0, import_react26.useCallback)((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: import_search_headless_react7.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 = (0, import_react26.useCallback)((query2) => { if (!renderEntityPreviews || !includedVerticals) { return; } executeEntityPreviewsQuery(query2, universalLimit ?? {}, includedVerticals); }, [executeEntityPreviewsQuery, renderEntityPreviews, includedVerticals, universalLimit]); const handleInputFocus = (0, import_react26.useCallback)((value = "") => { searchActions.setQuery(value); updateEntityPreviews(value); autocompletePromiseRef.current = executeAutocomplete2(); }, [searchActions, autocompletePromiseRef, executeAutocomplete2, updateEntityPreviews]); const handleInputChange = (0, import_react26.useCallback)((value = "") => { searchActions.setQuery(value); updateEntityPreviews(value); autocompletePromiseRef.current = executeAutocomplete2(); }, [searchActions, autocompletePromiseRef, executeAutocomplete2, updateEntityPreviews]); const handleClickClearButton = (0, import_react26.useCallback)(() => { updateEntityPreviews(""); searchActions.setQuery(""); reportAnalyticsEvent("SEARCH_CLEAR_BUTTON"); }, [handleSubmit, reportAnalyticsEvent, updateEntityPreviews]); function renderInput() { return /* @__PURE__ */ import_react26.default.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__ */ import_react26.default.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 = (0, import_react26.useMemo)(() => { 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__ */ import_react26.default.createElement(import_react26.Fragment, { key: i }, /* @__PURE__ */ import_react26.default.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, `auto