@progress/kendo-angular-treelist
Version:
Kendo UI TreeList for Angular - Display hierarchical data in an Angular tree grid view that supports sorting, filtering, paging, and much more.
244 lines (243 loc) • 9.64 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { EventEmitter, Injectable, Output, Renderer2 } from '@angular/core';
import { isDocumentAvailable, isPresent } from '@progress/kendo-angular-common';
import { getOffset, isNextSibling, isPreviousSibling, dropPosition, hintIcons, hintSVGIcons, hintClasses, hintStyles, dropIndicatorClasses, dropIndicatorStyles, defaultSelectors, rowIndexAttr } from './utils';
import * as i0 from "@angular/core";
/**
* @hidden
*/
export class RowReorderService {
renderer;
defaultSelectors = defaultSelectors;
dragTarget = null;
dropTarget = null;
view;
bindingDirective;
offsetY;
dropIndicator;
lastDropPosition = dropPosition.forbidden;
hintElement = null;
rowReorder = new EventEmitter();
constructor(renderer) {
this.renderer = renderer;
}
ngOnDestroy() {
this.destroyDropIndicator();
this.destroyHintElement();
}
press(ev) {
this.dragTarget = ev.dragTarget;
this.offsetY = ev.dragEvent.offsetY;
}
dragStart() {
this.createDropIndicator();
}
drag(ev) {
if (isPresent(ev.hintElement) && !isPresent(this.hintElement)) {
this.hintElement = ev.hintElement;
this.decorateHint();
}
const position = {
x: ev.dragEvent.clientX,
y: ev.dragEvent.clientY - this.offsetY
};
if (isPresent(this.hintElement)) {
this.renderer.setStyle(this.hintElement, 'left', `${position.x}px`);
this.renderer.setStyle(this.hintElement, 'top', `${position.y}px`);
}
this.positionDropIndicator(ev);
}
dragEnter(ev) {
this.dropTarget = ev.dropTarget;
this.view = ev.dragData;
}
dragLeave() {
this.dropTarget = null;
this.lastDropPosition === dropPosition.forbidden && this.hide();
}
dragEnd() {
this.destroyDropIndicator();
this.destroyHintElement();
this.dragTarget = null;
this.dropTarget = null;
this.hintElement = null;
}
drop() {
this.destroyDropIndicator();
this.destroyHintElement();
const rowReorderArgs = this.rowReorderArgs(this.dragTarget, this.dropTarget, this.view);
this.rowReorder.emit(rowReorderArgs);
}
get hintIcon() {
return hintIcons[this.lastDropPosition];
}
get hintSVGIcon() {
return hintSVGIcons[this.lastDropPosition];
}
getDefaultHintText(columns, data) {
let hintText = '';
const columnFieldsArray = columns
.toArray()
.filter(column => !column.hidden && isPresent(column.field))
.map(column => column.field);
const draggedDragRow = this.getDragRowPerElement(this.dragTarget, data);
const draggedDataItem = draggedDragRow?.dataItem;
isPresent(draggedDataItem) && columnFieldsArray.forEach(column => {
const columnValue = draggedDataItem[column];
isPresent(columnValue) ? hintText += `${columnValue} ` : null;
});
return hintText.trim();
}
rowReorderArgs(dragRow, dropRow, data) {
const dragRowData = this.getDragRowPerElement(dragRow, data);
const dropRowData = this.getDragRowPerElement(dropRow, data);
return {
draggedRows: [dragRowData],
dropTargetRow: dropRowData,
dropPosition: this.lastDropPosition
};
}
isOverChild(_item) { return false; }
reorderRows(_ev, _collection, _field) { }
get parentIdField() {
return this.bindingDirective.parentIdField;
}
get idField() {
return this.bindingDirective.idField;
}
get childrenField() {
return this.bindingDirective.childrenField;
}
get data() {
return this.bindingDirective.data;
}
getDragRowPerElement(row, data) {
let rowIndex = row?.getAttribute(rowIndexAttr);
rowIndex = rowIndex ? parseInt(rowIndex, 10) : -1;
const dataItem = rowIndex === -1 ? null : data?.at(rowIndex)?.data;
return {
dataItem,
rowIndex,
element: row
};
}
createDropIndicator() {
if (!isDocumentAvailable()) {
return;
}
this.dropIndicator = document.createElement('div');
this.decorateDropIndicator();
this.dropIndicator.innerHTML = `
<div class="k-drop-hint-start"></div>
<div class="k-drop-hint-line"></div>
`;
document.body.appendChild(this.dropIndicator);
this.hide();
}
destroyDropIndicator() {
if (!isDocumentAvailable()) {
return;
}
if (this.dropIndicator && this.dropIndicator.parentElement) {
document.body.removeChild(this.dropIndicator);
this.dropIndicator = null;
}
}
destroyHintElement() {
if (!isDocumentAvailable()) {
return;
}
if (this.hintElement?.parentElement) {
this.hintElement.parentElement.removeChild(this.hintElement);
this.hintElement = null;
}
}
decorateHint() {
hintClasses.forEach(className => this.renderer.addClass(this.hintElement, className));
Object.keys(hintStyles)
.forEach(style => this.renderer.setStyle(this.hintElement, style, hintStyles[style]));
}
positionDropIndicator(ev) {
this.lastDropPosition = this.getDropPosition(ev.dragEvent);
this.updateDropIndicatorPosition();
}
decorateDropIndicator() {
dropIndicatorClasses.forEach(className => this.renderer.addClass(this.dropIndicator, className));
Object.keys(dropIndicatorStyles)
.forEach(style => this.renderer.setStyle(this.dropIndicator, style, dropIndicatorStyles[style]));
}
getDropPosition(e) {
if (this.dropTarget === this.dragTarget || !isPresent(this.dropTarget)) {
return dropPosition.forbidden;
}
const itemViewPortCoords = this.dropTarget.getBoundingClientRect();
const itemDivisionsCount = 3;
const itemDivisionHeight = itemViewPortCoords.height / itemDivisionsCount;
const { dropTargetRow } = this.rowReorderArgs(this.dragTarget, this.dropTarget, this.view);
const pointerPosition = e.clientY;
const itemTop = itemViewPortCoords.top;
let currentDropPosition = null;
if (pointerPosition <= itemTop + itemDivisionHeight) {
currentDropPosition = dropPosition.before;
}
else if (pointerPosition >= itemTop + itemViewPortCoords.height - itemDivisionHeight) {
currentDropPosition = dropPosition.after;
}
else {
currentDropPosition = dropPosition.over;
}
if (currentDropPosition === dropPosition.before && isNextSibling(this.dropTarget, this.dragTarget)) {
currentDropPosition = dropPosition.forbidden;
}
else if (currentDropPosition === dropPosition.after && isPreviousSibling(this.dropTarget, this.dragTarget)) {
currentDropPosition = dropPosition.forbidden;
}
if (isPresent(dropTargetRow.dataItem)) {
if (this.isOverChild(dropTargetRow.dataItem)) {
currentDropPosition = dropPosition.forbidden;
}
}
else {
currentDropPosition = dropPosition.forbidden;
}
return currentDropPosition;
}
updateDropIndicatorPosition() {
if (this.shouldHideDropIndicator() || !this.dropTarget) {
this.hide();
return;
}
this.show();
const destinationItemOffset = getOffset(this.dropTarget);
let indicatorOffsetTop = destinationItemOffset.top;
const indicatorOffsetLeft = destinationItemOffset.left + this.dropIndicator.offsetWidth / 2;
if (this.lastDropPosition === dropPosition.after) {
indicatorOffsetTop += this.dropTarget.offsetHeight;
}
this.renderer.setStyle(this.dropIndicator, 'left', `${indicatorOffsetLeft}px`);
this.renderer.setStyle(this.dropIndicator, 'top', `${indicatorOffsetTop}px`);
}
shouldHideDropIndicator() {
return this.lastDropPosition === dropPosition.forbidden || this.lastDropPosition === dropPosition.over;
}
hide() {
if (isPresent(this.dropIndicator)) {
this.renderer.setStyle(this.dropIndicator, 'display', 'none');
}
}
show() {
if (isPresent(this.dropIndicator)) {
this.renderer.removeStyle(this.dropIndicator, 'display');
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RowReorderService, deps: [{ token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RowReorderService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RowReorderService, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: i0.Renderer2 }]; }, propDecorators: { rowReorder: [{
type: Output
}] } });