@progress/kendo-angular-spreadsheet
Version:
A Spreadsheet Component for Angular
977 lines (976 loc) • 101 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 { Component, ElementRef, EventEmitter, HostBinding, Input, NgZone, Output, ViewChild, ViewContainerRef } from '@angular/core';
import { NgFor, NgIf } from '@angular/common';
import { Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import { validatePackage } from '@progress/kendo-licensing';
import { packageMetadata } from './package-metadata';
import { SpreadsheetWidget, registerEditor, Matrix, dateToSerial, serialToDate, validation, calc } from '@progress/kendo-spreadsheet-common';
import { localeData, IntlService } from '@progress/kendo-angular-intl';
import { calendarIcon, caretAltDownIcon, downloadIcon, folderOpenIcon, formulaFxIcon } from '@progress/kendo-svg-icons';
import { L10N_PREFIX, LocalizationService } from '@progress/kendo-angular-l10n';
import { PopupService } from '@progress/kendo-angular-popup';
import { ContextMenuComponent, MenuItemComponent, MenuComponent } from '@progress/kendo-angular-menu';
import { Keys, hasObservers, isDocumentAvailable, isPresent, shouldShowValidationUI, WatermarkOverlayComponent } from '@progress/kendo-angular-common';
import { DialogService } from '@progress/kendo-angular-dialog';
import { IconWrapperComponent } from '@progress/kendo-angular-icons';
import { ToolBarDropDownButtonComponent, ToolBarSeparatorComponent, ToolBarButtonGroupComponent, ToolBarButtonComponent, ToolBarComponent } from '@progress/kendo-angular-toolbar';
import { SpreadsheetService } from './common/spreadsheet.service';
import { SpreadsheetToolsService } from './tools/tools.service';
import { SpreadsheetLocalizationService } from './localization/spreadsheet-localization.service';
import { mapToSheetDescriptor, rowAndColPresent } from './utils';
import { FormulaInputDirective } from './action-bar/formula-input.directive';
import { getSheetActions } from './sheets-bar/utils';
import { ErrorHandlingService } from './common/error-handling.service';
import { commandIcons, commandSVGIcons } from './tools/shared/command-icons';
import { InsertLinkDialogComponent } from './tools/insert/insert-link-dialog.component';
import { SheetsBarComponent } from './sheets-bar/sheets-bar.component';
import { NameBoxComponent } from './action-bar/namebox.component';
import { SpreadsheetGridLinesDirective } from './tools/gridlines-tool.directive';
import { SpreadsheetMergeDirective } from './tools/tables/merge-tool.directive';
import { SpreadsheetIncreaseDecimalDirective } from './tools/increase-decimal-tool.directive';
import { SpreadsheetDecreaseDecimalDirective } from './tools/decrease-decimal-tool.directive';
import { SpreadsheetDeleteRowButtonDirective } from './tools/tables/delete-row-button.directive';
import { SpreadsheetDeleteColumnButtonDirective } from './tools/tables/delete-column-button.directive';
import { SpreadsheetAddRowAboveButtonDirective } from './tools/tables/add-row-above-button.directive';
import { SpreadsheetAddRowBelowButtonDirective } from './tools/tables/add-row-below-button.directive';
import { SpreadsheetAddColumnRightButtonDirective } from './tools/tables/add-column-right-button.directive';
import { SpreadsheetAddColumnLeftButtonDirective } from './tools/tables/add-column-left-button.directive';
import { SpreadsheetInsertLinkDirective } from './tools/insert/insert-link-tool.directive';
import { SpreadsheetFormatDirective } from './tools/format-tool.directive';
import { SpreadsheetTextWrapDirective } from './tools/text-wrap-tool.directive';
import { SpreadsheetVerticalTextAlignDirective } from './tools/align/vertical-align-tool.directive';
import { SpreadsheetHorizontalTextAlignDirective } from './tools/align/horizontal-align-tool.directive';
import { SpreadsheetBackColorComponent } from './tools/colorpicker/spreadsheet-backcolor.component';
import { SpreadsheetForeColorComponent } from './tools/colorpicker/spreadsheet-forecolor.component';
import { SpreadsheetUnderlineDirective } from './tools/typographical-emphasis/underline-tool.directive';
import { SpreadsheetItalicDirective } from './tools/typographical-emphasis/italic-tool.directive';
import { SpreadsheetBoldDirective } from './tools/typographical-emphasis/bold-tool.directive';
import { SpreadsheetDecreaseFontSizeDirective } from './tools/font-size/decrease-font-tool.directive';
import { SpreadsheetIncreaseFontSizeDirective } from './tools/font-size/increase-font-tool.directive';
import { SpreadsheetFontSizeComponent } from './tools/font-size/spreadsheet-fontsize-tool.component';
import { SpreadsheetFontFamilyComponent } from './tools/font-family/spreadsheet-fontfamily-tool.component';
import { SpreadsheetRedoDirective } from './tools/history/redo-tool';
import { SpreadsheetUndoDirective } from './tools/history/undo-tool';
import { SpreadsheetSaveFileDirective } from './tools/save-file-tool.directive';
import { SpreadsheetLoadFileComponent } from './tools/load-file.component';
import { MainMenuDirective } from './common/main-menu.directive';
import { LocalizedMessagesDirective } from './localization/localized-messages.directive';
import { SpreadsheetDataValidationDirective } from './tools/data-validation-tool.directive';
import { ListEditorComponent } from './common/list-editor.component';
import { CalendarComponent } from './common/calendar-editor.component';
import * as i0 from "@angular/core";
import * as i1 from "@progress/kendo-angular-intl";
import * as i2 from "@progress/kendo-angular-l10n";
import * as i3 from "./common/spreadsheet.service";
import * as i4 from "./tools/tools.service";
import * as i5 from "./common/error-handling.service";
import * as i6 from "@progress/kendo-angular-dialog";
import * as i7 from "@progress/kendo-angular-popup";
/**
* Represents the [Kendo UI Spreadsheet component for Angular]({% slug overview_spreadsheet %}).
*/
export class SpreadsheetComponent {
ngZone;
intl;
host;
localization;
spreadsheetService;
toolsService;
errorService;
dialogService;
popupService;
container;
formulaBarInputRef;
formulaCellInputRef;
nameBoxRef;
dialogContainer;
contextMenu;
hostClass = true;
role = 'application';
/**
* The menu items configuration.
*/
set menuItems(items) {
this._menuItems = [];
this.ngZone.onStable.pipe(take(1)).subscribe(() => {
const normalizedItems = items.map(item => ({
active: item.active,
text: item.id === 'format' || item.id === 'data' ? this.messageFor(`${item.id}Tab`) : this.messageFor(item.id),
cssClass: item.active ? 'k-active' : null,
id: item.id
}));
this._menuItems = normalizedItems;
const activeItemIndex = this.menuItems.findIndex(item => item.active);
this.selectedMenuItem = this.menuItems[activeItemIndex > -1 ? activeItemIndex : 0];
});
}
get menuItems() {
return this._menuItems;
}
/**
* Sets the overflow option of the built-in Toolbar components.
* @default false
*/
overflow = false;
/**
* Sets the height of the formula list container.
* Accepts same values as the CSS [`style.height`](https://developer.mozilla.org/en-US/docs/Web/CSS/height) property.
*
* @default '300px'
*/
formulaListMaxHeight = '300px';
/**
* The name of the currently active sheet. Must match one of the sheet names.
*/
set activeSheet(value) {
this._activeSheet = value;
this.spreadsheetService.spreadsheet?.view?.sheetsbar.onSheetSelect(this.activeSheet);
}
get activeSheet() {
return this._activeSheet || this.spreadsheetService.spreadsheet?.activeSheet()?.name();
}
/**
* An array which defines the document sheets and their content.
*/
set sheets(value) {
const items = value.map((item, index, items) => ({
...item,
state: item.state || 'visible',
inEdit: false,
first: index === 0,
last: index === items.length - 1,
text: item.name,
active: (item.name === this.activeSheet) || items.length === 1,
index
}));
this._sheetsInfo = items;
}
get sheetsInfo() {
return this._sheetsInfo;
}
/**
* The number of columns in the document.
*
* @default 50
*/
columns = 50;
/**
* The initial column width in pixels.
*
* @default 100
*/
columnWidth = 100;
/**
* The initial styles applies to the sheet cells.
*/
defaultCellStyle;
/**
* The height of the header row in pixels.
*
* @default 30
*/
headerHeight = 30;
/**
* The width of the header column in pixels.
*
* @default 32
*/
headerWidth = 32;
/**
* The initial row height in pixels.
*
* @default 30
*/
rowHeight = 30;
/**
* The number of rows in the document.
*
* @default 200
*/
rows = 200;
/**
* An object containing any images used in the Spreadsheet. The keys should be image IDs (they are referenced by this ID in `sheets.drawings`),
* and the values should be image URLs. The image URLs can be either [`data URLs`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)
* (in which case the images are fully contained by the JSON), or external URLs.
*/
images;
/**
* Configures the Excel export settings of the Spreadsheet.
*/
excel;
/**
* Fired when a value in the Spreadsheet is changed. Exposes the `SpreadsheetWidget` instance and the selected `Range` as event data.
*/
change = new EventEmitter();
/**
* Fired when the selected range format is changed from the UI. Exposes the `SpreadsheetWidget` instance and the selected `Range` as event data.
*/
formatChange = new EventEmitter();
/**
* Fired when the selection is changed by the end user. Exposes the `SpreadsheetWidget` instance and the selected `Range` as event data.
*/
selectionChange = new EventEmitter();
/**
* Fired when the end user clicks the Export to Excel toolbar button.
* The event is preventable and exposes the `Workbook` object, a `preventDefault` method
* (if invoked, the generated file will not be saved), and the SpreadsheetWidget instance.
*/
excelExport = new EventEmitter();
/**
* Fired when the end user clicks the Open toolbar button.
* The event is preventable and exposes the selected `File` or `Blob`, a `preventDefault` method
* (if invoked, the selected file will not be loaded), and the SpreadsheetWidget instance.
*/
excelImport = new EventEmitter();
/**
* Fired when the active sheet is about to change.
* The event exposes the new active `Sheet` and the SpreadsheetWidget instance.
*/
activeSheetChange = new EventEmitter();
formulaFxIcon = formulaFxIcon;
folderOpenIcon = folderOpenIcon;
downloadIcon = downloadIcon;
selectedMenuItem;
get spreadsheetWidget() {
return this.spreadsheetService.spreadsheet;
}
showLicenseWatermark = false;
contextMenuItems = [];
_sheetsInfo;
_activeSheet;
_menuItems;
currentRange;
subs = new Subscription();
popupRef;
constructor(ngZone, intl, host, localization, spreadsheetService, toolsService, errorService, dialogService, popupService, container) {
this.ngZone = ngZone;
this.intl = intl;
this.host = host;
this.localization = localization;
this.spreadsheetService = spreadsheetService;
this.toolsService = toolsService;
this.errorService = errorService;
this.dialogService = dialogService;
this.popupService = popupService;
this.container = container;
const isValid = validatePackage(packageMetadata);
this.showLicenseWatermark = shouldShowValidationUI(isValid);
ngZone.onStable.pipe(take(1)).subscribe(() => {
if (!this.menuItems) {
this._menuItems = [{
id: 'file',
text: this.messageFor('file')
}, {
id: 'home',
text: this.messageFor('home'),
active: true,
cssClass: 'k-active'
}, {
id: 'insert',
text: this.messageFor('insert')
}, {
id: 'format',
text: this.messageFor('formatTab')
}, {
id: 'data',
text: this.messageFor('dataTab')
}, {
id: 'view',
text: this.messageFor('view')
}];
}
this.selectedMenuItem = this.menuItems[1];
});
}
ngAfterViewInit() {
if (!isDocumentAvailable()) {
return;
}
this.ngZone.runOutsideAngular(() => {
setTimeout(() => {
const spreadsheet = this.spreadsheetService.spreadsheet = new SpreadsheetWidget(this.host.nativeElement, this.options);
spreadsheet.bind('select', this.onSelectionChange);
spreadsheet.bind('change', this.onChange);
spreadsheet.bind('changeFormat', this.onChangeFormat);
spreadsheet.bind('excelImport', this.onExcelImport);
spreadsheet.bind('excelExport', this.onExcelExport);
spreadsheet.bind('keydown', this.onKeyDown);
spreadsheet.view.bind('update', this.updateState);
spreadsheet.view.bind('message', this.onMessage);
spreadsheet.bind('contextmenu', this.onContextMenu.bind(this));
const sheet = spreadsheet.activeSheet();
if (sheet) {
this.updateState({ range: sheet.range(sheet.activeCell()) });
}
this.updateActiveSheet(this.activeSheet || spreadsheet?.activeSheet()?.name());
this.configureSheets(spreadsheet);
});
this.subs.add(this.spreadsheetService.sheetsChanged.subscribe(this.onSheetsChanged.bind(this)));
this.subs.add(this.spreadsheetService.activeSheetChanged.subscribe(this.onActiveSheetChanged.bind(this)));
this.subs.add(this.spreadsheetService.selectionChanged.subscribe(range => {
this.currentRange = range;
if (this.popupRef) {
this.popupRef.close();
this.popupRef = null;
}
}));
this.spreadsheetService.dialogContainer = this.dialogContainer;
});
this.registerEditors();
}
ngOnChanges(changes) {
const dynamicOptions = [
'columns',
'columnWidth',
'defaultCellStyle',
'excel',
'headerHeight',
'headerWidth',
'images',
'names',
'rowHeight',
'rows',
'sheets'
];
const changedDynamicOptions = dynamicOptions.filter(o => isPresent(changes[o] && !changes[o].firstChange));
if (this.spreadsheetWidget && changedDynamicOptions.length) {
const newOptions = this.spreadsheetWidget.toJSON();
changedDynamicOptions.forEach(o => newOptions[o] = changes[o].currentValue);
this.spreadsheetWidget.fromJSON(newOptions);
}
}
ngOnDestroy() {
this.subs.unsubscribe();
}
/**
* @hidden
*/
onContextMenu(e) {
if (e.targetType === 'topcorner') {
return;
}
const selection = this.spreadsheetWidget.activeSheet().select();
const { topLeft, bottomRight } = selection;
const isRange = e.targetType === 'cell' && (topLeft.row !== bottomRight.row || topLeft.col !== bottomRight.col);
const targetType = isRange ? 'range' : e.targetType;
this.contextMenuItems = this.contextMenuItemsForTarget(targetType, e.showUnhide, e.showUnmerge);
this.contextMenu.show({ top: e.originalEvent.pageY, left: e.originalEvent.pageX });
}
/**
* @hidden
*/
onContextMenuSelect(e) {
let command;
switch (e.item.id) {
case 'cut':
command = { command: 'ToolbarCutCommand', options: { workbook: this.spreadsheetWidget.workbook } };
break;
case 'copy':
command = { command: 'ToolbarCopyCommand', options: { workbook: this.spreadsheetWidget.workbook } };
break;
case 'unmerge':
command = { command: 'MergeCellCommand', options: { value: 'unmerge' } };
break;
case 'mergeAll':
command = { command: 'MergeCellCommand', options: { value: 'cells' } };
break;
case 'mergeHorizontally':
command = { command: 'MergeCellCommand', options: { value: 'horizontally' } };
break;
case 'mergeVertically':
command = { command: 'MergeCellCommand', options: { value: 'vertically' } };
break;
case 'hideRow':
command = { command: 'HideLineCommand', options: { axis: 'row' } };
break;
case 'hideColumn':
command = { command: 'HideLineCommand', options: { axis: 'column' } };
break;
case 'unhideRow':
command = { command: 'UnHideLineCommand', options: { axis: 'row' } };
break;
case 'unhideColumn':
command = { command: 'UnHideLineCommand', options: { axis: 'column' } };
break;
case 'deleteRow':
command = { command: 'DeleteRowCommand' };
break;
case 'deleteColumn':
command = { command: 'DeleteColumnCommand' };
break;
case 'insertLink':
this.openLinkDialog();
break;
case 'addRowAbove':
command = { command: 'AddRowCommand', options: { value: 'above' } };
break;
case 'addRowBelow':
command = { command: 'AddRowCommand', options: { value: 'below' } };
break;
case 'addColumnLeft':
command = { command: 'AddColumnCommand', options: { value: 'left' } };
break;
case 'addColumnRight':
command = { command: 'AddColumnCommand', options: { value: 'right' } };
break;
}
if (command) {
this.spreadsheetWidget.executeCommand(command);
}
}
/**
* @hidden
*/
onKeyDown = (e) => {
const isCtrl = e.ctrlKey || e.metaKey;
const shift = e.shiftKey;
const altKey = e.altKey;
if (isCtrl && shift && e.keyCode === Keys.KeyS) {
this.spreadsheetService.onSheetsBarFocus.next();
}
if (altKey) {
const currentSheet = e.sender.activeSheet();
const validation = currentSheet.range(currentSheet.activeCell()).validation();
if (isPresent(validation) && validation.dataType === 'list') {
if (e.keyCode === Keys.ArrowDown) {
this.spreadsheetWidget.view.openCustomEditor();
}
}
}
};
/**
* @hidden
*/
messageFor(key) {
return this.localization.get(key);
}
/**
* @hidden
*/
onMenuItemSelect(e) {
const selectedMenuItem = this.menuItems.find(item => item.text === e.item.text);
const previousSelectedItem = this.menuItems.find(item => item.active);
if (selectedMenuItem !== previousSelectedItem) {
this._menuItems.forEach((item, idx) => {
item.active = idx === +e.index;
item.cssClass = idx === +e.index ? 'k-active' : null;
});
this.selectedMenuItem = this.menuItems.find(item => item.active);
}
}
onChange = (e) => {
hasObservers(this.change) && this.change.emit(e);
this.spreadsheetService.selectionChanged.next(e.range);
};
onSelectionChange = (e) => {
hasObservers(this.selectionChange) && this.selectionChange.emit(e);
this.spreadsheetService.selectionChanged.next(e.range);
};
onChangeFormat = (e) => {
hasObservers(this.formatChange) && this.formatChange.emit(e);
this.spreadsheetService.selectionChanged.next(e.range);
};
onExcelExport = (e) => hasObservers(this.excelExport) && this.excelExport.emit(e);
onExcelImport = (e) => hasObservers(this.excelImport) && this.excelImport.emit(e);
onActiveSheetChanged = (sheet) => {
const eventArgs = { sender: this.spreadsheetService.spreadsheet, sheet };
hasObservers(this.activeSheetChange) && this.activeSheetChange.emit(eventArgs);
const range = sheet.range(sheet.activeCell());
this.spreadsheetService.selectionChanged.next(range);
};
updateState = (e) => {
this.toolsService.updateTools(e);
if (e.reason?.sheetSelection) {
this.sheets = mapToSheetDescriptor(this.spreadsheetService.spreadsheet.sheets());
}
};
onMessage = (e) => {
this.ngZone.run(() => {
this.errorService.handleErrorMessage(e);
});
};
updateActiveSheet(name) {
this.ngZone.run(() => this.spreadsheetService.currentActiveSheet = name);
}
onSheetsChanged(e) {
this.ngZone.run(() => {
this.sheets = mapToSheetDescriptor(e.sheets);
this.updateActiveSheet(this.spreadsheetService.activeSheet);
});
}
get options() {
return {
activeSheet: this.activeSheet,
...{
sheets: this.sheetsInfo && structuredClone(this.sheetsInfo),
intl: {
localeInfo: () => localeData(this.intl.localeId),
parseDate: (value, fmt) => this.intl.parseDate(value, fmt),
toString: (value, fmt) => this.intl.toString(value, fmt),
format: (fmt, ...values) => this.intl.format(fmt, ...values)
}
},
columns: this.columns,
columnWidth: this.columnWidth,
defaultCellStyle: this.defaultCellStyle,
excel: this.excel,
headerHeight: this.headerHeight,
headerWidth: this.headerWidth,
images: this.images,
rowHeight: this.rowHeight,
rows: this.rows,
formulaBarInputRef: { current: this.formulaBarInputRef.current },
formulaCellInputRef: { current: this.formulaCellInputRef.current },
nameBoxRef: { current: this.nameBoxRef.current },
getIconHTMLString: (options) => {
const iconWrapper = this.container.createComponent(IconWrapperComponent);
iconWrapper.instance.name = options.iconName;
iconWrapper.instance.svgIcon = options.svgIcon;
iconWrapper.changeDetectorRef.detectChanges();
return iconWrapper.instance.element.nativeElement;
}
};
}
contextMenuItemsForTarget(target, unhide, unmerge) {
const commonItems = [{
text: this.messageFor('copy'),
icon: commandIcons.copy,
svgIcon: commandSVGIcons.copy,
id: 'copy'
}, {
text: this.messageFor('cut'),
icon: commandIcons.cut,
svgIcon: commandSVGIcons.cut,
id: 'cut'
}, {
text: this.messageFor('paste'),
icon: commandIcons.paste,
svgIcon: commandSVGIcons.paste,
id: 'paste',
disabled: true
}, {
separator: true
}, {
text: this.messageFor('mergeAll'),
icon: commandIcons.mergeAll,
svgIcon: commandSVGIcons.mergeAll,
id: 'mergeAll',
}, {
text: this.messageFor('mergeHorizontally'),
icon: commandIcons.mergeHorizontally,
svgIcon: commandSVGIcons.mergeHorizontally,
id: 'mergeHorizontally',
}, {
text: this.messageFor('mergeVertically'),
icon: commandIcons.mergeVertically,
svgIcon: commandSVGIcons.mergeVertically,
id: 'mergeVertically',
}, {
text: this.messageFor('unmerge'),
icon: commandIcons.unmerge,
svgIcon: commandSVGIcons.unmerge,
id: 'unmerge',
disabled: !unmerge
}, {
separator: true
}, {
text: this.messageFor('insertLink'),
icon: commandIcons.insertLink,
svgIcon: commandSVGIcons.insertLink,
id: 'insertLink'
}];
if (target === 'rowheader') {
commonItems.push({
separator: true
}, {
text: this.messageFor('addRowAbove'),
icon: commandIcons.addRowAbove,
svgIcon: commandSVGIcons.addRowAbove,
id: 'addRowAbove',
}, {
text: this.messageFor('addRowBelow'),
icon: commandIcons.addRowBelow,
svgIcon: commandSVGIcons.addRowBelow,
id: 'addRowBelow',
}, {
text: this.messageFor('deleteRow'),
icon: commandIcons.deleteRow,
svgIcon: commandSVGIcons.deleteRow,
id: 'deleteRow',
}, {
text: this.messageFor('hideRow'),
icon: commandIcons.hideRow,
svgIcon: commandSVGIcons.hideRow,
id: 'hideRow',
}, {
text: this.messageFor('unhideRow'),
icon: commandIcons.unhideRow,
svgIcon: commandSVGIcons.unhideRow,
id: 'unhideRow',
disabled: !unhide
});
}
if (target === 'columnheader') {
commonItems.push({
separator: true
}, {
text: this.messageFor('addColumnLeft'),
icon: commandIcons.addColumnLeft,
svgIcon: commandSVGIcons.addColumnLeft,
id: 'addColumnLeft',
}, {
text: this.messageFor('addColumnRight'),
icon: commandIcons.addColumnRight,
svgIcon: commandSVGIcons.addColumnRight,
id: 'addColumnRight',
}, {
text: this.messageFor('deleteColumn'),
icon: commandIcons.deleteColumn,
svgIcon: commandSVGIcons.deleteColumn,
id: 'deleteColumn',
}, {
text: this.messageFor('hideColumn'),
icon: commandIcons.hideColumn,
svgIcon: commandSVGIcons.hideColumn,
id: 'hideColumn',
}, {
text: this.messageFor('unhideColumn'),
icon: commandIcons.unhideColumn,
svgIcon: commandSVGIcons.unhideColumn,
id: 'unhideColumn',
disabled: !unhide
});
}
return commonItems;
}
openLinkDialog() {
const hasLink = isPresent(this.currentRange?.link());
const dialogSettings = {
appendTo: this.spreadsheetService.dialogContainer,
title: this.localization.get('insertLink'),
content: InsertLinkDialogComponent,
actions: [{
text: this.localization.get('dialogInsert'),
themeColor: 'primary'
}, {
text: this.localization.get('dialogCancel')
},
'spacer', {
text: this.localization.get('dialogRemoveLink'),
themeColor: 'primary',
fillMode: 'clear',
cssClass: hasLink ? '' : 'k-disabled'
}],
actionsLayout: 'start',
width: 400,
autoFocusedElement: '.k-textbox > .k-input-inner'
};
const dialog = this.dialogService.open(dialogSettings);
const dialogInstance = dialog.dialog.instance;
const dialogContent = dialog.content.instance;
if (hasLink) {
dialogContent.setData({ link: this.currentRange?.link() });
}
dialogInstance.action.pipe(take(1)).subscribe((event) => {
if (event.text === this.localization.get('dialogCancel')) {
return;
}
let link = null;
if (event.text === this.localization.get('dialogInsert')) {
link = dialogContent.urlLink || null;
}
this.spreadsheetService.spreadsheet.executeCommand({
command: 'HyperlinkCommand',
options: { link }
});
});
}
configureSheets = (spreadsheet) => {
if (!this.sheetsInfo) {
this.ngZone.run(() => {
const defaultSheetDescriptors = mapToSheetDescriptor([spreadsheet?.activeSheet()]);
this._sheetsInfo = [{
text: this.spreadsheetService.currentActiveSheet,
first: true, last: true,
state: 'visible',
...defaultSheetDescriptors,
sheetActions: getSheetActions(defaultSheetDescriptors).map(item => ({ ...item, text: this.messageFor(item.messageKey) }))
}];
});
}
};
registerEditors() {
registerEditor('_validation_list', () => {
return {
edit: (options) => {
this.popupRef?.close();
this.popupRef = null;
this.popupRef = this.popupService.open({
anchor: options.view.element.querySelector('.k-spreadsheet-editor-button'),
content: ListEditorComponent,
popupAlign: options.alignLeft ? { horizontal: 'right', vertical: 'top' } : { horizontal: 'left', vertical: 'top' },
anchorAlign: options.alignLeft ? { horizontal: 'right', vertical: 'bottom' } : { horizontal: 'left', vertical: 'bottom' },
popupClass: 'k-spreadsheet-list-popup'
});
const list = this.popupRef.content.instance;
list.itemSelect.subscribe((item) => {
this.popupRef.close();
this.popupRef = null;
const itemValue = item.value;
if (isPresent(itemValue)) {
options.callback(itemValue);
}
});
list.close.subscribe(() => {
this.popupRef.close();
this.popupRef = null;
});
const items = options.validation.from.value;
const data = [];
const add = (el) => { data.push({ value: el }); };
if (items instanceof Matrix) {
items.each(add, false);
}
else {
(items + "").split(/\s*,\s*/).forEach(add);
}
list.data = data;
},
icon: { iconName: 'caret-alt-down', svgIcon: caretAltDownIcon }
};
});
registerEditor('_validation_date', () => {
return {
edit: (options) => {
this.popupRef?.close();
this.popupRef = null;
this.popupRef = this.popupService.open({
anchor: options.view.element.querySelector('.k-spreadsheet-editor-button'),
content: CalendarComponent,
popupAlign: options.alignLeft ? { horizontal: 'right', vertical: 'top' } : { horizontal: 'left', vertical: 'top' },
anchorAlign: options.alignLeft ? { horizontal: 'right', vertical: 'bottom' } : { horizontal: 'left', vertical: 'bottom' }
});
const calendar = this.popupRef.content.instance;
calendar.valueChange.subscribe((value) => {
this.popupRef.close();
this.popupRef = null;
if (!options.range.format()) {
options.range.format('yyyy-mm-dd');
}
options.callback(dateToSerial(value));
});
const date = options.range.value();
calendar.value = this.createDate(date);
const sheet = options.range.sheet();
const currenValidation = options.validation;
if (currenValidation) {
let min = calendar.min;
let max = calendar.max;
const fromValidation = currenValidation.from;
const toValidation = currenValidation.to;
if (/^(?:greaterThan|between)/.test(currenValidation.comparerType)) {
if (this.isValidFormula(fromValidation)) {
min = this.createDate(fromValidation, sheet);
}
else {
min = this.createDate(fromValidation.value);
}
}
if (currenValidation.comparerType === 'between') {
if (this.isValidFormula(toValidation)) {
max = this.createDate(toValidation, sheet);
}
else {
max = this.createDate(currenValidation.to.value);
}
}
if (currenValidation.comparerType === 'lessThan' || currenValidation.comparerType === 'lessThanOrEqualTo') {
if (this.isValidFormula(fromValidation)) {
max = this.createDate(fromValidation, sheet);
}
else {
max = this.createDate(currenValidation.from.value);
}
}
calendar.disabledDates = (date) => {
let from;
let to;
if (fromValidation && this.isValidFormula(fromValidation)) {
from = sheet.range(fromValidation.value.row, fromValidation.value.col).value();
}
else {
from = fromValidation ? fromValidation.value | 0 : 0;
}
if (toValidation && this.isValidFormula(toValidation)) {
to = sheet.range(toValidation.value.row, toValidation.value.col).value();
}
else {
to = toValidation ? toValidation.value | 0 : 0;
}
date = dateToSerial(date) || 0;
return !validation.validationComparers[currenValidation.comparerType](date, from, to);
};
calendar.min = min;
calendar.max = max;
}
else {
calendar.disabledDates = null;
calendar.min = calendar.max = null;
}
},
icon: { iconName: 'calendar', svgIcon: calendarIcon }
};
});
}
isValidFormula(validation) {
const formula = calc.runtime.Formula;
return validation instanceof formula && rowAndColPresent(validation.value);
}
createDate(validation, sheet) {
return new Date(serialToDate(isPresent(sheet) ? sheet.range(validation.value.row, validation.value.col).value() : validation));
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SpreadsheetComponent, deps: [{ token: i0.NgZone }, { token: i1.IntlService }, { token: i0.ElementRef }, { token: i2.LocalizationService }, { token: i3.SpreadsheetService }, { token: i4.SpreadsheetToolsService }, { token: i5.ErrorHandlingService }, { token: i6.DialogService }, { token: i7.PopupService }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: SpreadsheetComponent, isStandalone: true, selector: "kendo-spreadsheet", inputs: { menuItems: "menuItems", overflow: "overflow", formulaListMaxHeight: "formulaListMaxHeight", activeSheet: "activeSheet", sheets: "sheets", columns: "columns", columnWidth: "columnWidth", defaultCellStyle: "defaultCellStyle", headerHeight: "headerHeight", headerWidth: "headerWidth", rowHeight: "rowHeight", rows: "rows", images: "images", excel: "excel" }, outputs: { change: "change", formatChange: "formatChange", selectionChange: "selectionChange", excelExport: "excelExport", excelImport: "excelImport", activeSheetChange: "activeSheetChange" }, host: { properties: { "class.k-spreadsheet": "this.hostClass", "attr.role": "this.role" } }, providers: [
SpreadsheetLocalizationService,
SpreadsheetService,
{
provide: LocalizationService,
useExisting: SpreadsheetLocalizationService
},
{
provide: L10N_PREFIX,
useValue: 'kendo.spreadsheet'
},
SpreadsheetToolsService,
PopupService,
ErrorHandlingService
], viewQueries: [{ propertyName: "formulaBarInputRef", first: true, predicate: ["formulaBar"], descendants: true, read: FormulaInputDirective }, { propertyName: "formulaCellInputRef", first: true, predicate: ["formulaCell"], descendants: true, read: FormulaInputDirective }, { propertyName: "nameBoxRef", first: true, predicate: ["nameBox"], descendants: true }, { propertyName: "dialogContainer", first: true, predicate: ["dialogContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "contextMenu", first: true, predicate: ["contextMenu"], descendants: true }], exportAs: ["kendo-spreadsheet"], usesOnChanges: true, ngImport: i0, template: `
<ng-container
kendoSpreadsheetLocalizedMessages
i18n-background="kendo.spreadsheet.background|The title of the tool that changes the text background color."
background="Background color"
i18n-color="kendo.spreadsheet.color|The title of the tool that changes the text font color."
color="Font color"
i18n-bold="kendo.spreadsheet.bold|The title of the Bold tool."
bold="Bold"
i18n-dataValidation="kendo.spreadsheet.dataValidation|The title of the Data Validation tool."
dataValidation="Data Validation"
i18n-validationCellRange="kendo.spreadsheet.validationCellRange|The text of the Cell Range label in the data validation dialog."
validationCellRange="Cell Range"
i18n-validationCriteria="kendo.spreadsheet.validationCriteria|The text of the Criteria dropdown list label in the data validation dialog."
validationCriteria="Criteria"
i18n-validationMinValue="kendo.spreadsheet.validationMinValue|The text of the Min value label in the data validation dialog."
validationMinValue="Min"
i18n-validationMaxValue="kendo.spreadsheet.validationMaxValue|The text of the Max value label in the data validation dialog."
validationMaxValue="Max"
i18n-validationStartValue="kendo.spreadsheet.validationStartValue|The text of the Start value label in the data validation dialog."
validationStartValue="Start"
i18n-validationEndValue="kendo.spreadsheet.validationEndValue|The text of the End value label in the data validation dialog."
validationEndValue="End"
i18n-validationValue="kendo.spreadsheet.validationValue|The text of the Value label in the data validation dialog."
validationValue="Text"
i18n-validationShowListButtonCheckbox="kendo.spreadsheet.validationShowListButtonCheckbox|The text for the Show list button checkbox label in the data validation dialog."
validationShowListButtonCheckbox="Display button to show list"
i18n-validationOnInvalidData="kendo.spreadsheet.validationOnInvalidData|The text for the On invalid data label in the data validation dialog."
validationOnInvalidData="On invalid data"
i18n-validationShowDateButtonCheckbox="kendo.spreadsheet.validationShowDateButtonCheckbox|The text for the Show date button checkbox label in the data validation dialog."
validationShowDateButtonCheckbox="Display button to show Calendar"
i18n-validationRejectInput="kendo.spreadsheet.validationRejectInput|The text for the Reject input radio button label in the data validation dialog."
validationRejectInput="Reject input"
i18n-validationShowWarning="kendo.spreadsheet.validationShowWarning|The text for the Show warning radio button label in the data validation dialog."
validationShowWarning="Show warning"
i18n-validationHintTitle="kendo.spreadsheet.validationHintTitle|The text for the Custom hint title input label in the data validation dialog."
validationHintTitle="Custom hint title"
i18n-validationHintMessage="kendo.spreadsheet.validationHintMessage|The text for the Custom hint input label in the data validation dialog."
validationHintMessage="Custom hint"
i18n-validationShowHint="kendo.spreadsheet.validationShowHint|The text for the Show hint radio button label in the data validation dialog."
validationShowHint="Show hint"
i18n-validationIgnoreBlankCheckbox="kendo.spreadsheet.validationIgnoreBlankCheckbox|The text for the Ignore blank checkbox label in the data validation dialog."
validationIgnoreBlankCheckbox="Ignore blank"
i18n-validationComparer="kendo.spreadsheet.validationComparer|The text of the Comparer dropdown list label in the data validation dialog."
validationComparer="Comparer"
i18n-anyValueValidationCriteria="kendo.spreadsheet.anyValueValidationCriteria|The text of the Any value validation criteria"
anyValueValidationCriteria="Any value"
i18n-numberValidationCriteria="kendo.spreadsheet.numberValidationCriteria|The text of the Number validation criteria"
numberValidationCriteria="Number"
i18n-textValidationCriteria="kendo.spreadsheet.textValidationCriteria|The text of the Text validation criteria"
textValidationCriteria="Text"
i18n-dateValidationCriteria="kendo.spreadsheet.dateValidationCriteria|The text of the Date validation criteria"
dateValidationCriteria="Date"
i18n-customFormulaValidationCriteria="kendo.spreadsheet.customFormulaValidationCriteria|The text of the Custom formula validation criteria"
customFormulaValidationCriteria="Custom Formula"
i18n-listValidationCriteria="kendo.spreadsheet.listValidationCriteria|The text of the List validation criteria"
listValidationCriteria="List"
i18n-greaterThanValidationComparer="kendo.spreadsheet.greaterThanValidationComparer|The text of the greater than validation comparer"
greaterThanValidationComparer="greater than"
i18n-lessThanValidationComparer="kendo.spreadsheet.lessThanValidationComparer|The text of the less than validation comparer"
lessThanValidationComparer="less than"
i18n-betweenValidationComparer="kendo.spreadsheet.betweenValidationComparer|The text of the between validation comparer"
betweenValidationComparer="between"
i18n-notBetweenValidationComparer="kendo.spreadsheet.notBetweenValidationComparer|The text of the not between validation comparer"
notBetweenValidationComparer="not between"
i18n-equalToValidationComparer="kendo.spreadsheet.equalToValidationComparer|The text of the equal to validation comparer"
equalToValidationComparer="equal to"
i18n-notEqualToValidationComparer="kendo.spreadsheet.notEqualToValidationComparer|The text of the not equal to validation comparer"
notEqualToValidationComparer="not equal to"
i18n-greaterThanOrEqualToValidationComparer="kendo.spreadsheet.greaterThanOrEqualToValidationComparer|The text of the greater than on equal to validation comparer"
greaterThanOrEqualToValidationComparer="greater than or equal to"
i18n-lessThanOrEqualToValidationComparer="kendo.spreadsheet.lessThanOrEqualToValidationComparer|The text of the less than on equal to validation comparer"
lessThanOrEqualToValidationComparer="less than or equal to"
i18n-italic="kendo.spreadsheet.italic|The title of the Italic tool."
italic="Italic"
i18n-underline="kendo.spreadsheet.underline|The title of the Underline tool."
underline="Underline"
i18n-loadFile="kendo.spreadsheet.loadFile|The title of the Load File tool."
loadFile="Open..."
i18n-saveFile="kendo.spreadsheet.saveFile|The title of the Save File tool."
saveFile="Export..."
i18n-format="kendo.spreadsheet.format|The text of the Format tool."
format="Custom format..."
i18n-fontFamily="kendo.spreadsheet.fontFamily|The text of the Font Family tool."
fontFamily="Font"
i18n-fontSize="kendo.spreadsheet.fontSize|The text of the Font Size tool."
fontSize="Font size"
i18n-home="kendo.spreadsheet.home|The text of the Home toolbar tab."
home="Home"
i18n-file="kendo.spreadsheet.file|The text of the File toolbar tab."
file="File"
i18n-insert="kendo.spreadsheet.insert|The text of the Insert toolbar tab."
insert="Insert"
i18n-formatTab="kendo.spreadsheet.formatTab|The text of the Format toolbar tab."
formatTab="Format"
i18n-dataTab="kendo.spreadsheet.dataTab|The text of the Data toolbar tab."
dataTab="Data"
i18n-view="kendo.spreadsheet.view|The text of the View toolbar tab."
view="View"
i18n-undo="kendo.spreadsheet.undo|The title of the Undo tool."
undo="Undo"
i18n-redo="kendo.spreadsheet.redo|The title of the Redo tool."
redo="Redo"
i18n-gridLines="kendo.spreadsheet.gridLines|The title of the Grid Lines tool."
gridLines="Toggle grid lines"
i18n-addColumnLeft="kendo.spreadsheet.addColumnLeft|The title of the tool that adds new column before currently selected column."
addColumnLeft="Add column left"
i18n-addColumnRight="kendo.spreadsheet.addColumnRight|The title of the tool that adds new column after currently selected column."
addColumnRight="Add column right"
i18n-addRowBelow="kendo.spreadsheet.addRowBelow|The title of the tool that adds new row below currently selected row."
addRowBelow="Add row below"
i18n-addRowAbove="kendo.spreadsheet.addRowAbove|The title of the tool that adds new row above currently selected row."
addRowAbove="Add row above"
i18n-deleteColumn="kendo.spreadsheet.deleteColumn|The title of the tool that deletes a column."
deleteColumn="Delete column"
i18n-deleteRow="kendo.spreadsheet.deleteRow|The title of the tool that deletes a row."
deleteRow="Delete row"
i18n-wrap="kendo.spreadsheet.wrap|The title of the Text Wrap tool."
wrap="Text wrap"
i18n-align="kendo.spreadsheet.align|The title of the Text Align tool."
align="Align"
i18n-alignHorizontal="kendo.spreadsheet.alignHorizontal|The title of the Text Align Horizontal tool."
alignHorizontal="Align horizontally"
i18n-alignVertical="kendo.spreadsheet.alignVertical|The title of the Text Align Vertical tool."
alignVertical="Align vertically"
i18n-alignLeft="kendo.spreadsheet.alignLeft|The title of the Text Align Left tool."