@progress/kendo-angular-grid
Version:
Kendo UI Grid for Angular - high performance data grid with paging, filtering, virtualization, CRUD, and more.
1,428 lines (1,412 loc) • 1.73 MB
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 { EventEmitter, Injectable, SecurityContext, InjectionToken, Optional, Inject, Directive, SkipSelf, Input, isDevMode, QueryList, Component, ContentChildren, ContentChild, forwardRef, Host, Output, HostBinding, Pipe, TemplateRef, ChangeDetectionStrategy, ViewChildren, ViewChild, Self, NgZone, HostListener, ElementRef, ViewContainerRef, ViewEncapsulation, inject, Injector, NgModule } from '@angular/core';
import { merge, of, Subject, zip as zip$1, from, Subscription, interval, fromEvent, Observable, BehaviorSubject } from 'rxjs';
import * as i1$3 from '@progress/kendo-angular-common';
import { isDocumentAvailable, Keys, hasClasses as hasClasses$1, isPresent as isPresent$1, normalizeKeys, anyChanged, TemplateContextDirective, DraggableDirective, EventsOutsideAngularDirective, replaceMessagePlaceholder, isChanged as isChanged$1, KendoInput, guid, areObjectsEqual, PrefixTemplateDirective, closest as closest$1, hasObservers, ResizeSensorComponent, isFirefox, firefoxMaxHeight, closestInScope as closestInScope$1, isFocusable as isFocusable$1, getLicenseMessage, shouldShowValidationUI, WatermarkOverlayComponent, PreventableEvent as PreventableEvent$1, ResizeBatchService } from '@progress/kendo-angular-common';
import * as i1 from '@angular/platform-browser';
import * as i1$1 from '@progress/kendo-angular-icons';
import { IconWrapperComponent, IconsService, KENDO_ICONS } from '@progress/kendo-angular-icons';
import { plusIcon, cancelIcon, lockIcon, unlockIcon, caretAltDownIcon, caretAltRightIcon, caretAltLeftIcon, arrowLeftIcon, arrowRightIcon, sortDescSmallIcon, sortAscSmallIcon, filterClearIcon, filterIcon, searchIcon, checkIcon, arrowRotateCcwIcon, columnsIcon, pencilIcon, saveIcon, trashIcon, fileExcelIcon, filePdfIcon, sparklesIcon, chevronUpIcon, chevronDownIcon, chevronRightIcon, displayInlineFlexIcon, maxWidthIcon, stickIcon, unstickIcon, setColumnPositionIcon, slidersIcon, moreVerticalIcon, reorderIcon, minusIcon, insertMiddleIcon, xIcon, xCircleIcon, plusCircleIcon, chevronLeftIcon, undoIcon, redoIcon, arrowsSwapIcon, groupIcon, tableWizardIcon } from '@progress/kendo-svg-icons';
import { switchMap, take, map, filter, takeUntil, switchMapTo, delay, tap, throttleTime, debounceTime, distinctUntilChanged, skip, auditTime, bufferCount, flatMap } from 'rxjs/operators';
import * as i1$2 from '@progress/kendo-angular-l10n';
import { ComponentMessages, LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n';
import * as i53 from '@progress/kendo-angular-pager';
import { PagerContextService, PagerNavigationService, PagerTemplateDirective, KENDO_PAGER } from '@progress/kendo-angular-pager';
import { orderBy, isCompositeFilterDescriptor, filterBy, groupBy, process } from '@progress/kendo-data-query';
import { NgTemplateOutlet, NgClass, NgStyle, KeyValuePipe } from '@angular/common';
import { getter } from '@progress/kendo-common';
import * as i1$4 from '@progress/kendo-angular-intl';
import { parseDate } from '@progress/kendo-angular-intl';
import * as i2 from '@progress/kendo-angular-popup';
import { PopupService } from '@progress/kendo-angular-popup';
import * as i1$6 from '@progress/kendo-angular-buttons';
import { ChipListComponent, ChipComponent, ButtonComponent, Button, KENDO_BUTTON, ButtonDirective } from '@progress/kendo-angular-buttons';
import * as i1$5 from '@progress/kendo-angular-dropdowns';
import { DropDownListComponent, AutoCompleteComponent } from '@progress/kendo-angular-dropdowns';
import * as i2$2 from '@angular/forms';
import { NG_VALUE_ACCESSOR, FormsModule, ReactiveFormsModule, FormControl, FormGroup } from '@angular/forms';
import * as i2$1 from '@progress/kendo-angular-utils';
import { DragTargetContainerDirective, DropTargetContainerDirective } from '@progress/kendo-angular-utils';
import * as i4 from '@progress/kendo-angular-inputs';
import { TextBoxComponent, NumericTextBoxComponent, NumericTextBoxCustomMessagesComponent, RadioButtonComponent, CheckBoxComponent, TextBoxPrefixTemplateDirective, KENDO_FORMFIELD, KENDO_TEXTBOX, KENDO_NUMERICTEXTBOX, KENDO_CHECKBOX } from '@progress/kendo-angular-inputs';
import * as i5 from '@progress/kendo-angular-dateinputs';
import { DatePickerComponent, DatePickerCustomMessagesComponent, KENDO_DATEPICKER, CalendarDOMService, CenturyViewService, DecadeViewService, MonthViewService, YearViewService, NavigationService as NavigationService$1 } from '@progress/kendo-angular-dateinputs';
import * as i54 from '@progress/kendo-angular-toolbar';
import { ToolBarToolComponent, KENDO_TOOLBAR } from '@progress/kendo-angular-toolbar';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { TabStripComponent, TabStripTabComponent, TabTitleDirective, TabContentDirective } from '@progress/kendo-angular-layout';
import { saveAs } from '@progress/kendo-file-saver';
import * as i5$1 from '@progress/kendo-angular-excel-export';
import { workbookOptions, toDataURL, ColumnBase as ColumnBase$1, ColumnComponent as ColumnComponent$1, ColumnGroupComponent as ColumnGroupComponent$1, FooterTemplateDirective as FooterTemplateDirective$1, GroupFooterTemplateDirective as GroupFooterTemplateDirective$1, GroupHeaderColumnTemplateDirective as GroupHeaderColumnTemplateDirective$1, GroupHeaderTemplateDirective as GroupHeaderTemplateDirective$1 } from '@progress/kendo-angular-excel-export';
import { PDFExportMarginComponent, PDFExportTemplateDirective, PDFExportComponent } from '@progress/kendo-angular-pdf-export';
import { validatePackage } from '@progress/kendo-licensing';
import { ActionSheetComponent, ActionSheetViewComponent, ActionSheetHeaderTemplateDirective, ActionSheetContentTemplateDirective, ActionSheetFooterTemplateDirective } from '@progress/kendo-angular-navigation';
import * as i3 from '@progress/kendo-angular-label';
import { KENDO_LABELS, LabelDirective } from '@progress/kendo-angular-label';
import * as i1$7 from '@progress/kendo-angular-dialog';
import { DialogContentBase, DialogActionsComponent, DialogService, DialogContainerService, WindowService, WindowContainerService } from '@progress/kendo-angular-dialog';
import { AIPromptComponent, AIPromptCustomMessagesComponent, PromptViewComponent, OutputViewComponent, AIPromptOutputTemplateDirective, AIPromptOutputBodyTemplateDirective } from '@progress/kendo-angular-conversational-ui';
import * as i1$8 from '@angular/common/http';
import { HttpHeaders, HttpRequest } from '@angular/common/http';
/* eslint-disable no-bitwise */
/**
* @hidden
*/
const append = (element) => {
if (!isDocumentAvailable()) {
return;
}
let appended = false;
return () => {
if (!appended) {
document.body.appendChild(element);
appended = true;
}
return element;
};
};
/**
* @hidden
*/
const getDocument$1 = element => element.ownerDocument.documentElement;
/**
* @hidden
*/
const getWindow$1 = element => element.ownerDocument.defaultView;
/**
* @hidden
*/
const offset = element => {
const { clientTop, clientLeft } = getDocument$1(element);
const { pageYOffset, pageXOffset } = getWindow$1(element);
const { top, left } = element.getBoundingClientRect();
return {
top: top + pageYOffset - clientTop,
left: left + pageXOffset - clientLeft
};
};
/**
* @hidden
* If the target is before the draggable element, returns `true`.
*
* DOCUMENT_POSITION_FOLLOWING = 4
*/
const isTargetBefore = (draggable, target) => (target.compareDocumentPosition(draggable) & 4) !== 0;
/**
* @hidden
* If the container and the element are the same
* or if the container holds (contains) the element, returns `true`.
*
* DOCUMENT_POSITION_CONTAINED_BY = 16
*/
const contains$2 = (element, container) => element === container ||
(container.compareDocumentPosition(element) & 16) !== 0;
/**
* @hidden
*/
const position = (target, before) => {
const targetRect = offset(target);
const { offsetWidth, offsetHeight } = target;
const left = targetRect.left + (before ? 0 : offsetWidth);
const top = targetRect.top;
const height = offsetHeight;
return { left, top, height };
};
/**
* @hidden
*/
class DragAndDropService {
changes = new EventEmitter();
register = [];
lastTarget = null;
add(target) {
this.register.push(target);
}
remove(target) {
this.register = this.register.filter(current => current !== target);
}
notifyDrag(draggable, element, mouseEvent) {
const target = this.targetFor(element);
if (this.lastTarget === target) {
return;
}
this.changes.next({
draggable,
mouseEvent,
target: this.lastTarget,
type: 'leave'
});
if (target) {
this.changes.next({
draggable,
mouseEvent,
target,
type: 'enter'
});
}
this.lastTarget = target;
}
notifyDrop(draggable, mouseEvent) {
const target = draggable && draggable.element && this.targetFor(draggable.element.nativeElement);
if (target && this.lastTarget === target) {
this.lastTarget = null;
return;
}
this.changes.next({
draggable,
mouseEvent,
target: this.lastTarget,
type: 'drop'
});
this.lastTarget = null;
}
targetFor(element) {
const comparer = contains$2.bind(null, element);
return this.register.find(({ element: { nativeElement } }) => comparer(nativeElement));
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DragAndDropService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DragAndDropService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DragAndDropService, decorators: [{
type: Injectable
}] });
const updateClass = (element, valid, svg) => {
const icon = element.querySelector('.k-icon');
if (svg) {
const svg = icon.firstElementChild;
svg.removeChild(svg.firstElementChild);
const path = valid ? plusIcon.content : cancelIcon.content;
icon.firstElementChild.innerHTML = path + icon.firstElementChild.innerHTML;
}
icon.setAttribute('class', icon.getAttribute('class').replace(/(plus|cancel)/, valid ? 'plus' : 'cancel'));
};
const updateLock = (element, locked = null, svg) => {
const icon = element.querySelectorAll('.k-icon')[1];
const value = locked === null ? '' : (locked ? `k${svg ? '-svg' : ''}-i-lock` : `k${svg ? '-svg' : ''}-i-unlock`);
if (svg) {
icon.setAttribute('class', icon.getAttribute('class').replace(/(k-svg-i-unlock|k-svg-i-lock)/, '').trim() + ` ${value}`);
icon.firstElementChild.innerHTML = locked ? lockIcon.content : unlockIcon.content;
}
else {
icon.setAttribute('class', icon.getAttribute('class').replace(/(k-i-unlock|k-i-lock)/, '').trim() + ` ${value}`);
}
};
const decorate = (element) => {
element.className = 'k-header k-drag-clue';
element.style.position = 'absolute';
element.style.zIndex = '20000';
};
const svgIconsMarkup = (viewBox, content, safeTitle) => `
<span class="k-icon k-svg-icon k-drag-status k-svg-i-cancel">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="${viewBox}"
aria-hidden="true">
${content}
</svg>
<span class="k-icon k-svg-icon k-icon-modifier">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="${viewBox}"
aria-hidden="true">
</svg>
</span>
</span>
${safeTitle}`;
const fontIconsMarkup = (safeTitle) => `
<span class="k-icon k-font-icon k-drag-status k-i-cancel">
<span class="k-icon k-font-icon k-icon-modifier"></span>
</span>
${safeTitle}`;
/**
* @hidden
*/
class DragHintService {
sanitizer;
iconsService;
dom;
cancelIcon = cancelIcon;
constructor(sanitizer, iconsService) {
this.sanitizer = sanitizer;
this.iconsService = iconsService;
}
ngOnDestroy() {
this.remove();
}
create(title) {
if (!isDocumentAvailable()) {
return;
}
this.dom = document.createElement("div");
decorate(this.dom);
const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, title);
const safeTitle = sanitized?.replace(/<[^>]*>/g, '');
const innerHtml = this.isSVG ?
svgIconsMarkup(this.cancelIcon.viewBox, this.cancelIcon.content, safeTitle) :
fontIconsMarkup(safeTitle);
this.dom.innerHTML = innerHtml;
}
attach() {
return append(this.dom);
}
remove() {
if (this.dom && this.dom.parentNode) {
(function (el) {
setTimeout(() => {
if (isDocumentAvailable()) {
document.body.removeChild(el);
}
});
})(this.dom); // hack for IE + pointer events!
this.dom = null;
}
}
show() {
this.dom.style.display = "";
}
hide() {
this.dom.style.display = "none";
}
enable() {
updateClass(this.dom, true, this.isSVG);
}
disable() {
updateClass(this.dom, false, this.isSVG);
}
removeLock() {
updateLock(this.dom, false, this.isSVG);
}
toggleLock(locked) {
updateLock(this.dom, locked, this.isSVG);
}
move(move) {
this.dom.style.top = move.pageY + 'px';
this.dom.style.left = move.pageX + 'px';
}
get isSVG() {
return (this.iconsService.iconSettings?.type || this.iconsService.changes.value.type) === 'svg';
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DragHintService, deps: [{ token: i1.DomSanitizer }, { token: i1$1.IconsService }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DragHintService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DragHintService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1.DomSanitizer }, { type: i1$1.IconsService }] });
/**
* @hidden
*/
class DropCueService {
dom;
create() {
if (!isDocumentAvailable()) {
return;
}
this.dom = document.createElement("div");
this.dom.className = 'k-grouping-dropclue';
this.hide();
}
attach() {
return append(this.dom);
}
remove() {
if (this.dom && this.dom.parentElement) {
document.body.removeChild(this.dom);
this.dom = null;
}
}
hide() {
this.dom.style.display = "none";
}
position({ left, top, height }) {
this.dom.style.display = 'block';
this.dom.style.height = height + 'px';
this.dom.style.top = top + 'px';
const width = this.dom.offsetWidth / 2;
this.dom.style.left = left - width + 'px';
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DropCueService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DropCueService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DropCueService, decorators: [{
type: Injectable
}] });
const EMPTY_REGEX = /^\s*$/;
/**
* @hidden
*/
const isPresent = (value) => value !== null && value !== undefined;
/**
* @hidden
*/
const isBlank = (value) => value === null || value === undefined;
/**
* @hidden
*/
const isArray = (value) => Array.isArray(value);
/**
* @hidden
*/
const isTruthy = (value) => !!value;
/**
* @hidden
*/
const isNullOrEmptyString = (value) => isBlank(value) || EMPTY_REGEX.test(value);
/**
* @hidden
*/
const observe = (list) => merge(of(list), list.changes);
/**
* @hidden
*/
const isUniversal = () => typeof document === 'undefined';
/**
* @hidden
*/
const isString = (value) => typeof value === 'string';
/**
* @hidden
*/
const isNumber = (value) => typeof value === "number" && !isNaN(value);
/**
* @hidden
*/
const extractFormat = (format) => {
if (isString(format) && !isNullOrEmptyString(format) && format.startsWith('{0:')) {
return format.slice(3, format.length - 1);
}
return format;
};
/**
* @hidden
*/
const not = (fn) => (...args) => !fn(...args);
/**
* @hidden
*/
const or = (...conditions) => (value) => conditions.reduce((acc, x) => acc || x(value), false);
/**
* @hidden
*/
const and = (...conditions) => (value) => conditions.reduce((acc, x) => acc && x(value), true);
/**
* @hidden
*/
const Skip = new InjectionToken("Skip");
/**
* @hidden
*/
const createPromise = () => {
let resolveFn, rejectFn;
const promise = new Promise((resolve, reject) => {
resolveFn = (data) => {
resolve(data);
return promise;
};
rejectFn = (data) => {
reject(data);
return promise;
};
});
promise.resolve = resolveFn;
promise.reject = rejectFn;
return promise;
};
/** @hidden */
const iterator = getIterator$1();
// TODO: Move to kendo-common
function getIterator$1() {
if (typeof Symbol === 'function' && Symbol.iterator) {
return Symbol.iterator;
}
const keys = Object.getOwnPropertyNames(Map.prototype);
const proto = Map.prototype;
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
if (key !== 'entries' && key !== 'size' && proto[key] === proto.entries) {
return key;
}
}
}
const FRAME_DURATION = 1000 / 60;
const wnd = typeof window !== 'undefined' ? window : {};
/** @hidden */
const requestAnimationFrame = wnd.requestAnimationFrame || wnd.msRequestAnimationFrame || (callback => setTimeout(callback, FRAME_DURATION));
/** @hidden */
const cancelAnimationFrame = wnd.cancelAnimationFrame || wnd.msCancelRequestAnimationFrame || clearTimeout;
/**
* @hidden
*/
const nodesToArray = (nodes) => [].slice.call(nodes);
/**
* @hidden
*/
const recursiveFlatMap = (item) => isGroupResult(item) ? item.items.flatMap(recursiveFlatMap) : [{ ...item }];
const mapColumnItemState = (c) => ({ id: c.id, width: c.width, hidden: c.hidden, locked: c.locked, sticky: c.sticky, orderIndex: c.orderIndex });
/**
* @hidden
*/
const updateColumnFromState = (columnState, c) => {
Object.keys(columnState).forEach((key) => c[key] = columnState[key]);
};
/**
* @hidden
*/
const recursiveColumnsFlatMap = (item) => (item.isColumnGroup || item.isSpanColumn) ?
[mapColumnItemState(item), ...item.children.toArray().flatMap(recursiveColumnsFlatMap)] :
[mapColumnItemState(item)];
/**
* @hidden
*/
const isGroupResult = (obj) => {
return 'aggregates' in obj && 'items' in obj && 'field' in obj && 'value' in obj;
};
/**
* @hidden
*/
const roundDown = (value) => Math.floor(value * 100) / 100;
/**
* @hidden
*/
const defaultCellRowSpan = (row, column, data) => {
const field = column.field;
let rowspan = 1;
const rowIndex = row.index;
if (!data[0].items) {
//If no groups are present.
rowspan = findRowSpan(data, rowIndex, field);
}
else {
// If groups are present.
const group = row.dataItem.group;
if (field === group.data.field) {
// Set the rowspan to the number of data items in the same group if the column containing the current row matches the grouping field.
rowspan = group.data.items.length;
}
else {
//The index of the current row in the group.
const rowIndex = row.dataItem.group.data.items.indexOf(row.dataItem.data);
const groupItems = row.dataItem.group.data.items;
rowspan = findRowSpan(groupItems, rowIndex, field);
}
}
return rowspan;
};
/**
* @hidden
* Returns the rowspan number based on the provided data array, index of the item, and field that is checked.
*/
const findRowSpan = (data, index, field) => {
let rowspan = 1;
if (typeof data[index][field]?.getTime === 'function') {
while (data[index][field].getTime() === data[index + 1]?.[field].getTime()) {
rowspan++;
index++;
}
}
else {
while (data[index][field] === data[index + 1]?.[field]) {
rowspan++;
index++;
}
}
return rowspan;
};
/**
* @hidden
* Determines whether selection of multiple ranges is enabled in the selectable settings.
*/
const isMultipleRangesEnabled = (selectableSettings) => {
return selectableSettings &&
typeof selectableSettings === 'object' &&
selectableSettings.selectable.multipleRanges;
};
/**
* @hidden
* Calculates the height of a table row by inserting a temporary row into the table body when the `rowHeight` option is not set.
*/
const calcRowHeight = (tableBody) => {
let result = 0;
if (!isDocumentAvailable()) {
return result;
}
if (tableBody) {
const row = tableBody.insertRow(0);
const cell = row.insertCell(0);
cell.textContent = ' ';
result = row.getBoundingClientRect().height;
tableBody.deleteRow(0);
}
return result;
};
/**
* @hidden
*/
const FOCUS_ROOT_ACTIVE = new InjectionToken('focus-root-initial-active-state');
/**
* @hidden
*/
class FocusRoot {
active;
groups = new Set();
constructor(active = false) {
this.active = active;
}
registerGroup(group) {
if (this.active) {
this.groups.add(group);
}
}
unregisterGroup(group) {
if (this.active) {
this.groups.delete(group);
}
}
activate() {
if (this.active) {
this.groups.forEach(f => f.activate());
}
}
deactivate() {
if (this.active) {
this.groups.forEach(f => f.deactivate());
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FocusRoot, deps: [{ token: FOCUS_ROOT_ACTIVE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FocusRoot });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FocusRoot, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [FOCUS_ROOT_ACTIVE]
}] }] });
const focusableRegex = /^(?:a|input|select|option|textarea|button|object)$/i;
const NODE_NAME_PREDICATES = {};
const toClassList = (classNames) => String(classNames).trim().split(' ');
/**
* @hidden
*/
const hasClasses = (element, classNames) => {
const namesList = toClassList(classNames);
return Boolean(toClassList(element.className).find((className) => namesList.indexOf(className) >= 0));
};
/**
* @hidden
*/
const matchesClasses = (classNames) => (element) => hasClasses(element, classNames);
/**
* @hidden
*/
const matchesNodeName = (nodeName) => {
if (!NODE_NAME_PREDICATES[nodeName]) {
NODE_NAME_PREDICATES[nodeName] = (element) => String(element.nodeName).toLowerCase() === nodeName.toLowerCase();
}
return NODE_NAME_PREDICATES[nodeName];
};
/**
* @hidden
*/
const closest = (node, predicate) => {
while (node && !predicate(node)) {
node = node.parentNode;
}
return node;
};
/**
* @hidden
*/
const closestInScope = (node, predicate, scope) => {
while (node && node !== scope && !predicate(node)) {
node = node.parentNode;
}
if (node !== scope) {
return node;
}
};
/**
* @hidden
*/
const contains$1 = (parent, node, matchSelf = false) => {
const outside = !closest(node, (child) => child === parent);
if (outside) {
return false;
}
const el = closest(node, (child) => child === node);
return el && (matchSelf || el !== parent);
};
/**
* @hidden
*/
const isVisible = (element) => {
if (!isDocumentAvailable()) {
return;
}
const rect = element.getBoundingClientRect();
const hasSize = rect.width > 0 && rect.height > 0;
const hasPosition = rect.x !== 0 && rect.y !== 0;
// Elements can have zero size due to styling, but they will still count as visible.
// For example, the selection checkbox has no size, but is made visible through styling.
return (hasSize || hasPosition) && window.getComputedStyle(element).visibility !== 'hidden';
};
/**
* @hidden
*/
const isFocusable = (element) => {
if (!element.tagName) {
return false;
}
const tagName = element.tagName.toLowerCase();
const hasTabIndex = Boolean(element.getAttribute('tabIndex'));
const focusable = !element.disabled && focusableRegex.test(tagName);
return focusable || hasTabIndex;
};
/**
* @hidden
*/
const isFocusableWithTabKey = (element, checkVisibility = true) => {
if (!isFocusable(element)) {
return false;
}
const visible = !checkVisibility || isVisible(element);
const ariaHidden = element.getAttribute('aria-hidden') === 'true';
const tabIndex = element.getAttribute('tabIndex');
return visible && !ariaHidden && tabIndex !== '-1';
};
/**
* @hidden
*/
const findElement = (node, predicate, matchSelf = true) => {
if (!node) {
return;
}
if (matchSelf && predicate(node)) {
return node;
}
node = node.firstChild;
while (node) {
if (node.nodeType === 1) {
const element = findElement(node, predicate);
if (element) {
return element;
}
}
node = node.nextSibling;
}
};
/**
* @hidden
*/
const findLastElement = (node, predicate, matchSelf = true) => {
let last = null;
findElement(node, (node) => {
if (predicate(node)) {
last = node;
}
return false;
}, matchSelf);
return last;
};
/**
* @hidden
*/
const findFocusable = (element, checkVisibility = true) => {
return findElement(element, (node) => isFocusableWithTabKey(node, checkVisibility));
};
/**
* @hidden
*/
const findFocusableChild = (element, checkVisibility = true) => {
return findElement(element, (node) => isFocusableWithTabKey(node, checkVisibility), false);
};
/**
* @hidden
*/
const findLastFocusableChild = (element, checkVisibility = true) => {
return findLastElement(element, (node) => isFocusableWithTabKey(node, checkVisibility), false);
};
/**
* @hidden
*/
function rtlScrollPosition(position, element, initial) {
let result = position;
if (initial < 0) {
result = -position;
}
else if (initial > 0) {
result = element.scrollWidth - element.offsetWidth - position;
}
return result;
}
const isButton = matchesNodeName('button');
const isInputTag = matchesNodeName('input');
const isKendoInputTag = matchesNodeName('kendo-checkbox') || matchesNodeName('kendo-textbox');
const navigableRegex = /(button|checkbox|color|file|radio|reset|submit)/i;
const isNavigableInput = element => isInputTag(element) && navigableRegex.test(element.type);
const isNavigable = element => !element.disabled && (isButton(element) || isNavigableInput(element) || isKendoInputTag(element));
/**
* @hidden
*/
class DefaultFocusableElement {
renderer;
get enabled() {
return this.focusable && !this.focusable.disabled;
}
get visible() {
return this.focusable && isVisible(this.focusable);
}
element;
focusable;
constructor(host, renderer) {
this.renderer = renderer;
this.element = host.nativeElement;
this.focusable = findFocusable(this.element, false) || this.element;
}
isNavigable() {
return this.canFocus() && isNavigable(this.element);
}
toggle(active) {
this.renderer.setAttribute(this.focusable, 'tabIndex', active ? '0' : '-1');
}
focus() {
if (this.focusable) {
this.focusable.focus();
}
}
canFocus() {
return this.visible && this.enabled;
}
hasFocus() {
return isDocumentAvailable() && document.activeElement !== this.element && closest(document.activeElement, e => e === this.element);
}
}
/**
* @hidden
*/
const CELL_CONTEXT = new InjectionToken('grid-cell-context');
/**
* @hidden
*/
const EMPTY_CELL_CONTEXT = {};
/**
* @hidden
*/
class GridToolbarNavigationService {
renderer;
toolbarElement;
navigableElements = [];
currentActiveIndex = 0;
defaultFocusableSelector = `
[kendogridtoolbarfocusable],
[kendogridaddcommand],
[kendogridcancelcommand],
[kendogrideditcommand],
[kendogridremovecommand],
[kendogridsavecommand],
[kendogridexcelcommand],
[kendogridpdfcommand]
`;
constructor(renderer) {
this.renderer = renderer;
}
notify() {
// ensure focusable elements are in the same order as in the DOM
this.navigableElements = this.findNavigableElements();
this.currentActiveIndex = 0;
this.updateFocus();
}
findNavigableElements() {
return Array.from(this.toolbarElement.querySelectorAll(this.defaultFocusableSelector) || []);
}
focus() {
this.navigableElements[this.currentActiveIndex]?.focus();
}
updateFocus() {
if (!this.navigableElements.length) {
return;
}
this.navigableElements.forEach(el => {
this.renderer.setAttribute(el, 'tabindex', '-1');
});
this.renderer.setAttribute(this.navigableElements[this.currentActiveIndex], 'tabindex', '0');
if (isDocumentAvailable() && document.activeElement.closest('.k-toolbar')) {
this.navigableElements[this.currentActiveIndex].focus();
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GridToolbarNavigationService, deps: [{ token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GridToolbarNavigationService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GridToolbarNavigationService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i0.Renderer2 }] });
/**
* @hidden
*
* The Context service is used to provide common
* services and DI tokens for a Grid instance.
*
* This keeps the constructor parameters stable
* and a avoids dependency cycles between components.
*/
class ContextService {
renderer;
localization;
grid;
topToolbarNavigation;
bottomToolbarNavigation;
navigable;
scroller;
dataBindingDirective;
highlightDirective;
excelComponent;
pdfComponent;
constructor(renderer, localization) {
this.renderer = renderer;
this.localization = localization;
this.topToolbarNavigation = new GridToolbarNavigationService(this.renderer);
this.bottomToolbarNavigation = new GridToolbarNavigationService(this.renderer);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContextService, deps: [{ token: i0.Renderer2 }, { token: i1$2.LocalizationService }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContextService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContextService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i1$2.LocalizationService }] });
/**
* A directive that controls how focusable elements receive
* focus in a navigable Grid. [See example]({% slug keyboard_navigation_grid %}).
*
* @example
* ```html
* <kendo-grid [data]="data" [navigable]="true">
* <kendo-grid-column>
* <ng-template kendoGridCellTemplate let-dataItem>
* <!-- The first focusable element is focused when you press Enter on the cell. -->
* <input type="text" kendoGridFocusable [value]="dataItem.ProductName" style="margin-right: 8px;" />
* <button kendoGridFocusable>Update</button>
* </ng-template>
* </kendo-grid-column>
* </kendo-grid>
* ```
* @remarks
* Applied to: {@link ButtonComponent}, {@link TextBoxComponent}, {@link NumericTextBoxComponent}, {@link DateInputComponent}, {@link DatePickerComponent}, {@link DateTimePicker}, {@link TextAreaComponent}, {@link ColorPickerComponent}, {@link DropDownListComponent}, {@link ComboBoxComponent}, {@link AutoCompleteComponent}.
*/
class FocusableDirective {
cellContext;
hostElement;
renderer;
ctx;
active = true;
group;
element;
_enabled = true;
/**
* @hidden
*/
set enabled(value) {
if (value === '') {
value = true;
}
else {
value = Boolean(value);
}
if (value !== this.enabled) {
this._enabled = value;
if (this.element) {
this.element.toggle(this.active && value);
}
}
}
get enabled() {
return this._enabled;
}
constructor(cellContext, hostElement, renderer, ctx) {
this.cellContext = cellContext;
this.hostElement = hostElement;
this.renderer = renderer;
this.ctx = ctx;
if (this.cellContext) {
this.group = this.cellContext.focusGroup;
}
if (this.group) {
this.group.registerElement(this);
}
}
ngAfterViewInit() {
if (!this.element && this.ctx.navigable) {
this.element = new DefaultFocusableElement(this.hostElement, this.renderer);
}
if (this.group && this.element) {
this.toggle(this.group.isActive);
}
}
ngOnDestroy() {
if (this.group) {
this.group.unregisterElement(this);
}
}
/**
* @hidden
*/
toggle(active) {
if (this.element && active !== this.active) {
this.element.toggle(this.enabled && active);
this.active = active;
}
}
/**
* @hidden
*/
canFocus() {
return this.enabled && this.element && this.element.canFocus();
}
/**
* @hidden
*/
isNavigable() {
return this.enabled && this.element && this.element.isNavigable();
}
/**
* @hidden
*/
focus() {
if (this.enabled && this.element) {
this.element.focus();
}
}
/**
* @hidden
*/
hasFocus() {
return this.enabled && this.element && this.element.hasFocus();
}
/**
* @hidden
*/
registerElement(element) {
this.element = element;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FocusableDirective, deps: [{ token: CELL_CONTEXT, optional: true, skipSelf: true }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: ContextService }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: FocusableDirective, isStandalone: true, selector: "[kendoGridFocusable],\n [kendoGridEditCommand],\n [kendoGridRemoveCommand],\n [kendoGridSaveCommand],\n [kendoGridCancelCommand],\n [kendoGridSelectionCheckbox]\n ", inputs: { enabled: ["kendoGridFocusable", "enabled"] }, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FocusableDirective, decorators: [{
type: Directive,
args: [{
selector: '[kendoGridFocusable]' + `,
[kendoGridEditCommand],
[kendoGridRemoveCommand],
[kendoGridSaveCommand],
[kendoGridCancelCommand],
[kendoGridSelectionCheckbox]
`,
standalone: true
}]
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [CELL_CONTEXT]
}, {
type: SkipSelf
}] }, { type: i0.ElementRef }, { type: i0.Renderer2 }, { type: ContextService }], propDecorators: { enabled: [{
type: Input,
args: ['kendoGridFocusable']
}] } });
/**
* @hidden
*/
class GridFocusableElement {
navigationService;
constructor(navigationService) {
this.navigationService = navigationService;
}
focus() {
this.navigationService.focusCell();
}
toggle(active) {
this.navigationService.toggle(active);
}
canFocus() {
return true;
}
hasFocus() {
return this.navigationService.hasFocus();
}
isNavigable() {
return false;
}
}
/**
* @hidden
*/
class NavigationCursor {
model;
changes = new Subject();
set metadata(value) {
this._metadata = value;
if (isPresent(value)) {
const newActiveCol = value.hasDetailTemplate && !this.metadata.isStacked ? 1 : 0;
const shouldChange = this.activeRow < value.headerRows && this.activeCol === 0;
if (shouldChange && newActiveCol !== this.activeCol) {
this.activeCol = newActiveCol;
this.reset();
}
}
}
get metadata() {
return this._metadata;
}
activeRow = 0;
activeCol = 0;
virtualCol = 0;
virtualRow = 0;
_metadata;
get row() {
return this.model.findRow(this.activeRow);
}
get cell() {
const row = this.row;
if (row) {
return this.model.findCell(this.activeCol, row);
}
}
get dataRowIndex() {
const row = this.row;
if (row) {
return row.dataRowIndex;
}
return -1;
}
constructor(model) {
this.model = model;
}
/**
* Assumes and announces a new cursor position.
*/
reset(rowIndex = this.activeRow, colIndex = this.activeCol, force = true) {
if (this.activate(rowIndex, colIndex, force)) {
this.virtualRow = rowIndex;
this.virtualCol = colIndex;
}
}
activate(rowIndex, colIndex, force) {
if (!force && this.isActiveRange(rowIndex, colIndex)) {
return false;
}
const prevColIndex = this.activeCol;
const prevRowIndex = this.activeRow;
this.activeCol = colIndex;
this.activeRow = rowIndex;
this.changes.next({
colIndex,
prevColIndex,
prevRowIndex,
rowIndex
});
return true;
}
isActiveRange(rowIndex, colIndex) {
if (this.activeRow !== rowIndex) {
return false;
}
const cell = this.cell;
const { start, end } = this.model.cellRange(cell);
return !cell || (start <= colIndex && colIndex <= end);
}
/**
* Assumes a new cursor position without announcing it.
*/
assume(rowIndex = this.activeRow, colIndex = this.activeCol) {
this.virtualRow = rowIndex;
this.virtualCol = colIndex;
this.activeCol = colIndex;
this.activeRow = rowIndex;
}
/**
* Announces a current cursor position to subscribers.
*/
announce() {
this.changes.next({
colIndex: this.activeCol,
prevColIndex: this.activeCol,
prevRowIndex: this.activeRow,
rowIndex: this.activeRow
});
}
activateVirtualCell(cell) {
const rowRange = this.model.rowRange(cell);
const cellRange = this.model.cellRange(cell);
const activeCol = this.activeCol;
const activeRow = this.activeRow;
if (rowRange.start <= activeRow && activeRow <= rowRange.end &&
cellRange.start <= activeCol && activeCol <= cellRange.end) {
this.activeRow = cell.rowIndex;
this.activeCol = cell.colIndex;
return true;
}
}
isActive(rowIndex, colIndex) {
return this.activeCol === colIndex && this.activeRow === rowIndex;
}
moveUp(offset = 1) {
return this.offsetRow(-offset);
}
moveDown(offset = 1) {
return this.offsetRow(offset);
}
moveLeft(offset = 1) {
return this.offsetCol(-offset);
}
moveRight(offset = 1) {
return this.offsetCol(offset);
}
lastCellIndex(row) {
return this.metadata.columns.leafColumnsToRender.length - 1 +
(this.metadata.hasDetailTemplate && (!row || !row.groupItem) ? 1 : 0);
}
offsetCol(offset) {
if (this.metadata.isStacked) {
return false;
}
const prevRow = this.model.findRow(this.virtualRow);
const lastIndex = this.lastCellIndex(prevRow);
const virtualCol = this.virtualCol;
this.virtualCol = Math.max(0, Math.min(virtualCol + offset, lastIndex));
let nextColIndex = this.virtualCol;
const nextRowIndex = this.virtualRow;
let cell = this.model.findCell(this.virtualCol, prevRow);
if (!cell && this.metadata.virtualColumns) {
return this.activate(nextRowIndex, nextColIndex);
}
if (!cell && this.metadata.hasDetailTemplate) {
this.virtualCol += 1;
return false;
}
if (cell.colSpan > 1 && cell.colIndex <= virtualCol && virtualCol < cell.colIndex + cell.colSpan) {
nextColIndex = offset > 0 ? Math.min(cell.colIndex + cell.colSpan, lastIndex) : Math.max(0, cell.colIndex + offset);
const nextCell = this.model.findCell(nextColIndex, prevRow);
if (!nextCell) {
this.virtualCol = nextColIndex;
return this.activate(cell.rowIndex, nextColIndex);
}
if (cell !== nextCell) {
cell = nextCell;
this.virtualCol = cell.colIndex;
}
else {
this.virtualCol = virtualCol;
}
return this.activate(cell.rowIndex, this.virtualCol);
}
this.virtualCol = cell.colIndex;
return this.activate(cell.rowIndex, cell.colIndex);
}
offsetRow(offset) {
let nextColIndex = this.virtualCol;
if (this.metadata && this.metadata.isVirtual) {
const maxIndex = this.metadata.maxLogicalRowIndex;
let nextIndex = Math.max(0, Math.min(this.activeRow + offset, maxIndex));
if (this.metadata.hasDetailTemplate && !this.model.findRow(nextIndex)) {
nextIndex = offset > 0 ? nextIndex + 1 : nextIndex - 1;
nextIndex = Math.max(0, Math.min(nextIndex, maxIndex));
}
if (this.metadata.hasDetailTemplate && nextIndex === maxIndex) {
if (this.model.lastRow.index !== maxIndex) {
// Don't attempt to navigate past the last collapsed row.
nextIndex--;
}
}
const nextRow = this.model.findRow(nextIndex);
if (nextRow) {
// remove duplication
let cell = this.model.findCell(this.virtualCol, nextRow);
if (!cell) {
return;
}
if (cell.rowIndex <= this.virtualRow && offset > 0 && cell.rowSpan > 1) {
cell = this.model.findCell(this.virtualCol, this.model.findRow(cell.rowIndex + cell.rowSpan - 1 + offset));
if (!cell) {
return;
}
}
nextIndex = cell.rowIndex;
nextColIndex = cell.colIndex;
}
this.virtualRow = nextIndex;
return this.activate(nextIndex, nextColIndex);
}
const nextRow = this.model.findRow(this.virtualRow + offset) || this.model.nextRow(this.virtualRow, offset);
if (!nextRow) {
return false;
}
let cell = this.model.findCell(this.virtualCol, nextRow);
if (cell && cell.rowIndex <= this.virtualRow && offset > 0 && cell.rowSpan > 1) { // spanned cell go to next
const nextPos = cell.rowIndex + cell.rowSpan - 1 + offset;
cell = this.model.findCell(this.virtualCol, this.model.findRow(nextPos));
}
if (!cell && (this.metadata.virtualColumns || this.metadata.hasDetailTemplate)) {
return this.activate(this.virtualRow + offset, this.virtualCol);
}
if (!cell) {
return false;
}
this.virtualRow = cell.rowIndex;
return this.activate(this.virtualRow, cell.colIndex);
}
}
/**
* @hidden
*/
class ItemMap {
count = 0;
items = {};
get first() {
if (this.count > 0) {
let result;
this.forEach(item => {
result = item;
return true;
});
return result;
}
}
get last() {
if (this.count > 0) {
const keys = Object.keys(this.items);
return this.items[keys[keys.length - 1]];
}
}
removeItem(key) {
if (this.items[key]) {
delete this.items[key];
this.count--;
}
}
setItem(key, item) {
if (!this.items[key]) {
this.count++;
}
this.items[key] = item;
}
getItem(key) {
return this.items[key];
}
toArray() {
const result = [];
this.forEach(item => {
result.push(item);
});
return result;
}
forEach(callback) {
for (const key in this.items) {
if (this.items.hasOwnProperty(key) && callback(this.items[key])) {
return this.items[key];
}
}
}
find(callback) {
return this.forEach(callback);
}
}
/**
* @hidden
*
* Contains information for the currently rendered rows and cells.
*/
class NavigationModel {
rows = new ItemMap();
get firstRow() {
return this.rows.first;
}
get lastRow() {
return this.rows.last;
}
registerCell(cell) {
const row = this.rows.getItem(cell.logicalRowIndex);
if (!row) {
return;
}
const colIndex = cell.logicalColIndex;
const modelCell = {
uid: cell.uid,
colIndex,
rowIndex: row.index,
colSpan: cell.colSpan,
rowSpan: cell.rowSpan,
detailExpandCell: cell.detailExpandCell,
dataItem: row.dataItem,
dataRowIndex: row.dataRowIndex,
focusGroup: cell.focusGroup
};
row.cells.setItem(colIndex, modelCell);
if (cell.groupItem) {
row.groupItem = cell.groupItem;
}
return modelCell;
}
unregisterCell(index, rowIndex, cell) {
const row = this.rows.getItem(rowIndex);
if (row) {
const match = row.cells.getItem(index);
if (match && match.uid === cell.uid) {
row.cells.removeItem(index);
}
}
}
registerRow(row) {
const modelRow = {
uid: row.uid,
index: row.logicalRowIndex,
dataItem: row.dataItem,
dataRowIndex: row.dataRowIndex,
cells: new ItemMap()
};
this.rows.setItem(row.logicalRowIndex, modelRow);
}
updateRow(row) {
const current = this.rows.getItem(row.logicalRowIndex);
if (current) {
Object.assign(current, {
dataItem: row.dataItem,
dataRowIndex: row.dataRowIndex
});
}
}
unregisterRow(index, row) {
const match = this.rows.getItem(index);
if (match && match.uid === row.uid) {
this.rows.removeItem(index);
}
}
cellRange(cell) {
if (cell) {
const start = cell.colIndex;
const end = cell.colIndex + (cell.colSpan || 1) - 1;
return {
start,
end
};
}
return {};
}
rowRange(cell) {
if (cell) {
const start = cell.rowIndex;
const end = cell.rowIndex + (cell.rowSpan || 1) - 1;
return {
start,
end
};
}
return {};
}
nextRow(rowIndex, offset) {
const row