@progress/kendo-angular-grid
Version:
Kendo UI Grid for Angular - high performance data grid with paging, filtering, virtualization, CRUD, and more.
191 lines (190 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 { Directive, EventEmitter, Input, NgZone, Output, Renderer2, isDevMode } from '@angular/core';
import { ClipboardService } from './clipboard.service';
import { GridComponent } from '../grid.component';
import { hasObservers, isDocumentAvailable } from '@progress/kendo-angular-common';
import { Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import { closest, contains } from '../rendering/common/dom-queries';
import { recursiveFlatMap } from '../utils';
import { ClipboardErrorMessages } from './error-messages';
import * as i0 from "@angular/core";
import * as i1 from "../grid.component";
import * as i2 from "./clipboard.service";
/**
* The directive that enables the Grid built-in clipboard support. Allows copy, cut, and paste interactions
* with the Grid.
*/
export class GridClipboardDirective {
host;
clipboardService;
renderer;
zone;
/**
* Determines the target of the clipboard operation ([see example]({% slug clipboard_grid %}#toc-clipboard-target)). The possible options are:
* - `activeCell`
* - `selection`
*
* @default 'selection'
*/
set clipboardTarget(value) {
if (isDevMode()) {
this.zone.onStable.pipe(take(1)).subscribe(() => {
if (value === 'activeCell' && !(this.host.navigable.length)) {
console.warn(ClipboardErrorMessages.clipboardTarget.activeCellNavigable);
}
else if (value === 'selection' && !(this.host.selectable || this.host.selectionDirective)) {
console.warn(ClipboardErrorMessages.selectionSelectable);
}
});
}
this._target = value;
}
get clipboardTarget() {
return this._target;
}
/**
* The `GridClipboardDirective` settings.
*
* @default { wholeRow: false, copyHeaders: false copy: true, cut: true, paste: true }
*/
set clipboardSettings(value) {
this._clipboardSettings = Object.assign({}, this._clipboardSettings, value);
}
get clipboardSettings() {
return this._clipboardSettings;
}
/**
* Fires when the user performs `cut`, `copy` or `paste` action within the Grid content area.
*/
clipboard = new EventEmitter();
_target = 'selection';
_clipboardSettings = {
wholeRow: false,
copyHeaders: false,
copy: true,
cut: true,
paste: true
};
subs = new Subscription();
constructor(host, clipboardService, renderer, zone) {
this.host = host;
this.clipboardService = clipboardService;
this.renderer = renderer;
this.zone = zone;
}
ngAfterViewInit() {
if (!isDocumentAvailable()) {
return;
}
if (this.clipboardTarget === 'selection' && !(this.host.selectable || this.host.selectionDirective)) {
console.warn(ClipboardErrorMessages.selectionSelectable);
}
// needed due to the following issue in Chrome
// https://bugs.chromium.org/p/chromium/issues/detail?id=1156117&q=focus%20programmatically%20paste&can=2
this.zone.runOutsideAngular(() => {
this.subs.add(this.renderer.listen(document, 'copy', (args) => this.onClipboard('copy', args)));
this.subs.add(this.renderer.listen(document, 'cut', (args) => this.onClipboard('cut', args)));
this.subs.add(this.renderer.listen(document, 'paste', (args) => this.onClipboard('paste', args)));
});
}
ngOnDestroy() {
this.subs.unsubscribe();
}
onClipboard = (operationType, args) => {
if (!this.clipboardSettings[operationType] || !this.inGrid(args)) {
return;
}
const gridData = Array.isArray(this.host.data) ? this.host.data : this.host.data.data;
const gridDataItems = gridData.flatMap(recursiveFlatMap);
const selection = this.host.selection;
const selectionDirective = this.host.selectionDirective;
const targetType = this.clipboardTarget;
const isCellSelection = (this.host.selectable?.cell || selectionDirective.isCellSelectionMode);
let clipboardData = [];
switch (targetType) {
case 'activeCell':
{
const targetCell = { ...this.host.activeCell };
clipboardData = targetCell && [{ dataItem: targetCell.dataItem, dataRowIndex: targetCell.dataRowIndex, colIndex: targetCell.colIndex }];
}
break;
case 'selection':
{
const identifier = selectionDirective.selectionKey;
clipboardData = gridDataItems.flatMap((item, index) => {
if (identifier) {
const key = typeof identifier === 'string' ? item[identifier] : identifier({ index: index + this.host.skip, dataItem: item });
return isCellSelection ?
selection.some(s => s.itemKey === key) ? [{ dataItem: item, dataRowIndex: index + this.host.skip }] : [] :
selection.indexOf(key) > -1 ? [{ dataItem: item, dataRowIndex: index + this.host.skip }] : [];
}
return isCellSelection ?
selection.some(s => s.itemKey === index + this.host.skip) ? [{ dataItem: item, dataRowIndex: index + this.host.skip }] : [] :
selection.indexOf(index + this.host.skip) > -1 ? [{ dataItem: item, dataRowIndex: index + this.host.skip }] : [];
});
}
break;
}
const isPaste = operationType === 'paste';
const pastedData = args.clipboardData.getData('text');
const visibleCols = this.host.columns.toArray().filter(c => c.isVisible);
const data = isPaste ?
{
dataString: pastedData,
gridItems: this.clipboardService.getGridData(pastedData, visibleCols, this.clipboardTarget, clipboardData[0]?.dataRowIndex, {
wholeRow: this.clipboardSettings.wholeRow,
isCellSelection
})
} :
this.clipboardService.createClipboardData(clipboardData || [], visibleCols, {
wholeRow: this.clipboardSettings.wholeRow || (this.clipboardTarget === 'selection' && !isCellSelection),
target: this.clipboardTarget,
copyHeaders: this.clipboardSettings.copyHeaders,
operationType
});
!isPaste && navigator.clipboard.writeText(data.dataString);
if (hasObservers(this.clipboard)) {
this.zone.run(() => {
this.clipboard.emit({
type: operationType,
originalEvent: args,
clipboardData: data.dataString,
gridData: data.gridItems,
target: {
dataRowIndex: this.clipboardService.targetRowIndex,
colField: this.clipboardService.targetColField,
dataItem: clipboardData.find(item => item.dataRowIndex === this.clipboardService.targetRowIndex)?.dataItem
}
});
});
}
this.clipboardService.targetColField = this.clipboardService.targetRowIndex = null;
};
inGrid = (args) => {
const target = document.activeElement.matches('.k-table-td') ? document.activeElement : args.target;
const inContentArea = closest(target, node => node.parentElement?.classList.contains('k-grid-container'));
const inHost = contains(this.host.wrapper.nativeElement, target);
return target && inHost && inContentArea;
};
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: GridClipboardDirective, deps: [{ token: i1.GridComponent }, { token: i2.ClipboardService }, { token: i0.Renderer2 }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: GridClipboardDirective, isStandalone: true, selector: "[kendoGridClipboard]", inputs: { clipboardTarget: "clipboardTarget", clipboardSettings: "clipboardSettings" }, outputs: { clipboard: "clipboard" }, providers: [ClipboardService], exportAs: ["kendoGridClipboard"], ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: GridClipboardDirective, decorators: [{
type: Directive,
args: [{
selector: '[kendoGridClipboard]',
exportAs: 'kendoGridClipboard',
providers: [ClipboardService],
standalone: true
}]
}], ctorParameters: function () { return [{ type: i1.GridComponent }, { type: i2.ClipboardService }, { type: i0.Renderer2 }, { type: i0.NgZone }]; }, propDecorators: { clipboardTarget: [{
type: Input
}], clipboardSettings: [{
type: Input
}], clipboard: [{
type: Output
}] } });