UNPKG

@progress/kendo-angular-spreadsheet

Version:

A Spreadsheet Component for Angular

1,115 lines (1,100 loc) 335 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import * as i0 from '@angular/core'; import { Injectable, EventEmitter, Output, Inject, Optional, Component, Input, Directive, HostBinding, ElementRef, ViewChild, ViewChildren, HostListener, InjectionToken, forwardRef, ViewContainerRef, NgModule } from '@angular/core'; import { NgFor, NgIf, NgStyle, NgSwitch, NgSwitchCase, NgForOf } from '@angular/common'; import { Subject, Subscription } from 'rxjs'; import { take, map } from 'rxjs/operators'; import { validatePackage } from '@progress/kendo-licensing'; import { SpreadsheetWidget, registerEditor, Matrix, dateToSerial, validation, calc, serialToDate } from '@progress/kendo-spreadsheet-common'; import * as i1$4 from '@progress/kendo-angular-intl'; import { localeData } from '@progress/kendo-angular-intl'; import { formulaFxIcon, trashIcon, copyIcon, pencilIcon, eyeSlashIcon, arrowRightIcon, arrowLeftIcon, alignCenterIcon, alignJustifyIcon, alignLeftIcon, alignRightIcon, alignTopIcon, alignMiddleIcon, alignBottomIcon, dropletIcon, boldIcon, foregroundColorIcon, italicIcon, arrowRotateCwIcon, underlineIcon, arrowRotateCcwIcon, textWrapIcon, tableColumnInsertLeftIcon, tableColumnInsertRightIcon, tableRowInsertAboveIcon, tableRowInsertBelowIcon, tableRowDeleteIcon, tableColumnDeleteIcon, bordersNoneIcon, folderOpenIcon, downloadIcon, customFormatIcon, fontSizeIcon, fontFamilyIcon, cellsMergeIcon, cellsMergeHorizontallyIcon, cellsMergeVerticallyIcon, tableUnmergeIcon, linkIcon, fontGrowIcon, fontShrinkIcon, decimalDecreaseIcon, exclamationCircleIcon, decimalIncreaseIcon, cutIcon, clipboardIcon, eyeIcon, plusIcon, menuIcon, caretAltDownIcon, calendarIcon } from '@progress/kendo-svg-icons'; import * as i1 from '@progress/kendo-angular-l10n'; import { LocalizationService, L10N_PREFIX, RTL, ComponentMessages } from '@progress/kendo-angular-l10n'; import * as i1$1 from '@progress/kendo-angular-popup'; import { PopupService } from '@progress/kendo-angular-popup'; import { MenuComponent, MenuItemComponent, ContextMenuComponent } from '@progress/kendo-angular-menu'; import { EventsOutsideAngularDirective, isDocumentAvailable, isPresent, Keys, hasObservers, shouldShowValidationUI, WatermarkOverlayComponent, ResizeBatchService } from '@progress/kendo-angular-common'; import * as i1$2 from '@progress/kendo-angular-dialog'; import { DialogContentBase, DialogActionsComponent, DialogContainerService, DialogService, WindowService, WindowContainerService } from '@progress/kendo-angular-dialog'; import { IconWrapperComponent, IconsService } from '@progress/kendo-angular-icons'; import * as i1$3 from '@progress/kendo-angular-toolbar'; import { ToolBarToolComponent, ToolBarComponent, ToolBarButtonComponent, ToolBarButtonGroupComponent, ToolBarSeparatorComponent, ToolBarDropDownButtonComponent } from '@progress/kendo-angular-toolbar'; import { TextBoxComponent, ColorPickerComponent, CheckBoxComponent, RadioButtonComponent } from '@progress/kendo-angular-inputs'; import { LabelComponent } from '@progress/kendo-angular-label'; import { ButtonComponent, DropDownButtonComponent } from '@progress/kendo-angular-buttons'; import { TabStripComponent, TabStripTabComponent, TabTemplateDirective } from '@progress/kendo-angular-layout'; import { ComboBoxComponent, DropDownListComponent, ItemTemplateDirective } from '@progress/kendo-angular-dropdowns'; import { saveAs } from '@progress/kendo-file-saver'; import { Workbook } from '@progress/kendo-ooxml'; import * as i4 from '@angular/forms'; import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms'; import { CalendarComponent as CalendarComponent$1 } from '@progress/kendo-angular-dateinputs'; /** * @hidden */ const packageMetadata = { name: '@progress/kendo-angular-spreadsheet', productName: 'Kendo UI for Angular', productCode: 'KENDOUIANGULAR', productCodes: ['KENDOUIANGULAR'], publishDate: 1749540215, version: '19.1.1', licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/?utm_medium=product&utm_source=kendoangular&utm_campaign=kendo-ui-angular-purchase-license-keys-warning', }; let spreadsheetCounter = 0; /** * @hidden */ class SpreadsheetService { spreadsheet; sheetsChanged = new Subject(); onSheetsBarFocus = new Subject(); activeSheetChanged = new Subject(); selectionChanged = new Subject(); dialogContainer; constructor() { spreadsheetCounter++; } set currentActiveSheet(value) { this._currentActiveSheet = value; } get currentActiveSheet() { return this._currentActiveSheet; } get activeSheet() { return this.spreadsheet.activeSheet()?.name(); } get formulaListId() { return `k-spreadsheet-${spreadsheetCounter}-formula-list`; } get tablistId() { return `k-spreadsheet-${spreadsheetCounter}-tablist`; } _currentActiveSheet; notifySheetsChange(actionType, sheetInfo) { const sheets = this.spreadsheet.sheets(); this.sheetsChanged.next({ sheets, sheet: sheetInfo, actionType }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SpreadsheetService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SpreadsheetService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SpreadsheetService, decorators: [{ type: Injectable }], ctorParameters: function () { return []; } }); /** * @hidden */ class SpreadsheetToolsService { ngZone; stateChange = new EventEmitter(); set toolsState(state) { this._toolsState = state; } get toolsState() { return this._toolsState; } toolsFunctions = [ 'bold', 'italic', 'underline', 'fontFamily', 'fontSize', 'color', 'background', 'textAlign', 'verticalAlign', 'wrap', 'gridLines', 'format' ]; _toolsState = {}; constructor(ngZone) { this.ngZone = ngZone; } updateTools = (e) => { this.ngZone.run(() => { const state = {}; this.toolsFunctions.forEach(tool => { if (typeof e.range[tool] === 'function') { state[tool] = e.range[tool](); } else if (tool === 'gridLines') { state[tool] = e.range.sheet().showGridLines(); } }); if (this.toolsFunctions.some(k => state[k] !== this.toolsState[k])) { this.toolsState = state; this.stateChange.emit(state); } }); }; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SpreadsheetToolsService, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SpreadsheetToolsService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SpreadsheetToolsService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: i0.NgZone }]; }, propDecorators: { stateChange: [{ type: Output }] } }); /** * @hidden */ class SpreadsheetLocalizationService extends LocalizationService { constructor(prefix, messageService, _rtl) { super(prefix, messageService, _rtl); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SpreadsheetLocalizationService, deps: [{ token: L10N_PREFIX }, { token: i1.MessageService, optional: true }, { token: RTL, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SpreadsheetLocalizationService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SpreadsheetLocalizationService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: undefined, decorators: [{ type: Inject, args: [L10N_PREFIX] }] }, { type: i1.MessageService, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RTL] }] }]; } }); /** * @hidden */ const mapToSheetDescriptor = (sheetsArray) => { return sheetsArray.map((sheet) => { return { activeCell: sheet.activeCell(), columns: sheet._columns, defaultCellStyle: sheet._defaultCellStyle, drawings: sheet._drawings, frozenColumns: sheet.frozenColumns, frozenRows: sheet.frozenRows, hyperlinks: sheet._hyperlinks, mergedCells: sheet.mergedCells, name: sheet.name(), rows: sheet._rows, selection: sheet.selection(), showGridLines: sheet.showGridLines, state: sheet.state() }; }); }; /** * @hidden */ const replaceMessagePlaceholder = (message, name, value) => message.replace(new RegExp(`\{\\s*${name}\\s*\}`, 'g'), value); /** * @hidden */ const rowAndColPresent = (value) => { return (value && value.row !== null && value.col !== null && value.row > -1 && value.col > -1); }; /** * @hidden */ class FormulaListComponent { element; spreadsheetService; renderer; maxHeight; data = []; itemClick; formulaFxIcon = formulaFxIcon; constructor(element, spreadsheetService, renderer) { this.element = element; this.spreadsheetService = spreadsheetService; this.renderer = renderer; } ngOnInit() { this.renderer.setAttribute(this.element.nativeElement, 'id', this.spreadsheetService.formulaListId); } handleMouseDown = (ev) => { ev.preventDefault(); }; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormulaListComponent, deps: [{ token: i0.ElementRef }, { token: SpreadsheetService }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: FormulaListComponent, isStandalone: true, selector: "kendo-spreadsheet-formula-list", inputs: { maxHeight: "maxHeight", data: "data", itemClick: "itemClick" }, ngImport: i0, template: ` <ul #ulRef class="k-spreadsheet-formula-list k-list-ul k-list-md k-group k-reset" role="menu" [style.overflowY]="'auto'" [style.maxHeight]="maxHeight" [kendoEventsOutsideAngular]="{mousedown: handleMouseDown}"> <li *ngFor="let item of data" (click)="itemClick(item.text)" class="k-list-item" role="menuitem"> <kendo-icon-wrapper [svgIcon]="formulaFxIcon" name="formula-fx" ></kendo-icon-wrapper> <span className="k-list-item-text">{{item.text}}</span> </li> </ul> `, isInline: true, dependencies: [{ kind: "directive", type: EventsOutsideAngularDirective, selector: "[kendoEventsOutsideAngular]", inputs: ["kendoEventsOutsideAngular", "scope"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormulaListComponent, decorators: [{ type: Component, args: [{ selector: 'kendo-spreadsheet-formula-list', template: ` <ul #ulRef class="k-spreadsheet-formula-list k-list-ul k-list-md k-group k-reset" role="menu" [style.overflowY]="'auto'" [style.maxHeight]="maxHeight" [kendoEventsOutsideAngular]="{mousedown: handleMouseDown}"> <li *ngFor="let item of data" (click)="itemClick(item.text)" class="k-list-item" role="menuitem"> <kendo-icon-wrapper [svgIcon]="formulaFxIcon" name="formula-fx" ></kendo-icon-wrapper> <span className="k-list-item-text">{{item.text}}</span> </li> </ul> `, standalone: true, imports: [EventsOutsideAngularDirective, NgFor, IconWrapperComponent] }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: SpreadsheetService }, { type: i0.Renderer2 }]; }, propDecorators: { maxHeight: [{ type: Input }], data: [{ type: Input }], itemClick: [{ type: Input }] } }); /** * @hidden */ class FormulaInputDirective { element; popupService; spreadsheetService; localization; hostClasses = true; get title() { return this.localization.get('formulaInput'); } role = 'combobox'; ariaHasPopup = 'menu'; ariaExpanded = 'false'; get ariaControls() { return this.ariaExpanded === 'true' ? this.spreadsheetService.formulaListId : null; } formulaListMaxHeight; list; data; constructor(element, popupService, spreadsheetService, localization) { this.element = element; this.popupService = popupService; this.spreadsheetService = spreadsheetService; this.localization = localization; } popupRef; handler; get listElement() { return this.list?.element.nativeElement.firstElementChild; } get current() { return { element: this.element.nativeElement, list: { element: this.list?.element.nativeElement, data: (d) => { if (d) { this.data = d; this.list && (this.list.data = d); } else { return this.data; } }, itemClick: (handler) => { this.handler = handler; }, value: () => { return {}; }, focus: () => { const items = Array.from(this.listElement?.children || []); return items.indexOf(this.focusedItem()); }, focusNext: () => this.focusNext(1), focusPrev: () => this.focusNext(-1), focusFirst: () => { const list = this.listElement; if (list && list.children.item(0)) { this.unfocus(); list.children.item(0).classList.add('k-focus'); } }, focusLast: () => { const list = this.listElement; if (list && list.children.length) { this.unfocus(); list.children.item(list.children.length - 1).classList.add('k-focus'); } }, }, popup: { open: () => { this.popupRef?.close(); this.popupRef = null; this.popupRef = this.popupService.open({ anchor: this.element, content: FormulaListComponent, animate: { direction: 'down', duration: 100 } }); const list = this.popupRef.content.instance; this.list = list; list.data = this.data; list.itemClick = this.handler; list.maxHeight = this.formulaListMaxHeight; this.ariaExpanded = 'true'; }, close: () => { this.popupRef?.close(); this.popupRef = null; this.ariaExpanded = 'false'; }, position: () => { }, visible: () => { return this.popupRef; } } }; } focusedItem = () => this.list?.element.nativeElement.querySelector('.k-focus'); unfocus = () => { const focused = this.focusedItem(); if (focused) { focused.classList.remove('k-focus'); } }; focusNext = (dir) => { const element = this.list?.element.nativeElement.firstElementChild; const items = Array.from((element && element.children) || []); const focused = this.focusedItem(); let next; if (focused) { const index = items.indexOf(focused); focused.classList.remove('k-focus'); next = items[index + dir] ? items[index + dir] : (dir === 1 ? items[0] : items[items.length - 1]); } else { next = (dir === 1 ? items[0] : items[items.length - 1]); } if (next) { next.classList.add('k-focus'); const { offsetTop, offsetHeight, parentElement } = next; if (dir > 0) { if (offsetTop + offsetHeight >= parentElement.offsetHeight + parentElement.scrollTop) { parentElement.scrollTop = Math.min(parentElement.scrollTop + offsetHeight, parentElement.scrollHeight - parentElement.offsetHeight); } if (next === items[0]) { next.scrollIntoView(); } } else { if (offsetTop <= parentElement.scrollTop) { parentElement.scrollTop = Math.max(parentElement.scrollTop - offsetHeight, 0); } if (next === items[items.length - 1]) { next.scrollIntoView(); } } } }; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormulaInputDirective, deps: [{ token: i0.ElementRef }, { token: i1$1.PopupService }, { token: SpreadsheetService }, { token: SpreadsheetLocalizationService }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: FormulaInputDirective, isStandalone: true, selector: "[kendoSpreadsheetFormulaInput]", inputs: { formulaListMaxHeight: "formulaListMaxHeight" }, host: { properties: { "class.k-spreadsheet-formula-input": "this.hostClasses", "attr.title": "this.title", "attr.role": "this.role", "attr.aria-haspopup": "this.ariaHasPopup", "attr.aria-expanded": "this.ariaExpanded", "attr.aria-controls": "this.ariaControls" } }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormulaInputDirective, decorators: [{ type: Directive, args: [{ selector: '[kendoSpreadsheetFormulaInput]', standalone: true }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i1$1.PopupService }, { type: SpreadsheetService }, { type: SpreadsheetLocalizationService }]; }, propDecorators: { hostClasses: [{ type: HostBinding, args: ['class.k-spreadsheet-formula-input'] }], title: [{ type: HostBinding, args: ['attr.title'] }], role: [{ type: HostBinding, args: ['attr.role'] }], ariaHasPopup: [{ type: HostBinding, args: ['attr.aria-haspopup'] }], ariaExpanded: [{ type: HostBinding, args: ['attr.aria-expanded'] }], ariaControls: [{ type: HostBinding, args: ['attr.aria-controls'] }], formulaListMaxHeight: [{ type: Input }] } }); /** * @hidden */ const getSheetActions = (items, item) => { const visibleItems = items.filter(item => item.state === 'visible'); const itemIndex = item ? visibleItems.findIndex(i => i === item) : 0; return [{ messageKey: 'sheetDelete', icon: 'trash', svgIcon: trashIcon, commandName: 'delete', dialogButton: 'dialogDelete', disabled: visibleItems.length === 1 }, { messageKey: 'sheetDuplicate', icon: 'copy', svgIcon: copyIcon, commandName: 'copy' }, { messageKey: 'sheetRename', icon: 'pencil', svgIcon: pencilIcon, commandName: 'rename', dialogButton: 'dialogRename' }, { messageKey: 'sheetHide', icon: 'eye-slash', svgIcon: eyeSlashIcon, commandName: 'hide', disabled: visibleItems.length === 1 }, { messageKey: 'sheetMoveRight', icon: 'arrow-right', svgIcon: arrowRightIcon, commandName: 'move', disabled: visibleItems.length === 1 || itemIndex === visibleItems.length - 1 }, { messageKey: 'sheetMoveLeft', icon: 'arrow-left', svgIcon: arrowLeftIcon, commandName: 'move', disabled: visibleItems.length === 1 || itemIndex === 0 }]; }; /** * @hidden */ class ErrorHandlingService { spreadsheetService; localization; dialogService; spreadsheet; sheetsChanged = new Subject(); activeSheetChanged = new Subject(); selectionChanged = new Subject(); dialogContainer; constructor(spreadsheetService, localization, dialogService) { this.spreadsheetService = spreadsheetService; this.localization = localization; this.dialogService = dialogService; } set currentActiveSheet(value) { this._currentActiveSheet = value; } get currentActiveSheet() { return this._currentActiveSheet; } get activeSheet() { return this.spreadsheet.activeSheet()?.name(); } _currentActiveSheet; notifySheetsChange(actionType, sheetInfo) { const sheets = this.spreadsheet.sheets(); this.sheetsChanged.next({ sheets, sheet: sheetInfo, actionType }); } handleErrorMessage(messageData) { this.openDialog(messageData); } openDialog(messageData) { let dialogContent; let dialogTitle; if (messageData.name === 'message') { const localizationMsg = this.messageFor('invalidNameError'); let inputValue = messageData.text.split(' ').pop(); if (messageData.text.startsWith('Parse')) { inputValue = inputValue.substring(0, inputValue.length - 1); } dialogContent = replaceMessagePlaceholder(localizationMsg, 'inputValue', inputValue); } else if (messageData.name === 'validationError') { dialogContent = messageData.text; dialogTitle = messageData.title; } else { dialogContent = this.messageFor(messageData.name); } const dialogSettings = { appendTo: this.spreadsheetService.dialogContainer, title: dialogTitle ?? this.messageFor('dialogError'), content: dialogContent, actions: [{ text: this.messageFor('dialogOk'), themeColor: 'primary' }], width: 400, actionsLayout: 'start' }; const dialog = this.dialogService.open(dialogSettings); const dialogInstance = dialog.dialog?.instance; dialogInstance.action.pipe(take(1)).subscribe((event) => { if (event.text === this.messageFor('dialogOk')) { messageData.close(); } }); dialogInstance.close.pipe(take(1)).subscribe(() => { messageData.close(); }); } messageFor(text) { return this.localization.get(text); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ErrorHandlingService, deps: [{ token: SpreadsheetService }, { token: i1.LocalizationService }, { token: i1$2.DialogService }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ErrorHandlingService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ErrorHandlingService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: SpreadsheetService }, { type: i1.LocalizationService }, { type: i1$2.DialogService }]; } }); /** * @hidden */ const commandIcons = { alignCenter: 'align-center', alignJustify: 'align-justify', alignLeft: 'align-left', alignRight: 'align-right', alignTop: 'align-top', alignMiddle: 'align-middle', alignBottom: 'align-bottom', align: 'align-left', alignHorizontal: 'align-left', alignVertical: 'align-middle', background: 'droplet', bold: 'bold', color: 'foreground-color', italic: 'italic', redo: 'arrow-rotate-cw', underline: 'underline', undo: 'arrow-rotate-ccw', wrap: 'text-wrap', addColumnLeft: 'table-column-insert-left', addColumnRight: 'table-column-insert-right', addRowAbove: 'table-row-insert-above', addRowBelow: 'table-row-insert-below', deleteRow: 'table-row-delete', deleteColumn: 'table-column-delete', gridLines: 'borders-none', folderOpen: 'folder-open', download: 'download', format: 'custom-format', fontSize: 'font-size', fontFamily: 'font-family', merge: 'cells-merge', mergeAll: 'cells-merge', mergeHorizontally: 'cells-merge-horizontally', mergeVertically: 'cells-merge-vertically', unmerge: 'table-unmerge', insertLink: 'hyperlink', increaseFontSize: 'font-grow', decreaseFontSize: 'font-shrink', increaseDecimal: 'decimal-increase', decreaseDecimal: 'decimal-decrease', dataValidation: 'exclamation-circle', copy: 'copy', cut: 'cut', paste: 'clipboard', hideRow: 'eye-slash', unhideRow: 'eye', hideColumn: 'eye-slash', unhideColumn: 'eye' }; /** * @hidden */ const commandSVGIcons = { alignCenter: alignCenterIcon, alignJustify: alignJustifyIcon, alignLeft: alignLeftIcon, alignRight: alignRightIcon, alignTop: alignTopIcon, alignMiddle: alignMiddleIcon, alignBottom: alignBottomIcon, align: alignLeftIcon, alignHorizontal: alignLeftIcon, alignVertical: alignMiddleIcon, background: dropletIcon, bold: boldIcon, color: foregroundColorIcon, italic: italicIcon, redo: arrowRotateCwIcon, underline: underlineIcon, undo: arrowRotateCcwIcon, wrap: textWrapIcon, addColumnLeft: tableColumnInsertLeftIcon, addColumnRight: tableColumnInsertRightIcon, addRowAbove: tableRowInsertAboveIcon, addRowBelow: tableRowInsertBelowIcon, deleteRow: tableRowDeleteIcon, deleteColumn: tableColumnDeleteIcon, gridLines: bordersNoneIcon, folderOpen: folderOpenIcon, download: downloadIcon, format: customFormatIcon, fontSize: fontSizeIcon, fontFamily: fontFamilyIcon, merge: cellsMergeIcon, mergeAll: cellsMergeIcon, mergeHorizontally: cellsMergeHorizontallyIcon, mergeVertically: cellsMergeVerticallyIcon, unmerge: tableUnmergeIcon, insertLink: linkIcon, increaseFontSize: fontGrowIcon, decreaseFontSize: fontShrinkIcon, decreaseDecimal: decimalDecreaseIcon, dataValidation: exclamationCircleIcon, increaseDecimal: decimalIncreaseIcon, copy: copyIcon, cut: cutIcon, paste: clipboardIcon, hideRow: eyeSlashIcon, unhideRow: eyeIcon, hideColumn: eyeSlashIcon, unhideColumn: eyeIcon }; /** * @hidden */ class InsertLinkDialogComponent extends DialogContentBase { dialog; urlLink = ''; title; constructor(dialog) { super(dialog); this.dialog = dialog; } setData(args) { this.urlLink = args.link; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: InsertLinkDialogComponent, deps: [{ token: i1$2.DialogRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: InsertLinkDialogComponent, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: ` <form class="k-form k-form-md" method="dialog"> <div class="k-form-field"> <kendo-label [for]="textbox" text="URL"></kendo-label> <kendo-textbox #textbox [(value)]="urlLink"> </kendo-textbox> </div> </form> `, isInline: true, dependencies: [{ kind: "component", type: LabelComponent, selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }, { kind: "component", type: TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: InsertLinkDialogComponent, decorators: [{ type: Component, args: [{ template: ` <form class="k-form k-form-md" method="dialog"> <div class="k-form-field"> <kendo-label [for]="textbox" text="URL"></kendo-label> <kendo-textbox #textbox [(value)]="urlLink"> </kendo-textbox> </div> </form> `, standalone: true, imports: [LabelComponent, TextBoxComponent] }] }], ctorParameters: function () { return [{ type: i1$2.DialogRef }]; } }); /** * @hidden */ class ActionDialogComponent extends DialogContentBase { dialog; commandName; value; tabindex; constructor(dialog) { super(dialog); this.dialog = dialog; } setData(args) { this.value = args.value; this.tabindex = args.tabindex; this.commandName = args.commandName; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ActionDialogComponent, deps: [{ token: i1$2.DialogRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ActionDialogComponent, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: ` <ng-container *ngIf="commandName === 'delete'"> <p>The deleted sheet data will be lost.</p> <p>Are you sure you want to proceed?</p> </ng-container> <form class="k-form k-form-md" *ngIf="commandName === 'rename'" method="dialog"> <div class="k-form-field"> <kendo-label [for]="textbox" text="Rename sheet"></kendo-label> <div class="k-form-field-wrap"> <kendo-textbox #textbox autocomplete="off" placeholder="Sheet name" [(value)]="value"> </kendo-textbox> </div> </div> </form> `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: LabelComponent, selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }, { kind: "component", type: TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ActionDialogComponent, decorators: [{ type: Component, args: [{ template: ` <ng-container *ngIf="commandName === 'delete'"> <p>The deleted sheet data will be lost.</p> <p>Are you sure you want to proceed?</p> </ng-container> <form class="k-form k-form-md" *ngIf="commandName === 'rename'" method="dialog"> <div class="k-form-field"> <kendo-label [for]="textbox" text="Rename sheet"></kendo-label> <div class="k-form-field-wrap"> <kendo-textbox #textbox autocomplete="off" placeholder="Sheet name" [(value)]="value"> </kendo-textbox> </div> </div> </form> `, standalone: true, imports: [NgIf, LabelComponent, TextBoxComponent] }] }], ctorParameters: function () { return [{ type: i1$2.DialogRef }]; } }); /** * @hidden */ 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: SpreadsheetService }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: SpreadsheetLocalizationService }, { token: i1$2.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: TabStripComp