@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
JavaScript
/**-----------------------------------------------------------------------------------------
* 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 }]; } });