import {TransitionGroupItemDirective} from "./transition-group-item.directive"; import {AfterViewInit, Component, ContentChildren, OnDestroy, QueryList, ViewEncapsulation} from "@angular/core"; import {Subscription} from "rxjs"; import {THREE} from "@angular/cdk/keycodes"; /** * Transition Component for array changes * * Call init() method if array is changed. e.g. (different array, insert, delete) and before swap action * * If a change is triggered, but you want to avoid transition use disable() - (Your changes) - init() - enable() * * */ @Component({ selector: '[transition-group]', template: '', styleUrls: ['transition-group.component.less'], encapsulation: ViewEncapsulation.None }) export class TransitionGroupComponent implements AfterViewInit, OnDestroy { @ContentChildren(TransitionGroupItemDirective) items: QueryList; private disabled: boolean = false; private subscription: Subscription; ngAfterViewInit() { this.init(); this.subscription = this.items.changes.subscribe(items => { if(!this.disabled) { items.forEach(item => item.prevPos = item.newPos || item.prevPos); items.forEach(this.runCallback); this.refreshPosition('newPos'); items.forEach(item => item.prevPos = item.prevPos || item.newPos); // for new items const animate = () => { items.forEach(this.applyTranslation); this['_forceReflow'] = document.body.offsetHeight; // force reflow to put everything in position this.items.forEach(this.runTransition.bind(this)); } const willMoveSome = items.some((item) => { const dx = item.prevPos.left - item.newPos.left; const dy = item.prevPos.top - item.newPos.top; return dx || dy; }); if (willMoveSome) { animate(); } else { setTimeout(() => { // for removed items this.refreshPosition('newPos'); animate(); }, 0); } } }); } ngOnDestroy() { if (this.subscription) { this.subscription.unsubscribe(); } } /** * Should be used in every array change e.g. (different array, insert, delete) * and before swap action * */ init() { this.refreshPosition('prevPos'); this.refreshPosition('newPos'); } runCallback(item: TransitionGroupItemDirective) { if (item.moveCallback) { item.moveCallback(); } } runTransition(item: TransitionGroupItemDirective) { if (!item.moved) { return; } const cssClass = 'list-move'; let el = item.el; let style: any = el.style; el.classList.add(cssClass); style.transform = style.WebkitTransform = style.transitionDuration = ''; el.addEventListener('transitionend', item.moveCallback = (e: any) => { if (!e || /transform$/.test(e.propertyName)) { el.removeEventListener('transitionend', item.moveCallback); item.moveCallback = null; el.classList.remove(cssClass); } }); } refreshPosition(prop: string) { this.items.forEach(item => { item[prop] = item.el.getBoundingClientRect(); }); } applyTranslation(item: TransitionGroupItemDirective) { item.moved = false; const dx = item.prevPos.left - item.newPos.left; const dy = item.prevPos.top - item.newPos.top; if (dx || dy) { item.moved = true; let style: any = item.el.style; style.transform = style.WebkitTransform = 'translate(' + dx + 'px,' + dy + 'px)'; style.transitionDuration = '0s'; } } /** * Enable transition * * */ enable() { this.disabled = false; } /** * Disable transition * * */ disable() { this.disabled = true; } }