@progress/kendo-angular-grid
Version:
Kendo UI Grid for Angular - high performance data grid with paging, filtering, virtualization, CRUD, and more.
236 lines (235 loc) • 9.67 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, isDifferentParent, defaultSelectors } from './utils';
import * as i0 from "@angular/core";
/**
* @hidden
*/
export class RowReorderService {
renderer;
hintElement = null;
defaultSelectors = defaultSelectors;
hintText = '';
skip;
dropIndicator;
lastDropPosition = dropPosition.forbidden;
dragTarget = null;
dropTarget = null;
offsetY;
rowReorder = new EventEmitter();
constructor(renderer) {
this.renderer = renderer;
}
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;
}
dragLeave() {
this.dropTarget = null;
this.hide();
}
dragEnd() {
this.destroyDropIndicator();
this.dragTarget = null;
this.dropTarget = null;
this.hintElement = null;
}
drop(ev) {
this.destroyDropIndicator();
const rowReorderArgs = this.rowReorderArgs(this.dragTarget, this.dropTarget, ev.dragData);
this.rowReorder.emit(rowReorderArgs);
}
reorderRows(ev, collection) {
if (this.lastDropPosition === dropPosition.forbidden) {
return;
}
const { draggedRows, dropTargetRow } = ev;
const draggedDataItem = draggedRows[0].dataItem;
const dropTargetDataItem = dropTargetRow.dataItem;
const draggedItemIndex = collection.indexOf(draggedDataItem);
const dropTargetIndex = collection.indexOf(dropTargetDataItem);
const idxToAdd = this.calculateIndexToAdd(draggedItemIndex, dropTargetIndex);
collection.splice(draggedItemIndex, 1);
collection.splice(idxToAdd, 0, draggedDataItem);
}
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();
}
getDraggedRow(data) {
return this.getDragRowPerElement(this.dragTarget, data);
}
rowReorderArgs(dragRow, dropRow, data) {
const dragRowData = this.getDragRowPerElement(dragRow, data);
const dropRowData = this.getDragRowPerElement(dropRow, data);
return {
draggedRows: [dragRowData],
dropTargetRow: dropRowData,
dropPosition: this.lastDropPosition
};
}
getDragRowPerElement(row, data) {
let rowIndex = row?.getAttribute('data-kendo-grid-item-index');
rowIndex = rowIndex ? parseInt(rowIndex, 10) : -1;
const skip = this.skip || 0;
const dataItem = rowIndex === -1 ? null : data[rowIndex - skip];
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;
}
}
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();
}
calculateIndexToAdd(dragIndex, dropIndex) {
if (dragIndex > dropIndex && this.lastDropPosition === dropPosition.after) {
return dropIndex + 1;
}
else if (dragIndex > dropIndex && this.lastDropPosition === dropPosition.before) {
return dropIndex;
}
else if (dragIndex < dropIndex && this.lastDropPosition === dropPosition.after) {
return dropIndex;
}
else if (dragIndex < dropIndex && this.lastDropPosition === dropPosition.before) {
return dropIndex - 1;
}
}
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;
}
if (isDifferentParent(this.dropTarget, this.dragTarget)) {
return dropPosition.forbidden;
}
const itemViewPortCoords = this.dropTarget.getBoundingClientRect();
const itemDivisionsCount = 2;
const itemDivisionHeight = itemViewPortCoords.height / itemDivisionsCount;
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;
}
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;
}
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;
}
hide() {
if (isPresent(this.dropIndicator)) {
this.dropIndicator.style.display = 'none';
}
}
show() {
if (isPresent(this.dropIndicator)) {
this.dropIndicator.style.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
}] } });