react-instantsearch
Version:
⚡ Lightning-fast search for React, by Algolia
482 lines (479 loc) • 23.8 kB
JavaScript
import { _ as _$1 } from '@swc/helpers/cjs/_object_spread.cjs';
import { _ as _$4 } from '@swc/helpers/cjs/_object_spread_props.cjs';
import { _ } from '@swc/helpers/cjs/_object_without_properties.cjs';
import { _ as _$5 } from '@swc/helpers/cjs/_sliced_to_array.cjs';
import { _ as _$2 } from '@swc/helpers/cjs/_to_consumable_array.cjs';
import { _ as _$3 } from '@swc/helpers/cjs/_type_of.cjs';
import { createAutocompleteComponent, createAutocompletePanelComponent, createAutocompleteIndexComponent, createAutocompleteSuggestionComponent, createAutocompleteRecentSearchComponent, createAutocompleteDetachedContainerComponent, createAutocompleteDetachedOverlayComponent, createAutocompleteDetachedFormContainerComponent, createAutocompleteDetachedSearchButtonComponent, createAutocompletePropGetters, createAutocompleteStorage, cx } from 'instantsearch-ui-components';
import React, { createElement, Fragment, useEffect, useId, useMemo, useRef, useState } from 'react';
import { useInstantSearch, useSearchBox, Index, Configure, useAutocomplete } from 'react-instantsearch-core';
import { AutocompleteSearch } from '../components/AutocompleteSearch.js';
import { ReverseHighlight } from './ReverseHighlight.js';
var Autocomplete = createAutocompleteComponent({
createElement: createElement,
Fragment: Fragment
});
var AutocompletePanel = createAutocompletePanelComponent({
createElement: createElement,
Fragment: Fragment
});
var AutocompleteIndex = createAutocompleteIndexComponent({
createElement: createElement,
Fragment: Fragment
});
var AutocompleteSuggestion = createAutocompleteSuggestionComponent({
createElement: createElement,
Fragment: Fragment
});
var AutocompleteRecentSearch = createAutocompleteRecentSearchComponent({
createElement: createElement,
Fragment: Fragment
});
var AutocompleteDetachedContainer = createAutocompleteDetachedContainerComponent({
createElement: createElement,
Fragment: Fragment
});
var AutocompleteDetachedOverlay = createAutocompleteDetachedOverlayComponent({
createElement: createElement,
Fragment: Fragment
});
var AutocompleteDetachedFormContainer = createAutocompleteDetachedFormContainerComponent({
createElement: createElement,
Fragment: Fragment
});
var AutocompleteDetachedSearchButton = createAutocompleteDetachedSearchButtonComponent({
createElement: createElement,
Fragment: Fragment
});
var usePropGetters = createAutocompletePropGetters({
useEffect: useEffect,
useId: useId,
useMemo: useMemo,
useRef: useRef,
useState: useState
});
var useStorage = createAutocompleteStorage({
useEffect: useEffect,
useMemo: useMemo,
useState: 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 = useMemo(function() {
return resolveMediaQuery(mediaQuery, DETACHED_MEDIA_QUERY_CSS_VAR, DEFAULT_DETACHED_MEDIA_QUERY);
}, [
mediaQuery
]);
var _useState = _$5(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 = _$5(useState(false), 2), isModalOpen = _useState1[0], setIsModalOpen = _useState1[1];
var _useState2 = _$5(useState(false), 2), isModalDetached = _useState2[0], setIsModalDetached = _useState2[1];
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
]);
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
]);
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 = _(_0, [
"indices",
"showSuggestions",
"showRecent",
"searchParameters",
"detachedMediaQuery",
"translations"
]);
var _showRecent_classNames, _showRecent_classNames1, _showRecent_classNames2, _showRecent_classNames3;
var translations = _$1({}, DEFAULT_TRANSLATIONS, userTranslations);
var _useInstantSearch = useInstantSearch(), indexUiState = _useInstantSearch.indexUiState, indexRenderState = _useInstantSearch.indexRenderState, status = _useInstantSearch.status;
var refine = useSearchBox({}, {
$$type: 'ais.autocomplete',
$$widgetType: 'ais.autocomplete'
}).refine;
var isSearchStalled = status === 'stalled';
var searchParameters = _$1({
hitsPerPage: 5
}, userSearchParameters);
var indicesConfig = _$2(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: 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: 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: 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: 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" : _$3(showRecent)) === 'object' ? showRecent.headerComponent : undefined,
itemComponent: (typeof showRecent === "undefined" ? "undefined" : _$3(showRecent)) === 'object' && showRecent.itemComponent ? showRecent.itemComponent : AutocompleteRecentSearch,
classNames: {
root: cx('ais-AutocompleteRecentSearches', (typeof showRecent === "undefined" ? "undefined" : _$3(showRecent)) === 'object' ? (_showRecent_classNames = showRecent.classNames) === null || _showRecent_classNames === void 0 ? void 0 : _showRecent_classNames.root : undefined),
list: cx('ais-AutocompleteRecentSearchesList', (typeof showRecent === "undefined" ? "undefined" : _$3(showRecent)) === 'object' ? (_showRecent_classNames1 = showRecent.classNames) === null || _showRecent_classNames1 === void 0 ? void 0 : _showRecent_classNames1.list : undefined),
header: cx('ais-AutocompleteRecentSearchesHeader', (typeof showRecent === "undefined" ? "undefined" : _$3(showRecent)) === 'object' ? (_showRecent_classNames2 = showRecent.classNames) === null || _showRecent_classNames2 === void 0 ? void 0 : _showRecent_classNames2.header : undefined),
item: cx('ais-AutocompleteRecentSearchesItem', (typeof showRecent === "undefined" ? "undefined" : _$3(showRecent)) === 'object' ? (_showRecent_classNames3 = showRecent.classNames) === null || _showRecent_classNames3 === void 0 ? void 0 : _showRecent_classNames3.item : undefined)
}
} : undefined;
var isSearchPage = useMemo(function() {
return typeof indexRenderState.hits !== 'undefined' || typeof indexRenderState.infiniteHits !== 'undefined';
}, [
indexRenderState
]);
return /*#__PURE__*/ React.createElement(Fragment, null, /*#__PURE__*/ React.createElement(Index, {
EXPERIMENTAL_isolated: true
}, /*#__PURE__*/ React.createElement(Configure, searchParameters), indicesConfig.map(function(index) {
return /*#__PURE__*/ React.createElement(Index, {
key: index.indexName,
indexName: index.indexName
}, /*#__PURE__*/ React.createElement(Configure, index.searchParameters));
}), /*#__PURE__*/ React.createElement(InnerAutocomplete, _$4(_$1({}, 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 = _(_0, [
"indicesConfig",
"refineSearchBox",
"isSearchStalled",
"getSearchPageURL",
"onSelect",
"indexUiState",
"isSearchPage",
"panelComponent",
"showRecent",
"recentSearchConfig",
"showSuggestions",
"transformItems",
"placeholder",
"detachedMediaQuery",
"translations",
"classNames"
]);
var _useAutocomplete = 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 = 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(_$4(_$1({}, 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
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
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 _$4(_$1({}, item), {
__indexName: indexId
});
}),
getItemProps: getItemProps,
classNames: currentIndexConfig.classNames
});
});
var searchBoxContent = /*#__PURE__*/ React.createElement(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 ? _$4(_$1({}, classNames), {
detachedContainer: cx('ais-AutocompleteDetachedContainer--modal', classNames === null || classNames === void 0 ? void 0 : classNames.detachedContainer)
}) : classNames;
if (isDetached) {
return /*#__PURE__*/ React.createElement(Autocomplete, _$4(_$1({}, 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, _$4(_$1({}, 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, {
attribute: "query",
hit: item
});
}
export { EXPERIMENTAL_Autocomplete };