@progress/kendo-angular-spreadsheet
Version:
A Spreadsheet Component for Angular
1,115 lines (1,100 loc) • 335 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 * 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