UNPKG

@progress/kendo-angular-spreadsheet

Version:

A Spreadsheet Component for Angular

429 lines (428 loc) 21.5 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { Component, ElementRef, HostBinding, Input, NgZone, QueryList, Renderer2, ViewChild, ViewChildren } from '@angular/core'; import { NgFor, NgIf } from '@angular/common'; import { Subscription } from 'rxjs'; import { take } from 'rxjs/operators'; import { ButtonComponent, DropDownButtonComponent } from '@progress/kendo-angular-buttons'; import { Keys, isDocumentAvailable, isPresent, EventsOutsideAngularDirective } from '@progress/kendo-angular-common'; import { DialogService } from '@progress/kendo-angular-dialog'; import { TabStripComponent, TabTemplateDirective, TabStripTabComponent } from '@progress/kendo-angular-layout'; import { caretAltDownIcon, menuIcon, plusIcon, eyeIcon, eyeSlashIcon } from '@progress/kendo-svg-icons'; import { SpreadsheetService } from '../common/spreadsheet.service'; import { SpreadsheetLocalizationService } from '../localization/spreadsheet-localization.service'; import { ActionDialogComponent } from './action-dialog.component'; import { mapToSheetDescriptor } from '../utils'; import { getSheetActions } from './utils'; import * as i0 from "@angular/core"; import * as i1 from "../common/spreadsheet.service"; import * as i2 from "../localization/spreadsheet-localization.service"; import * as i3 from "@progress/kendo-angular-dialog"; /** * @hidden */ export class SheetsBarComponent { spreadsheetService; element; renderer; localization; dialogService; ngZone; hostClasses = true; sheets; sheetDescriptors; subs = new Subscription(); constructor(spreadsheetService, element, renderer, localization, dialogService, ngZone) { this.spreadsheetService = spreadsheetService; this.element = element; this.renderer = renderer; this.localization = localization; this.dialogService = dialogService; this.ngZone = ngZone; this.subs.add(spreadsheetService.onSheetsBarFocus.subscribe(() => ngZone.run(() => this.tabstrip.selectTab(this.sheets.findIndex(sh => sh.text === spreadsheetService.currentActiveSheet))))); } addButton; menuButton; tabstrip; actionDdbs; actionDdbRefs; get activeSheet() { return this.spreadsheetService.spreadsheet?.activeSheet()?.name(); } plusIcon = plusIcon; menuIcon = menuIcon; caretAltDownIcon = caretAltDownIcon; selected = false; sheetsMenuList = []; openedDdb = null; tabListSub; hiddenStateIcons = { hidden: 'eye-slash', visible: 'eye' }; hiddenStateSVGIcons = { hidden: eyeSlashIcon, visible: eyeIcon }; get tablistId() { return this.spreadsheetService.tablistId; } ngAfterViewInit() { if (!isDocumentAvailable() || !this.element.nativeElement) { return; } const tablist = this.element.nativeElement.querySelector('.k-tabstrip-items'); this.renderer.setAttribute(tablist, 'id', this.tablistId); this.tabListSub = this.renderer.listen(tablist, 'keydown', this.onTabListKeyDown.bind(this)); } ngOnChanges(changes) { if (changes['sheets']) { this.sheetsMenuList = this.sheets?.map(sheet => ({ text: sheet.text, icon: this.hiddenStateIcons[sheet.state], svgIcon: this.hiddenStateSVGIcons[sheet.state] })); this.sheets = changes['sheets'].currentValue?.map((sheet, _, items) => ({ ...sheet, sheetActions: getSheetActions(items, sheet) .map(item => ({ ...item, text: this.messageFor(item.messageKey) })) })); } } ngOnDestroy() { if (this.tabListSub) { this.tabListSub(); } this.subs.unsubscribe(); } onAddClick = () => { if (this.spreadsheetService.spreadsheet) { this.spreadsheetService.spreadsheet.view.sheetsbar.onAddSelect(); this.notifySheetsChange(); } }; onTabSelect(ev) { if (ev.title !== this.activeSheet) { this.selectSheet(ev.title); } } onOpen(ddb) { if (isPresent(this.openedDdb) && this.openedDdb !== ddb) { this.openedDdb.toggle(false); } this.openedDdb = ddb; } onClose() { this.openedDdb = null; const activeTabIdx = this.sheets.findIndex(sheet => sheet.active); this.tabstrip.selectTab(activeTabIdx); } onActionClick(dataItem, sheet) { if (dataItem.disabled) { return; } if (dataItem.commandName === 'delete' || dataItem.commandName === 'rename') { this.openDialog(dataItem, sheet); } else { this.actionsCallback[dataItem.commandName](sheet, dataItem.messageKey); } } onMenuItemClick(item) { const sheet = this.sheets.find(s => s.text === item.text); sheet.state = 'visible'; this.spreadsheetService.spreadsheet.sheets().find(sh => sh.name() === sheet.text)._state('visible'); this.selectSheet(sheet.text); } messageFor(key) { return this.localization.get(key); } openDialog(dataItem, sheet) { const dialogSettings = { appendTo: this.spreadsheetService.dialogContainer, title: this.messageFor(dataItem.commandName), content: ActionDialogComponent, actions: [{ text: this.messageFor(dataItem.dialogButton), themeColor: 'primary' }, { text: this.messageFor('dialogCancel') }], actionsLayout: 'stretched', autoFocusedElement: '.k-textbox .k-input-inner, .k-button-solid-primary' }; const dialog = this.dialogService.open(dialogSettings); const dialogInstance = dialog.dialog.instance; const dialogContent = dialog.content.instance; dialogInstance.action.pipe(take(1)).subscribe((event) => { if (event.text === this.messageFor(dataItem.dialogButton)) { const sheetsBar = this.spreadsheetService.spreadsheet.view.sheetsbar; if (sheetsBar) { const allSheets = this.spreadsheetService.spreadsheet.sheets(); const sheetIndex = allSheets.findIndex(s => s.name() === sheet.text); dataItem.commandName === 'delete' ? sheetsBar.onSheetRemove(sheet.text) : sheetsBar.onSheetRename(dialogContent.value, sheetIndex); this.notifySheetsChange(); } } }); dialogContent.setData({ value: sheet.text, tabindex: -1, commandName: dataItem.commandName }); } getCopyRegex(sheetName) { const newName = sheetName.replaceAll('(', '\\(').replaceAll(')', '\\)'); const st = `(${newName})\\s?\\(`; return new RegExp(st, 's'); } actionsCallback = { copy: (sheetInfo) => { let copies = 0; const regex = this.getCopyRegex(sheetInfo.text); this.sheets.forEach(sheet => { const isPresent = regex.test(sheet.text); if (isPresent) { copies += 1; } }); const sheetToCopy = this.spreadsheetService.spreadsheet.sheets().find(s => s.name() === sheetInfo.text); const newName = `${sheetInfo.text} (${copies + 1})`; this.spreadsheetService.spreadsheet.insertSheet({ data: { ...sheetToCopy.toJSON(), name: newName }, index: sheetInfo.index + 1 }); this.selectSheet(newName); }, move: (sheetInfo, itemKey) => { const isMoveRight = itemKey === 'sheetMoveRight'; let oldIndex = -1; let newIndex = -1; const sheets = this.spreadsheetService.spreadsheet.sheets(); if (isMoveRight) { for (let i = 0; i < sheets.length; i++) { if (sheets[i].name() === sheetInfo.text) { oldIndex = i; } if (oldIndex > -1 && i > oldIndex && sheets[i]._state() === 'visible') { newIndex = i; break; } } } else { for (let i = sheets.length - 1; i >= 0; i--) { if (sheets[i].name() === sheetInfo.text) { oldIndex = i; } if (oldIndex > -1 && (i < oldIndex) && sheets[i]._state() === 'visible') { newIndex = i; break; } } } const sheetsBar = this.spreadsheetService.spreadsheet.view.sheetsbar; sheetsBar.onSheetReorderEnd({ oldIndex, newIndex }); this.selectSheet(sheetInfo.text); this.notifySheetsChange(); }, hide: (sheet) => { sheet.state = 'hidden'; const sheets = this.spreadsheetService.spreadsheet.sheets(); const sheetIndex = sheets.findIndex(s => s.name() === sheet.text); sheets[sheetIndex]._state('hidden'); const newSelectedIndex = sheetIndex < sheets.length - 1 ? sheetIndex + 1 : 0; const sheetToSelect = sheets[newSelectedIndex].name(); this.selectSheet(sheetToSelect); this.notifySheetsChange(); } }; selectSheet(sheetName) { const spreadsheetSheet = this.spreadsheetService.spreadsheet.sheets().find(s => s.name() === sheetName); this.spreadsheetService.spreadsheet.activeSheet(spreadsheetSheet); this.spreadsheetService.currentActiveSheet = sheetName; this.spreadsheetService.activeSheetChanged.next(spreadsheetSheet); this.notifySheetsChange(); } onTabListKeyDown(ev) { const buttonEl = ev.target.querySelector('kendo-dropdownbutton'); const index = Array.from(this.actionDdbRefs).findIndex(el => el.nativeElement === buttonEl); const ddb = Array.from(this.actionDdbs)[index]; if (!ddb) { return; } const altKey = ev.altKey; const arrowDown = ev.keyCode === Keys.ArrowDown; const shouldOpenDdb = altKey && arrowDown && !ddb.isOpen; if (shouldOpenDdb) { ev.preventDefault(); ddb.togglePopupVisibility(); } } notifySheetsChange() { this.ngZone.run(() => { const newSheets = this.spreadsheetService.spreadsheet.sheets(); const sheetDesc = mapToSheetDescriptor(newSheets); this.sheets = sheetDesc .flatMap((item, index, items) => item.state === 'visible' ? [{ ...item, inEdit: false, first: index === 0, last: index === items.length - 1, text: item.name, active: (item.name === this.activeSheet) || items.length === 1, index, sheetActions: getSheetActions(items, item) .map(item => ({ ...item, text: this.messageFor(item.messageKey) })) }] : []); this.sheetsMenuList = this.sheets?.map(sheet => ({ text: sheet.text, icon: this.hiddenStateIcons[sheet.state], svgIcon: this.hiddenStateSVGIcons[sheet.state] })); }); this.ngZone.onStable.pipe(take(1)).subscribe(() => this.spreadsheetService.spreadsheet.view.clipboard.focus()); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SheetsBarComponent, deps: [{ token: i1.SpreadsheetService }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i2.SpreadsheetLocalizationService }, { token: i3.DialogService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: SheetsBarComponent, isStandalone: true, selector: "[kendoSpreadsheetSheetsBar]", inputs: { sheets: "sheets", sheetDescriptors: "sheetDescriptors" }, host: { properties: { "class.k-spreadsheet-sheets-bar": "this.hostClasses" } }, viewQueries: [{ propertyName: "addButton", first: true, predicate: ["addButton"], descendants: true }, { propertyName: "menuButton", first: true, predicate: ["menuButton"], descendants: true }, { propertyName: "tabstrip", first: true, predicate: ["tabstrip"], descendants: true }, { propertyName: "actionDdbs", predicate: ["sheetDdb"], descendants: true }, { propertyName: "actionDdbRefs", predicate: ["sheetDdb"], descendants: true, read: ElementRef }], usesOnChanges: true, ngImport: i0, template: ` <button kendoButton #addButton [title]="messageFor('addSheet')" type="button" fillMode="flat" class="k-spreadsheet-sheet-add" icon="plus" [svgIcon]="plusIcon" [kendoEventsOutsideAngular]="{click: onAddClick}" [attr.aria-controls]="tablistId"> </button> <kendo-dropdownbutton #menuButton fillMode="flat" buttonClass="k-spreadsheet-sheets-menu" icon="menu" [svgIcon]="menuIcon" [data]="sheetsMenuList" (itemClick)="onMenuItemClick($event)" (open)="onOpen(menuButton)" [buttonAttributes]="{title: messageFor('sheetsMenu')}" [attr.aria-controls]="tablistId"> </kendo-dropdownbutton> <kendo-tabstrip #tabstrip [tabPosition]="'bottom'" [showContentArea]="false" [scrollable]="{ scrollButtonsPosition: 'end', mouseScroll: false }" class="k-spreadsheet-sheets" (tabSelect)="onTabSelect($event)"> <ng-container *ngFor="let sheet of sheets"> <kendo-tabstrip-tab *ngIf="sheet.state === 'visible'" [title]="sheet.text" [selected]="sheet.text === activeSheet"> <ng-template kendoTabTemplate> <span class="k-link"> <span class="k-link-text">{{sheet.text}}</span> </span> <span class="k-item-actions"> <kendo-dropdownbutton #sheetDdb fillMode="flat" icon="caret-alt-down" [svgIcon]="caretAltDownIcon" buttonClass="k-menu-button" [data]="sheet.sheetActions" [buttonAttributes]="{'aria-hidden': 'true', 'tabindex': '-1', role: 'presentation'}" (open)="onOpen(sheetDdb)" (close)="onClose()" (click)="$event.stopPropagation()" (itemClick)="onActionClick($event, sheet)"> </kendo-dropdownbutton> </span> </ng-template> </kendo-tabstrip-tab> </ng-container> </kendo-tabstrip> `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "directive", type: EventsOutsideAngularDirective, selector: "[kendoEventsOutsideAngular]", inputs: ["kendoEventsOutsideAngular", "scope"] }, { kind: "component", type: DropDownButtonComponent, selector: "kendo-dropdownbutton", inputs: ["arrowIcon", "icon", "svgIcon", "iconClass", "imageUrl", "textField", "data", "size", "rounded", "fillMode", "themeColor", "buttonAttributes"], outputs: ["itemClick", "focus", "blur"], exportAs: ["kendoDropDownButton"] }, { kind: "component", type: TabStripComponent, selector: "kendo-tabstrip", inputs: ["height", "animate", "tabAlignment", "tabPosition", "keepTabContent", "closable", "scrollable", "size", "closeIcon", "closeIconClass", "closeSVGIcon", "showContentArea"], outputs: ["tabSelect", "tabClose", "tabScroll"], exportAs: ["kendoTabStrip"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: TabStripTabComponent, selector: "kendo-tabstrip-tab", inputs: ["title", "disabled", "cssClass", "cssStyle", "selected", "closable", "closeIcon", "closeIconClass", "closeSVGIcon"], exportAs: ["kendoTabStripTab"] }, { kind: "directive", type: TabTemplateDirective, selector: "[kendoTabTemplate]" }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SheetsBarComponent, decorators: [{ type: Component, args: [{ selector: '[kendoSpreadsheetSheetsBar]', template: ` <button kendoButton #addButton [title]="messageFor('addSheet')" type="button" fillMode="flat" class="k-spreadsheet-sheet-add" icon="plus" [svgIcon]="plusIcon" [kendoEventsOutsideAngular]="{click: onAddClick}" [attr.aria-controls]="tablistId"> </button> <kendo-dropdownbutton #menuButton fillMode="flat" buttonClass="k-spreadsheet-sheets-menu" icon="menu" [svgIcon]="menuIcon" [data]="sheetsMenuList" (itemClick)="onMenuItemClick($event)" (open)="onOpen(menuButton)" [buttonAttributes]="{title: messageFor('sheetsMenu')}" [attr.aria-controls]="tablistId"> </kendo-dropdownbutton> <kendo-tabstrip #tabstrip [tabPosition]="'bottom'" [showContentArea]="false" [scrollable]="{ scrollButtonsPosition: 'end', mouseScroll: false }" class="k-spreadsheet-sheets" (tabSelect)="onTabSelect($event)"> <ng-container *ngFor="let sheet of sheets"> <kendo-tabstrip-tab *ngIf="sheet.state === 'visible'" [title]="sheet.text" [selected]="sheet.text === activeSheet"> <ng-template kendoTabTemplate> <span class="k-link"> <span class="k-link-text">{{sheet.text}}</span> </span> <span class="k-item-actions"> <kendo-dropdownbutton #sheetDdb fillMode="flat" icon="caret-alt-down" [svgIcon]="caretAltDownIcon" buttonClass="k-menu-button" [data]="sheet.sheetActions" [buttonAttributes]="{'aria-hidden': 'true', 'tabindex': '-1', role: 'presentation'}" (open)="onOpen(sheetDdb)" (close)="onClose()" (click)="$event.stopPropagation()" (itemClick)="onActionClick($event, sheet)"> </kendo-dropdownbutton> </span> </ng-template> </kendo-tabstrip-tab> </ng-container> </kendo-tabstrip> `, standalone: true, imports: [ButtonComponent, EventsOutsideAngularDirective, DropDownButtonComponent, TabStripComponent, NgFor, NgIf, TabStripTabComponent, TabTemplateDirective] }] }], ctorParameters: function () { return [{ type: i1.SpreadsheetService }, { type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i2.SpreadsheetLocalizationService }, { type: i3.DialogService }, { type: i0.NgZone }]; }, propDecorators: { hostClasses: [{ type: HostBinding, args: ['class.k-spreadsheet-sheets-bar'] }], sheets: [{ type: Input }], sheetDescriptors: [{ type: Input }], addButton: [{ type: ViewChild, args: ['addButton'] }], menuButton: [{ type: ViewChild, args: ['menuButton'] }], tabstrip: [{ type: ViewChild, args: ['tabstrip'] }], actionDdbs: [{ type: ViewChildren, args: ['sheetDdb'] }], actionDdbRefs: [{ type: ViewChildren, args: ['sheetDdb', { read: ElementRef }] }] } });