UNPKG

@progress/kendo-angular-grid

Version:

Kendo UI Grid for Angular - high performance data grid with paging, filtering, virtualization, CRUD, and more.

189 lines (188 loc) 8.42 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { DomEventsService } from './../common/dom-events.service'; import { Directive, ElementRef, Renderer2 } from '@angular/core'; import { DraggableDirective, isDocumentAvailable, isPresent } from '@progress/kendo-angular-common'; import { SelectionService } from './selection.service'; import { CellSelectionService } from './cell-selection.service'; import { take, delay } from 'rxjs/operators'; import { merge } from 'rxjs'; import { isMultipleRangesEnabled } from '../utils'; import * as i0 from "@angular/core"; import * as i1 from "@progress/kendo-angular-common"; import * as i2 from "./selection.service"; import * as i3 from "./cell-selection.service"; import * as i4 from "./../common/dom-events.service"; const createElement = () => { if (!isDocumentAvailable()) { return; } const marquee = document.createElement("div"); marquee.className = "k-marquee"; const marqueeColor = document.createElement("div"); marqueeColor.className = "k-marquee-color"; marquee.appendChild(marqueeColor); return marquee; }; const POINTER_OFFSET = 2; const MINIMAL_DRAG_DISTANCE = 5; const offsets = { topLeft: { x: POINTER_OFFSET, y: POINTER_OFFSET }, topRight: { x: -POINTER_OFFSET, y: POINTER_OFFSET }, bottomLeft: { x: POINTER_OFFSET, y: -POINTER_OFFSET }, bottomRight: { x: -POINTER_OFFSET, y: -POINTER_OFFSET } }; /** * @hidden */ export class GridMarqueeDirective { draggable; selection; cellSelection; domEvents; host; renderer; // possibly add snap pressArgs; marqueeElement; pressTarget; subscriptions; selectionStarted = false; dragEndSubscription; constructor(draggable, selection, cellSelection, domEvents, host, renderer) { this.draggable = draggable; this.selection = selection; this.cellSelection = cellSelection; this.domEvents = domEvents; this.host = host; this.renderer = renderer; } ngOnInit() { this.subscriptions = (this.draggable.kendoPress.subscribe(this.start.bind(this))); this.subscriptions.add(this.draggable.kendoDrag.subscribe(this.moveMarquee.bind(this))); } ngOnDestroy() { this.subscriptions.unsubscribe(); this.clean(); } start(args) { const isInvalidTarget = args.originalEvent.target.matches('.k-grid-content, .k-grid-content-locked, .k-grid-aria-root, .k-checkbox'); const isRowReorderColumn = isPresent(args.originalEvent.target.closest('.k-drag-cell')); if (isInvalidTarget || isRowReorderColumn) { this.pressArgs = null; return; } this.pressArgs = args; this.pressTarget = null; } moveMarquee(args) { if (!this.pressTarget) { this.pressTarget = this.cellSelection.active ? this.cellSelection.mouseDownEventArgs : this.selection.mouseDownEventArgs; } const press = this.pressArgs; if (!press) { return; } if (!this.selectionStarted) { const distance = Math.sqrt((args.pageX - press.pageX) ** 2 + (args.pageY - press.pageY) ** 2); if (distance > MINIMAL_DRAG_DISTANCE) { this.selectionStarted = true; this.renderer.addClass(this.host.nativeElement, 'user-select-none'); this.renderer.setStyle(this.host.nativeElement, 'user-select', 'none'); this.dragEndSubscription = merge(this.domEvents.cellMouseup.pipe(take(1)), this.draggable.kendoRelease.pipe(delay(1), take(1))) .subscribe(this.endSelection.bind(this)); } else { return; } } this.initMarquee(); const element = this.marqueeElement; const marqueeQuadrant = this.getMarqueeQuadrant(args.pageX, args.pageY, press.pageX, press.pageY); let left = Math.min(args.pageX, press.pageX); let top = Math.min(args.pageY, press.pageY); const width = Math.abs(args.pageX - press.pageX); const height = Math.abs(args.pageY - press.pageY); if (marqueeQuadrant) { left += offsets[marqueeQuadrant].x; top += offsets[marqueeQuadrant].y; } element.style.left = `${left}px`; element.style.top = `${top}px`; element.style.width = `${width}px`; element.style.height = `${height}px`; } endSelection(args) { if (args.type === 'mouseup' || args.type === 'touchend') { const modifier = args.originalEvent.ctrlKey || args.originalEvent.metaKey; const preserveCurrentSelection = modifier && (isMultipleRangesEnabled(this.selection.settings) || isMultipleRangesEnabled(this.cellSelection.settings)); if (this.cellSelection.active) { this.cellSelection.dragging = true; this.cellSelection.changes.emit(this.cellSelection.selectRange(this.pressTarget.rowIndex, this.pressTarget.column.leafIndex, args.rowIndex, args.column.leafIndex, preserveCurrentSelection)); } else if (this.selection.active) { this.selection.dragging = true; this.selection.changes.emit(this.selection.selectRange(this.pressTarget.rowIndex, args.rowIndex, preserveCurrentSelection)); } } this.clean(); } clean() { if (this.marqueeElement) { document.body.removeChild(this.marqueeElement); this.marqueeElement = null; } if (this.dragEndSubscription) { this.dragEndSubscription.unsubscribe(); } this.renderer.removeClass(this.host.nativeElement, 'user-select-none'); this.renderer.removeStyle(this.host.nativeElement, 'user-select'); this.dragEndSubscription = null; this.pressTarget = null; this.pressArgs = null; this.selectionStarted = false; // eslint-disable-next-line no-unused-expressions this.cellSelection.active ? this.cellSelection.dragging = false : this.selection.dragging = false; } initMarquee() { if (!isDocumentAvailable()) { return; } if (!this.marqueeElement) { this.marqueeElement = createElement(); document.body.appendChild(this.marqueeElement); } } getMarqueeQuadrant(pointerX, pointerY, startX, startY) { const leftHalf = pointerX < startX; const rightHalf = pointerX > startX; const topHalf = pointerY < startY; const bottomHalf = pointerY > startY; if (leftHalf && topHalf) { return 'topLeft'; } if (leftHalf && bottomHalf) { return 'bottomLeft'; } if (rightHalf && topHalf) { return 'topRight'; } if (rightHalf && bottomHalf) { return 'bottomRight'; } return null; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: GridMarqueeDirective, deps: [{ token: i1.DraggableDirective }, { token: i2.SelectionService }, { token: i3.CellSelectionService }, { token: i4.DomEventsService }, { token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: GridMarqueeDirective, isStandalone: true, selector: "[kendoGridSelectionMarquee]", ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: GridMarqueeDirective, decorators: [{ type: Directive, args: [{ selector: '[kendoGridSelectionMarquee]', standalone: true }] }], ctorParameters: function () { return [{ type: i1.DraggableDirective }, { type: i2.SelectionService }, { type: i3.CellSelectionService }, { type: i4.DomEventsService }, { type: i0.ElementRef }, { type: i0.Renderer2 }]; } });