openaire-library/utils/transition-group/transition-group.component.ts

144 lines
3.9 KiB
TypeScript

import {TransitionGroupItemDirective} from "./transition-group-item.directive";
import {
AfterViewInit, ChangeDetectorRef,
Component,
ContentChildren,
ElementRef, Input,
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: '<ng-content></ng-content>',
styleUrls: ['transition-group.component.less'],
encapsulation: ViewEncapsulation.None
})
export class TransitionGroupComponent implements AfterViewInit, OnDestroy {
@ContentChildren(TransitionGroupItemDirective) items: QueryList<TransitionGroupItemDirective>;
@Input()
public id: string;
public size: number;
private disabled: boolean = false;
private subscription: Subscription;
constructor(public element: ElementRef) {}
ngAfterViewInit() {
this.subscription = this.items.changes.subscribe(items => {
if(items.length === this.size && !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');
this.size = this.items.length;
}
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;
}
}