UNPKG

@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.

274 lines (273 loc) 10.8 kB
/**----------------------------------------------------------------------------------------- * 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]; if (isPresent(columnValue)) { hintText += `${columnValue} `; } }); 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) { } /** * Triggers row reordering programmatically via keyboard shortcut. * @param dragRowIndex - The index of the row to move * @param dropRowIndex - The index of the target row * @param dropPosition - The position relative to the target row ('before' or 'after') * @param data - The data array (view) */ reorderViaKeyboard(dragRowIndex, dropRowIndex, dropPosition, data) { if (dropPosition === 'forbidden') { return; } const dragRow = this.createVirtualRowElement(dragRowIndex); const dropRow = this.createVirtualRowElement(dropRowIndex); this.lastDropPosition = dropPosition; const rowReorderArgs = this.rowReorderArgs(dragRow, dropRow, data); this.rowReorder.emit(rowReorderArgs); } createVirtualRowElement(rowIndex) { const virtualElement = { getAttribute: (attr) => { if (attr === rowIndexAttr) { return String(rowIndex); } return null; } }; return virtualElement; } 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: "18.2.14", ngImport: i0, type: RowReorderService, deps: [{ token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RowReorderService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RowReorderService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i0.Renderer2 }], propDecorators: { rowReorder: [{ type: Output }] } });