UNPKG

@empathyco/x-components

Version:
345 lines (342 loc) • 16.5 kB
import { deepEqual } from '@empathyco/x-utils'; import { defineComponent, computed, provide, inject, watch, onBeforeUnmount } from 'vue'; import '../../../components/animations/animate-clip-path/animate-clip-path.style.scss.js'; import '../../../components/animations/animate-scale/animate-scale.style.scss.js'; import '../../../components/animations/animate-translate/animate-translate.style.scss.js'; import '../../../components/animations/animate-width.vue2.js'; import '../../../components/animations/animate-width.vue3.js'; import '../../../components/animations/change-height.vue2.js'; import '../../../components/animations/collapse-height.vue2.js'; import '../../../components/animations/collapse-height.vue3.js'; import '../../../components/animations/collapse-width.vue2.js'; import '../../../components/animations/collapse-width.vue3.js'; import '../../../components/animations/cross-fade.vue2.js'; import '../../../components/animations/cross-fade.vue3.js'; import '../../../components/animations/fade-and-slide.vue2.js'; import '../../../components/animations/fade-and-slide.vue3.js'; import '../../../components/animations/fade.vue2.js'; import '../../../components/animations/fade.vue3.js'; import '../../../components/animations/no-animation.vue.js'; import '../../../components/animations/staggered-fade-and-slide.vue2.js'; import '../../../components/animations/staggered-fade-and-slide.vue3.js'; import { LIST_ITEMS_KEY } from '../../../components/decorators/injection.consts.js'; import '../../../components/auto-progress-bar.vue2.js'; import '../../../components/auto-progress-bar.vue3.js'; import '../../../components/base-dropdown.vue2.js'; import '../../../components/base-dropdown.vue3.js'; import '../../../components/base-event-button.vue2.js'; import '../../../components/base-grid.vue2.js'; import '../../../components/base-grid.vue3.js'; import '../../../components/base-keyboard-navigation.vue2.js'; import '../../../components/base-rating.vue2.js'; import '../../../components/base-rating.vue3.js'; import '../../../components/base-slider.vue2.js'; import '../../../components/base-slider.vue3.js'; import '../../../components/base-switch.vue2.js'; import '../../../components/base-switch.vue3.js'; import '../../../components/base-teleport.vue2.js'; import '../../../components/base-variable-column-grid.vue2.js'; import '../../../components/column-picker/base-column-picker-dropdown.vue2.js'; import '../../../components/column-picker/base-column-picker-list.vue2.js'; import '../../../components/currency/base-currency.vue2.js'; import '../../../components/display-click-provider.vue.js'; import '../../../components/display-emitter.vue.js'; import '../../../components/filters/labels/base-price-filter-label.vue.js'; import '../../../components/filters/labels/base-rating-filter-label.vue2.js'; import '../../../components/global-x-bus.vue.js'; import '../../../components/highlight.vue2.js'; import '../../../components/items-list.vue2.js'; import '../../../components/layouts/fixed-header-and-asides-layout.vue2.js'; import '../../../components/layouts/fixed-header-and-asides-layout.vue3.js'; import '../../../components/layouts/multi-column-max-width-layout.vue2.js'; import '../../../components/layouts/multi-column-max-width-layout.vue3.js'; import '../../../components/layouts/multi-column-max-width-layout.vue4.js'; import '../../../components/layouts/single-column-layout.vue2.js'; import '../../../components/layouts/single-column-layout.vue3.js'; import '../../../components/location-provider.vue.js'; import '../../../components/modals/base-events-modal-close.vue2.js'; import '../../../components/modals/base-events-modal-open.vue2.js'; import '../../../components/modals/base-events-modal.vue2.js'; import '../../../components/modals/base-id-modal-close.vue2.js'; import '../../../components/modals/base-id-modal-open.vue2.js'; import '../../../components/modals/base-id-modal.vue2.js'; import '../../../components/modals/base-modal.vue2.js'; import '../../../components/modals/base-modal.vue3.js'; import '../../../components/modals/close-main-modal.vue2.js'; import '../../../components/modals/main-modal.vue2.js'; import '../../../components/modals/open-main-modal.vue2.js'; import '../../../components/page-loader-button.vue2.js'; import '../../../components/page-loader-button.vue3.js'; import '../../../components/page-selector.vue2.js'; import '../../../components/page-selector.vue3.js'; import '../../../components/panels/base-header-toggle-panel.vue2.js'; import '../../../components/panels/base-header-toggle-panel.vue3.js'; import '../../../components/panels/base-id-toggle-panel-button.vue2.js'; import '../../../components/panels/base-id-toggle-panel.vue2.js'; import '../../../components/panels/base-tabs-panel.vue2.js'; import '../../../components/panels/base-tabs-panel.vue3.js'; import '../../../components/panels/base-toggle-panel.vue2.js'; import '../../../components/result/base-result-add-to-cart.vue2.js'; import '../../../components/result/base-result-current-price.vue2.js'; import '../../../components/result/base-result-image.vue2.js'; import '../../../components/result/base-result-image.vue3.js'; import '../../../components/result/base-result-link.vue2.js'; import '../../../components/result/base-result-link.vue3.js'; import '../../../components/result/base-result-previous-price.vue2.js'; import '../../../components/result/base-result-rating.vue2.js'; import '../../../components/result/base-result-rating.vue3.js'; import '../../../components/result/result-variant-selector.vue2.js'; import '../../../components/result/result-variant-selector.vue3.js'; import '../../../components/result/result-variants-provider.vue.js'; import '../../../components/scroll/base-scroll.vue2.js'; import 'vuex'; import '@vue/devtools-api'; import '../../../plugins/devtools/timeline.devtools.js'; import 'rxjs/operators'; import 'rxjs'; import '../../../plugins/devtools/colors.utils.js'; import '../../../plugins/x-bus.js'; import '../../../plugins/x-plugin.js'; import { useXBus } from '../../../composables/use-x-bus.js'; import '../../../components/sliding-panel.vue2.js'; import '../../../components/sliding-panel.vue3.js'; import '../../../components/snippet-callbacks.vue2.js'; import '../../../components/suggestions/base-suggestion.vue2.js'; import '../../../components/suggestions/base-suggestions.vue2.js'; import '../../../components/suggestions/base-suggestions.vue3.js'; import '../../../composables/create-use-device.js'; import { debounce } from '../../../utils/debounce.js'; import '@vueuse/core'; import { useState } from '../../../composables/use-state.js'; import { createRawFilters } from '../../../utils/filters.js'; import { createOrigin } from '../../../utils/origin.js'; import '../../../utils/storage.js'; import { getHashFromQueryPreviewInfo } from '../utils/get-hash-from-query-preview.js'; import { queriesPreviewXModule } from '../x-module.js'; /** * Retrieves a preview of the results of a query and exposes them in the default slot, * along with the query preview and the totalResults of the search request. * By default, it renders the names of the results. * * @public */ var _sfc_main = defineComponent({ name: 'QueryPreview', xModule: queriesPreviewXModule.name, props: { /** The information about the request of the query preview. */ queryPreviewInfo: { type: Object, required: true, }, /** The origin property for the request. */ queryFeature: { type: String, }, /** Number of query preview results to be rendered. */ maxItemsToRender: { type: Number, }, /** * Debounce time in milliseconds for triggering the search requests. * It will default to 0 to fit the most common use case (pre-search), * and it would work properly with a 250 value inside empathize. */ debounceTimeMs: { type: Number, default: 0, }, /** * Controls whether the QueryPreview should be removed from the state * when the component is destroyed. */ persistInCache: { type: Boolean, default: false, }, }, emits: ['load', 'error'], setup(props, { emit, slots }) { const xBus = useXBus(); /** * previewResults: The results preview of the queries preview cacheable mounted. * It is a dictionary, indexed by the query preview query. * * params: As the request is handled in this component, we need * the extra params that will be used in the request. * * config: As the request is handled in this component, we need * the config that will be used in the request. */ const { queriesPreview: previewResults, params, config } = useState('queriesPreview'); /** * Query Preview key converted into a unique id. * * @returns The query hash. */ const queryPreviewHash = computed(() => getHashFromQueryPreviewInfo(props.queryPreviewInfo, { ...params.value, ...props.queryPreviewInfo.extraParams, })); provide('queryPreviewHash', queryPreviewHash); /** * Gets from the state the results preview of the query preview. * * @returns The results preview of the actual query preview. */ const queryPreviewResults = computed(() => { const resultsPreview = previewResults.value[queryPreviewHash.value]; return resultsPreview?.results ? { ...resultsPreview, results: resultsPreview.results.slice(0, props.maxItemsToRender), } : undefined; }); /** * The results to render from the state. * * @remarks The results list are provided with `items` key. It can be * concatenated with list items from components such as `BannersList`, `PromotedsList`, * `BaseGrid` or any component that injects the list. * * @returns A list of results. */ const results = computed(() => queryPreviewResults.value?.results); provide(LIST_ITEMS_KEY, results); /** * It injects the provided {@link FeatureLocation} of the selected query in the search request. * * @internal */ const injectedLocation = inject('location', 'none'); const location = typeof injectedLocation === 'object' && 'value' in injectedLocation ? injectedLocation.value : injectedLocation; /** * The computed request object to be used to retrieve the query preview results. * * @returns The search request object. */ const queryPreviewRequest = computed(() => { const origin = createOrigin({ feature: props.queryFeature, location, }); const filters = props.queryPreviewInfo.filters?.reduce((filtersList, filterId) => { const facetId = filterId.split(':')[0]; const rawFilter = createRawFilters([filterId])[0]; filtersList[facetId] = filtersList[facetId] ? filtersList[facetId].concat(rawFilter) : [rawFilter]; return filtersList; }, {}); return { query: props.queryPreviewInfo.query, rows: config.value.maxItemsToRequest, extraParams: { ...params.value, ...props.queryPreviewInfo.extraParams, }, filters, ...(origin && { origin }), }; }); /** * The debounce method to trigger the request after the debounceTimeMs defined * for cacheable queries. * * @returns The search request object. */ const emitQueryPreviewRequestUpdated = computed(() => debounce(request => { xBus.emit('QueryPreviewRequestUpdated', request, { priority: 0, replaceable: false }); }, props.debounceTimeMs)); /** * Initialises watcher to emit debounced requests, and first value for the requests. * * @internal */ watch(queryPreviewRequest, (newRequest, oldRequest) => { if (!deepEqual(newRequest, oldRequest)) { emitQueryPreviewRequestUpdated.value(newRequest); } }); const cachedQueryPreview = previewResults.value[queryPreviewHash.value]; // If the query has been saved it will emit load instead of the emitting the updated request. if (cachedQueryPreview?.status === 'success') { emit('load', queryPreviewHash.value); xBus.emit('QueryPreviewMounted', queryPreviewHash.value, { priority: 0, replaceable: false, }); } else { emitQueryPreviewRequestUpdated.value(queryPreviewRequest.value); } /** * Cancels the (remaining) requests when the component is destroyed * via the `debounce.cancel()` method. * If the prop 'persistInCache' is set to false, it also removes the QueryPreview * from the state when the component is destroyed. */ onBeforeUnmount(() => { emitQueryPreviewRequestUpdated.value.cancel(); xBus.emit('QueryPreviewUnmounted', { queryPreviewHash: queryPreviewHash.value, cache: props.persistInCache }, { priority: 0, replaceable: false, }); }); /** * Cancels the previous request when the debounced function changes (e.g: the debounceTimeMs * prop changes or there is a request in progress that cancels it). * * @param _new - The new debounced function. * @param old - The previous debounced function. * @internal */ watch(emitQueryPreviewRequestUpdated, (_new, old) => { old.cancel(); }); const queryPreviewResultsStatus = computed(() => queryPreviewResults.value?.status); /** * Emits an event when the query results are loaded or fail to load. * * @param status - The status of the query preview request. */ watch(queryPreviewResultsStatus, () => { if (queryPreviewResultsStatus.value === 'success') { emit(results.value?.length ? 'load' : 'error', queryPreviewHash.value); } else if (queryPreviewResultsStatus.value === 'error') { emit('error', queryPreviewHash.value); } }); const hasResults = computed(() => (queryPreviewResults.value?.totalResults ?? 0) > 0); /** * Render function to execute the `default` slot, binding `slotsProps` and getting only the * first `vNode` to avoid Fragments and Text root nodes. * If there are no results, nothing is rendered. * * @remarks `slotProps` must be values without Vue reactivity and located inside the * render-function to update the binding data properly. * * @returns The root `vNode` of the `default` slot or empty string if there are no results. */ function renderDefaultSlot() { const slotProps = { queryPreviewInfo: props.queryPreviewInfo, results: queryPreviewResults.value?.results, totalResults: queryPreviewResults.value?.totalResults, displayTagging: queryPreviewResults.value?.displayTagging, queryTagging: queryPreviewResults.value?.queryTagging, }; return hasResults.value ? slots.default?.(slotProps)[0] : ''; } /* Hack to render through a render-function, the `default` slot or, in its absence, the component itself. It is the alternative for the NoElement antipattern. */ const componentProps = { hasResults, queryPreviewResults }; return (slots.default ? renderDefaultSlot : componentProps); }, }); export { _sfc_main as default }; //# sourceMappingURL=query-preview.vue2.js.map