@empathyco/x-components 6.0.0-alpha.203 → 6.0.0-alpha.204
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 +11 -0
- package/docs/API-reference/api/x-components.addquerytohistory.md +15 -1
- package/docs/API-reference/api/x-components.addquerytohistoryaction.addquerytohistory.md +15 -1
- package/docs/API-reference/api/x-components.addquerytohistoryaction.md +1 -1
- package/docs/API-reference/api/x-components.basedropdown.md +1 -1
- package/docs/API-reference/api/x-components.baseidmodal.md +1 -1
- package/docs/API-reference/api/x-components.baseresultimage.md +1 -1
- package/docs/API-reference/api/x-components.collapseheight.md +3 -3
- package/docs/API-reference/api/x-components.collapsewidth.md +3 -3
- package/docs/API-reference/api/x-components.createfetchandsaveactions.md +15 -1
- package/docs/API-reference/api/x-components.createrelatedtagsquerygetter.md +15 -1
- package/docs/API-reference/api/x-components.debouncefunction.md +17 -1
- package/docs/API-reference/api/x-components.defaultfacetsservice.clearfilterswithmetadata.md +17 -1
- package/docs/API-reference/api/x-components.defaultfacetsservice.md +1 -1
- package/docs/API-reference/api/x-components.historyqueriesgetter.historyqueries.md +15 -1
- package/docs/API-reference/api/x-components.historyqueriesgetter.md +1 -1
- package/docs/API-reference/api/x-components.isinrange.md +15 -1
- package/docs/API-reference/api/x-components.md +10 -10
- package/docs/API-reference/api/x-components.querysuggestionsgetter.md +1 -1
- package/docs/API-reference/api/x-components.querysuggestionsgetter.querysuggestions.md +15 -1
- package/docs/API-reference/api/x-components.usecollapseanimation.md +4 -4
- package/docs/API-reference/api/x-components.useemitdisplayevent.md +15 -1
- package/docs/API-reference/api/x-components.useondisplay.md +15 -1
- package/docs/API-reference/api/x-components.usescroll.md +15 -1
- package/docs/API-reference/api/x-types.suggestion.key.md +1 -1
- package/docs/API-reference/api/x-types.suggestion.md +4 -4
- package/js/components/animations/animate-scale/animate-scale.style.css.js +1 -1
- package/js/components/animations/use-collapse-animation.js +9 -0
- package/js/components/animations/use-collapse-animation.js.map +1 -1
- package/js/components/base-dropdown.vue.js +3 -3
- package/js/components/base-dropdown.vue.js.map +1 -1
- package/js/components/base-dropdown.vue2.js +6 -4
- package/js/components/base-dropdown.vue2.js.map +1 -1
- package/js/components/base-grid.vue.js.map +1 -1
- package/js/components/base-grid.vue2.js.map +1 -1
- package/js/components/base-rating.vue3.js +1 -1
- package/js/components/column-picker/base-column-picker-list.vue.js.map +1 -1
- package/js/components/column-picker/base-column-picker-list.vue2.js.map +1 -1
- package/js/components/modals/base-id-modal.vue.js +1 -1
- package/js/components/modals/base-id-modal.vue.js.map +1 -1
- package/js/components/modals/base-id-modal.vue2.js +3 -3
- package/js/components/modals/base-id-modal.vue2.js.map +1 -1
- package/js/components/panels/base-tabs-panel.vue.js +1 -1
- package/js/components/panels/base-tabs-panel.vue.js.map +1 -1
- package/js/components/panels/base-tabs-panel.vue2.js.map +1 -1
- package/js/components/result/base-result-current-price.vue.js +4 -3
- package/js/components/result/base-result-current-price.vue.js.map +1 -1
- package/js/components/result/base-result-current-price.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 -1
- package/js/components/result/base-result-image.vue2.js.map +1 -1
- package/js/components/result/base-result-previous-price.vue.js +1 -1
- package/js/components/result/base-result-previous-price.vue.js.map +1 -1
- package/js/components/result/base-result-previous-price.vue2.js.map +1 -1
- package/js/components/sliding-panel.vue3.js +1 -1
- package/js/x-modules/facets/components/facets/facets.vue.js.map +1 -1
- package/js/x-modules/facets/components/facets/facets.vue2.js.map +1 -1
- package/js/x-modules/facets/components/filters/editable-number-range-filter.vue.js +2 -2
- package/js/x-modules/facets/components/filters/editable-number-range-filter.vue.js.map +1 -1
- package/js/x-modules/facets/components/filters/editable-number-range-filter.vue2.js.map +1 -1
- package/js/x-modules/facets/components/lists/filters-search.vue.js +1 -1
- package/js/x-modules/facets/components/lists/filters-search.vue.js.map +1 -1
- package/js/x-modules/facets/components/lists/filters-search.vue2.js.map +1 -1
- package/js/x-modules/facets/components/lists/filters-search.vue3.js +1 -1
- package/js/x-modules/facets/components/lists/selected-filters-list.vue.js.map +1 -1
- package/js/x-modules/facets/components/lists/selected-filters-list.vue2.js.map +1 -1
- package/js/x-modules/related-prompts/components/related-prompts-tag-list.vue.js.map +1 -1
- package/js/x-modules/related-prompts/components/related-prompts-tag-list.vue2.js.map +1 -1
- package/js/x-modules/search/components/sort-picker-list.vue.js.map +1 -1
- package/js/x-modules/search/components/sort-picker-list.vue2.js.map +1 -1
- package/package.json +17 -17
- package/report/tsdoc-metadata.json +1 -1
- package/report/x-adapter-platform.api.json +1 -1
- package/report/x-components.api.json +161 -67
- package/report/x-components.api.md +46 -45
- package/report/x-types.api.json +4 -4
- package/types/components/animations/collapse-height.vue.d.ts +3 -3
- package/types/components/animations/collapse-width.vue.d.ts +3 -3
- package/types/components/animations/use-collapse-animation.d.ts +3 -3
- package/types/components/animations/use-collapse-animation.d.ts.map +1 -1
- package/types/components/base-dropdown.vue.d.ts +1 -1
- package/types/components/base-dropdown.vue.d.ts.map +1 -1
- package/types/components/base-grid.types.d.ts +11 -0
- package/types/components/base-grid.types.d.ts.map +1 -0
- package/types/components/base-grid.vue.d.ts +2 -10
- package/types/components/base-grid.vue.d.ts.map +1 -1
- package/types/components/column-picker/base-column-picker-list.types.d.ts +9 -0
- package/types/components/column-picker/base-column-picker-list.types.d.ts.map +1 -0
- package/types/components/column-picker/base-column-picker-list.vue.d.ts +1 -8
- package/types/components/column-picker/base-column-picker-list.vue.d.ts.map +1 -1
- package/types/components/modals/base-id-modal.vue.d.ts +1 -1
- package/types/components/result/base-result-image.vue.d.ts +1 -1
- package/types/components/result/base-result-image.vue.d.ts.map +1 -1
- package/types/x-modules/facets/components/facets/facets.types.d.ts +12 -0
- package/types/x-modules/facets/components/facets/facets.types.d.ts.map +1 -0
- package/types/x-modules/facets/components/facets/facets.vue.d.ts +1 -10
- package/types/x-modules/facets/components/facets/facets.vue.d.ts.map +1 -1
- package/types/x-modules/facets/components/lists/selected-filters-list.types.d.ts +11 -0
- package/types/x-modules/facets/components/lists/selected-filters-list.types.d.ts.map +1 -0
- package/types/x-modules/facets/components/lists/selected-filters-list.vue.d.ts +1 -9
- package/types/x-modules/facets/components/lists/selected-filters-list.vue.d.ts.map +1 -1
- package/types/x-modules/search/components/sort-picker-list.types.d.ts +12 -0
- package/types/x-modules/search/components/sort-picker-list.types.d.ts.map +1 -0
- package/types/x-modules/search/components/sort-picker-list.vue.d.ts +1 -10
- package/types/x-modules/search/components/sort-picker-list.vue.d.ts.map +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import injectCss from '../../../../tools/inject-css.js';
|
|
2
2
|
|
|
3
|
-
var css = ".x-animate-scale--enter-active,.x-animate-scale--enter-active *,.x-animate-scale--leave-active,.x-animate-scale--leave-active *{animation-duration:var(--x-duration-animation,.3s);animation-timing-function:linear}.x-animate-scale--enter-active,.x-animate-scale--leave-active{overflow:hidden}.x-animate-scale--enter-active.x-animate-scale--bottom,.x-animate-scale--enter-active.x-animate-scale--bottom-to-top,.x-animate-scale--enter-active.x-animate-scale--top,.x-animate-scale--enter-active.x-animate-scale--top-to-bottom,.x-animate-scale--leave-active.x-animate-scale--bottom,.x-animate-scale--leave-active.x-animate-scale--bottom-to-top,.x-animate-scale--leave-active.x-animate-scale--top,.x-animate-scale--leave-active.x-animate-scale--top-to-bottom{animation-name:containerAnimationY}.x-animate-scale--enter-active.x-animate-scale--bottom-to-top>*,.x-animate-scale--enter-active.x-animate-scale--bottom>*,.x-animate-scale--enter-active.x-animate-scale--top-to-bottom>*,.x-animate-scale--enter-active.x-animate-scale--top>*,.x-animate-scale--leave-active.x-animate-scale--bottom-to-top>*,.x-animate-scale--leave-active.x-animate-scale--bottom>*,.x-animate-scale--leave-active.x-animate-scale--top-to-bottom>*,.x-animate-scale--leave-active.x-animate-scale--top>*{animation-name:contentAnimationY}.x-animate-scale--enter-active.x-animate-scale--left,.x-animate-scale--enter-active.x-animate-scale--left-to-right,.x-animate-scale--enter-active.x-animate-scale--right,.x-animate-scale--enter-active.x-animate-scale--right-to-left,.x-animate-scale--leave-active.x-animate-scale--left,.x-animate-scale--leave-active.x-animate-scale--left-to-right,.x-animate-scale--leave-active.x-animate-scale--right,.x-animate-scale--leave-active.x-animate-scale--right-to-left{animation-name:containerAnimationX}.x-animate-scale--enter-active.x-animate-scale--left-to-right>*,.x-animate-scale--enter-active.x-animate-scale--left>*,.x-animate-scale--enter-active.x-animate-scale--right-to-left>*,.x-animate-scale--enter-active.x-animate-scale--right>*,.x-animate-scale--leave-active.x-animate-scale--left-to-right>*,.x-animate-scale--leave-active.x-animate-scale--left>*,.x-animate-scale--leave-active.x-animate-scale--right-to-left>*,.x-animate-scale--leave-active.x-animate-scale--right>*{animation-name:contentAnimationX}.x-animate-scale--leave-active,.x-animate-scale--leave-active>*{animation-direction:reverse}.x-animate-scale--top.x-animate-scale--enter-active,.x-animate-scale--top.x-animate-scale--enter-active>*,.x-animate-scale--top.x-animate-scale--leave-active,.x-animate-scale--top.x-animate-scale--leave-active>*{transform-origin:top center}.x-animate-scale--bottom.x-animate-scale--enter-active,.x-animate-scale--bottom.x-animate-scale--enter-active>*,.x-animate-scale--bottom.x-animate-scale--leave-active,.x-animate-scale--bottom.x-animate-scale--leave-active>*{transform-origin:bottom center}.x-animate-scale--left.x-animate-scale--enter-active,.x-animate-scale--left.x-animate-scale--enter-active>*,.x-animate-scale--left.x-animate-scale--leave-active,.x-animate-scale--left.x-animate-scale--leave-active>*{transform-origin:left center}.x-animate-scale--right.x-animate-scale--enter-active,.x-animate-scale--right.x-animate-scale--enter-active>*,.x-animate-scale--right.x-animate-scale--leave-active,.x-animate-scale--right.x-animate-scale--leave-active>*{transform-origin:right center}.x-animate-scale--top-to-bottom.x-animate-scale--enter-active,.x-animate-scale--top-to-bottom>.x-animate-scale--enter-active{transform-origin:top center}.x-animate-scale--
|
|
3
|
+
var css = ".x-animate-scale--enter-active,.x-animate-scale--enter-active *,.x-animate-scale--leave-active,.x-animate-scale--leave-active *{animation-duration:var(--x-duration-animation,.3s);animation-timing-function:linear}.x-animate-scale--enter-active,.x-animate-scale--leave-active{overflow:hidden}.x-animate-scale--enter-active.x-animate-scale--bottom,.x-animate-scale--enter-active.x-animate-scale--bottom-to-top,.x-animate-scale--enter-active.x-animate-scale--top,.x-animate-scale--enter-active.x-animate-scale--top-to-bottom,.x-animate-scale--leave-active.x-animate-scale--bottom,.x-animate-scale--leave-active.x-animate-scale--bottom-to-top,.x-animate-scale--leave-active.x-animate-scale--top,.x-animate-scale--leave-active.x-animate-scale--top-to-bottom{animation-name:containerAnimationY}.x-animate-scale--enter-active.x-animate-scale--bottom-to-top>*,.x-animate-scale--enter-active.x-animate-scale--bottom>*,.x-animate-scale--enter-active.x-animate-scale--top-to-bottom>*,.x-animate-scale--enter-active.x-animate-scale--top>*,.x-animate-scale--leave-active.x-animate-scale--bottom-to-top>*,.x-animate-scale--leave-active.x-animate-scale--bottom>*,.x-animate-scale--leave-active.x-animate-scale--top-to-bottom>*,.x-animate-scale--leave-active.x-animate-scale--top>*{animation-name:contentAnimationY}.x-animate-scale--enter-active.x-animate-scale--left,.x-animate-scale--enter-active.x-animate-scale--left-to-right,.x-animate-scale--enter-active.x-animate-scale--right,.x-animate-scale--enter-active.x-animate-scale--right-to-left,.x-animate-scale--leave-active.x-animate-scale--left,.x-animate-scale--leave-active.x-animate-scale--left-to-right,.x-animate-scale--leave-active.x-animate-scale--right,.x-animate-scale--leave-active.x-animate-scale--right-to-left{animation-name:containerAnimationX}.x-animate-scale--enter-active.x-animate-scale--left-to-right>*,.x-animate-scale--enter-active.x-animate-scale--left>*,.x-animate-scale--enter-active.x-animate-scale--right-to-left>*,.x-animate-scale--enter-active.x-animate-scale--right>*,.x-animate-scale--leave-active.x-animate-scale--left-to-right>*,.x-animate-scale--leave-active.x-animate-scale--left>*,.x-animate-scale--leave-active.x-animate-scale--right-to-left>*,.x-animate-scale--leave-active.x-animate-scale--right>*{animation-name:contentAnimationX}.x-animate-scale--leave-active,.x-animate-scale--leave-active>*{animation-direction:reverse}.x-animate-scale--top.x-animate-scale--enter-active,.x-animate-scale--top.x-animate-scale--enter-active>*,.x-animate-scale--top.x-animate-scale--leave-active,.x-animate-scale--top.x-animate-scale--leave-active>*{transform-origin:top center}.x-animate-scale--bottom.x-animate-scale--enter-active,.x-animate-scale--bottom.x-animate-scale--enter-active>*,.x-animate-scale--bottom.x-animate-scale--leave-active,.x-animate-scale--bottom.x-animate-scale--leave-active>*{transform-origin:bottom center}.x-animate-scale--left.x-animate-scale--enter-active,.x-animate-scale--left.x-animate-scale--enter-active>*,.x-animate-scale--left.x-animate-scale--leave-active,.x-animate-scale--left.x-animate-scale--leave-active>*{transform-origin:left center}.x-animate-scale--right.x-animate-scale--enter-active,.x-animate-scale--right.x-animate-scale--enter-active>*,.x-animate-scale--right.x-animate-scale--leave-active,.x-animate-scale--right.x-animate-scale--leave-active>*{transform-origin:right center}.x-animate-scale--top-to-bottom.x-animate-scale--enter-active,.x-animate-scale--top-to-bottom>.x-animate-scale--enter-active{transform-origin:top center}.x-animate-scale--top-to-bottom.x-animate-scale--leave-active,.x-animate-scale--top-to-bottom>.x-animate-scale--leave-active{transform-origin:bottom center}.x-animate-scale--bottom-to-top.x-animate-scale--enter-active,.x-animate-scale--bottom-to-top>.x-animate-scale--enter-active{transform-origin:bottom center}.x-animate-scale--bottom-to-top.x-animate-scale--leave-active,.x-animate-scale--bottom-to-top>.x-animate-scale--leave-active{transform-origin:top center}.x-animate-scale--left-to-right.x-animate-scale--enter-active,.x-animate-scale--left-to-right>.x-animate-scale--enter-active{transform-origin:left center}.x-animate-scale--left-to-right.x-animate-scale--leave-active,.x-animate-scale--left-to-right>.x-animate-scale--leave-active{transform-origin:right center}.x-animate-scale--right-to-left.x-animate-scale--enter-active,.x-animate-scale--right-to-left>.x-animate-scale--enter-active{transform-origin:right center}.x-animate-scale--right-to-left.x-animate-scale--leave-active,.x-animate-scale--right-to-left>.x-animate-scale--leave-active{transform-origin:left center}@keyframes containerAnimationY{0%{transform:scaleY(0)}5.5555555556%{transform:scaleY(.0000762079)}11.1111111111%{transform:scaleY(.0012193263)}16.6666666667%{transform:scaleY(.0061728395)}22.2222222222%{transform:scaleY(.0195092212)}27.7777777778%{transform:scaleY(.0476299345)}33.3333333333%{transform:scaleY(.0987654321)}38.8888888889%{transform:scaleY(.1829751562)}44.4444444444%{transform:scaleY(.3121475385)}50%{transform:scaleY(.5)}55.5555555556%{transform:scaleY(.6878524615)}61.1111111111%{transform:scaleY(.8170248438)}66.6666666667%{transform:scaleY(.9012345679)}72.2222222222%{transform:scaleY(.9523700655)}77.7777777778%{transform:scaleY(.9804907788)}83.3333333333%{transform:scaleY(.9938271605)}88.8888888889%{transform:scaleY(.9987806737)}94.4444444444%{transform:scaleY(.9999237921)}to{transform:scaleY(1)}}@keyframes contentAnimationY{0%{transform:scaleY(99999999)}5.5555555556%{transform:scaleY(13122)}11.1111111111%{transform:scaleY(820.125)}16.6666666667%{transform:scaleY(162)}22.2222222222%{transform:scaleY(51.2578125)}27.7777777778%{transform:scaleY(20.9952)}33.3333333333%{transform:scaleY(10.125)}38.8888888889%{transform:scaleY(5.4652228238)}44.4444444444%{transform:scaleY(3.2036132813)}50%{transform:scaleY(2)}55.5555555556%{transform:scaleY(1.4538001329)}61.1111111111%{transform:scaleY(1.2239529895)}66.6666666667%{transform:scaleY(1.1095890411)}72.2222222222%{transform:scaleY(1.0500120029)}77.7777777778%{transform:scaleY(1.019897404)}83.3333333333%{transform:scaleY(1.0062111801)}88.8888888889%{transform:scaleY(1.0012208149)}94.4444444444%{transform:scaleY(1.0000762137)}to{transform:scaleY(1)}}@keyframes containerAnimationX{0%{transform:scaleX(0)}5.5555555556%{transform:scaleX(.0000762079)}11.1111111111%{transform:scaleX(.0012193263)}16.6666666667%{transform:scaleX(.0061728395)}22.2222222222%{transform:scaleX(.0195092212)}27.7777777778%{transform:scaleX(.0476299345)}33.3333333333%{transform:scaleX(.0987654321)}38.8888888889%{transform:scaleX(.1829751562)}44.4444444444%{transform:scaleX(.3121475385)}50%{transform:scaleX(.5)}55.5555555556%{transform:scaleX(.6878524615)}61.1111111111%{transform:scaleX(.8170248438)}66.6666666667%{transform:scaleX(.9012345679)}72.2222222222%{transform:scaleX(.9523700655)}77.7777777778%{transform:scaleX(.9804907788)}83.3333333333%{transform:scaleX(.9938271605)}88.8888888889%{transform:scaleX(.9987806737)}94.4444444444%{transform:scaleX(.9999237921)}to{transform:scaleX(1)}}@keyframes contentAnimationX{0%{transform:scaleX(99999999)}5.5555555556%{transform:scaleX(13122)}11.1111111111%{transform:scaleX(820.125)}16.6666666667%{transform:scaleX(162)}22.2222222222%{transform:scaleX(51.2578125)}27.7777777778%{transform:scaleX(20.9952)}33.3333333333%{transform:scaleX(10.125)}38.8888888889%{transform:scaleX(5.4652228238)}44.4444444444%{transform:scaleX(3.2036132813)}50%{transform:scaleX(2)}55.5555555556%{transform:scaleX(1.4538001329)}61.1111111111%{transform:scaleX(1.2239529895)}66.6666666667%{transform:scaleX(1.1095890411)}72.2222222222%{transform:scaleX(1.0500120029)}77.7777777778%{transform:scaleX(1.019897404)}83.3333333333%{transform:scaleX(1.0062111801)}88.8888888889%{transform:scaleX(1.0012208149)}94.4444444444%{transform:scaleX(1.0000762137)}to{transform:scaleX(1)}}";
|
|
4
4
|
injectCss(css);
|
|
5
5
|
|
|
6
6
|
export { css };
|
|
@@ -21,6 +21,9 @@ function useCollapseAnimation(property) {
|
|
|
21
21
|
* @param element - The DOM element that is going to be animated.
|
|
22
22
|
*/
|
|
23
23
|
function expand(element) {
|
|
24
|
+
if (!(element instanceof HTMLElement)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
24
27
|
element.style[property] = '0';
|
|
25
28
|
const originalValue = element.style.contentVisibility;
|
|
26
29
|
element.style.contentVisibility = 'visible';
|
|
@@ -34,6 +37,9 @@ function useCollapseAnimation(property) {
|
|
|
34
37
|
* @param element - The DOM element that is going to be animated.
|
|
35
38
|
*/
|
|
36
39
|
function cleanUpAnimationStyles(element) {
|
|
40
|
+
if (!(element instanceof HTMLElement)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
37
43
|
element.style.removeProperty(property);
|
|
38
44
|
}
|
|
39
45
|
/**
|
|
@@ -42,6 +48,9 @@ function useCollapseAnimation(property) {
|
|
|
42
48
|
* @param element - The DOM element that is going to be animated.
|
|
43
49
|
*/
|
|
44
50
|
function collapse(element) {
|
|
51
|
+
if (!(element instanceof HTMLElement)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
45
54
|
element.style[property] = `${element[scrollProperty]}px`;
|
|
46
55
|
// This is intended. We want to provoke a layer repaint to apply this style.
|
|
47
56
|
element.getBoundingClientRect();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-collapse-animation.js","sources":["../../../../src/components/animations/use-collapse-animation.ts"],"sourcesContent":["/**\n * Type options for the property that will be animated.\n */\ntype AnimatedProperty = 'height' | 'width'\n\n/**\n * Returns parametrized methods to use in a component to allow the collapsing of the provided\n * property.\n *\n * @param property - The property that will be animated.\n * @returns Composable for the module.\n * @public\n */\nexport function useCollapseAnimation(property: AnimatedProperty) {\n const scrollProperty = property === 'width' ? 'scrollWidth' : 'scrollHeight'\n\n /**\n * Changes the element's animated property from 0 to the element's content size.\n *\n * @remarks `content-visibility` CSS property boosts the rendering performance waiting to be\n * needed until rendering the content. This behaviour collides with this animation method.\n * When the `scrollProperty` is evaluated, the content has not been rendered yet and the value\n * is 0 so nothing is animated. To avoid this behaviour, we change the `content-visibility` to\n * default value, force a layer repaint and then, evaluate the `scrollProperty` value which\n * now has value. Then we restore the `content-visibility` value to its previous state.\n *\n * @param element - The DOM element that is going to be animated.\n */\n function expand(element:
|
|
1
|
+
{"version":3,"file":"use-collapse-animation.js","sources":["../../../../src/components/animations/use-collapse-animation.ts"],"sourcesContent":["/**\n * Type options for the property that will be animated.\n */\ntype AnimatedProperty = 'height' | 'width'\n\n/**\n * Returns parametrized methods to use in a component to allow the collapsing of the provided\n * property.\n *\n * @param property - The property that will be animated.\n * @returns Composable for the module.\n * @public\n */\nexport function useCollapseAnimation(property: AnimatedProperty) {\n const scrollProperty = property === 'width' ? 'scrollWidth' : 'scrollHeight'\n\n /**\n * Changes the element's animated property from 0 to the element's content size.\n *\n * @remarks `content-visibility` CSS property boosts the rendering performance waiting to be\n * needed until rendering the content. This behaviour collides with this animation method.\n * When the `scrollProperty` is evaluated, the content has not been rendered yet and the value\n * is 0 so nothing is animated. To avoid this behaviour, we change the `content-visibility` to\n * default value, force a layer repaint and then, evaluate the `scrollProperty` value which\n * now has value. Then we restore the `content-visibility` value to its previous state.\n *\n * @param element - The DOM element that is going to be animated.\n */\n function expand(element: Element): void {\n if (!(element instanceof HTMLElement)) {\n return\n }\n element.style[property] = '0'\n const originalValue = element.style.contentVisibility\n element.style.contentVisibility = 'visible'\n element.getBoundingClientRect()\n element.style[property] = `${element[scrollProperty]}px`\n element.style.contentVisibility = originalValue\n }\n\n /**\n * Removes the animated property from the element.\n *\n * @param element - The DOM element that is going to be animated.\n */\n function cleanUpAnimationStyles(element: Element): void {\n if (!(element instanceof HTMLElement)) {\n return\n }\n element.style.removeProperty(property)\n }\n\n /**\n * Changes the element's animated property from the element's content size to 0.\n *\n * @param element - The DOM element that is going to be animated.\n */\n function collapse(element: Element): void {\n if (!(element instanceof HTMLElement)) {\n return\n }\n element.style[property] = `${element[scrollProperty]}px`\n // This is intended. We want to provoke a layer repaint to apply this style.\n element.getBoundingClientRect()\n element.style[property] = '0'\n }\n\n return {\n expand,\n cleanUpAnimationStyles,\n collapse,\n }\n}\n"],"names":[],"mappings":"AAKA;;;;;;;AAOG;AACG,SAAU,oBAAoB,CAAC,QAA0B,EAAA;AAC7D,IAAA,MAAM,cAAc,GAAG,QAAQ,KAAK,OAAO,GAAG,aAAa,GAAG,cAAc;AAE5E;;;;;;;;;;;AAWG;IACH,SAAS,MAAM,CAAC,OAAgB,EAAA;AAC9B,QAAA,IAAI,EAAE,OAAO,YAAY,WAAW,CAAC,EAAE;YACrC;QACF;AACA,QAAA,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,GAAG;AAC7B,QAAA,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC,iBAAiB;AACrD,QAAA,OAAO,CAAC,KAAK,CAAC,iBAAiB,GAAG,SAAS;QAC3C,OAAO,CAAC,qBAAqB,EAAE;AAC/B,QAAA,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAA,EAAG,OAAO,CAAC,cAAc,CAAC,CAAA,EAAA,CAAI;AACxD,QAAA,OAAO,CAAC,KAAK,CAAC,iBAAiB,GAAG,aAAa;IACjD;AAEA;;;;AAIG;IACH,SAAS,sBAAsB,CAAC,OAAgB,EAAA;AAC9C,QAAA,IAAI,EAAE,OAAO,YAAY,WAAW,CAAC,EAAE;YACrC;QACF;AACA,QAAA,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC;IACxC;AAEA;;;;AAIG;IACH,SAAS,QAAQ,CAAC,OAAgB,EAAA;AAChC,QAAA,IAAI,EAAE,OAAO,YAAY,WAAW,CAAC,EAAE;YACrC;QACF;AACA,QAAA,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAA,EAAG,OAAO,CAAC,cAAc,CAAC,CAAA,EAAA,CAAI;;QAExD,OAAO,CAAC,qBAAqB,EAAE;AAC/B,QAAA,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,GAAG;IAC/B;IAEA,OAAO;QACL,MAAM;QACN,sBAAsB;QACtB,QAAQ;KACT;AACH;;;;"}
|
|
@@ -25,7 +25,7 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
25
25
|
"data-test": "dropdown-toggle",
|
|
26
26
|
role: "combobox",
|
|
27
27
|
"aria-haspopup": "listbox",
|
|
28
|
-
"aria-expanded": _ctx.isOpen
|
|
28
|
+
"aria-expanded": _ctx.isOpen,
|
|
29
29
|
"aria-controls": _ctx.listId,
|
|
30
30
|
"aria-label": _ctx.ariaLabel,
|
|
31
31
|
"aria-autocomplete": "none",
|
|
@@ -72,13 +72,13 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
72
72
|
null,
|
|
73
73
|
renderList(_ctx.items, (item, index) => {
|
|
74
74
|
return openBlock(), createElementBlock("li", {
|
|
75
|
-
key: item.id
|
|
75
|
+
key: item.id ?? item,
|
|
76
76
|
class: "x-dropdown__list-item"
|
|
77
77
|
}, [
|
|
78
78
|
createElementVNode("button", {
|
|
79
79
|
ref_for: true,
|
|
80
80
|
ref: (el) => _ctx.itemsButtonRefs[index] = el,
|
|
81
|
-
"aria-selected":
|
|
81
|
+
"aria-selected": item === _ctx.modelValue,
|
|
82
82
|
class: normalizeClass([_ctx.itemsCSSClasses[index], "x-dropdown__item"]),
|
|
83
83
|
"data-test": "dropdown-item",
|
|
84
84
|
role: "option",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-dropdown.vue.js","sources":["../../../src/components/base-dropdown.vue"],"sourcesContent":["<template>\n <div\n ref=\"rootRef\"\n :class=\"dropdownCSSClasses\"\n class=\"x-dropdown\"\n @keydown=\"updateSearchBuffer\"\n @keydown.down.prevent=\"highlightNextItem\"\n @keydown.up.prevent=\"highlightPreviousItem\"\n >\n <button\n ref=\"toggleButtonRef\"\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 @click=\"toggle\"\n @keydown.up.down.prevent.stop=\"open\"\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\" :is-open=\"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 :id=\"listId\"\n class=\"x-dropdown__items-list\"\n data-test=\"dropdown-list\"\n role=\"listbox\"\n tabindex=\"-1\"\n @keydown.end=\"highlightLastItem\"\n @keydown.esc=\"closeAndFocusToggleButton\"\n @keydown.home=\"highlightFirstItem\"\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 :aria-selected=\"(item === modelValue).toString()\"\n :class=\"itemsCSSClasses[index]\"\n class=\"x-dropdown__item\"\n data-test=\"dropdown-item\"\n role=\"option\"\n @click=\"emitSelectedItemChanged(item)\"\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 :is-highlighted=\"index === highlightedItemIndex\"\n :is-selected=\"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\">\nimport type { Identifiable } from '@empathyco/x-types'\nimport type { PropType } from 'vue'\nimport { computed, defineComponent, nextTick, onBeforeUnmount, ref, watch } from 'vue'\nimport { AnimationProp } from '../types'\nimport { debounceFunction, getTargetElement, normalizeString } from '../utils'\nimport { NoAnimation } from './animations'\n\ntype DropdownItem = string | number | Identifiable\nlet 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 */\nexport 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\nwith the 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\nThe `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 <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 setup>\nimport { BaseDropdown } from '@empathyco/x-components'\nimport { ref } from 'vue'\nconst items = ['a', 2, { id: '3' }]\nconst value = ref('a')\n</script>\n```\n</docs>\n"],"names":["_openBlock","_createElementBlock","_normalizeClass","_withKeys","_withModifiers","_createElementVNode","_renderSlot","_createTextVNode","_createBlock","_resolveDynamicComponent","_withCtx","_withDirectives","_Fragment","_renderList"],"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 :class=\"dropdownCSSClasses\"\n class=\"x-dropdown\"\n @keydown=\"updateSearchBuffer\"\n @keydown.down.prevent=\"highlightNextItem\"\n @keydown.up.prevent=\"highlightPreviousItem\"\n >\n <button\n ref=\"toggleButtonRef\"\n class=\"x-dropdown__toggle\"\n data-test=\"dropdown-toggle\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n :aria-expanded=\"isOpen\"\n :aria-controls=\"listId\"\n :aria-label=\"ariaLabel\"\n aria-autocomplete=\"none\"\n @click=\"toggle\"\n @keydown.up.down.prevent.stop=\"open\"\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\" :is-open=\"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 :id=\"listId\"\n class=\"x-dropdown__items-list\"\n data-test=\"dropdown-list\"\n role=\"listbox\"\n tabindex=\"-1\"\n @keydown.end=\"highlightLastItem\"\n @keydown.esc=\"closeAndFocusToggleButton\"\n @keydown.home=\"highlightFirstItem\"\n >\n <li\n v-for=\"(item, index) in items\"\n :key=\"(item as Identifiable).id ?? item\"\n class=\"x-dropdown__list-item\"\n >\n <button\n :ref=\"el => (itemsButtonRefs[index] = el as HTMLButtonElement | null)\"\n :aria-selected=\"item === modelValue\"\n :class=\"itemsCSSClasses[index]\"\n class=\"x-dropdown__item\"\n data-test=\"dropdown-item\"\n role=\"option\"\n @click=\"emitSelectedItemChanged(item)\"\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 :is-highlighted=\"index === highlightedItemIndex\"\n :is-selected=\"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\">\nimport type { Identifiable } from '@empathyco/x-types'\nimport type { PropType } from 'vue'\nimport { computed, defineComponent, nextTick, onBeforeUnmount, ref, watch } from 'vue'\nimport { AnimationProp } from '../types'\nimport { debounceFunction, getTargetElement, normalizeString } from '../utils'\nimport { NoAnimation } from './animations'\n\ntype DropdownItem = string | number | Identifiable\nlet 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 */\nexport 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 | null)[]>([])\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 if (button) {\n const safeButtonWordCharacters = button.textContent!.replace(/\\W/g, '')\n const normalizedButtonText = normalizeString(safeButtonWordCharacters)\n if (normalizedButtonText.startsWith(normalizedSearch)) {\n matchingIndices.push(index)\n }\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\nwith the 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\nThe `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 <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 setup>\nimport { BaseDropdown } from '@empathyco/x-components'\nimport { ref } from 'vue'\nconst items = ['a', 2, { id: '3' }]\nconst value = ref('a')\n</script>\n```\n</docs>\n"],"names":["_openBlock","_createElementBlock","_normalizeClass","_withKeys","_withModifiers","_createElementVNode","_renderSlot","_createTextVNode","_createBlock","_resolveDynamicComponent","_withCtx","_withDirectives","_Fragment","_renderList"],"mappings":";;;;;;;;;AACE,EAAA,OAAAA,SAAA,EAAA,EAAAC,kBAAA;AAAA,IA8EM,KAAA;AAAA,IAAA;AAAA,MA7EJ,GAAA,EAAI,SAAA;AAAA,MACH,KAAA,EAAKC,cAAA,CAAA,CAAE,IAAA,CAAA,kBAAA,EACF,YAAY,CAAA,CAAA;AAAA,MACjB,SAAA,EAAO;AAAA,QAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,CAAA,GAAA,IAAA,KAAE,IAAA,CAAA,kBAAA,IAAA,IAAA,CAAA,kBAAA,CAAA,GAAA,IAAA,CAAA,CAAA;AAAA,QAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAAC,QAAA,CAAAC,aAAA,CAAA,CAAA,GAAA,IAAA,KACa,IAAA,CAAA,iBAAA,IAAA,IAAA,CAAA,iBAAA,CAAA,GAAA,IAAA,CAAA,EAAiB,CAAA,SAAA,CAAA,CAAA,EAAA,CAAA,MAAA,CAAA,CAAA,CAAA;AAAA,QAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAAD,QAAA,CAAAC,aAAA,CAAA,CAAA,GAAA,IAAA,KACnB,IAAA,CAAA,qBAAA,IAAA,IAAA,CAAA,qBAAA,CAAA,GAAA,IAAA,CAAA,EAAqB,CAAA,SAAA,CAAA,CAAA,EAAA,CAAA,IAAA,CAAA,CAAA;AAAA;;;MAE1CC,kBAAA,CAuBS,QAAA,EAAA;AAAA,QAtBP,GAAA,EAAI,iBAAA;AAAA,QACJ,KAAA,EAAM,oBAAA;AAAA,QACN,WAAA,EAAU,iBAAA;AAAA,QACV,IAAA,EAAK,UAAA;AAAA,QACL,eAAA,EAAc,SAAA;AAAA,QACb,eAAA,EAAe,IAAA,CAAA,MAAA;AAAA,QACf,eAAA,EAAe,IAAA,CAAA,MAAA;AAAA,QACf,YAAA,EAAY,IAAA,CAAA,SAAA;AAAA,QACb,mBAAA,EAAkB,MAAA;AAAA,QACjB,SAAK,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,CAAA,GAAA,IAAA,KAAE,IAAA,CAAA,MAAA,IAAA,IAAA,CAAA,MAAA,CAAA,GAAA,IAAA,CAAA,CAAA;AAAA,QACP,SAAA,EAAO,8DAAuB,IAAA,CAAA,IAAA,IAAA,IAAA,CAAA,IAAA,CAAA,GAAA,IAAA,CAAA,EAAI,CAAA,SAAA,EAAA,MAAA,CAAA,CAAA,EAAA,CAAA,IAAA,EAAA,MAAA,CAAA,CAAA;AAAA,OAAA,EAAA;AAQvB,QAAA,IAAA,CAAA,aAAA,GAAZC,UAAA,CAEO,IAAA,CAAA,MAAA,EAAA,QAAA,EAAA;AAAA,UAAA,GAAA,EAAA,CAAA;UAFqB,MAAA,EAAS,IAAA,CAAA,MAAA;AAAA,UAAS,IAAA,EAAM,IAAA,CAAA;AAAA,SAAA,EAApD,MAEO;AAAA,UAAAC,eAAA;4BADF,IAAA,CAAA,UAAU,CAAA;AAAA,YAAA;AAAA;AAAA;AAAA,SAAA,EAAA,IAAA,CAAA,GAEfD,UAAA,CAAmE,IAAA,CAAA,MAAA,EAAA,MAAA,EAAA;AAAA,UAAA,GAAA,EAAA,CAAA;UAArD,IAAA,EAAM,IAAA,CAAA;AAAA,SAAA,EAApB,MAAmE;AAAA,UAAAC,eAAA;4BAApB,IAAA,CAAA,UAAU,CAAA;AAAA,YAAA;AAAA;AAAA;AAAA,SAAA,EAAA,IAAA;;AAG3D,OAAAP,SAAA,EAAA,EAAAQ,WAAA,CA4CYC,wBA5CI,IAAA,CAAA,SAAS,CAAA,EAAA,IAAA,EAAA;AAAA,QAAA,OAAA,EAAAC,OAAA,CACvB,MA0CK;AAAA,UAAAC,cAAA,CA1CLN,kBAAA,CA0CK,IAAA,EAAA;AAAA,YAxCF,EAAA,EAAI,IAAA,CAAA,MAAA;AAAA,YACL,KAAA,EAAM,wBAAA;AAAA,YACN,WAAA,EAAU,eAAA;AAAA,YACV,IAAA,EAAK,SAAA;AAAA,YACL,QAAA,EAAS,IAAA;AAAA,YACR,SAAA,EAAO;AAAA,cAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAAF,QAAA,CAAA,CAAA,GAAA,IAAA,KAAM,IAAA,CAAA,iBAAA,IAAA,IAAA,CAAA,iBAAA,CAAA,GAAA,IAAA,CAAA,EAAiB,CAAA,KAAA,CAAA,CAAA,CAAA;AAAA,cAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAAA,QAAA,CAAA,CAAA,GAAA,IAAA,KACjB,IAAA,CAAA,yBAAA,IAAA,IAAA,CAAA,yBAAA,CAAA,GAAA,IAAA,CAAA,EAAyB,CAAA,KAAA,CAAA,CAAA,CAAA;AAAA,cAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAAA,QAAA,CAAA,CAAA,GAAA,IAAA,KACxB,IAAA,CAAA,kBAAA,IAAA,IAAA,CAAA,kBAAA,CAAA,GAAA,IAAA,CAAA,EAAkB,CAAA,MAAA,CAAA,CAAA;AAAA;;AAEjC,aAAAH,SAAA,CAAA,IAAA,CAAA,EAAAC,kBAAA;AAAA,cA8BKW,QAAA;AAAA,cAAA,IAAA;AAAA,cAAAC,UAAA,CA7BqB,IAAA,CAAA,KAAA,EAAK,CAArB,IAAA,EAAM,KAAA,KAAK;oCADrBZ,kBAAA,CA8BK,IAAA,EAAA;AAAA,kBA5BF,GAAA,EAAM,KAAsB,EAAA,IAAM,IAAA;AAAA,kBACnC,KAAA,EAAM;AAAA,iBAAA,EAAA;kBAENI,kBAAA,CAwBS,QAAA,EAAA;AAAA,oBAAA,OAAA,EAAA,IAAA;oBAvBN,GAAA,EAAK,CAAA,EAAA,KAAO,IAAA,CAAA,eAAA,CAAgB,KAAK,CAAA,GAAI,EAAA;AAAA,oBACrC,iBAAe,IAAA,KAAS,IAAA,CAAA,UAAA;AAAA,oBACxB,KAAA,EAAKH,cAAA,CAAA,CAAE,IAAA,CAAA,eAAA,CAAgB,KAAK,GACvB,kBAAkB,CAAA,CAAA;AAAA,oBACxB,WAAA,EAAU,eAAA;AAAA,oBACV,IAAA,EAAK,QAAA;AAAA,oBACJ,OAAA,EAAK,CAAA,MAAA,KAAE,IAAA,CAAA,uBAAA,CAAwB,IAAI;AAAA,mBAAA,EAAA;oBASpCI,UAAA,CAOO,IAAA,CAAA,MAAA,EAAA,MAAA,EAAA;AAAA,sBANJ,eAAgB,KAAA,KAAU,IAAA,CAAA,oBAAA;AAAA,sBAC1B,YAAa,IAAA,KAAS,IAAA,CAAA,UAAA;AAAA,sBACtB;AAAA,qBAAA,EAHH,MAOO;AAAA,sBAAAC,eAAA;wCADF,IAAI,CAAA;AAAA,wBAAA;AAAA;AAAA;AAAA,qBAAA,EAAA,IAAA;;;;;;;;oBArCL,IAAA,CAAA,MAAM;AAAA,WAAA;;;;;;;;;;;;;;"}
|
|
@@ -199,10 +199,12 @@ var _sfc_main = defineComponent({
|
|
|
199
199
|
if (search) {
|
|
200
200
|
const normalizedSearch = normalizeString(search);
|
|
201
201
|
const matchingIndices = itemsButtonRefs?.value?.reduce((matchingIndices, button, index) => {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
202
|
+
if (button) {
|
|
203
|
+
const safeButtonWordCharacters = button.textContent.replace(/\W/g, '');
|
|
204
|
+
const normalizedButtonText = normalizeString(safeButtonWordCharacters);
|
|
205
|
+
if (normalizedButtonText.startsWith(normalizedSearch)) {
|
|
206
|
+
matchingIndices.push(index);
|
|
207
|
+
}
|
|
206
208
|
}
|
|
207
209
|
return matchingIndices;
|
|
208
210
|
}, []);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-dropdown.vue2.js","sources":["../../../src/components/base-dropdown.vue"],"sourcesContent":["<template>\n <div\n ref=\"rootRef\"\n :class=\"dropdownCSSClasses\"\n class=\"x-dropdown\"\n @keydown=\"updateSearchBuffer\"\n @keydown.down.prevent=\"highlightNextItem\"\n @keydown.up.prevent=\"highlightPreviousItem\"\n >\n <button\n ref=\"toggleButtonRef\"\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 @click=\"toggle\"\n @keydown.up.down.prevent.stop=\"open\"\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\" :is-open=\"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 :id=\"listId\"\n class=\"x-dropdown__items-list\"\n data-test=\"dropdown-list\"\n role=\"listbox\"\n tabindex=\"-1\"\n @keydown.end=\"highlightLastItem\"\n @keydown.esc=\"closeAndFocusToggleButton\"\n @keydown.home=\"highlightFirstItem\"\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 :aria-selected=\"(item === modelValue).toString()\"\n :class=\"itemsCSSClasses[index]\"\n class=\"x-dropdown__item\"\n data-test=\"dropdown-item\"\n role=\"option\"\n @click=\"emitSelectedItemChanged(item)\"\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 :is-highlighted=\"index === highlightedItemIndex\"\n :is-selected=\"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\">\nimport type { Identifiable } from '@empathyco/x-types'\nimport type { PropType } from 'vue'\nimport { computed, defineComponent, nextTick, onBeforeUnmount, ref, watch } from 'vue'\nimport { AnimationProp } from '../types'\nimport { debounceFunction, getTargetElement, normalizeString } from '../utils'\nimport { NoAnimation } from './animations'\n\ntype DropdownItem = string | number | Identifiable\nlet 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 */\nexport 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\nwith the 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\nThe `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 <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 setup>\nimport { BaseDropdown } from '@empathyco/x-components'\nimport { ref } from 'vue'\nconst items = ['a', 2, { id: '3' }]\nconst value = ref('a')\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 :class=\"dropdownCSSClasses\"\n class=\"x-dropdown\"\n @keydown=\"updateSearchBuffer\"\n @keydown.down.prevent=\"highlightNextItem\"\n @keydown.up.prevent=\"highlightPreviousItem\"\n >\n <button\n ref=\"toggleButtonRef\"\n class=\"x-dropdown__toggle\"\n data-test=\"dropdown-toggle\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n :aria-expanded=\"isOpen\"\n :aria-controls=\"listId\"\n :aria-label=\"ariaLabel\"\n aria-autocomplete=\"none\"\n @click=\"toggle\"\n @keydown.up.down.prevent.stop=\"open\"\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\" :is-open=\"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 :id=\"listId\"\n class=\"x-dropdown__items-list\"\n data-test=\"dropdown-list\"\n role=\"listbox\"\n tabindex=\"-1\"\n @keydown.end=\"highlightLastItem\"\n @keydown.esc=\"closeAndFocusToggleButton\"\n @keydown.home=\"highlightFirstItem\"\n >\n <li\n v-for=\"(item, index) in items\"\n :key=\"(item as Identifiable).id ?? item\"\n class=\"x-dropdown__list-item\"\n >\n <button\n :ref=\"el => (itemsButtonRefs[index] = el as HTMLButtonElement | null)\"\n :aria-selected=\"item === modelValue\"\n :class=\"itemsCSSClasses[index]\"\n class=\"x-dropdown__item\"\n data-test=\"dropdown-item\"\n role=\"option\"\n @click=\"emitSelectedItemChanged(item)\"\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 :is-highlighted=\"index === highlightedItemIndex\"\n :is-selected=\"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\">\nimport type { Identifiable } from '@empathyco/x-types'\nimport type { PropType } from 'vue'\nimport { computed, defineComponent, nextTick, onBeforeUnmount, ref, watch } from 'vue'\nimport { AnimationProp } from '../types'\nimport { debounceFunction, getTargetElement, normalizeString } from '../utils'\nimport { NoAnimation } from './animations'\n\ntype DropdownItem = string | number | Identifiable\nlet 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 */\nexport 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 | null)[]>([])\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 if (button) {\n const safeButtonWordCharacters = button.textContent!.replace(/\\W/g, '')\n const normalizedButtonText = normalizeString(safeButtonWordCharacters)\n if (normalizedButtonText.startsWith(normalizedSearch)) {\n matchingIndices.push(index)\n }\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\nwith the 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\nThe `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 <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 setup>\nimport { BaseDropdown } from '@empathyco/x-components'\nimport { ref } from 'vue'\nconst items = ['a', 2, { id: '3' }]\nconst value = ref('a')\n</script>\n```\n</docs>\n"],"names":["NoAnimation","debounceFunction"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA2FA,IAAI,gBAAgB,CAAA;AAEpB;;;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,IAAI;AACf,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,CAAA,KAAM,YAAY,CAAA,KAAM,IAAI;AACvF,YAAA,QAAQ,EAAE,IAAI;AACf,SAAA;;AAED,QAAA,SAAS,EAAE,MAAM;AACjB;;;AAGE;AACF,QAAA,SAAS,EAAE;AACT,YAAA,IAAI,EAAE,aAAa;AACnB,YAAA,OAAO,EAAE,MAAMA,WAAW;AAC3B,SAAA;;AAED,QAAA,eAAe,EAAE;AACf,YAAA,IAAI,EAAE,MAAM;AACZ,YAAA,OAAO,EAAE,IAAI;AACd,SAAA;AACF,KAAA;IACD,KAAK,EAAE,CAAC,mBAAmB,CAAC;AAC5B,IAAA,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAI,EAAG,EAAA;AAC1B,QAAA,MAAM,OAAM,GAAI,GAAG,EAAc;;AAEjC,QAAA,MAAM,kBAAkB,GAAG,EAAoB;;AAE/C,QAAA,MAAM,eAAc,GAAI,GAAG,CAA+B,EAAE,CAAA;;AAG5D,QAAA,MAAM,SAAS,GAAG,CAAC,KAAK,CAAA;;AAExB,QAAA,MAAM,oBAAmB,GAAI,GAAG,CAAC,EAAE,CAAA;;AAEnC,QAAA,MAAM,YAAW,GAAI,GAAG,CAAC,EAAE,CAAA;;AAG3B,QAAA,IAAI,yBAAoC;;AAExC,QAAA,MAAM,MAAK,GAAI,CAAA,WAAA,EAAc,aAAa,EAAE,EAAC;AAE7C;;;;AAIE;AACF,QAAA,MAAM,qBAAqB,QAAQ,CAAC,OAAO,EAAE,qBAAqB,EAAE,MAAK,EAAG,CAAC,CAAA;AAE7E;;;;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,KAAI,KAAM,KAAK;SACzE,CAAC,CAAC,CACL;;AAGA,QAAA,MAAM,IAAG,GAAI,OAAO,MAAM,CAAC,KAAI,GAAI,IAAI,CAAA;;AAEvC,QAAA,MAAM,KAAI,GAAI,OAAO,MAAM,CAAC,KAAI,GAAI,KAAK,CAAA;;AAEzC,QAAA,MAAM,MAAK,GAAI,OAAO,MAAM,CAAC,KAAI,GAAI,CAAC,MAAM,CAAC,KAAK,CAAA;AAElD;;AAEE;AACF,QAAA,SAAS,yBAAyB,GAAA;AAChC,YAAA,KAAK,EAAC;AACN,YAAA,eAAe,CAAC,KAAK,EAAE,KAAK,EAAC;QAC/B;AAEA;;;;AAIE;QACF,SAAS,uBAAuB,CAAC,IAAkB,EAAA;AACjD,YAAA,IAAI,CAAC,mBAAmB,EAAE,IAAI,CAAA;AAC9B,YAAA,yBAAyB,EAAC;QAC5B;AAEA;;AAEE;AACF,QAAA,SAAS,iBAAiB,GAAA;AACxB,YAAA,IAAI,EAAC;AACL,YAAA,oBAAoB,CAAC,KAAI,GAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAK;QACnF;AAEA;;AAEE;AACF,QAAA,SAAS,qBAAqB,GAAA;AAC5B,YAAA,MAAM,YAAW,GAAI,oBAAoB,CAAC,KAAI;AAC9C,YAAA,IAAI,EAAC;YACL,oBAAoB,CAAC,QAAQ,YAAW,GAAI,CAAA,GAAI,YAAW,GAAI,CAAA,GAAI,KAAK,CAAC,KAAK,CAAC,SAAS,CAAA;QAC1F;AAEA;;AAEE;AACF,QAAA,SAAS,kBAAkB,GAAA;AACzB,YAAA,oBAAoB,CAAC,KAAI,GAAI,CAAA;QAC/B;AAEA;;AAEE;AACF,QAAA,SAAS,iBAAiB,GAAA;YACxB,oBAAoB,CAAC,KAAI,GAAI,KAAK,CAAC,KAAK,CAAC,SAAS,CAAA;QACpD;AAEA;;;;AAIE;QACF,SAAS,kBAAkB,CAAC,KAAoB,EAAA;YAC9C,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;AAC1B,gBAAA,MAAM,GAAE,GAAI,KAAK,CAAC,GAAE;AACpB,gBAAA,YAAY,CAAC,SAAS,GAAE;AACxB,gBAAA,yBAAyB,EAAC;YAC5B;QACF;AAEA;;AAEE;AACF,QAAA,SAAS,iBAAiB,GAAA;AACxB,YAAA,YAAY,CAAC,KAAI,GAAI,EAAC;QACxB;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,EAAC;YACR;QACF;AAEA;;;AAGE;AACF,QAAA,SAAS,yBAAyB,GAAA;AAChC,YAAA,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,2BAA2B,CAAA;AAClE,YAAA,QAAQ,CAAC,gBAAgB,CAAC,YAAY,EAAE,2BAA2B,CAAA;AACnE,YAAA,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,2BAA2B,CAAA;QAClE;AAEA;;;AAGE;AACF,QAAA,SAAS,4BAA4B,GAAA;AACnC,YAAA,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,2BAA2B,CAAA;AACrE,YAAA,QAAQ,CAAC,mBAAmB,CAAC,YAAY,EAAE,2BAA2B,CAAA;AACtE,YAAA,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,2BAA2B,CAAA;QACrE;AAEA;;;;;;;;AAQE;AACF,QAAA,KAAK,CAAC,YAAY,EAAE,MAAK,IAAG;YAC1B,IAAI,MAAM,EAAE;AACV,gBAAA,MAAM,gBAAe,GAAI,eAAe,CAAC,MAAM,CAAA;AAC/C,gBAAA,MAAM,eAAc,GAAI,eAAe,EAAE,KAAK,EAAE,MAAM,CACpD,CAAC,eAAe,EAAE,MAAM,EAAE,KAAK,KAAG;oBAChC,IAAI,MAAM,EAAE;AACV,wBAAA,MAAM,wBAAuB,GAAI,MAAM,CAAC,WAAY,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAA;AACtE,wBAAA,MAAM,oBAAmB,GAAI,eAAe,CAAC,wBAAwB,CAAA;AACrE,wBAAA,IAAI,oBAAoB,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE;AACrD,4BAAA,eAAe,CAAC,IAAI,CAAC,KAAK,CAAA;wBAC5B;oBACF;AACA,oBAAA,OAAO,eAAc;gBACvB,CAAC,EACD,EAAE,CACJ;AACA,gBAAA,oBAAoB,CAAC,KAAI;;AAEvB,oBAAA,eAAe,EAAE,IAAI,CAAC,KAAI,IAAK,KAAI,IAAK,oBAAoB,CAAC,KAAK;;wBAElE,eAAe,GAAG,CAAC;;AAEnB,wBAAA,CAAA;YACJ;AACF,QAAA,CAAC,CAAA;AAED;;;;;AAKE;QACF,KAAK,CACH,MAAM,KAAK,CAAC,eAAe,EAC3B,eAAc,IAAG;AACf,YAAA,yBAAwB,GAAIC,QAAgB,CAAC,iBAAiB,EAAE,eAAe,CAAA;AACjF,QAAA,CAAC,EACD,EAAE,SAAS,EAAE,MAAM,CACrB;AAEA;;;;AAIE;AACF,QAAA,KAAK,CACH,oBAAoB,EACpB,oBAAmB,IAAG;AACpB,YAAA,QAAQ,CAAC,MAAM,eAAe,CAAC,KAAK,CAAC,oBAAoB,CAAC,EAAE,KAAK,EAAE,CAAA;AACrE,QAAA,CAAC,EACD,EAAE,SAAS,EAAE,MAAM,CACrB;AAEA;;;;AAIE;AACF,QAAA,KAAK,CAAC,MAAM,EAAE,MAAK,IAAG;YACpB,oBAAoB,CAAC,KAAI,GAAI;AAC3B,kBAAE,KAAK,CAAC,UAAS,KAAM;AACrB,sBAAE;sBACA,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU;kBACtC,EAAC;AACP,QAAA,CAAC,CAAA;AAED;;;;AAIE;AACF,QAAA,KAAK,CAAC,MAAM,EAAE,MAAK,IAAG;AACpB;;;;;AAKE;YACF,IAAI,MAAM,EAAE;AACV,gBAAA,yBAAyB,EAAC;YAC5B;iBAAO;AACL,gBAAA,4BAA4B,EAAC;YAC/B;AACF,QAAA,CAAC,CAAA;AAED;;;AAGE;QACF,eAAe,CAAC,MAAI;AAClB,YAAA,4BAA4B,EAAC;AAC/B,QAAA,CAAC,CAAA;QAED,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,kBAAkB;SACpB;IACF,CAAC;AACF,CAAA,CAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-grid.vue.js","sources":["../../../src/components/base-grid.vue"],"sourcesContent":["<template>\n <component\n :is=\"animation\"\n ref=\"gridEl\"\n :style=\"style\"\n class=\"x-base-grid\"\n :class=\"cssClasses\"\n tag=\"ul\"\n data-test=\"grid\"\n >\n <li\n v-for=\"{ item, cssClass, slotName } in gridItems\"\n :key=\"item.id\"\n :class=\"cssClass\"\n class=\"x-base-grid__item\"\n >\n <!--\n @slot Customized item rendering. Specifying a slot with the item's modelName will result in\n the item using that slot composition to render.\n @binding {item} item - Item to render\n -->\n <slot v-if=\"slots[slotName]\" :name=\"slotName\" :item=\"item\" />\n <!--\n @slot (required) Default item rendering. This slot will be used by default for rendering\n the item without an specific slot implementation.\n @binding {item} item - Item to render\n -->\n <slot v-else :item=\"item\">{{ item.name || item.modelName || item.id || item }}</slot>\n </li>\n </component>\n</template>\n\n<script lang=\"ts\">\nimport type { MaybeComputedElementRef, UseResizeObserverReturn } from '@vueuse/core'\nimport type { PropType, Ref } from 'vue'\nimport type { ListItem, VueCSSClasses } from '../utils/types'\nimport { useResizeObserver } from '@vueuse/core'\nimport { computed, defineComponent, inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { useXBus } from '../composables/use-x-bus'\nimport { AnimationProp } from '../types/animation-prop'\nimport { toKebabCase } from '../utils/string'\nimport { LIST_ITEMS_KEY } from './decorators/injection.consts'\n\n/**\n * The type returned by the gridItems function. Basically it's a list of items with its CSS\n * classes and a slotName.\n */\ninterface GridItem {\n slotName: string\n item: ListItem\n cssClass: VueCSSClasses\n}\n\n/**\n * Grid component that is able to render different items based on their modelName value. In order\n * to achieve this, it exposes a scopedSlot for each different modelName. In case the items used\n * do not have modelName property, the default slot is used instead. It has a required property:\n * the `items` to render; and an optional one: the number `columns` the grid is divided in. If the\n * number of columns is not specified, the grid automatically fills the rows with as many columns\n * as it can fit.\n *\n * @public\n */\nexport default defineComponent({\n name: 'BaseGrid',\n props: {\n /** Animation component that will be used to animate the base grid. */\n animation: {\n type: AnimationProp,\n default: 'ul',\n },\n /**\n * Number of columns the grid is divided into. By default, its value is 0, setting the grid\n * columns mode to auto-fill.\n */\n columns: {\n type: Number,\n default: 0,\n },\n /**\n * The list of items to be rendered.\n *\n * @remarks The items must have an ID property.\n */\n items: {\n type: Array as PropType<ListItem[]>,\n },\n },\n setup(props, { slots }) {\n // eslint-disable-next-line ts/consistent-type-definitions\n type ElementRef = {\n $el: HTMLElement\n }\n\n const xBus = useXBus()\n\n /** It injects {@link ListItem} provided by an ancestor. */\n const injectedListItems = inject<Ref<ListItem[]>>(LIST_ITEMS_KEY as string)\n const gridEl = ref<ElementRef | HTMLElement>()\n const renderedColumnsNumber = ref(0)\n\n /**\n * Emits the {@link XEventsTypes.RenderedColumnsNumberChanged}\n * event whenever the number of columns rendered inside the grid changes.\n */\n watch(\n renderedColumnsNumber,\n () => xBus.emit('RenderedColumnsNumberChanged', renderedColumnsNumber.value),\n { immediate: false },\n )\n\n /**\n * It returns the items passed as props or the injected ones.\n *\n * @returns List of grid items.\n */\n const computedItems = computed<ListItem[] | void>(() => {\n return (\n props.items ??\n injectedListItems?.value ??\n console.warn('It is necessary to pass a prop or inject the list of filters')\n )\n })\n\n /**\n * CSS class based on the column property value so items inside the grid can fill different\n * amount of columns or rows based on how many columns the grid is divided into.\n *\n * @returns CSS class with the column property value.\n */\n const cssClasses = computed(() => `x-base-grid--cols-${props.columns || 'auto'}`)\n\n /**\n * CSSStyleDeclaration object specifying the number of columns the grid is divided into based on\n * the column property value.\n *\n * @returns A CSSStyleDeclaration to use as the style attribute.\n */\n const style = computed<Partial<CSSStyleDeclaration>>(() => ({\n gridTemplateColumns: props.columns\n ? `repeat(${props.columns}, minmax(0, 1fr))`\n : 'repeat(auto-fill, minmax(var(--x-size-min-width-grid-item, 150px), 1fr))',\n }))\n\n /**\n * Maps the item to an object containing: the `item`, its `CSS class` and its slot name.\n *\n * @returns An array of objects containing the item and its CSS class.\n */\n const gridItems = computed<GridItem[]>(() =>\n (computedItems.value as ListItem[]).map(item => {\n const slotName = toKebabCase(item.modelName)\n return {\n slotName,\n item,\n cssClass: `x-base-grid__${slotName}`,\n }\n }),\n )\n\n /**\n * Checks if a given value is an `ElementRef` object.\n *\n * @param value - The value to check.\n * @returns `true` if the value is an `ElementRef` object, `false` otherwise.\n */\n const isElementRef = (value: any): value is ElementRef => {\n return value && value.$el instanceof HTMLElement\n }\n\n /** Updates the number of columns rendered inside the grid. */\n function updateRenderedColumnsNumber() {\n const { gridTemplateColumns } = getComputedStyle(\n isElementRef(gridEl.value) ? gridEl.value.$el : (gridEl.value as Element),\n )\n renderedColumnsNumber.value = gridTemplateColumns.split(' ').length\n }\n\n /** Initialises the rendered columns number and sets a ResizeObserver to keep it updated. */\n let resizeObserver: UseResizeObserverReturn\n onMounted(() => {\n resizeObserver = useResizeObserver(\n gridEl as MaybeComputedElementRef,\n updateRenderedColumnsNumber,\n )\n })\n onBeforeUnmount(() => resizeObserver?.stop())\n\n return {\n gridItems,\n cssClasses,\n style,\n gridEl,\n slots,\n }\n },\n})\n</script>\n\n<style lang=\"css\" scoped>\n.x-base-grid {\n display: grid;\n grid-auto-flow: dense;\n list-style: none;\n align-items: stretch;\n}\n\n.x-base-grid__banner,\n.x-base-grid__next-queries-group,\n.x-base-grid__related-prompts-group {\n grid-column-start: 1;\n grid-column-end: -1;\n}\n\n.x-base-grid__related-prompts-group.x-staggered-fade-and-slide-leave-active,\n.x-base-grid__related-prompts-group.x-staggered-fade-and-slide-enter-active {\n transition: none;\n position: relative;\n}\n\n.x-base-grid__item {\n display: flex;\n flex-flow: column nowrap;\n}\n\n.x-base-grid__item > * {\n flex-grow: 1;\n}\n\n.x-base-grid--cols-auto .x-base-grid__item {\n min-width: var(--x-size-min-width-grid-item);\n}\n</style>\n\n<docs lang=\"mdx\">\n## Examples\n\nThis component renders a list of elements in different slots depending on their modelName. In order\nto achieve this, it exposes a scopedSlot for each different modelName. In case the items used do not\nhave modelName property, the default slot is used instead. It has a required property, the `items`\nto render, and an optional one, the number of `columns` the grid is divided in. If the number of\ncolumns is not specified, the grid automatically fills the rows with as many columns as it can fit.\n\n### Basic example\n\nIt renders a list of items using the default slot:\n\n```vue\n<template>\n <BaseGrid :items=\"items\">\n <template #default=\"{ item }\">\n {{ `Default slot content: ${item.id}` }}\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nconst items = [\n { id: 1, name: 'Item 1' },\n { id: 2, name: 'Item 2' },\n { id: 3, name: 'Item 3' },\n]\n</script>\n```\n\n### Configuring the number of columns\n\nIt renders a grid with 12 columns instead of 6, which is the default value:\n\n```vue\n<template>\n <BaseGrid :items=\"items\" :columns=\"12\">\n <template #default=\"{ item }\">\n {{ `Default slot content: ${item.id}` }}\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nconst items = [\n { id: 1, name: 'Item 1' },\n { id: 2, name: 'Item 2' },\n { id: 3, name: 'Item 3' },\n]\n</script>\n```\n\n### Rendering usage\n\nConfiguring the number of columns.\n\nIt renders a list of items using the different scopedSlots created by the item's modelName. For\nexample, if you want to use this component as the search grid, you pass the search results (results,\nbanners, promoted, next queries...etc) as items. Each of these results have a different modelName\nand are rendered in different slots.\n\n```vue\n<template>\n <BaseGrid :animation=\"animation\" :items=\"items\">\n <template #banner=\"{ item }\">\n <span class=\"banner\">\n {{ `${item.title} banner` }}\n </span>\n </template>\n <template #next-queries=\"{ item }\">\n <span>\n {{ `${item.totalResults} next queries` }}\n </span>\n </template>\n <template #promoted=\"{ item }\">\n <span class=\"promoted\">\n {{ `${item.title} promoted` }}\n </span>\n </template>\n <template #result=\"{ item }\">\n <BaseResultLink :result=\"item\">\n {{ item.name }}\n </BaseResultLink>\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nimport BaseResultLink from './BaseResultLink.vue'\nconst animation = 'ul'\nconst items = [\n { id: 1, modelName: 'banner', title: 'Banner 1' },\n { id: 2, modelName: 'next-queries', totalResults: 5 },\n { id: 3, modelName: 'promoted', title: 'Promo 1' },\n { id: 4, modelName: 'result', name: 'Result 1' },\n]\n</script>\n```\n\n### Customizing the items width\n\nThe `--x-size-min-width-grid-item` variable can be used to customize the min width of the grid\nitems.\n\n```vue\n<template>\n <BaseGrid :items=\"items\" style=\"--x-size-min-width-grid-item: 150px\">\n <template #default=\"{ item }\">\n {{ `Default slot content: ${item.id}` }}\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nconst items = [\n { id: 1, name: 'Item 1' },\n { id: 2, name: 'Item 2' },\n { id: 3, name: 'Item 3' },\n]\n</script>\n```\n</docs>\n"],"names":["_openBlock","_createBlock","_resolveDynamicComponent","_normalizeStyle","_normalizeClass","_createElementBlock","_Fragment","_renderList","_renderSlot","_createTextVNode","_toDisplayString"],"mappings":";;;;;;AACE,EAAA,OAAAA,SAAA,EAAA,EAAAC,WAAA,CA4BYC,wBA3BL,IAAA,CAAA,SAAS,CAAA,EAAA;AAAA,IACd,GAAA,EAAI,QAAA;AAAA,IACH,KAAA,EAAKC,eAAE,IAAA,CAAA,KAAK,CAAA;AAAA,IACb,KAAA,EAAKC,cAAA,CAAA,CAAC,aAAA,EACE,IAAA,CAAA,UAAU,CAAA,CAAA;AAAA,IAClB,GAAA,EAAI,IAAA;AAAA,IACJ,WAAA,EAAU;AAAA,GAAA,EAAA;qBAGR,MAAiD;AAAA,OAAAJ,SAAA,CAAA,IAAA,CAAA,EADnDK,kBAAA;AAAA,QAkBKC,QAAA;AAAA,QAAA,IAAA;AAAA,QAAAC,UAAA,CAjBoC,IAAA,CAAA,SAAA,EAAS,CAAA,EAAvC,IAAA,EAAM,QAAA,EAAU,QAAA,EAAQ,KAAA;AADnC,UAAA,OAAAP,SAAA,EAAA,EAAAK,kBAAA;AAAA,YAkBK,IAAA;AAAA,YAAA;AAAA,cAhBF,KAAK,IAAA,CAAK,EAAA;AAAA,cACV,KAAA,EAAKD,cAAA,CAAA,CAAE,QAAA,EACF,mBAAmB,CAAA;AAAA,aAAA;;AAOb,cAAA,IAAA,CAAA,KAAA,CAAM,QAAQ,CAAA,GAA1BI,UAAA,CAA6D,IAAA,CAAA,MAAA,EAAzB,QAAA,EAAQ;AAAA,gBAAA,GAAA,EAAA,CAAA;AAAG,gBAAA;AAAA,eAAA,EAAA,MAAA,EAAA,IAAA,CAAA,GAM/CA,UAAA,CAAqF,IAAA,CAAA,MAAA,EAAA,SAAA,EAAA;AAAA,gBAAA,GAAA,EAAA,CAAA;AAAvE,gBAAA;AAAA,eAAA,EAAd,MAAqF;AAAA,gBAAAC,eAAA;AAAxD,kBAAAC,eAAA,CAAA,IAAA,CAAK,IAAA,IAAQ,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,kBAAA;AAAA;AAAA;AAAA,eAAA,EAAA,IAAA;;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"base-grid.vue.js","sources":["../../../src/components/base-grid.vue"],"sourcesContent":["<template>\n <component\n :is=\"animation\"\n ref=\"gridEl\"\n :style=\"style\"\n class=\"x-base-grid\"\n :class=\"cssClasses\"\n tag=\"ul\"\n data-test=\"grid\"\n >\n <li\n v-for=\"{ item, cssClass, slotName } in gridItems\"\n :key=\"item.id\"\n :class=\"cssClass\"\n class=\"x-base-grid__item\"\n >\n <!--\n @slot Customized item rendering. Specifying a slot with the item's modelName will result in\n the item using that slot composition to render.\n @binding {item} item - Item to render\n -->\n <slot v-if=\"slots[slotName]\" :name=\"slotName\" :item=\"item\" />\n <!--\n @slot (required) Default item rendering. This slot will be used by default for rendering\n the item without an specific slot implementation.\n @binding {item} item - Item to render\n -->\n <slot v-else :item=\"item\">{{ (item as any).name || item.modelName || item.id || item }}</slot>\n </li>\n </component>\n</template>\n\n<script lang=\"ts\">\nimport type { MaybeComputedElementRef, UseResizeObserverReturn } from '@vueuse/core'\nimport type { PropType, Ref } from 'vue'\nimport type { ListItem } from '../utils/types'\nimport type { GridItem } from './base-grid.types'\nimport { useResizeObserver } from '@vueuse/core'\nimport { computed, defineComponent, inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { useXBus } from '../composables/use-x-bus'\nimport { AnimationProp } from '../types/animation-prop'\nimport { toKebabCase } from '../utils/string'\nimport { LIST_ITEMS_KEY } from './decorators/injection.consts'\n\n/**\n * Grid component that is able to render different items based on their modelName value. In order\n * to achieve this, it exposes a scopedSlot for each different modelName. In case the items used\n * do not have modelName property, the default slot is used instead. It has a required property:\n * the `items` to render; and an optional one: the number `columns` the grid is divided in. If the\n * number of columns is not specified, the grid automatically fills the rows with as many columns\n * as it can fit.\n *\n * @public\n */\nexport default defineComponent({\n name: 'BaseGrid',\n props: {\n /** Animation component that will be used to animate the base grid. */\n animation: {\n type: AnimationProp,\n default: 'ul',\n },\n /**\n * Number of columns the grid is divided into. By default, its value is 0, setting the grid\n * columns mode to auto-fill.\n */\n columns: {\n type: Number,\n default: 0,\n },\n /**\n * The list of items to be rendered.\n *\n * @remarks The items must have an ID property.\n */\n items: {\n type: Array as PropType<ListItem[]>,\n },\n },\n setup(props, { slots }) {\n // eslint-disable-next-line ts/consistent-type-definitions\n type ElementRef = {\n $el: HTMLElement\n }\n\n const xBus = useXBus()\n\n /** It injects {@link ListItem} provided by an ancestor. */\n const injectedListItems = inject<Ref<ListItem[]>>(LIST_ITEMS_KEY as string)\n const gridEl = ref<ElementRef | HTMLElement>()\n const renderedColumnsNumber = ref(0)\n\n /**\n * Emits the {@link XEventsTypes.RenderedColumnsNumberChanged}\n * event whenever the number of columns rendered inside the grid changes.\n */\n watch(\n renderedColumnsNumber,\n () => xBus.emit('RenderedColumnsNumberChanged', renderedColumnsNumber.value),\n { immediate: false },\n )\n\n /**\n * It returns the items passed as props or the injected ones.\n *\n * @returns List of grid items.\n */\n const computedItems = computed<ListItem[] | void>(() => {\n return (\n props.items ??\n injectedListItems?.value ??\n console.warn('It is necessary to pass a prop or inject the list of filters')\n )\n })\n\n /**\n * CSS class based on the column property value so items inside the grid can fill different\n * amount of columns or rows based on how many columns the grid is divided into.\n *\n * @returns CSS class with the column property value.\n */\n const cssClasses = computed(() => `x-base-grid--cols-${props.columns || 'auto'}`)\n\n /**\n * CSSStyleDeclaration object specifying the number of columns the grid is divided into based on\n * the column property value.\n *\n * @returns A CSSStyleDeclaration to use as the style attribute.\n */\n const style = computed<Partial<CSSStyleDeclaration>>(() => ({\n gridTemplateColumns: props.columns\n ? `repeat(${props.columns}, minmax(0, 1fr))`\n : 'repeat(auto-fill, minmax(var(--x-size-min-width-grid-item, 150px), 1fr))',\n }))\n\n /**\n * Maps the item to an object containing: the `item`, its `CSS class` and its slot name.\n *\n * @returns An array of objects containing the item and its CSS class.\n */\n const gridItems = computed<GridItem[]>(() =>\n (computedItems.value as ListItem[]).map(item => {\n const slotName = toKebabCase(item.modelName)\n return {\n slotName,\n item,\n cssClass: `x-base-grid__${slotName}`,\n }\n }),\n )\n\n /**\n * Checks if a given value is an `ElementRef` object.\n *\n * @param value - The value to check.\n * @returns `true` if the value is an `ElementRef` object, `false` otherwise.\n */\n const isElementRef = (value: any): value is ElementRef => {\n return value && value.$el instanceof HTMLElement\n }\n\n /** Updates the number of columns rendered inside the grid. */\n function updateRenderedColumnsNumber() {\n const { gridTemplateColumns } = getComputedStyle(\n isElementRef(gridEl.value) ? gridEl.value.$el : (gridEl.value as Element),\n )\n renderedColumnsNumber.value = gridTemplateColumns.split(' ').length\n }\n\n /** Initialises the rendered columns number and sets a ResizeObserver to keep it updated. */\n let resizeObserver: UseResizeObserverReturn\n onMounted(() => {\n resizeObserver = useResizeObserver(\n gridEl as MaybeComputedElementRef,\n updateRenderedColumnsNumber,\n )\n })\n onBeforeUnmount(() => resizeObserver?.stop())\n\n return {\n gridItems,\n cssClasses,\n style,\n gridEl,\n slots,\n }\n },\n})\n</script>\n\n<style lang=\"css\" scoped>\n.x-base-grid {\n display: grid;\n grid-auto-flow: dense;\n list-style: none;\n align-items: stretch;\n}\n\n.x-base-grid__banner,\n.x-base-grid__next-queries-group,\n.x-base-grid__related-prompts-group {\n grid-column-start: 1;\n grid-column-end: -1;\n}\n\n.x-base-grid__related-prompts-group.x-staggered-fade-and-slide-leave-active,\n.x-base-grid__related-prompts-group.x-staggered-fade-and-slide-enter-active {\n transition: none;\n position: relative;\n}\n\n.x-base-grid__item {\n display: flex;\n flex-flow: column nowrap;\n}\n\n.x-base-grid__item > * {\n flex-grow: 1;\n}\n\n.x-base-grid--cols-auto .x-base-grid__item {\n min-width: var(--x-size-min-width-grid-item);\n}\n</style>\n\n<docs lang=\"mdx\">\n## Examples\n\nThis component renders a list of elements in different slots depending on their modelName. In order\nto achieve this, it exposes a scopedSlot for each different modelName. In case the items used do not\nhave modelName property, the default slot is used instead. It has a required property, the `items`\nto render, and an optional one, the number of `columns` the grid is divided in. If the number of\ncolumns is not specified, the grid automatically fills the rows with as many columns as it can fit.\n\n### Basic example\n\nIt renders a list of items using the default slot:\n\n```vue\n<template>\n <BaseGrid :items=\"items\">\n <template #default=\"{ item }\">\n {{ `Default slot content: ${item.id}` }}\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nconst items = [\n { id: 1, name: 'Item 1' },\n { id: 2, name: 'Item 2' },\n { id: 3, name: 'Item 3' },\n]\n</script>\n```\n\n### Configuring the number of columns\n\nIt renders a grid with 12 columns instead of 6, which is the default value:\n\n```vue\n<template>\n <BaseGrid :items=\"items\" :columns=\"12\">\n <template #default=\"{ item }\">\n {{ `Default slot content: ${item.id}` }}\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nconst items = [\n { id: 1, name: 'Item 1' },\n { id: 2, name: 'Item 2' },\n { id: 3, name: 'Item 3' },\n]\n</script>\n```\n\n### Rendering usage\n\nConfiguring the number of columns.\n\nIt renders a list of items using the different scopedSlots created by the item's modelName. For\nexample, if you want to use this component as the search grid, you pass the search results (results,\nbanners, promoted, next queries...etc) as items. Each of these results have a different modelName\nand are rendered in different slots.\n\n```vue\n<template>\n <BaseGrid :animation=\"animation\" :items=\"items\">\n <template #banner=\"{ item }\">\n <span class=\"banner\">\n {{ `${item.title} banner` }}\n </span>\n </template>\n <template #next-queries=\"{ item }\">\n <span>\n {{ `${item.totalResults} next queries` }}\n </span>\n </template>\n <template #promoted=\"{ item }\">\n <span class=\"promoted\">\n {{ `${item.title} promoted` }}\n </span>\n </template>\n <template #result=\"{ item }\">\n <BaseResultLink :result=\"item\">\n {{ item.name }}\n </BaseResultLink>\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nimport BaseResultLink from './BaseResultLink.vue'\nconst animation = 'ul'\nconst items = [\n { id: 1, modelName: 'banner', title: 'Banner 1' },\n { id: 2, modelName: 'next-queries', totalResults: 5 },\n { id: 3, modelName: 'promoted', title: 'Promo 1' },\n { id: 4, modelName: 'result', name: 'Result 1' },\n]\n</script>\n```\n\n### Customizing the items width\n\nThe `--x-size-min-width-grid-item` variable can be used to customize the min width of the grid\nitems.\n\n```vue\n<template>\n <BaseGrid :items=\"items\" style=\"--x-size-min-width-grid-item: 150px\">\n <template #default=\"{ item }\">\n {{ `Default slot content: ${item.id}` }}\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nconst items = [\n { id: 1, name: 'Item 1' },\n { id: 2, name: 'Item 2' },\n { id: 3, name: 'Item 3' },\n]\n</script>\n```\n</docs>\n"],"names":["_openBlock","_createBlock","_resolveDynamicComponent","_normalizeStyle","_normalizeClass","_createElementBlock","_Fragment","_renderList","_renderSlot","_createTextVNode","_toDisplayString"],"mappings":";;;;;;AACE,EAAA,OAAAA,SAAA,EAAA,EAAAC,WAAA,CA4BYC,wBA3BL,IAAA,CAAA,SAAS,CAAA,EAAA;AAAA,IACd,GAAA,EAAI,QAAA;AAAA,IACH,KAAA,EAAKC,eAAE,IAAA,CAAA,KAAK,CAAA;AAAA,IACb,KAAA,EAAKC,cAAA,CAAA,CAAC,aAAA,EACE,IAAA,CAAA,UAAU,CAAA,CAAA;AAAA,IAClB,GAAA,EAAI,IAAA;AAAA,IACJ,WAAA,EAAU;AAAA,GAAA,EAAA;qBAGR,MAAiD;AAAA,OAAAJ,SAAA,CAAA,IAAA,CAAA,EADnDK,kBAAA;AAAA,QAkBKC,QAAA;AAAA,QAAA,IAAA;AAAA,QAAAC,UAAA,CAjBoC,IAAA,CAAA,SAAA,EAAS,CAAA,EAAvC,IAAA,EAAM,QAAA,EAAU,QAAA,EAAQ,KAAA;AADnC,UAAA,OAAAP,SAAA,EAAA,EAAAK,kBAAA;AAAA,YAkBK,IAAA;AAAA,YAAA;AAAA,cAhBF,KAAK,IAAA,CAAK,EAAA;AAAA,cACV,KAAA,EAAKD,cAAA,CAAA,CAAE,QAAA,EACF,mBAAmB,CAAA;AAAA,aAAA;;AAOb,cAAA,IAAA,CAAA,KAAA,CAAM,QAAQ,CAAA,GAA1BI,UAAA,CAA6D,IAAA,CAAA,MAAA,EAAzB,QAAA,EAAQ;AAAA,gBAAA,GAAA,EAAA,CAAA;AAAG,gBAAA;AAAA,eAAA,EAAA,MAAA,EAAA,IAAA,CAAA,GAM/CA,UAAA,CAA8F,IAAA,CAAA,MAAA,EAAA,SAAA,EAAA;AAAA,gBAAA,GAAA,EAAA,CAAA;AAAhF,gBAAA;AAAA,eAAA,EAAd,MAA8F;AAAA,gBAAAC,eAAA;AAAhE,kBAAAC,eAAA,CAAA,IAAA,CAAa,IAAA,IAAQ,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,kBAAA;AAAA;AAAA;AAAA,eAAA,EAAA,IAAA;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-grid.vue2.js","sources":["../../../src/components/base-grid.vue"],"sourcesContent":["<template>\n <component\n :is=\"animation\"\n ref=\"gridEl\"\n :style=\"style\"\n class=\"x-base-grid\"\n :class=\"cssClasses\"\n tag=\"ul\"\n data-test=\"grid\"\n >\n <li\n v-for=\"{ item, cssClass, slotName } in gridItems\"\n :key=\"item.id\"\n :class=\"cssClass\"\n class=\"x-base-grid__item\"\n >\n <!--\n @slot Customized item rendering. Specifying a slot with the item's modelName will result in\n the item using that slot composition to render.\n @binding {item} item - Item to render\n -->\n <slot v-if=\"slots[slotName]\" :name=\"slotName\" :item=\"item\" />\n <!--\n @slot (required) Default item rendering. This slot will be used by default for rendering\n the item without an specific slot implementation.\n @binding {item} item - Item to render\n -->\n <slot v-else :item=\"item\">{{ item.name || item.modelName || item.id || item }}</slot>\n </li>\n </component>\n</template>\n\n<script lang=\"ts\">\nimport type { MaybeComputedElementRef, UseResizeObserverReturn } from '@vueuse/core'\nimport type { PropType, Ref } from 'vue'\nimport type { ListItem, VueCSSClasses } from '../utils/types'\nimport { useResizeObserver } from '@vueuse/core'\nimport { computed, defineComponent, inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { useXBus } from '../composables/use-x-bus'\nimport { AnimationProp } from '../types/animation-prop'\nimport { toKebabCase } from '../utils/string'\nimport { LIST_ITEMS_KEY } from './decorators/injection.consts'\n\n/**\n * The type returned by the gridItems function. Basically it's a list of items with its CSS\n * classes and a slotName.\n */\ninterface GridItem {\n slotName: string\n item: ListItem\n cssClass: VueCSSClasses\n}\n\n/**\n * Grid component that is able to render different items based on their modelName value. In order\n * to achieve this, it exposes a scopedSlot for each different modelName. In case the items used\n * do not have modelName property, the default slot is used instead. It has a required property:\n * the `items` to render; and an optional one: the number `columns` the grid is divided in. If the\n * number of columns is not specified, the grid automatically fills the rows with as many columns\n * as it can fit.\n *\n * @public\n */\nexport default defineComponent({\n name: 'BaseGrid',\n props: {\n /** Animation component that will be used to animate the base grid. */\n animation: {\n type: AnimationProp,\n default: 'ul',\n },\n /**\n * Number of columns the grid is divided into. By default, its value is 0, setting the grid\n * columns mode to auto-fill.\n */\n columns: {\n type: Number,\n default: 0,\n },\n /**\n * The list of items to be rendered.\n *\n * @remarks The items must have an ID property.\n */\n items: {\n type: Array as PropType<ListItem[]>,\n },\n },\n setup(props, { slots }) {\n // eslint-disable-next-line ts/consistent-type-definitions\n type ElementRef = {\n $el: HTMLElement\n }\n\n const xBus = useXBus()\n\n /** It injects {@link ListItem} provided by an ancestor. */\n const injectedListItems = inject<Ref<ListItem[]>>(LIST_ITEMS_KEY as string)\n const gridEl = ref<ElementRef | HTMLElement>()\n const renderedColumnsNumber = ref(0)\n\n /**\n * Emits the {@link XEventsTypes.RenderedColumnsNumberChanged}\n * event whenever the number of columns rendered inside the grid changes.\n */\n watch(\n renderedColumnsNumber,\n () => xBus.emit('RenderedColumnsNumberChanged', renderedColumnsNumber.value),\n { immediate: false },\n )\n\n /**\n * It returns the items passed as props or the injected ones.\n *\n * @returns List of grid items.\n */\n const computedItems = computed<ListItem[] | void>(() => {\n return (\n props.items ??\n injectedListItems?.value ??\n console.warn('It is necessary to pass a prop or inject the list of filters')\n )\n })\n\n /**\n * CSS class based on the column property value so items inside the grid can fill different\n * amount of columns or rows based on how many columns the grid is divided into.\n *\n * @returns CSS class with the column property value.\n */\n const cssClasses = computed(() => `x-base-grid--cols-${props.columns || 'auto'}`)\n\n /**\n * CSSStyleDeclaration object specifying the number of columns the grid is divided into based on\n * the column property value.\n *\n * @returns A CSSStyleDeclaration to use as the style attribute.\n */\n const style = computed<Partial<CSSStyleDeclaration>>(() => ({\n gridTemplateColumns: props.columns\n ? `repeat(${props.columns}, minmax(0, 1fr))`\n : 'repeat(auto-fill, minmax(var(--x-size-min-width-grid-item, 150px), 1fr))',\n }))\n\n /**\n * Maps the item to an object containing: the `item`, its `CSS class` and its slot name.\n *\n * @returns An array of objects containing the item and its CSS class.\n */\n const gridItems = computed<GridItem[]>(() =>\n (computedItems.value as ListItem[]).map(item => {\n const slotName = toKebabCase(item.modelName)\n return {\n slotName,\n item,\n cssClass: `x-base-grid__${slotName}`,\n }\n }),\n )\n\n /**\n * Checks if a given value is an `ElementRef` object.\n *\n * @param value - The value to check.\n * @returns `true` if the value is an `ElementRef` object, `false` otherwise.\n */\n const isElementRef = (value: any): value is ElementRef => {\n return value && value.$el instanceof HTMLElement\n }\n\n /** Updates the number of columns rendered inside the grid. */\n function updateRenderedColumnsNumber() {\n const { gridTemplateColumns } = getComputedStyle(\n isElementRef(gridEl.value) ? gridEl.value.$el : (gridEl.value as Element),\n )\n renderedColumnsNumber.value = gridTemplateColumns.split(' ').length\n }\n\n /** Initialises the rendered columns number and sets a ResizeObserver to keep it updated. */\n let resizeObserver: UseResizeObserverReturn\n onMounted(() => {\n resizeObserver = useResizeObserver(\n gridEl as MaybeComputedElementRef,\n updateRenderedColumnsNumber,\n )\n })\n onBeforeUnmount(() => resizeObserver?.stop())\n\n return {\n gridItems,\n cssClasses,\n style,\n gridEl,\n slots,\n }\n },\n})\n</script>\n\n<style lang=\"css\" scoped>\n.x-base-grid {\n display: grid;\n grid-auto-flow: dense;\n list-style: none;\n align-items: stretch;\n}\n\n.x-base-grid__banner,\n.x-base-grid__next-queries-group,\n.x-base-grid__related-prompts-group {\n grid-column-start: 1;\n grid-column-end: -1;\n}\n\n.x-base-grid__related-prompts-group.x-staggered-fade-and-slide-leave-active,\n.x-base-grid__related-prompts-group.x-staggered-fade-and-slide-enter-active {\n transition: none;\n position: relative;\n}\n\n.x-base-grid__item {\n display: flex;\n flex-flow: column nowrap;\n}\n\n.x-base-grid__item > * {\n flex-grow: 1;\n}\n\n.x-base-grid--cols-auto .x-base-grid__item {\n min-width: var(--x-size-min-width-grid-item);\n}\n</style>\n\n<docs lang=\"mdx\">\n## Examples\n\nThis component renders a list of elements in different slots depending on their modelName. In order\nto achieve this, it exposes a scopedSlot for each different modelName. In case the items used do not\nhave modelName property, the default slot is used instead. It has a required property, the `items`\nto render, and an optional one, the number of `columns` the grid is divided in. If the number of\ncolumns is not specified, the grid automatically fills the rows with as many columns as it can fit.\n\n### Basic example\n\nIt renders a list of items using the default slot:\n\n```vue\n<template>\n <BaseGrid :items=\"items\">\n <template #default=\"{ item }\">\n {{ `Default slot content: ${item.id}` }}\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nconst items = [\n { id: 1, name: 'Item 1' },\n { id: 2, name: 'Item 2' },\n { id: 3, name: 'Item 3' },\n]\n</script>\n```\n\n### Configuring the number of columns\n\nIt renders a grid with 12 columns instead of 6, which is the default value:\n\n```vue\n<template>\n <BaseGrid :items=\"items\" :columns=\"12\">\n <template #default=\"{ item }\">\n {{ `Default slot content: ${item.id}` }}\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nconst items = [\n { id: 1, name: 'Item 1' },\n { id: 2, name: 'Item 2' },\n { id: 3, name: 'Item 3' },\n]\n</script>\n```\n\n### Rendering usage\n\nConfiguring the number of columns.\n\nIt renders a list of items using the different scopedSlots created by the item's modelName. For\nexample, if you want to use this component as the search grid, you pass the search results (results,\nbanners, promoted, next queries...etc) as items. Each of these results have a different modelName\nand are rendered in different slots.\n\n```vue\n<template>\n <BaseGrid :animation=\"animation\" :items=\"items\">\n <template #banner=\"{ item }\">\n <span class=\"banner\">\n {{ `${item.title} banner` }}\n </span>\n </template>\n <template #next-queries=\"{ item }\">\n <span>\n {{ `${item.totalResults} next queries` }}\n </span>\n </template>\n <template #promoted=\"{ item }\">\n <span class=\"promoted\">\n {{ `${item.title} promoted` }}\n </span>\n </template>\n <template #result=\"{ item }\">\n <BaseResultLink :result=\"item\">\n {{ item.name }}\n </BaseResultLink>\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nimport BaseResultLink from './BaseResultLink.vue'\nconst animation = 'ul'\nconst items = [\n { id: 1, modelName: 'banner', title: 'Banner 1' },\n { id: 2, modelName: 'next-queries', totalResults: 5 },\n { id: 3, modelName: 'promoted', title: 'Promo 1' },\n { id: 4, modelName: 'result', name: 'Result 1' },\n]\n</script>\n```\n\n### Customizing the items width\n\nThe `--x-size-min-width-grid-item` variable can be used to customize the min width of the grid\nitems.\n\n```vue\n<template>\n <BaseGrid :items=\"items\" style=\"--x-size-min-width-grid-item: 150px\">\n <template #default=\"{ item }\">\n {{ `Default slot content: ${item.id}` }}\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nconst items = [\n { id: 1, name: 'Item 1' },\n { id: 2, name: 'Item 2' },\n { id: 3, name: 'Item 3' },\n]\n</script>\n```\n</docs>\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-grid.vue2.js","sources":["../../../src/components/base-grid.vue"],"sourcesContent":["<template>\n <component\n :is=\"animation\"\n ref=\"gridEl\"\n :style=\"style\"\n class=\"x-base-grid\"\n :class=\"cssClasses\"\n tag=\"ul\"\n data-test=\"grid\"\n >\n <li\n v-for=\"{ item, cssClass, slotName } in gridItems\"\n :key=\"item.id\"\n :class=\"cssClass\"\n class=\"x-base-grid__item\"\n >\n <!--\n @slot Customized item rendering. Specifying a slot with the item's modelName will result in\n the item using that slot composition to render.\n @binding {item} item - Item to render\n -->\n <slot v-if=\"slots[slotName]\" :name=\"slotName\" :item=\"item\" />\n <!--\n @slot (required) Default item rendering. This slot will be used by default for rendering\n the item without an specific slot implementation.\n @binding {item} item - Item to render\n -->\n <slot v-else :item=\"item\">{{ (item as any).name || item.modelName || item.id || item }}</slot>\n </li>\n </component>\n</template>\n\n<script lang=\"ts\">\nimport type { MaybeComputedElementRef, UseResizeObserverReturn } from '@vueuse/core'\nimport type { PropType, Ref } from 'vue'\nimport type { ListItem } from '../utils/types'\nimport type { GridItem } from './base-grid.types'\nimport { useResizeObserver } from '@vueuse/core'\nimport { computed, defineComponent, inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { useXBus } from '../composables/use-x-bus'\nimport { AnimationProp } from '../types/animation-prop'\nimport { toKebabCase } from '../utils/string'\nimport { LIST_ITEMS_KEY } from './decorators/injection.consts'\n\n/**\n * Grid component that is able to render different items based on their modelName value. In order\n * to achieve this, it exposes a scopedSlot for each different modelName. In case the items used\n * do not have modelName property, the default slot is used instead. It has a required property:\n * the `items` to render; and an optional one: the number `columns` the grid is divided in. If the\n * number of columns is not specified, the grid automatically fills the rows with as many columns\n * as it can fit.\n *\n * @public\n */\nexport default defineComponent({\n name: 'BaseGrid',\n props: {\n /** Animation component that will be used to animate the base grid. */\n animation: {\n type: AnimationProp,\n default: 'ul',\n },\n /**\n * Number of columns the grid is divided into. By default, its value is 0, setting the grid\n * columns mode to auto-fill.\n */\n columns: {\n type: Number,\n default: 0,\n },\n /**\n * The list of items to be rendered.\n *\n * @remarks The items must have an ID property.\n */\n items: {\n type: Array as PropType<ListItem[]>,\n },\n },\n setup(props, { slots }) {\n // eslint-disable-next-line ts/consistent-type-definitions\n type ElementRef = {\n $el: HTMLElement\n }\n\n const xBus = useXBus()\n\n /** It injects {@link ListItem} provided by an ancestor. */\n const injectedListItems = inject<Ref<ListItem[]>>(LIST_ITEMS_KEY as string)\n const gridEl = ref<ElementRef | HTMLElement>()\n const renderedColumnsNumber = ref(0)\n\n /**\n * Emits the {@link XEventsTypes.RenderedColumnsNumberChanged}\n * event whenever the number of columns rendered inside the grid changes.\n */\n watch(\n renderedColumnsNumber,\n () => xBus.emit('RenderedColumnsNumberChanged', renderedColumnsNumber.value),\n { immediate: false },\n )\n\n /**\n * It returns the items passed as props or the injected ones.\n *\n * @returns List of grid items.\n */\n const computedItems = computed<ListItem[] | void>(() => {\n return (\n props.items ??\n injectedListItems?.value ??\n console.warn('It is necessary to pass a prop or inject the list of filters')\n )\n })\n\n /**\n * CSS class based on the column property value so items inside the grid can fill different\n * amount of columns or rows based on how many columns the grid is divided into.\n *\n * @returns CSS class with the column property value.\n */\n const cssClasses = computed(() => `x-base-grid--cols-${props.columns || 'auto'}`)\n\n /**\n * CSSStyleDeclaration object specifying the number of columns the grid is divided into based on\n * the column property value.\n *\n * @returns A CSSStyleDeclaration to use as the style attribute.\n */\n const style = computed<Partial<CSSStyleDeclaration>>(() => ({\n gridTemplateColumns: props.columns\n ? `repeat(${props.columns}, minmax(0, 1fr))`\n : 'repeat(auto-fill, minmax(var(--x-size-min-width-grid-item, 150px), 1fr))',\n }))\n\n /**\n * Maps the item to an object containing: the `item`, its `CSS class` and its slot name.\n *\n * @returns An array of objects containing the item and its CSS class.\n */\n const gridItems = computed<GridItem[]>(() =>\n (computedItems.value as ListItem[]).map(item => {\n const slotName = toKebabCase(item.modelName)\n return {\n slotName,\n item,\n cssClass: `x-base-grid__${slotName}`,\n }\n }),\n )\n\n /**\n * Checks if a given value is an `ElementRef` object.\n *\n * @param value - The value to check.\n * @returns `true` if the value is an `ElementRef` object, `false` otherwise.\n */\n const isElementRef = (value: any): value is ElementRef => {\n return value && value.$el instanceof HTMLElement\n }\n\n /** Updates the number of columns rendered inside the grid. */\n function updateRenderedColumnsNumber() {\n const { gridTemplateColumns } = getComputedStyle(\n isElementRef(gridEl.value) ? gridEl.value.$el : (gridEl.value as Element),\n )\n renderedColumnsNumber.value = gridTemplateColumns.split(' ').length\n }\n\n /** Initialises the rendered columns number and sets a ResizeObserver to keep it updated. */\n let resizeObserver: UseResizeObserverReturn\n onMounted(() => {\n resizeObserver = useResizeObserver(\n gridEl as MaybeComputedElementRef,\n updateRenderedColumnsNumber,\n )\n })\n onBeforeUnmount(() => resizeObserver?.stop())\n\n return {\n gridItems,\n cssClasses,\n style,\n gridEl,\n slots,\n }\n },\n})\n</script>\n\n<style lang=\"css\" scoped>\n.x-base-grid {\n display: grid;\n grid-auto-flow: dense;\n list-style: none;\n align-items: stretch;\n}\n\n.x-base-grid__banner,\n.x-base-grid__next-queries-group,\n.x-base-grid__related-prompts-group {\n grid-column-start: 1;\n grid-column-end: -1;\n}\n\n.x-base-grid__related-prompts-group.x-staggered-fade-and-slide-leave-active,\n.x-base-grid__related-prompts-group.x-staggered-fade-and-slide-enter-active {\n transition: none;\n position: relative;\n}\n\n.x-base-grid__item {\n display: flex;\n flex-flow: column nowrap;\n}\n\n.x-base-grid__item > * {\n flex-grow: 1;\n}\n\n.x-base-grid--cols-auto .x-base-grid__item {\n min-width: var(--x-size-min-width-grid-item);\n}\n</style>\n\n<docs lang=\"mdx\">\n## Examples\n\nThis component renders a list of elements in different slots depending on their modelName. In order\nto achieve this, it exposes a scopedSlot for each different modelName. In case the items used do not\nhave modelName property, the default slot is used instead. It has a required property, the `items`\nto render, and an optional one, the number of `columns` the grid is divided in. If the number of\ncolumns is not specified, the grid automatically fills the rows with as many columns as it can fit.\n\n### Basic example\n\nIt renders a list of items using the default slot:\n\n```vue\n<template>\n <BaseGrid :items=\"items\">\n <template #default=\"{ item }\">\n {{ `Default slot content: ${item.id}` }}\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nconst items = [\n { id: 1, name: 'Item 1' },\n { id: 2, name: 'Item 2' },\n { id: 3, name: 'Item 3' },\n]\n</script>\n```\n\n### Configuring the number of columns\n\nIt renders a grid with 12 columns instead of 6, which is the default value:\n\n```vue\n<template>\n <BaseGrid :items=\"items\" :columns=\"12\">\n <template #default=\"{ item }\">\n {{ `Default slot content: ${item.id}` }}\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nconst items = [\n { id: 1, name: 'Item 1' },\n { id: 2, name: 'Item 2' },\n { id: 3, name: 'Item 3' },\n]\n</script>\n```\n\n### Rendering usage\n\nConfiguring the number of columns.\n\nIt renders a list of items using the different scopedSlots created by the item's modelName. For\nexample, if you want to use this component as the search grid, you pass the search results (results,\nbanners, promoted, next queries...etc) as items. Each of these results have a different modelName\nand are rendered in different slots.\n\n```vue\n<template>\n <BaseGrid :animation=\"animation\" :items=\"items\">\n <template #banner=\"{ item }\">\n <span class=\"banner\">\n {{ `${item.title} banner` }}\n </span>\n </template>\n <template #next-queries=\"{ item }\">\n <span>\n {{ `${item.totalResults} next queries` }}\n </span>\n </template>\n <template #promoted=\"{ item }\">\n <span class=\"promoted\">\n {{ `${item.title} promoted` }}\n </span>\n </template>\n <template #result=\"{ item }\">\n <BaseResultLink :result=\"item\">\n {{ item.name }}\n </BaseResultLink>\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nimport BaseResultLink from './BaseResultLink.vue'\nconst animation = 'ul'\nconst items = [\n { id: 1, modelName: 'banner', title: 'Banner 1' },\n { id: 2, modelName: 'next-queries', totalResults: 5 },\n { id: 3, modelName: 'promoted', title: 'Promo 1' },\n { id: 4, modelName: 'result', name: 'Result 1' },\n]\n</script>\n```\n\n### Customizing the items width\n\nThe `--x-size-min-width-grid-item` variable can be used to customize the min width of the grid\nitems.\n\n```vue\n<template>\n <BaseGrid :items=\"items\" style=\"--x-size-min-width-grid-item: 150px\">\n <template #default=\"{ item }\">\n {{ `Default slot content: ${item.id}` }}\n </template>\n </BaseGrid>\n</template>\n\n<script setup>\nimport { BaseGrid } from '@empathyco/x-components'\nconst items = [\n { id: 1, name: 'Item 1' },\n { id: 2, name: 'Item 2' },\n { id: 3, name: 'Item 3' },\n]\n</script>\n```\n</docs>\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import injectCss from '../../tools/inject-css.js';
|
|
2
2
|
|
|
3
|
-
var css = ".x-rating[data-v-f3b7cb68]{display:inline-block;max-width:fit-content;position:relative}.x-rating--empty[data-v-f3b7cb68]
|
|
3
|
+
var css = ".x-rating[data-v-f3b7cb68]{display:inline-block;max-width:fit-content;position:relative}.x-rating--empty[data-v-f3b7cb68]{display:flex;flex-flow:row nowrap;overflow:hidden;white-space:nowrap}.x-rating--filled[data-v-f3b7cb68]{display:flex;flex-flow:row nowrap;height:100%;left:0;overflow:hidden;position:absolute;top:0;white-space:nowrap}.x-rating__default-icon[data-v-f3b7cb68]{fill:currentColor;stroke:currentColor}.x-rating__default-icon--empty[data-v-f3b7cb68]{fill:none}";
|
|
4
4
|
injectCss(css);
|
|
5
5
|
|
|
6
6
|
export { css };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-column-picker-list.vue.js","sources":["../../../../src/components/column-picker/base-column-picker-list.vue"],"sourcesContent":["<template>\n <div class=\"x-column-picker-list x-button-group\" data-test=\"column-picker-list\" role=\"list\">\n <template\n v-for=\"({ column, cssClasses, events, isSelected }, index) in columnsWithCssClasses\"\n :key=\"column\"\n >\n <BaseEventButton\n class=\"x-column-picker-list__button x-button\"\n :class=\"[buttonClass, cssClasses]\"\n data-test=\"column-picker-button\"\n :aria-pressed=\"isSelected.toString()\"\n :events=\"events\"\n :aria-label=\"`${column} columns`\"\n role=\"listitem\"\n >\n <!--\n @slot Customized Column Picker Button content. Specifying a slot with the column value\n will result in the column using that slot composition to render.\n @binding {number} column - Columns Number to pick.\n @binding {boolean} isSelected - True if the columns number are the chosen value.\n -->\n <slot v-bind=\"{ column, isSelected }\">\n {{ column }}\n </slot>\n </BaseEventButton>\n\n <!--\n @slot Customized Column Picker divider. Specify an element to act as divider for\n the items in the column picker. Empty by default.\n -->\n <slot v-if=\"index !== columnsWithCssClasses.length - 1\" name=\"divider\"></slot>\n </template>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport type { PropType } from 'vue'\nimport type {
|
|
1
|
+
{"version":3,"file":"base-column-picker-list.vue.js","sources":["../../../../src/components/column-picker/base-column-picker-list.vue"],"sourcesContent":["<template>\n <div class=\"x-column-picker-list x-button-group\" data-test=\"column-picker-list\" role=\"list\">\n <template\n v-for=\"({ column, cssClasses, events, isSelected }, index) in columnsWithCssClasses\"\n :key=\"column\"\n >\n <BaseEventButton\n class=\"x-column-picker-list__button x-button\"\n :class=\"[buttonClass, cssClasses]\"\n data-test=\"column-picker-button\"\n :aria-pressed=\"isSelected.toString()\"\n :events=\"events\"\n :aria-label=\"`${column} columns`\"\n role=\"listitem\"\n >\n <!--\n @slot Customized Column Picker Button content. Specifying a slot with the column value\n will result in the column using that slot composition to render.\n @binding {number} column - Columns Number to pick.\n @binding {boolean} isSelected - True if the columns number are the chosen value.\n -->\n <slot v-bind=\"{ column, isSelected }\">\n {{ column }}\n </slot>\n </BaseEventButton>\n\n <!--\n @slot Customized Column Picker divider. Specify an element to act as divider for\n the items in the column picker. Empty by default.\n -->\n <slot v-if=\"index !== columnsWithCssClasses.length - 1\" name=\"divider\"></slot>\n </template>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport type { PropType } from 'vue'\nimport type { ColumnPickerItem } from './base-column-picker-list.types'\nimport { computed, defineComponent, onBeforeMount, ref, watch } from 'vue'\nimport { use$x } from '../../composables/use-$x'\nimport BaseEventButton from '../base-event-button.vue'\n\n/**\n * Column picker list component renders a list of buttons to choose the columns number.\n *\n * Additionally, this component exposes the following props to modify the classes of the\n * elements: `buttonClass`.\n *\n * @public\n */\nexport default defineComponent({\n name: 'BaseColumnPickerList',\n components: { BaseEventButton },\n props: {\n /** An array of numbers that represents the number of columns to render. */\n columns: {\n type: Array as PropType<number[]>,\n required: true,\n },\n /** The value of the selected columns number. */\n modelValue: Number,\n /** Class inherited by each button. */\n buttonClass: String,\n },\n emits: ['update:modelValue'],\n setup(props, { emit }) {\n const $x = use$x()\n\n const providedSelectedColumns = computed(() => props.modelValue ?? props.columns[0])\n const selectedColumns = ref(providedSelectedColumns.value)\n\n /**\n * Assigns `selectedColumns` value and emits `ColumnsNumberProvided`.\n *\n * @param column - Column number provided.\n */\n function emitColumnsNumberProvided(column: number) {\n selectedColumns.value = column\n $x.emit('ColumnsNumberProvided', column)\n }\n\n /**\n * Emits `update:modelValue` with the column selected.\n *\n * @param column - Column number selected.\n */\n function emitUpdateModelValue(column: number) {\n if (props.modelValue !== column) {\n emit('update:modelValue', column)\n }\n }\n\n watch(providedSelectedColumns, emitColumnsNumberProvided)\n watch(selectedColumns, emitUpdateModelValue)\n\n $x.on('ColumnsNumberProvided', false).subscribe(column => (selectedColumns.value = column))\n\n /**\n * Synchronizes the columns number before mounting the component. If the real number of selected\n * columns equals the provided columns, it emits the event to sync it with every other component.\n * If it is not equal it means that the user has already selected a number of columns, so we emit\n * a `update:modelValue` event so developers can sync the provided value.\n */\n onBeforeMount(() => {\n if (selectedColumns.value === providedSelectedColumns.value) {\n emitColumnsNumberProvided(selectedColumns.value)\n } else {\n emitUpdateModelValue(selectedColumns.value)\n }\n })\n\n /**\n * Maps the column to an object containing: the `column` and `CSS classes`.\n *\n * @returns An array of objects containing the column number and CSS classes.\n */\n const columnsWithCssClasses = computed<ColumnPickerItem[]>(() =>\n props.columns.map(column => ({\n column,\n cssClasses: [\n `x-column-picker-list__button--${column}-cols`,\n { 'x-selected': selectedColumns.value === column },\n ],\n isSelected: selectedColumns.value === column,\n events: {\n UserClickedColumnPicker: column,\n ColumnsNumberProvided: column,\n },\n })),\n )\n\n return { columnsWithCssClasses }\n },\n})\n</script>\n\n<docs lang=\"mdx\">\n## Examples\n\nThis component renders a list of elements in different slots depending on the columns prop. Each\nbutton emits the needed events to sync other instances of column pickers or grids with the\nnumber of columns being selected when clicked.\n\n### Default usage\n\nIt is required to send the columns prop.\n\n```vue live\n<template>\n <BaseColumnPickerList :columns=\"columns\" />\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport BaseColumnPickerList from '@empathyco/x-components/js/components/column-picker/base-column-picker-list.vue'\n\nconst columns = ref([2, 4, 6])\n</script>\n```\n\n### Using v-model\n\nIt is possible to do two-way binding in order to synchronize the value with the parent. It will be\nupdated if the value changes or if the parent changes it.\n\n```vue live\n<template>\n <BaseColumnPickerList :columns=\"columns\" v-model=\"selectedColumns\" />\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport BaseColumnPickerList from '@empathyco/x-components/js/components/column-picker/base-column-picker-list.vue'\n\nconst columns = ref([2, 4, 6])\nconst selectedColumns = ref(4)\n</script>\n```\n\n### Customized usage\n\n#### Overriding the slots\n\nIt is possible to override the column picker button content.\n\n```vue live\n<template>\n <BaseColumnPickerList :columns=\"columns\">\n <template #default=\"{ column, isSelected }\">\n <span>{{ column }} {{ isSelected ? '🟢' : '' }}</span>\n </template>\n </BaseColumnPickerList>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport BaseColumnPickerList from '@empathyco/x-components/js/components/column-picker/base-column-picker-list.vue'\n\nconst columns = ref([2, 4, 6])\n</script>\n```\n\nIt is also possible to add a divider element between the column picker buttons by overriding the\n`divider` slot.\n\n```vue live\n<template>\n <BaseColumnPickerList :columns=\"columns\">\n <template #divider>\n <ChevronRightIcon aria-hidden=\"true\" />\n </template>\n </BaseColumnPickerList>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport BaseColumnPickerList from '@empathyco/x-components/js/components/column-picker/base-column-picker-list.vue'\nimport ChevronRightIcon from '@empathyco/x-components/js/components/icons/chevron-right.vue'\n\nconst columns = ref([2, 4, 6])\n</script>\n```\n\n#### Customizing the buttons with classes\n\nThe `buttonClass` prop can be used to add classes to the buttons.\n\n```vue live\n<template>\n <BaseColumnPickerList :columns=\"columns\" buttonClass=\"x-button--round\" />\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport BaseColumnPickerList from '@empathyco/x-components/js/components/column-picker/base-column-picker-list.vue'\n\nconst columns = ref([2, 4, 6])\n</script>\n```\n\n## Events\n\nA list of events that the component will emit:\n\n- [`UserClickedColumnPicker`](https://github.com/empathyco/x/blob/main/packages/x-components/src/wiring/events.types.ts):\n the event is emitted after the user clicks an item. The event payload is the number of columns\n that the clicked item represents.\n- [`ColumnsNumberProvided`](https://github.com/empathyco/x/blob/main/packages/x-components/src/wiring/events.types.ts):\n the event is emitted on component mount. The event payload is the current `selectedColumns` value.\n</docs>\n"],"names":["_openBlock","_createElementBlock","_Fragment","_renderList","_createVNode","_normalizeClass","_renderSlot","_mergeProps","_createTextVNode","_createCommentVNode"],"mappings":";;;;;EACO,KAAA,EAAM,qCAAA;AAAA,EAAsC,WAAA,EAAU,oBAAA;AAAA,EAAqB,IAAA,EAAK;;;;AAArF,EAAA,OAAAA,SAAA,EAAA,EAAAC,kBAAA,CA+BM,OA/BN,UAAA,EA+BM;AAAA,KAAAD,SAAA,CAAA,IAAA,CAAA,EA9BJC,kBAAA;AAAA,MA6BWC,QAAA;AAAA,MAAA,IAAA;AAAA,MAAAC,UAAA,CA5BqD,4BAAqB,CAAA,EAAzE,MAAA,EAAQ,YAAY,MAAA,EAAQ,UAAA,IAAc,KAAA,KAAK;;;iBACnD,MAAA,EAAM;AAAA,UAAA;AAAA,YAEZC,WAAA,CAkBkB,0BAAA,EAAA;AAAA,cAjBhB,KAAA,EAAKC,cAAA,CAAA,CAAC,uCAAA,EAAuC,CACpC,kBAAa,UAAU,CAAA,CAAA,CAAA;AAAA,cAChC,WAAA,EAAU,sBAAA;AAAA,cACT,cAAA,EAAc,WAAW,QAAA,EAAQ;AAAA,cACjC,MAAA;AAAA,cACA,YAAA,EAAU,GAAK,MAAM,CAAA,QAAA,CAAA;AAAA,cACtB,IAAA,EAAK;AAAA,aAAA,EAAA;+BAQL,MAEO;AAAA,gBAFPC,WAEO,IAAA,CAAA,MAAA,EAAA,SAAA,EAFPC,UAAA,CAEO,qBAFS,MAAA,EAAQ,UAAA,KAAxB,MAEO;AAAA,kBAAAC,eAAA;oCADF,MAAM,CAAA;AAAA,oBAAA;AAAA;AAAA;AAAA,iBAAA;;;;;YAQD,KAAA,KAAU,IAAA,CAAA,qBAAA,CAAsB,MAAA,GAAM,CAAA,GAAlDF,UAAA,CAA8E,IAAA,CAAA,MAAA,EAAA,SAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,CAAA,GAAAG,kBAAA,CAAA,MAAA,EAAA,IAAA;;;;;;;;;;;;;;;"}
|