@empathyco/x-components 6.0.0-alpha.21 → 6.0.0-alpha.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/core/index.js +3 -0
- package/core/index.js.map +1 -1
- package/design-system/deprecated-full-theme.css +2849 -2849
- package/docs/API-reference/api/x-components.configmutations.config.md +11 -0
- package/docs/API-reference/api/x-components.configmutations.md +29 -0
- package/docs/API-reference/api/x-components.configmutations.mergeconfig.md +24 -0
- package/docs/API-reference/api/x-components.configmutations.setconfig.md +24 -0
- package/docs/API-reference/api/x-components.createrelatedtagsquerygetter.md +26 -0
- package/docs/API-reference/api/x-components.createrelatedtagsquerygetteroptions.getrelatedtags.md +13 -0
- package/docs/API-reference/api/x-components.createrelatedtagsquerygetteroptions.md +20 -0
- package/docs/API-reference/api/x-components.empathizemutations.md +1 -1
- package/docs/API-reference/api/x-components.facetsmutations.md +1 -1
- package/docs/API-reference/api/x-components.historyqueriesmutations.md +1 -1
- package/docs/API-reference/api/x-components.historyqueriesstate.md +1 -1
- package/docs/API-reference/api/x-components.identifierresultsmutations.md +1 -1
- package/docs/API-reference/api/x-components.identifierresultsstate.md +1 -1
- package/docs/API-reference/api/x-components.md +8 -0
- package/docs/API-reference/api/x-components.mergeconfig.md +27 -0
- package/docs/API-reference/api/x-components.nextqueriesmutations.md +1 -1
- package/docs/API-reference/api/x-components.nextqueriesstate.md +1 -1
- package/docs/API-reference/api/x-components.popularsearchesmutations.md +1 -1
- package/docs/API-reference/api/x-components.queriespreviewmutations.md +1 -1
- package/docs/API-reference/api/x-components.querymutations.md +20 -0
- package/docs/API-reference/api/x-components.querymutations.setquery.md +24 -0
- package/docs/API-reference/api/x-components.querystate.md +20 -0
- package/docs/API-reference/api/x-components.querystate.query.md +13 -0
- package/docs/API-reference/api/x-components.querysuggestionsmutations.md +1 -1
- package/docs/API-reference/api/x-components.querysuggestionsstate.md +1 -1
- package/docs/API-reference/api/x-components.recommendationsmutations.md +1 -1
- package/docs/API-reference/api/x-components.relatedpromptsmutations.md +1 -1
- package/docs/API-reference/api/x-components.relatedpromptsstate.md +1 -1
- package/docs/API-reference/api/x-components.relatedtagsmutations.md +1 -1
- package/docs/API-reference/api/x-components.relatedtagsstate.md +1 -1
- package/docs/API-reference/api/x-components.searchboxmutations.md +1 -1
- package/docs/API-reference/api/x-components.searchboxstate.md +1 -1
- package/docs/API-reference/api/x-components.searchmutations.md +1 -1
- package/docs/API-reference/api/x-components.searchstate.md +1 -1
- package/docs/API-reference/api/x-components.semanticqueriesmutations.md +1 -1
- package/docs/API-reference/api/x-components.semanticqueriesstate.md +1 -1
- package/docs/API-reference/api/x-components.setconfig.md +27 -0
- package/docs/API-reference/api/x-components.setquery.md +25 -0
- package/docs/API-reference/api/x-components.taggingmutations.md +1 -1
- package/docs/API-reference/api/x-components.urlmutations.md +1 -1
- package/docs/API-reference/api/x-components.urlstate.md +1 -1
- package/js/components/base-dropdown.vue.js.map +1 -1
- package/js/components/base-dropdown.vue2.js +1 -7
- package/js/components/base-dropdown.vue2.js.map +1 -1
- package/js/components/result/base-result-image.vue.js.map +1 -1
- package/js/components/result/base-result-image.vue2.js +2 -2
- package/js/components/result/base-result-image.vue2.js.map +1 -1
- package/js/index.js +3 -0
- package/js/index.js.map +1 -1
- package/js/x-modules/device/store/emitters.js +1 -0
- package/js/x-modules/device/store/emitters.js.map +1 -1
- package/js/x-modules/empathize/store/emitters.js +1 -0
- package/js/x-modules/empathize/store/emitters.js.map +1 -1
- package/js/x-modules/experience-controls/store/emitters.js +1 -0
- package/js/x-modules/experience-controls/store/emitters.js.map +1 -1
- package/js/x-modules/extra-params/store/emitters.js +1 -0
- package/js/x-modules/extra-params/store/emitters.js.map +1 -1
- package/js/x-modules/history-queries/store/emitters.js +1 -0
- package/js/x-modules/history-queries/store/emitters.js.map +1 -1
- package/js/x-modules/identifier-results/store/emitters.js +1 -0
- package/js/x-modules/identifier-results/store/emitters.js.map +1 -1
- package/js/x-modules/next-queries/store/emitters.js +1 -0
- package/js/x-modules/next-queries/store/emitters.js.map +1 -1
- package/js/x-modules/queries-preview/store/emitters.js +1 -0
- package/js/x-modules/queries-preview/store/emitters.js.map +1 -1
- package/js/x-modules/query-suggestions/store/emitters.js +1 -0
- package/js/x-modules/query-suggestions/store/emitters.js.map +1 -1
- package/js/x-modules/recommendations/store/emitters.js +1 -0
- package/js/x-modules/recommendations/store/emitters.js.map +1 -1
- package/js/x-modules/related-prompts/store/emitters.js +1 -0
- package/js/x-modules/related-prompts/store/emitters.js.map +1 -1
- package/js/x-modules/related-tags/store/emitters.js +1 -0
- package/js/x-modules/related-tags/store/emitters.js.map +1 -1
- package/js/x-modules/scroll/store/emitters.js +1 -0
- package/js/x-modules/scroll/store/emitters.js.map +1 -1
- package/js/x-modules/search/store/emitters.js +1 -0
- package/js/x-modules/search/store/emitters.js.map +1 -1
- package/js/x-modules/search/store/module.js +2 -1
- package/js/x-modules/search/store/module.js.map +1 -1
- package/js/x-modules/search-box/store/emitters.js +1 -0
- package/js/x-modules/search-box/store/emitters.js.map +1 -1
- package/js/x-modules/url/store/emitters.js +1 -0
- package/js/x-modules/url/store/emitters.js.map +1 -1
- package/package.json +2 -2
- package/report/x-components.api.json +690 -34
- package/report/x-components.api.md +49 -7
- package/types/components/base-dropdown.vue.d.ts.map +1 -1
- package/types/store/index.d.ts +4 -1
- package/types/store/index.d.ts.map +1 -1
|
@@ -11,7 +11,7 @@ URL store state.
|
|
|
11
11
|
```typescript
|
|
12
12
|
export interface UrlState extends QueryState, UrlParams
|
|
13
13
|
```
|
|
14
|
-
**Extends:** QueryState
|
|
14
|
+
**Extends:** [QueryState](./x-components.querystate.md)<!-- -->, [UrlParams](./x-components.urlparams.md)
|
|
15
15
|
|
|
16
16
|
## Properties
|
|
17
17
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-dropdown.vue.js","sources":["../../../src/components/base-dropdown.vue"],"sourcesContent":["<template>\n <div\n ref=\"rootRef\"\n @keydown=\"updateSearchBuffer\"\n @keydown.down.prevent=\"highlightNextItem\"\n @keydown.up.prevent=\"highlightPreviousItem\"\n :class=\"dropdownCSSClasses\"\n class=\"x-dropdown\"\n >\n <button\n ref=\"toggleButtonRef\"\n @click=\"toggle\"\n @keydown.up.down.prevent.stop=\"open\"\n class=\"x-dropdown__toggle\"\n data-test=\"dropdown-toggle\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n :aria-expanded=\"isOpen.toString()\"\n :aria-controls=\"listId\"\n :aria-label=\"ariaLabel\"\n aria-autocomplete=\"none\"\n >\n <!--\n @slot Used to render the contents of the dropdown toggle button. If not provided, it uses\n the item slot as fallback.\n @binding {string|number|Identifiable} item - The item data to render.\n @binding {boolean} isOpen - True if the dropdown is opened, and false if it is closed.\n -->\n <slot v-if=\"hasToggleSlot\" :isOpen=\"isOpen\" :item=\"modelValue\" name=\"toggle\">\n {{ modelValue }}\n </slot>\n <slot v-else :item=\"modelValue\" name=\"item\">{{ modelValue }}</slot>\n </button>\n\n <component :is=\"animation\">\n <ul\n v-show=\"isOpen\"\n @keydown.end=\"highlightLastItem\"\n @keydown.esc=\"closeAndFocusToggleButton\"\n @keydown.home=\"highlightFirstItem\"\n :id=\"listId\"\n class=\"x-dropdown__items-list\"\n data-test=\"dropdown-list\"\n role=\"listbox\"\n tabindex=\"-1\"\n >\n <li v-for=\"(item, index) in items\" :key=\"item.id || item\" class=\"x-dropdown__list-item\">\n <button\n :ref=\"el => (itemsButtonRefs[index] = el)\"\n @click=\"emitSelectedItemChanged(item)\"\n :aria-selected=\"(item === modelValue).toString()\"\n :class=\"itemsCSSClasses[index]\"\n class=\"x-dropdown__item\"\n data-test=\"dropdown-item\"\n role=\"option\"\n >\n <!--\n @slot (required) Used to render each one of the items content, and as fallback\n for the toggle button content slot if it is not provided.\n @binding {string|number|Identifiable} item - Item to render\n @binding {boolean} isHighlighted - True when the item has the focus.\n @binding {boolean} isSelected - True when the item is selected.\n -->\n <slot\n :isHighlighted=\"index === highlightedItemIndex\"\n :isSelected=\"item === modelValue\"\n :item=\"item\"\n name=\"item\"\n >\n {{ item }}\n </slot>\n </button>\n </li>\n </ul>\n </component>\n </div>\n</template>\n\n<script lang=\"ts\">\n import { Identifiable } from '@empathyco/x-types';\n import { computed, defineComponent, nextTick, onBeforeUnmount, PropType, ref, watch } from 'vue';\n import { AnimationProp } from '../types';\n import { debounceFunction, normalizeString, getTargetElement, isInRange } from '../utils';\n import { NoAnimation } from './animations';\n\n type DropdownItem = string | number | Identifiable;\n let dropdownCount = 0;\n\n /**\n * Dropdown component that mimics a Select element behavior, but with the option\n * to customize the toggle button and each item contents.\n */\n export default defineComponent({\n name: 'BaseDropdown',\n props: {\n /** List of items to display.*/\n items: {\n type: Array as PropType<DropdownItem[]>,\n required: true\n },\n /** The selected item. */\n modelValue: {\n type: null as unknown as PropType<DropdownItem | null>,\n validator: (v: any) =>\n typeof v === 'string' || typeof v === 'number' || typeof v === 'object' || v === null,\n required: true\n },\n /** Description of what the dropdown is used for. */\n ariaLabel: String,\n /**\n * Animation component to use for expanding the dropdown. This is a single element animation,\n * so only `<transition>` components are allowed.\n */\n animation: {\n type: AnimationProp,\n default: () => NoAnimation\n },\n /** Time to wait without receiving any keystroke before resetting the items search query. */\n searchTimeoutMs: {\n type: Number,\n default: 1000\n }\n },\n emits: ['update:modelValue'],\n setup(props, { emit, slots }) {\n const rootRef = ref<HTMLElement>();\n /** The button that opens and closes the list of options. */\n const toggleButtonRef = ref<HTMLButtonElement>();\n /** Array containing the dropdown list buttons HTMLElements. */\n const itemsButtonRefs = ref<HTMLButtonElement[]>([]);\n\n /** Property to check whether the dropdown is expanded or closed. */\n const isOpen = ref(false);\n /** Index of the element that has the focus in the list. -1 means no element has focus. */\n const highlightedItemIndex = ref(-1);\n /** String to search for the first element that starts with it. */\n const searchBuffer = ref('');\n\n /** Resets the search buffer after a certain time has passed. */\n let restartResetSearchTimeout: () => void;\n /* Unique ID to identify the dropdown. */\n const listId = `x-dropdown-${dropdownCount++}`;\n\n /**\n * Dynamic CSS classes to add to the dropdown root element.\n *\n * @returns An object containing the CSS classes to add to the dropdown root element.\n */\n const dropdownCSSClasses = computed(() => ({ 'x-dropdown--is-open': isOpen }));\n\n /**\n * Dynamic CSS classes to add to each one of the items.\n *\n * @returns An object containing the CSS classes to add to each item.\n */\n const itemsCSSClasses = computed(() =>\n props.items.map((item, index) => ({\n 'x-dropdown__item--is-selected': props.modelValue === item,\n 'x-dropdown__item--is-highlighted': highlightedItemIndex.value === index\n }))\n );\n\n /* Opens the dropdown. */\n const open = () => (isOpen.value = true);\n /* Closes the dropdown. */\n const close = () => (isOpen.value = false);\n /* Toggles the dropdown. */\n const toggle = () => (isOpen.value = !isOpen.value);\n\n /**\n * Closes the modal and focuses the toggle button.\n */\n function closeAndFocusToggleButton() {\n close();\n toggleButtonRef.value?.focus();\n }\n\n /**\n * Emits the event that the selected item has changed.\n *\n * @param item - The new selected item.\n */\n function emitSelectedItemChanged(item: DropdownItem) {\n emit('update:modelValue', item);\n closeAndFocusToggleButton();\n }\n\n /**\n * Highlights the item after the one that is currently highlighted.\n */\n function highlightNextItem() {\n open();\n highlightedItemIndex.value = (highlightedItemIndex.value + 1) % props.items.length;\n }\n\n /**\n * Highlights the item before the one that is currently highlighted.\n */\n function highlightPreviousItem() {\n const currentIndex = highlightedItemIndex.value;\n open();\n highlightedItemIndex.value = currentIndex > 0 ? currentIndex - 1 : props.items.length - 1;\n }\n\n /**\n * Highlights the first of the provided items.\n */\n function highlightFirstItem() {\n highlightedItemIndex.value = 0;\n }\n\n /**\n * Highlights the last of the provided items.\n */\n function highlightLastItem() {\n highlightedItemIndex.value = props.items.length - 1;\n }\n\n /**\n * Updates the variable that is used to search in the filters.\n *\n * @param event - The event coming from the user typing.\n */\n function updateSearchBuffer(event: KeyboardEvent) {\n if (/^\\w$/.test(event.key)) {\n const key = event.key;\n searchBuffer.value += key;\n restartResetSearchTimeout();\n }\n }\n\n /**\n * Resets the search buffer.\n */\n function resetSearchBuffer() {\n searchBuffer.value = '';\n }\n\n /**\n * Closes the dropdown if the passed event has happened on an element out of the dropdown.\n *\n * @param event - The event to check if it has happened out of the dropdown component.\n */\n function closeIfEventIsOutOfDropdown(event: MouseEvent | TouchEvent | FocusEvent) {\n if (!rootRef.value?.contains(getTargetElement(event))) {\n close();\n }\n }\n\n /**\n * Adds listeners to the document element to detect if the focus has moved out from the\n * dropdown.\n */\n function addDocumentCloseListeners() {\n document.addEventListener('mousedown', closeIfEventIsOutOfDropdown);\n document.addEventListener('touchstart', closeIfEventIsOutOfDropdown);\n document.addEventListener('focusin', closeIfEventIsOutOfDropdown);\n }\n\n /**\n * Removes the listeners of the document element to detect if the focus has moved out from the\n * dropdown.\n */\n function removeDocumentCloseListeners() {\n document.removeEventListener('mousedown', closeIfEventIsOutOfDropdown);\n document.removeEventListener('touchstart', closeIfEventIsOutOfDropdown);\n document.removeEventListener('focusin', closeIfEventIsOutOfDropdown);\n }\n\n /**\n * Highlights the item that matches the search buffer. To do so it checks the list buttons\n * text content. It highlights items following this priority:\n * - If an element is already highlighted, it starts searching from that element.\n * - If no element is found starting from the previously highlighted, it returns the first one.\n * - If no element is found matching the search query it highlights the first element.\n *\n * @param search - The search string to find in the HTML.\n */\n watch(searchBuffer, search => {\n if (search) {\n const normalizedSearch = normalizeString(search);\n const matchingIndices = itemsButtonRefs?.value?.reduce<number[]>(\n (matchingIndices, button, index) => {\n const safeButtonWordCharacters = button.textContent!.replace(/[^\\w]/g, '');\n const normalizedButtonText = normalizeString(safeButtonWordCharacters);\n if (normalizedButtonText.startsWith(normalizedSearch)) {\n matchingIndices.push(index);\n }\n return matchingIndices;\n },\n []\n );\n highlightedItemIndex.value =\n // First matching item starting to search from the current highlighted element\n matchingIndices?.find(index => index >= highlightedItemIndex.value) ??\n // First matching item\n matchingIndices?.[0] ??\n // First item\n 0;\n }\n });\n\n /**\n * Updates the debounced function to reset the search.\n *\n * @param searchTimeoutMs - The new milliseconds that have to pass without typing before\n * resetting the search.\n */\n watch(\n () => props.searchTimeoutMs,\n searchTimeoutMs => {\n restartResetSearchTimeout = debounceFunction(resetSearchBuffer, searchTimeoutMs);\n },\n { immediate: true }\n );\n\n /**\n * Focuses the DOM element which matches the `highlightedItemIndex`.\n *\n * @param highlightedItemIndex - The index of the HTML element to focus.\n */\n watch(\n highlightedItemIndex,\n highlightedItemIndex => {\n nextTick(() => {\n if (itemsButtonRefs && isInRange(highlightedItemIndex, [0, props.items.length - 1])) {\n const newItem = itemsButtonRefs?.value?.[highlightedItemIndex];\n newItem?.focus();\n }\n });\n },\n { immediate: true }\n );\n\n /**\n * When the dropdown is open it sets the focused element to the one that is selected.\n *\n * @param isOpen - True if the dropdown is open, false otherwise.\n */\n watch(isOpen, isOpen => {\n highlightedItemIndex.value = isOpen\n ? props.modelValue === null\n ? 0\n : props.items.indexOf(props.modelValue)\n : -1;\n });\n\n /**\n * Adds and removes listeners to close the dropdown when it loses the focus.\n *\n * @param isOpen - True if the dropdown is open, false otherwise.\n */\n watch(isOpen, isOpen => {\n /*\n * Because there is an issue with Firefox in macOS and Safari that doesn't focus the target\n * element of the `mousedown` events, the `focusout` event `relatedTarget` property can't be\n * used to detect whether the user has blurred the dropdown. The hack here is to use\n * document listeners that have the side effect of losing the focus.\n */\n if (isOpen) {\n addDocumentCloseListeners();\n } else {\n removeDocumentCloseListeners();\n }\n });\n\n /**\n * If the dropdown is destroyed before removing the document listeners, it ensures that they\n * are removed too.\n */\n onBeforeUnmount(() => {\n removeDocumentCloseListeners();\n });\n\n return {\n hasToggleSlot: !!slots.toggle,\n closeAndFocusToggleButton,\n dropdownCSSClasses,\n emitSelectedItemChanged,\n highlightFirstItem,\n highlightLastItem,\n highlightNextItem,\n highlightPreviousItem,\n highlightedItemIndex,\n isOpen,\n itemsButtonRefs,\n itemsCSSClasses,\n listId,\n open,\n rootRef,\n toggle,\n toggleButtonRef,\n updateSearchBuffer\n };\n }\n });\n</script>\n\n<style lang=\"css\" scoped>\n .x-dropdown {\n position: relative;\n }\n\n .x-dropdown__items-list {\n z-index: 1;\n list-style: none;\n position: absolute;\n padding: 0;\n margin: 0;\n top: calc(100% + var(--x-size-gap-dropdown-default, 0));\n }\n</style>\n\n<docs lang=\"mdx\">\n## Example\n\nThe `Dropdown` component is a simple yet customizable select component. The component needs to work\nthe list of items available to select, which are passed using the `items` prop, and the selected\nitem, which is passed in using the `value` prop.\n\nThe supported items must be an array that can contain unique strings, unique numbers, or objects\nwith a unique `id` property.\n\nThe content of each item can be customized using the `item` slot, which apart from the data of the\nitem, it also receives via prop if the element is selected or highlighted.\n\nThere `toggle` slot can be used to customize the button that opens the dropdown. If this is not\nprovided, the `item` slot will be used for that.\n\n```vue\n<template>\n <BaseDropdown v-model=\"value\" :items=\"items\">\n <template #toggle=\"{ item, isOpen }\">{{ item }} {{ isOpen ? '🔼' : '🔽' }}️</template>\n\n <template #item=\"{ item, isSelected, isHighlighted }\">\n <span v-if=\"isHighlighted\">🟢</span>\n <span v-if=\"isSelected\">✅</span>\n <span>{{ item }}</span>\n </template>\n </BaseDropdown>\n</template>\n\n<script>\n import { BaseDropdown } from '@empathyco/x-components';\n\n export default {\n name: 'DropdownTest',\n components: {\n BaseDropdown\n },\n data() {\n return {\n items: ['a', 2, { id: '3' }],\n value: 'a'\n };\n }\n };\n</script>\n```\n</docs>\n"],"names":["_openBlock","_createElementBlock","_withKeys","_withModifiers","dropdownCSSClasses","_normalizeClass","_createElementVNode","listId","ariaLabel","hasToggleSlot","_createCommentVNode","isOpen","_createTextVNode","_toDisplayString","modelValue","_createBlock","_resolveDynamicComponent","_withCtx","_withDirectives","_Fragment","_renderList","itemsButtonRefs","itemsCSSClasses","_renderSlot"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-dropdown.vue.js","sources":["../../../src/components/base-dropdown.vue"],"sourcesContent":["<template>\n <div\n ref=\"rootRef\"\n @keydown=\"updateSearchBuffer\"\n @keydown.down.prevent=\"highlightNextItem\"\n @keydown.up.prevent=\"highlightPreviousItem\"\n :class=\"dropdownCSSClasses\"\n class=\"x-dropdown\"\n >\n <button\n ref=\"toggleButtonRef\"\n @click=\"toggle\"\n @keydown.up.down.prevent.stop=\"open\"\n class=\"x-dropdown__toggle\"\n data-test=\"dropdown-toggle\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n :aria-expanded=\"isOpen.toString()\"\n :aria-controls=\"listId\"\n :aria-label=\"ariaLabel\"\n aria-autocomplete=\"none\"\n >\n <!--\n @slot Used to render the contents of the dropdown toggle button. If not provided, it uses\n the item slot as fallback.\n @binding {string|number|Identifiable} item - The item data to render.\n @binding {boolean} isOpen - True if the dropdown is opened, and false if it is closed.\n -->\n <slot v-if=\"hasToggleSlot\" :isOpen=\"isOpen\" :item=\"modelValue\" name=\"toggle\">\n {{ modelValue }}\n </slot>\n <slot v-else :item=\"modelValue\" name=\"item\">{{ modelValue }}</slot>\n </button>\n\n <component :is=\"animation\">\n <ul\n v-show=\"isOpen\"\n @keydown.end=\"highlightLastItem\"\n @keydown.esc=\"closeAndFocusToggleButton\"\n @keydown.home=\"highlightFirstItem\"\n :id=\"listId\"\n class=\"x-dropdown__items-list\"\n data-test=\"dropdown-list\"\n role=\"listbox\"\n tabindex=\"-1\"\n >\n <li v-for=\"(item, index) in items\" :key=\"item.id || item\" class=\"x-dropdown__list-item\">\n <button\n :ref=\"el => (itemsButtonRefs[index] = el)\"\n @click=\"emitSelectedItemChanged(item)\"\n :aria-selected=\"(item === modelValue).toString()\"\n :class=\"itemsCSSClasses[index]\"\n class=\"x-dropdown__item\"\n data-test=\"dropdown-item\"\n role=\"option\"\n >\n <!--\n @slot (required) Used to render each one of the items content, and as fallback\n for the toggle button content slot if it is not provided.\n @binding {string|number|Identifiable} item - Item to render\n @binding {boolean} isHighlighted - True when the item has the focus.\n @binding {boolean} isSelected - True when the item is selected.\n -->\n <slot\n :isHighlighted=\"index === highlightedItemIndex\"\n :isSelected=\"item === modelValue\"\n :item=\"item\"\n name=\"item\"\n >\n {{ item }}\n </slot>\n </button>\n </li>\n </ul>\n </component>\n </div>\n</template>\n\n<script lang=\"ts\">\n import { Identifiable } from '@empathyco/x-types';\n import { computed, defineComponent, nextTick, onBeforeUnmount, PropType, ref, watch } from 'vue';\n import { AnimationProp } from '../types';\n import { debounceFunction, normalizeString, getTargetElement } from '../utils';\n import { NoAnimation } from './animations';\n\n type DropdownItem = string | number | Identifiable;\n let dropdownCount = 0;\n\n /**\n * Dropdown component that mimics a Select element behavior, but with the option\n * to customize the toggle button and each item contents.\n */\n export default defineComponent({\n name: 'BaseDropdown',\n props: {\n /** List of items to display.*/\n items: {\n type: Array as PropType<DropdownItem[]>,\n required: true\n },\n /** The selected item. */\n modelValue: {\n type: null as unknown as PropType<DropdownItem | null>,\n validator: (v: any) =>\n typeof v === 'string' || typeof v === 'number' || typeof v === 'object' || v === null,\n required: true\n },\n /** Description of what the dropdown is used for. */\n ariaLabel: String,\n /**\n * Animation component to use for expanding the dropdown. This is a single element animation,\n * so only `<transition>` components are allowed.\n */\n animation: {\n type: AnimationProp,\n default: () => NoAnimation\n },\n /** Time to wait without receiving any keystroke before resetting the items search query. */\n searchTimeoutMs: {\n type: Number,\n default: 1000\n }\n },\n emits: ['update:modelValue'],\n setup(props, { emit, slots }) {\n const rootRef = ref<HTMLElement>();\n /** The button that opens and closes the list of options. */\n const toggleButtonRef = ref<HTMLButtonElement>();\n /** Array containing the dropdown list buttons HTMLElements. */\n const itemsButtonRefs = ref<HTMLButtonElement[]>([]);\n\n /** Property to check whether the dropdown is expanded or closed. */\n const isOpen = ref(false);\n /** Index of the element that has the focus in the list. -1 means no element has focus. */\n const highlightedItemIndex = ref(-1);\n /** String to search for the first element that starts with it. */\n const searchBuffer = ref('');\n\n /** Resets the search buffer after a certain time has passed. */\n let restartResetSearchTimeout: () => void;\n /* Unique ID to identify the dropdown. */\n const listId = `x-dropdown-${dropdownCount++}`;\n\n /**\n * Dynamic CSS classes to add to the dropdown root element.\n *\n * @returns An object containing the CSS classes to add to the dropdown root element.\n */\n const dropdownCSSClasses = computed(() => ({ 'x-dropdown--is-open': isOpen }));\n\n /**\n * Dynamic CSS classes to add to each one of the items.\n *\n * @returns An object containing the CSS classes to add to each item.\n */\n const itemsCSSClasses = computed(() =>\n props.items.map((item, index) => ({\n 'x-dropdown__item--is-selected': props.modelValue === item,\n 'x-dropdown__item--is-highlighted': highlightedItemIndex.value === index\n }))\n );\n\n /* Opens the dropdown. */\n const open = () => (isOpen.value = true);\n /* Closes the dropdown. */\n const close = () => (isOpen.value = false);\n /* Toggles the dropdown. */\n const toggle = () => (isOpen.value = !isOpen.value);\n\n /**\n * Closes the modal and focuses the toggle button.\n */\n function closeAndFocusToggleButton() {\n close();\n toggleButtonRef.value?.focus();\n }\n\n /**\n * Emits the event that the selected item has changed.\n *\n * @param item - The new selected item.\n */\n function emitSelectedItemChanged(item: DropdownItem) {\n emit('update:modelValue', item);\n closeAndFocusToggleButton();\n }\n\n /**\n * Highlights the item after the one that is currently highlighted.\n */\n function highlightNextItem() {\n open();\n highlightedItemIndex.value = (highlightedItemIndex.value + 1) % props.items.length;\n }\n\n /**\n * Highlights the item before the one that is currently highlighted.\n */\n function highlightPreviousItem() {\n const currentIndex = highlightedItemIndex.value;\n open();\n highlightedItemIndex.value = currentIndex > 0 ? currentIndex - 1 : props.items.length - 1;\n }\n\n /**\n * Highlights the first of the provided items.\n */\n function highlightFirstItem() {\n highlightedItemIndex.value = 0;\n }\n\n /**\n * Highlights the last of the provided items.\n */\n function highlightLastItem() {\n highlightedItemIndex.value = props.items.length - 1;\n }\n\n /**\n * Updates the variable that is used to search in the filters.\n *\n * @param event - The event coming from the user typing.\n */\n function updateSearchBuffer(event: KeyboardEvent) {\n if (/^\\w$/.test(event.key)) {\n const key = event.key;\n searchBuffer.value += key;\n restartResetSearchTimeout();\n }\n }\n\n /**\n * Resets the search buffer.\n */\n function resetSearchBuffer() {\n searchBuffer.value = '';\n }\n\n /**\n * Closes the dropdown if the passed event has happened on an element out of the dropdown.\n *\n * @param event - The event to check if it has happened out of the dropdown component.\n */\n function closeIfEventIsOutOfDropdown(event: MouseEvent | TouchEvent | FocusEvent) {\n if (!rootRef.value?.contains(getTargetElement(event))) {\n close();\n }\n }\n\n /**\n * Adds listeners to the document element to detect if the focus has moved out from the\n * dropdown.\n */\n function addDocumentCloseListeners() {\n document.addEventListener('mousedown', closeIfEventIsOutOfDropdown);\n document.addEventListener('touchstart', closeIfEventIsOutOfDropdown);\n document.addEventListener('focusin', closeIfEventIsOutOfDropdown);\n }\n\n /**\n * Removes the listeners of the document element to detect if the focus has moved out from the\n * dropdown.\n */\n function removeDocumentCloseListeners() {\n document.removeEventListener('mousedown', closeIfEventIsOutOfDropdown);\n document.removeEventListener('touchstart', closeIfEventIsOutOfDropdown);\n document.removeEventListener('focusin', closeIfEventIsOutOfDropdown);\n }\n\n /**\n * Highlights the item that matches the search buffer. To do so it checks the list buttons\n * text content. It highlights items following this priority:\n * - If an element is already highlighted, it starts searching from that element.\n * - If no element is found starting from the previously highlighted, it returns the first one.\n * - If no element is found matching the search query it highlights the first element.\n *\n * @param search - The search string to find in the HTML.\n */\n watch(searchBuffer, search => {\n if (search) {\n const normalizedSearch = normalizeString(search);\n const matchingIndices = itemsButtonRefs?.value?.reduce<number[]>(\n (matchingIndices, button, index) => {\n const safeButtonWordCharacters = button.textContent!.replace(/[^\\w]/g, '');\n const normalizedButtonText = normalizeString(safeButtonWordCharacters);\n if (normalizedButtonText.startsWith(normalizedSearch)) {\n matchingIndices.push(index);\n }\n return matchingIndices;\n },\n []\n );\n highlightedItemIndex.value =\n // First matching item starting to search from the current highlighted element\n matchingIndices?.find(index => index >= highlightedItemIndex.value) ??\n // First matching item\n matchingIndices?.[0] ??\n // First item\n 0;\n }\n });\n\n /**\n * Updates the debounced function to reset the search.\n *\n * @param searchTimeoutMs - The new milliseconds that have to pass without typing before\n * resetting the search.\n */\n watch(\n () => props.searchTimeoutMs,\n searchTimeoutMs => {\n restartResetSearchTimeout = debounceFunction(resetSearchBuffer, searchTimeoutMs);\n },\n { immediate: true }\n );\n\n /**\n * Focuses the DOM element which matches the `highlightedItemIndex`.\n *\n * @param highlightedItemIndex - The index of the HTML element to focus.\n */\n watch(\n highlightedItemIndex,\n highlightedItemIndex => {\n nextTick(() => itemsButtonRefs.value[highlightedItemIndex]?.focus());\n },\n { immediate: true }\n );\n\n /**\n * When the dropdown is open it sets the focused element to the one that is selected.\n *\n * @param isOpen - True if the dropdown is open, false otherwise.\n */\n watch(isOpen, isOpen => {\n highlightedItemIndex.value = isOpen\n ? props.modelValue === null\n ? 0\n : props.items.indexOf(props.modelValue)\n : -1;\n });\n\n /**\n * Adds and removes listeners to close the dropdown when it loses the focus.\n *\n * @param isOpen - True if the dropdown is open, false otherwise.\n */\n watch(isOpen, isOpen => {\n /*\n * Because there is an issue with Firefox in macOS and Safari that doesn't focus the target\n * element of the `mousedown` events, the `focusout` event `relatedTarget` property can't be\n * used to detect whether the user has blurred the dropdown. The hack here is to use\n * document listeners that have the side effect of losing the focus.\n */\n if (isOpen) {\n addDocumentCloseListeners();\n } else {\n removeDocumentCloseListeners();\n }\n });\n\n /**\n * If the dropdown is destroyed before removing the document listeners, it ensures that they\n * are removed too.\n */\n onBeforeUnmount(() => {\n removeDocumentCloseListeners();\n });\n\n return {\n hasToggleSlot: !!slots.toggle,\n closeAndFocusToggleButton,\n dropdownCSSClasses,\n emitSelectedItemChanged,\n highlightFirstItem,\n highlightLastItem,\n highlightNextItem,\n highlightPreviousItem,\n highlightedItemIndex,\n isOpen,\n itemsButtonRefs,\n itemsCSSClasses,\n listId,\n open,\n rootRef,\n toggle,\n toggleButtonRef,\n updateSearchBuffer\n };\n }\n });\n</script>\n\n<style lang=\"css\" scoped>\n .x-dropdown {\n position: relative;\n }\n\n .x-dropdown__items-list {\n z-index: 1;\n list-style: none;\n position: absolute;\n padding: 0;\n margin: 0;\n top: calc(100% + var(--x-size-gap-dropdown-default, 0));\n }\n</style>\n\n<docs lang=\"mdx\">\n## Example\n\nThe `Dropdown` component is a simple yet customizable select component. The component needs to work\nthe list of items available to select, which are passed using the `items` prop, and the selected\nitem, which is passed in using the `value` prop.\n\nThe supported items must be an array that can contain unique strings, unique numbers, or objects\nwith a unique `id` property.\n\nThe content of each item can be customized using the `item` slot, which apart from the data of the\nitem, it also receives via prop if the element is selected or highlighted.\n\nThere `toggle` slot can be used to customize the button that opens the dropdown. If this is not\nprovided, the `item` slot will be used for that.\n\n```vue\n<template>\n <BaseDropdown v-model=\"value\" :items=\"items\">\n <template #toggle=\"{ item, isOpen }\">{{ item }} {{ isOpen ? '🔼' : '🔽' }}️</template>\n\n <template #item=\"{ item, isSelected, isHighlighted }\">\n <span v-if=\"isHighlighted\">🟢</span>\n <span v-if=\"isSelected\">✅</span>\n <span>{{ item }}</span>\n </template>\n </BaseDropdown>\n</template>\n\n<script>\n import { BaseDropdown } from '@empathyco/x-components';\n\n export default {\n name: 'DropdownTest',\n components: {\n BaseDropdown\n },\n data() {\n return {\n items: ['a', 2, { id: '3' }],\n value: 'a'\n };\n }\n };\n</script>\n```\n</docs>\n"],"names":["_openBlock","_createElementBlock","_withKeys","_withModifiers","dropdownCSSClasses","_normalizeClass","_createElementVNode","listId","ariaLabel","hasToggleSlot","_createCommentVNode","isOpen","_createTextVNode","_toDisplayString","modelValue","_createBlock","_resolveDynamicComponent","_withCtx","_withDirectives","_Fragment","_renderList","itemsButtonRefs","itemsCSSClasses","_renderSlot"],"mappings":"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"}
|
|
@@ -3,7 +3,6 @@ import { AnimationProp } from '../types/animation-prop.js';
|
|
|
3
3
|
import { debounce } from '../utils/debounce.js';
|
|
4
4
|
import { getTargetElement } from '../utils/html.js';
|
|
5
5
|
import { normalizeString } from '../utils/normalize.js';
|
|
6
|
-
import { isInRange } from '../utils/number.js';
|
|
7
6
|
import '../utils/storage.js';
|
|
8
7
|
import './animations/animate-width.vue2.js';
|
|
9
8
|
import './animations/animate-width.vue3.js';
|
|
@@ -230,12 +229,7 @@ var _sfc_main = defineComponent({
|
|
|
230
229
|
* @param highlightedItemIndex - The index of the HTML element to focus.
|
|
231
230
|
*/
|
|
232
231
|
watch(highlightedItemIndex, highlightedItemIndex => {
|
|
233
|
-
nextTick(() =>
|
|
234
|
-
if (itemsButtonRefs && isInRange(highlightedItemIndex, [0, props.items.length - 1])) {
|
|
235
|
-
const newItem = itemsButtonRefs?.value?.[highlightedItemIndex];
|
|
236
|
-
newItem?.focus();
|
|
237
|
-
}
|
|
238
|
-
});
|
|
232
|
+
nextTick(() => itemsButtonRefs.value[highlightedItemIndex]?.focus());
|
|
239
233
|
}, { immediate: true });
|
|
240
234
|
/**
|
|
241
235
|
* When the dropdown is open it sets the focused element to the one that is selected.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-dropdown.vue2.js","sources":["../../../src/components/base-dropdown.vue"],"sourcesContent":["<template>\n <div\n ref=\"rootRef\"\n @keydown=\"updateSearchBuffer\"\n @keydown.down.prevent=\"highlightNextItem\"\n @keydown.up.prevent=\"highlightPreviousItem\"\n :class=\"dropdownCSSClasses\"\n class=\"x-dropdown\"\n >\n <button\n ref=\"toggleButtonRef\"\n @click=\"toggle\"\n @keydown.up.down.prevent.stop=\"open\"\n class=\"x-dropdown__toggle\"\n data-test=\"dropdown-toggle\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n :aria-expanded=\"isOpen.toString()\"\n :aria-controls=\"listId\"\n :aria-label=\"ariaLabel\"\n aria-autocomplete=\"none\"\n >\n <!--\n @slot Used to render the contents of the dropdown toggle button. If not provided, it uses\n the item slot as fallback.\n @binding {string|number|Identifiable} item - The item data to render.\n @binding {boolean} isOpen - True if the dropdown is opened, and false if it is closed.\n -->\n <slot v-if=\"hasToggleSlot\" :isOpen=\"isOpen\" :item=\"modelValue\" name=\"toggle\">\n {{ modelValue }}\n </slot>\n <slot v-else :item=\"modelValue\" name=\"item\">{{ modelValue }}</slot>\n </button>\n\n <component :is=\"animation\">\n <ul\n v-show=\"isOpen\"\n @keydown.end=\"highlightLastItem\"\n @keydown.esc=\"closeAndFocusToggleButton\"\n @keydown.home=\"highlightFirstItem\"\n :id=\"listId\"\n class=\"x-dropdown__items-list\"\n data-test=\"dropdown-list\"\n role=\"listbox\"\n tabindex=\"-1\"\n >\n <li v-for=\"(item, index) in items\" :key=\"item.id || item\" class=\"x-dropdown__list-item\">\n <button\n :ref=\"el => (itemsButtonRefs[index] = el)\"\n @click=\"emitSelectedItemChanged(item)\"\n :aria-selected=\"(item === modelValue).toString()\"\n :class=\"itemsCSSClasses[index]\"\n class=\"x-dropdown__item\"\n data-test=\"dropdown-item\"\n role=\"option\"\n >\n <!--\n @slot (required) Used to render each one of the items content, and as fallback\n for the toggle button content slot if it is not provided.\n @binding {string|number|Identifiable} item - Item to render\n @binding {boolean} isHighlighted - True when the item has the focus.\n @binding {boolean} isSelected - True when the item is selected.\n -->\n <slot\n :isHighlighted=\"index === highlightedItemIndex\"\n :isSelected=\"item === modelValue\"\n :item=\"item\"\n name=\"item\"\n >\n {{ item }}\n </slot>\n </button>\n </li>\n </ul>\n </component>\n </div>\n</template>\n\n<script lang=\"ts\">\n import { Identifiable } from '@empathyco/x-types';\n import { computed, defineComponent, nextTick, onBeforeUnmount, PropType, ref, watch } from 'vue';\n import { AnimationProp } from '../types';\n import { debounceFunction, normalizeString, getTargetElement, isInRange } from '../utils';\n import { NoAnimation } from './animations';\n\n type DropdownItem = string | number | Identifiable;\n let dropdownCount = 0;\n\n /**\n * Dropdown component that mimics a Select element behavior, but with the option\n * to customize the toggle button and each item contents.\n */\n export default defineComponent({\n name: 'BaseDropdown',\n props: {\n /** List of items to display.*/\n items: {\n type: Array as PropType<DropdownItem[]>,\n required: true\n },\n /** The selected item. */\n modelValue: {\n type: null as unknown as PropType<DropdownItem | null>,\n validator: (v: any) =>\n typeof v === 'string' || typeof v === 'number' || typeof v === 'object' || v === null,\n required: true\n },\n /** Description of what the dropdown is used for. */\n ariaLabel: String,\n /**\n * Animation component to use for expanding the dropdown. This is a single element animation,\n * so only `<transition>` components are allowed.\n */\n animation: {\n type: AnimationProp,\n default: () => NoAnimation\n },\n /** Time to wait without receiving any keystroke before resetting the items search query. */\n searchTimeoutMs: {\n type: Number,\n default: 1000\n }\n },\n emits: ['update:modelValue'],\n setup(props, { emit, slots }) {\n const rootRef = ref<HTMLElement>();\n /** The button that opens and closes the list of options. */\n const toggleButtonRef = ref<HTMLButtonElement>();\n /** Array containing the dropdown list buttons HTMLElements. */\n const itemsButtonRefs = ref<HTMLButtonElement[]>([]);\n\n /** Property to check whether the dropdown is expanded or closed. */\n const isOpen = ref(false);\n /** Index of the element that has the focus in the list. -1 means no element has focus. */\n const highlightedItemIndex = ref(-1);\n /** String to search for the first element that starts with it. */\n const searchBuffer = ref('');\n\n /** Resets the search buffer after a certain time has passed. */\n let restartResetSearchTimeout: () => void;\n /* Unique ID to identify the dropdown. */\n const listId = `x-dropdown-${dropdownCount++}`;\n\n /**\n * Dynamic CSS classes to add to the dropdown root element.\n *\n * @returns An object containing the CSS classes to add to the dropdown root element.\n */\n const dropdownCSSClasses = computed(() => ({ 'x-dropdown--is-open': isOpen }));\n\n /**\n * Dynamic CSS classes to add to each one of the items.\n *\n * @returns An object containing the CSS classes to add to each item.\n */\n const itemsCSSClasses = computed(() =>\n props.items.map((item, index) => ({\n 'x-dropdown__item--is-selected': props.modelValue === item,\n 'x-dropdown__item--is-highlighted': highlightedItemIndex.value === index\n }))\n );\n\n /* Opens the dropdown. */\n const open = () => (isOpen.value = true);\n /* Closes the dropdown. */\n const close = () => (isOpen.value = false);\n /* Toggles the dropdown. */\n const toggle = () => (isOpen.value = !isOpen.value);\n\n /**\n * Closes the modal and focuses the toggle button.\n */\n function closeAndFocusToggleButton() {\n close();\n toggleButtonRef.value?.focus();\n }\n\n /**\n * Emits the event that the selected item has changed.\n *\n * @param item - The new selected item.\n */\n function emitSelectedItemChanged(item: DropdownItem) {\n emit('update:modelValue', item);\n closeAndFocusToggleButton();\n }\n\n /**\n * Highlights the item after the one that is currently highlighted.\n */\n function highlightNextItem() {\n open();\n highlightedItemIndex.value = (highlightedItemIndex.value + 1) % props.items.length;\n }\n\n /**\n * Highlights the item before the one that is currently highlighted.\n */\n function highlightPreviousItem() {\n const currentIndex = highlightedItemIndex.value;\n open();\n highlightedItemIndex.value = currentIndex > 0 ? currentIndex - 1 : props.items.length - 1;\n }\n\n /**\n * Highlights the first of the provided items.\n */\n function highlightFirstItem() {\n highlightedItemIndex.value = 0;\n }\n\n /**\n * Highlights the last of the provided items.\n */\n function highlightLastItem() {\n highlightedItemIndex.value = props.items.length - 1;\n }\n\n /**\n * Updates the variable that is used to search in the filters.\n *\n * @param event - The event coming from the user typing.\n */\n function updateSearchBuffer(event: KeyboardEvent) {\n if (/^\\w$/.test(event.key)) {\n const key = event.key;\n searchBuffer.value += key;\n restartResetSearchTimeout();\n }\n }\n\n /**\n * Resets the search buffer.\n */\n function resetSearchBuffer() {\n searchBuffer.value = '';\n }\n\n /**\n * Closes the dropdown if the passed event has happened on an element out of the dropdown.\n *\n * @param event - The event to check if it has happened out of the dropdown component.\n */\n function closeIfEventIsOutOfDropdown(event: MouseEvent | TouchEvent | FocusEvent) {\n if (!rootRef.value?.contains(getTargetElement(event))) {\n close();\n }\n }\n\n /**\n * Adds listeners to the document element to detect if the focus has moved out from the\n * dropdown.\n */\n function addDocumentCloseListeners() {\n document.addEventListener('mousedown', closeIfEventIsOutOfDropdown);\n document.addEventListener('touchstart', closeIfEventIsOutOfDropdown);\n document.addEventListener('focusin', closeIfEventIsOutOfDropdown);\n }\n\n /**\n * Removes the listeners of the document element to detect if the focus has moved out from the\n * dropdown.\n */\n function removeDocumentCloseListeners() {\n document.removeEventListener('mousedown', closeIfEventIsOutOfDropdown);\n document.removeEventListener('touchstart', closeIfEventIsOutOfDropdown);\n document.removeEventListener('focusin', closeIfEventIsOutOfDropdown);\n }\n\n /**\n * Highlights the item that matches the search buffer. To do so it checks the list buttons\n * text content. It highlights items following this priority:\n * - If an element is already highlighted, it starts searching from that element.\n * - If no element is found starting from the previously highlighted, it returns the first one.\n * - If no element is found matching the search query it highlights the first element.\n *\n * @param search - The search string to find in the HTML.\n */\n watch(searchBuffer, search => {\n if (search) {\n const normalizedSearch = normalizeString(search);\n const matchingIndices = itemsButtonRefs?.value?.reduce<number[]>(\n (matchingIndices, button, index) => {\n const safeButtonWordCharacters = button.textContent!.replace(/[^\\w]/g, '');\n const normalizedButtonText = normalizeString(safeButtonWordCharacters);\n if (normalizedButtonText.startsWith(normalizedSearch)) {\n matchingIndices.push(index);\n }\n return matchingIndices;\n },\n []\n );\n highlightedItemIndex.value =\n // First matching item starting to search from the current highlighted element\n matchingIndices?.find(index => index >= highlightedItemIndex.value) ??\n // First matching item\n matchingIndices?.[0] ??\n // First item\n 0;\n }\n });\n\n /**\n * Updates the debounced function to reset the search.\n *\n * @param searchTimeoutMs - The new milliseconds that have to pass without typing before\n * resetting the search.\n */\n watch(\n () => props.searchTimeoutMs,\n searchTimeoutMs => {\n restartResetSearchTimeout = debounceFunction(resetSearchBuffer, searchTimeoutMs);\n },\n { immediate: true }\n );\n\n /**\n * Focuses the DOM element which matches the `highlightedItemIndex`.\n *\n * @param highlightedItemIndex - The index of the HTML element to focus.\n */\n watch(\n highlightedItemIndex,\n highlightedItemIndex => {\n nextTick(() => {\n if (itemsButtonRefs && isInRange(highlightedItemIndex, [0, props.items.length - 1])) {\n const newItem = itemsButtonRefs?.value?.[highlightedItemIndex];\n newItem?.focus();\n }\n });\n },\n { immediate: true }\n );\n\n /**\n * When the dropdown is open it sets the focused element to the one that is selected.\n *\n * @param isOpen - True if the dropdown is open, false otherwise.\n */\n watch(isOpen, isOpen => {\n highlightedItemIndex.value = isOpen\n ? props.modelValue === null\n ? 0\n : props.items.indexOf(props.modelValue)\n : -1;\n });\n\n /**\n * Adds and removes listeners to close the dropdown when it loses the focus.\n *\n * @param isOpen - True if the dropdown is open, false otherwise.\n */\n watch(isOpen, isOpen => {\n /*\n * Because there is an issue with Firefox in macOS and Safari that doesn't focus the target\n * element of the `mousedown` events, the `focusout` event `relatedTarget` property can't be\n * used to detect whether the user has blurred the dropdown. The hack here is to use\n * document listeners that have the side effect of losing the focus.\n */\n if (isOpen) {\n addDocumentCloseListeners();\n } else {\n removeDocumentCloseListeners();\n }\n });\n\n /**\n * If the dropdown is destroyed before removing the document listeners, it ensures that they\n * are removed too.\n */\n onBeforeUnmount(() => {\n removeDocumentCloseListeners();\n });\n\n return {\n hasToggleSlot: !!slots.toggle,\n closeAndFocusToggleButton,\n dropdownCSSClasses,\n emitSelectedItemChanged,\n highlightFirstItem,\n highlightLastItem,\n highlightNextItem,\n highlightPreviousItem,\n highlightedItemIndex,\n isOpen,\n itemsButtonRefs,\n itemsCSSClasses,\n listId,\n open,\n rootRef,\n toggle,\n toggleButtonRef,\n updateSearchBuffer\n };\n }\n });\n</script>\n\n<style lang=\"css\" scoped>\n .x-dropdown {\n position: relative;\n }\n\n .x-dropdown__items-list {\n z-index: 1;\n list-style: none;\n position: absolute;\n padding: 0;\n margin: 0;\n top: calc(100% + var(--x-size-gap-dropdown-default, 0));\n }\n</style>\n\n<docs lang=\"mdx\">\n## Example\n\nThe `Dropdown` component is a simple yet customizable select component. The component needs to work\nthe list of items available to select, which are passed using the `items` prop, and the selected\nitem, which is passed in using the `value` prop.\n\nThe supported items must be an array that can contain unique strings, unique numbers, or objects\nwith a unique `id` property.\n\nThe content of each item can be customized using the `item` slot, which apart from the data of the\nitem, it also receives via prop if the element is selected or highlighted.\n\nThere `toggle` slot can be used to customize the button that opens the dropdown. If this is not\nprovided, the `item` slot will be used for that.\n\n```vue\n<template>\n <BaseDropdown v-model=\"value\" :items=\"items\">\n <template #toggle=\"{ item, isOpen }\">{{ item }} {{ isOpen ? '🔼' : '🔽' }}️</template>\n\n <template #item=\"{ item, isSelected, isHighlighted }\">\n <span v-if=\"isHighlighted\">🟢</span>\n <span v-if=\"isSelected\">✅</span>\n <span>{{ item }}</span>\n </template>\n </BaseDropdown>\n</template>\n\n<script>\n import { BaseDropdown } from '@empathyco/x-components';\n\n export default {\n name: 'DropdownTest',\n components: {\n BaseDropdown\n },\n data() {\n return {\n items: ['a', 2, { id: '3' }],\n value: 'a'\n };\n }\n };\n</script>\n```\n</docs>\n"],"names":["NoAnimation","debounceFunction"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-dropdown.vue2.js","sources":["../../../src/components/base-dropdown.vue"],"sourcesContent":["<template>\n <div\n ref=\"rootRef\"\n @keydown=\"updateSearchBuffer\"\n @keydown.down.prevent=\"highlightNextItem\"\n @keydown.up.prevent=\"highlightPreviousItem\"\n :class=\"dropdownCSSClasses\"\n class=\"x-dropdown\"\n >\n <button\n ref=\"toggleButtonRef\"\n @click=\"toggle\"\n @keydown.up.down.prevent.stop=\"open\"\n class=\"x-dropdown__toggle\"\n data-test=\"dropdown-toggle\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n :aria-expanded=\"isOpen.toString()\"\n :aria-controls=\"listId\"\n :aria-label=\"ariaLabel\"\n aria-autocomplete=\"none\"\n >\n <!--\n @slot Used to render the contents of the dropdown toggle button. If not provided, it uses\n the item slot as fallback.\n @binding {string|number|Identifiable} item - The item data to render.\n @binding {boolean} isOpen - True if the dropdown is opened, and false if it is closed.\n -->\n <slot v-if=\"hasToggleSlot\" :isOpen=\"isOpen\" :item=\"modelValue\" name=\"toggle\">\n {{ modelValue }}\n </slot>\n <slot v-else :item=\"modelValue\" name=\"item\">{{ modelValue }}</slot>\n </button>\n\n <component :is=\"animation\">\n <ul\n v-show=\"isOpen\"\n @keydown.end=\"highlightLastItem\"\n @keydown.esc=\"closeAndFocusToggleButton\"\n @keydown.home=\"highlightFirstItem\"\n :id=\"listId\"\n class=\"x-dropdown__items-list\"\n data-test=\"dropdown-list\"\n role=\"listbox\"\n tabindex=\"-1\"\n >\n <li v-for=\"(item, index) in items\" :key=\"item.id || item\" class=\"x-dropdown__list-item\">\n <button\n :ref=\"el => (itemsButtonRefs[index] = el)\"\n @click=\"emitSelectedItemChanged(item)\"\n :aria-selected=\"(item === modelValue).toString()\"\n :class=\"itemsCSSClasses[index]\"\n class=\"x-dropdown__item\"\n data-test=\"dropdown-item\"\n role=\"option\"\n >\n <!--\n @slot (required) Used to render each one of the items content, and as fallback\n for the toggle button content slot if it is not provided.\n @binding {string|number|Identifiable} item - Item to render\n @binding {boolean} isHighlighted - True when the item has the focus.\n @binding {boolean} isSelected - True when the item is selected.\n -->\n <slot\n :isHighlighted=\"index === highlightedItemIndex\"\n :isSelected=\"item === modelValue\"\n :item=\"item\"\n name=\"item\"\n >\n {{ item }}\n </slot>\n </button>\n </li>\n </ul>\n </component>\n </div>\n</template>\n\n<script lang=\"ts\">\n import { Identifiable } from '@empathyco/x-types';\n import { computed, defineComponent, nextTick, onBeforeUnmount, PropType, ref, watch } from 'vue';\n import { AnimationProp } from '../types';\n import { debounceFunction, normalizeString, getTargetElement } from '../utils';\n import { NoAnimation } from './animations';\n\n type DropdownItem = string | number | Identifiable;\n let dropdownCount = 0;\n\n /**\n * Dropdown component that mimics a Select element behavior, but with the option\n * to customize the toggle button and each item contents.\n */\n export default defineComponent({\n name: 'BaseDropdown',\n props: {\n /** List of items to display.*/\n items: {\n type: Array as PropType<DropdownItem[]>,\n required: true\n },\n /** The selected item. */\n modelValue: {\n type: null as unknown as PropType<DropdownItem | null>,\n validator: (v: any) =>\n typeof v === 'string' || typeof v === 'number' || typeof v === 'object' || v === null,\n required: true\n },\n /** Description of what the dropdown is used for. */\n ariaLabel: String,\n /**\n * Animation component to use for expanding the dropdown. This is a single element animation,\n * so only `<transition>` components are allowed.\n */\n animation: {\n type: AnimationProp,\n default: () => NoAnimation\n },\n /** Time to wait without receiving any keystroke before resetting the items search query. */\n searchTimeoutMs: {\n type: Number,\n default: 1000\n }\n },\n emits: ['update:modelValue'],\n setup(props, { emit, slots }) {\n const rootRef = ref<HTMLElement>();\n /** The button that opens and closes the list of options. */\n const toggleButtonRef = ref<HTMLButtonElement>();\n /** Array containing the dropdown list buttons HTMLElements. */\n const itemsButtonRefs = ref<HTMLButtonElement[]>([]);\n\n /** Property to check whether the dropdown is expanded or closed. */\n const isOpen = ref(false);\n /** Index of the element that has the focus in the list. -1 means no element has focus. */\n const highlightedItemIndex = ref(-1);\n /** String to search for the first element that starts with it. */\n const searchBuffer = ref('');\n\n /** Resets the search buffer after a certain time has passed. */\n let restartResetSearchTimeout: () => void;\n /* Unique ID to identify the dropdown. */\n const listId = `x-dropdown-${dropdownCount++}`;\n\n /**\n * Dynamic CSS classes to add to the dropdown root element.\n *\n * @returns An object containing the CSS classes to add to the dropdown root element.\n */\n const dropdownCSSClasses = computed(() => ({ 'x-dropdown--is-open': isOpen }));\n\n /**\n * Dynamic CSS classes to add to each one of the items.\n *\n * @returns An object containing the CSS classes to add to each item.\n */\n const itemsCSSClasses = computed(() =>\n props.items.map((item, index) => ({\n 'x-dropdown__item--is-selected': props.modelValue === item,\n 'x-dropdown__item--is-highlighted': highlightedItemIndex.value === index\n }))\n );\n\n /* Opens the dropdown. */\n const open = () => (isOpen.value = true);\n /* Closes the dropdown. */\n const close = () => (isOpen.value = false);\n /* Toggles the dropdown. */\n const toggle = () => (isOpen.value = !isOpen.value);\n\n /**\n * Closes the modal and focuses the toggle button.\n */\n function closeAndFocusToggleButton() {\n close();\n toggleButtonRef.value?.focus();\n }\n\n /**\n * Emits the event that the selected item has changed.\n *\n * @param item - The new selected item.\n */\n function emitSelectedItemChanged(item: DropdownItem) {\n emit('update:modelValue', item);\n closeAndFocusToggleButton();\n }\n\n /**\n * Highlights the item after the one that is currently highlighted.\n */\n function highlightNextItem() {\n open();\n highlightedItemIndex.value = (highlightedItemIndex.value + 1) % props.items.length;\n }\n\n /**\n * Highlights the item before the one that is currently highlighted.\n */\n function highlightPreviousItem() {\n const currentIndex = highlightedItemIndex.value;\n open();\n highlightedItemIndex.value = currentIndex > 0 ? currentIndex - 1 : props.items.length - 1;\n }\n\n /**\n * Highlights the first of the provided items.\n */\n function highlightFirstItem() {\n highlightedItemIndex.value = 0;\n }\n\n /**\n * Highlights the last of the provided items.\n */\n function highlightLastItem() {\n highlightedItemIndex.value = props.items.length - 1;\n }\n\n /**\n * Updates the variable that is used to search in the filters.\n *\n * @param event - The event coming from the user typing.\n */\n function updateSearchBuffer(event: KeyboardEvent) {\n if (/^\\w$/.test(event.key)) {\n const key = event.key;\n searchBuffer.value += key;\n restartResetSearchTimeout();\n }\n }\n\n /**\n * Resets the search buffer.\n */\n function resetSearchBuffer() {\n searchBuffer.value = '';\n }\n\n /**\n * Closes the dropdown if the passed event has happened on an element out of the dropdown.\n *\n * @param event - The event to check if it has happened out of the dropdown component.\n */\n function closeIfEventIsOutOfDropdown(event: MouseEvent | TouchEvent | FocusEvent) {\n if (!rootRef.value?.contains(getTargetElement(event))) {\n close();\n }\n }\n\n /**\n * Adds listeners to the document element to detect if the focus has moved out from the\n * dropdown.\n */\n function addDocumentCloseListeners() {\n document.addEventListener('mousedown', closeIfEventIsOutOfDropdown);\n document.addEventListener('touchstart', closeIfEventIsOutOfDropdown);\n document.addEventListener('focusin', closeIfEventIsOutOfDropdown);\n }\n\n /**\n * Removes the listeners of the document element to detect if the focus has moved out from the\n * dropdown.\n */\n function removeDocumentCloseListeners() {\n document.removeEventListener('mousedown', closeIfEventIsOutOfDropdown);\n document.removeEventListener('touchstart', closeIfEventIsOutOfDropdown);\n document.removeEventListener('focusin', closeIfEventIsOutOfDropdown);\n }\n\n /**\n * Highlights the item that matches the search buffer. To do so it checks the list buttons\n * text content. It highlights items following this priority:\n * - If an element is already highlighted, it starts searching from that element.\n * - If no element is found starting from the previously highlighted, it returns the first one.\n * - If no element is found matching the search query it highlights the first element.\n *\n * @param search - The search string to find in the HTML.\n */\n watch(searchBuffer, search => {\n if (search) {\n const normalizedSearch = normalizeString(search);\n const matchingIndices = itemsButtonRefs?.value?.reduce<number[]>(\n (matchingIndices, button, index) => {\n const safeButtonWordCharacters = button.textContent!.replace(/[^\\w]/g, '');\n const normalizedButtonText = normalizeString(safeButtonWordCharacters);\n if (normalizedButtonText.startsWith(normalizedSearch)) {\n matchingIndices.push(index);\n }\n return matchingIndices;\n },\n []\n );\n highlightedItemIndex.value =\n // First matching item starting to search from the current highlighted element\n matchingIndices?.find(index => index >= highlightedItemIndex.value) ??\n // First matching item\n matchingIndices?.[0] ??\n // First item\n 0;\n }\n });\n\n /**\n * Updates the debounced function to reset the search.\n *\n * @param searchTimeoutMs - The new milliseconds that have to pass without typing before\n * resetting the search.\n */\n watch(\n () => props.searchTimeoutMs,\n searchTimeoutMs => {\n restartResetSearchTimeout = debounceFunction(resetSearchBuffer, searchTimeoutMs);\n },\n { immediate: true }\n );\n\n /**\n * Focuses the DOM element which matches the `highlightedItemIndex`.\n *\n * @param highlightedItemIndex - The index of the HTML element to focus.\n */\n watch(\n highlightedItemIndex,\n highlightedItemIndex => {\n nextTick(() => itemsButtonRefs.value[highlightedItemIndex]?.focus());\n },\n { immediate: true }\n );\n\n /**\n * When the dropdown is open it sets the focused element to the one that is selected.\n *\n * @param isOpen - True if the dropdown is open, false otherwise.\n */\n watch(isOpen, isOpen => {\n highlightedItemIndex.value = isOpen\n ? props.modelValue === null\n ? 0\n : props.items.indexOf(props.modelValue)\n : -1;\n });\n\n /**\n * Adds and removes listeners to close the dropdown when it loses the focus.\n *\n * @param isOpen - True if the dropdown is open, false otherwise.\n */\n watch(isOpen, isOpen => {\n /*\n * Because there is an issue with Firefox in macOS and Safari that doesn't focus the target\n * element of the `mousedown` events, the `focusout` event `relatedTarget` property can't be\n * used to detect whether the user has blurred the dropdown. The hack here is to use\n * document listeners that have the side effect of losing the focus.\n */\n if (isOpen) {\n addDocumentCloseListeners();\n } else {\n removeDocumentCloseListeners();\n }\n });\n\n /**\n * If the dropdown is destroyed before removing the document listeners, it ensures that they\n * are removed too.\n */\n onBeforeUnmount(() => {\n removeDocumentCloseListeners();\n });\n\n return {\n hasToggleSlot: !!slots.toggle,\n closeAndFocusToggleButton,\n dropdownCSSClasses,\n emitSelectedItemChanged,\n highlightFirstItem,\n highlightLastItem,\n highlightNextItem,\n highlightPreviousItem,\n highlightedItemIndex,\n isOpen,\n itemsButtonRefs,\n itemsCSSClasses,\n listId,\n open,\n rootRef,\n toggle,\n toggleButtonRef,\n updateSearchBuffer\n };\n }\n });\n</script>\n\n<style lang=\"css\" scoped>\n .x-dropdown {\n position: relative;\n }\n\n .x-dropdown__items-list {\n z-index: 1;\n list-style: none;\n position: absolute;\n padding: 0;\n margin: 0;\n top: calc(100% + var(--x-size-gap-dropdown-default, 0));\n }\n</style>\n\n<docs lang=\"mdx\">\n## Example\n\nThe `Dropdown` component is a simple yet customizable select component. The component needs to work\nthe list of items available to select, which are passed using the `items` prop, and the selected\nitem, which is passed in using the `value` prop.\n\nThe supported items must be an array that can contain unique strings, unique numbers, or objects\nwith a unique `id` property.\n\nThe content of each item can be customized using the `item` slot, which apart from the data of the\nitem, it also receives via prop if the element is selected or highlighted.\n\nThere `toggle` slot can be used to customize the button that opens the dropdown. If this is not\nprovided, the `item` slot will be used for that.\n\n```vue\n<template>\n <BaseDropdown v-model=\"value\" :items=\"items\">\n <template #toggle=\"{ item, isOpen }\">{{ item }} {{ isOpen ? '🔼' : '🔽' }}️</template>\n\n <template #item=\"{ item, isSelected, isHighlighted }\">\n <span v-if=\"isHighlighted\">🟢</span>\n <span v-if=\"isSelected\">✅</span>\n <span>{{ item }}</span>\n </template>\n </BaseDropdown>\n</template>\n\n<script>\n import { BaseDropdown } from '@empathyco/x-components';\n\n export default {\n name: 'DropdownTest',\n components: {\n BaseDropdown\n },\n data() {\n return {\n items: ['a', 2, { id: '3' }],\n value: 'a'\n };\n }\n };\n</script>\n```\n</docs>\n"],"names":["NoAnimation","debounceFunction"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAsFE,IAAI,aAAY,GAAI,CAAC,CAAA;AAErB;;;AAGE;AACF,gBAAe,eAAe,CAAC;AAC7B,IAAA,IAAI,EAAE,cAAc;AACpB,IAAA,KAAK,EAAE;;AAEL,QAAA,KAAK,EAAE;AACL,YAAA,IAAI,EAAE,KAAiC;AACvC,YAAA,QAAQ,EAAE,IAAG;AACd,SAAA;;AAED,QAAA,UAAU,EAAE;AACV,YAAA,IAAI,EAAE,IAAgD;YACtD,SAAS,EAAE,CAAC,CAAM,KAChB,OAAO,CAAA,KAAM,QAAO,IAAK,OAAO,CAAA,KAAM,QAAO,IAAK,OAAO,CAAE,KAAI,YAAY,CAAE,KAAI,IAAI;AACvF,YAAA,QAAQ,EAAE,IAAG;AACd,SAAA;;AAED,QAAA,SAAS,EAAE,MAAM;AACjB;;;AAGE;AACF,QAAA,SAAS,EAAE;AACT,YAAA,IAAI,EAAE,aAAa;AACnB,YAAA,OAAO,EAAE,MAAMA,WAAU;AAC1B,SAAA;;AAED,QAAA,eAAe,EAAE;AACf,YAAA,IAAI,EAAE,MAAM;AACZ,YAAA,OAAO,EAAE,IAAG;AACd,SAAA;AACD,KAAA;IACD,KAAK,EAAE,CAAC,mBAAmB,CAAC;AAC5B,IAAA,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAM,EAAC,EAAA;AAC1B,QAAA,MAAM,OAAQ,GAAE,GAAG,EAAe,CAAA;;AAElC,QAAA,MAAM,kBAAkB,GAAG,EAAqB,CAAA;;AAEhD,QAAA,MAAM,eAAc,GAAI,GAAG,CAAsB,EAAE,CAAC,CAAA;;AAGpD,QAAA,MAAM,MAAK,GAAI,GAAG,CAAC,KAAK,CAAC,CAAA;;AAEzB,QAAA,MAAM,oBAAmB,GAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;;AAEpC,QAAA,MAAM,YAAW,GAAI,GAAG,CAAC,EAAE,CAAC,CAAA;;AAG5B,QAAA,IAAI,yBAAqC,CAAA;;AAEzC,QAAA,MAAM,MAAO,GAAE,CAAA,WAAA,EAAc,aAAa,EAAE,EAAE,CAAA;AAE9C;;;;AAIE;AACF,QAAA,MAAM,kBAAiB,GAAI,QAAQ,CAAC,OAAO,EAAE,qBAAqB,EAAE,MAAO,EAAC,CAAC,CAAC,CAAA;AAE9E;;;;AAIE;QACF,MAAM,eAAc,GAAI,QAAQ,CAAC,MAC/B,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,MAAM;AAChC,YAAA,+BAA+B,EAAE,KAAK,CAAC,UAAS,KAAM,IAAI;AAC1D,YAAA,kCAAkC,EAAE,oBAAoB,CAAC,KAAM,KAAI,KAAI;SACxE,CAAC,CAAA,CACH,CAAA;;AAGD,QAAA,MAAM,IAAG,GAAI,OAAO,MAAM,CAAC,KAAI,GAAI,IAAI,CAAC,CAAA;;AAExC,QAAA,MAAM,KAAI,GAAI,OAAO,MAAM,CAAC,KAAI,GAAI,KAAK,CAAC,CAAA;;AAE1C,QAAA,MAAM,SAAS,OAAO,MAAM,CAAC,KAAM,GAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAEnD;;AAEE;AACF,QAAA,SAAS,yBAAyB,GAAA;AAChC,YAAA,KAAK,EAAE,CAAA;AACP,YAAA,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,CAAA;SAChC;AAEA;;;;AAIE;QACF,SAAS,uBAAuB,CAAC,IAAkB,EAAA;AACjD,YAAA,IAAI,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAA;AAC/B,YAAA,yBAAyB,EAAE,CAAA;SAC7B;AAEA;;AAEE;AACF,QAAA,SAAS,iBAAiB,GAAA;AACxB,YAAA,IAAI,EAAE,CAAA;AACN,YAAA,oBAAoB,CAAC,QAAQ,CAAC,oBAAoB,CAAC,KAAI,GAAI,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAA;SACpF;AAEA;;AAEE;AACF,QAAA,SAAS,qBAAqB,GAAA;AAC5B,YAAA,MAAM,eAAe,oBAAoB,CAAC,KAAK,CAAA;AAC/C,YAAA,IAAI,EAAE,CAAA;YACN,oBAAoB,CAAC,QAAQ,YAAa,GAAE,IAAI,YAAa,GAAE,IAAI,KAAK,CAAC,KAAK,CAAC,MAAK,GAAI,CAAC,CAAA;SAC3F;AAEA;;AAEE;AACF,QAAA,SAAS,kBAAkB,GAAA;AACzB,YAAA,oBAAoB,CAAC,KAAM,GAAE,CAAC,CAAA;SAChC;AAEA;;AAEE;AACF,QAAA,SAAS,iBAAiB,GAAA;YACxB,oBAAoB,CAAC,KAAI,GAAI,KAAK,CAAC,KAAK,CAAC,MAAO,GAAE,CAAC,CAAA;SACrD;AAEA;;;;AAIE;QACF,SAAS,kBAAkB,CAAC,KAAoB,EAAA;YAC9C,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;AAC1B,gBAAA,MAAM,GAAI,GAAE,KAAK,CAAC,GAAG,CAAA;AACrB,gBAAA,YAAY,CAAC,SAAS,GAAG,CAAA;AACzB,gBAAA,yBAAyB,EAAE,CAAA;AAC7B,aAAA;SACF;AAEA;;AAEE;AACF,QAAA,SAAS,iBAAiB,GAAA;AACxB,YAAA,YAAY,CAAC,KAAM,GAAE,EAAE,CAAA;SACzB;AAEA;;;;AAIE;QACF,SAAS,2BAA2B,CAAC,KAA2C,EAAA;AAC9E,YAAA,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,EAAE;AACrD,gBAAA,KAAK,EAAE,CAAA;AACT,aAAA;SACF;AAEA;;;AAGE;AACF,QAAA,SAAS,yBAAyB,GAAA;AAChC,YAAA,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,2BAA2B,CAAC,CAAA;AACnE,YAAA,QAAQ,CAAC,gBAAgB,CAAC,YAAY,EAAE,2BAA2B,CAAC,CAAA;AACpE,YAAA,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,2BAA2B,CAAC,CAAA;SACnE;AAEA;;;AAGE;AACF,QAAA,SAAS,4BAA4B,GAAA;AACnC,YAAA,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,2BAA2B,CAAC,CAAA;AACtE,YAAA,QAAQ,CAAC,mBAAmB,CAAC,YAAY,EAAE,2BAA2B,CAAC,CAAA;AACvE,YAAA,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,2BAA2B,CAAC,CAAA;SACtE;AAEA;;;;;;;;AAQE;AACF,QAAA,KAAK,CAAC,YAAY,EAAE,MAAO,IAAG;AAC5B,YAAA,IAAI,MAAM,EAAE;AACV,gBAAA,MAAM,gBAAiB,GAAE,eAAe,CAAC,MAAM,CAAC,CAAA;AAChD,gBAAA,MAAM,eAAc,GAAI,eAAe,EAAE,KAAK,EAAE,MAAM,CACpD,CAAC,eAAe,EAAE,MAAM,EAAE,KAAK,KAAK;AAClC,oBAAA,MAAM,wBAAyB,GAAE,MAAM,CAAC,WAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;AAC1E,oBAAA,MAAM,oBAAqB,GAAE,eAAe,CAAC,wBAAwB,CAAC,CAAA;AACtE,oBAAA,IAAI,oBAAoB,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE;AACrD,wBAAA,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AAC7B,qBAAA;AACA,oBAAA,OAAO,eAAe,CAAA;iBACvB,EACD,EAAC,CACF,CAAA;AACD,gBAAA,oBAAoB,CAAC,KAAM;;AAEzB,oBAAA,eAAe,EAAE,IAAI,CAAC,KAAI,IAAK,KAAI,IAAK,oBAAoB,CAAC,KAAK;;wBAElE,eAAe,GAAG,CAAC;;AAEnB,wBAAA,CAAC,CAAA;AACL,aAAA;AACF,SAAC,CAAC,CAAA;AAEF;;;;;AAKE;QACF,KAAK,CACH,MAAM,KAAK,CAAC,eAAe,EAC3B,eAAc,IAAK;AACjB,YAAA,yBAAwB,GAAIC,QAAgB,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAA;AAClF,SAAC,EACD,EAAE,SAAS,EAAE,IAAK,EAAA,CACnB,CAAA;AAED;;;;AAIE;AACF,QAAA,KAAK,CACH,oBAAoB,EACpB,oBAAmB,IAAK;AACtB,YAAA,QAAQ,CAAC,MAAM,eAAe,CAAC,KAAK,CAAC,oBAAoB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;AACtE,SAAC,EACD,EAAE,SAAS,EAAE,IAAK,EAAA,CACnB,CAAA;AAED;;;;AAIE;AACF,QAAA,KAAK,CAAC,MAAM,EAAE,MAAK,IAAK;YACtB,oBAAoB,CAAC,KAAI,GAAI,MAAK;AAChC,kBAAE,KAAK,CAAC,UAAS,KAAM,IAAG;AACxB,sBAAE,CAAA;sBACA,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAA;kBACtC,CAAC,CAAC,CAAA;AACR,SAAC,CAAC,CAAA;AAEF;;;;AAIE;AACF,QAAA,KAAK,CAAC,MAAM,EAAE,MAAK,IAAK;AACtB;;;;;AAKE;AACF,YAAA,IAAI,MAAM,EAAE;AACV,gBAAA,yBAAyB,EAAE,CAAA;AAC3B,aAAA;AAAK,iBAAA;AACL,gBAAA,4BAA4B,EAAE,CAAA;AAChC,aAAA;AACF,SAAC,CAAC,CAAA;AAEF;;;AAGE;QACF,eAAe,CAAC,MAAM;AACpB,YAAA,4BAA4B,EAAE,CAAA;AAChC,SAAC,CAAC,CAAA;QAEF,OAAO;AACL,YAAA,aAAa,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM;YAC7B,yBAAyB;YACzB,kBAAkB;YAClB,uBAAuB;YACvB,kBAAkB;YAClB,iBAAiB;YACjB,iBAAiB;YACjB,qBAAqB;YACrB,oBAAoB;YACpB,MAAM;YACN,eAAe;YACf,eAAe;YACf,MAAM;YACN,IAAI;YACJ,OAAO;YACP,MAAM;YACN,eAAe;YACf,kBAAiB;SAClB,CAAA;KACH;AACD,CAAA,CAAC;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-result-image.vue.js","sources":["../../../../src/components/result/base-result-image.vue"],"sourcesContent":["<template>\n <!-- This is a div because using a picture causes the onload event of the image to fire twice. -->\n <!-- eslint-disable-next-line vuejs-accessibility/mouse-events-have-key-events -->\n <div\n @mouseenter.once=\"userHasHoveredImage = true\"\n @mouseenter=\"isHovering = true\"\n @mouseleave=\"isHovering = false\"\n class=\"x-result-picture x-picture\"\n data-test=\"result-picture\"\n >\n <img\n v-if=\"shouldLoadNextImage\"\n @load=\"flagImageLoaded\"\n @error=\"flagImageAsFailed\"\n loading=\"lazy\"\n :src=\"pendingImages[0]\"\n :style=\"loaderStyles\"\n class=\"x-picture-image\"\n data-test=\"result-picture-loader\"\n alt=\"\"\n role=\"presentation\"\n />\n <component :is=\"animation\" class=\"x-picture-image\" :appear=\"false\">\n <!-- @slot Fallback image content. It will be rendered when all the images failed -->\n <slot v-if=\"!loadedImages.length && !pendingImages.length\" name=\"fallback\" />\n\n <!-- @slot Loading image content. It will be rendered while the real image is not loaded -->\n <slot v-else-if=\"!loadedImages.length\" name=\"placeholder\" />\n\n <img\n v-else\n :key=\"imageSrc\"\n :alt=\"result.name\"\n :src=\"imageSrc\"\n class=\"x-result-picture-image\"\n data-test=\"result-picture-image\"\n />\n </component>\n </div>\n</template>\n\n<script lang=\"ts\">\n import { Result } from '@empathyco/x-types';\n import { computed, defineComponent, PropType, Ref, ref, watch } from 'vue';\n import { NoAnimation } from '../animations';\n import { AnimationProp } from '../../types';\n\n /**\n * Component to be reused that renders an `<img>`.\n *\n * @public\n */\n export default defineComponent({\n name: 'BaseResultImage',\n props: {\n /** (Required) The {@link @empathyco/x-types#Result} information. */\n result: {\n type: Object as PropType<Result>,\n required: true\n },\n /**\n * Animation to use when switching between the placeholder, the loaded image, or the failed\n * image fallback.\n */\n loadAnimation: {\n type: AnimationProp,\n default: () => NoAnimation\n },\n /** Animation to use when switching between the loaded image and the hover image. */\n hoverAnimation: {\n type: AnimationProp\n },\n /**\n * Indicates if the next valid image should be displayed on hover.\n *\n * @public\n */\n showNextImageOnHover: {\n type: Boolean,\n default: false\n }\n },\n setup(props) {\n /**\n * Copy of the images of the result.\n *\n * It is used as a queue of images to load, once an image loads/fails to load, it is removed\n * from this array.\n *\n * @internal\n */\n const pendingImages: Ref<string[]> = ref([]);\n\n /**\n * Contains the images that have been loaded successfully.\n *\n * @internal\n */\n const loadedImages: Ref<string[]> = ref([]);\n\n /**\n * Indicates if the user is hovering the image.\n *\n * @internal\n */\n const isHovering = ref(false);\n\n /**\n * Indicates if the user has hovered the image.\n *\n * @internal\n */\n const userHasHoveredImage = ref(false);\n\n /**.\n * Styles to use inline in the image loader, to prevent override from CSS\n *\n * @internal\n */\n const loaderStyles: Partial<CSSStyleDeclaration> = {\n position: 'absolute !important',\n top: '0 !important',\n left: '0 !important',\n width: '100% !important',\n height: '100% !important',\n pointerEvents: 'none !important',\n visibility: 'hidden !important'\n };\n\n /**\n * Initializes images state and resets when the result's images change.\n *\n * @internal\n */\n watch(\n () => props.result.images,\n () => {\n pendingImages.value = [...(props.result.images ?? [])];\n loadedImages.value = pendingImages.value.filter(image =>\n loadedImages.value.includes(image)\n );\n },\n { immediate: true }\n );\n\n /**\n * Animation to be used.\n *\n * @returns The animation to be used, taking into account if the user has hovered the image.\n *\n * @internal\n */\n const animation = computed(() => {\n return userHasHoveredImage\n ? props.hoverAnimation ?? props.loadAnimation\n : props.loadAnimation;\n });\n\n /**\n * Gets the src from the result image.\n *\n * @returns The result image src.\n *\n * @internal\n */\n const imageSrc = computed(() => {\n return loadedImages.value[\n !props.showNextImageOnHover || !isHovering.value ? 0 : loadedImages.value.length - 1\n ];\n });\n\n /**\n * Indicates if the loader should try to load the next image.\n *\n * @returns True if it should try to load the next image.\n *\n * @internal\n */\n const shouldLoadNextImage = computed(() => {\n const numImagesToLoad = props.showNextImageOnHover && userHasHoveredImage ? 2 : 1;\n return !!pendingImages.value.length && loadedImages.value.length < numImagesToLoad;\n });\n\n /**\n * Sets an image as failed.\n *\n * @internal\n */\n const flagImageAsFailed = () => {\n pendingImages.value.shift();\n };\n\n /**\n * Sets an image as loaded.\n *\n * @internal\n */\n const flagImageLoaded = () => {\n const image = pendingImages.value.shift();\n if (image) {\n loadedImages.value.push(image);\n }\n };\n\n return {\n pendingImages,\n loadedImages,\n isHovering,\n userHasHoveredImage,\n loaderStyles,\n animation,\n imageSrc,\n shouldLoadNextImage,\n flagImageAsFailed,\n flagImageLoaded\n };\n }\n });\n</script>\n\n<style lang=\"css\" scoped>\n .x-result-picture {\n position: relative;\n min-width: 1px;\n min-height: 1px;\n }\n\n .x-result-picture-image {\n max-width: 100%;\n max-height: 100%;\n }\n</style>\n\n<docs lang=\"mdx\">\n## Examples\n\n### Basic example\n\nThis component is for the result image. It may be part of the search result page, recommendations or\nother section which needs to include results.\n\nThe result prop is required. It will render a `<img/>` with the result image:\n\n```vue\n<BaseResultImage :result=\"result\" />\n```\n\n### Showing the next image on hover\n\nIf a result has multiple images, it can show the next one on hover.\n\n```vue\n<BaseResultImage :result=\"result\" showNextImageOnHover />\n```\n\n### Customizing slots content\n\nFallback and placeholder contents can be customized.\n\nThe fallback slot allows you to replace the content of the fallback image.\n\nThe other slot is called `placeholder`, and allows you to set the image that its going to be\ndisplayed while the real one is loaded.\n\n```vue\n<BaseResultImage :result=\"result\">\n <template #placeholder>\n <img alt=\"Placeholder image\" src=\"./placeholder-image.svg\"/>\n </template>\n <template #fallback>\n <img alt=\"Fallback image\" src=\"./fallback-image.svg\"/>\n </template>\n</BaseResultImage>\n```\n\n### Customizing the animations\n\nTwo animations can be used this component.\n\nThe `loadAnimation` is used to transition between the placeholder, the fallback and the image.\n\nThe `hoverAnimation` is used to transition between the image and the hover image, if the\n`showNextImageOnHover` prop is `true`.\n\n`hoverAnimation` will default to `loadAnimation` if it is not provided.\n\n```vue\n<template>\n <BaseResultImage\n :result=\"result\"\n :loadAnimation=\"loadAnimation\"\n :hoverAnimation=\"hoverAnimation\"\n showNextImageOnHover\n />\n</template>\n\n<script>\n import { BaseResultImage } from '@empathyco/x-components';\n import { CrossFade, CollapseHeight } from '@empathyco/x-components/animations';\n\n export default {\n name: 'BaseResultImageAnimations',\n components: {\n BaseResultImage\n },\n data() {\n return {\n loadAnimation: CrossFade,\n hoverAnimation: CollapseHeight,\n result: {\n name: 'jacket',\n images: ['https://some-image', 'https://some-image-2']\n }\n };\n }\n };\n</script>\n```\n</docs>\n"],"names":["_openBlock","_createElementBlock","_Fragment","_createCommentVNode","_createElementVNode","isHovering","shouldLoadNextImage","pendingImages","animation","_createBlock","_resolveDynamicComponent","loadedImages","_renderSlot","imageSrc"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-result-image.vue.js","sources":["../../../../src/components/result/base-result-image.vue"],"sourcesContent":["<template>\n <!-- This is a div because using a picture causes the onload event of the image to fire twice. -->\n <!-- eslint-disable-next-line vuejs-accessibility/mouse-events-have-key-events -->\n <div\n @mouseenter.once=\"userHasHoveredImage = true\"\n @mouseenter=\"isHovering = true\"\n @mouseleave=\"isHovering = false\"\n class=\"x-result-picture x-picture\"\n data-test=\"result-picture\"\n >\n <img\n v-if=\"shouldLoadNextImage\"\n @load=\"flagImageLoaded\"\n @error=\"flagImageAsFailed\"\n loading=\"lazy\"\n :src=\"pendingImages[0]\"\n :style=\"loaderStyles\"\n class=\"x-picture-image\"\n data-test=\"result-picture-loader\"\n alt=\"\"\n role=\"presentation\"\n />\n <component :is=\"animation\" class=\"x-picture-image\" :appear=\"false\">\n <!-- @slot Fallback image content. It will be rendered when all the images failed -->\n <slot v-if=\"!loadedImages.length && !pendingImages.length\" name=\"fallback\" />\n\n <!-- @slot Loading image content. It will be rendered while the real image is not loaded -->\n <slot v-else-if=\"!loadedImages.length\" name=\"placeholder\" />\n\n <img\n v-else\n :key=\"imageSrc\"\n :alt=\"result.name\"\n :src=\"imageSrc\"\n class=\"x-result-picture-image\"\n data-test=\"result-picture-image\"\n />\n </component>\n </div>\n</template>\n\n<script lang=\"ts\">\n import { Result } from '@empathyco/x-types';\n import { computed, defineComponent, PropType, Ref, ref, watch } from 'vue';\n import { NoAnimation } from '../animations';\n import { AnimationProp } from '../../types';\n\n /**\n * Component to be reused that renders an `<img>`.\n *\n * @public\n */\n export default defineComponent({\n name: 'BaseResultImage',\n props: {\n /** (Required) The {@link @empathyco/x-types#Result} information. */\n result: {\n type: Object as PropType<Result>,\n required: true\n },\n /**\n * Animation to use when switching between the placeholder, the loaded image, or the failed\n * image fallback.\n */\n loadAnimation: {\n type: AnimationProp,\n default: () => NoAnimation\n },\n /** Animation to use when switching between the loaded image and the hover image. */\n hoverAnimation: {\n type: AnimationProp\n },\n /**\n * Indicates if the next valid image should be displayed on hover.\n *\n * @public\n */\n showNextImageOnHover: {\n type: Boolean,\n default: false\n }\n },\n setup(props) {\n /**\n * Copy of the images of the result.\n *\n * It is used as a queue of images to load, once an image loads/fails to load, it is removed\n * from this array.\n *\n * @internal\n */\n const pendingImages: Ref<string[]> = ref([]);\n\n /**\n * Contains the images that have been loaded successfully.\n *\n * @internal\n */\n const loadedImages: Ref<string[]> = ref([]);\n\n /**\n * Indicates if the user is hovering the image.\n *\n * @internal\n */\n const isHovering = ref(false);\n\n /**\n * Indicates if the user has hovered the image.\n *\n * @internal\n */\n const userHasHoveredImage = ref(false);\n\n /**.\n * Styles to use inline in the image loader, to prevent override from CSS\n *\n * @internal\n */\n const loaderStyles: Partial<CSSStyleDeclaration> = {\n position: 'absolute !important',\n top: '0 !important',\n left: '0 !important',\n width: '100% !important',\n height: '100% !important',\n pointerEvents: 'none !important',\n visibility: 'hidden !important'\n };\n\n /**\n * Initializes images state and resets when the result's images change.\n *\n * @internal\n */\n watch(\n () => props.result.images,\n () => {\n pendingImages.value = [...(props.result.images ?? [])];\n loadedImages.value = pendingImages.value.filter(image =>\n loadedImages.value.includes(image)\n );\n },\n { immediate: true }\n );\n\n /**\n * Animation to be used.\n *\n * @returns The animation to be used, taking into account if the user has hovered the image.\n *\n * @internal\n */\n const animation = computed(() => {\n return userHasHoveredImage.value\n ? props.hoverAnimation ?? props.loadAnimation\n : props.loadAnimation;\n });\n\n /**\n * Gets the src from the result image.\n *\n * @returns The result image src.\n *\n * @internal\n */\n const imageSrc = computed(() => {\n return loadedImages.value[\n !props.showNextImageOnHover || !isHovering.value ? 0 : loadedImages.value.length - 1\n ];\n });\n\n /**\n * Indicates if the loader should try to load the next image.\n *\n * @returns True if it should try to load the next image.\n *\n * @internal\n */\n const shouldLoadNextImage = computed(() => {\n const numImagesToLoad = props.showNextImageOnHover && userHasHoveredImage.value ? 2 : 1;\n return !!pendingImages.value.length && loadedImages.value.length < numImagesToLoad;\n });\n\n /**\n * Sets an image as failed.\n *\n * @internal\n */\n const flagImageAsFailed = () => {\n pendingImages.value.shift();\n };\n\n /**\n * Sets an image as loaded.\n *\n * @internal\n */\n const flagImageLoaded = () => {\n const image = pendingImages.value.shift();\n if (image) {\n loadedImages.value.push(image);\n }\n };\n\n return {\n pendingImages,\n loadedImages,\n isHovering,\n userHasHoveredImage,\n loaderStyles,\n animation,\n imageSrc,\n shouldLoadNextImage,\n flagImageAsFailed,\n flagImageLoaded\n };\n }\n });\n</script>\n\n<style lang=\"css\" scoped>\n .x-result-picture {\n position: relative;\n min-width: 1px;\n min-height: 1px;\n }\n\n .x-result-picture-image {\n max-width: 100%;\n max-height: 100%;\n }\n</style>\n\n<docs lang=\"mdx\">\n## Examples\n\n### Basic example\n\nThis component is for the result image. It may be part of the search result page, recommendations or\nother section which needs to include results.\n\nThe result prop is required. It will render a `<img/>` with the result image:\n\n```vue\n<BaseResultImage :result=\"result\" />\n```\n\n### Showing the next image on hover\n\nIf a result has multiple images, it can show the next one on hover.\n\n```vue\n<BaseResultImage :result=\"result\" showNextImageOnHover />\n```\n\n### Customizing slots content\n\nFallback and placeholder contents can be customized.\n\nThe fallback slot allows you to replace the content of the fallback image.\n\nThe other slot is called `placeholder`, and allows you to set the image that its going to be\ndisplayed while the real one is loaded.\n\n```vue\n<BaseResultImage :result=\"result\">\n <template #placeholder>\n <img alt=\"Placeholder image\" src=\"./placeholder-image.svg\"/>\n </template>\n <template #fallback>\n <img alt=\"Fallback image\" src=\"./fallback-image.svg\"/>\n </template>\n</BaseResultImage>\n```\n\n### Customizing the animations\n\nTwo animations can be used this component.\n\nThe `loadAnimation` is used to transition between the placeholder, the fallback and the image.\n\nThe `hoverAnimation` is used to transition between the image and the hover image, if the\n`showNextImageOnHover` prop is `true`.\n\n`hoverAnimation` will default to `loadAnimation` if it is not provided.\n\n```vue\n<template>\n <BaseResultImage\n :result=\"result\"\n :loadAnimation=\"loadAnimation\"\n :hoverAnimation=\"hoverAnimation\"\n showNextImageOnHover\n />\n</template>\n\n<script>\n import { BaseResultImage } from '@empathyco/x-components';\n import { CrossFade, CollapseHeight } from '@empathyco/x-components/animations';\n\n export default {\n name: 'BaseResultImageAnimations',\n components: {\n BaseResultImage\n },\n data() {\n return {\n loadAnimation: CrossFade,\n hoverAnimation: CollapseHeight,\n result: {\n name: 'jacket',\n images: ['https://some-image', 'https://some-image-2']\n }\n };\n }\n };\n</script>\n```\n</docs>\n"],"names":["_openBlock","_createElementBlock","_Fragment","_createCommentVNode","_createElementVNode","isHovering","shouldLoadNextImage","pendingImages","animation","_createBlock","_resolveDynamicComponent","loadedImages","_renderSlot","imageSrc"],"mappings":";;;;;;;AACE,SAAA,WAAA,CAAA,IAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,EAAA,KAAA,EAAA,QAAA,EAAA;AACA,EAAA,OAAAA,SAAA,EAAA,EAAAC,kBAAA;AAAA,IAAAC,QAAA;AAAA,IAAA,IAAA;AAAA,IAAA;AAAA,MACAC,kBAmCM,CAAA,6FAAA,CAAA;AAAA,MAAAA,kBAAA,CAtCR,6EAIyC,CAAA;AAAA,MAAAC,kBAAA;AAC1B,QAAA,KAAA;AAAA,QAAA;AAAA,UACV,gBAAA,EAAU,sCAAEC,IAAU,CAAA,mBAAA,GAAA,IAAA,CAAA;AAAA,UACvB,cAAM,MAA4B,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,CAAA,MAAA,KAAA,IAAA,CAAA,UAAA,GAAA,IAAA,CAAA;AAAA,UAClC,cAAU,MAAgB,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,CAAA,MAAA,KAAA,IAAA,CAAA,UAAA,GAAA,KAAA,CAAA;AAAA,UAAA,KAAA,EAAA,4BAAA;AAGlBC,UAAAA,WAAAA,EAAAA,gBAAAA;AAAAA,SAAAA;;eAXZ,mBAYW,IAAAN,SAAA,EAAA,EAAAC,kBAAA,CAAA,KAAA,EAAA;AAAA,YACJ,GAAA,EAAA,CAAA;AAAA,YACD,QAAO,MAAO,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,CAAA,GAAA,IAAA,KAAA,IAAA,CAAA,eAAA,IAAA,IAAA,CAAA,eAAA,CAAA,GAAA,IAAA,CAAA,CAAA;AAAA,YACb,SAAKM,MAAa,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,CAAA,GAAA,IAAA,KAAA,IAAA,CAAA,iBAAA,IAAA,IAAA,CAAA,iBAAA,CAAA,GAAA,IAAA,CAAA,CAAA;AAAA,YAClB,OAhBP,EAAA,MAAA;AAAA,YAiBM,GAAA,EAAK,KAAC,aAAiB,CAAA,CAAA,CAAA;AAAA,YACvB,sBAAU,IAAuB,CAAA,YAAA,CAAA;AAAA,YACjC,KAAM,EAAA,iBAAA;AAAA,YACN,WAAK,EAAA,uBAAA;AAAA,YAAA,GAAA,EAAA,EAAA;AApBX,YAAA,IAAA,EAAA,cAAA;AAAA,WAsBoBC,EAAAA,IAAAA,EAAAA,EAAAA,EAAAA,UAAAA,CAAAA,IAAWL,mBAAM,MAAiB,EAAA,IAAA,CAAA;AAAA,WAAAH,SAAA,EAAe,EAAAS,WAAA,CAAAC,uBAAA,CAAA,IAAA,CAAA,SAAA,CAAA,EAAA;AAAA,YAAA,KAAA,EAAA,iBAAA;AAtBrE,YAAA,MAAA,EAAA,KAAA;AAAA,WAAA,EAAA;qBAwBmBC,OAAa,CAAA,MAAA;AAAA,cAAAR,kBAAA,CAAmD,gFAxBnF,CAAA;AAAA,cAAA,CAAA,IAAA,CA2BwBQ,uBAAmB,CAArC,IAAA,CAAA,aAAA,CAAA,MAAA,GAAAC,UAAA,CAAA,IAAA,CAAA,MAAA,EAAA,UAAA,EAA4D,EA3BlE,GAAA,EAAA,CAAA,EAAA,EAAA,KAAA,CAAA,EAAA,IAAA,CAAA,GAAA,CAAA,IAAA,CA0BM,kCAC4D,EAAAX,kBAAA;AAAA,gBAAAC,QAAA;AAAA,gBAAA,EAAA,GAAA,EAAA,CAAA,EAAA;AAAA,gBAAA;AAAA,kBAAAC,kBAAA,CAAA,uFAAA,CAAA;kCAE5D,MAOE,EAAA,aAAA,EAAA,EAAA,EAAA,KAAA,CAAA,EAAA,IAAA,CAAA;AAAA,iBAAA;AALC,gBAAA,IAAA;AAAA;AAAA,+BACgB,EAAAF,kBAAA,CAAA,KAAA,EAAA;AAAA,gBAChB,GAAKY,EAAAA,IAAAA,CAAAA,QAAAA;AAAAA,gBACN,KAAK,IAAC,CAAA,MAAA,CAAA,IAAA;AAAA,gBACN,GAAA,EAAA,IAAA,CAAA,QAAA;AAAA,gBAAA,KAAA,EAAA,wBAAA;;AAnCR,eAAA,EAAA,IAAA,EAAA,CAAA,EAAA,UAAA,CAAA,CAAA;AAAA,aAAA,CAAA;;;;;;;;;;;;;;;;;"}
|
|
@@ -113,7 +113,7 @@ var _sfc_main = defineComponent({
|
|
|
113
113
|
* @internal
|
|
114
114
|
*/
|
|
115
115
|
const animation = computed(() => {
|
|
116
|
-
return userHasHoveredImage
|
|
116
|
+
return userHasHoveredImage.value
|
|
117
117
|
? props.hoverAnimation ?? props.loadAnimation
|
|
118
118
|
: props.loadAnimation;
|
|
119
119
|
});
|
|
@@ -135,7 +135,7 @@ var _sfc_main = defineComponent({
|
|
|
135
135
|
* @internal
|
|
136
136
|
*/
|
|
137
137
|
const shouldLoadNextImage = computed(() => {
|
|
138
|
-
const numImagesToLoad = props.showNextImageOnHover && userHasHoveredImage ? 2 : 1;
|
|
138
|
+
const numImagesToLoad = props.showNextImageOnHover && userHasHoveredImage.value ? 2 : 1;
|
|
139
139
|
return !!pendingImages.value.length && loadedImages.value.length < numImagesToLoad;
|
|
140
140
|
});
|
|
141
141
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-result-image.vue2.js","sources":["../../../../src/components/result/base-result-image.vue"],"sourcesContent":["<template>\n <!-- This is a div because using a picture causes the onload event of the image to fire twice. -->\n <!-- eslint-disable-next-line vuejs-accessibility/mouse-events-have-key-events -->\n <div\n @mouseenter.once=\"userHasHoveredImage = true\"\n @mouseenter=\"isHovering = true\"\n @mouseleave=\"isHovering = false\"\n class=\"x-result-picture x-picture\"\n data-test=\"result-picture\"\n >\n <img\n v-if=\"shouldLoadNextImage\"\n @load=\"flagImageLoaded\"\n @error=\"flagImageAsFailed\"\n loading=\"lazy\"\n :src=\"pendingImages[0]\"\n :style=\"loaderStyles\"\n class=\"x-picture-image\"\n data-test=\"result-picture-loader\"\n alt=\"\"\n role=\"presentation\"\n />\n <component :is=\"animation\" class=\"x-picture-image\" :appear=\"false\">\n <!-- @slot Fallback image content. It will be rendered when all the images failed -->\n <slot v-if=\"!loadedImages.length && !pendingImages.length\" name=\"fallback\" />\n\n <!-- @slot Loading image content. It will be rendered while the real image is not loaded -->\n <slot v-else-if=\"!loadedImages.length\" name=\"placeholder\" />\n\n <img\n v-else\n :key=\"imageSrc\"\n :alt=\"result.name\"\n :src=\"imageSrc\"\n class=\"x-result-picture-image\"\n data-test=\"result-picture-image\"\n />\n </component>\n </div>\n</template>\n\n<script lang=\"ts\">\n import { Result } from '@empathyco/x-types';\n import { computed, defineComponent, PropType, Ref, ref, watch } from 'vue';\n import { NoAnimation } from '../animations';\n import { AnimationProp } from '../../types';\n\n /**\n * Component to be reused that renders an `<img>`.\n *\n * @public\n */\n export default defineComponent({\n name: 'BaseResultImage',\n props: {\n /** (Required) The {@link @empathyco/x-types#Result} information. */\n result: {\n type: Object as PropType<Result>,\n required: true\n },\n /**\n * Animation to use when switching between the placeholder, the loaded image, or the failed\n * image fallback.\n */\n loadAnimation: {\n type: AnimationProp,\n default: () => NoAnimation\n },\n /** Animation to use when switching between the loaded image and the hover image. */\n hoverAnimation: {\n type: AnimationProp\n },\n /**\n * Indicates if the next valid image should be displayed on hover.\n *\n * @public\n */\n showNextImageOnHover: {\n type: Boolean,\n default: false\n }\n },\n setup(props) {\n /**\n * Copy of the images of the result.\n *\n * It is used as a queue of images to load, once an image loads/fails to load, it is removed\n * from this array.\n *\n * @internal\n */\n const pendingImages: Ref<string[]> = ref([]);\n\n /**\n * Contains the images that have been loaded successfully.\n *\n * @internal\n */\n const loadedImages: Ref<string[]> = ref([]);\n\n /**\n * Indicates if the user is hovering the image.\n *\n * @internal\n */\n const isHovering = ref(false);\n\n /**\n * Indicates if the user has hovered the image.\n *\n * @internal\n */\n const userHasHoveredImage = ref(false);\n\n /**.\n * Styles to use inline in the image loader, to prevent override from CSS\n *\n * @internal\n */\n const loaderStyles: Partial<CSSStyleDeclaration> = {\n position: 'absolute !important',\n top: '0 !important',\n left: '0 !important',\n width: '100% !important',\n height: '100% !important',\n pointerEvents: 'none !important',\n visibility: 'hidden !important'\n };\n\n /**\n * Initializes images state and resets when the result's images change.\n *\n * @internal\n */\n watch(\n () => props.result.images,\n () => {\n pendingImages.value = [...(props.result.images ?? [])];\n loadedImages.value = pendingImages.value.filter(image =>\n loadedImages.value.includes(image)\n );\n },\n { immediate: true }\n );\n\n /**\n * Animation to be used.\n *\n * @returns The animation to be used, taking into account if the user has hovered the image.\n *\n * @internal\n */\n const animation = computed(() => {\n return userHasHoveredImage\n ? props.hoverAnimation ?? props.loadAnimation\n : props.loadAnimation;\n });\n\n /**\n * Gets the src from the result image.\n *\n * @returns The result image src.\n *\n * @internal\n */\n const imageSrc = computed(() => {\n return loadedImages.value[\n !props.showNextImageOnHover || !isHovering.value ? 0 : loadedImages.value.length - 1\n ];\n });\n\n /**\n * Indicates if the loader should try to load the next image.\n *\n * @returns True if it should try to load the next image.\n *\n * @internal\n */\n const shouldLoadNextImage = computed(() => {\n const numImagesToLoad = props.showNextImageOnHover && userHasHoveredImage ? 2 : 1;\n return !!pendingImages.value.length && loadedImages.value.length < numImagesToLoad;\n });\n\n /**\n * Sets an image as failed.\n *\n * @internal\n */\n const flagImageAsFailed = () => {\n pendingImages.value.shift();\n };\n\n /**\n * Sets an image as loaded.\n *\n * @internal\n */\n const flagImageLoaded = () => {\n const image = pendingImages.value.shift();\n if (image) {\n loadedImages.value.push(image);\n }\n };\n\n return {\n pendingImages,\n loadedImages,\n isHovering,\n userHasHoveredImage,\n loaderStyles,\n animation,\n imageSrc,\n shouldLoadNextImage,\n flagImageAsFailed,\n flagImageLoaded\n };\n }\n });\n</script>\n\n<style lang=\"css\" scoped>\n .x-result-picture {\n position: relative;\n min-width: 1px;\n min-height: 1px;\n }\n\n .x-result-picture-image {\n max-width: 100%;\n max-height: 100%;\n }\n</style>\n\n<docs lang=\"mdx\">\n## Examples\n\n### Basic example\n\nThis component is for the result image. It may be part of the search result page, recommendations or\nother section which needs to include results.\n\nThe result prop is required. It will render a `<img/>` with the result image:\n\n```vue\n<BaseResultImage :result=\"result\" />\n```\n\n### Showing the next image on hover\n\nIf a result has multiple images, it can show the next one on hover.\n\n```vue\n<BaseResultImage :result=\"result\" showNextImageOnHover />\n```\n\n### Customizing slots content\n\nFallback and placeholder contents can be customized.\n\nThe fallback slot allows you to replace the content of the fallback image.\n\nThe other slot is called `placeholder`, and allows you to set the image that its going to be\ndisplayed while the real one is loaded.\n\n```vue\n<BaseResultImage :result=\"result\">\n <template #placeholder>\n <img alt=\"Placeholder image\" src=\"./placeholder-image.svg\"/>\n </template>\n <template #fallback>\n <img alt=\"Fallback image\" src=\"./fallback-image.svg\"/>\n </template>\n</BaseResultImage>\n```\n\n### Customizing the animations\n\nTwo animations can be used this component.\n\nThe `loadAnimation` is used to transition between the placeholder, the fallback and the image.\n\nThe `hoverAnimation` is used to transition between the image and the hover image, if the\n`showNextImageOnHover` prop is `true`.\n\n`hoverAnimation` will default to `loadAnimation` if it is not provided.\n\n```vue\n<template>\n <BaseResultImage\n :result=\"result\"\n :loadAnimation=\"loadAnimation\"\n :hoverAnimation=\"hoverAnimation\"\n showNextImageOnHover\n />\n</template>\n\n<script>\n import { BaseResultImage } from '@empathyco/x-components';\n import { CrossFade, CollapseHeight } from '@empathyco/x-components/animations';\n\n export default {\n name: 'BaseResultImageAnimations',\n components: {\n BaseResultImage\n },\n data() {\n return {\n loadAnimation: CrossFade,\n hoverAnimation: CollapseHeight,\n result: {\n name: 'jacket',\n images: ['https://some-image', 'https://some-image-2']\n }\n };\n }\n };\n</script>\n```\n</docs>\n"],"names":["NoAnimation"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-result-image.vue2.js","sources":["../../../../src/components/result/base-result-image.vue"],"sourcesContent":["<template>\n <!-- This is a div because using a picture causes the onload event of the image to fire twice. -->\n <!-- eslint-disable-next-line vuejs-accessibility/mouse-events-have-key-events -->\n <div\n @mouseenter.once=\"userHasHoveredImage = true\"\n @mouseenter=\"isHovering = true\"\n @mouseleave=\"isHovering = false\"\n class=\"x-result-picture x-picture\"\n data-test=\"result-picture\"\n >\n <img\n v-if=\"shouldLoadNextImage\"\n @load=\"flagImageLoaded\"\n @error=\"flagImageAsFailed\"\n loading=\"lazy\"\n :src=\"pendingImages[0]\"\n :style=\"loaderStyles\"\n class=\"x-picture-image\"\n data-test=\"result-picture-loader\"\n alt=\"\"\n role=\"presentation\"\n />\n <component :is=\"animation\" class=\"x-picture-image\" :appear=\"false\">\n <!-- @slot Fallback image content. It will be rendered when all the images failed -->\n <slot v-if=\"!loadedImages.length && !pendingImages.length\" name=\"fallback\" />\n\n <!-- @slot Loading image content. It will be rendered while the real image is not loaded -->\n <slot v-else-if=\"!loadedImages.length\" name=\"placeholder\" />\n\n <img\n v-else\n :key=\"imageSrc\"\n :alt=\"result.name\"\n :src=\"imageSrc\"\n class=\"x-result-picture-image\"\n data-test=\"result-picture-image\"\n />\n </component>\n </div>\n</template>\n\n<script lang=\"ts\">\n import { Result } from '@empathyco/x-types';\n import { computed, defineComponent, PropType, Ref, ref, watch } from 'vue';\n import { NoAnimation } from '../animations';\n import { AnimationProp } from '../../types';\n\n /**\n * Component to be reused that renders an `<img>`.\n *\n * @public\n */\n export default defineComponent({\n name: 'BaseResultImage',\n props: {\n /** (Required) The {@link @empathyco/x-types#Result} information. */\n result: {\n type: Object as PropType<Result>,\n required: true\n },\n /**\n * Animation to use when switching between the placeholder, the loaded image, or the failed\n * image fallback.\n */\n loadAnimation: {\n type: AnimationProp,\n default: () => NoAnimation\n },\n /** Animation to use when switching between the loaded image and the hover image. */\n hoverAnimation: {\n type: AnimationProp\n },\n /**\n * Indicates if the next valid image should be displayed on hover.\n *\n * @public\n */\n showNextImageOnHover: {\n type: Boolean,\n default: false\n }\n },\n setup(props) {\n /**\n * Copy of the images of the result.\n *\n * It is used as a queue of images to load, once an image loads/fails to load, it is removed\n * from this array.\n *\n * @internal\n */\n const pendingImages: Ref<string[]> = ref([]);\n\n /**\n * Contains the images that have been loaded successfully.\n *\n * @internal\n */\n const loadedImages: Ref<string[]> = ref([]);\n\n /**\n * Indicates if the user is hovering the image.\n *\n * @internal\n */\n const isHovering = ref(false);\n\n /**\n * Indicates if the user has hovered the image.\n *\n * @internal\n */\n const userHasHoveredImage = ref(false);\n\n /**.\n * Styles to use inline in the image loader, to prevent override from CSS\n *\n * @internal\n */\n const loaderStyles: Partial<CSSStyleDeclaration> = {\n position: 'absolute !important',\n top: '0 !important',\n left: '0 !important',\n width: '100% !important',\n height: '100% !important',\n pointerEvents: 'none !important',\n visibility: 'hidden !important'\n };\n\n /**\n * Initializes images state and resets when the result's images change.\n *\n * @internal\n */\n watch(\n () => props.result.images,\n () => {\n pendingImages.value = [...(props.result.images ?? [])];\n loadedImages.value = pendingImages.value.filter(image =>\n loadedImages.value.includes(image)\n );\n },\n { immediate: true }\n );\n\n /**\n * Animation to be used.\n *\n * @returns The animation to be used, taking into account if the user has hovered the image.\n *\n * @internal\n */\n const animation = computed(() => {\n return userHasHoveredImage.value\n ? props.hoverAnimation ?? props.loadAnimation\n : props.loadAnimation;\n });\n\n /**\n * Gets the src from the result image.\n *\n * @returns The result image src.\n *\n * @internal\n */\n const imageSrc = computed(() => {\n return loadedImages.value[\n !props.showNextImageOnHover || !isHovering.value ? 0 : loadedImages.value.length - 1\n ];\n });\n\n /**\n * Indicates if the loader should try to load the next image.\n *\n * @returns True if it should try to load the next image.\n *\n * @internal\n */\n const shouldLoadNextImage = computed(() => {\n const numImagesToLoad = props.showNextImageOnHover && userHasHoveredImage.value ? 2 : 1;\n return !!pendingImages.value.length && loadedImages.value.length < numImagesToLoad;\n });\n\n /**\n * Sets an image as failed.\n *\n * @internal\n */\n const flagImageAsFailed = () => {\n pendingImages.value.shift();\n };\n\n /**\n * Sets an image as loaded.\n *\n * @internal\n */\n const flagImageLoaded = () => {\n const image = pendingImages.value.shift();\n if (image) {\n loadedImages.value.push(image);\n }\n };\n\n return {\n pendingImages,\n loadedImages,\n isHovering,\n userHasHoveredImage,\n loaderStyles,\n animation,\n imageSrc,\n shouldLoadNextImage,\n flagImageAsFailed,\n flagImageLoaded\n };\n }\n });\n</script>\n\n<style lang=\"css\" scoped>\n .x-result-picture {\n position: relative;\n min-width: 1px;\n min-height: 1px;\n }\n\n .x-result-picture-image {\n max-width: 100%;\n max-height: 100%;\n }\n</style>\n\n<docs lang=\"mdx\">\n## Examples\n\n### Basic example\n\nThis component is for the result image. It may be part of the search result page, recommendations or\nother section which needs to include results.\n\nThe result prop is required. It will render a `<img/>` with the result image:\n\n```vue\n<BaseResultImage :result=\"result\" />\n```\n\n### Showing the next image on hover\n\nIf a result has multiple images, it can show the next one on hover.\n\n```vue\n<BaseResultImage :result=\"result\" showNextImageOnHover />\n```\n\n### Customizing slots content\n\nFallback and placeholder contents can be customized.\n\nThe fallback slot allows you to replace the content of the fallback image.\n\nThe other slot is called `placeholder`, and allows you to set the image that its going to be\ndisplayed while the real one is loaded.\n\n```vue\n<BaseResultImage :result=\"result\">\n <template #placeholder>\n <img alt=\"Placeholder image\" src=\"./placeholder-image.svg\"/>\n </template>\n <template #fallback>\n <img alt=\"Fallback image\" src=\"./fallback-image.svg\"/>\n </template>\n</BaseResultImage>\n```\n\n### Customizing the animations\n\nTwo animations can be used this component.\n\nThe `loadAnimation` is used to transition between the placeholder, the fallback and the image.\n\nThe `hoverAnimation` is used to transition between the image and the hover image, if the\n`showNextImageOnHover` prop is `true`.\n\n`hoverAnimation` will default to `loadAnimation` if it is not provided.\n\n```vue\n<template>\n <BaseResultImage\n :result=\"result\"\n :loadAnimation=\"loadAnimation\"\n :hoverAnimation=\"hoverAnimation\"\n showNextImageOnHover\n />\n</template>\n\n<script>\n import { BaseResultImage } from '@empathyco/x-components';\n import { CrossFade, CollapseHeight } from '@empathyco/x-components/animations';\n\n export default {\n name: 'BaseResultImageAnimations',\n components: {\n BaseResultImage\n },\n data() {\n return {\n loadAnimation: CrossFade,\n hoverAnimation: CollapseHeight,\n result: {\n name: 'jacket',\n images: ['https://some-image', 'https://some-image-2']\n }\n };\n }\n };\n</script>\n```\n</docs>\n"],"names":["NoAnimation"],"mappings":"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"}
|
package/js/index.js
CHANGED
|
@@ -157,7 +157,10 @@ export { isSimpleSelector, registerStoreEmitters } from './plugins/x-emitters.js
|
|
|
157
157
|
export { XPlugin, xPlugin } from './plugins/x-plugin.js';
|
|
158
158
|
export { assertXPluginOptionsAreValid, getGetterPath } from './plugins/x-plugin.utils.js';
|
|
159
159
|
export { DirectionalFocusNavigationService } from './services/directional-focus-navigation.service.js';
|
|
160
|
+
export { mergeConfig, setConfig } from './store/utils/config-store.utils.js';
|
|
160
161
|
export { createFetchAndSaveActions } from './store/utils/fetch-and-save-action.utils.js';
|
|
162
|
+
export { cleanGettersProxyCache, getGettersProxy, getGettersProxyFromModule } from './store/utils/getters-proxy.utils.js';
|
|
163
|
+
export { createRelatedTagsQueryGetter, setQuery } from './store/utils/query.utils.js';
|
|
161
164
|
export { setStatus } from './store/utils/status-store.utils.js';
|
|
162
165
|
export { createStoreEmitters } from './store/utils/store-emitters.utils.js';
|
|
163
166
|
export { RootXStoreModule } from './store/x.module.js';
|
package/js/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/device/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { deviceXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the device module.\n *\n * @internal\n */\nexport const deviceEmitters = createStoreEmitters(deviceXStoreModule, {});\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/device/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { deviceXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the device module.\n *\n * @internal\n */\nexport const deviceEmitters = createStoreEmitters(deviceXStoreModule, {});\n"],"names":[],"mappings":";;;;AAGA;;;;AAIG;AACU,MAAA,cAAc,GAAG,mBAAmB,CAAC,kBAAkB,EAAE,EAAE;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/empathize/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { empathizeXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the empathize module.\n *\n * @internal\n */\nexport const empathizeEmitters = createStoreEmitters(empathizeXStoreModule, {});\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/empathize/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { empathizeXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the empathize module.\n *\n * @internal\n */\nexport const empathizeEmitters = createStoreEmitters(empathizeXStoreModule, {});\n"],"names":[],"mappings":";;;;AAGA;;;;AAIG;AACU,MAAA,iBAAiB,GAAG,mBAAmB,CAAC,qBAAqB,EAAE,EAAE;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/experience-controls/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { experienceControlsXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the {@link ExperienceControlsXModule}.\n *\n * @internal\n */\nexport const experienceControlsEmitters = createStoreEmitters(experienceControlsXStoreModule, {\n ExperienceControlsEventsChanged: {\n selector: state => state.events\n },\n ExperienceControlsRequestUpdated: (_, getters) => getters.experienceControlsRequest\n});\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/experience-controls/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { experienceControlsXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the {@link ExperienceControlsXModule}.\n *\n * @internal\n */\nexport const experienceControlsEmitters = createStoreEmitters(experienceControlsXStoreModule, {\n ExperienceControlsEventsChanged: {\n selector: state => state.events\n },\n ExperienceControlsRequestUpdated: (_, getters) => getters.experienceControlsRequest\n});\n"],"names":[],"mappings":";;;;AAGA;;;;AAIG;AACU,MAAA,0BAA0B,GAAG,mBAAmB,CAAC,8BAA8B,EAAE;AAC5F,IAAA,+BAA+B,EAAE;AAC/B,QAAA,QAAQ,EAAE,KAAK,IAAI,KAAK,CAAC,MAAM;AAChC,KAAA;IACD,gCAAgC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,yBAAyB;AACpF,CAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/extra-params/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { extraParamsXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the {@link ExtraParamsXModule}.\n *\n * @internal\n */\nexport const extraParamsEmitters = createStoreEmitters(extraParamsXStoreModule, {\n ExtraParamsChanged: {\n selector: state => state.params,\n metadata: { priority: 9 }\n }\n});\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/extra-params/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { extraParamsXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the {@link ExtraParamsXModule}.\n *\n * @internal\n */\nexport const extraParamsEmitters = createStoreEmitters(extraParamsXStoreModule, {\n ExtraParamsChanged: {\n selector: state => state.params,\n metadata: { priority: 9 }\n }\n});\n"],"names":[],"mappings":";;;;AAGA;;;;AAIG;AACU,MAAA,mBAAmB,GAAG,mBAAmB,CAAC,uBAAuB,EAAE;AAC9E,IAAA,kBAAkB,EAAE;AAClB,QAAA,QAAQ,EAAE,KAAK,IAAI,KAAK,CAAC,MAAM;AAC/B,QAAA,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE;AAC1B,KAAA;AACF,CAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/history-queries/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { historyQueriesXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the history-queries module.\n *\n * @internal\n */\nexport const historyQueriesEmitters = createStoreEmitters(historyQueriesXStoreModule, {\n // Used for filtering next-queries with the session history queries\n SessionHistoryQueriesChanged: (_state, getters) => getters.sessionHistoryQueries,\n // Used for refreshing the session until it is extracted from the history queries module\n HistoryQueriesQueryChanged: { immediate: true, selector: state => state.query },\n // Used to load history-queries from the browser storage\n HistoryQueriesStorageKeyChanged: {\n immediate: true,\n selector: (_state, getters) => getters.storageKey\n }\n});\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/history-queries/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { historyQueriesXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the history-queries module.\n *\n * @internal\n */\nexport const historyQueriesEmitters = createStoreEmitters(historyQueriesXStoreModule, {\n // Used for filtering next-queries with the session history queries\n SessionHistoryQueriesChanged: (_state, getters) => getters.sessionHistoryQueries,\n // Used for refreshing the session until it is extracted from the history queries module\n HistoryQueriesQueryChanged: { immediate: true, selector: state => state.query },\n // Used to load history-queries from the browser storage\n HistoryQueriesStorageKeyChanged: {\n immediate: true,\n selector: (_state, getters) => getters.storageKey\n }\n});\n"],"names":[],"mappings":";;;;AAGA;;;;AAIG;AACU,MAAA,sBAAsB,GAAG,mBAAmB,CAAC,0BAA0B,EAAE;;IAEpF,4BAA4B,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,qBAAqB;;AAEhF,IAAA,0BAA0B,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,IAAI,KAAK,CAAC,KAAK,EAAE;;AAE/E,IAAA,+BAA+B,EAAE;AAC/B,QAAA,SAAS,EAAE,IAAI;QACf,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,UAAU;AAClD,KAAA;AACF,CAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/identifier-results/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { createEmitterArrayFilter } from '../../../utils/array';\nimport { identifierResultsXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the identifier-results module.\n *\n * @internal\n */\nexport const identifierResultsEmitters = createStoreEmitters(identifierResultsXStoreModule, {\n IdentifierResultsChanged: {\n selector: state => state.identifierResults,\n filter: createEmitterArrayFilter('id')\n },\n IdentifierResultsRequestUpdated: (_, getters) => getters.identifierResultsRequest\n});\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/identifier-results/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { createEmitterArrayFilter } from '../../../utils/array';\nimport { identifierResultsXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the identifier-results module.\n *\n * @internal\n */\nexport const identifierResultsEmitters = createStoreEmitters(identifierResultsXStoreModule, {\n IdentifierResultsChanged: {\n selector: state => state.identifierResults,\n filter: createEmitterArrayFilter('id')\n },\n IdentifierResultsRequestUpdated: (_, getters) => getters.identifierResultsRequest\n});\n"],"names":[],"mappings":";;;;;AAIA;;;;AAIG;AACU,MAAA,yBAAyB,GAAG,mBAAmB,CAAC,6BAA6B,EAAE;AAC1F,IAAA,wBAAwB,EAAE;AACxB,QAAA,QAAQ,EAAE,KAAK,IAAI,KAAK,CAAC,iBAAiB;AAC1C,QAAA,MAAM,EAAE,wBAAwB,CAAC,IAAI,CAAC;AACvC,KAAA;IACD,+BAA+B,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,wBAAwB;AAClF,CAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/next-queries/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { createEmitterArrayFilter } from '../../../utils/array';\nimport { nextQueriesXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the next-queries module.\n *\n * @internal\n */\nexport const nextQueriesEmitters = createStoreEmitters(nextQueriesXStoreModule, {\n NextQueriesChanged: {\n selector: (_, getters) => getters.nextQueries,\n filter: createEmitterArrayFilter('query')\n },\n NextQueriesRequestUpdated: (_, getters) => getters.request\n});\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/next-queries/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { createEmitterArrayFilter } from '../../../utils/array';\nimport { nextQueriesXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the next-queries module.\n *\n * @internal\n */\nexport const nextQueriesEmitters = createStoreEmitters(nextQueriesXStoreModule, {\n NextQueriesChanged: {\n selector: (_, getters) => getters.nextQueries,\n filter: createEmitterArrayFilter('query')\n },\n NextQueriesRequestUpdated: (_, getters) => getters.request\n});\n"],"names":[],"mappings":";;;;;AAIA;;;;AAIG;AACU,MAAA,mBAAmB,GAAG,mBAAmB,CAAC,uBAAuB,EAAE;AAC9E,IAAA,kBAAkB,EAAE;QAClB,QAAQ,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,WAAW;AAC7C,QAAA,MAAM,EAAE,wBAAwB,CAAC,OAAO,CAAC;AAC1C,KAAA;IACD,yBAAyB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO;AAC3D,CAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/queries-preview/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { queriesPreviewXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the queries-preview module.\n *\n * @internal\n */\nexport const queriesPreviewEmitters = createStoreEmitters(queriesPreviewXStoreModule, {\n QueryPreviewUnselected: {\n selector: state =>\n !state.selectedQueryPreview ? state.params : state.selectedQueryPreview.extraParams!,\n filter: (newValue, oldValue, state) => !state.selectedQueryPreview\n }\n});\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/queries-preview/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { queriesPreviewXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the queries-preview module.\n *\n * @internal\n */\nexport const queriesPreviewEmitters = createStoreEmitters(queriesPreviewXStoreModule, {\n QueryPreviewUnselected: {\n selector: state =>\n !state.selectedQueryPreview ? state.params : state.selectedQueryPreview.extraParams!,\n filter: (newValue, oldValue, state) => !state.selectedQueryPreview\n }\n});\n"],"names":[],"mappings":";;;;AAGA;;;;AAIG;AACU,MAAA,sBAAsB,GAAG,mBAAmB,CAAC,0BAA0B,EAAE;AACpF,IAAA,sBAAsB,EAAE;QACtB,QAAQ,EAAE,KAAK,IACb,CAAC,KAAK,CAAC,oBAAoB,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,oBAAoB,CAAC,WAAY;AACtF,QAAA,MAAM,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,KAAK,CAAC,KAAK,CAAC,oBAAoB;AACnE,KAAA;AACF,CAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/query-suggestions/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { querySuggestionsXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the query-suggestions module.\n *\n * @internal\n */\nexport const querySuggestionsEmitters = createStoreEmitters(querySuggestionsXStoreModule, {\n QuerySuggestionsChanged: state => state.suggestions,\n QuerySuggestionsRequestUpdated: (_, getters) => getters.request\n});\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/query-suggestions/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { querySuggestionsXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the query-suggestions module.\n *\n * @internal\n */\nexport const querySuggestionsEmitters = createStoreEmitters(querySuggestionsXStoreModule, {\n QuerySuggestionsChanged: state => state.suggestions,\n QuerySuggestionsRequestUpdated: (_, getters) => getters.request\n});\n"],"names":[],"mappings":";;;;AAGA;;;;AAIG;AACU,MAAA,wBAAwB,GAAG,mBAAmB,CAAC,4BAA4B,EAAE;AACxF,IAAA,uBAAuB,EAAE,KAAK,IAAI,KAAK,CAAC,WAAW;IACnD,8BAA8B,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO;AAChE,CAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/recommendations/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { recommendationsXStoreModule } from './module';\n/**\n * {@link StoreEmitters} For the recommendations module.\n *\n * @internal\n */\nexport const recommendationsEmitters = createStoreEmitters(recommendationsXStoreModule, {\n RecommendationsChanged: state => state.recommendations,\n RecommendationsRequestUpdated: {\n selector: (_, getters) => getters.request\n }\n});\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/recommendations/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { recommendationsXStoreModule } from './module';\n/**\n * {@link StoreEmitters} For the recommendations module.\n *\n * @internal\n */\nexport const recommendationsEmitters = createStoreEmitters(recommendationsXStoreModule, {\n RecommendationsChanged: state => state.recommendations,\n RecommendationsRequestUpdated: {\n selector: (_, getters) => getters.request\n }\n});\n"],"names":[],"mappings":";;;;AAEA;;;;AAIG;AACU,MAAA,uBAAuB,GAAG,mBAAmB,CAAC,2BAA2B,EAAE;AACtF,IAAA,sBAAsB,EAAE,KAAK,IAAI,KAAK,CAAC,eAAe;AACtD,IAAA,6BAA6B,EAAE;QAC7B,QAAQ,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO;AAC1C,KAAA;AACF,CAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/related-prompts/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { relatedPromptsXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the related-prompts module.\n *\n * @internal\n */\nexport const relatedPromptsStoreEmitters = createStoreEmitters(relatedPromptsXStoreModule, {\n RelatedPromptsRequestUpdated: (_, getters) => getters.request\n});\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/related-prompts/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { relatedPromptsXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the related-prompts module.\n *\n * @internal\n */\nexport const relatedPromptsStoreEmitters = createStoreEmitters(relatedPromptsXStoreModule, {\n RelatedPromptsRequestUpdated: (_, getters) => getters.request\n});\n"],"names":[],"mappings":";;;;AAGA;;;;AAIG;AACU,MAAA,2BAA2B,GAAG,mBAAmB,CAAC,0BAA0B,EAAE;IACzF,4BAA4B,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO;AAC9D,CAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/related-tags/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { createEmitterArrayFilter } from '../../../utils/array';\nimport { relatedTagsXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the related-tags module.\n *\n * @internal\n */\nexport const relatedTagsEmitters = createStoreEmitters(relatedTagsXStoreModule, {\n RelatedTagsChanged: state => state.relatedTags,\n RelatedTagsRequestUpdated: (_, getters) => getters.request,\n SelectedRelatedTagsChanged: {\n selector: state => state.selectedRelatedTags,\n filter: createEmitterArrayFilter('tag')\n }\n});\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"emitters.js","sources":["../../../../../src/x-modules/related-tags/store/emitters.ts"],"sourcesContent":["import { createStoreEmitters } from '../../../store';\nimport { createEmitterArrayFilter } from '../../../utils/array';\nimport { relatedTagsXStoreModule } from './module';\n\n/**\n * {@link StoreEmitters} For the related-tags module.\n *\n * @internal\n */\nexport const relatedTagsEmitters = createStoreEmitters(relatedTagsXStoreModule, {\n RelatedTagsChanged: state => state.relatedTags,\n RelatedTagsRequestUpdated: (_, getters) => getters.request,\n SelectedRelatedTagsChanged: {\n selector: state => state.selectedRelatedTags,\n filter: createEmitterArrayFilter('tag')\n }\n});\n"],"names":[],"mappings":";;;;;AAIA;;;;AAIG;AACU,MAAA,mBAAmB,GAAG,mBAAmB,CAAC,uBAAuB,EAAE;AAC9E,IAAA,kBAAkB,EAAE,KAAK,IAAI,KAAK,CAAC,WAAW;IAC9C,yBAAyB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO;AAC1D,IAAA,0BAA0B,EAAE;AAC1B,QAAA,QAAQ,EAAE,KAAK,IAAI,KAAK,CAAC,mBAAmB;AAC5C,QAAA,MAAM,EAAE,wBAAwB,CAAC,KAAK,CAAC;AACxC,KAAA;AACF,CAAA;;;;"}
|