@empathyco/x-components 3.0.0-alpha.237 → 3.0.0-alpha.239
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 +26 -0
- package/core/index.js +1 -1
- package/docs/API-reference/api/x-components.gettargetelement.md +30 -0
- package/docs/API-reference/api/x-components.md +1 -0
- package/docs/API-reference/api/x-components.staggeringtransitiongroup.md +9 -0
- package/docs/API-reference/api/x-components.staggeringtransitiongroup.moveclass.md +13 -0
- package/docs/API-reference/api/x-components.staggeringtransitiongroup.name.md +13 -0
- package/docs/API-reference/api/x-components.staggeringtransitiongroup.staggering.md +13 -0
- package/docs/API-reference/api/x-components.staggeringtransitiongroup.tag.md +13 -0
- package/js/components/animations/staggered-fade-and-slide.vue.js +1 -1
- package/js/components/animations/staggered-fade-and-slide.vue.js.map +1 -1
- package/js/components/animations/staggered-fade-and-slide.vue_rollup-plugin-vue_styles.0.vue.js +1 -1
- package/js/components/animations/staggering-transition-group.vue.js +1 -1
- package/js/components/animations/staggering-transition-group.vue.js.map +1 -1
- package/js/components/animations/staggering-transition-group.vue_rollup-plugin-vue_script.vue.js +1 -2
- package/js/components/animations/staggering-transition-group.vue_rollup-plugin-vue_script.vue.js.map +1 -1
- package/js/components/animations/staggering-transition-group.vue_rollup-plugin-vue_styles.0.vue.js +1 -1
- package/js/components/base-dropdown.vue.js +1 -1
- package/js/components/base-dropdown.vue.js.map +1 -1
- package/js/components/base-dropdown.vue_rollup-plugin-vue_script.vue.js +2 -1
- package/js/components/base-dropdown.vue_rollup-plugin-vue_script.vue.js.map +1 -1
- package/js/components/base-dropdown.vue_rollup-plugin-vue_styles.0.vue.js +1 -1
- package/js/components/modals/base-events-modal.vue.js.map +1 -1
- package/js/components/modals/base-events-modal.vue_rollup-plugin-vue_script.vue.js +2 -2
- package/js/components/modals/base-events-modal.vue_rollup-plugin-vue_script.vue.js.map +1 -1
- package/js/components/modals/base-id-modal.vue.js.map +1 -1
- package/js/components/modals/base-id-modal.vue_rollup-plugin-vue_script.vue.js +2 -2
- package/js/components/modals/base-id-modal.vue_rollup-plugin-vue_script.vue.js.map +1 -1
- package/js/components/modals/base-modal.vue.js +1 -1
- package/js/components/modals/base-modal.vue.js.map +1 -1
- package/js/components/modals/base-modal.vue_rollup-plugin-vue_script.vue.js +2 -1
- package/js/components/modals/base-modal.vue_rollup-plugin-vue_script.vue.js.map +1 -1
- package/js/components/modals/base-modal.vue_rollup-plugin-vue_styles.0.vue.js +1 -1
- package/js/components/result/base-result-image.vue.js +1 -1
- package/js/components/result/base-result-image.vue.js.map +1 -1
- package/js/components/result/base-result-image.vue_rollup-plugin-vue_script.vue.js +14 -2
- package/js/components/result/base-result-image.vue_rollup-plugin-vue_script.vue.js.map +1 -1
- package/js/components/result/base-result-image.vue_rollup-plugin-vue_styles.0.vue.js +1 -1
- package/js/index.js +1 -1
- package/js/utils/html.js +18 -1
- package/js/utils/html.js.map +1 -1
- package/package.json +3 -3
- package/report/x-components.api.json +153 -0
- package/report/x-components.api.md +5 -4
- package/types/components/animations/staggering-transition-group.vue.d.ts +36 -21
- package/types/components/animations/staggering-transition-group.vue.d.ts.map +1 -1
- package/types/components/base-dropdown.vue.d.ts.map +1 -1
- package/types/components/modals/base-modal.vue.d.ts.map +1 -1
- package/types/components/result/base-result-image.vue.d.ts +6 -0
- package/types/components/result/base-result-image.vue.d.ts.map +1 -1
- package/types/utils/html.d.ts +15 -0
- package/types/utils/html.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,32 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See
|
|
4
4
|
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [3.0.0-alpha.239](https://github.com/empathyco/x/compare/@empathyco/x-components@3.0.0-alpha.238...@empathyco/x-components@3.0.0-alpha.239) (2022-12-01)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @empathyco/x-components
|
|
9
|
+
|
|
10
|
+
# Change Log
|
|
11
|
+
|
|
12
|
+
All notable changes to this project will be documented in this file. See
|
|
13
|
+
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
14
|
+
|
|
15
|
+
## [3.0.0-alpha.238](https://github.com/empathyco/x/compare/@empathyco/x-components@3.0.0-alpha.237...@empathyco/x-components@3.0.0-alpha.238) (2022-12-01)
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
- **components:** add `getTargetElement` util to retrieve the `event.target` (#870)
|
|
20
|
+
([8be7beb](https://github.com/empathyco/x/commit/8be7beba25a5a90b142fbb1a905d41503befc92c))
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
- **components:** reset`BaseResultImage` images state when `result` `prop` changes (#888)
|
|
25
|
+
([a783ace](https://github.com/empathyco/x/commit/a783ace74929db22b7ed087b0eb19481078a35f5))
|
|
26
|
+
|
|
27
|
+
# Change Log
|
|
28
|
+
|
|
29
|
+
All notable changes to this project will be documented in this file. See
|
|
30
|
+
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
31
|
+
|
|
6
32
|
## [3.0.0-alpha.237](https://github.com/empathyco/x/compare/@empathyco/x-components@3.0.0-alpha.236...@empathyco/x-components@3.0.0-alpha.237) (2022-12-01)
|
|
7
33
|
|
|
8
34
|
### Features
|
package/core/index.js
CHANGED
|
@@ -147,7 +147,7 @@ export { areFiltersDifferent, createRawFilters } from '../js/utils/filters.js';
|
|
|
147
147
|
export { FOCUSABLE_SELECTORS } from '../js/utils/focus.js';
|
|
148
148
|
export { noOp } from '../js/utils/function.js';
|
|
149
149
|
export { getURLParameter } from '../js/utils/get-url-parameters.js';
|
|
150
|
-
export { isElementEqualOrContained } from '../js/utils/html.js';
|
|
150
|
+
export { getTargetElement, isElementEqualOrContained } from '../js/utils/html.js';
|
|
151
151
|
export { SPLIT_WORDS_REGEX, isNewQuery } from '../js/utils/is-new-query.js';
|
|
152
152
|
export { normalizeString } from '../js/utils/normalize.js';
|
|
153
153
|
export { isInRange } from '../js/utils/number.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
|
2
|
+
|
|
3
|
+
[Home](./index.md) > [@empathyco/x-components](./x-components.md) > [getTargetElement](./x-components.gettargetelement.md)
|
|
4
|
+
|
|
5
|
+
## getTargetElement() function
|
|
6
|
+
|
|
7
|
+
Returns the target element for a given event. The target element is obtained from `composedPath` Event method because if the event is triggered inside a Shadow DOM context, `event.target` points to Shadow DOM element, not the element that triggered the event. `composedPath` method also is available in a non-shadow DOM context.
|
|
8
|
+
|
|
9
|
+
<b>Signature:</b>
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
export declare function getTargetElement(event: Event): Element;
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Parameters
|
|
16
|
+
|
|
17
|
+
| Parameter | Type | Description |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| event | Event | Event which takes place in the DOM. |
|
|
20
|
+
|
|
21
|
+
<b>Returns:</b>
|
|
22
|
+
|
|
23
|
+
Element
|
|
24
|
+
|
|
25
|
+
Target Element of the event.
|
|
26
|
+
|
|
27
|
+
## Remarks
|
|
28
|
+
|
|
29
|
+
In a shadow DOM context, this function only works if the Shadow DOM uses `open` encapsulation mode.
|
|
30
|
+
|
|
@@ -177,6 +177,7 @@ X-Components is a library usable everywhere not only for search experiences.
|
|
|
177
177
|
| [flatHierarchicalFilters(hierarchicalFilters)](./x-components.flathierarchicalfilters.md) | This function flattens the Hierarchical Filters, returning an array with all filters including the children. |
|
|
178
178
|
| [getRootXComponent(component)](./x-components.getrootxcomponent.md) | Given a component, finds the root XComponent in the ancestors hierarchy. |
|
|
179
179
|
| [getStateAndGettersFromModule(state, getters, moduleName)](./x-components.getstateandgettersfrommodule.md) | Returns an object with the getters and state of a module of store defined by the moduleName parameter. |
|
|
180
|
+
| [getTargetElement(event)](./x-components.gettargetelement.md) | Returns the target element for a given event. The target element is obtained from <code>composedPath</code> Event method because if the event is triggered inside a Shadow DOM context, <code>event.target</code> points to Shadow DOM element, not the element that triggered the event. <code>composedPath</code> method also is available in a non-shadow DOM context. |
|
|
180
181
|
| [Getter(module, getter)](./x-components.getter.md) | Generates a computed property which returns the selected getter value.<!-- -->The decorated property needs to be public for type inference to work. |
|
|
181
182
|
| [groupItemsBy(array, groupBy)](./x-components.groupitemsby.md) | Groups the array items based on the provided <code>groupBy</code> function. |
|
|
182
183
|
| [isArrayEmpty(array)](./x-components.isarrayempty.md) | Returns if the given array is <code>null</code>, <code>undefined</code>, or has no elements. |
|
|
@@ -13,6 +13,15 @@ export default class StaggeringTransitionGroup extends Vue
|
|
|
13
13
|
```
|
|
14
14
|
<b>Extends:</b> Vue
|
|
15
15
|
|
|
16
|
+
## Properties
|
|
17
|
+
|
|
18
|
+
| Property | Modifiers | Type | Description |
|
|
19
|
+
| --- | --- | --- | --- |
|
|
20
|
+
| [moveClass](./x-components.staggeringtransitiongroup.moveclass.md) | | string | The CSS move class name. |
|
|
21
|
+
| [name](./x-components.staggeringtransitiongroup.name.md) | | string | The name of the transition. Used to generate the CSS classes. |
|
|
22
|
+
| [staggering](./x-components.staggeringtransitiongroup.staggering.md) | | number | The time in ms to stagger each item. |
|
|
23
|
+
| [tag](./x-components.staggeringtransitiongroup.tag.md) | | string | The tag of the node to render to the DOM. |
|
|
24
|
+
|
|
16
25
|
## Methods
|
|
17
26
|
|
|
18
27
|
| Method | Modifiers | Description |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
|
2
|
+
|
|
3
|
+
[Home](./index.md) > [@empathyco/x-components](./x-components.md) > [StaggeringTransitionGroup](./x-components.staggeringtransitiongroup.md) > [moveClass](./x-components.staggeringtransitiongroup.moveclass.md)
|
|
4
|
+
|
|
5
|
+
## StaggeringTransitionGroup.moveClass property
|
|
6
|
+
|
|
7
|
+
The CSS move class name.
|
|
8
|
+
|
|
9
|
+
<b>Signature:</b>
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
moveClass: string;
|
|
13
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
|
2
|
+
|
|
3
|
+
[Home](./index.md) > [@empathyco/x-components](./x-components.md) > [StaggeringTransitionGroup](./x-components.staggeringtransitiongroup.md) > [name](./x-components.staggeringtransitiongroup.name.md)
|
|
4
|
+
|
|
5
|
+
## StaggeringTransitionGroup.name property
|
|
6
|
+
|
|
7
|
+
The name of the transition. Used to generate the CSS classes.
|
|
8
|
+
|
|
9
|
+
<b>Signature:</b>
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
name: string;
|
|
13
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
|
2
|
+
|
|
3
|
+
[Home](./index.md) > [@empathyco/x-components](./x-components.md) > [StaggeringTransitionGroup](./x-components.staggeringtransitiongroup.md) > [staggering](./x-components.staggeringtransitiongroup.staggering.md)
|
|
4
|
+
|
|
5
|
+
## StaggeringTransitionGroup.staggering property
|
|
6
|
+
|
|
7
|
+
The time in ms to stagger each item.
|
|
8
|
+
|
|
9
|
+
<b>Signature:</b>
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
staggering: number;
|
|
13
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
|
2
|
+
|
|
3
|
+
[Home](./index.md) > [@empathyco/x-components](./x-components.md) > [StaggeringTransitionGroup](./x-components.staggeringtransitiongroup.md) > [tag](./x-components.staggeringtransitiongroup.tag.md)
|
|
4
|
+
|
|
5
|
+
## StaggeringTransitionGroup.tag property
|
|
6
|
+
|
|
7
|
+
The tag of the node to render to the DOM.
|
|
8
|
+
|
|
9
|
+
<b>Signature:</b>
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
tag: string;
|
|
13
|
+
```
|
|
@@ -33,7 +33,7 @@ __vue_render__._withStripped = true;
|
|
|
33
33
|
/* style */
|
|
34
34
|
const __vue_inject_styles__ = undefined;
|
|
35
35
|
/* scoped */
|
|
36
|
-
const __vue_scope_id__ = "data-v-
|
|
36
|
+
const __vue_scope_id__ = "data-v-6a081eff";
|
|
37
37
|
/* module identifier */
|
|
38
38
|
const __vue_module_identifier__ = undefined;
|
|
39
39
|
/* functional template */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staggered-fade-and-slide.vue.js","sources":["../../../../src/components/animations/staggered-fade-and-slide.vue"],"sourcesContent":["<template>\n <staggering-transition-group\n v-on=\"$listeners\"\n class=\"x-staggered-fade-and-slide\"\n :name=\"name\"\n v-bind=\"$attrs\"\n :appear=\"appear\"\n >\n <!-- @slot (Required) Transition-group content -->\n <slot />\n </staggering-transition-group>\n</template>\n\n<script lang=\"ts\">\n import { mixins } from 'vue-class-component';\n import { Component, Prop } from 'vue-property-decorator';\n import StaggeringTransitionGroup from '../animations/staggering-transition-group.vue';\n import DisableAnimationMixin from './disable-animation.mixin';\n\n /**\n * Renders a transition group wrapping the elements passed in the default slot and animating\n * them with an staggered fade and slide animation.\n *\n * @public\n */\n @Component({\n components: { StaggeringTransitionGroup },\n inheritAttrs: false\n })\n export default class StaggeredFadeAndSlide extends mixins(DisableAnimationMixin) {\n /**\n * Indicates if the transition must be applied on the initial render of the node.\n */\n @Prop({\n type: Boolean,\n default: true\n })\n public appear!: boolean;\n /**\n * The name of the animation.\n *\n * @public\n */\n protected animationName = 'x-staggered-fade-and-slide-';\n }\n</script>\n\n<style lang=\"scss\" scoped>\n $transition-duration: 0.25s;\n\n .x-staggered-fade-and-slide
|
|
1
|
+
{"version":3,"file":"staggered-fade-and-slide.vue.js","sources":["../../../../src/components/animations/staggered-fade-and-slide.vue"],"sourcesContent":["<template>\n <staggering-transition-group\n v-on=\"$listeners\"\n class=\"x-staggered-fade-and-slide\"\n :name=\"name\"\n v-bind=\"$attrs\"\n :appear=\"appear\"\n >\n <!-- @slot (Required) Transition-group content -->\n <slot />\n </staggering-transition-group>\n</template>\n\n<script lang=\"ts\">\n import { mixins } from 'vue-class-component';\n import { Component, Prop } from 'vue-property-decorator';\n import StaggeringTransitionGroup from '../animations/staggering-transition-group.vue';\n import DisableAnimationMixin from './disable-animation.mixin';\n\n /**\n * Renders a transition group wrapping the elements passed in the default slot and animating\n * them with an staggered fade and slide animation.\n *\n * @public\n */\n @Component({\n components: { StaggeringTransitionGroup },\n inheritAttrs: false\n })\n export default class StaggeredFadeAndSlide extends mixins(DisableAnimationMixin) {\n /**\n * Indicates if the transition must be applied on the initial render of the node.\n */\n @Prop({\n type: Boolean,\n default: true\n })\n public appear!: boolean;\n /**\n * The name of the animation.\n *\n * @public\n */\n protected animationName = 'x-staggered-fade-and-slide-';\n }\n</script>\n\n<style lang=\"scss\" scoped>\n $transition-duration: 0.25s;\n\n .x-staggered-fade-and-slide {\n z-index: 0;\n\n &::v-deep .x-staggered-fade-and-slide {\n &--enter-active,\n &--leave-active {\n transition: $transition-duration ease-out;\n transition-property: opacity, transform;\n }\n\n &--move {\n transition: transform $transition-duration ease-out;\n }\n\n &--enter,\n &--leave-to {\n transform: translate3d(0, 50%, 0);\n opacity: 0;\n z-index: -1;\n }\n }\n }\n</style>\n\n<docs lang=\"mdx\">\nThe Staggered fade and slide components works as the normal fade and slide components, but it also\nadds a configurable delay to each transition.\n\n## Example\n\n### Used with animatable components\n\n```vue\n<AnimatableComponent :animation=\"StaggeredFadeAndSlide\" />\n```\n\n### Used as a regular component:\n\nThis components exposes all the props and events of the Staggering transition group, like the `tag`\nor the `stagger` props:\n\n```vue\n<StaggeredFadeAndSlide tag=\"ul\" :stagger=\"50\">\n <li>Element to animate</li>\n <li>Element to animate</li>\n <li>Element to animate</li>\n</StaggeredFadeAndSlide>\n```\n</docs>\n"],"names":[],"mappings":";;;;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/js/components/animations/staggered-fade-and-slide.vue_rollup-plugin-vue_styles.0.vue.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createInjector, createInjectorSSR } from 'vue-runtime-helpers';
|
|
2
2
|
|
|
3
|
-
var css = ".x-staggered-fade-and-slide[data-v-
|
|
3
|
+
var css = ".x-staggered-fade-and-slide[data-v-6a081eff] {\n z-index: 0;\n}\n.x-staggered-fade-and-slide[data-v-6a081eff] .x-staggered-fade-and-slide--enter-active, .x-staggered-fade-and-slide[data-v-6a081eff] .x-staggered-fade-and-slide--leave-active {\n transition: 0.25s ease-out;\n transition-property: opacity, transform;\n}\n.x-staggered-fade-and-slide[data-v-6a081eff] .x-staggered-fade-and-slide--move {\n transition: transform 0.25s ease-out;\n}\n.x-staggered-fade-and-slide[data-v-6a081eff] .x-staggered-fade-and-slide--enter, .x-staggered-fade-and-slide[data-v-6a081eff] .x-staggered-fade-and-slide--leave-to {\n transform: translate3d(0, 50%, 0);\n opacity: 0;\n z-index: -1;\n}";
|
|
4
4
|
const isBrowser = /*#__PURE__*/ (function () {
|
|
5
5
|
return (
|
|
6
6
|
Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) !==
|
|
@@ -9,7 +9,7 @@ const __vue_script__ = script;
|
|
|
9
9
|
/* style */
|
|
10
10
|
const __vue_inject_styles__ = undefined;
|
|
11
11
|
/* scoped */
|
|
12
|
-
const __vue_scope_id__ = "data-v-
|
|
12
|
+
const __vue_scope_id__ = "data-v-5eaf2336";
|
|
13
13
|
/* module identifier */
|
|
14
14
|
const __vue_module_identifier__ = undefined;
|
|
15
15
|
/* functional template */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staggering-transition-group.vue.js","sources":["../../../../src/components/animations/staggering-transition-group.vue"],"sourcesContent":["<script lang=\"ts\">\n import Vue, { CreateElement, VNode } from 'vue';\n import { Component, Prop } from 'vue-property-decorator';\n import { noOp } from '../../utils';\n\n /* eslint-disable @typescript-eslint/unbound-method */\n /**\n * A replacement component for Vue's transition-group, that also adds the option to stagger\n * the animations.\n *\n * @public\n */\n @Component\n export default class StaggeringTransitionGroup extends Vue {\n /**\n * The name of the transition. Used to generate the CSS classes.\n *\n * @internal\n */\n @Prop({ default: 'v' })\n public name!: string;\n\n /** The CSS move class name.\n *\n * @internal\n */\n @Prop()\n public moveClass!: string;\n\n /**\n * The tag of the node to render to the DOM.\n *\n * @internal\n */\n @Prop({ default: 'div' })\n public tag!: string;\n\n /**\n * The time in ms to stagger each item.\n *\n * @internal\n */\n @Prop({ default: 25 })\n public staggering!: number;\n\n /**\n * The CSS class for the moving transitions.\n *\n * @returns The move transition name.\n * @internal\n */\n protected get moveClassName(): string {\n return this.moveClass ?? `${this.name}-move`;\n }\n\n /**\n * The transition data contains the needed events and props to perform a transition using Vue\n * virtual node's API.\n *\n * The `beforeLeave` hook is extended to also restore the previous position of the element using\n * the position absolute.\n * The `afterEnter` and `afterLeave` hooks are extended to also clean the transition delay\n * applied by the stagger.\n *\n * @returns The transition data for Vue virtual nodes.\n * @internal\n */\n protected get transitionData(): TransitionData {\n const transitionData: TransitionData = { ...this.$props, ...this.$attrs, ...this.$listeners };\n\n transitionData.beforeLeave = this.addRestorePositionHook(transitionData.beforeLeave);\n transitionData.afterEnter = this.addClearStaggeringCall(transitionData.afterEnter);\n transitionData.afterLeave = this.addClearStaggeringCall(transitionData.afterLeave);\n\n return transitionData;\n }\n\n /** The list of old virtual nodes, generated by the previous called render method.\n *\n * @internal */\n protected oldChildren!: TransitionVNode[];\n /** The list of new virtual nodes, generated by the last called render method.\n *\n * @internal */\n protected newChildren!: TransitionVNode[];\n /** A map containing the previous positions relative to the container, for each item\n * rendered inside the slot of this component component.\n * This is used together with the `newPositions` to calculate the move transition.\n *\n * @internal */\n protected oldPositions!: WeakMap<Element, Bounds>;\n /** A map containing the new positions relative to the container, for each item\n * rendered inside the slot of this component component.\n * This is used together with the `newPositions` to calculate the move transition.\n *\n * @internal */\n protected newPositions!: WeakMap<Element, Bounds>;\n /** A map containing the move cleanup functions pending to have been called. When invoked\n * this functions remove all the styles and classes associated to the move transition.\n *\n * @internal */\n protected pendingCleanupMoveCallbacks!: WeakMap<Element, () => void>;\n /** The counter for the stagger, used to calculate the delay for the transition of each child\n * element. It is reset every time the render method is triggered.\n *\n * @internal */\n protected staggerCounter!: number;\n /** The bounds of the container rendered using the `tag` prop. This is used to calculate the\n * relative positions of each leaving child, which are then applied with the position absolute.\n *\n * @internal */\n protected wrapperBounds!: DOMRect;\n\n beforeCreate(): void {\n /* Initialize properties here to avoid making them reactive,\n which would cause infinite loops */\n this.oldChildren = [];\n this.newChildren = [];\n this.oldPositions = new WeakMap();\n this.newPositions = new WeakMap();\n this.pendingCleanupMoveCallbacks = new WeakMap<Element, () => void>();\n this.staggerCounter = 0;\n }\n\n render(createElement: CreateElement): VNode {\n this.staggerCounter = 0;\n // New children are now the old ones\n this.oldChildren = this.newChildren;\n // Only vnodes with a tag (i.e. no HTML comments) and with a `key` property are valid.\n this.newChildren = (this.$slots.default ?? []).filter(this.isTransitionValidVNode);\n\n // Apply transition data to both new and old nodes & store the position of the old nodes.\n this.newChildren.forEach(this.addTransitionData);\n this.oldChildren.forEach(this.syncOldNodes);\n\n return createElement(\n this.tag,\n { staticClass: 'x-staggering-transition-group' },\n this.newChildren\n );\n }\n\n mounted(): void {\n this.newChildren.forEach(this.applyStagger);\n }\n\n beforeUpdate(): void {\n this.wrapperBounds = this.$el.getBoundingClientRect();\n }\n\n updated(): void {\n this.wrapperBounds = this.$el.getBoundingClientRect();\n this.newChildren.forEach(this.recordNewPosition);\n const { leavingNodes, stayingNodes, enteringNodes } = this.getNodesByTransitionType();\n\n leavingNodes.forEach(vNode => {\n this.applyStagger(vNode);\n this.disableClickingEvents(vNode);\n });\n const movedChildren = stayingNodes.filter(this.applyTranslation);\n const movedStagger = movedChildren.map(this.getNextTransitionDelay);\n enteringNodes.forEach(this.applyStagger);\n\n // force reflow to put everything in position\n document.body.getBoundingClientRect();\n\n movedChildren.forEach(this.startMoveAnimation(movedStagger));\n }\n\n /**\n * Extends the provided leave transition hook restoring the position of the element with an\n * absolute position.\n * Additionally, it removes the element position from the maps of positions.\n *\n * @param transitionHook - The leave transition hook to extend.\n * @returns The new leave transition hook extended.\n * @internal\n */\n protected addRestorePositionHook(transitionHook: TransitionHook = noOp): TransitionHook {\n return element => {\n const { top, left, width, height } = this.oldPositions.get(element)!;\n const { marginTop, marginLeft } = window.getComputedStyle(element);\n const style = element.style;\n style.position = 'absolute';\n style.top = `${top - parseFloat(marginTop)}px`;\n style.left = `${left - parseFloat(marginLeft)}px`;\n style.width = `${width}px`;\n style.height = `${height}px`;\n this.newPositions.delete(element);\n this.oldPositions.delete(element);\n const pendingCallback = this.pendingCleanupMoveCallbacks.get(element);\n pendingCallback?.();\n transitionHook(element);\n };\n }\n\n /**\n * Extends the provided transition hook clearing the transition delay.\n *\n * @param transitionHook - The transition hook to extend.\n * @returns The new transition hook, that also clears the transitionDelay from the element.\n * @internal\n */\n protected addClearStaggeringCall(transitionHook: TransitionHook = noOp): TransitionHook {\n return element => {\n element.style.transitionDelay = '';\n transitionHook(element);\n };\n }\n\n /**\n * Returns if the vNode contains a non empty key, and a non empty tag.\n *\n * @param vNode - The VNode to check if it is a valid transition node, containing a `tag` and a\n * `key` property.\n * @returns True when the vNode contains a non empty key and a non empty tag. False otherwise.\n * @internal\n */\n protected isTransitionValidVNode(vNode: VNode): vNode is TransitionVNode {\n // TODO Add warning with logger: <staggering-transition-group> children must be keyed.\n return !!vNode.key && !!vNode.tag;\n }\n\n /**\n * Adds the generated transition data to the vNode, creating the `data` property if necessary.\n *\n * @param vNode - The VNode to add the transition data to.\n * @internal\n */\n protected addTransitionData(vNode: TransitionVNode): void {\n if (!vNode.data) {\n vNode.data = {};\n }\n vNode.data.transition = this.transitionData;\n }\n\n /**\n * Re-applies the transition data to an old node, just in case it changed from the previous\n * render call. It also records the position of the node, to then calculate the move\n * transitions.\n *\n * @param vNode - The vNode to add the transition data to, and record his current position as\n * old.\n * @internal\n */\n protected syncOldNodes(vNode: TransitionVNode): void {\n // Synchronize transition data, in case it changed in the last frame.\n // We can trust data to be defined because each new node has the transition applied\n vNode.data!.transition = this.transitionData;\n this.recordOldPosition(vNode);\n }\n\n /**\n * Saves the position of the vNode in the map of old positions.\n *\n * @param vNode - The node to store its position.\n * @internal\n */\n protected recordOldPosition(vNode: TransitionVNode): void {\n this.oldPositions.set(vNode.elm, this.createRelativeBounds(vNode));\n }\n\n /**\n * Saves the position of the vNode in the map of new positions.\n *\n * @param vNode - The node to store its position.\n * @internal\n */\n protected recordNewPosition(vNode: TransitionVNode): void {\n this.newPositions.set(vNode.elm, this.createRelativeBounds(vNode));\n }\n\n /**\n * Creates an object containing the position of the vNode relative to its container.\n *\n * @param vNode - The virtual node to store its relative position.\n * @returns The relative bounds of the provided virtual node.\n * @internal\n */\n protected createRelativeBounds(vNode: TransitionVNode): Bounds {\n const { left, top, width, height } = vNode.elm.getBoundingClientRect();\n const { left: wrapperLeft, top: wrapperTop } = this.wrapperBounds;\n return {\n left: left - wrapperLeft,\n top: top - wrapperTop,\n width,\n height\n };\n }\n\n /**\n * Splits the children of the component into three groups:\n * - Nodes that are leaving.\n * - Nodes that are entering.\n * - Nodes that stay.\n *\n * This is then used to apply the stagger in the correct order: leave -\\> move -\\> enter.\n *\n * @returns The children nodes, divided in different groups depending on if they are leaving,\n * staying or entering.\n * @internal\n */\n protected getNodesByTransitionType(): TransitionTypeNodes {\n const leave = this.oldChildren.filter(child => !this.newPositions.has(child.elm));\n const enter = this.newChildren.filter(child => !this.oldPositions.has(child.elm));\n const stay = this.oldChildren.filter(child => this.newPositions.has(child.elm));\n return {\n leavingNodes: leave,\n enteringNodes: enter,\n stayingNodes: stay\n };\n }\n\n /**\n * Applies an incremental delay to the virtual node element.\n *\n * @param vNode - The virtual node to apply the stagger to.\n * @internal\n */\n protected applyStagger(vNode: TransitionVNode): void {\n vNode.elm.style.transitionDelay = this.getNextTransitionDelay();\n }\n\n /**\n * Disables listening to click events in a virtual node element.\n *\n * @remarks This is done to avoid letting the user click elements that are performing the moving\n * animation to leave the DOM but are still rendered.\n *\n * @param vNode - The virtual node to disable listening to click events.\n * @internal\n */\n protected disableClickingEvents(vNode: TransitionVNode): void {\n vNode.elm.style.pointerEvents = 'none';\n }\n\n /**\n * Calculates the next transition delay property, incrementing the `staggerCounter` property\n * each time it is called.\n *\n * @returns The value for the next element `style.transitionDelay` property.\n * @internal\n */\n protected getNextTransitionDelay(): string {\n return `${this.staggerCounter++ * this.staggering}ms`;\n }\n\n /**\n * Calculates if the virtual node should have a move transition. If its has it, then it\n * applies it immediately using the `style.transform`.\n *\n * @param vNode - The virtual node to calculate if it should have a move transition.\n * @returns True when a move transition was applied to the virtual node.\n * @internal\n */\n protected applyTranslation(vNode: TransitionVNode): boolean {\n const oldPosition = this.oldPositions.get(vNode.elm as Element)!;\n const newPosition = this.newPositions.get(vNode.elm as Element)!;\n const dx = oldPosition.left - newPosition.left;\n const dy = oldPosition.top - newPosition.top;\n if (dx !== 0 || dy !== 0) {\n const style = vNode.elm.style;\n style.transform = `translate3d(${dx}px,${dy}px,0)`;\n style.transitionDuration = '0s';\n return true;\n }\n return false;\n }\n\n /**\n * Generates a function to start the moving animations to each node that it needs them with the\n * provided stagger.\n *\n * @param moveStagger - A list containing the delay to add to each node.\n * @returns A function that starts the moving animation with the provided stagger to a single\n * virtual node.\n * @internal\n */\n protected startMoveAnimation(\n moveStagger: string[]\n ): (vNode: TransitionVNode, index: number) => void {\n return (vNode, index) => {\n const element = vNode.elm;\n const style = element.style;\n element.classList.add(this.moveClassName);\n style.transform = style.transitionDuration = '';\n style.transitionDelay = moveStagger[index];\n const cleanMoveTransition = (event?: TransitionEvent): void => {\n if (!event || (event.target === element && /transform$/.test(event.propertyName))) {\n element.removeEventListener('transitionend', cleanMoveTransition);\n element.style.transitionDelay = '';\n element.classList.remove(this.moveClassName);\n this.pendingCleanupMoveCallbacks.delete(element);\n }\n };\n this.pendingCleanupMoveCallbacks.set(element, cleanMoveTransition);\n element.addEventListener('transitionend', cleanMoveTransition);\n };\n }\n }\n\n /**\n * Contains arrays of nodes, splitted by the action they should have.\n */\n interface TransitionTypeNodes {\n leavingNodes: TransitionVNode[];\n stayingNodes: TransitionVNode[];\n enteringNodes: TransitionVNode[];\n }\n\n /**\n * Safe transition version of the VNode type, with the required non optional properties.\n */\n interface TransitionVNode extends VNode {\n elm: HTMLElement;\n tag: string;\n key: string;\n }\n\n /**\n * Vue's VNode {@link https://vuejs.org/v2/api/#transition | transition} data props and events.\n */\n interface TransitionData {\n name?: string;\n appear?: boolean;\n css?: boolean;\n type?: 'transition' | 'animation'; // Unused\n mode?: 'out-in' | 'in-out'; // Unused\n duration?: number;\n enterClass?: string;\n leaveClass?: string;\n appearClass?: string;\n enterToClass?: string;\n leaveToClass?: string;\n appearToClass?: string;\n enterActiveClass?: string;\n leaveActiveClass?: string;\n appearActiveClass?: string;\n beforeEnter?: TransitionHook;\n enter?: TransitionHook;\n afterEnter?: TransitionHook;\n beforeLeave?: TransitionHook;\n leave?: TransitionHook;\n afterLeave?: TransitionHook;\n }\n\n /**\n * A function that receives an HTMLElement. Used to perform actions when the different phases\n * of Vue transitions happens.\n */\n type TransitionHook = (element: HTMLElement) => void;\n\n /**\n * Represents the dimensions and positions of an element.\n */\n interface Bounds {\n top: number;\n left: number;\n width: number;\n height: number;\n }\n</script>\n\n<style lang=\"scss\" scoped>\n .x-staggering-transition-group {\n position: relative;\n }\n</style>\n\n<docs lang=\"mdx\">\n## Examples\n\n### Basic example\n\nApart from all the props and events that the classic transition group has, the staggering transition\ngroup also exposes a new `stagger` property, which allows to configure the delay for each one of the\nnodes when animating.\n\n```vue\n<staggering-transition-group appear :stagger=\"50\" name=\"staggered-fade-slide-\">\n <!-- @slot (Required) Transition-group content -->\n <slot />\n</staggering-transition-group>\n```\n</docs>\n"],"names":[],"mappings":";;;;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"staggering-transition-group.vue.js","sources":["../../../../src/components/animations/staggering-transition-group.vue"],"sourcesContent":["<script lang=\"ts\">\n import Vue, { CreateElement, VNode } from 'vue';\n import { Component, Prop } from 'vue-property-decorator';\n import { noOp } from '../../utils';\n\n /* eslint-disable @typescript-eslint/unbound-method */\n /**\n * A replacement component for Vue's transition-group, that also adds the option to stagger\n * the animations.\n *\n * @public\n */\n @Component\n export default class StaggeringTransitionGroup extends Vue {\n /**\n * The name of the transition. Used to generate the CSS classes.\n *\n * @public\n */\n @Prop({ default: 'v' })\n public name!: string;\n\n /**\n * The CSS move class name.\n *\n * @public\n */\n @Prop()\n public moveClass!: string;\n\n /**\n * The tag of the node to render to the DOM.\n *\n * @public\n */\n @Prop({ default: 'div' })\n public tag!: string;\n\n /**\n * The time in ms to stagger each item.\n *\n * @public\n */\n @Prop({ default: 25 })\n public staggering!: number;\n\n /**\n * The CSS class for the moving transitions.\n *\n * @returns The move transition name.\n * @internal\n */\n protected get moveClassName(): string {\n return this.moveClass ?? `${this.name}-move`;\n }\n\n /**\n * The transition data contains the needed events and props to perform a transition using Vue\n * virtual node's API.\n *\n * The `beforeLeave` hook is extended to also restore the previous position of the element using\n * the position absolute.\n * The `afterEnter` and `afterLeave` hooks are extended to also clean the transition delay\n * applied by the stagger.\n *\n * @returns The transition data for Vue virtual nodes.\n * @internal\n */\n protected get transitionData(): TransitionData {\n const transitionData: TransitionData = { ...this.$props, ...this.$attrs, ...this.$listeners };\n\n transitionData.beforeLeave = this.addRestorePositionHook(transitionData.beforeLeave);\n transitionData.afterEnter = this.addClearStaggeringCall(transitionData.afterEnter);\n transitionData.afterLeave = this.addClearStaggeringCall(transitionData.afterLeave);\n\n return transitionData;\n }\n\n /**\n * The list of old virtual nodes, generated by the previous called render method.\n *\n * @internal\n */\n protected oldChildren!: TransitionVNode[];\n /**\n * The list of new virtual nodes, generated by the last called render method.\n *\n * @internal\n */\n protected newChildren!: TransitionVNode[];\n /**\n * A map containing the previous positions relative to the container, for each item\n * rendered inside the slot of this component.\n * This is used together with the `newPositions` to calculate the move transition.\n *\n * @internal\n */\n protected oldPositions!: WeakMap<Element, Bounds>;\n /**\n * A map containing the new positions relative to the container, for each item\n * rendered inside the slot of this component.\n * This is used together with the `newPositions` to calculate the move transition.\n *\n * @internal\n */\n protected newPositions!: WeakMap<Element, Bounds>;\n /**\n * A map containing the move cleanup functions pending to have been called. When invoked\n * this functions remove all the styles and classes associated to the move transition.\n *\n * @internal\n */\n protected pendingCleanupMoveCallbacks!: WeakMap<Element, () => void>;\n /**\n * The counter for the stagger, used to calculate the delay for the transition of each child\n * element. It is reset every time the render method is triggered.\n *\n * @internal\n */\n protected staggerCounter!: number;\n /**\n * The bounds of the container rendered using the `tag` prop. This is used to calculate the\n * relative positions of each leaving child, which are then applied with the position absolute.\n *\n * @internal\n */\n protected wrapperBounds!: DOMRect;\n\n beforeCreate(): void {\n // Initialize properties here to avoid making them reactive, which would cause infinite loops.\n this.oldChildren = [];\n this.newChildren = [];\n this.oldPositions = new WeakMap();\n this.newPositions = new WeakMap();\n this.pendingCleanupMoveCallbacks = new WeakMap<Element, () => void>();\n this.staggerCounter = 0;\n }\n\n render(createElement: CreateElement): VNode {\n this.staggerCounter = 0;\n // New children are now the old ones\n this.oldChildren = this.newChildren;\n // Only vnodes with a tag (i.e. no HTML comments) and with a `key` property are valid.\n this.newChildren = (this.$slots.default ?? []).filter(this.isTransitionValidVNode);\n\n // Apply transition data to both new and old nodes & store the position of the old nodes.\n this.newChildren.forEach(this.addTransitionData);\n this.oldChildren.forEach(this.syncOldNodes);\n\n return createElement(\n this.tag,\n { staticClass: 'x-staggering-transition-group' },\n this.newChildren\n );\n }\n\n mounted(): void {\n this.newChildren.forEach(this.applyStagger);\n }\n\n beforeUpdate(): void {\n this.wrapperBounds = this.$el.getBoundingClientRect();\n }\n\n updated(): void {\n this.wrapperBounds = this.$el.getBoundingClientRect();\n this.newChildren.forEach(this.recordNewPosition);\n const { leavingNodes, stayingNodes, enteringNodes } = this.getNodesByTransitionType();\n\n leavingNodes.forEach(vNode => {\n this.applyStagger(vNode);\n this.disableClickingEvents(vNode);\n });\n const movedChildren = stayingNodes.filter(this.applyTranslation);\n const movedStagger = movedChildren.map(this.getNextTransitionDelay);\n enteringNodes.forEach(this.applyStagger);\n\n // force reflow to put everything in position\n document.body.getBoundingClientRect();\n\n movedChildren.forEach(this.startMoveAnimation(movedStagger));\n }\n\n /**\n * Extends the provided leave transition hook restoring the position of the element with an\n * absolute position.\n * Additionally, it removes the element position from the maps of positions.\n *\n * @param transitionHook - The leave transition hook to extend.\n * @returns The new leave transition hook extended.\n * @internal\n */\n protected addRestorePositionHook(transitionHook: TransitionHook = noOp): TransitionHook {\n return element => {\n const { top, left, width, height } = this.oldPositions.get(element)!;\n const { marginTop, marginLeft } = window.getComputedStyle(element);\n const style = element.style;\n style.position = 'absolute';\n style.top = `${top - parseFloat(marginTop)}px`;\n style.left = `${left - parseFloat(marginLeft)}px`;\n style.width = `${width}px`;\n style.height = `${height}px`;\n this.newPositions.delete(element);\n this.oldPositions.delete(element);\n const pendingCallback = this.pendingCleanupMoveCallbacks.get(element);\n pendingCallback?.();\n transitionHook(element);\n };\n }\n\n /**\n * Extends the provided transition hook clearing the transition delay.\n *\n * @param transitionHook - The transition hook to extend.\n * @returns The new transition hook, that also clears the transitionDelay from the element.\n * @internal\n */\n protected addClearStaggeringCall(transitionHook: TransitionHook = noOp): TransitionHook {\n return element => {\n element.style.transitionDelay = '';\n transitionHook(element);\n };\n }\n\n /**\n * Returns if the vNode contains a non empty key, and a non empty tag.\n *\n * @param vNode - The VNode to check if it is a valid transition node, containing a `tag` and a\n * `key` property.\n * @returns True when the vNode contains a non empty key and a non empty tag. False otherwise.\n * @internal\n */\n protected isTransitionValidVNode(vNode: VNode): vNode is TransitionVNode {\n // TODO Add warning with logger: <staggering-transition-group> children must be keyed.\n return !!vNode.key && !!vNode.tag;\n }\n\n /**\n * Adds the generated transition data to the vNode, creating the `data` property if necessary.\n *\n * @param vNode - The VNode to add the transition data to.\n * @internal\n */\n protected addTransitionData(vNode: TransitionVNode): void {\n if (!vNode.data) {\n vNode.data = {};\n }\n vNode.data.transition = this.transitionData;\n }\n\n /**\n * Re-applies the transition data to an old node, just in case it changed from the previous\n * render call. It also records the position of the node, to then calculate the move\n * transitions.\n *\n * @param vNode - The vNode to add the transition data to, and record his current position as\n * old.\n * @internal\n */\n protected syncOldNodes(vNode: TransitionVNode): void {\n // Synchronize transition data, in case it changed in the last frame.\n // We can trust data to be defined because each new node has the transition applied\n vNode.data!.transition = this.transitionData;\n this.recordOldPosition(vNode);\n }\n\n /**\n * Saves the position of the vNode in the map of old positions.\n *\n * @param vNode - The node to store its position.\n * @internal\n */\n protected recordOldPosition(vNode: TransitionVNode): void {\n this.oldPositions.set(vNode.elm, this.createRelativeBounds(vNode));\n }\n\n /**\n * Saves the position of the vNode in the map of new positions.\n *\n * @param vNode - The node to store its position.\n * @internal\n */\n protected recordNewPosition(vNode: TransitionVNode): void {\n this.newPositions.set(vNode.elm, this.createRelativeBounds(vNode));\n }\n\n /**\n * Creates an object containing the position of the vNode relative to its container.\n *\n * @param vNode - The virtual node to store its relative position.\n * @returns The relative bounds of the provided virtual node.\n * @internal\n */\n protected createRelativeBounds(vNode: TransitionVNode): Bounds {\n const { left, top, width, height } = vNode.elm.getBoundingClientRect();\n const { left: wrapperLeft, top: wrapperTop } = this.wrapperBounds;\n return {\n left: left - wrapperLeft,\n top: top - wrapperTop,\n width,\n height\n };\n }\n\n /**\n * Splits the children of the component into three groups:\n * - Nodes that are leaving.\n * - Nodes that are entering.\n * - Nodes that stay.\n *\n * This is then used to apply the stagger in the correct order: leave -\\> move -\\> enter.\n *\n * @returns The children nodes, divided in different groups depending on if they are leaving,\n * staying or entering.\n * @internal\n */\n protected getNodesByTransitionType(): TransitionTypeNodes {\n const leave = this.oldChildren.filter(child => !this.newPositions.has(child.elm));\n const enter = this.newChildren.filter(child => !this.oldPositions.has(child.elm));\n const stay = this.oldChildren.filter(child => this.newPositions.has(child.elm));\n return {\n leavingNodes: leave,\n enteringNodes: enter,\n stayingNodes: stay\n };\n }\n\n /**\n * Applies an incremental delay to the virtual node element.\n *\n * @param vNode - The virtual node to apply the stagger to.\n * @internal\n */\n protected applyStagger(vNode: TransitionVNode): void {\n vNode.elm.style.transitionDelay = this.getNextTransitionDelay();\n }\n\n /**\n * Disables listening to click events in a virtual node element.\n *\n * @remarks This is done to avoid letting the user click elements that are performing the moving\n * animation to leave the DOM but are still rendered.\n *\n * @param vNode - The virtual node to disable listening to click events.\n * @internal\n */\n protected disableClickingEvents(vNode: TransitionVNode): void {\n vNode.elm.style.pointerEvents = 'none';\n }\n\n /**\n * Calculates the next transition delay property, incrementing the `staggerCounter` property\n * each time it is called.\n *\n * @returns The value for the next element `style.transitionDelay` property.\n * @internal\n */\n protected getNextTransitionDelay(): string {\n return `${this.staggerCounter++ * this.staggering}ms`;\n }\n\n /**\n * Calculates if the virtual node should have a move transition. If its has it, then it\n * applies it immediately using the `style.transform`.\n *\n * @param vNode - The virtual node to calculate if it should have a move transition.\n * @returns True when a move transition was applied to the virtual node.\n * @internal\n */\n protected applyTranslation(vNode: TransitionVNode): boolean {\n const oldPosition = this.oldPositions.get(vNode.elm as Element)!;\n const newPosition = this.newPositions.get(vNode.elm as Element)!;\n const dx = oldPosition.left - newPosition.left;\n const dy = oldPosition.top - newPosition.top;\n if (dx !== 0 || dy !== 0) {\n const style = vNode.elm.style;\n style.transform = `translate3d(${dx}px,${dy}px,0)`;\n style.transitionDuration = '0s';\n return true;\n }\n return false;\n }\n\n /**\n * Generates a function to start the moving animations to each node that it needs them with the\n * provided stagger.\n *\n * @param moveStagger - A list containing the delay to add to each node.\n * @returns A function that starts the moving animation with the provided stagger to a single\n * virtual node.\n * @internal\n */\n protected startMoveAnimation(\n moveStagger: string[]\n ): (vNode: TransitionVNode, index: number) => void {\n return (vNode, index) => {\n const element = vNode.elm;\n const style = element.style;\n element.classList.add(this.moveClassName);\n style.transform = style.transitionDuration = '';\n style.transitionDelay = moveStagger[index];\n const cleanMoveTransition = (event?: TransitionEvent): void => {\n if (!event || (event.target === element && /transform$/.test(event.propertyName))) {\n element.removeEventListener('transitionend', cleanMoveTransition);\n element.style.transitionDelay = '';\n element.classList.remove(this.moveClassName);\n this.pendingCleanupMoveCallbacks.delete(element);\n }\n };\n this.pendingCleanupMoveCallbacks.set(element, cleanMoveTransition);\n element.addEventListener('transitionend', cleanMoveTransition);\n };\n }\n }\n\n /**\n * Contains arrays of nodes, splitted by the action they should have.\n */\n interface TransitionTypeNodes {\n leavingNodes: TransitionVNode[];\n stayingNodes: TransitionVNode[];\n enteringNodes: TransitionVNode[];\n }\n\n /**\n * Safe transition version of the VNode type, with the required non optional properties.\n */\n interface TransitionVNode extends VNode {\n elm: HTMLElement;\n tag: string;\n key: string;\n }\n\n /**\n * Vue's VNode {@link https://vuejs.org/v2/api/#transition | transition} data props and events.\n */\n interface TransitionData {\n name?: string;\n appear?: boolean;\n css?: boolean;\n type?: 'transition' | 'animation'; // Unused\n mode?: 'out-in' | 'in-out'; // Unused\n duration?: number;\n enterClass?: string;\n leaveClass?: string;\n appearClass?: string;\n enterToClass?: string;\n leaveToClass?: string;\n appearToClass?: string;\n enterActiveClass?: string;\n leaveActiveClass?: string;\n appearActiveClass?: string;\n beforeEnter?: TransitionHook;\n enter?: TransitionHook;\n afterEnter?: TransitionHook;\n beforeLeave?: TransitionHook;\n leave?: TransitionHook;\n afterLeave?: TransitionHook;\n }\n\n /**\n * A function that receives an HTMLElement. Used to perform actions when the different phases\n * of Vue transitions happens.\n */\n type TransitionHook = (element: HTMLElement) => void;\n\n /**\n * Represents the dimensions and positions of an element.\n */\n interface Bounds {\n top: number;\n left: number;\n width: number;\n height: number;\n }\n</script>\n\n<style lang=\"scss\" scoped>\n .x-staggering-transition-group {\n position: relative;\n }\n</style>\n\n<docs lang=\"mdx\">\n## Examples\n\n### Basic example\n\nApart from all the props and events that the classic transition group has, the staggering transition\ngroup also exposes a new `stagger` property, which allows to configure the delay for each one of the\nnodes when animating.\n\n```vue\n<staggering-transition-group appear :stagger=\"50\" name=\"staggered-fade-slide-\">\n <!-- @slot (Required) Transition-group content -->\n <slot />\n</staggering-transition-group>\n```\n</docs>\n"],"names":[],"mappings":";;;;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/js/components/animations/staggering-transition-group.vue_rollup-plugin-vue_script.vue.js
CHANGED
|
@@ -41,8 +41,7 @@ let StaggeringTransitionGroup = class StaggeringTransitionGroup extends Vue {
|
|
|
41
41
|
return transitionData;
|
|
42
42
|
}
|
|
43
43
|
beforeCreate() {
|
|
44
|
-
|
|
45
|
-
which would cause infinite loops */
|
|
44
|
+
// Initialize properties here to avoid making them reactive, which would cause infinite loops.
|
|
46
45
|
this.oldChildren = [];
|
|
47
46
|
this.newChildren = [];
|
|
48
47
|
this.oldPositions = new WeakMap();
|
package/js/components/animations/staggering-transition-group.vue_rollup-plugin-vue_script.vue.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staggering-transition-group.vue_rollup-plugin-vue_script.vue.js","sources":["../../../../src/components/animations/staggering-transition-group.vue?rollup-plugin-vue=script.ts"],"sourcesContent":["\nimport Vue, { CreateElement, VNode } from 'vue';\nimport { Component, Prop } from 'vue-property-decorator';\nimport { noOp } from '../../utils';\n\n/* eslint-disable @typescript-eslint/unbound-method */\n/**\n * A replacement component for Vue's transition-group, that also adds the option to stagger\n * the animations.\n *\n * @public\n */\n@Component\nexport default class StaggeringTransitionGroup extends Vue {\n /**\n * The name of the transition. Used to generate the CSS classes.\n *\n * @internal\n */\n @Prop({ default: 'v' })\n public name!: string;\n\n /** The CSS move class name.\n *\n * @internal\n */\n @Prop()\n public moveClass!: string;\n\n /**\n * The tag of the node to render to the DOM.\n *\n * @internal\n */\n @Prop({ default: 'div' })\n public tag!: string;\n\n /**\n * The time in ms to stagger each item.\n *\n * @internal\n */\n @Prop({ default: 25 })\n public staggering!: number;\n\n /**\n * The CSS class for the moving transitions.\n *\n * @returns The move transition name.\n * @internal\n */\n protected get moveClassName(): string {\n return this.moveClass ?? `${this.name}-move`;\n }\n\n /**\n * The transition data contains the needed events and props to perform a transition using Vue\n * virtual node's API.\n *\n * The `beforeLeave` hook is extended to also restore the previous position of the element using\n * the position absolute.\n * The `afterEnter` and `afterLeave` hooks are extended to also clean the transition delay\n * applied by the stagger.\n *\n * @returns The transition data for Vue virtual nodes.\n * @internal\n */\n protected get transitionData(): TransitionData {\n const transitionData: TransitionData = { ...this.$props, ...this.$attrs, ...this.$listeners };\n\n transitionData.beforeLeave = this.addRestorePositionHook(transitionData.beforeLeave);\n transitionData.afterEnter = this.addClearStaggeringCall(transitionData.afterEnter);\n transitionData.afterLeave = this.addClearStaggeringCall(transitionData.afterLeave);\n\n return transitionData;\n }\n\n /** The list of old virtual nodes, generated by the previous called render method.\n *\n * @internal */\n protected oldChildren!: TransitionVNode[];\n /** The list of new virtual nodes, generated by the last called render method.\n *\n * @internal */\n protected newChildren!: TransitionVNode[];\n /** A map containing the previous positions relative to the container, for each item\n * rendered inside the slot of this component component.\n * This is used together with the `newPositions` to calculate the move transition.\n *\n * @internal */\n protected oldPositions!: WeakMap<Element, Bounds>;\n /** A map containing the new positions relative to the container, for each item\n * rendered inside the slot of this component component.\n * This is used together with the `newPositions` to calculate the move transition.\n *\n * @internal */\n protected newPositions!: WeakMap<Element, Bounds>;\n /** A map containing the move cleanup functions pending to have been called. When invoked\n * this functions remove all the styles and classes associated to the move transition.\n *\n * @internal */\n protected pendingCleanupMoveCallbacks!: WeakMap<Element, () => void>;\n /** The counter for the stagger, used to calculate the delay for the transition of each child\n * element. It is reset every time the render method is triggered.\n *\n * @internal */\n protected staggerCounter!: number;\n /** The bounds of the container rendered using the `tag` prop. This is used to calculate the\n * relative positions of each leaving child, which are then applied with the position absolute.\n *\n * @internal */\n protected wrapperBounds!: DOMRect;\n\n beforeCreate(): void {\n /* Initialize properties here to avoid making them reactive,\n which would cause infinite loops */\n this.oldChildren = [];\n this.newChildren = [];\n this.oldPositions = new WeakMap();\n this.newPositions = new WeakMap();\n this.pendingCleanupMoveCallbacks = new WeakMap<Element, () => void>();\n this.staggerCounter = 0;\n }\n\n render(createElement: CreateElement): VNode {\n this.staggerCounter = 0;\n // New children are now the old ones\n this.oldChildren = this.newChildren;\n // Only vnodes with a tag (i.e. no HTML comments) and with a `key` property are valid.\n this.newChildren = (this.$slots.default ?? []).filter(this.isTransitionValidVNode);\n\n // Apply transition data to both new and old nodes & store the position of the old nodes.\n this.newChildren.forEach(this.addTransitionData);\n this.oldChildren.forEach(this.syncOldNodes);\n\n return createElement(\n this.tag,\n { staticClass: 'x-staggering-transition-group' },\n this.newChildren\n );\n }\n\n mounted(): void {\n this.newChildren.forEach(this.applyStagger);\n }\n\n beforeUpdate(): void {\n this.wrapperBounds = this.$el.getBoundingClientRect();\n }\n\n updated(): void {\n this.wrapperBounds = this.$el.getBoundingClientRect();\n this.newChildren.forEach(this.recordNewPosition);\n const { leavingNodes, stayingNodes, enteringNodes } = this.getNodesByTransitionType();\n\n leavingNodes.forEach(vNode => {\n this.applyStagger(vNode);\n this.disableClickingEvents(vNode);\n });\n const movedChildren = stayingNodes.filter(this.applyTranslation);\n const movedStagger = movedChildren.map(this.getNextTransitionDelay);\n enteringNodes.forEach(this.applyStagger);\n\n // force reflow to put everything in position\n document.body.getBoundingClientRect();\n\n movedChildren.forEach(this.startMoveAnimation(movedStagger));\n }\n\n /**\n * Extends the provided leave transition hook restoring the position of the element with an\n * absolute position.\n * Additionally, it removes the element position from the maps of positions.\n *\n * @param transitionHook - The leave transition hook to extend.\n * @returns The new leave transition hook extended.\n * @internal\n */\n protected addRestorePositionHook(transitionHook: TransitionHook = noOp): TransitionHook {\n return element => {\n const { top, left, width, height } = this.oldPositions.get(element)!;\n const { marginTop, marginLeft } = window.getComputedStyle(element);\n const style = element.style;\n style.position = 'absolute';\n style.top = `${top - parseFloat(marginTop)}px`;\n style.left = `${left - parseFloat(marginLeft)}px`;\n style.width = `${width}px`;\n style.height = `${height}px`;\n this.newPositions.delete(element);\n this.oldPositions.delete(element);\n const pendingCallback = this.pendingCleanupMoveCallbacks.get(element);\n pendingCallback?.();\n transitionHook(element);\n };\n }\n\n /**\n * Extends the provided transition hook clearing the transition delay.\n *\n * @param transitionHook - The transition hook to extend.\n * @returns The new transition hook, that also clears the transitionDelay from the element.\n * @internal\n */\n protected addClearStaggeringCall(transitionHook: TransitionHook = noOp): TransitionHook {\n return element => {\n element.style.transitionDelay = '';\n transitionHook(element);\n };\n }\n\n /**\n * Returns if the vNode contains a non empty key, and a non empty tag.\n *\n * @param vNode - The VNode to check if it is a valid transition node, containing a `tag` and a\n * `key` property.\n * @returns True when the vNode contains a non empty key and a non empty tag. False otherwise.\n * @internal\n */\n protected isTransitionValidVNode(vNode: VNode): vNode is TransitionVNode {\n // TODO Add warning with logger: <staggering-transition-group> children must be keyed.\n return !!vNode.key && !!vNode.tag;\n }\n\n /**\n * Adds the generated transition data to the vNode, creating the `data` property if necessary.\n *\n * @param vNode - The VNode to add the transition data to.\n * @internal\n */\n protected addTransitionData(vNode: TransitionVNode): void {\n if (!vNode.data) {\n vNode.data = {};\n }\n vNode.data.transition = this.transitionData;\n }\n\n /**\n * Re-applies the transition data to an old node, just in case it changed from the previous\n * render call. It also records the position of the node, to then calculate the move\n * transitions.\n *\n * @param vNode - The vNode to add the transition data to, and record his current position as\n * old.\n * @internal\n */\n protected syncOldNodes(vNode: TransitionVNode): void {\n // Synchronize transition data, in case it changed in the last frame.\n // We can trust data to be defined because each new node has the transition applied\n vNode.data!.transition = this.transitionData;\n this.recordOldPosition(vNode);\n }\n\n /**\n * Saves the position of the vNode in the map of old positions.\n *\n * @param vNode - The node to store its position.\n * @internal\n */\n protected recordOldPosition(vNode: TransitionVNode): void {\n this.oldPositions.set(vNode.elm, this.createRelativeBounds(vNode));\n }\n\n /**\n * Saves the position of the vNode in the map of new positions.\n *\n * @param vNode - The node to store its position.\n * @internal\n */\n protected recordNewPosition(vNode: TransitionVNode): void {\n this.newPositions.set(vNode.elm, this.createRelativeBounds(vNode));\n }\n\n /**\n * Creates an object containing the position of the vNode relative to its container.\n *\n * @param vNode - The virtual node to store its relative position.\n * @returns The relative bounds of the provided virtual node.\n * @internal\n */\n protected createRelativeBounds(vNode: TransitionVNode): Bounds {\n const { left, top, width, height } = vNode.elm.getBoundingClientRect();\n const { left: wrapperLeft, top: wrapperTop } = this.wrapperBounds;\n return {\n left: left - wrapperLeft,\n top: top - wrapperTop,\n width,\n height\n };\n }\n\n /**\n * Splits the children of the component into three groups:\n * - Nodes that are leaving.\n * - Nodes that are entering.\n * - Nodes that stay.\n *\n * This is then used to apply the stagger in the correct order: leave -\\> move -\\> enter.\n *\n * @returns The children nodes, divided in different groups depending on if they are leaving,\n * staying or entering.\n * @internal\n */\n protected getNodesByTransitionType(): TransitionTypeNodes {\n const leave = this.oldChildren.filter(child => !this.newPositions.has(child.elm));\n const enter = this.newChildren.filter(child => !this.oldPositions.has(child.elm));\n const stay = this.oldChildren.filter(child => this.newPositions.has(child.elm));\n return {\n leavingNodes: leave,\n enteringNodes: enter,\n stayingNodes: stay\n };\n }\n\n /**\n * Applies an incremental delay to the virtual node element.\n *\n * @param vNode - The virtual node to apply the stagger to.\n * @internal\n */\n protected applyStagger(vNode: TransitionVNode): void {\n vNode.elm.style.transitionDelay = this.getNextTransitionDelay();\n }\n\n /**\n * Disables listening to click events in a virtual node element.\n *\n * @remarks This is done to avoid letting the user click elements that are performing the moving\n * animation to leave the DOM but are still rendered.\n *\n * @param vNode - The virtual node to disable listening to click events.\n * @internal\n */\n protected disableClickingEvents(vNode: TransitionVNode): void {\n vNode.elm.style.pointerEvents = 'none';\n }\n\n /**\n * Calculates the next transition delay property, incrementing the `staggerCounter` property\n * each time it is called.\n *\n * @returns The value for the next element `style.transitionDelay` property.\n * @internal\n */\n protected getNextTransitionDelay(): string {\n return `${this.staggerCounter++ * this.staggering}ms`;\n }\n\n /**\n * Calculates if the virtual node should have a move transition. If its has it, then it\n * applies it immediately using the `style.transform`.\n *\n * @param vNode - The virtual node to calculate if it should have a move transition.\n * @returns True when a move transition was applied to the virtual node.\n * @internal\n */\n protected applyTranslation(vNode: TransitionVNode): boolean {\n const oldPosition = this.oldPositions.get(vNode.elm as Element)!;\n const newPosition = this.newPositions.get(vNode.elm as Element)!;\n const dx = oldPosition.left - newPosition.left;\n const dy = oldPosition.top - newPosition.top;\n if (dx !== 0 || dy !== 0) {\n const style = vNode.elm.style;\n style.transform = `translate3d(${dx}px,${dy}px,0)`;\n style.transitionDuration = '0s';\n return true;\n }\n return false;\n }\n\n /**\n * Generates a function to start the moving animations to each node that it needs them with the\n * provided stagger.\n *\n * @param moveStagger - A list containing the delay to add to each node.\n * @returns A function that starts the moving animation with the provided stagger to a single\n * virtual node.\n * @internal\n */\n protected startMoveAnimation(\n moveStagger: string[]\n ): (vNode: TransitionVNode, index: number) => void {\n return (vNode, index) => {\n const element = vNode.elm;\n const style = element.style;\n element.classList.add(this.moveClassName);\n style.transform = style.transitionDuration = '';\n style.transitionDelay = moveStagger[index];\n const cleanMoveTransition = (event?: TransitionEvent): void => {\n if (!event || (event.target === element && /transform$/.test(event.propertyName))) {\n element.removeEventListener('transitionend', cleanMoveTransition);\n element.style.transitionDelay = '';\n element.classList.remove(this.moveClassName);\n this.pendingCleanupMoveCallbacks.delete(element);\n }\n };\n this.pendingCleanupMoveCallbacks.set(element, cleanMoveTransition);\n element.addEventListener('transitionend', cleanMoveTransition);\n };\n }\n}\n\n/**\n * Contains arrays of nodes, splitted by the action they should have.\n */\ninterface TransitionTypeNodes {\n leavingNodes: TransitionVNode[];\n stayingNodes: TransitionVNode[];\n enteringNodes: TransitionVNode[];\n}\n\n/**\n * Safe transition version of the VNode type, with the required non optional properties.\n */\ninterface TransitionVNode extends VNode {\n elm: HTMLElement;\n tag: string;\n key: string;\n}\n\n/**\n * Vue's VNode {@link https://vuejs.org/v2/api/#transition | transition} data props and events.\n */\ninterface TransitionData {\n name?: string;\n appear?: boolean;\n css?: boolean;\n type?: 'transition' | 'animation'; // Unused\n mode?: 'out-in' | 'in-out'; // Unused\n duration?: number;\n enterClass?: string;\n leaveClass?: string;\n appearClass?: string;\n enterToClass?: string;\n leaveToClass?: string;\n appearToClass?: string;\n enterActiveClass?: string;\n leaveActiveClass?: string;\n appearActiveClass?: string;\n beforeEnter?: TransitionHook;\n enter?: TransitionHook;\n afterEnter?: TransitionHook;\n beforeLeave?: TransitionHook;\n leave?: TransitionHook;\n afterLeave?: TransitionHook;\n}\n\n/**\n * A function that receives an HTMLElement. Used to perform actions when the different phases\n * of Vue transitions happens.\n */\ntype TransitionHook = (element: HTMLElement) => void;\n\n/**\n * Represents the dimensions and positions of an element.\n */\ninterface Bounds {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"staggering-transition-group.vue_rollup-plugin-vue_script.vue.js","sources":["../../../../src/components/animations/staggering-transition-group.vue?rollup-plugin-vue=script.ts"],"sourcesContent":["\nimport Vue, { CreateElement, VNode } from 'vue';\nimport { Component, Prop } from 'vue-property-decorator';\nimport { noOp } from '../../utils';\n\n/* eslint-disable @typescript-eslint/unbound-method */\n/**\n * A replacement component for Vue's transition-group, that also adds the option to stagger\n * the animations.\n *\n * @public\n */\n@Component\nexport default class StaggeringTransitionGroup extends Vue {\n /**\n * The name of the transition. Used to generate the CSS classes.\n *\n * @public\n */\n @Prop({ default: 'v' })\n public name!: string;\n\n /**\n * The CSS move class name.\n *\n * @public\n */\n @Prop()\n public moveClass!: string;\n\n /**\n * The tag of the node to render to the DOM.\n *\n * @public\n */\n @Prop({ default: 'div' })\n public tag!: string;\n\n /**\n * The time in ms to stagger each item.\n *\n * @public\n */\n @Prop({ default: 25 })\n public staggering!: number;\n\n /**\n * The CSS class for the moving transitions.\n *\n * @returns The move transition name.\n * @internal\n */\n protected get moveClassName(): string {\n return this.moveClass ?? `${this.name}-move`;\n }\n\n /**\n * The transition data contains the needed events and props to perform a transition using Vue\n * virtual node's API.\n *\n * The `beforeLeave` hook is extended to also restore the previous position of the element using\n * the position absolute.\n * The `afterEnter` and `afterLeave` hooks are extended to also clean the transition delay\n * applied by the stagger.\n *\n * @returns The transition data for Vue virtual nodes.\n * @internal\n */\n protected get transitionData(): TransitionData {\n const transitionData: TransitionData = { ...this.$props, ...this.$attrs, ...this.$listeners };\n\n transitionData.beforeLeave = this.addRestorePositionHook(transitionData.beforeLeave);\n transitionData.afterEnter = this.addClearStaggeringCall(transitionData.afterEnter);\n transitionData.afterLeave = this.addClearStaggeringCall(transitionData.afterLeave);\n\n return transitionData;\n }\n\n /**\n * The list of old virtual nodes, generated by the previous called render method.\n *\n * @internal\n */\n protected oldChildren!: TransitionVNode[];\n /**\n * The list of new virtual nodes, generated by the last called render method.\n *\n * @internal\n */\n protected newChildren!: TransitionVNode[];\n /**\n * A map containing the previous positions relative to the container, for each item\n * rendered inside the slot of this component.\n * This is used together with the `newPositions` to calculate the move transition.\n *\n * @internal\n */\n protected oldPositions!: WeakMap<Element, Bounds>;\n /**\n * A map containing the new positions relative to the container, for each item\n * rendered inside the slot of this component.\n * This is used together with the `newPositions` to calculate the move transition.\n *\n * @internal\n */\n protected newPositions!: WeakMap<Element, Bounds>;\n /**\n * A map containing the move cleanup functions pending to have been called. When invoked\n * this functions remove all the styles and classes associated to the move transition.\n *\n * @internal\n */\n protected pendingCleanupMoveCallbacks!: WeakMap<Element, () => void>;\n /**\n * The counter for the stagger, used to calculate the delay for the transition of each child\n * element. It is reset every time the render method is triggered.\n *\n * @internal\n */\n protected staggerCounter!: number;\n /**\n * The bounds of the container rendered using the `tag` prop. This is used to calculate the\n * relative positions of each leaving child, which are then applied with the position absolute.\n *\n * @internal\n */\n protected wrapperBounds!: DOMRect;\n\n beforeCreate(): void {\n // Initialize properties here to avoid making them reactive, which would cause infinite loops.\n this.oldChildren = [];\n this.newChildren = [];\n this.oldPositions = new WeakMap();\n this.newPositions = new WeakMap();\n this.pendingCleanupMoveCallbacks = new WeakMap<Element, () => void>();\n this.staggerCounter = 0;\n }\n\n render(createElement: CreateElement): VNode {\n this.staggerCounter = 0;\n // New children are now the old ones\n this.oldChildren = this.newChildren;\n // Only vnodes with a tag (i.e. no HTML comments) and with a `key` property are valid.\n this.newChildren = (this.$slots.default ?? []).filter(this.isTransitionValidVNode);\n\n // Apply transition data to both new and old nodes & store the position of the old nodes.\n this.newChildren.forEach(this.addTransitionData);\n this.oldChildren.forEach(this.syncOldNodes);\n\n return createElement(\n this.tag,\n { staticClass: 'x-staggering-transition-group' },\n this.newChildren\n );\n }\n\n mounted(): void {\n this.newChildren.forEach(this.applyStagger);\n }\n\n beforeUpdate(): void {\n this.wrapperBounds = this.$el.getBoundingClientRect();\n }\n\n updated(): void {\n this.wrapperBounds = this.$el.getBoundingClientRect();\n this.newChildren.forEach(this.recordNewPosition);\n const { leavingNodes, stayingNodes, enteringNodes } = this.getNodesByTransitionType();\n\n leavingNodes.forEach(vNode => {\n this.applyStagger(vNode);\n this.disableClickingEvents(vNode);\n });\n const movedChildren = stayingNodes.filter(this.applyTranslation);\n const movedStagger = movedChildren.map(this.getNextTransitionDelay);\n enteringNodes.forEach(this.applyStagger);\n\n // force reflow to put everything in position\n document.body.getBoundingClientRect();\n\n movedChildren.forEach(this.startMoveAnimation(movedStagger));\n }\n\n /**\n * Extends the provided leave transition hook restoring the position of the element with an\n * absolute position.\n * Additionally, it removes the element position from the maps of positions.\n *\n * @param transitionHook - The leave transition hook to extend.\n * @returns The new leave transition hook extended.\n * @internal\n */\n protected addRestorePositionHook(transitionHook: TransitionHook = noOp): TransitionHook {\n return element => {\n const { top, left, width, height } = this.oldPositions.get(element)!;\n const { marginTop, marginLeft } = window.getComputedStyle(element);\n const style = element.style;\n style.position = 'absolute';\n style.top = `${top - parseFloat(marginTop)}px`;\n style.left = `${left - parseFloat(marginLeft)}px`;\n style.width = `${width}px`;\n style.height = `${height}px`;\n this.newPositions.delete(element);\n this.oldPositions.delete(element);\n const pendingCallback = this.pendingCleanupMoveCallbacks.get(element);\n pendingCallback?.();\n transitionHook(element);\n };\n }\n\n /**\n * Extends the provided transition hook clearing the transition delay.\n *\n * @param transitionHook - The transition hook to extend.\n * @returns The new transition hook, that also clears the transitionDelay from the element.\n * @internal\n */\n protected addClearStaggeringCall(transitionHook: TransitionHook = noOp): TransitionHook {\n return element => {\n element.style.transitionDelay = '';\n transitionHook(element);\n };\n }\n\n /**\n * Returns if the vNode contains a non empty key, and a non empty tag.\n *\n * @param vNode - The VNode to check if it is a valid transition node, containing a `tag` and a\n * `key` property.\n * @returns True when the vNode contains a non empty key and a non empty tag. False otherwise.\n * @internal\n */\n protected isTransitionValidVNode(vNode: VNode): vNode is TransitionVNode {\n // TODO Add warning with logger: <staggering-transition-group> children must be keyed.\n return !!vNode.key && !!vNode.tag;\n }\n\n /**\n * Adds the generated transition data to the vNode, creating the `data` property if necessary.\n *\n * @param vNode - The VNode to add the transition data to.\n * @internal\n */\n protected addTransitionData(vNode: TransitionVNode): void {\n if (!vNode.data) {\n vNode.data = {};\n }\n vNode.data.transition = this.transitionData;\n }\n\n /**\n * Re-applies the transition data to an old node, just in case it changed from the previous\n * render call. It also records the position of the node, to then calculate the move\n * transitions.\n *\n * @param vNode - The vNode to add the transition data to, and record his current position as\n * old.\n * @internal\n */\n protected syncOldNodes(vNode: TransitionVNode): void {\n // Synchronize transition data, in case it changed in the last frame.\n // We can trust data to be defined because each new node has the transition applied\n vNode.data!.transition = this.transitionData;\n this.recordOldPosition(vNode);\n }\n\n /**\n * Saves the position of the vNode in the map of old positions.\n *\n * @param vNode - The node to store its position.\n * @internal\n */\n protected recordOldPosition(vNode: TransitionVNode): void {\n this.oldPositions.set(vNode.elm, this.createRelativeBounds(vNode));\n }\n\n /**\n * Saves the position of the vNode in the map of new positions.\n *\n * @param vNode - The node to store its position.\n * @internal\n */\n protected recordNewPosition(vNode: TransitionVNode): void {\n this.newPositions.set(vNode.elm, this.createRelativeBounds(vNode));\n }\n\n /**\n * Creates an object containing the position of the vNode relative to its container.\n *\n * @param vNode - The virtual node to store its relative position.\n * @returns The relative bounds of the provided virtual node.\n * @internal\n */\n protected createRelativeBounds(vNode: TransitionVNode): Bounds {\n const { left, top, width, height } = vNode.elm.getBoundingClientRect();\n const { left: wrapperLeft, top: wrapperTop } = this.wrapperBounds;\n return {\n left: left - wrapperLeft,\n top: top - wrapperTop,\n width,\n height\n };\n }\n\n /**\n * Splits the children of the component into three groups:\n * - Nodes that are leaving.\n * - Nodes that are entering.\n * - Nodes that stay.\n *\n * This is then used to apply the stagger in the correct order: leave -\\> move -\\> enter.\n *\n * @returns The children nodes, divided in different groups depending on if they are leaving,\n * staying or entering.\n * @internal\n */\n protected getNodesByTransitionType(): TransitionTypeNodes {\n const leave = this.oldChildren.filter(child => !this.newPositions.has(child.elm));\n const enter = this.newChildren.filter(child => !this.oldPositions.has(child.elm));\n const stay = this.oldChildren.filter(child => this.newPositions.has(child.elm));\n return {\n leavingNodes: leave,\n enteringNodes: enter,\n stayingNodes: stay\n };\n }\n\n /**\n * Applies an incremental delay to the virtual node element.\n *\n * @param vNode - The virtual node to apply the stagger to.\n * @internal\n */\n protected applyStagger(vNode: TransitionVNode): void {\n vNode.elm.style.transitionDelay = this.getNextTransitionDelay();\n }\n\n /**\n * Disables listening to click events in a virtual node element.\n *\n * @remarks This is done to avoid letting the user click elements that are performing the moving\n * animation to leave the DOM but are still rendered.\n *\n * @param vNode - The virtual node to disable listening to click events.\n * @internal\n */\n protected disableClickingEvents(vNode: TransitionVNode): void {\n vNode.elm.style.pointerEvents = 'none';\n }\n\n /**\n * Calculates the next transition delay property, incrementing the `staggerCounter` property\n * each time it is called.\n *\n * @returns The value for the next element `style.transitionDelay` property.\n * @internal\n */\n protected getNextTransitionDelay(): string {\n return `${this.staggerCounter++ * this.staggering}ms`;\n }\n\n /**\n * Calculates if the virtual node should have a move transition. If its has it, then it\n * applies it immediately using the `style.transform`.\n *\n * @param vNode - The virtual node to calculate if it should have a move transition.\n * @returns True when a move transition was applied to the virtual node.\n * @internal\n */\n protected applyTranslation(vNode: TransitionVNode): boolean {\n const oldPosition = this.oldPositions.get(vNode.elm as Element)!;\n const newPosition = this.newPositions.get(vNode.elm as Element)!;\n const dx = oldPosition.left - newPosition.left;\n const dy = oldPosition.top - newPosition.top;\n if (dx !== 0 || dy !== 0) {\n const style = vNode.elm.style;\n style.transform = `translate3d(${dx}px,${dy}px,0)`;\n style.transitionDuration = '0s';\n return true;\n }\n return false;\n }\n\n /**\n * Generates a function to start the moving animations to each node that it needs them with the\n * provided stagger.\n *\n * @param moveStagger - A list containing the delay to add to each node.\n * @returns A function that starts the moving animation with the provided stagger to a single\n * virtual node.\n * @internal\n */\n protected startMoveAnimation(\n moveStagger: string[]\n ): (vNode: TransitionVNode, index: number) => void {\n return (vNode, index) => {\n const element = vNode.elm;\n const style = element.style;\n element.classList.add(this.moveClassName);\n style.transform = style.transitionDuration = '';\n style.transitionDelay = moveStagger[index];\n const cleanMoveTransition = (event?: TransitionEvent): void => {\n if (!event || (event.target === element && /transform$/.test(event.propertyName))) {\n element.removeEventListener('transitionend', cleanMoveTransition);\n element.style.transitionDelay = '';\n element.classList.remove(this.moveClassName);\n this.pendingCleanupMoveCallbacks.delete(element);\n }\n };\n this.pendingCleanupMoveCallbacks.set(element, cleanMoveTransition);\n element.addEventListener('transitionend', cleanMoveTransition);\n };\n }\n}\n\n/**\n * Contains arrays of nodes, splitted by the action they should have.\n */\ninterface TransitionTypeNodes {\n leavingNodes: TransitionVNode[];\n stayingNodes: TransitionVNode[];\n enteringNodes: TransitionVNode[];\n}\n\n/**\n * Safe transition version of the VNode type, with the required non optional properties.\n */\ninterface TransitionVNode extends VNode {\n elm: HTMLElement;\n tag: string;\n key: string;\n}\n\n/**\n * Vue's VNode {@link https://vuejs.org/v2/api/#transition | transition} data props and events.\n */\ninterface TransitionData {\n name?: string;\n appear?: boolean;\n css?: boolean;\n type?: 'transition' | 'animation'; // Unused\n mode?: 'out-in' | 'in-out'; // Unused\n duration?: number;\n enterClass?: string;\n leaveClass?: string;\n appearClass?: string;\n enterToClass?: string;\n leaveToClass?: string;\n appearToClass?: string;\n enterActiveClass?: string;\n leaveActiveClass?: string;\n appearActiveClass?: string;\n beforeEnter?: TransitionHook;\n enter?: TransitionHook;\n afterEnter?: TransitionHook;\n beforeLeave?: TransitionHook;\n leave?: TransitionHook;\n afterLeave?: TransitionHook;\n}\n\n/**\n * A function that receives an HTMLElement. Used to perform actions when the different phases\n * of Vue transitions happens.\n */\ntype TransitionHook = (element: HTMLElement) => void;\n\n/**\n * Represents the dimensions and positions of an element.\n */\ninterface Bounds {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n"],"names":[],"mappings":"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"}
|
package/js/components/animations/staggering-transition-group.vue_rollup-plugin-vue_styles.0.vue.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createInjector, createInjectorSSR } from 'vue-runtime-helpers';
|
|
2
2
|
|
|
3
|
-
var css = ".x-staggering-transition-group[data-v-
|
|
3
|
+
var css = ".x-staggering-transition-group[data-v-5eaf2336] {\n position: relative;\n}";
|
|
4
4
|
const isBrowser = /*#__PURE__*/ (function () {
|
|
5
5
|
return (
|
|
6
6
|
Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) !==
|
|
@@ -219,7 +219,7 @@ __vue_render__._withStripped = true;
|
|
|
219
219
|
/* style */
|
|
220
220
|
const __vue_inject_styles__ = undefined;
|
|
221
221
|
/* scoped */
|
|
222
|
-
const __vue_scope_id__ = "data-v-
|
|
222
|
+
const __vue_scope_id__ = "data-v-59445e96";
|
|
223
223
|
/* module identifier */
|
|
224
224
|
const __vue_module_identifier__ = undefined;
|
|
225
225
|
/* functional template */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-dropdown.vue.js","sources":["../../../src/components/base-dropdown.vue"],"sourcesContent":["<template>\n <div\n @keydown=\"updateSearchBuffer\"\n @keydown.down.prevent=\"highlightNextItem\"\n @keydown.up.prevent=\"highlightPreviousItem\"\n :class=\"dropdownCSSClasses\"\n class=\"x-dropdown\"\n >\n <button\n ref=\"toggleButton\"\n @click=\"toggle\"\n @keydown.up.down.prevent.stop=\"open\"\n class=\"x-dropdown__toggle\"\n data-test=\"dropdown-toggle\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n :aria-expanded=\"isOpen.toString()\"\n :aria-controls=\"listId\"\n :aria-label=\"ariaLabel\"\n aria-autocomplete=\"none\"\n >\n <!--\n @slot Used to render the contents of the dropdown toggle button. If not provided, it uses\n the item slot as fallback.\n @binding {string|number|Identifiable} item - The item data to render.\n @binding {boolean} isOpen - True if the dropdown is opened, and false if it is closed.\n -->\n <slot v-if=\"$scopedSlots.toggle\" :isOpen=\"isOpen\" :item=\"value\" name=\"toggle\">\n {{ value }}\n </slot>\n <slot v-else :item=\"value\" name=\"item\">{{ value }}</slot>\n </button>\n\n <component :is=\"animation\">\n <ul\n v-show=\"isOpen\"\n @keydown.end=\"highlightLastItem\"\n @keydown.esc=\"closeAndFocusToggleButton\"\n @keydown.home=\"highlightFirstItem\"\n :id=\"listId\"\n class=\"x-dropdown__items-list\"\n role=\"listbox\"\n tabindex=\"-1\"\n >\n <li v-for=\"(item, index) in items\" :key=\"item.id || item\" class=\"x-dropdown__list-item\">\n <button\n ref=\"itemButtons\"\n @click=\"emitSelectedItemChanged(item)\"\n :aria-selected=\"(item === value).toString()\"\n :class=\"itemsCSSClasses[index]\"\n class=\"x-dropdown__item\"\n data-test=\"dropdown-item\"\n role=\"option\"\n >\n <!--\n @slot (required) Used to render each one of the items content, and as fallback\n for the toggle button content slot if it is not provided.\n @binding {string|number|Identifiable} item - Item to render\n @binding {boolean} isHighlighted - True when the item has the focus.\n @binding {boolean} isSelected - True when the item is selected.\n -->\n <slot\n :isHighlighted=\"index === highlightedItemIndex\"\n :isSelected=\"item === value\"\n :item=\"item\"\n name=\"item\"\n >\n {{ item }}\n </slot>\n </button>\n </li>\n </ul>\n </component>\n </div>\n</template>\n\n<script lang=\"ts\">\n import { Identifiable } from '@empathyco/x-types';\n import { Component, Prop, Watch } from 'vue-property-decorator';\n import Vue from 'vue';\n import { normalizeString } from '../utils/normalize';\n import { isInRange } from '../utils/number';\n import { debounce } from '../utils/debounce';\n import { VueCSSClasses } from '../utils/types';\n import { NoElement } from './no-element';\n\n type DropdownItem = string | number | Identifiable;\n let dropdownCount = 0;\n\n /**\n * Dropdown component that mimics a Select element behavior, but with the option\n * to customize the toggle button and each item contents.\n *\n * @public\n */\n @Component({\n components: {\n NoElement\n },\n model: {\n event: 'change'\n }\n })\n export default class BaseDropdown extends Vue {\n /**\n * List of items to display.\n *\n * @public\n */\n @Prop({ required: true })\n public items!: DropdownItem[];\n\n /**\n * Description of what the dropdown is used for.\n *\n * @public\n */\n @Prop()\n public ariaLabel?: string;\n\n /**\n * The selected item.\n *\n * @public\n */\n @Prop({ required: true })\n public value!: DropdownItem | null;\n\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 * @public\n */\n @Prop({ default: 'NoElement' })\n public animation!: typeof Vue | string;\n\n /**\n * Time to wait without receiving any keystroke before resetting the items search query.\n *\n * @public\n */\n @Prop({ default: 1000 })\n public searchTimeoutMs!: number;\n\n public $refs!: {\n /** Array containing the dropdown list buttons HTMLElements. */\n itemButtons: HTMLButtonElement[];\n /** The button that opens and closes the list of options. */\n toggleButton: HTMLButtonElement;\n };\n\n /**\n * Property to track whether the dropdown is expanded and displaying the full\n * list of items, or closed.\n *\n * @internal\n */\n protected isOpen = false;\n\n /**\n * Index of the element that has the focus in the list. -1 means no element has focus.\n *\n * @internal\n */\n protected highlightedItemIndex = -1;\n\n /**\n * String to search for the first element that starts with it.\n *\n * @internal\n */\n protected searchBuffer = '';\n\n /**\n * Resets the search buffer after a certain time has passed.\n *\n * @internal\n */\n protected restartResetSearchTimeout!: () => void;\n\n protected readonly listId = `x-dropdown-${dropdownCount++}`;\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 * @internal\n */\n protected get dropdownCSSClasses(): VueCSSClasses {\n return {\n 'x-dropdown--is-open': this.isOpen\n };\n }\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 * @internal\n */\n protected get itemsCSSClasses(): VueCSSClasses[] {\n return this.items.map((item, index) => {\n return {\n 'x-dropdown__item--is-selected': this.value === item,\n 'x-dropdown__item--is-highlighted': this.highlightedItemIndex === index\n };\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 * @internal\n */\n protected beforeDestroy(): void {\n this.removeDocumentCloseListeners();\n }\n\n /**\n * Opens the dropdown.\n *\n * @internal\n */\n protected open(): void {\n this.isOpen = true;\n }\n\n /**\n * Closes the dropdown.\n *\n * @internal\n */\n protected close(): void {\n this.isOpen = false;\n }\n\n /**\n * Closes the modal and focuses the toggle button.\n *\n * @internal\n */\n protected closeAndFocusToggleButton(): void {\n this.close();\n this.$refs.toggleButton.focus();\n }\n\n /**\n * If the dropdown is opened it closes it. If it is closed it opens it.\n *\n * @internal\n */\n protected toggle(): void {\n this.isOpen = !this.isOpen;\n }\n\n /**\n * Emits the event that the selected item has changed.\n *\n * @param item - The new selected item.\n * @internal\n */\n protected emitSelectedItemChanged(item: DropdownItem): void {\n this.$emit('change', item);\n this.closeAndFocusToggleButton();\n }\n\n /**\n * Highlights the item after the one that is currently highlighted.\n *\n * @internal\n */\n protected highlightNextItem(): void {\n this.open();\n this.highlightedItemIndex = (this.highlightedItemIndex + 1) % this.items.length;\n }\n\n /**\n * Highlights the item before the one that is currently highlighted.\n *\n * @internal\n */\n protected highlightPreviousItem(): void {\n this.open();\n this.highlightedItemIndex =\n this.highlightedItemIndex > 0 ? this.highlightedItemIndex - 1 : this.items.length - 1;\n }\n\n /**\n * Highlights the first of the provided items.\n *\n * @internal\n */\n protected highlightFirstItem(): void {\n this.highlightedItemIndex = 0;\n }\n\n /**\n * Highlights the last of the provided items.\n *\n * @internal\n */\n protected highlightLastItem(): void {\n this.highlightedItemIndex = this.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 * @internal\n */\n protected updateSearchBuffer(event: KeyboardEvent): void {\n if (/^\\w$/.test(event.key)) {\n const key = event.key;\n this.searchBuffer += key;\n this.restartResetSearchTimeout();\n }\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 folowing 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 * @internal\n */\n @Watch('searchBuffer')\n protected highlightMatchingItem(search: string): void {\n if (search) {\n const normalizedSearch = normalizeString(search);\n const matchingIndices = this.$refs.itemButtons.reduce<number[]>(\n (matchingIndices, button, index) => {\n const safeButtonWordCharacters = button.textContent!.replace(/[^\\w]/g, '');\n const normalizedButtonText = normalizeString(safeButtonWordCharacters);\n if (normalizedButtonText.startsWith(normalizedSearch)) {\n matchingIndices.push(index);\n }\n return matchingIndices;\n },\n []\n );\n this.highlightedItemIndex =\n // First matching item starting to search from the current highlighted element\n matchingIndices.find(index => index >= this.highlightedItemIndex) ??\n // First matching item\n matchingIndices[0] ??\n // First item\n 0;\n }\n }\n\n /**\n * Resets the search buffer.\n *\n * @internal\n */\n protected resetSearch(): void {\n this.searchBuffer = '';\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 * @internal\n */\n @Watch('searchTimeoutMs', { immediate: true })\n protected updateSearchTimeout(searchTimeoutMs: number): void {\n // eslint-disable-next-line @typescript-eslint/unbound-method\n this.restartResetSearchTimeout = debounce(this.resetSearch, searchTimeoutMs);\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 * @internal\n */\n @Watch('highlightedItemIndex', { immediate: true })\n protected focusHighlightedItem(highlightedItemIndex: number): void {\n this.$nextTick(() => {\n if (this.$refs.itemButtons && isInRange(highlightedItemIndex, [0, this.items.length - 1])) {\n const newItem = this.$refs.itemButtons[this.highlightedItemIndex];\n newItem.focus();\n }\n });\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 * @internal\n */\n @Watch('isOpen')\n protected updateHighlightedItem(isOpen: boolean): void {\n if (isOpen) {\n this.highlightedItemIndex = this.value === null ? 0 : this.items.indexOf(this.value);\n } else {\n this.highlightedItemIndex = -1;\n }\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 * @internal\n */\n @Watch('isOpen')\n protected syncCloseListeners(isOpen: boolean): void {\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 or not 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 this.addDocumentCloseListeners();\n } else {\n this.removeDocumentCloseListeners();\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 * @internal\n */\n protected addDocumentCloseListeners(): void {\n /* eslint-disable @typescript-eslint/unbound-method */\n document.addEventListener('mousedown', this.closeIfEventIsOutOfDropdown);\n document.addEventListener('touchstart', this.closeIfEventIsOutOfDropdown);\n document.addEventListener('focusin', this.closeIfEventIsOutOfDropdown);\n /* eslint-enable @typescript-eslint/unbound-method */\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 * @internal\n */\n protected removeDocumentCloseListeners(): void {\n /* eslint-disable @typescript-eslint/unbound-method */\n document.removeEventListener('mousedown', this.closeIfEventIsOutOfDropdown);\n document.removeEventListener('touchstart', this.closeIfEventIsOutOfDropdown);\n document.removeEventListener('focusin', this.closeIfEventIsOutOfDropdown);\n /* eslint-enable @typescript-eslint/unbound-method */\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 happen out of the dropdown component.\n */\n protected closeIfEventIsOutOfDropdown(event: MouseEvent | TouchEvent | FocusEvent): void {\n if (!this.$el.contains(event.target as HTMLElement)) {\n this.close();\n }\n }\n }\n</script>\n\n<style lang=\"scss\" scoped>\n .x-dropdown {\n position: relative;\n\n &__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 }\n</style>\n\n<docs lang=\"mdx\">\n## Example\n\nThe `Dropdown` component is a simple yet customizable select component. The component needs to work\nthe list of items available to select, which are passed using the `items` prop, and the selected\nitem, which is passed in using the `value` prop.\n\nThe supported items must be an array that can contain unique strings, unique numbers, or objects\nwith a unique `id` property.\n\nThe content of each item can be customized using the `item` slot, which apart from the data of the\nitem, it also receives via prop if the element is selected or highlighted.\n\nThere `toggle` slot can be used to customize the button that opens the dropdown. If this is not\nprovided, the `item` slot will be used for that.\n\n```vue\n<template>\n <BaseDropdown :items=\"items\" v-model=\"value\">\n <template #toggle=\"{ item, isOpen }\">{{ item }} {{ isOpen ? '🔼' : '🔽' }}️</template>\n\n <template #item=\"{ item, isSelected, isHighlighted }\">\n <span v-if=\"isHighlighted\">🟢</span>\n <span v-if=\"isSelected\">✅</span>\n <span>{{ item }}</span>\n </template>\n </BaseDropdown>\n</template>\n\n<script>\n import { BaseDropdown } from '@empathyco/x-components';\n\n export default {\n name: 'DropdownTest',\n components: {\n BaseDropdown\n },\n data() {\n return {\n items: ['a', 2, { id: '3' }],\n value: ['a']\n };\n }\n };\n</script>\n```\n</docs>\n"],"names":[],"mappings":";;;;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"base-dropdown.vue.js","sources":["../../../src/components/base-dropdown.vue"],"sourcesContent":["<template>\n <div\n @keydown=\"updateSearchBuffer\"\n @keydown.down.prevent=\"highlightNextItem\"\n @keydown.up.prevent=\"highlightPreviousItem\"\n :class=\"dropdownCSSClasses\"\n class=\"x-dropdown\"\n >\n <button\n ref=\"toggleButton\"\n @click=\"toggle\"\n @keydown.up.down.prevent.stop=\"open\"\n class=\"x-dropdown__toggle\"\n data-test=\"dropdown-toggle\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n :aria-expanded=\"isOpen.toString()\"\n :aria-controls=\"listId\"\n :aria-label=\"ariaLabel\"\n aria-autocomplete=\"none\"\n >\n <!--\n @slot Used to render the contents of the dropdown toggle button. If not provided, it uses\n the item slot as fallback.\n @binding {string|number|Identifiable} item - The item data to render.\n @binding {boolean} isOpen - True if the dropdown is opened, and false if it is closed.\n -->\n <slot v-if=\"$scopedSlots.toggle\" :isOpen=\"isOpen\" :item=\"value\" name=\"toggle\">\n {{ value }}\n </slot>\n <slot v-else :item=\"value\" name=\"item\">{{ value }}</slot>\n </button>\n\n <component :is=\"animation\">\n <ul\n v-show=\"isOpen\"\n @keydown.end=\"highlightLastItem\"\n @keydown.esc=\"closeAndFocusToggleButton\"\n @keydown.home=\"highlightFirstItem\"\n :id=\"listId\"\n class=\"x-dropdown__items-list\"\n role=\"listbox\"\n tabindex=\"-1\"\n >\n <li v-for=\"(item, index) in items\" :key=\"item.id || item\" class=\"x-dropdown__list-item\">\n <button\n ref=\"itemButtons\"\n @click=\"emitSelectedItemChanged(item)\"\n :aria-selected=\"(item === value).toString()\"\n :class=\"itemsCSSClasses[index]\"\n class=\"x-dropdown__item\"\n data-test=\"dropdown-item\"\n role=\"option\"\n >\n <!--\n @slot (required) Used to render each one of the items content, and as fallback\n for the toggle button content slot if it is not provided.\n @binding {string|number|Identifiable} item - Item to render\n @binding {boolean} isHighlighted - True when the item has the focus.\n @binding {boolean} isSelected - True when the item is selected.\n -->\n <slot\n :isHighlighted=\"index === highlightedItemIndex\"\n :isSelected=\"item === value\"\n :item=\"item\"\n name=\"item\"\n >\n {{ item }}\n </slot>\n </button>\n </li>\n </ul>\n </component>\n </div>\n</template>\n\n<script lang=\"ts\">\n import { Identifiable } from '@empathyco/x-types';\n import { Component, Prop, Watch } from 'vue-property-decorator';\n import Vue from 'vue';\n import { getTargetElement } from '../utils/html';\n import { normalizeString } from '../utils/normalize';\n import { isInRange } from '../utils/number';\n import { debounce } from '../utils/debounce';\n import { VueCSSClasses } from '../utils/types';\n import { NoElement } from './no-element';\n\n type DropdownItem = string | number | Identifiable;\n let dropdownCount = 0;\n\n /**\n * Dropdown component that mimics a Select element behavior, but with the option\n * to customize the toggle button and each item contents.\n *\n * @public\n */\n @Component({\n components: {\n NoElement\n },\n model: {\n event: 'change'\n }\n })\n export default class BaseDropdown extends Vue {\n /**\n * List of items to display.\n *\n * @public\n */\n @Prop({ required: true })\n public items!: DropdownItem[];\n\n /**\n * Description of what the dropdown is used for.\n *\n * @public\n */\n @Prop()\n public ariaLabel?: string;\n\n /**\n * The selected item.\n *\n * @public\n */\n @Prop({ required: true })\n public value!: DropdownItem | null;\n\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 * @public\n */\n @Prop({ default: 'NoElement' })\n public animation!: typeof Vue | string;\n\n /**\n * Time to wait without receiving any keystroke before resetting the items search query.\n *\n * @public\n */\n @Prop({ default: 1000 })\n public searchTimeoutMs!: number;\n\n public $refs!: {\n /** Array containing the dropdown list buttons HTMLElements. */\n itemButtons: HTMLButtonElement[];\n /** The button that opens and closes the list of options. */\n toggleButton: HTMLButtonElement;\n };\n\n /**\n * Property to track whether the dropdown is expanded and displaying the full\n * list of items, or closed.\n *\n * @internal\n */\n protected isOpen = false;\n\n /**\n * Index of the element that has the focus in the list. -1 means no element has focus.\n *\n * @internal\n */\n protected highlightedItemIndex = -1;\n\n /**\n * String to search for the first element that starts with it.\n *\n * @internal\n */\n protected searchBuffer = '';\n\n /**\n * Resets the search buffer after a certain time has passed.\n *\n * @internal\n */\n protected restartResetSearchTimeout!: () => void;\n\n protected readonly listId = `x-dropdown-${dropdownCount++}`;\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 * @internal\n */\n protected get dropdownCSSClasses(): VueCSSClasses {\n return {\n 'x-dropdown--is-open': this.isOpen\n };\n }\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 * @internal\n */\n protected get itemsCSSClasses(): VueCSSClasses[] {\n return this.items.map((item, index) => {\n return {\n 'x-dropdown__item--is-selected': this.value === item,\n 'x-dropdown__item--is-highlighted': this.highlightedItemIndex === index\n };\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 * @internal\n */\n protected beforeDestroy(): void {\n this.removeDocumentCloseListeners();\n }\n\n /**\n * Opens the dropdown.\n *\n * @internal\n */\n protected open(): void {\n this.isOpen = true;\n }\n\n /**\n * Closes the dropdown.\n *\n * @internal\n */\n protected close(): void {\n this.isOpen = false;\n }\n\n /**\n * Closes the modal and focuses the toggle button.\n *\n * @internal\n */\n protected closeAndFocusToggleButton(): void {\n this.close();\n this.$refs.toggleButton.focus();\n }\n\n /**\n * If the dropdown is opened it closes it. If it is closed it opens it.\n *\n * @internal\n */\n protected toggle(): void {\n this.isOpen = !this.isOpen;\n }\n\n /**\n * Emits the event that the selected item has changed.\n *\n * @param item - The new selected item.\n * @internal\n */\n protected emitSelectedItemChanged(item: DropdownItem): void {\n this.$emit('change', item);\n this.closeAndFocusToggleButton();\n }\n\n /**\n * Highlights the item after the one that is currently highlighted.\n *\n * @internal\n */\n protected highlightNextItem(): void {\n this.open();\n this.highlightedItemIndex = (this.highlightedItemIndex + 1) % this.items.length;\n }\n\n /**\n * Highlights the item before the one that is currently highlighted.\n *\n * @internal\n */\n protected highlightPreviousItem(): void {\n this.open();\n this.highlightedItemIndex =\n this.highlightedItemIndex > 0 ? this.highlightedItemIndex - 1 : this.items.length - 1;\n }\n\n /**\n * Highlights the first of the provided items.\n *\n * @internal\n */\n protected highlightFirstItem(): void {\n this.highlightedItemIndex = 0;\n }\n\n /**\n * Highlights the last of the provided items.\n *\n * @internal\n */\n protected highlightLastItem(): void {\n this.highlightedItemIndex = this.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 * @internal\n */\n protected updateSearchBuffer(event: KeyboardEvent): void {\n if (/^\\w$/.test(event.key)) {\n const key = event.key;\n this.searchBuffer += key;\n this.restartResetSearchTimeout();\n }\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 folowing 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 * @internal\n */\n @Watch('searchBuffer')\n protected highlightMatchingItem(search: string): void {\n if (search) {\n const normalizedSearch = normalizeString(search);\n const matchingIndices = this.$refs.itemButtons.reduce<number[]>(\n (matchingIndices, button, index) => {\n const safeButtonWordCharacters = button.textContent!.replace(/[^\\w]/g, '');\n const normalizedButtonText = normalizeString(safeButtonWordCharacters);\n if (normalizedButtonText.startsWith(normalizedSearch)) {\n matchingIndices.push(index);\n }\n return matchingIndices;\n },\n []\n );\n this.highlightedItemIndex =\n // First matching item starting to search from the current highlighted element\n matchingIndices.find(index => index >= this.highlightedItemIndex) ??\n // First matching item\n matchingIndices[0] ??\n // First item\n 0;\n }\n }\n\n /**\n * Resets the search buffer.\n *\n * @internal\n */\n protected resetSearch(): void {\n this.searchBuffer = '';\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 * @internal\n */\n @Watch('searchTimeoutMs', { immediate: true })\n protected updateSearchTimeout(searchTimeoutMs: number): void {\n // eslint-disable-next-line @typescript-eslint/unbound-method\n this.restartResetSearchTimeout = debounce(this.resetSearch, searchTimeoutMs);\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 * @internal\n */\n @Watch('highlightedItemIndex', { immediate: true })\n protected focusHighlightedItem(highlightedItemIndex: number): void {\n this.$nextTick(() => {\n if (this.$refs.itemButtons && isInRange(highlightedItemIndex, [0, this.items.length - 1])) {\n const newItem = this.$refs.itemButtons[this.highlightedItemIndex];\n newItem.focus();\n }\n });\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 * @internal\n */\n @Watch('isOpen')\n protected updateHighlightedItem(isOpen: boolean): void {\n if (isOpen) {\n this.highlightedItemIndex = this.value === null ? 0 : this.items.indexOf(this.value);\n } else {\n this.highlightedItemIndex = -1;\n }\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 * @internal\n */\n @Watch('isOpen')\n protected syncCloseListeners(isOpen: boolean): void {\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 or not 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 this.addDocumentCloseListeners();\n } else {\n this.removeDocumentCloseListeners();\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 * @internal\n */\n protected addDocumentCloseListeners(): void {\n /* eslint-disable @typescript-eslint/unbound-method */\n document.addEventListener('mousedown', this.closeIfEventIsOutOfDropdown);\n document.addEventListener('touchstart', this.closeIfEventIsOutOfDropdown);\n document.addEventListener('focusin', this.closeIfEventIsOutOfDropdown);\n /* eslint-enable @typescript-eslint/unbound-method */\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 * @internal\n */\n protected removeDocumentCloseListeners(): void {\n /* eslint-disable @typescript-eslint/unbound-method */\n document.removeEventListener('mousedown', this.closeIfEventIsOutOfDropdown);\n document.removeEventListener('touchstart', this.closeIfEventIsOutOfDropdown);\n document.removeEventListener('focusin', this.closeIfEventIsOutOfDropdown);\n /* eslint-enable @typescript-eslint/unbound-method */\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 happen out of the dropdown component.\n */\n protected closeIfEventIsOutOfDropdown(event: MouseEvent | TouchEvent | FocusEvent): void {\n if (!this.$el.contains(getTargetElement(event))) {\n this.close();\n }\n }\n }\n</script>\n\n<style lang=\"scss\" scoped>\n .x-dropdown {\n position: relative;\n\n &__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 }\n</style>\n\n<docs lang=\"mdx\">\n## Example\n\nThe `Dropdown` component is a simple yet customizable select component. The component needs to work\nthe list of items available to select, which are passed using the `items` prop, and the selected\nitem, which is passed in using the `value` prop.\n\nThe supported items must be an array that can contain unique strings, unique numbers, or objects\nwith a unique `id` property.\n\nThe content of each item can be customized using the `item` slot, which apart from the data of the\nitem, it also receives via prop if the element is selected or highlighted.\n\nThere `toggle` slot can be used to customize the button that opens the dropdown. If this is not\nprovided, the `item` slot will be used for that.\n\n```vue\n<template>\n <BaseDropdown :items=\"items\" v-model=\"value\">\n <template #toggle=\"{ item, isOpen }\">{{ item }} {{ isOpen ? '🔼' : '🔽' }}️</template>\n\n <template #item=\"{ item, isSelected, isHighlighted }\">\n <span v-if=\"isHighlighted\">🟢</span>\n <span v-if=\"isSelected\">✅</span>\n <span>{{ item }}</span>\n </template>\n </BaseDropdown>\n</template>\n\n<script>\n import { BaseDropdown } from '@empathyco/x-components';\n\n export default {\n name: 'DropdownTest',\n components: {\n BaseDropdown\n },\n data() {\n return {\n items: ['a', 2, { id: '3' }],\n value: ['a']\n };\n }\n };\n</script>\n```\n</docs>\n"],"names":[],"mappings":";;;;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __decorate } from 'tslib';
|
|
2
2
|
import { Prop, Watch, Component } from 'vue-property-decorator';
|
|
3
3
|
import Vue from 'vue';
|
|
4
|
+
import { getTargetElement } from '../utils/html.js';
|
|
4
5
|
import { normalizeString } from '../utils/normalize.js';
|
|
5
6
|
import { isInRange } from '../utils/number.js';
|
|
6
7
|
import { debounce } from '../utils/debounce.js';
|
|
@@ -291,7 +292,7 @@ let BaseDropdown = class BaseDropdown extends Vue {
|
|
|
291
292
|
* @param event - The event to check if it has happen out of the dropdown component.
|
|
292
293
|
*/
|
|
293
294
|
closeIfEventIsOutOfDropdown(event) {
|
|
294
|
-
if (!this.$el.contains(event
|
|
295
|
+
if (!this.$el.contains(getTargetElement(event))) {
|
|
295
296
|
this.close();
|
|
296
297
|
}
|
|
297
298
|
}
|