@empathyco/x-components 6.0.0-alpha.214 → 6.0.0-alpha.216
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 +13 -0
- package/ai/index.js +1 -0
- package/core/index.js.map +1 -1
- package/docs/API-reference/api/x-components.aicarousel.md +57 -0
- package/docs/API-reference/api/x-components.md +9 -0
- package/docs/API-reference/api/x-components.resultfeature.md +1 -1
- package/docs/API-reference/components/ai/x-components.ai-carousel.md +28 -0
- package/js/components/animations/change-height.vue.js +2 -1
- package/js/components/animations/change-height.vue.js.map +1 -1
- package/js/components/animations/change-height.vue2.js.map +1 -1
- package/js/components/animations/change-height.vue3.js +7 -0
- package/js/components/animations/change-height.vue3.js.map +1 -0
- package/js/components/base-dropdown.vue2.js +1 -0
- package/js/components/base-dropdown.vue2.js.map +1 -1
- package/js/components/modals/base-modal.vue2.js +1 -0
- package/js/components/modals/base-modal.vue2.js.map +1 -1
- package/js/components/panels/base-header-toggle-panel.vue2.js +1 -0
- package/js/components/panels/base-header-toggle-panel.vue2.js.map +1 -1
- package/js/components/panels/base-id-toggle-panel.vue2.js +1 -0
- package/js/components/panels/base-id-toggle-panel.vue2.js.map +1 -1
- package/js/components/panels/base-tabs-panel.vue2.js +1 -0
- package/js/components/panels/base-tabs-panel.vue2.js.map +1 -1
- package/js/components/result/base-result-image.vue2.js +1 -0
- package/js/components/result/base-result-image.vue2.js.map +1 -1
- package/js/index.js +1 -0
- package/js/index.js.map +1 -1
- package/js/x-modules/ai/components/ai-carousel.vue.js +150 -0
- package/js/x-modules/ai/components/ai-carousel.vue.js.map +1 -0
- package/js/x-modules/ai/components/ai-carousel.vue2.js +199 -0
- package/js/x-modules/ai/components/ai-carousel.vue2.js.map +1 -0
- package/js/x-modules/ai/components/ai-carousel.vue3.js +7 -0
- package/js/x-modules/ai/components/ai-carousel.vue3.js.map +1 -0
- package/js/x-modules/ai/wiring.js +7 -3
- package/js/x-modules/ai/wiring.js.map +1 -1
- package/js/x-modules/empathize/components/empathize.vue2.js +1 -0
- package/js/x-modules/empathize/components/empathize.vue2.js.map +1 -1
- package/js/x-modules/queries-preview/components/query-preview-button.vue2.js +1 -0
- package/js/x-modules/queries-preview/components/query-preview-button.vue2.js.map +1 -1
- package/js/x-modules/queries-preview/components/query-preview.vue2.js +1 -0
- package/js/x-modules/queries-preview/components/query-preview.vue2.js.map +1 -1
- package/js/x-modules/scroll/components/scroll-to-top.vue2.js +1 -0
- package/js/x-modules/scroll/components/scroll-to-top.vue2.js.map +1 -1
- package/package.json +2 -2
- package/report/x-components.api.json +375 -1
- package/report/x-components.api.md +58 -6
- package/types/types/origin.d.ts +1 -1
- package/types/types/origin.d.ts.map +1 -1
- package/types/x-modules/ai/components/ai-carousel.vue.d.ts +50 -0
- package/types/x-modules/ai/components/ai-carousel.vue.d.ts.map +1 -0
- package/types/x-modules/ai/components/index.d.ts +1 -0
- package/types/x-modules/ai/components/index.d.ts.map +1 -1
- package/types/x-modules/ai/wiring.d.ts +4 -1
- package/types/x-modules/ai/wiring.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## 6.0.0-alpha.216 (2026-03-04)
|
|
7
|
+
|
|
8
|
+
- fix: update slot props in AI carousel and improve height transition styles (#2041) ([e025504](https://github.com/empathyco/x/commit/e025504)), closes [#2041](https://github.com/empathyco/x/issues/2041)
|
|
9
|
+
|
|
10
|
+
## 6.0.0-alpha.215 (2026-03-04)
|
|
11
|
+
|
|
12
|
+
- feat: add AI carousel component with related integration and tests ([8fe744e](https://github.com/empathyco/x/commit/8fe744e))
|
|
13
|
+
- feat: implement title expansion toggle in AI carousel component ([a7ce3e2](https://github.com/empathyco/x/commit/a7ce3e2))
|
|
14
|
+
- test: remove slot customization test from AI carousel component ([b077cc5](https://github.com/empathyco/x/commit/b077cc5))
|
|
15
|
+
- fix: update feature naming from 'ai-carousel' to 'ai_carousel' in component and tests ([2fb1607](https://github.com/empathyco/x/commit/2fb1607))
|
|
16
|
+
- fix: update feature naming from 'ai-carousel' to 'ai_carousel' in component and tests ([d95f9c4](https://github.com/empathyco/x/commit/d95f9c4))
|
|
17
|
+
- chore: remove Vue alias configuration from Vite setup ([168a0e3](https://github.com/empathyco/x/commit/168a0e3))
|
|
18
|
+
|
|
6
19
|
## 6.0.0-alpha.214 (2026-03-04)
|
|
7
20
|
|
|
8
21
|
**Note:** Version bump only for package @empathyco/x-components
|
package/ai/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { default as AiCarousel } from '../js/x-modules/ai/components/ai-carousel.vue.js';
|
|
1
2
|
export { default as AiOverview } from '../js/x-modules/ai/components/ai-overview.vue.js';
|
|
2
3
|
export { fetchAndSaveAiSuggestionsSearch } from '../js/x-modules/ai/store/actions/fetch-and-save-ai-suggestions-search.action.js';
|
|
3
4
|
export { fetchAndSaveAiSuggestions } from '../js/x-modules/ai/store/actions/fetch-and-save-ai-suggestions.action.js';
|
package/core/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
|
2
|
+
|
|
3
|
+
[Home](./index.md) > [@empathyco/x-components](./x-components.md) > [AiCarousel](./x-components.aicarousel.md)
|
|
4
|
+
|
|
5
|
+
## AiCarousel variable
|
|
6
|
+
|
|
7
|
+
**Signature:**
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
_default: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
11
|
+
title: {
|
|
12
|
+
type: StringConstructor;
|
|
13
|
+
};
|
|
14
|
+
slidingPanelClasses: {
|
|
15
|
+
type: StringConstructor;
|
|
16
|
+
};
|
|
17
|
+
slidingPanelContainerClasses: {
|
|
18
|
+
type: StringConstructor;
|
|
19
|
+
};
|
|
20
|
+
slidingPanelButtonsClasses: {
|
|
21
|
+
type: StringConstructor;
|
|
22
|
+
};
|
|
23
|
+
}>, {
|
|
24
|
+
emptyTaggingRequest: TaggingRequest;
|
|
25
|
+
isNoResults: import("vue").ComputedRef<boolean>;
|
|
26
|
+
isTitleOverflowing: import("vue").Ref<boolean, boolean>;
|
|
27
|
+
queries: import("vue").ComputedRef<import("@empathyco/x-types").AiSuggestionQuery[]>;
|
|
28
|
+
query: import("vue").ComputedRef<string>;
|
|
29
|
+
suggestionsSearch: import("vue").ComputedRef<import("@empathyco/x-types").AiSuggestionSearch[]>;
|
|
30
|
+
tagging: import("vue").ComputedRef<import("@empathyco/x-types").AiSuggestionTagging | undefined>;
|
|
31
|
+
title: import("vue").ComputedRef<string>;
|
|
32
|
+
titleExpanded: import("vue").Ref<boolean, boolean>;
|
|
33
|
+
titleRef: import("vue").Ref<HTMLElement | null, HTMLElement | null>;
|
|
34
|
+
toggleTitleExpansion: () => void;
|
|
35
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
36
|
+
title: {
|
|
37
|
+
type: StringConstructor;
|
|
38
|
+
};
|
|
39
|
+
slidingPanelClasses: {
|
|
40
|
+
type: StringConstructor;
|
|
41
|
+
};
|
|
42
|
+
slidingPanelContainerClasses: {
|
|
43
|
+
type: StringConstructor;
|
|
44
|
+
};
|
|
45
|
+
slidingPanelButtonsClasses: {
|
|
46
|
+
type: StringConstructor;
|
|
47
|
+
};
|
|
48
|
+
}>> & Readonly<{}>, {}, {}, {
|
|
49
|
+
ChangeHeight: import("vue").DefineComponent<{}, {}, any>;
|
|
50
|
+
DisplayClickProvider: import("vue").DefineComponent<{}, {}, any>;
|
|
51
|
+
DisplayEmitter: import("vue").DefineComponent<{}, {}, any>;
|
|
52
|
+
CollapseHeight: import("vue").DefineComponent<{}, {}, any>;
|
|
53
|
+
AIStarIcon: import("vue").DefineComponent<{}, {}, any>;
|
|
54
|
+
ChevronDownIcon: import("vue").DefineComponent<{}, {}, any>;
|
|
55
|
+
SlidingPanel: import("vue").DefineComponent<{}, {}, any>;
|
|
56
|
+
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>
|
|
57
|
+
```
|
|
@@ -3564,6 +3564,15 @@ Saves a new query into the history queries.
|
|
|
3564
3564
|
Saves the selectedQueryPreview query into the history queries.
|
|
3565
3565
|
|
|
3566
3566
|
|
|
3567
|
+
</td></tr>
|
|
3568
|
+
<tr><td>
|
|
3569
|
+
|
|
3570
|
+
[AiCarousel](./x-components.aicarousel.md)
|
|
3571
|
+
|
|
3572
|
+
|
|
3573
|
+
</td><td>
|
|
3574
|
+
|
|
3575
|
+
|
|
3567
3576
|
</td></tr>
|
|
3568
3577
|
<tr><td>
|
|
3569
3578
|
|
|
@@ -9,5 +9,5 @@ The name of the tool that generated the results.
|
|
|
9
9
|
**Signature:**
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
|
-
export type ResultFeature = 'search' | 'topclicked_recommendations' | 'brand_recommendations' | 'next_query_recommendations' | 'semantic_recommendations' | 'partial_results' | 'identifier_result' | 'related_prompts' | 'overview';
|
|
12
|
+
export type ResultFeature = 'search' | 'topclicked_recommendations' | 'brand_recommendations' | 'next_query_recommendations' | 'semantic_recommendations' | 'partial_results' | 'identifier_result' | 'related_prompts' | 'overview' | 'ai_carousel';
|
|
13
13
|
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
title: AiCarousel
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# AiCarousel
|
|
8
|
+
|
|
9
|
+
## Props
|
|
10
|
+
|
|
11
|
+
| Name | Description | Type | Default |
|
|
12
|
+
| ----------------------------------------- | ----------- | ------------------- | ------------- |
|
|
13
|
+
| <code>title</code> | | <code>string</code> | <code></code> |
|
|
14
|
+
| <code>slidingPanelClasses</code> | | <code>string</code> | <code></code> |
|
|
15
|
+
| <code>slidingPanelContainerClasses</code> | | <code>string</code> | <code></code> |
|
|
16
|
+
| <code>slidingPanelButtonsClasses</code> | | <code>string</code> | <code></code> |
|
|
17
|
+
|
|
18
|
+
## Slots
|
|
19
|
+
|
|
20
|
+
| Name | Description | Bindings<br />(name - type - description) |
|
|
21
|
+
| ---------------------------------------- | ---------------------- | ----------------------------------------- |
|
|
22
|
+
| <code>sliding-panel</code> | | <br /> |
|
|
23
|
+
| <code>sliding-panels-addons</code> | | |
|
|
24
|
+
| <code>sliding-panels-left-button</code> | | None |
|
|
25
|
+
| <code>sliding-panels-right-button</code> | | None |
|
|
26
|
+
| <code>result</code> | (required) result card | |
|
|
27
|
+
| <code>extra-content</code> | | None |
|
|
28
|
+
| <code>cta-button</code> | | None |
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import _sfc_main from './change-height.vue2.js';
|
|
2
2
|
import { openBlock, createElementBlock, createElementVNode, renderSlot } from 'vue';
|
|
3
|
+
import './change-height.vue3.js';
|
|
3
4
|
import _export_sfc from '../../_virtual/_plugin-vue_export-helper.js';
|
|
4
5
|
|
|
5
|
-
const _hoisted_1 = { class: "x-
|
|
6
|
+
const _hoisted_1 = { class: "x-change-height" };
|
|
6
7
|
const _hoisted_2 = { ref: "wrapper" };
|
|
7
8
|
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
8
9
|
return openBlock(), createElementBlock("div", _hoisted_1, [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-height.vue.js","sources":["../../../../src/components/animations/change-height.vue"],"sourcesContent":["<template>\n <div class=\"x-
|
|
1
|
+
{"version":3,"file":"change-height.vue.js","sources":["../../../../src/components/animations/change-height.vue"],"sourcesContent":["<template>\n <div class=\"x-change-height\">\n <div ref=\"wrapper\">\n <slot />\n </div>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, onMounted, onUnmounted, ref } from 'vue'\n\nexport default defineComponent({\n setup() {\n const wrapper = ref<HTMLElement>()\n const observer = new ResizeObserver(entries => {\n for (const entry of entries) {\n if (entry.target.parentElement) {\n entry.target.parentElement.style.height = `${entry.contentRect.height}px`\n }\n }\n })\n\n onMounted(() => {\n if (wrapper.value?.parentElement) {\n const height = wrapper.value.getBoundingClientRect().height\n wrapper.value.parentElement.style.height = `${height}px`\n observer.observe(wrapper.value)\n }\n })\n\n onUnmounted(() => {\n if (wrapper.value) {\n observer.unobserve(wrapper.value)\n }\n })\n\n return {\n wrapper,\n }\n },\n})\n</script>\n<style lang=\"css\">\n.x-change-height {\n --x-change-height-duration: 0.3s;\n overflow: hidden;\n transition: all var(--x-change-height-duration) ease-out;\n}\n</style>\n\n<docs lang=\"mdx\">\n## Examples\n\nThe ChangeHeight component automatically animates the height of its content when it changes size.\n\n### Basic usage:\n\n```vue\n<template>\n <ChangeHeight>\n <div>Content whose height will be animated</div>\n </ChangeHeight>\n</template>\n\n<script setup>\nimport ChangeHeight from '@empathyco/x-components/js/components/animations/change-height.vue'\n</script>\n```\n\n### Example with dynamic content:\n\n```vue\n<template>\n <div>\n <button @click=\"expanded = !expanded\">Toggle</button>\n <ChangeHeight>\n <div v-if=\"expanded\" style=\"height: 200px; background: #eee;\">Expanded content</div>\n <div v-else style=\"height: 50px; background: #ccc;\">Collapsed content</div>\n </ChangeHeight>\n </div>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport ChangeHeight from '@empathyco/x-components/js/components/animations/change-height.vue'\n\nconst expanded = ref(false)\n</script>\n```\n</docs>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_renderSlot"],"mappings":";;;;;AACO,MAAA,UAAA,GAAA,EAAA,KAAA,EAAM,iBAAA,EAAiB;AACrB,MAAA,UAAA,GAAA,EAAA,GAAA,EAAI,SAAA,EAAS;;AADpB,EAAA,OAAAA,SAAA,EAAA,EAAAC,kBAAA,CAIM,OAJN,UAAA,EAIM;AAAA,IAHJC,kBAAA;AAAA,MAEM,KAAA;AAAA,MAFN,UAAA;AAAA,MAEM;AAAA,QADJC,UAAA,CAAQ,IAAA,CAAA,MAAA,EAAA,SAAA;AAAA,OAAA;;;;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-height.vue2.js","sources":["../../../../src/components/animations/change-height.vue"],"sourcesContent":["<template>\n <div class=\"x-
|
|
1
|
+
{"version":3,"file":"change-height.vue2.js","sources":["../../../../src/components/animations/change-height.vue"],"sourcesContent":["<template>\n <div class=\"x-change-height\">\n <div ref=\"wrapper\">\n <slot />\n </div>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, onMounted, onUnmounted, ref } from 'vue'\n\nexport default defineComponent({\n setup() {\n const wrapper = ref<HTMLElement>()\n const observer = new ResizeObserver(entries => {\n for (const entry of entries) {\n if (entry.target.parentElement) {\n entry.target.parentElement.style.height = `${entry.contentRect.height}px`\n }\n }\n })\n\n onMounted(() => {\n if (wrapper.value?.parentElement) {\n const height = wrapper.value.getBoundingClientRect().height\n wrapper.value.parentElement.style.height = `${height}px`\n observer.observe(wrapper.value)\n }\n })\n\n onUnmounted(() => {\n if (wrapper.value) {\n observer.unobserve(wrapper.value)\n }\n })\n\n return {\n wrapper,\n }\n },\n})\n</script>\n<style lang=\"css\">\n.x-change-height {\n --x-change-height-duration: 0.3s;\n overflow: hidden;\n transition: all var(--x-change-height-duration) ease-out;\n}\n</style>\n\n<docs lang=\"mdx\">\n## Examples\n\nThe ChangeHeight component automatically animates the height of its content when it changes size.\n\n### Basic usage:\n\n```vue\n<template>\n <ChangeHeight>\n <div>Content whose height will be animated</div>\n </ChangeHeight>\n</template>\n\n<script setup>\nimport ChangeHeight from '@empathyco/x-components/js/components/animations/change-height.vue'\n</script>\n```\n\n### Example with dynamic content:\n\n```vue\n<template>\n <div>\n <button @click=\"expanded = !expanded\">Toggle</button>\n <ChangeHeight>\n <div v-if=\"expanded\" style=\"height: 200px; background: #eee;\">Expanded content</div>\n <div v-else style=\"height: 50px; background: #ccc;\">Collapsed content</div>\n </ChangeHeight>\n </div>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport ChangeHeight from '@empathyco/x-components/js/components/animations/change-height.vue'\n\nconst expanded = ref(false)\n</script>\n```\n</docs>\n"],"names":[],"mappings":";;AAWA,gBAAe,eAAe,CAAC;IAC7B,KAAK,GAAA;AACH,QAAA,MAAM,OAAM,GAAI,GAAG,EAAc;AACjC,QAAA,MAAM,QAAO,GAAI,IAAI,cAAc,CAAC,OAAM,IAAG;AAC3C,YAAA,KAAK,MAAM,KAAI,IAAK,OAAO,EAAE;AAC3B,gBAAA,IAAI,KAAK,CAAC,MAAM,CAAC,aAAa,EAAE;AAC9B,oBAAA,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,MAAK,GAAI,CAAA,EAAG,KAAK,CAAC,WAAW,CAAC,MAAM,IAAG;gBAC1E;YACF;AACF,QAAA,CAAC,CAAA;QAED,SAAS,CAAC,MAAI;AACZ,YAAA,IAAI,OAAO,CAAC,KAAK,EAAE,aAAa,EAAE;gBAChC,MAAM,MAAK,GAAI,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,CAAC,MAAK;AAC1D,gBAAA,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,MAAK,GAAI,CAAA,EAAG,MAAM,CAAA,EAAA,CAAG;AACvD,gBAAA,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAA;YAChC;AACF,QAAA,CAAC,CAAA;QAED,WAAW,CAAC,MAAI;AACd,YAAA,IAAI,OAAO,CAAC,KAAK,EAAE;AACjB,gBAAA,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,CAAA;YAClC;AACF,QAAA,CAAC,CAAA;QAED,OAAO;YACL,OAAO;SACT;IACF,CAAC;AACF,CAAA,CAAA;;;;"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import injectCss from '../../../tools/inject-css.js';
|
|
2
|
+
|
|
3
|
+
var css = ".x-change-height{--x-change-height-duration:0.3s;overflow:hidden;transition:all var(--x-change-height-duration) ease-out}";
|
|
4
|
+
injectCss(css);
|
|
5
|
+
|
|
6
|
+
export { css };
|
|
7
|
+
//# sourceMappingURL=change-height.vue3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change-height.vue3.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;"}
|
|
@@ -10,6 +10,7 @@ import './animations/animate-translate/animate-translate.style.css.js';
|
|
|
10
10
|
import './animations/animate-width.vue2.js';
|
|
11
11
|
import './animations/animate-width.vue3.js';
|
|
12
12
|
import './animations/change-height.vue2.js';
|
|
13
|
+
import './animations/change-height.vue3.js';
|
|
13
14
|
import './animations/collapse-height.vue2.js';
|
|
14
15
|
import './animations/collapse-height.vue3.js';
|
|
15
16
|
import './animations/collapse-width.vue2.js';
|
|
@@ -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\"\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
|
+
{"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;;;;"}
|
|
@@ -21,6 +21,7 @@ import '../animations/animate-translate/animate-translate.style.css.js';
|
|
|
21
21
|
import '../animations/animate-width.vue2.js';
|
|
22
22
|
import '../animations/animate-width.vue3.js';
|
|
23
23
|
import '../animations/change-height.vue2.js';
|
|
24
|
+
import '../animations/change-height.vue3.js';
|
|
24
25
|
import '../animations/collapse-height.vue2.js';
|
|
25
26
|
import '../animations/collapse-height.vue3.js';
|
|
26
27
|
import '../animations/collapse-width.vue2.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-modal.vue2.js","sources":["../../../../src/components/modals/base-modal.vue"],"sourcesContent":["<template>\n <div v-show=\"isWaitingForLeave || open\" ref=\"modalRef\" class=\"x-modal\" data-test=\"modal\">\n <component\n :is=\"animation\"\n @before-leave=\"isWaitingForLeave = true\"\n @after-leave=\"isWaitingForLeave = false\"\n >\n <div\n v-if=\"open\"\n ref=\"modalContentRef\"\n class=\"x-modal__content\"\n data-test=\"modal-content\"\n role=\"dialog\"\n :class=\"contentClass\"\n aria-label=\"Base modal content\"\n >\n <!-- @slot (Required) Modal container content -->\n <slot />\n </div>\n </component>\n <component :is=\"overlayAnimation\">\n <div\n v-if=\"open\"\n class=\"x-modal__overlay\"\n :class=\"overlayClass\"\n data-test=\"modal-overlay\"\n @click=\"emitOverlayClicked\"\n @keydown=\"emitOverlayClicked\"\n />\n </component>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { useDebounce } from '../../composables'\nimport { AnimationProp } from '../../types'\nimport { FOCUSABLE_SELECTORS, getTargetElement } from '../../utils'\nimport { Fade, NoAnimation } from '../animations'\n\n/**\n * Base component with no XPlugin dependencies that serves as a utility for constructing more\n * complex modals.\n *\n * @public\n */\nexport default defineComponent({\n name: 'BaseModal',\n props: {\n /** Determines if the modal is open or not. */\n open: {\n type: Boolean,\n required: true,\n },\n /**\n * Determines if the focused element changes to one inside the modal when it opens. Either the\n * first element with a positive tabindex or just the first focusable element.\n */\n focusOnOpen: {\n type: Boolean,\n default: true,\n },\n /**\n * The reference selector of a DOM element to use as reference to position the modal.\n * This selector can be an ID or a class, if it is a class, it will use the first\n * element that matches.\n */\n referenceSelector: String,\n /** Animation to use for opening/closing the modal.This animation only affects the content. */\n animation: {\n type: AnimationProp,\n default: () => NoAnimation,\n },\n /**\n * Animation to use for the overlay (backdrop) part of the modal. By default, it uses\n * a fade transition.\n */\n overlayAnimation: {\n type: AnimationProp,\n default: () => Fade,\n },\n /** Class inherited by content element. */\n contentClass: String,\n /** Class inherited by overlay element. */\n overlayClass: String,\n },\n emits: ['click:overlay', 'focusin:body'],\n setup(props, { emit }) {\n /** Reference to the modal element in the DOM. */\n const modalRef = ref<HTMLDivElement>()\n /** Reference to the modal content element in the DOM. */\n const modalContentRef = ref<HTMLDivElement>()\n\n /** The previous value of the body overflow style. */\n const previousBodyOverflow = ref('')\n /** The previous value of the HTML element overflow style. */\n const previousHTMLOverflow = ref('')\n /** Boolean to delay the leave animation until it has completed. */\n const isWaitingForLeave = ref(false)\n /** The reference element to use to find the modal's position. */\n let referenceElement: HTMLElement | undefined\n\n /** Disables the scroll of both the body and the window. */\n function disableScroll() {\n previousBodyOverflow.value = document.body.style.overflow\n previousHTMLOverflow.value = document.documentElement.style.overflow\n document.body.style.overflow = document.documentElement.style.overflow = 'hidden'\n }\n\n /** Restores the scroll of both the body and the window. */\n function enableScroll() {\n document.body.style.overflow = previousBodyOverflow.value\n document.documentElement.style.overflow = previousHTMLOverflow.value\n }\n\n /**\n * Emits the `click:overlay` event if the click has been triggered in the overlay layer.\n *\n * @param event - The click event.\n */\n function emitOverlayClicked(event: Event) {\n // eslint-disable-next-line vue/custom-event-name-casing\n emit('click:overlay', event)\n }\n\n /**\n * Emits the `focusin:body` event if a focus event has been triggered outside the modal.\n *\n * @param event - The focusin event.\n */\n function emitFocusInBody(event: FocusEvent) {\n if (!modalContentRef.value?.contains(getTargetElement(event))) {\n // eslint-disable-next-line vue/custom-event-name-casing\n emit('focusin:body', event)\n }\n }\n\n /**\n * Adds listeners to the body element ot detect if the modal should be closed.\n *\n * @remarks TODO find a better solution and remove the timeout\n * To avoid emit the focusin on opening X that provokes closing it immediately.\n * This is because this event was emitted after the open of main modal when the user clicks\n * on the customer website search box (focus event). This way we avoid add the listener before\n * the open and the avoid the event that provokes the close.\n */\n function addBodyListeners() {\n setTimeout(() => {\n document.body.addEventListener('focusin', emitFocusInBody)\n })\n }\n\n /** Removes the body listeners. */\n function removeBodyListeners() {\n document.body.removeEventListener('focusin', emitFocusInBody)\n }\n\n /**\n * Sets the focused element to the first element either the first element with a positive\n * tabindex or, if there isn't any, the first focusable element inside the modal.\n */\n function setFocus() {\n const candidates: HTMLElement[] = Array.from(\n modalContentRef.value?.querySelectorAll(FOCUSABLE_SELECTORS) ?? [],\n )\n const element = candidates.find(element => element.tabIndex) ?? candidates[0]\n element?.focus()\n }\n\n /**\n * Syncs the body to the open state of the modal, adding or removing styles and listeners.\n *\n * @remarks nextTick() to wait for `modalContentRef` to be updated to look for focusable\n * candidates inside.\n *\n * @param isOpen - True when the modal is opened.\n */\n async function syncBody(isOpen: boolean) {\n if (isOpen) {\n disableScroll()\n addBodyListeners()\n if (props.focusOnOpen) {\n await nextTick()\n setFocus()\n }\n } else {\n enableScroll()\n removeBodyListeners()\n }\n }\n\n /**\n * Updates the position of the modal setting the top of the element depending\n * on the selector. The modal will be placed under this selector.\n */\n const debouncedUpdatePosition = useDebounce(\n () => {\n const { height, y } = referenceElement?.getBoundingClientRect() ?? { height: 0, y: 0 }\n modalRef.value!.style.top = `${height + y}px`\n modalRef.value!.style.bottom = '0'\n modalRef.value!.style.height = 'auto'\n },\n 100,\n { leading: true },\n )\n\n let resizeObserver: ResizeObserver\n\n onMounted(() => {\n watch(() => props.open, syncBody)\n if (props.open) {\n syncBody(true)\n }\n\n resizeObserver = new ResizeObserver(debouncedUpdatePosition)\n\n watch(\n () => props.referenceSelector,\n () => {\n resizeObserver.disconnect()\n\n if (props.referenceSelector) {\n const element = document.querySelector(props.referenceSelector) as HTMLElement\n if (element) {\n referenceElement = element\n resizeObserver.observe(element)\n }\n } else {\n referenceElement = undefined\n debouncedUpdatePosition()\n }\n },\n { immediate: true },\n )\n })\n\n onBeforeUnmount(() => {\n if (props.open) {\n removeBodyListeners()\n enableScroll()\n }\n resizeObserver.disconnect()\n })\n\n return {\n emitOverlayClicked,\n isWaitingForLeave,\n modalContentRef,\n modalRef,\n }\n },\n})\n</script>\n\n<style lang=\"css\" scoped>\n.x-modal {\n position: fixed;\n top: 0;\n left: 0;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n width: 100%;\n height: 100%;\n z-index: 1;\n}\n\n.x-modal__content {\n display: flex;\n flex-flow: column nowrap;\n z-index: 1;\n}\n\n.x-modal__overlay {\n width: 100%;\n height: 100%;\n position: absolute;\n background-color: rgb(0, 0, 0);\n opacity: 0.3;\n}\n</style>\n\n<docs lang=\"mdx\">\n## Examples\n\nThe `BaseModal` is a simple component that serves to create complex modals. Its open state has to be\npassed via prop. There is a prop, `referenceSelector`, used to place the modal under some element\ninstead of set the top of the element directly. It also accepts an animation to use for opening &\nclosing.\n\nIt emits a `click:overlay` event when any part out of the content is clicked, but only if the modal\nis open.\n\n```vue\n<template>\n <div>\n <button @click=\"open = true\">Open modal</button>\n <BaseModal\n :animation=\"fadeAndSlide\"\n :open=\"open\"\n @click:overlay=\"open = false\"\n referenceSelector=\".header\"\n >\n <h1>Hello</h1>\n <p>The modal is working</p>\n <button @click=\"open = false\">Close modal</button>\n </BaseModal>\n </div>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport { BaseModal, FadeAndSlide } from '@empathyco/x-components'\nconst open = ref(false)\nconst fadeAndSlide = FadeAndSlide\n</script>\n```\n\n### Customized usage\n\n#### Customizing the content with classes\n\nThe `contentClass` prop can be used to add classes to the modal content.\n\n```vue\n<template>\n <div>\n <button @click=\"open = true\">Open modal</button>\n <BaseModal\n :animation=\"fadeAndSlide\"\n :open=\"open\"\n @click:overlay=\"open = false\"\n referenceSelector=\".header\"\n contentClass=\"x-bg-neutral-75\"\n >\n <h1>Hello</h1>\n <p>The modal is working</p>\n <button @click=\"open = false\">Close modal</button>\n </BaseModal>\n </div>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport { BaseModal, FadeAndSlide } from '@empathyco/x-components'\nconst open = ref(false)\nconst fadeAndSlide = FadeAndSlide\n</script>\n```\n\n#### Customizing the overlay with classes\n\nThe `overlayClass` prop can be used to add classes to the modal overlay.\n\n```vue\n<template>\n <div>\n <button @click=\"open = true\">Open modal</button>\n <BaseModal\n :animation=\"fadeAndSlide\"\n :open=\"open\"\n @click:overlay=\"open = false\"\n referenceSelector=\".header\"\n overlayClass=\"x-bg-neutral-75\"\n >\n <h1>Hello</h1>\n <p>The modal is working</p>\n <button @click=\"open = false\">Close modal</button>\n </BaseModal>\n </div>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport { BaseModal, FadeAndSlide } from '@empathyco/x-components'\nconst open = ref(false)\nconst fadeAndSlide = FadeAndSlide\n</script>\n```\n\n## Vue Events\n\nA list of events that the component will emit:\n\n- `click:overlay`: the event is emitted after the user clicks any part out of the content but only\n if the modal is open. The event payload is the mouse event that triggers it.\n- `focusin:body`: the event is emitted after the user focus in any part out of the content but only\n if the modal is open. The event payload is the focus event that triggers it.\n</docs>\n"],"names":["NoAnimation"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-modal.vue2.js","sources":["../../../../src/components/modals/base-modal.vue"],"sourcesContent":["<template>\n <div v-show=\"isWaitingForLeave || open\" ref=\"modalRef\" class=\"x-modal\" data-test=\"modal\">\n <component\n :is=\"animation\"\n @before-leave=\"isWaitingForLeave = true\"\n @after-leave=\"isWaitingForLeave = false\"\n >\n <div\n v-if=\"open\"\n ref=\"modalContentRef\"\n class=\"x-modal__content\"\n data-test=\"modal-content\"\n role=\"dialog\"\n :class=\"contentClass\"\n aria-label=\"Base modal content\"\n >\n <!-- @slot (Required) Modal container content -->\n <slot />\n </div>\n </component>\n <component :is=\"overlayAnimation\">\n <div\n v-if=\"open\"\n class=\"x-modal__overlay\"\n :class=\"overlayClass\"\n data-test=\"modal-overlay\"\n @click=\"emitOverlayClicked\"\n @keydown=\"emitOverlayClicked\"\n />\n </component>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { useDebounce } from '../../composables'\nimport { AnimationProp } from '../../types'\nimport { FOCUSABLE_SELECTORS, getTargetElement } from '../../utils'\nimport { Fade, NoAnimation } from '../animations'\n\n/**\n * Base component with no XPlugin dependencies that serves as a utility for constructing more\n * complex modals.\n *\n * @public\n */\nexport default defineComponent({\n name: 'BaseModal',\n props: {\n /** Determines if the modal is open or not. */\n open: {\n type: Boolean,\n required: true,\n },\n /**\n * Determines if the focused element changes to one inside the modal when it opens. Either the\n * first element with a positive tabindex or just the first focusable element.\n */\n focusOnOpen: {\n type: Boolean,\n default: true,\n },\n /**\n * The reference selector of a DOM element to use as reference to position the modal.\n * This selector can be an ID or a class, if it is a class, it will use the first\n * element that matches.\n */\n referenceSelector: String,\n /** Animation to use for opening/closing the modal.This animation only affects the content. */\n animation: {\n type: AnimationProp,\n default: () => NoAnimation,\n },\n /**\n * Animation to use for the overlay (backdrop) part of the modal. By default, it uses\n * a fade transition.\n */\n overlayAnimation: {\n type: AnimationProp,\n default: () => Fade,\n },\n /** Class inherited by content element. */\n contentClass: String,\n /** Class inherited by overlay element. */\n overlayClass: String,\n },\n emits: ['click:overlay', 'focusin:body'],\n setup(props, { emit }) {\n /** Reference to the modal element in the DOM. */\n const modalRef = ref<HTMLDivElement>()\n /** Reference to the modal content element in the DOM. */\n const modalContentRef = ref<HTMLDivElement>()\n\n /** The previous value of the body overflow style. */\n const previousBodyOverflow = ref('')\n /** The previous value of the HTML element overflow style. */\n const previousHTMLOverflow = ref('')\n /** Boolean to delay the leave animation until it has completed. */\n const isWaitingForLeave = ref(false)\n /** The reference element to use to find the modal's position. */\n let referenceElement: HTMLElement | undefined\n\n /** Disables the scroll of both the body and the window. */\n function disableScroll() {\n previousBodyOverflow.value = document.body.style.overflow\n previousHTMLOverflow.value = document.documentElement.style.overflow\n document.body.style.overflow = document.documentElement.style.overflow = 'hidden'\n }\n\n /** Restores the scroll of both the body and the window. */\n function enableScroll() {\n document.body.style.overflow = previousBodyOverflow.value\n document.documentElement.style.overflow = previousHTMLOverflow.value\n }\n\n /**\n * Emits the `click:overlay` event if the click has been triggered in the overlay layer.\n *\n * @param event - The click event.\n */\n function emitOverlayClicked(event: Event) {\n // eslint-disable-next-line vue/custom-event-name-casing\n emit('click:overlay', event)\n }\n\n /**\n * Emits the `focusin:body` event if a focus event has been triggered outside the modal.\n *\n * @param event - The focusin event.\n */\n function emitFocusInBody(event: FocusEvent) {\n if (!modalContentRef.value?.contains(getTargetElement(event))) {\n // eslint-disable-next-line vue/custom-event-name-casing\n emit('focusin:body', event)\n }\n }\n\n /**\n * Adds listeners to the body element ot detect if the modal should be closed.\n *\n * @remarks TODO find a better solution and remove the timeout\n * To avoid emit the focusin on opening X that provokes closing it immediately.\n * This is because this event was emitted after the open of main modal when the user clicks\n * on the customer website search box (focus event). This way we avoid add the listener before\n * the open and the avoid the event that provokes the close.\n */\n function addBodyListeners() {\n setTimeout(() => {\n document.body.addEventListener('focusin', emitFocusInBody)\n })\n }\n\n /** Removes the body listeners. */\n function removeBodyListeners() {\n document.body.removeEventListener('focusin', emitFocusInBody)\n }\n\n /**\n * Sets the focused element to the first element either the first element with a positive\n * tabindex or, if there isn't any, the first focusable element inside the modal.\n */\n function setFocus() {\n const candidates: HTMLElement[] = Array.from(\n modalContentRef.value?.querySelectorAll(FOCUSABLE_SELECTORS) ?? [],\n )\n const element = candidates.find(element => element.tabIndex) ?? candidates[0]\n element?.focus()\n }\n\n /**\n * Syncs the body to the open state of the modal, adding or removing styles and listeners.\n *\n * @remarks nextTick() to wait for `modalContentRef` to be updated to look for focusable\n * candidates inside.\n *\n * @param isOpen - True when the modal is opened.\n */\n async function syncBody(isOpen: boolean) {\n if (isOpen) {\n disableScroll()\n addBodyListeners()\n if (props.focusOnOpen) {\n await nextTick()\n setFocus()\n }\n } else {\n enableScroll()\n removeBodyListeners()\n }\n }\n\n /**\n * Updates the position of the modal setting the top of the element depending\n * on the selector. The modal will be placed under this selector.\n */\n const debouncedUpdatePosition = useDebounce(\n () => {\n const { height, y } = referenceElement?.getBoundingClientRect() ?? { height: 0, y: 0 }\n modalRef.value!.style.top = `${height + y}px`\n modalRef.value!.style.bottom = '0'\n modalRef.value!.style.height = 'auto'\n },\n 100,\n { leading: true },\n )\n\n let resizeObserver: ResizeObserver\n\n onMounted(() => {\n watch(() => props.open, syncBody)\n if (props.open) {\n syncBody(true)\n }\n\n resizeObserver = new ResizeObserver(debouncedUpdatePosition)\n\n watch(\n () => props.referenceSelector,\n () => {\n resizeObserver.disconnect()\n\n if (props.referenceSelector) {\n const element = document.querySelector(props.referenceSelector) as HTMLElement\n if (element) {\n referenceElement = element\n resizeObserver.observe(element)\n }\n } else {\n referenceElement = undefined\n debouncedUpdatePosition()\n }\n },\n { immediate: true },\n )\n })\n\n onBeforeUnmount(() => {\n if (props.open) {\n removeBodyListeners()\n enableScroll()\n }\n resizeObserver.disconnect()\n })\n\n return {\n emitOverlayClicked,\n isWaitingForLeave,\n modalContentRef,\n modalRef,\n }\n },\n})\n</script>\n\n<style lang=\"css\" scoped>\n.x-modal {\n position: fixed;\n top: 0;\n left: 0;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n width: 100%;\n height: 100%;\n z-index: 1;\n}\n\n.x-modal__content {\n display: flex;\n flex-flow: column nowrap;\n z-index: 1;\n}\n\n.x-modal__overlay {\n width: 100%;\n height: 100%;\n position: absolute;\n background-color: rgb(0, 0, 0);\n opacity: 0.3;\n}\n</style>\n\n<docs lang=\"mdx\">\n## Examples\n\nThe `BaseModal` is a simple component that serves to create complex modals. Its open state has to be\npassed via prop. There is a prop, `referenceSelector`, used to place the modal under some element\ninstead of set the top of the element directly. It also accepts an animation to use for opening &\nclosing.\n\nIt emits a `click:overlay` event when any part out of the content is clicked, but only if the modal\nis open.\n\n```vue\n<template>\n <div>\n <button @click=\"open = true\">Open modal</button>\n <BaseModal\n :animation=\"fadeAndSlide\"\n :open=\"open\"\n @click:overlay=\"open = false\"\n referenceSelector=\".header\"\n >\n <h1>Hello</h1>\n <p>The modal is working</p>\n <button @click=\"open = false\">Close modal</button>\n </BaseModal>\n </div>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport { BaseModal, FadeAndSlide } from '@empathyco/x-components'\nconst open = ref(false)\nconst fadeAndSlide = FadeAndSlide\n</script>\n```\n\n### Customized usage\n\n#### Customizing the content with classes\n\nThe `contentClass` prop can be used to add classes to the modal content.\n\n```vue\n<template>\n <div>\n <button @click=\"open = true\">Open modal</button>\n <BaseModal\n :animation=\"fadeAndSlide\"\n :open=\"open\"\n @click:overlay=\"open = false\"\n referenceSelector=\".header\"\n contentClass=\"x-bg-neutral-75\"\n >\n <h1>Hello</h1>\n <p>The modal is working</p>\n <button @click=\"open = false\">Close modal</button>\n </BaseModal>\n </div>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport { BaseModal, FadeAndSlide } from '@empathyco/x-components'\nconst open = ref(false)\nconst fadeAndSlide = FadeAndSlide\n</script>\n```\n\n#### Customizing the overlay with classes\n\nThe `overlayClass` prop can be used to add classes to the modal overlay.\n\n```vue\n<template>\n <div>\n <button @click=\"open = true\">Open modal</button>\n <BaseModal\n :animation=\"fadeAndSlide\"\n :open=\"open\"\n @click:overlay=\"open = false\"\n referenceSelector=\".header\"\n overlayClass=\"x-bg-neutral-75\"\n >\n <h1>Hello</h1>\n <p>The modal is working</p>\n <button @click=\"open = false\">Close modal</button>\n </BaseModal>\n </div>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport { BaseModal, FadeAndSlide } from '@empathyco/x-components'\nconst open = ref(false)\nconst fadeAndSlide = FadeAndSlide\n</script>\n```\n\n## Vue Events\n\nA list of events that the component will emit:\n\n- `click:overlay`: the event is emitted after the user clicks any part out of the content but only\n if the modal is open. The event payload is the mouse event that triggers it.\n- `focusin:body`: the event is emitted after the user focus in any part out of the content but only\n if the modal is open. The event payload is the focus event that triggers it.\n</docs>\n"],"names":["NoAnimation"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA;;;;;AAKE;AACF,gBAAe,eAAe,CAAC;AAC7B,IAAA,IAAI,EAAE,WAAW;AACjB,IAAA,KAAK,EAAE;;AAEL,QAAA,IAAI,EAAE;AACJ,YAAA,IAAI,EAAE,OAAO;AACb,YAAA,QAAQ,EAAE,IAAI;AACf,SAAA;AACD;;;AAGE;AACF,QAAA,WAAW,EAAE;AACX,YAAA,IAAI,EAAE,OAAO;AACb,YAAA,OAAO,EAAE,IAAI;AACd,SAAA;AACD;;;;AAIE;AACF,QAAA,iBAAiB,EAAE,MAAM;;AAEzB,QAAA,SAAS,EAAE;AACT,YAAA,IAAI,EAAE,aAAa;AACnB,YAAA,OAAO,EAAE,MAAMA,WAAW;AAC3B,SAAA;AACD;;;AAGE;AACF,QAAA,gBAAgB,EAAE;AAChB,YAAA,IAAI,EAAE,aAAa;AACnB,YAAA,OAAO,EAAE,MAAM,IAAI;AACpB,SAAA;;AAED,QAAA,YAAY,EAAE,MAAM;;AAEpB,QAAA,YAAY,EAAE,MAAM;AACrB,KAAA;AACD,IAAA,KAAK,EAAE,CAAC,eAAe,EAAE,cAAc,CAAC;AACxC,IAAA,KAAK,CAAC,KAAK,EAAE,EAAE,IAAG,EAAG,EAAA;;AAEnB,QAAA,MAAM,QAAO,GAAI,GAAG,EAAiB;;AAErC,QAAA,MAAM,eAAc,GAAI,GAAG,EAAiB;;AAG5C,QAAA,MAAM,oBAAmB,GAAI,GAAG,CAAC,EAAE,CAAA;;AAEnC,QAAA,MAAM,oBAAmB,GAAI,GAAG,CAAC,EAAE,CAAA;;AAEnC,QAAA,MAAM,iBAAgB,GAAI,GAAG,CAAC,KAAK,CAAA;;AAEnC,QAAA,IAAI,gBAAwC;;AAG5C,QAAA,SAAS,aAAa,GAAA;YACpB,oBAAoB,CAAC,KAAI,GAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAO;YACxD,oBAAoB,CAAC,KAAI,GAAI,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,QAAO;AACnE,YAAA,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAO,GAAI,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,QAAO,GAAI,QAAO;QAClF;;AAGA,QAAA,SAAS,YAAY,GAAA;YACnB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,oBAAoB,CAAC,KAAI;YACxD,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,QAAO,GAAI,oBAAoB,CAAC,KAAI;QACrE;AAEA;;;;AAIE;QACF,SAAS,kBAAkB,CAAC,KAAY,EAAA;;AAEtC,YAAA,IAAI,CAAC,eAAe,EAAE,KAAK,CAAA;QAC7B;AAEA;;;;AAIE;QACF,SAAS,eAAe,CAAC,KAAiB,EAAA;AACxC,YAAA,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,EAAE;;AAE7D,gBAAA,IAAI,CAAC,cAAc,EAAE,KAAK,CAAA;YAC5B;QACF;AAEA;;;;;;;;AAQE;AACF,QAAA,SAAS,gBAAgB,GAAA;YACvB,UAAU,CAAC,MAAI;gBACb,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,eAAe,CAAA;AAC3D,YAAA,CAAC,CAAA;QACH;;AAGA,QAAA,SAAS,mBAAmB,GAAA;YAC1B,QAAQ,CAAC,IAAI,CAAC,mBAAmB,CAAC,SAAS,EAAE,eAAe,CAAA;QAC9D;AAEA;;;AAGE;AACF,QAAA,SAAS,QAAQ,GAAA;AACf,YAAA,MAAM,UAAU,GAAkB,KAAK,CAAC,IAAI,CAC1C,eAAe,CAAC,KAAK,EAAE,gBAAgB,CAAC,mBAAmB,CAAA,IAAK,EAAE,CACpE;AACA,YAAA,MAAM,OAAM,GAAI,UAAU,CAAC,IAAI,CAAC,OAAM,IAAK,OAAO,CAAC,QAAQ,CAAA,IAAK,UAAU,CAAC,CAAC,CAAA;YAC5E,OAAO,EAAE,KAAK,EAAC;QACjB;AAEA;;;;;;;AAOE;QACF,eAAe,QAAQ,CAAC,MAAe,EAAA;YACrC,IAAI,MAAM,EAAE;AACV,gBAAA,aAAa,EAAC;AACd,gBAAA,gBAAgB,EAAC;AACjB,gBAAA,IAAI,KAAK,CAAC,WAAW,EAAE;oBACrB,MAAM,QAAQ,EAAC;AACf,oBAAA,QAAQ,EAAC;gBACX;YACF;iBAAO;AACL,gBAAA,YAAY,EAAC;AACb,gBAAA,mBAAmB,EAAC;YACtB;QACF;AAEA;;;AAGE;AACF,QAAA,MAAM,0BAA0B,WAAW,CACzC,MAAI;YACF,MAAM,EAAE,MAAM,EAAE,CAAA,KAAM,gBAAgB,EAAE,qBAAqB,EAAC,IAAK,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAA,EAAE;AACrF,YAAA,QAAQ,CAAC,KAAM,CAAC,KAAK,CAAC,GAAE,GAAI,CAAA,EAAG,MAAK,GAAI,CAAC,CAAA,EAAA,CAAG;YAC5C,QAAQ,CAAC,KAAM,CAAC,KAAK,CAAC,MAAK,GAAI,GAAE;YACjC,QAAQ,CAAC,KAAM,CAAC,KAAK,CAAC,MAAK,GAAI,MAAK;QACtC,CAAC,EACD,GAAG,EACH,EAAE,OAAO,EAAE,IAAG,EAAG,CACnB;AAEA,QAAA,IAAI,cAA6B;QAEjC,SAAS,CAAC,MAAI;YACZ,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAA;AAChC,YAAA,IAAI,KAAK,CAAC,IAAI,EAAE;gBACd,QAAQ,CAAC,IAAI,CAAA;YACf;AAEA,YAAA,iBAAiB,IAAI,cAAc,CAAC,uBAAuB,CAAA;YAE3D,KAAK,CACH,MAAM,KAAK,CAAC,iBAAiB,EAC7B,MAAI;gBACF,cAAc,CAAC,UAAU,EAAC;AAE1B,gBAAA,IAAI,KAAK,CAAC,iBAAiB,EAAE;oBAC3B,MAAM,OAAM,GAAI,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,iBAAiB,CAAe;oBAC7E,IAAI,OAAO,EAAE;wBACX,gBAAe,GAAI,OAAM;AACzB,wBAAA,cAAc,CAAC,OAAO,CAAC,OAAO,CAAA;oBAChC;gBACF;qBAAO;oBACL,gBAAe,GAAI,SAAQ;AAC3B,oBAAA,uBAAuB,EAAC;gBAC1B;AACF,YAAA,CAAC,EACD,EAAE,SAAS,EAAE,MAAM,CACrB;AACF,QAAA,CAAC,CAAA;QAED,eAAe,CAAC,MAAI;AAClB,YAAA,IAAI,KAAK,CAAC,IAAI,EAAE;AACd,gBAAA,mBAAmB,EAAC;AACpB,gBAAA,YAAY,EAAC;YACf;YACA,cAAc,CAAC,UAAU,EAAC;AAC5B,QAAA,CAAC,CAAA;QAED,OAAO;YACL,kBAAkB;YAClB,iBAAiB;YACjB,eAAe;YACf,QAAQ;SACV;IACF,CAAC;AACF,CAAA,CAAA;;;;"}
|
|
@@ -6,6 +6,7 @@ import '../animations/animate-translate/animate-translate.style.css.js';
|
|
|
6
6
|
import '../animations/animate-width.vue2.js';
|
|
7
7
|
import '../animations/animate-width.vue3.js';
|
|
8
8
|
import '../animations/change-height.vue2.js';
|
|
9
|
+
import '../animations/change-height.vue3.js';
|
|
9
10
|
import '../animations/collapse-height.vue2.js';
|
|
10
11
|
import '../animations/collapse-height.vue3.js';
|
|
11
12
|
import '../animations/collapse-width.vue2.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-header-toggle-panel.vue2.js","sources":["../../../../src/components/panels/base-header-toggle-panel.vue"],"sourcesContent":["<template>\n <div class=\"x-header-toggle-panel\">\n <!-- @slot (Required) Slot that is be used for replacing the whole header. -->\n <slot name=\"header\" v-bind=\"{ toggleOpen, open }\">\n <!-- header-toggle-panel__header -->\n <button\n class=\"x-header-toggle-panel__header\"\n :class=\"headerClass\"\n data-test=\"toggle-panel-header\"\n @click=\"toggleOpen\"\n >\n <!-- @slot (Required) Slot used to just pass the content. -->\n <slot name=\"header-content\" v-bind=\"{ open }\"></slot>\n </button>\n </slot>\n\n <BaseTogglePanel :open=\"open\" :animation=\"animation\">\n <!-- @slot (Required) Default content. -->\n <slot />\n </BaseTogglePanel>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, ref } from 'vue'\nimport { AnimationProp } from '../../types'\nimport { NoAnimation } from '../animations'\nimport BaseTogglePanel from './base-toggle-panel.vue'\n\n/**\n * Toggle panel which uses the base toggle panel, adds a header and manage the\n * open / close state of the panel.\n *\n * @public\n */\nexport default defineComponent({\n name: 'BaseHeaderTogglePanel',\n components: { BaseTogglePanel },\n props: {\n /** Animation component that will be used to animate the base-toggle-panel. */\n animation: {\n type: AnimationProp,\n default: () => NoAnimation,\n },\n /** Handles if the panel is open by default. */\n startCollapsed: Boolean,\n /** Class inherited by content element. */\n headerClass: String,\n },\n setup(props, { emit }) {\n /**\n * Handles if the base panel is open or closed.\n *\n * @internal\n */\n const open = ref(!props.startCollapsed)\n\n /**\n * Emits open status event.\n *\n * @internal\n */\n const emitOpenStatusEvent = () => {\n emit(open.value ? 'open' : 'close')\n }\n\n /**\n * Toggles the open property.\n *\n * @internal\n */\n const toggleOpen = () => {\n open.value = !open.value\n emitOpenStatusEvent()\n }\n\n return {\n open,\n toggleOpen,\n }\n },\n})\n</script>\n\n<style lang=\"css\" scoped>\n.x-header-toggle-panel__header {\n cursor: pointer;\n}\n</style>\n\n<docs lang=\"mdx\">\n## Events\n\nA list of events that the component will emit:\n\n- `open`: emitted after the user clicks the element and opens it.\n- `close`: emitted after the user clicks the element and closes it.\n\n## Examples\n\nToggle panel which uses the base toggle panel, adds a header and manages the open/close state of the panel.\n\n### Basic usage\n\n```vue\n<template>\n <BaseHeaderTogglePanel :animation=\"animation\" :start-collapsed=\"false\">\n <template #header-content=\"{ open }\">\n <p>Header, open: {{ open ? 'close' : 'open' }}</p>\n </template>\n <template #default>\n <p>Default content</p>\n </template>\n </BaseHeaderTogglePanel>\n</template>\n\n<script setup>\nimport { BaseHeaderTogglePanel } from '@empathyco/x-components'\nimport { CollapseHeight } from '@empathyco/x-components/animations'\nconst animation = CollapseHeight\n</script>\n```\n\n### Custom header\n\n```vue\n<template>\n <BaseHeaderTogglePanel :animation=\"animation\" :start-collapsed=\"true\">\n <template #header=\"{ toggleOpen, open }\">\n <p>Header, open: {{ open ? 'close' : 'open' }}</p>\n <button @click=\"toggleOpen\">Toggle</button>\n </template>\n <template #default>\n <p>Default content</p>\n </template>\n </BaseHeaderTogglePanel>\n</template>\n\n<script setup>\nimport { BaseHeaderTogglePanel } from '@empathyco/x-components'\nimport { CollapseHeight } from '@empathyco/x-components/animations'\nconst animation = CollapseHeight\n</script>\n```\n\n### Customizing default header with classes\n\nThe `headerClass` prop can be used to add classes to the header of the toggle panel.\n\n```vue\n<template>\n <BaseHeaderTogglePanel headerClass=\"custom-class\" :animation=\"animation\" :start-collapsed=\"false\">\n <template #header-content=\"{ open }\">\n <p>Header, open: {{ open ? 'close' : 'open' }}</p>\n </template>\n <template #default>\n <p>Default content</p>\n </template>\n </BaseHeaderTogglePanel>\n</template>\n\n<script setup>\nimport { BaseHeaderTogglePanel } from '@empathyco/x-components'\nimport { CollapseHeight } from '@empathyco/x-components/animations'\nconst animation = CollapseHeight\n</script>\n```\n</docs>\n"],"names":["NoAnimation"],"mappings":"
|
|
1
|
+
{"version":3,"file":"base-header-toggle-panel.vue2.js","sources":["../../../../src/components/panels/base-header-toggle-panel.vue"],"sourcesContent":["<template>\n <div class=\"x-header-toggle-panel\">\n <!-- @slot (Required) Slot that is be used for replacing the whole header. -->\n <slot name=\"header\" v-bind=\"{ toggleOpen, open }\">\n <!-- header-toggle-panel__header -->\n <button\n class=\"x-header-toggle-panel__header\"\n :class=\"headerClass\"\n data-test=\"toggle-panel-header\"\n @click=\"toggleOpen\"\n >\n <!-- @slot (Required) Slot used to just pass the content. -->\n <slot name=\"header-content\" v-bind=\"{ open }\"></slot>\n </button>\n </slot>\n\n <BaseTogglePanel :open=\"open\" :animation=\"animation\">\n <!-- @slot (Required) Default content. -->\n <slot />\n </BaseTogglePanel>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, ref } from 'vue'\nimport { AnimationProp } from '../../types'\nimport { NoAnimation } from '../animations'\nimport BaseTogglePanel from './base-toggle-panel.vue'\n\n/**\n * Toggle panel which uses the base toggle panel, adds a header and manage the\n * open / close state of the panel.\n *\n * @public\n */\nexport default defineComponent({\n name: 'BaseHeaderTogglePanel',\n components: { BaseTogglePanel },\n props: {\n /** Animation component that will be used to animate the base-toggle-panel. */\n animation: {\n type: AnimationProp,\n default: () => NoAnimation,\n },\n /** Handles if the panel is open by default. */\n startCollapsed: Boolean,\n /** Class inherited by content element. */\n headerClass: String,\n },\n setup(props, { emit }) {\n /**\n * Handles if the base panel is open or closed.\n *\n * @internal\n */\n const open = ref(!props.startCollapsed)\n\n /**\n * Emits open status event.\n *\n * @internal\n */\n const emitOpenStatusEvent = () => {\n emit(open.value ? 'open' : 'close')\n }\n\n /**\n * Toggles the open property.\n *\n * @internal\n */\n const toggleOpen = () => {\n open.value = !open.value\n emitOpenStatusEvent()\n }\n\n return {\n open,\n toggleOpen,\n }\n },\n})\n</script>\n\n<style lang=\"css\" scoped>\n.x-header-toggle-panel__header {\n cursor: pointer;\n}\n</style>\n\n<docs lang=\"mdx\">\n## Events\n\nA list of events that the component will emit:\n\n- `open`: emitted after the user clicks the element and opens it.\n- `close`: emitted after the user clicks the element and closes it.\n\n## Examples\n\nToggle panel which uses the base toggle panel, adds a header and manages the open/close state of the panel.\n\n### Basic usage\n\n```vue\n<template>\n <BaseHeaderTogglePanel :animation=\"animation\" :start-collapsed=\"false\">\n <template #header-content=\"{ open }\">\n <p>Header, open: {{ open ? 'close' : 'open' }}</p>\n </template>\n <template #default>\n <p>Default content</p>\n </template>\n </BaseHeaderTogglePanel>\n</template>\n\n<script setup>\nimport { BaseHeaderTogglePanel } from '@empathyco/x-components'\nimport { CollapseHeight } from '@empathyco/x-components/animations'\nconst animation = CollapseHeight\n</script>\n```\n\n### Custom header\n\n```vue\n<template>\n <BaseHeaderTogglePanel :animation=\"animation\" :start-collapsed=\"true\">\n <template #header=\"{ toggleOpen, open }\">\n <p>Header, open: {{ open ? 'close' : 'open' }}</p>\n <button @click=\"toggleOpen\">Toggle</button>\n </template>\n <template #default>\n <p>Default content</p>\n </template>\n </BaseHeaderTogglePanel>\n</template>\n\n<script setup>\nimport { BaseHeaderTogglePanel } from '@empathyco/x-components'\nimport { CollapseHeight } from '@empathyco/x-components/animations'\nconst animation = CollapseHeight\n</script>\n```\n\n### Customizing default header with classes\n\nThe `headerClass` prop can be used to add classes to the header of the toggle panel.\n\n```vue\n<template>\n <BaseHeaderTogglePanel headerClass=\"custom-class\" :animation=\"animation\" :start-collapsed=\"false\">\n <template #header-content=\"{ open }\">\n <p>Header, open: {{ open ? 'close' : 'open' }}</p>\n </template>\n <template #default>\n <p>Default content</p>\n </template>\n </BaseHeaderTogglePanel>\n</template>\n\n<script setup>\nimport { BaseHeaderTogglePanel } from '@empathyco/x-components'\nimport { CollapseHeight } from '@empathyco/x-components/animations'\nconst animation = CollapseHeight\n</script>\n```\n</docs>\n"],"names":["NoAnimation"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA6BA;;;;;AAKE;AACF,gBAAe,eAAe,CAAC;AAC7B,IAAA,IAAI,EAAE,uBAAuB;IAC7B,UAAU,EAAE,EAAE,eAAc,EAAG;AAC/B,IAAA,KAAK,EAAE;;AAEL,QAAA,SAAS,EAAE;AACT,YAAA,IAAI,EAAE,aAAa;AACnB,YAAA,OAAO,EAAE,MAAMA,WAAW;AAC3B,SAAA;;AAED,QAAA,cAAc,EAAE,OAAO;;AAEvB,QAAA,WAAW,EAAE,MAAM;AACpB,KAAA;AACD,IAAA,KAAK,CAAC,KAAK,EAAE,EAAE,IAAG,EAAG,EAAA;AACnB;;;;AAIE;QACF,MAAM,IAAG,GAAI,GAAG,CAAC,CAAC,KAAK,CAAC,cAAc,CAAA;AAEtC;;;;AAIE;QACF,MAAM,mBAAkB,GAAI,MAAI;AAC9B,YAAA,IAAI,CAAC,IAAI,CAAC,KAAI,GAAI,MAAK,GAAI,OAAO,CAAA;AACpC,QAAA,CAAA;AAEA;;;;AAIE;QACF,MAAM,UAAS,GAAI,MAAI;AACrB,YAAA,IAAI,CAAC,KAAI,GAAI,CAAC,IAAI,CAAC,KAAI;AACvB,YAAA,mBAAmB,EAAC;AACtB,QAAA,CAAA;QAEA,OAAO;YACL,IAAI;YACJ,UAAU;SACZ;IACF,CAAC;AACF,CAAA,CAAA;;;;"}
|
|
@@ -18,6 +18,7 @@ import '../animations/animate-translate/animate-translate.style.css.js';
|
|
|
18
18
|
import '../animations/animate-width.vue2.js';
|
|
19
19
|
import '../animations/animate-width.vue3.js';
|
|
20
20
|
import '../animations/change-height.vue2.js';
|
|
21
|
+
import '../animations/change-height.vue3.js';
|
|
21
22
|
import '../animations/collapse-height.vue2.js';
|
|
22
23
|
import '../animations/collapse-height.vue3.js';
|
|
23
24
|
import '../animations/collapse-width.vue2.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-id-toggle-panel.vue2.js","sources":["../../../../src/components/panels/base-id-toggle-panel.vue"],"sourcesContent":["<template>\n <BaseTogglePanel :open=\"isOpen\" :animation=\"animation\">\n <!-- @slot (Required) Default content -->\n <slot />\n </BaseTogglePanel>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, ref, watch } from 'vue'\nimport { use$x } from '../../composables'\nimport { AnimationProp } from '../../types'\nimport { NoAnimation } from '../animations'\nimport BaseTogglePanel from './base-toggle-panel.vue'\n\n/**\n * Simple panel that could receives its initial open state via prop, if not the default is opens\n * and a required prop, named `panelId`, which are responsible of rendering default slot\n * inside a configurable transition.\n *\n * It reacts to `UserClickedPanelToggleButton` event, when their payload matches the component's\n * 'panelId' prop, to handle its open/close state.\n *\n * The default slot offers the possibility to customise the modal content.\n *\n * @public\n */\nexport default defineComponent({\n name: 'BaseIdTogglePanel',\n components: { BaseTogglePanel },\n props: {\n /** Shows the panel open at the beginning or not, depending on its state. */\n startOpen: {\n type: Boolean,\n default: true,\n },\n /** Animation component that will be used to animate the panel content. */\n animation: {\n type: AnimationProp,\n default: () => NoAnimation,\n },\n /**\n * The id to link with the BaseIdTogglePanelButton.\n * Both components must use the same Id to make them interact.\n */\n panelId: {\n type: String,\n required: true,\n },\n },\n setup(props) {\n const $x = use$x()\n\n /** Whether the toggle panel is open or not. */\n const isOpen = ref(props.startOpen)\n\n /**\n * Method to subscribe to the {@link XEventsTypes.UserClickedPanelToggleButton} event.\n *\n * @param panelId - The payload of the {@link XEventsTypes.UserClickedPanelToggleButton} event.\n *\n * @public\n */\n const togglePanel = (panelId: string) => {\n if (props.panelId === panelId) {\n isOpen.value = !isOpen.value\n }\n }\n\n $x.on('UserClickedPanelToggleButton', false).subscribe(togglePanel)\n\n /**\n * Emits the {@link XEventsTypes.TogglePanelStateChanged} event, when the internal state\n * changes.\n *\n * @remarks This event is necessary to link the state with the {@link BaseIdTogglePanelButton}\n * component.\n * @public\n */\n watch(\n () => isOpen.value,\n () => {\n $x.emit('TogglePanelStateChanged', isOpen.value, {\n id: props.panelId,\n target: document.getElementById(props.panelId) as HTMLElement,\n })\n },\n { immediate: true },\n )\n\n return { isOpen }\n },\n})\n</script>\n\n<docs lang=\"mdx\">\n## Events\n\nA list of events that the component will watch and emit:\n\n- [`UserClickedPanelToggleButton`](https://github.com/empathyco/x/blob/main/packages/x-components/src/wiring/events.types.ts):\n watched to toggle the panel when the payload matches the `panelId` prop.\n- [`TogglePanelStateChanged`](https://github.com/empathyco/x/blob/main/packages/x-components/src/wiring/events.types.ts):\n emitted when the internal open state changes, with the new state and panel id.\n\n## Examples\n\n### Basic usage\n\n```vue\n<template>\n <div>\n <BaseIdTogglePanelButton panelId=\"myToggle\">\n <img src=\"./open-button-icon.svg\" alt=\"Toggle Aside\" />\n <span>Toggle Aside</span>\n </BaseIdTogglePanelButton>\n <BaseIdTogglePanel :startOpen=\"true\" :animation=\"animation\" panelId=\"myToggle\">\n <div class=\"x-text1\">My toggle</div>\n </BaseIdTogglePanel>\n </div>\n</template>\n\n<script setup>\nimport { BaseIdTogglePanel, BaseIdTogglePanelButton } from '@empathyco/x-components'\nimport { CollapseFromTop } from '@empathyco/x-components/animations'\nconst animation = CollapseFromTop\n</script>\n```\n\n### Listening to state changes\n\nYou can listen to the `TogglePanelStateChanged` event to react to panel open/close state changes:\n\n```vue\n<template>\n <div>\n <span>Panel is {{ isPanelOpen ? 'open' : 'closed' }}</span>\n </div>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport { use$x } from '@empathyco/x-components'\n\nconst x = use$x()\nconst isPanelOpen = ref(false)\nconst panelId = 'myToggle'\n\nx.on('TogglePanelStateChanged').subscribe((isOpen, { id }) => {\n if (id === panelId) {\n isPanelOpen.value = isOpen\n }\n})\n</script>\n```\n</docs>\n"],"names":["NoAnimation"],"mappings":"
|
|
1
|
+
{"version":3,"file":"base-id-toggle-panel.vue2.js","sources":["../../../../src/components/panels/base-id-toggle-panel.vue"],"sourcesContent":["<template>\n <BaseTogglePanel :open=\"isOpen\" :animation=\"animation\">\n <!-- @slot (Required) Default content -->\n <slot />\n </BaseTogglePanel>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, ref, watch } from 'vue'\nimport { use$x } from '../../composables'\nimport { AnimationProp } from '../../types'\nimport { NoAnimation } from '../animations'\nimport BaseTogglePanel from './base-toggle-panel.vue'\n\n/**\n * Simple panel that could receives its initial open state via prop, if not the default is opens\n * and a required prop, named `panelId`, which are responsible of rendering default slot\n * inside a configurable transition.\n *\n * It reacts to `UserClickedPanelToggleButton` event, when their payload matches the component's\n * 'panelId' prop, to handle its open/close state.\n *\n * The default slot offers the possibility to customise the modal content.\n *\n * @public\n */\nexport default defineComponent({\n name: 'BaseIdTogglePanel',\n components: { BaseTogglePanel },\n props: {\n /** Shows the panel open at the beginning or not, depending on its state. */\n startOpen: {\n type: Boolean,\n default: true,\n },\n /** Animation component that will be used to animate the panel content. */\n animation: {\n type: AnimationProp,\n default: () => NoAnimation,\n },\n /**\n * The id to link with the BaseIdTogglePanelButton.\n * Both components must use the same Id to make them interact.\n */\n panelId: {\n type: String,\n required: true,\n },\n },\n setup(props) {\n const $x = use$x()\n\n /** Whether the toggle panel is open or not. */\n const isOpen = ref(props.startOpen)\n\n /**\n * Method to subscribe to the {@link XEventsTypes.UserClickedPanelToggleButton} event.\n *\n * @param panelId - The payload of the {@link XEventsTypes.UserClickedPanelToggleButton} event.\n *\n * @public\n */\n const togglePanel = (panelId: string) => {\n if (props.panelId === panelId) {\n isOpen.value = !isOpen.value\n }\n }\n\n $x.on('UserClickedPanelToggleButton', false).subscribe(togglePanel)\n\n /**\n * Emits the {@link XEventsTypes.TogglePanelStateChanged} event, when the internal state\n * changes.\n *\n * @remarks This event is necessary to link the state with the {@link BaseIdTogglePanelButton}\n * component.\n * @public\n */\n watch(\n () => isOpen.value,\n () => {\n $x.emit('TogglePanelStateChanged', isOpen.value, {\n id: props.panelId,\n target: document.getElementById(props.panelId) as HTMLElement,\n })\n },\n { immediate: true },\n )\n\n return { isOpen }\n },\n})\n</script>\n\n<docs lang=\"mdx\">\n## Events\n\nA list of events that the component will watch and emit:\n\n- [`UserClickedPanelToggleButton`](https://github.com/empathyco/x/blob/main/packages/x-components/src/wiring/events.types.ts):\n watched to toggle the panel when the payload matches the `panelId` prop.\n- [`TogglePanelStateChanged`](https://github.com/empathyco/x/blob/main/packages/x-components/src/wiring/events.types.ts):\n emitted when the internal open state changes, with the new state and panel id.\n\n## Examples\n\n### Basic usage\n\n```vue\n<template>\n <div>\n <BaseIdTogglePanelButton panelId=\"myToggle\">\n <img src=\"./open-button-icon.svg\" alt=\"Toggle Aside\" />\n <span>Toggle Aside</span>\n </BaseIdTogglePanelButton>\n <BaseIdTogglePanel :startOpen=\"true\" :animation=\"animation\" panelId=\"myToggle\">\n <div class=\"x-text1\">My toggle</div>\n </BaseIdTogglePanel>\n </div>\n</template>\n\n<script setup>\nimport { BaseIdTogglePanel, BaseIdTogglePanelButton } from '@empathyco/x-components'\nimport { CollapseFromTop } from '@empathyco/x-components/animations'\nconst animation = CollapseFromTop\n</script>\n```\n\n### Listening to state changes\n\nYou can listen to the `TogglePanelStateChanged` event to react to panel open/close state changes:\n\n```vue\n<template>\n <div>\n <span>Panel is {{ isPanelOpen ? 'open' : 'closed' }}</span>\n </div>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nimport { use$x } from '@empathyco/x-components'\n\nconst x = use$x()\nconst isPanelOpen = ref(false)\nconst panelId = 'myToggle'\n\nx.on('TogglePanelStateChanged').subscribe((isOpen, { id }) => {\n if (id === panelId) {\n isPanelOpen.value = isOpen\n }\n})\n</script>\n```\n</docs>\n"],"names":["NoAnimation"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcA;;;;;;;;;;;AAWE;AACF,gBAAe,eAAe,CAAC;AAC7B,IAAA,IAAI,EAAE,mBAAmB;IACzB,UAAU,EAAE,EAAE,eAAc,EAAG;AAC/B,IAAA,KAAK,EAAE;;AAEL,QAAA,SAAS,EAAE;AACT,YAAA,IAAI,EAAE,OAAO;AACb,YAAA,OAAO,EAAE,IAAI;AACd,SAAA;;AAED,QAAA,SAAS,EAAE;AACT,YAAA,IAAI,EAAE,aAAa;AACnB,YAAA,OAAO,EAAE,MAAMA,WAAW;AAC3B,SAAA;AACD;;;AAGE;AACF,QAAA,OAAO,EAAE;AACP,YAAA,IAAI,EAAE,MAAM;AACZ,YAAA,QAAQ,EAAE,IAAI;AACf,SAAA;AACF,KAAA;AACD,IAAA,KAAK,CAAC,KAAK,EAAA;AACT,QAAA,MAAM,EAAC,GAAI,KAAK,EAAC;;QAGjB,MAAM,MAAK,GAAI,GAAG,CAAC,KAAK,CAAC,SAAS,CAAA;AAElC;;;;;;AAME;AACF,QAAA,MAAM,WAAU,GAAI,CAAC,OAAe,KAAG;AACrC,YAAA,IAAI,KAAK,CAAC,OAAM,KAAM,OAAO,EAAE;AAC7B,gBAAA,MAAM,CAAC,KAAI,GAAI,CAAC,MAAM,CAAC,KAAI;YAC7B;AACF,QAAA,CAAA;AAEA,QAAA,EAAE,CAAC,EAAE,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC,WAAW,CAAA;AAElE;;;;;;;AAOE;QACF,KAAK,CACH,MAAM,MAAM,CAAC,KAAK,EAClB,MAAI;YACF,EAAE,CAAC,IAAI,CAAC,yBAAyB,EAAE,MAAM,CAAC,KAAK,EAAE;gBAC/C,EAAE,EAAE,KAAK,CAAC,OAAO;gBACjB,MAAM,EAAE,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,OAAO,CAAgB;AAC9D,aAAA,CAAA;AACH,QAAA,CAAC,EACD,EAAE,SAAS,EAAE,MAAM,CACrB;QAEA,OAAO,EAAE,MAAK,EAAE;IAClB,CAAC;AACF,CAAA,CAAA;;;;"}
|
|
@@ -6,6 +6,7 @@ import '../animations/animate-translate/animate-translate.style.css.js';
|
|
|
6
6
|
import '../animations/animate-width.vue2.js';
|
|
7
7
|
import '../animations/animate-width.vue3.js';
|
|
8
8
|
import '../animations/change-height.vue2.js';
|
|
9
|
+
import '../animations/change-height.vue3.js';
|
|
9
10
|
import '../animations/collapse-height.vue2.js';
|
|
10
11
|
import '../animations/collapse-height.vue3.js';
|
|
11
12
|
import '../animations/collapse-width.vue2.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-tabs-panel.vue2.js","sources":["../../../../src/components/panels/base-tabs-panel.vue"],"sourcesContent":["<template>\n <section v-if=\"getTabs().length > 0\" class=\"x-tabs-panel\" data-test=\"base-tabs-panel\">\n <component\n :is=\"tabsAnimation\"\n class=\"x-tabs-panel__items-list\"\n :class=\"tabsListClass\"\n data-test=\"base-tabs-panel-list\"\n role=\"tablist\"\n >\n <!--\n @slot Slot used to replace the whole tab.\n @binding {string} tab - The tab name.\n @binding {boolean} isSelected - Indicates if the tab is selected.\n @binding {function} select - Function to select the tab.\n -->\n <slot\n v-for=\"tab in getTabs()\"\n name=\"tab\"\n v-bind=\"{ tab, isSelected: tabIsSelected(tab), select: () => selectTab(tab) }\"\n >\n <button\n :id=\"`base-tabs-panel-${tab}`\"\n :key=\"tab\"\n class=\"x-tabs-panel__list-item x-tabs-panel__button x-button\"\n :class=\"tabIsSelected(tab) ? activeTabClass : tabClass\"\n :aria-selected=\"tabIsSelected(tab)\"\n data-test=\"base-tabs-panel-button\"\n role=\"tab\"\n @click=\"selectTab(tab)\"\n >\n <!--\n @slot Slot used to just pass the content.\n @binding {string} tab - The tab name.\n @binding {boolean} isSelected - Indicates if the tab is selected.\n -->\n <slot name=\"tab-content\" v-bind=\"{ tab, isSelected: tabIsSelected(tab) }\">\n {{ tab }}\n </slot>\n </button>\n </slot>\n </component>\n\n <component :is=\"contentAnimation\">\n <div\n v-if=\"selectedTab && slots[selectedTab]\"\n :key=\"selectedTab\"\n :class=\"contentClass\"\n :aria-labelledby=\"`base-tabs-panel-${selectedTab}`\"\n data-test=\"base-tabs-panel-content\"\n role=\"tabpanel\"\n >\n <!--\n @slot Slot used to display the selected tab content.\n @binding {string} tab - This content's tab name.\n @binding {function} selectTab - Function to select a tab.\n @binding {function} deselectTab - Function to deselect the tab.\n -->\n <slot\n :name=\"selectedTab\"\n v-bind=\"{ tab: selectedTab, selectTab, deselectTab: () => selectTab(selectedTab) }\"\n />\n </div>\n </component>\n </section>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, ref } from 'vue'\nimport { AnimationProp } from '../../types'\nimport { NoAnimation } from '../animations'\n\n/**\n * Base Tabs Panel.\n *\n * @public\n */\nexport default defineComponent({\n name: 'BaseTabsPanel',\n props: {\n /**\n * Animation component that will be used to animate the tabs list.\n *\n * @public\n */\n tabsAnimation: {\n type: AnimationProp,\n default: 'header',\n },\n /**\n * Animation component that will be used to animate the selected tab content.\n *\n * @public\n */\n contentAnimation: {\n type: AnimationProp,\n default: () => NoAnimation,\n },\n /**\n * The tab to be initially selected.\n *\n * @public\n */\n initialTab: {\n type: String,\n default: '',\n },\n /**\n * Allows the tabs to be unselected.\n *\n * @public\n */\n allowTabDeselect: {\n type: Boolean,\n default: false,\n },\n /** Class inherited by content element. */\n activeTabClass: String,\n /** Class inherited by content element. */\n contentClass: String,\n /** Class inherited by content element. */\n tabClass: String,\n /** Class inherited by content element. */\n tabsListClass: String,\n },\n setup(props, { slots }) {\n /**\n * The currently selected tab.\n *\n * @internal\n */\n const selectedTab = ref(props.initialTab)\n\n /**\n * Extracts the tab from the slots.\n *\n * @returns The list of tabs.\n *\n * @internal\n */\n const getTabs = () =>\n Object.keys(slots).filter(slotName => !['tab', 'tab-content'].includes(slotName))\n\n /**\n * Changes the current selected tab. If the tab is already selected\n * and `allowTabDeselect` is `true`, the tab will be unselected.\n *\n * @param tab - The tab to be selected.\n *\n * @internal\n */\n const selectTab = (tab: string) => {\n if (props.allowTabDeselect && selectedTab.value === tab) {\n selectedTab.value = ''\n } else {\n selectedTab.value = tab\n }\n }\n\n /**\n * Checks if a tab is selected.\n *\n * @param tab - Tab to check.\n * @returns True if the tab is selected, false otherwise.\n *\n * @internal\n */\n const tabIsSelected = (tab: string) => selectedTab.value === tab\n\n return {\n selectedTab,\n slots,\n getTabs,\n selectTab,\n tabIsSelected,\n }\n },\n})\n</script>\n\n<style lang=\"css\" scoped>\n.x-tabs-panel__items-list {\n display: flex;\n}\n</style>\n\n<docs lang=\"mdx\">\n## Events\n\nThis component emits no events.\n\n## Examples\n\nThis component renders a list of tabs based on the name of the slots passed on its template. By\ndefault, the name of each slot will be used as tab label. If an initial tab is passed by prop, the\ncontent of its correspondent slot will be displayed in a panel. Otherwise, no content will be displayed\nuntil a tab is selected.\n\n### Basic example\n\nIt renders a list of tabs and, when a tab is clicked, a panel with its content will be displayed.\n\n```vue\n<template>\n <BaseTabsPanel>\n <template #summer>\n <div>Summer Top Sales</div>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n <template #outlet>\n <div>Outlet Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel } from '@empathyco/x-components'\n</script>\n```\n\n### Play with props\n\n#### Define the tab to be initially opened\n\nBy default, no tab is selected so any panel is displayed. The `initialTab` prop allows to indicate\nwhich tab should be opened at first.\n\n```vue\n<template>\n <BaseTabsPanel initialTab=\"summer\">\n <template #summer>\n <div>Summer Top Sales</div>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n <template #outlet>\n <div>Outlet Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel } from '@empathyco/x-components'\n</script>\n```\n\n#### Allowing tabs deselection\n\nThe prop `allowTabDeselect` allows the tabs to be deselected. When a tab that is already selected is\nclicked again, the tab will be deselected and no panel content will be displayed. By default, this\nbehavior is deactivated.\n\n```vue\n<template>\n <BaseTabsPanel initialTab=\"summer\" allowTabDeselect>\n <template #summer>\n <div>Summer Top Sales</div>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n <template #outlet>\n <div>Outlet Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel } from '@empathyco/x-components'\n</script>\n```\n\n#### Customizing the content with classes\n\n- The `activeTabClass` prop can be used to add classes to the active tab button.\n- The `contentClass` prop can be used to add classes to the content.\n- The `tabClass` prop can be used to add classes to the tab buttons.\n- The `tabsListClass` prop can be used to add classes to the tabs list.\n\n```vue\n<template>\n <BaseTabsPanel\n activeTabClass=\"x-button-auxiliary\"\n contentClass=\"x-p-12 x-bg-auxiliary-25\"\n tabClass=\"x-button-ghost\"\n tabsListClass=\"x-flex-col\"\n >\n <template #summer>\n <div>Summer Top Sales</div>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n <template #outlet>\n <div>Outlet Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel } from '@empathyco/x-components'\n</script>\n```\n\n#### Play with the animations\n\n- The `tabsAnimation` prop can be used to animate the tabs list.\n- The `contentAnimation` prop can be used to animate the selected tab content.\n\n```vue\n<template>\n <BaseTabsPanel :tabsAnimation=\"staggeredFadeAndSlide\" :contentAnimation=\"staggeredFadeAndSlide\">\n <template #summer>\n <div>Summer Top Sales</div>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel, StaggeredFadeAndSlide } from '@empathyco/x-components'\nconst staggeredFadeAndSlide = StaggeredFadeAndSlide\n</script>\n```\n\n### Overriding the slots\n\n#### Customizing the tab button\n\nBy default, the component is rendering a button for each tab to be displayed. This button can be\nreplaced entirely through the `tab` slot.\n\n```vue\n<template>\n <BaseTabsPanel>\n <template #tab=\"{ tab, isSelected, select }\">\n <CheckIcon v-if=\"isSelected\" />\n This is the {{ tab }} tab.\n <button @click=\"select\">Open tab</button>\n </template>\n <template #summer>\n <div>Summer Top Sales</div>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n <template #outlet>\n <div>Outlet Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel, CheckIcon } from '@empathyco/x-components'\n</script>\n```\n\n#### Customizing the tab button content\n\nAlternatively to the previous example, instead of changing the whole tab button, the slot\n`tab-content` offers the possibility of changing just its contents.\n\n```vue\n<template>\n <BaseTabsPanel>\n <template #tab-content=\"{ tab, isSelected }\">\n <CheckIcon v-if=\"isSelected\" />\n {{ tab }}\n </template>\n <template #summer>\n <div>Summer Top Sales</div>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n <template #outlet>\n <div>Outlet Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel, CheckIcon } from '@empathyco/x-components'\n</script>\n```\n\n#### Customizing the tab panel content\n\nThe displayed tab name and a method to select a tab are exposed to the tab panel content slot.\n\n```vue\n<template>\n <BaseTabsPanel>\n <template #summer=\"{ tab, selectTab, deselectTab }\">\n <h1>{{ tab }}</h1>\n <button @click=\"() => selectTab('fall')\">Open Fall</button>\n <button @click=\"deselectTab\">Close tab</button>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel } from '@empathyco/x-components'\n</script>\n```\n</docs>\n"],"names":["NoAnimation"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuEA;;;;AAIE;AACF,gBAAe,eAAe,CAAC;AAC7B,IAAA,IAAI,EAAE,eAAe;AACrB,IAAA,KAAK,EAAE;AACL;;;;AAIE;AACF,QAAA,aAAa,EAAE;AACb,YAAA,IAAI,EAAE,aAAa;AACnB,YAAA,OAAO,EAAE,QAAQ;AAClB,SAAA;AACD;;;;AAIE;AACF,QAAA,gBAAgB,EAAE;AAChB,YAAA,IAAI,EAAE,aAAa;AACnB,YAAA,OAAO,EAAE,MAAMA,WAAW;AAC3B,SAAA;AACD;;;;AAIE;AACF,QAAA,UAAU,EAAE;AACV,YAAA,IAAI,EAAE,MAAM;AACZ,YAAA,OAAO,EAAE,EAAE;AACZ,SAAA;AACD;;;;AAIE;AACF,QAAA,gBAAgB,EAAE;AAChB,YAAA,IAAI,EAAE,OAAO;AACb,YAAA,OAAO,EAAE,KAAK;AACf,SAAA;;AAED,QAAA,cAAc,EAAE,MAAM;;AAEtB,QAAA,YAAY,EAAE,MAAM;;AAEpB,QAAA,QAAQ,EAAE,MAAM;;AAEhB,QAAA,aAAa,EAAE,MAAM;AACtB,KAAA;AACD,IAAA,KAAK,CAAC,KAAK,EAAE,EAAE,KAAI,EAAG,EAAA;AACpB;;;;AAIE;QACF,MAAM,cAAc,GAAG,CAAC,KAAK,CAAC,UAAU,CAAA;AAExC;;;;;;AAME;AACF,QAAA,MAAM,OAAM,GAAI,MACd,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAO,IAAK,CAAC,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;AAElF;;;;;;;AAOE;AACF,QAAA,MAAM,SAAQ,GAAI,CAAC,GAAW,KAAG;YAC/B,IAAI,KAAK,CAAC,gBAAe,IAAK,WAAW,CAAC,KAAI,KAAM,GAAG,EAAE;AACvD,gBAAA,WAAW,CAAC,KAAI,GAAI,EAAC;YACvB;iBAAO;AACL,gBAAA,WAAW,CAAC,KAAI,GAAI,GAAE;YACxB;AACF,QAAA,CAAA;AAEA;;;;;;;AAOE;AACF,QAAA,MAAM,aAAY,GAAI,CAAC,GAAW,KAAK,WAAW,CAAC,KAAI,KAAM,GAAE;QAE/D,OAAO;YACL,WAAW;YACX,KAAK;YACL,OAAO;YACP,SAAS;YACT,aAAa;SACf;IACF,CAAC;AACF,CAAA,CAAA;;;;"}
|
|
1
|
+
{"version":3,"file":"base-tabs-panel.vue2.js","sources":["../../../../src/components/panels/base-tabs-panel.vue"],"sourcesContent":["<template>\n <section v-if=\"getTabs().length > 0\" class=\"x-tabs-panel\" data-test=\"base-tabs-panel\">\n <component\n :is=\"tabsAnimation\"\n class=\"x-tabs-panel__items-list\"\n :class=\"tabsListClass\"\n data-test=\"base-tabs-panel-list\"\n role=\"tablist\"\n >\n <!--\n @slot Slot used to replace the whole tab.\n @binding {string} tab - The tab name.\n @binding {boolean} isSelected - Indicates if the tab is selected.\n @binding {function} select - Function to select the tab.\n -->\n <slot\n v-for=\"tab in getTabs()\"\n name=\"tab\"\n v-bind=\"{ tab, isSelected: tabIsSelected(tab), select: () => selectTab(tab) }\"\n >\n <button\n :id=\"`base-tabs-panel-${tab}`\"\n :key=\"tab\"\n class=\"x-tabs-panel__list-item x-tabs-panel__button x-button\"\n :class=\"tabIsSelected(tab) ? activeTabClass : tabClass\"\n :aria-selected=\"tabIsSelected(tab)\"\n data-test=\"base-tabs-panel-button\"\n role=\"tab\"\n @click=\"selectTab(tab)\"\n >\n <!--\n @slot Slot used to just pass the content.\n @binding {string} tab - The tab name.\n @binding {boolean} isSelected - Indicates if the tab is selected.\n -->\n <slot name=\"tab-content\" v-bind=\"{ tab, isSelected: tabIsSelected(tab) }\">\n {{ tab }}\n </slot>\n </button>\n </slot>\n </component>\n\n <component :is=\"contentAnimation\">\n <div\n v-if=\"selectedTab && slots[selectedTab]\"\n :key=\"selectedTab\"\n :class=\"contentClass\"\n :aria-labelledby=\"`base-tabs-panel-${selectedTab}`\"\n data-test=\"base-tabs-panel-content\"\n role=\"tabpanel\"\n >\n <!--\n @slot Slot used to display the selected tab content.\n @binding {string} tab - This content's tab name.\n @binding {function} selectTab - Function to select a tab.\n @binding {function} deselectTab - Function to deselect the tab.\n -->\n <slot\n :name=\"selectedTab\"\n v-bind=\"{ tab: selectedTab, selectTab, deselectTab: () => selectTab(selectedTab) }\"\n />\n </div>\n </component>\n </section>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, ref } from 'vue'\nimport { AnimationProp } from '../../types'\nimport { NoAnimation } from '../animations'\n\n/**\n * Base Tabs Panel.\n *\n * @public\n */\nexport default defineComponent({\n name: 'BaseTabsPanel',\n props: {\n /**\n * Animation component that will be used to animate the tabs list.\n *\n * @public\n */\n tabsAnimation: {\n type: AnimationProp,\n default: 'header',\n },\n /**\n * Animation component that will be used to animate the selected tab content.\n *\n * @public\n */\n contentAnimation: {\n type: AnimationProp,\n default: () => NoAnimation,\n },\n /**\n * The tab to be initially selected.\n *\n * @public\n */\n initialTab: {\n type: String,\n default: '',\n },\n /**\n * Allows the tabs to be unselected.\n *\n * @public\n */\n allowTabDeselect: {\n type: Boolean,\n default: false,\n },\n /** Class inherited by content element. */\n activeTabClass: String,\n /** Class inherited by content element. */\n contentClass: String,\n /** Class inherited by content element. */\n tabClass: String,\n /** Class inherited by content element. */\n tabsListClass: String,\n },\n setup(props, { slots }) {\n /**\n * The currently selected tab.\n *\n * @internal\n */\n const selectedTab = ref(props.initialTab)\n\n /**\n * Extracts the tab from the slots.\n *\n * @returns The list of tabs.\n *\n * @internal\n */\n const getTabs = () =>\n Object.keys(slots).filter(slotName => !['tab', 'tab-content'].includes(slotName))\n\n /**\n * Changes the current selected tab. If the tab is already selected\n * and `allowTabDeselect` is `true`, the tab will be unselected.\n *\n * @param tab - The tab to be selected.\n *\n * @internal\n */\n const selectTab = (tab: string) => {\n if (props.allowTabDeselect && selectedTab.value === tab) {\n selectedTab.value = ''\n } else {\n selectedTab.value = tab\n }\n }\n\n /**\n * Checks if a tab is selected.\n *\n * @param tab - Tab to check.\n * @returns True if the tab is selected, false otherwise.\n *\n * @internal\n */\n const tabIsSelected = (tab: string) => selectedTab.value === tab\n\n return {\n selectedTab,\n slots,\n getTabs,\n selectTab,\n tabIsSelected,\n }\n },\n})\n</script>\n\n<style lang=\"css\" scoped>\n.x-tabs-panel__items-list {\n display: flex;\n}\n</style>\n\n<docs lang=\"mdx\">\n## Events\n\nThis component emits no events.\n\n## Examples\n\nThis component renders a list of tabs based on the name of the slots passed on its template. By\ndefault, the name of each slot will be used as tab label. If an initial tab is passed by prop, the\ncontent of its correspondent slot will be displayed in a panel. Otherwise, no content will be displayed\nuntil a tab is selected.\n\n### Basic example\n\nIt renders a list of tabs and, when a tab is clicked, a panel with its content will be displayed.\n\n```vue\n<template>\n <BaseTabsPanel>\n <template #summer>\n <div>Summer Top Sales</div>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n <template #outlet>\n <div>Outlet Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel } from '@empathyco/x-components'\n</script>\n```\n\n### Play with props\n\n#### Define the tab to be initially opened\n\nBy default, no tab is selected so any panel is displayed. The `initialTab` prop allows to indicate\nwhich tab should be opened at first.\n\n```vue\n<template>\n <BaseTabsPanel initialTab=\"summer\">\n <template #summer>\n <div>Summer Top Sales</div>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n <template #outlet>\n <div>Outlet Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel } from '@empathyco/x-components'\n</script>\n```\n\n#### Allowing tabs deselection\n\nThe prop `allowTabDeselect` allows the tabs to be deselected. When a tab that is already selected is\nclicked again, the tab will be deselected and no panel content will be displayed. By default, this\nbehavior is deactivated.\n\n```vue\n<template>\n <BaseTabsPanel initialTab=\"summer\" allowTabDeselect>\n <template #summer>\n <div>Summer Top Sales</div>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n <template #outlet>\n <div>Outlet Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel } from '@empathyco/x-components'\n</script>\n```\n\n#### Customizing the content with classes\n\n- The `activeTabClass` prop can be used to add classes to the active tab button.\n- The `contentClass` prop can be used to add classes to the content.\n- The `tabClass` prop can be used to add classes to the tab buttons.\n- The `tabsListClass` prop can be used to add classes to the tabs list.\n\n```vue\n<template>\n <BaseTabsPanel\n activeTabClass=\"x-button-auxiliary\"\n contentClass=\"x-p-12 x-bg-auxiliary-25\"\n tabClass=\"x-button-ghost\"\n tabsListClass=\"x-flex-col\"\n >\n <template #summer>\n <div>Summer Top Sales</div>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n <template #outlet>\n <div>Outlet Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel } from '@empathyco/x-components'\n</script>\n```\n\n#### Play with the animations\n\n- The `tabsAnimation` prop can be used to animate the tabs list.\n- The `contentAnimation` prop can be used to animate the selected tab content.\n\n```vue\n<template>\n <BaseTabsPanel :tabsAnimation=\"staggeredFadeAndSlide\" :contentAnimation=\"staggeredFadeAndSlide\">\n <template #summer>\n <div>Summer Top Sales</div>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel, StaggeredFadeAndSlide } from '@empathyco/x-components'\nconst staggeredFadeAndSlide = StaggeredFadeAndSlide\n</script>\n```\n\n### Overriding the slots\n\n#### Customizing the tab button\n\nBy default, the component is rendering a button for each tab to be displayed. This button can be\nreplaced entirely through the `tab` slot.\n\n```vue\n<template>\n <BaseTabsPanel>\n <template #tab=\"{ tab, isSelected, select }\">\n <CheckIcon v-if=\"isSelected\" />\n This is the {{ tab }} tab.\n <button @click=\"select\">Open tab</button>\n </template>\n <template #summer>\n <div>Summer Top Sales</div>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n <template #outlet>\n <div>Outlet Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel, CheckIcon } from '@empathyco/x-components'\n</script>\n```\n\n#### Customizing the tab button content\n\nAlternatively to the previous example, instead of changing the whole tab button, the slot\n`tab-content` offers the possibility of changing just its contents.\n\n```vue\n<template>\n <BaseTabsPanel>\n <template #tab-content=\"{ tab, isSelected }\">\n <CheckIcon v-if=\"isSelected\" />\n {{ tab }}\n </template>\n <template #summer>\n <div>Summer Top Sales</div>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n <template #outlet>\n <div>Outlet Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel, CheckIcon } from '@empathyco/x-components'\n</script>\n```\n\n#### Customizing the tab panel content\n\nThe displayed tab name and a method to select a tab are exposed to the tab panel content slot.\n\n```vue\n<template>\n <BaseTabsPanel>\n <template #summer=\"{ tab, selectTab, deselectTab }\">\n <h1>{{ tab }}</h1>\n <button @click=\"() => selectTab('fall')\">Open Fall</button>\n <button @click=\"deselectTab\">Close tab</button>\n </template>\n <template #fall>\n <div>Fall Top Sales</div>\n </template>\n </BaseTabsPanel>\n</template>\n\n<script setup>\nimport { BaseTabsPanel } from '@empathyco/x-components'\n</script>\n```\n</docs>\n"],"names":["NoAnimation"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAuEA;;;;AAIE;AACF,gBAAe,eAAe,CAAC;AAC7B,IAAA,IAAI,EAAE,eAAe;AACrB,IAAA,KAAK,EAAE;AACL;;;;AAIE;AACF,QAAA,aAAa,EAAE;AACb,YAAA,IAAI,EAAE,aAAa;AACnB,YAAA,OAAO,EAAE,QAAQ;AAClB,SAAA;AACD;;;;AAIE;AACF,QAAA,gBAAgB,EAAE;AAChB,YAAA,IAAI,EAAE,aAAa;AACnB,YAAA,OAAO,EAAE,MAAMA,WAAW;AAC3B,SAAA;AACD;;;;AAIE;AACF,QAAA,UAAU,EAAE;AACV,YAAA,IAAI,EAAE,MAAM;AACZ,YAAA,OAAO,EAAE,EAAE;AACZ,SAAA;AACD;;;;AAIE;AACF,QAAA,gBAAgB,EAAE;AAChB,YAAA,IAAI,EAAE,OAAO;AACb,YAAA,OAAO,EAAE,KAAK;AACf,SAAA;;AAED,QAAA,cAAc,EAAE,MAAM;;AAEtB,QAAA,YAAY,EAAE,MAAM;;AAEpB,QAAA,QAAQ,EAAE,MAAM;;AAEhB,QAAA,aAAa,EAAE,MAAM;AACtB,KAAA;AACD,IAAA,KAAK,CAAC,KAAK,EAAE,EAAE,KAAI,EAAG,EAAA;AACpB;;;;AAIE;QACF,MAAM,cAAc,GAAG,CAAC,KAAK,CAAC,UAAU,CAAA;AAExC;;;;;;AAME;AACF,QAAA,MAAM,OAAM,GAAI,MACd,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAO,IAAK,CAAC,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;AAElF;;;;;;;AAOE;AACF,QAAA,MAAM,SAAQ,GAAI,CAAC,GAAW,KAAG;YAC/B,IAAI,KAAK,CAAC,gBAAe,IAAK,WAAW,CAAC,KAAI,KAAM,GAAG,EAAE;AACvD,gBAAA,WAAW,CAAC,KAAI,GAAI,EAAC;YACvB;iBAAO;AACL,gBAAA,WAAW,CAAC,KAAI,GAAI,GAAE;YACxB;AACF,QAAA,CAAA;AAEA;;;;;;;AAOE;AACF,QAAA,MAAM,aAAY,GAAI,CAAC,GAAW,KAAK,WAAW,CAAC,KAAI,KAAM,GAAE;QAE/D,OAAO;YACL,WAAW;YACX,KAAK;YACL,OAAO;YACP,SAAS;YACT,aAAa;SACf;IACF,CAAC;AACF,CAAA,CAAA;;;;"}
|
|
@@ -6,6 +6,7 @@ import '../animations/animate-translate/animate-translate.style.css.js';
|
|
|
6
6
|
import '../animations/animate-width.vue2.js';
|
|
7
7
|
import '../animations/animate-width.vue3.js';
|
|
8
8
|
import '../animations/change-height.vue2.js';
|
|
9
|
+
import '../animations/change-height.vue3.js';
|
|
9
10
|
import '../animations/collapse-height.vue2.js';
|
|
10
11
|
import '../animations/collapse-height.vue3.js';
|
|
11
12
|
import '../animations/collapse-width.vue2.js';
|