UNPKG

@progress/kendo-angular-spreadsheet

Version:

A Spreadsheet Component for Angular

977 lines (976 loc) 101 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { Component, ElementRef, 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."