UNPKG

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