2022-07-12 15:24:23 +02:00
|
|
|
import {TransitionGroupItemDirective} from "./transition-group-item.directive";
|
2022-11-04 15:35:10 +01:00
|
|
|
import {
|
|
|
|
AfterViewInit,
|
|
|
|
Component,
|
|
|
|
ContentChildren,
|
2022-11-07 15:55:27 +01:00
|
|
|
ElementRef, Input,
|
2022-11-04 15:35:10 +01:00
|
|
|
OnDestroy,
|
|
|
|
QueryList,
|
|
|
|
ViewEncapsulation
|
|
|
|
} from "@angular/core";
|
2022-07-12 15:24:23 +02:00
|
|
|
import {Subscription} from "rxjs";
|
2022-07-14 16:38:54 +02:00
|
|
|
import {THREE} from "@angular/cdk/keycodes";
|
2022-07-12 15:24:23 +02:00
|
|
|
|
2022-07-14 16:38:54 +02:00
|
|
|
/**
|
|
|
|
* 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()
|
|
|
|
*
|
|
|
|
* */
|
2022-07-12 15:24:23 +02:00
|
|
|
@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>;
|
2022-11-07 15:55:27 +01:00
|
|
|
@Input()
|
|
|
|
public id: string;
|
2022-07-14 16:38:54 +02:00
|
|
|
private disabled: boolean = false;
|
2022-07-12 15:24:23 +02:00
|
|
|
private subscription: Subscription;
|
|
|
|
|
2022-11-04 15:35:10 +01:00
|
|
|
constructor(public element: ElementRef) {}
|
|
|
|
|
2022-07-12 15:24:23 +02:00
|
|
|
ngAfterViewInit() {
|
|
|
|
this.init();
|
|
|
|
this.subscription = this.items.changes.subscribe(items => {
|
2022-07-14 16:38:54 +02:00
|
|
|
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) {
|
2022-07-12 15:24:23 +02:00
|
|
|
animate();
|
2022-07-14 16:38:54 +02:00
|
|
|
} else {
|
|
|
|
setTimeout(() => { // for removed items
|
|
|
|
this.refreshPosition('newPos');
|
|
|
|
animate();
|
|
|
|
}, 0);
|
|
|
|
}
|
2022-07-12 15:24:23 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
ngOnDestroy() {
|
|
|
|
if (this.subscription) {
|
|
|
|
this.subscription.unsubscribe();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-14 16:38:54 +02:00
|
|
|
/**
|
|
|
|
* Should be used in every array change e.g. (different array, insert, delete)
|
|
|
|
* and before swap action
|
|
|
|
* */
|
2022-07-12 15:24:23 +02:00
|
|
|
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';
|
|
|
|
}
|
|
|
|
}
|
2022-07-14 16:38:54 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Enable transition
|
|
|
|
*
|
|
|
|
* */
|
|
|
|
enable() {
|
|
|
|
this.disabled = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disable transition
|
|
|
|
*
|
|
|
|
* */
|
|
|
|
disable() {
|
|
|
|
this.disabled = true;
|
|
|
|
}
|
2022-07-12 15:24:23 +02:00
|
|
|
}
|