UNPKG

react-instantsearch

Version:
484 lines (480 loc) 24.6 kB
'use strict'; var _object_spread = require('@swc/helpers/_/_object_spread'); var _object_spread_props = require('@swc/helpers/_/_object_spread_props'); var _object_without_properties = require('@swc/helpers/_/_object_without_properties'); var _sliced_to_array = require('@swc/helpers/_/_sliced_to_array'); var _to_consumable_array = require('@swc/helpers/_/_to_consumable_array'); var _type_of = require('@swc/helpers/_/_type_of'); var instantsearchUiComponents = require('instantsearch-ui-components'); var React = require('react'); var reactInstantsearchCore = require('react-instantsearch-core'); var AutocompleteSearch = require('../components/AutocompleteSearch.js'); var ReverseHighlight = require('./ReverseHighlight.js'); var Autocomplete = instantsearchUiComponents.createAutocompleteComponent({ createElement: React.createElement, Fragment: React.Fragment }); var AutocompletePanel = instantsearchUiComponents.createAutocompletePanelComponent({ createElement: React.createElement, Fragment: React.Fragment }); var AutocompleteIndex = instantsearchUiComponents.createAutocompleteIndexComponent({ createElement: React.createElement, Fragment: React.Fragment }); var AutocompleteSuggestion = instantsearchUiComponents.createAutocompleteSuggestionComponent({ createElement: React.createElement, Fragment: React.Fragment }); var AutocompleteRecentSearch = instantsearchUiComponents.createAutocompleteRecentSearchComponent({ createElement: React.createElement, Fragment: React.Fragment }); var AutocompleteDetachedContainer = instantsearchUiComponents.createAutocompleteDetachedContainerComponent({ createElement: React.createElement, Fragment: React.Fragment }); var AutocompleteDetachedOverlay = instantsearchUiComponents.createAutocompleteDetachedOverlayComponent({ createElement: React.createElement, Fragment: React.Fragment }); var AutocompleteDetachedFormContainer = instantsearchUiComponents.createAutocompleteDetachedFormContainerComponent({ createElement: React.createElement, Fragment: React.Fragment }); var AutocompleteDetachedSearchButton = instantsearchUiComponents.createAutocompleteDetachedSearchButtonComponent({ createElement: React.createElement, Fragment: React.Fragment }); var usePropGetters = instantsearchUiComponents.createAutocompletePropGetters({ useEffect: React.useEffect, useId: React.useId, useMemo: React.useMemo, useRef: React.useRef, useState: React.useState }); var useStorage = instantsearchUiComponents.createAutocompleteStorage({ useEffect: React.useEffect, useMemo: React.useMemo, useState: React.useState }); var DEFAULT_DETACHED_MEDIA_QUERY = '(max-width: 680px)'; var DEFAULT_DETACHED_MODAL_MEDIA_QUERY = '(min-width: 680px)'; var DETACHED_MEDIA_QUERY_CSS_VAR = '--ais-autocomplete-detached-media-query'; var DETACHED_MODAL_MEDIA_QUERY_CSS_VAR = '--ais-autocomplete-detached-modal-media-query'; var DEFAULT_TRANSLATIONS = { detachedCancelButtonText: 'Cancel', detachedSearchButtonTitle: 'Search', detachedClearButtonTitle: 'Clear' }; function getCssMediaQueryValue(name) { if (typeof window === 'undefined' || typeof document === 'undefined') { return ''; } return getComputedStyle(document.documentElement).getPropertyValue(name).trim(); } function resolveMediaQuery(value, cssVarName, fallback) { if (value === '') { return ''; } if (value) { return value; } return getCssMediaQueryValue(cssVarName) || fallback; } function getMediaQueryList(mediaQuery) { if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') { return null; } return window.matchMedia(mediaQuery); } /** * Hook to manage detached (mobile) mode state */ function useDetachedMode(mediaQuery) { var resolvedMediaQuery = React.useMemo(function() { return resolveMediaQuery(mediaQuery, DETACHED_MEDIA_QUERY_CSS_VAR, DEFAULT_DETACHED_MEDIA_QUERY); }, [ mediaQuery ]); var _useState = _sliced_to_array._(React.useState(function() { var _getMediaQueryList; return resolvedMediaQuery ? Boolean((_getMediaQueryList = getMediaQueryList(resolvedMediaQuery)) === null || _getMediaQueryList === void 0 ? void 0 : _getMediaQueryList.matches) : false; }), 2), isDetached = _useState[0], setIsDetached = _useState[1]; var _useState1 = _sliced_to_array._(React.useState(false), 2), isModalOpen = _useState1[0], setIsModalOpen = _useState1[1]; var _useState2 = _sliced_to_array._(React.useState(false), 2), isModalDetached = _useState2[0], setIsModalDetached = _useState2[1]; React.useEffect(function() { if (!resolvedMediaQuery) { setIsDetached(false); return function() {}; } var mql = getMediaQueryList(resolvedMediaQuery); if (!mql) { setIsDetached(false); return function() {}; } var handler = function handler(event) { var wasDetached = isDetached; setIsDetached(event.matches); // Close modal if switching from detached to non-detached if (wasDetached && !event.matches) { setIsModalOpen(false); } }; mql.addEventListener('change', handler); return function() { return mql.removeEventListener('change', handler); }; }, [ resolvedMediaQuery, isDetached ]); React.useEffect(function() { if (!isDetached) { setIsModalDetached(false); return function() {}; } var modalMediaQuery = resolveMediaQuery(undefined, DETACHED_MODAL_MEDIA_QUERY_CSS_VAR, DEFAULT_DETACHED_MODAL_MEDIA_QUERY); if (!modalMediaQuery) { setIsModalDetached(false); return function() {}; } var mql = getMediaQueryList(modalMediaQuery); if (!mql) { setIsModalDetached(false); return function() {}; } var handler = function handler(event) { setIsModalDetached(event.matches); }; setIsModalDetached(mql.matches); mql.addEventListener('change', handler); return function() { return mql.removeEventListener('change', handler); }; }, [ isDetached ]); React.useEffect(function() { if (typeof document === 'undefined') return function() {}; if (isModalOpen) { var scrollY = window.scrollY; document.body.style.top = "-".concat(scrollY, "px"); document.body.classList.add('ais-Autocomplete--detached'); return function() { document.body.classList.remove('ais-Autocomplete--detached'); document.body.style.top = ''; window.scrollTo(0, scrollY); }; } return function() {}; }, [ isModalOpen ]); return { isDetached: isDetached, isModalDetached: isModalDetached, isModalOpen: isModalOpen, setIsModalOpen: setIsModalOpen }; } function EXPERIMENTAL_Autocomplete(_0) { var _0_indices = _0.indices, indices = _0_indices === void 0 ? [] : _0_indices, showSuggestions = _0.showSuggestions, showRecent = _0.showRecent, userSearchParameters = _0.searchParameters, detachedMediaQuery = _0.detachedMediaQuery, tmp = _0.translations, userTranslations = tmp === void 0 ? {} : tmp, props = _object_without_properties._(_0, [ "indices", "showSuggestions", "showRecent", "searchParameters", "detachedMediaQuery", "translations" ]); var _showRecent_classNames, _showRecent_classNames1, _showRecent_classNames2, _showRecent_classNames3; var translations = _object_spread._({}, DEFAULT_TRANSLATIONS, userTranslations); var _useInstantSearch = reactInstantsearchCore.useInstantSearch(), indexUiState = _useInstantSearch.indexUiState, indexRenderState = _useInstantSearch.indexRenderState, status = _useInstantSearch.status; var refine = reactInstantsearchCore.useSearchBox({}, { $$type: 'ais.autocomplete', $$widgetType: 'ais.autocomplete' }).refine; var isSearchStalled = status === 'stalled'; var searchParameters = _object_spread._({ hitsPerPage: 5 }, userSearchParameters); var indicesConfig = _to_consumable_array._(indices); if (showSuggestions === null || showSuggestions === void 0 ? void 0 : showSuggestions.indexName) { var _showSuggestions_classNames, _showSuggestions_classNames1, _showSuggestions_classNames2, _showSuggestions_classNames3; indicesConfig.unshift({ indexName: showSuggestions.indexName, headerComponent: showSuggestions.headerComponent, itemComponent: showSuggestions.itemComponent || function(param) { var item = param.item, onSelect = param.onSelect, onApply = param.onApply; return /*#__PURE__*/ React.createElement(AutocompleteSuggestion, { item: item, onSelect: onSelect, onApply: onApply }, /*#__PURE__*/ React.createElement(ConditionalReverseHighlight, { item: item })); }, classNames: { root: instantsearchUiComponents.cx('ais-AutocompleteSuggestions', showSuggestions === null || showSuggestions === void 0 ? void 0 : (_showSuggestions_classNames = showSuggestions.classNames) === null || _showSuggestions_classNames === void 0 ? void 0 : _showSuggestions_classNames.root), list: instantsearchUiComponents.cx('ais-AutocompleteSuggestionsList', showSuggestions === null || showSuggestions === void 0 ? void 0 : (_showSuggestions_classNames1 = showSuggestions.classNames) === null || _showSuggestions_classNames1 === void 0 ? void 0 : _showSuggestions_classNames1.list), header: instantsearchUiComponents.cx('ais-AutocompleteSuggestionsHeader', showSuggestions === null || showSuggestions === void 0 ? void 0 : (_showSuggestions_classNames2 = showSuggestions.classNames) === null || _showSuggestions_classNames2 === void 0 ? void 0 : _showSuggestions_classNames2.header), item: instantsearchUiComponents.cx('ais-AutocompleteSuggestionsItem', showSuggestions === null || showSuggestions === void 0 ? void 0 : (_showSuggestions_classNames3 = showSuggestions.classNames) === null || _showSuggestions_classNames3 === void 0 ? void 0 : _showSuggestions_classNames3.item) }, getQuery: function getQuery(item) { return item.query; }, getURL: showSuggestions.getURL }); } var recentSearchConfig = showRecent ? { headerComponent: (typeof showRecent === "undefined" ? "undefined" : _type_of._(showRecent)) === 'object' ? showRecent.headerComponent : undefined, itemComponent: (typeof showRecent === "undefined" ? "undefined" : _type_of._(showRecent)) === 'object' && showRecent.itemComponent ? showRecent.itemComponent : AutocompleteRecentSearch, classNames: { root: instantsearchUiComponents.cx('ais-AutocompleteRecentSearches', (typeof showRecent === "undefined" ? "undefined" : _type_of._(showRecent)) === 'object' ? (_showRecent_classNames = showRecent.classNames) === null || _showRecent_classNames === void 0 ? void 0 : _showRecent_classNames.root : undefined), list: instantsearchUiComponents.cx('ais-AutocompleteRecentSearchesList', (typeof showRecent === "undefined" ? "undefined" : _type_of._(showRecent)) === 'object' ? (_showRecent_classNames1 = showRecent.classNames) === null || _showRecent_classNames1 === void 0 ? void 0 : _showRecent_classNames1.list : undefined), header: instantsearchUiComponents.cx('ais-AutocompleteRecentSearchesHeader', (typeof showRecent === "undefined" ? "undefined" : _type_of._(showRecent)) === 'object' ? (_showRecent_classNames2 = showRecent.classNames) === null || _showRecent_classNames2 === void 0 ? void 0 : _showRecent_classNames2.header : undefined), item: instantsearchUiComponents.cx('ais-AutocompleteRecentSearchesItem', (typeof showRecent === "undefined" ? "undefined" : _type_of._(showRecent)) === 'object' ? (_showRecent_classNames3 = showRecent.classNames) === null || _showRecent_classNames3 === void 0 ? void 0 : _showRecent_classNames3.item : undefined) } } : undefined; var isSearchPage = React.useMemo(function() { return typeof indexRenderState.hits !== 'undefined' || typeof indexRenderState.infiniteHits !== 'undefined'; }, [ indexRenderState ]); return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement(reactInstantsearchCore.Index, { EXPERIMENTAL_isolated: true }, /*#__PURE__*/ React.createElement(reactInstantsearchCore.Configure, searchParameters), indicesConfig.map(function(index) { return /*#__PURE__*/ React.createElement(reactInstantsearchCore.Index, { key: index.indexName, indexName: index.indexName }, /*#__PURE__*/ React.createElement(reactInstantsearchCore.Configure, index.searchParameters)); }), /*#__PURE__*/ React.createElement(InnerAutocomplete, _object_spread_props._(_object_spread._({}, props), { indicesConfig: indicesConfig, refineSearchBox: refine, isSearchStalled: isSearchStalled, indexUiState: indexUiState, isSearchPage: isSearchPage, showRecent: showRecent, recentSearchConfig: recentSearchConfig, showSuggestions: showSuggestions, detachedMediaQuery: detachedMediaQuery, translations: translations })))); } function InnerAutocomplete(_0) { var indicesConfig = _0.indicesConfig, refineSearchBox = _0.refineSearchBox, isSearchStalled = _0.isSearchStalled, getSearchPageURL = _0.getSearchPageURL, userOnSelect = _0.onSelect, indexUiState = _0.indexUiState, isSearchPage = _0.isSearchPage, PanelComponent = _0.panelComponent, showRecent = _0.showRecent, recentSearchConfig = _0.recentSearchConfig, showSuggestions = _0.showSuggestions, transformItems = _0.transformItems, placeholder = _0.placeholder, _0_detachedMediaQuery = _0.detachedMediaQuery, detachedMediaQuery = _0_detachedMediaQuery === void 0 ? DEFAULT_DETACHED_MEDIA_QUERY : _0_detachedMediaQuery, translations = _0.translations, classNames = _0.classNames, props = _object_without_properties._(_0, [ "indicesConfig", "refineSearchBox", "isSearchStalled", "getSearchPageURL", "onSelect", "indexUiState", "isSearchPage", "panelComponent", "showRecent", "recentSearchConfig", "showSuggestions", "transformItems", "placeholder", "detachedMediaQuery", "translations", "classNames" ]); var _useAutocomplete = reactInstantsearchCore.useAutocomplete({ transformItems: transformItems }), indices = _useAutocomplete.indices, refineAutocomplete = _useAutocomplete.refine, currentRefinement = _useAutocomplete.currentRefinement; var _useDetachedMode = useDetachedMode(detachedMediaQuery), isDetached = _useDetachedMode.isDetached, isModalDetached = _useDetachedMode.isModalDetached, isModalOpen = _useDetachedMode.isModalOpen, setIsModalOpen = _useDetachedMode.setIsModalOpen; var previousIsDetachedRef = React.useRef(isDetached); var _useStorage = useStorage({ showRecent: showRecent, query: currentRefinement, indices: indices, indicesConfig: indicesConfig, suggestionsIndexName: showSuggestions === null || showSuggestions === void 0 ? void 0 : showSuggestions.indexName }), storage = _useStorage.storage, storageHits = _useStorage.storageHits, indicesForPropGetters = _useStorage.indicesForPropGetters, indicesConfigForPropGetters = _useStorage.indicesConfigForPropGetters; var _usePropGetters = usePropGetters({ indices: indicesForPropGetters, indicesConfig: indicesConfigForPropGetters, onRefine: function onRefine(query) { refineAutocomplete(query); refineSearchBox(query); storage.onAdd(query); }, onApply: function onApply(query) { refineAutocomplete(query); }, onSelect: userOnSelect !== null && userOnSelect !== void 0 ? userOnSelect : function(param) { var query = param.query, setQuery = param.setQuery, url = param.url; if (url) { window.location.href = url; return; } if (!isSearchPage && typeof getSearchPageURL !== 'undefined') { window.location.href = getSearchPageURL(_object_spread_props._(_object_spread._({}, indexUiState), { query: query })); return; } setQuery(query); }, onSubmit: function onSubmit() { // Close the detached modal when form is submitted if (isDetached) { setIsModalOpen(false); } }, placeholder: placeholder, isDetached: isDetached }), getInputProps = _usePropGetters.getInputProps, getItemProps = _usePropGetters.getItemProps, getPanelProps = _usePropGetters.getPanelProps, getRootProps = _usePropGetters.getRootProps, isOpen = _usePropGetters.isOpen, setIsOpen = _usePropGetters.setIsOpen, focusInput = _usePropGetters.focusInput; // Open panel and focus input when modal opens React.useEffect(function() { if (isDetached && isModalOpen) { setIsOpen(true); // Focus input to show the keyboard on mobile focusInput(); } }, [ isDetached, isModalOpen, setIsOpen, focusInput ]); // Keep the modal open if the panel was open before switching to detached React.useEffect(function() { var wasDetached = previousIsDetachedRef.current; var switchedToDetached = !wasDetached && isDetached; if (switchedToDetached && isOpen) { setIsModalOpen(true); } previousIsDetachedRef.current = isDetached; }, [ isDetached, isOpen, setIsModalOpen ]); var elements = {}; if (showRecent && recentSearchConfig) { var RecentSearchItemComponent = recentSearchConfig.itemComponent; elements.recent = /*#__PURE__*/ React.createElement(AutocompleteIndex, { HeaderComponent: recentSearchConfig.headerComponent, // @ts-ignore - there seems to be problems with React.ComponentType and this, but it's actually correct ItemComponent: function ItemComponent(param) { var item = param.item, onSelect = param.onSelect, onApply = param.onApply; return /*#__PURE__*/ React.createElement(RecentSearchItemComponent, { item: item, onSelect: onSelect, onRemoveRecentSearch: function onRemoveRecentSearch() { return storage.onRemove(item.query); }, onApply: onApply }, /*#__PURE__*/ React.createElement(ConditionalReverseHighlight, { item: item })); }, classNames: recentSearchConfig.classNames, items: storageHits, getItemProps: getItemProps, key: "recentSearches" }); } indices.forEach(function(param) { var indexId = param.indexId, indexName = param.indexName, hits = param.hits; var elementId = indexName === (showSuggestions === null || showSuggestions === void 0 ? void 0 : showSuggestions.indexName) ? 'suggestions' : indexName; var filteredHits = elementId === 'suggestions' && showRecent ? hits.filter(function(suggestionHit) { return !storageHits.find(function(storageHit) { return storageHit.query === suggestionHit.query; }); }) : hits; var currentIndexConfig = indicesConfig.find(function(config) { return config.indexName === indexName; }); if (!currentIndexConfig) { return; } elements[elementId] = /*#__PURE__*/ React.createElement(AutocompleteIndex, { key: indexId, // @ts-expect-error - there seems to be problems with React.ComponentType and this, but it's actually correct HeaderComponent: currentIndexConfig.headerComponent, // @ts-expect-error - there seems to be problems with React.ComponentType and this, but it's actually correct ItemComponent: currentIndexConfig.itemComponent, items: filteredHits.map(function(item) { return _object_spread_props._(_object_spread._({}, item), { __indexName: indexId }); }), getItemProps: getItemProps, classNames: currentIndexConfig.classNames }); }); var searchBoxContent = /*#__PURE__*/ React.createElement(AutocompleteSearch.AutocompleteSearch, { inputProps: getInputProps(), clearQuery: function clearQuery() { refineSearchBox(''); refineAutocomplete(''); }, onQueryChange: function onQueryChange(query) { refineAutocomplete(query); }, query: currentRefinement || indexUiState.query || '', refine: refineSearchBox, isSearchStalled: isSearchStalled }); var panelContent = /*#__PURE__*/ React.createElement(AutocompletePanel, getPanelProps(), PanelComponent ? /*#__PURE__*/ React.createElement(PanelComponent, { elements: elements, indices: indices }) : Object.keys(elements).map(function(elementId) { return elements[elementId]; })); var detachedContainerClassNames = isModalDetached ? _object_spread_props._(_object_spread._({}, classNames), { detachedContainer: instantsearchUiComponents.cx('ais-AutocompleteDetachedContainer--modal', classNames === null || classNames === void 0 ? void 0 : classNames.detachedContainer) }) : classNames; if (isDetached) { return /*#__PURE__*/ React.createElement(Autocomplete, _object_spread_props._(_object_spread._({}, props, getRootProps()), { classNames: classNames }), /*#__PURE__*/ React.createElement(AutocompleteDetachedSearchButton, { query: currentRefinement || indexUiState.query || '', placeholder: placeholder, classNames: classNames, onClick: function onClick() { setIsModalOpen(true); setIsOpen(true); }, onClear: function onClear() { refineSearchBox(''); refineAutocomplete(''); }, translations: translations }), isModalOpen && /*#__PURE__*/ React.createElement(AutocompleteDetachedOverlay, { classNames: classNames, onClose: function onClose() { setIsModalOpen(false); setIsOpen(false); } }, /*#__PURE__*/ React.createElement(AutocompleteDetachedContainer, { classNames: detachedContainerClassNames }, /*#__PURE__*/ React.createElement(AutocompleteDetachedFormContainer, { classNames: classNames, onCancel: function onCancel() { setIsModalOpen(false); setIsOpen(false); }, translations: translations }, searchBoxContent), panelContent))); } // Normal (non-detached) rendering return /*#__PURE__*/ React.createElement(Autocomplete, _object_spread_props._(_object_spread._({}, props, getRootProps()), { classNames: classNames }), searchBoxContent, panelContent); } function ConditionalReverseHighlight(param) { var item = param.item; var _item__highlightResult; if (!((_item__highlightResult = item._highlightResult) === null || _item__highlightResult === void 0 ? void 0 : _item__highlightResult.query) || // @ts-expect-error - we should not have matchLevel as arrays here item._highlightResult.query.matchLevel === 'none') { return item.query; } return /*#__PURE__*/ React.createElement(ReverseHighlight.ReverseHighlight, { attribute: "query", hit: item }); } exports.EXPERIMENTAL_Autocomplete = EXPERIMENTAL_Autocomplete;