@empathyco/x-components
Version:
Empathy X Components
345 lines (342 loc) • 16.5 kB
JavaScript
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