igniteui-angular
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
1,331 lines (1,318 loc) • 4.83 MB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Directive, Input, HostListener, EventEmitter, InjectionToken, isDevMode, inject, PLATFORM_ID, Inject, ElementRef, ViewContainerRef, createComponent, Output, HostBinding, Self, Optional, booleanAttribute, SecurityContext, DestroyRef, Component, ContentChildren, ContentChild, RendererStyleFlags2, Pipe, ViewChild, LOCALE_ID, forwardRef, Host, QueryList, ViewChildren, TemplateRef, ChangeDetectionStrategy, SimpleChange, ChangeDetectorRef, NgZone, SkipSelf, CUSTOM_ELEMENTS_SCHEMA, reflectComponentType, NgModule } from '@angular/core';
import * as i4 from '@angular/forms';
import { NgModel, NgControl, FormControlName, NG_VALUE_ACCESSOR, Validators, NG_VALIDATORS, FormGroup, FormsModule, RequiredValidator, MinValidator, MaxValidator, EmailValidator, MinLengthValidator, MaxLengthValidator, PatternValidator, FormControl, ReactiveFormsModule } from '@angular/forms';
import { Observable, NEVER, Subject, fromEvent, BehaviorSubject, interval, animationFrameScheduler, noop, merge, Subscription, timer, sampleTime, filter as filter$1, pipe } from 'rxjs';
import { takeUntil, filter, throttle, throttleTime, first as first$2, startWith, take, debounce, tap, switchMap, skipLast, debounceTime, map, shareReplay, takeWhile, timeout, pluck } from 'rxjs/operators';
import { isPlatformBrowser, formatDate as formatDate$1, CurrencyPipe, FormatWidth, getLocaleDateFormat, formatPercent, formatNumber, getLocaleCurrencyCode, DatePipe, getLocaleDateTimeFormat, DOCUMENT, NgTemplateOutlet, NgClass, TitleCasePipe, getLocaleFirstDayOfWeek, NgStyle, getLocaleCurrencySymbol, formatCurrency as formatCurrency$1, getLocaleNumberFormat, NumberFormatStyle, DecimalPipe, PercentPipe, getCurrencySymbol, AsyncPipe } from '@angular/common';
import { mergeWith, isEqual as isEqual$1 } from 'lodash-es';
import { strToU8, zip } from 'fflate';
import { scaleInVerTop, scaleOutVerTop, AnimationUtil, fadeIn, fadeOut, slideInTop, slideOutTop, slideInBottom, slideOutBottom, scaleInHorRight, scaleOutHorRight, scaleInHorLeft, scaleOutHorLeft, scaleInVerBottom, scaleOutVerBottom, scaleInCenter, growVerIn, growVerOut, slideInLeft } from 'igniteui-angular/animations';
import * as i1 from '@angular/animations';
import { style, animate, useAnimation } from '@angular/animations';
import * as i1$1 from '@angular/platform-browser';
import { ɵgetDOM as _getDOM, HammerGestureConfig, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
import * as i2 from '@angular/common/http';
import { addRow, addChild, pinLeft, unpinLeft, jumpDown, jumpUp, caseSensitive, editor } from '@igniteui/material-icons-extended';
import { __decorate, __param } from 'tslib';
import { IgcTrialWatermark } from 'igniteui-trial-watermark';
/**
* Common service to be injected between components where those implementing common
* ToggleView interface can register and toggle directives can call their methods.
* TODO: Track currently active? Events?
*/
class IgxNavigationService {
constructor() {
this.navs = {};
}
add(id, navItem) {
this.navs[id] = navItem;
}
remove(id) {
delete this.navs[id];
}
get(id) {
if (id) {
return this.navs[id];
}
}
toggle(id, ...args) {
if (this.navs[id]) {
return this.navs[id].toggle(...args);
}
}
open(id, ...args) {
if (this.navs[id]) {
return this.navs[id].open(...args);
}
}
close(id, ...args) {
if (this.navs[id]) {
return this.navs[id].close(...args);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: IgxNavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: IgxNavigationService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: IgxNavigationService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [] });
/**
* Directive that can toggle targets through provided NavigationService.
*
* Usage:
* ```
* <button type="button" igxNavToggle="ID">Toggle</button>
* ```
* Where the `ID` matches the ID of compatible `IToggleView` component.
*/
class IgxNavigationToggleDirective {
constructor(nav) {
this.state = nav;
}
toggleNavigationDrawer() {
this.state.toggle(this.target, true);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: IgxNavigationToggleDirective, deps: [{ token: IgxNavigationService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.6", type: IgxNavigationToggleDirective, isStandalone: true, selector: "[igxNavToggle]", inputs: { target: ["igxNavToggle", "target"] }, host: { listeners: { "click": "toggleNavigationDrawer()" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: IgxNavigationToggleDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxNavToggle]',
standalone: true
}]
}], ctorParameters: () => [{ type: IgxNavigationService }], propDecorators: { target: [{
type: Input,
args: ['igxNavToggle']
}], toggleNavigationDrawer: [{
type: HostListener,
args: ['click']
}] } });
/**
* Directive that can close targets through provided NavigationService.
*
* Usage:
* ```
* <button type="button" igxNavClose="ID">Close</button>
* ```
* Where the `ID` matches the ID of compatible `IToggleView` component.
*/
class IgxNavigationCloseDirective {
constructor(nav) {
this.state = nav;
}
closeNavigationDrawer() {
this.state.close(this.target, true);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: IgxNavigationCloseDirective, deps: [{ token: IgxNavigationService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.6", type: IgxNavigationCloseDirective, isStandalone: true, selector: "[igxNavClose]", inputs: { target: ["igxNavClose", "target"] }, host: { listeners: { "click": "closeNavigationDrawer()" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: IgxNavigationCloseDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxNavClose]',
standalone: true
}]
}], ctorParameters: () => [{ type: IgxNavigationService }], propDecorators: { target: [{
type: Input,
args: ['igxNavClose']
}], closeNavigationDrawer: [{
type: HostListener,
args: ['click']
}] } });
class IgxTextHighlightService {
constructor() {
this.highlightGroupsMap = new Map();
this.onActiveElementChanged = new EventEmitter();
}
/**
* Activates the highlight at a given index.
* (if such index exists)
*/
setActiveHighlight(groupName, highlight) {
this.highlightGroupsMap.set(groupName, highlight);
this.onActiveElementChanged.emit(groupName);
}
/**
* Clears any existing highlight.
*/
clearActiveHighlight(groupName) {
this.highlightGroupsMap.set(groupName, {
index: -1
});
this.onActiveElementChanged.emit(groupName);
}
/**
* Destroys a highlight group.
*/
destroyGroup(groupName) {
this.highlightGroupsMap.delete(groupName);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: IgxTextHighlightService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: IgxTextHighlightService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: IgxTextHighlightService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [] });
/* Copyright (c) 2014-2020 Denis Pushkarev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE
*/
// Note: Originally copied from core-js-pure package and modified. (https://github.com/zloirock/core-js)
const queue = {};
let counter = 0;
let eventListenerAdded = false;
const run = (id) => {
if (queue.hasOwnProperty(id)) {
const fn = queue[id];
delete queue[id];
fn();
}
};
const listener = (event) => run(event.data);
// Use function instead of arrow function to workaround an issue in codesandbox
function setImmediate(cb, ...args) {
if (window.setImmediate) {
return window.setImmediate(cb);
}
if (!eventListenerAdded) {
eventListenerAdded = true;
window.addEventListener('message', listener, false);
}
queue[++counter] = () => {
cb.apply(undefined, args);
};
const windowLocation = window.location;
window.postMessage(counter + '', windowLocation.protocol + '//' + windowLocation.host);
return counter;
}
function clearImmediate(id) {
if (window.clearImmediate) {
return window.clearImmediate(id);
}
delete queue[id];
}
/** @hidden @internal */
const ELEMENTS_TOKEN = /*@__PURE__*/ new InjectionToken('elements environment');
/**
* @hidden
*/
const showMessage = (message, isMessageShown) => {
if (!isMessageShown && isDevMode()) {
console.warn(message);
}
return true;
};
const mkenum = (x) => x;
/**
*
* @hidden @internal
*/
const getResizeObserver = () => globalThis.window?.ResizeObserver;
/**
* @hidden
*/
const cloneArray = (array, deep) => {
const arr = [];
if (!array) {
return arr;
}
let i = array.length;
while (i--) {
arr[i] = deep ? cloneValue(array[i]) : array[i];
}
return arr;
};
/**
* Doesn't clone leaf items
*
* @hidden
*/
const cloneHierarchicalArray = (array, childDataKey) => {
const result = [];
if (!array) {
return result;
}
for (const item of array) {
const clonedItem = cloneValue(item);
if (Array.isArray(item[childDataKey])) {
clonedItem[childDataKey] = cloneHierarchicalArray(clonedItem[childDataKey], childDataKey);
}
result.push(clonedItem);
}
return result;
};
/**
* Creates an object with prototype from provided source and copies
* all properties descriptors from provided source
* @param obj Source to copy prototype and descriptors from
* @returns New object with cloned prototype and property descriptors
*/
const copyDescriptors = (obj) => {
if (obj) {
return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
}
};
/**
* Deep clones all first level keys of Obj2 and merges them to Obj1
*
* @param obj1 Object to merge into
* @param obj2 Object to merge from
* @returns Obj1 with merged cloned keys from Obj2
* @hidden
*/
const mergeObjects = (obj1, obj2) => mergeWith(obj1, obj2, (objValue, srcValue) => {
if (Array.isArray(srcValue)) {
return objValue = srcValue;
}
});
/**
* Creates deep clone of provided value.
* Supports primitive values, dates and objects.
* If passed value is array returns shallow copy of the array.
*
* @param value value to clone
* @returns Deep copy of provided value
* @hidden
*/
const cloneValue = (value) => {
if (isDate(value)) {
return new Date(value.getTime());
}
if (Array.isArray(value)) {
return [...value];
}
if (value instanceof Map || value instanceof Set) {
return value;
}
if (isObject(value)) {
const result = {};
for (const key of Object.keys(value)) {
if (key === "externalObject") {
continue;
}
result[key] = cloneValue(value[key]);
}
return result;
}
return value;
};
/**
* Creates deep clone of provided value.
* Supports primitive values, dates and objects.
* If passed value is array returns shallow copy of the array.
* For Objects property values and references are cached and reused.
* This allows for circular references to same objects.
*
* @param value value to clone
* @param cache map of cached values already parsed
* @returns Deep copy of provided value
* @hidden
*/
const cloneValueCached = (value, cache) => {
if (isDate(value)) {
return new Date(value.getTime());
}
if (Array.isArray(value)) {
return [...value];
}
if (value instanceof Map || value instanceof Set) {
return value;
}
if (isObject(value)) {
if (cache.has(value)) {
return cache.get(value);
}
const result = {};
cache.set(value, result);
for (const key of Object.keys(value)) {
result[key] = cloneValueCached(value[key], cache);
}
return result;
}
return value;
};
/**
* Parse provided input to Date.
*
* @param value input to parse
* @returns Date if parse succeed or null
* @hidden
*/
const parseDate = (value) => {
// if value is Invalid Date return null
if (isDate(value)) {
return !isNaN(value.getTime()) ? value : null;
}
return value ? new Date(value) : null;
};
/**
* Returns an array with unique dates only.
*
* @param columnValues collection of date values (might be numbers or ISO 8601 strings)
* @returns collection of unique dates.
* @hidden
*/
const uniqueDates = (columnValues) => columnValues.reduce((a, c) => {
if (!a.cache[c.label]) {
a.result.push(c);
}
a.cache[c.label] = true;
return a;
}, { result: [], cache: {} }).result;
/**
* Checks if provided variable is Object
*
* @param value Value to check
* @returns true if provided variable is Object
* @hidden
*/
const isObject = (value) => !!(value && value.toString() === '[object Object]');
/**
* Checks if provided variable is Date
*
* @param value Value to check
* @returns true if provided variable is Date
* @hidden
*/
const isDate = (value) => {
return Object.prototype.toString.call(value) === "[object Date]";
};
/**
* Checks if the two passed arguments are equal
* Currently supports date objects
*
* @param obj1
* @param obj2
* @returns: `boolean`
* @hidden
*/
const isEqual = (obj1, obj2) => {
if (isDate(obj1) && isDate(obj2)) {
return obj1.getTime() === obj2.getTime();
}
return obj1 === obj2;
};
/**
* Utility service taking care of various utility functions such as
* detecting browser features, general cross browser DOM manipulation, etc.
*
* @hidden @internal
*/
class PlatformUtil {
constructor(platformId) {
this.platformId = platformId;
this.isBrowser = isPlatformBrowser(this.platformId);
this.isIOS = this.isBrowser && /iPad|iPhone|iPod/.test(navigator.userAgent) && !('MSStream' in window);
this.isSafari = this.isBrowser && /Safari[\/\s](\d+\.\d+)/.test(navigator.userAgent);
this.isFirefox = this.isBrowser && /Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent);
this.isEdge = this.isBrowser && /Edge[\/\s](\d+\.\d+)/.test(navigator.userAgent);
this.isChromium = this.isBrowser && (/Chrom|e?ium/g.test(navigator.userAgent) ||
/Google Inc/g.test(navigator.vendor)) && !/Edge/g.test(navigator.userAgent);
this.browserVersion = this.isBrowser ? parseFloat(navigator.userAgent.match(/Version\/([\d.]+)/)?.at(1)) : 0;
/** @hidden @internal */
this.isElements = inject(ELEMENTS_TOKEN, { optional: true });
this.KEYMAP = mkenum({
ENTER: 'Enter',
SPACE: ' ',
ESCAPE: 'Escape',
ARROW_DOWN: 'ArrowDown',
ARROW_UP: 'ArrowUp',
ARROW_LEFT: 'ArrowLeft',
ARROW_RIGHT: 'ArrowRight',
END: 'End',
HOME: 'Home',
PAGE_DOWN: 'PageDown',
PAGE_UP: 'PageUp',
F2: 'F2',
TAB: 'Tab',
SEMICOLON: ';',
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values#editing_keys
DELETE: 'Delete',
BACKSPACE: 'Backspace',
CONTROL: 'Control',
X: 'x',
Y: 'y',
Z: 'z'
});
}
/**
* @hidden @internal
* Returns the actual size of the node content, using Range
* ```typescript
* let range = document.createRange();
* let column = this.grid.columnList.filter(c => c.field === 'ID')[0];
*
* let size = getNodeSizeViaRange(range, column.cells[0].nativeElement);
*
* @remarks
* The last parameter is useful when the size of the element to measure is modified by a
* parent element that has explicit size. In such cases the calculated size is never lower
* and the function may instead remove the parent size while measuring to get the correct value.
* ```
*/
getNodeSizeViaRange(range, node, sizeHoldingNode) {
let overflow = null;
let nodeStyles;
if (!this.isFirefox) {
overflow = node.style.overflow;
// we need that hack - otherwise content won't be measured correctly in IE/Edge
node.style.overflow = 'visible';
}
if (sizeHoldingNode) {
const style = sizeHoldingNode.style;
nodeStyles = [style.width, style.minWidth, style.flexBasis];
style.width = '';
style.minWidth = '';
style.flexBasis = '';
}
range.selectNodeContents(node);
const scale = node.getBoundingClientRect().width / node.offsetWidth;
const width = range.getBoundingClientRect().width / scale;
if (!this.isFirefox) {
// we need that hack - otherwise content won't be measured correctly in IE/Edge
node.style.overflow = overflow;
}
if (sizeHoldingNode) {
sizeHoldingNode.style.width = nodeStyles[0];
sizeHoldingNode.style.minWidth = nodeStyles[1];
sizeHoldingNode.style.flexBasis = nodeStyles[2];
}
return width;
}
/**
* Returns true if the current keyboard event is an activation key (Enter/Space bar)
*
* @hidden
* @internal
*
* @memberof PlatformUtil
*/
isActivationKey(event) {
return event.key === this.KEYMAP.ENTER || event.key === this.KEYMAP.SPACE;
}
/**
* Returns true if the current keyboard event is a combination that closes the filtering UI of the grid. (Escape/Ctrl+Shift+L)
*
* @hidden
* @internal
* @param event
* @memberof PlatformUtil
*/
isFilteringKeyCombo(event) {
return event.key === this.KEYMAP.ESCAPE || (event.ctrlKey && event.shiftKey && event.key.toLowerCase() === 'l');
}
/**
* @hidden @internal
*/
isLeftClick(event) {
return event.button === 0;
}
/**
* @hidden @internal
*/
isNavigationKey(key) {
return [
this.KEYMAP.HOME, this.KEYMAP.END, this.KEYMAP.SPACE,
this.KEYMAP.ARROW_DOWN, this.KEYMAP.ARROW_LEFT, this.KEYMAP.ARROW_RIGHT, this.KEYMAP.ARROW_UP
].includes(key);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: PlatformUtil, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: PlatformUtil, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: PlatformUtil, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Inject,
args: [PLATFORM_ID]
}] }] });
/**
* @hidden
*/
const flatten$1 = (arr) => {
let result = [];
arr.forEach(el => {
result.push(el);
if (el.children) {
const children = Array.isArray(el.children) ? el.children : el.children.toArray();
result = result.concat(flatten$1(children));
}
});
return result;
};
const HORIZONTAL_NAV_KEYS = new Set(['arrowleft', 'left', 'arrowright', 'right', 'home', 'end']);
const NAVIGATION_KEYS = new Set([
'down',
'up',
'left',
'right',
'arrowdown',
'arrowup',
'arrowleft',
'arrowright',
'home',
'end',
'space',
'spacebar',
' '
]);
const ACCORDION_NAVIGATION_KEYS = new Set('up down arrowup arrowdown home end'.split(' '));
const ROW_EXPAND_KEYS = new Set('right down arrowright arrowdown'.split(' '));
const ROW_COLLAPSE_KEYS = new Set('left up arrowleft arrowup'.split(' '));
const ROW_ADD_KEYS = new Set(['+', 'add', '≠', '±', '=']);
const SUPPORTED_KEYS = new Set([...Array.from(NAVIGATION_KEYS),
...Array.from(ROW_ADD_KEYS), 'enter', 'f2', 'escape', 'esc', 'pagedown', 'pageup']);
const HEADER_KEYS = new Set([...Array.from(NAVIGATION_KEYS), 'escape', 'esc', 'l',
/** This symbol corresponds to the Alt + L combination under MAC. */
'¬']);
/**
* @hidden
* @internal
*
* Creates a new ResizeObserver on `target` and returns it as an Observable.
* Run the resizeObservable outside angular zone, because it patches the MutationObserver which causes an infinite loop.
* Related issue: https://github.com/angular/angular/issues/31712
*/
const resizeObservable = (target) => {
const resizeObserver = getResizeObserver();
// check whether we are on server env or client env
if (resizeObserver) {
return new Observable((observer) => {
const instance = new resizeObserver((entries) => {
observer.next(entries);
});
instance.observe(target);
const unsubscribe = () => instance.disconnect();
return unsubscribe;
});
}
else {
// if on a server env return a empty observable that does not complete immediately
return NEVER;
}
};
/**
* @hidden
* @internal
*
* Compares two maps.
*/
const compareMaps = (map1, map2) => {
if (!map2) {
return !map1 ? true : false;
}
if (map1.size !== map2.size) {
return false;
}
let match = true;
const keys = Array.from(map2.keys());
for (const key of keys) {
if (map1.has(key)) {
match = map1.get(key) === map2.get(key);
}
else {
match = false;
}
if (!match) {
break;
}
}
return match;
};
/**
*
* Given a property access path in the format `x.y.z` resolves and returns
* the value of the `z` property in the passed object.
*
* @hidden
* @internal
*/
const resolveNestedPath = (obj, path) => {
const parts = path?.split('.') ?? [];
let current = obj[parts.shift()];
parts.forEach(prop => {
if (current) {
current = current[prop];
}
});
return current;
};
/**
*
* Given a property access path in the format `x.y.z` and a value
* this functions builds and returns an object following the access path.
*
* @example
* ```typescript
* console.log('x.y.z.', 42);
* >> { x: { y: { z: 42 } } }
* ```
*
* @hidden
* @internal
*/
const reverseMapper = (path, value) => {
const obj = {};
const parts = path?.split('.') ?? [];
let _prop = parts.shift();
let mapping;
// Initial binding for first level bindings
obj[_prop] = value;
mapping = obj;
parts.forEach(prop => {
// Start building the hierarchy
mapping[_prop] = {};
// Go down a level
mapping = mapping[_prop];
// Bind the value and move the key
mapping[prop] = value;
_prop = prop;
});
return obj;
};
const yieldingLoop = (count, chunkSize, callback, done) => {
let i = 0;
const chunk = () => {
const end = Math.min(i + chunkSize, count);
for (; i < end; ++i) {
callback(i);
}
if (i < count) {
setImmediate(chunk);
}
else {
done();
}
};
chunk();
};
const isConstructor = (ref) => typeof ref === 'function' && Boolean(ref.prototype) && Boolean(ref.prototype.constructor);
/**
* Similar to Angular's formatDate. However it will not throw on `undefined | null | ''` instead
* coalescing to an empty string.
*/
const formatDate = (value, format, locale, timezone) => {
if (value === null || value === undefined || value === '') {
return '';
}
return formatDate$1(value, format, locale, timezone);
};
const formatCurrency = new CurrencyPipe(undefined).transform;
/** Converts pixel values to their rem counterparts for a base value */
const rem = (value) => {
const base = parseFloat(globalThis.window?.getComputedStyle(globalThis.document?.documentElement).getPropertyValue('--ig-base-font-size'));
return Number(value) / base;
};
/** Get the size of the component as derived from the CSS size variable */
function getComponentSize(el) {
return globalThis.window?.getComputedStyle(el).getPropertyValue('--component-size');
}
/** Get the first item in an array */
function first$1(arr) {
return arr.at(0);
}
/** Get the last item in an array */
function last$1(arr) {
return arr.at(-1);
}
/** Calculates the modulo of two numbers, ensuring a non-negative result. */
function modulo(n, d) {
return ((n % d) + d) % d;
}
/**
* Splits an array into chunks of length `size` and returns a generator
* yielding each chunk.
* The last chunk may contain less than `size` elements.
*
* @example
* ```typescript
* const arr = [0,1,2,3,4,5,6,7,8,9];
*
* Array.from(chunk(arr, 2)) // [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]
* Array.from(chunk(arr, 3)) // [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
* Array.from(chunk([], 3)) // []
* Array.from(chunk(arr, -3)) // Error
* ```
*/
function* intoChunks(arr, size) {
if (size < 1) {
throw new Error('size must be an integer >= 1');
}
for (let i = 0; i < arr.length; i += size) {
yield arr.slice(i, i + size);
}
}
/**
* @param size
* @returns string that represents the --component-size default value
*/
function getComponentCssSizeVar(size) {
switch (size) {
case "1":
return 'var(--ig-size, var(--ig-size-small))';
case "2":
return 'var(--ig-size, var(--ig-size-medium))';
case "3":
default:
return 'var(--ig-size, var(--ig-size-large))';
}
}
/**
* @param path - The URI path to be normalized.
* @returns string encoded using the encodeURI function.
*/
function normalizeURI(path) {
return path?.split('/').map(encodeURI).join('/');
}
function getComponentTheme(el) {
return globalThis.window
?.getComputedStyle(el)
.getPropertyValue('--theme')
.trim();
}
/**
* Collection re-created w/ the built in track by identity will always log
* warning even for valid cases of recalculating all collection items.
* See https://github.com/angular/angular/blob/55581b4181639568fb496e91055142a1b489e988/packages/core/src/render3/instructions/control_flow.ts#L393-L409
* Current solution explicit track function doing the same as suggested in:
* https://github.com/angular/angular/issues/56471#issuecomment-2180315803
* This should be used with moderation and when necessary.
* @internal
*/
function trackByIdentity(item) {
return item;
}
var PagingError;
(function (PagingError) {
PagingError[PagingError["None"] = 0] = "None";
PagingError[PagingError["IncorrectPageIndex"] = 1] = "IncorrectPageIndex";
PagingError[PagingError["IncorrectRecordsPerPage"] = 2] = "IncorrectRecordsPerPage";
})(PagingError || (PagingError = {}));
var TransactionType;
(function (TransactionType) {
TransactionType["ADD"] = "add";
TransactionType["DELETE"] = "delete";
TransactionType["UPDATE"] = "update";
})(TransactionType || (TransactionType = {}));
var TransactionEventOrigin;
(function (TransactionEventOrigin) {
TransactionEventOrigin["UNDO"] = "undo";
TransactionEventOrigin["REDO"] = "redo";
TransactionEventOrigin["CLEAR"] = "clear";
TransactionEventOrigin["ADD"] = "add";
TransactionEventOrigin["END"] = "endPending";
})(TransactionEventOrigin || (TransactionEventOrigin = {}));
/* mustCoerceToInt */
var SortingDirection;
(function (SortingDirection) {
SortingDirection[SortingDirection["None"] = 0] = "None";
SortingDirection[SortingDirection["Asc"] = 1] = "Asc";
SortingDirection[SortingDirection["Desc"] = 2] = "Desc";
})(SortingDirection || (SortingDirection = {}));
class DefaultSortingStrategy {
static { this._instance = null; }
constructor() { }
static instance() {
return this._instance || (this._instance = new this());
}
/* blazorSuppress */
sort(data, fieldName, dir, ignoreCase, valueResolver, isDate, isTime) {
const key = fieldName;
const reverse = (dir === SortingDirection.Desc ? -1 : 1);
const cmpFunc = (obj1, obj2) => this.compareObjects(obj1, obj2, key, reverse, ignoreCase, valueResolver, isDate, isTime);
return this.arraySort(data, cmpFunc);
}
compareValues(a, b) {
const an = (a === null || a === undefined);
const bn = (b === null || b === undefined);
if (an) {
if (bn) {
return 0;
}
return -1;
}
else if (bn) {
return 1;
}
return a > b ? 1 : a < b ? -1 : 0;
}
compareObjects(obj1, obj2, key, reverse, ignoreCase, valueResolver, isDate, isTime) {
let a = valueResolver.call(this, obj1, key, isDate, isTime);
let b = valueResolver.call(this, obj2, key, isDate, isTime);
if (ignoreCase) {
a = a && a.toLowerCase ? a.toLowerCase() : a;
b = b && b.toLowerCase ? b.toLowerCase() : b;
}
return reverse * this.compareValues(a, b);
}
arraySort(data, compareFn) {
return data.sort(compareFn);
}
}
class GroupMemberCountSortingStrategy {
static { this._instance = null; }
constructor() { }
static instance() {
return this._instance || (this._instance = new this());
}
sort(data, fieldName, dir) {
const groupedArray = this.groupBy(data, fieldName);
const reverse = (dir === SortingDirection.Desc ? -1 : 1);
const cmpFunc = (a, b) => {
return this.compareObjects(a, b, groupedArray, fieldName, reverse);
};
return data
.sort((a, b) => a[fieldName].localeCompare(b[fieldName]))
.sort(cmpFunc);
}
groupBy(data, key) {
return data.reduce((acc, curr) => {
(acc[curr[key]] = acc[curr[key]] || []).push(curr);
return acc;
}, {});
}
compareObjects(obj1, obj2, data, fieldName, reverse) {
const firstItemValuesLength = data[obj1[fieldName]].length;
const secondItemValuesLength = data[obj2[fieldName]].length;
return reverse * (firstItemValuesLength - secondItemValuesLength);
}
}
class FormattedValuesSortingStrategy extends DefaultSortingStrategy {
static { this._instance = null; }
constructor() {
super();
}
static instance() {
return this._instance || (this._instance = new this());
}
sort(data, fieldName, dir, ignoreCase, valueResolver, isDate, isTime, grid) {
const key = fieldName;
const reverse = (dir === SortingDirection.Desc ? -1 : 1);
const cmpFunc = (obj1, obj2) => this.compareObjects(obj1, obj2, key, reverse, ignoreCase, valueResolver, isDate, isTime, grid);
return this.arraySort(data, cmpFunc);
}
compareObjects(obj1, obj2, key, reverse, ignoreCase, valueResolver, isDate, isTime, grid) {
let a = valueResolver.call(this, obj1, key, isDate, isTime);
let b = valueResolver.call(this, obj2, key, isDate, isTime);
if (grid) {
const col = grid.getColumnByName(key);
if (col && col.formatter) {
a = col.formatter(a);
b = col.formatter(b);
}
}
if (ignoreCase) {
a = a && a.toLowerCase ? a.toLowerCase() : a;
b = b && b.toLowerCase ? b.toLowerCase() : b;
}
return reverse * this.compareValues(a, b);
}
}
const isHierarchyMatch = (h1, h2, expressions) => {
if (h1.length !== h2.length) {
return false;
}
return h1.every((level, index) => {
const expr = expressions.find(e => e.fieldName === level.fieldName);
const comparer = expr.groupingComparer || DefaultSortingStrategy.instance().compareValues;
return level.fieldName === h2[index].fieldName && comparer(level.value, h2[index].value) === 0;
});
};
const getHierarchy = (gRow) => {
const hierarchy = [];
if (gRow !== undefined && gRow.expression) {
hierarchy.push({ fieldName: gRow.expression.fieldName, value: gRow.value });
while (gRow.groupParent) {
gRow = gRow.groupParent;
hierarchy.unshift({ fieldName: gRow.expression.fieldName, value: gRow.value });
}
}
return hierarchy;
};
const DATE_TYPE = 'date';
const TIME_TYPE = 'time';
const DATE_TIME_TYPE = 'dateTime';
const STRING_TYPE = 'string';
/**
* Represents a class implementing the IGridSortingStrategy interface.
* It provides sorting functionality for grid data based on sorting expressions.
*/
class IgxSorting {
/* blazorSuppress */
/**
* Sorts the provided data based on the given sorting expressions.
* `data`: The array of data to be sorted.
* `expressions`: An array of sorting expressions that define the sorting rules. The expression contains information like file name, whether the letter case should be taken into account, etc.
* `grid`: (Optional) The instance of the grid where the sorting is applied.
* Returns a new array with the data sorted according to the sorting expressions.
*/
sort(data, expressions, grid) {
return this.sortDataRecursive(data, expressions, 0, grid);
}
/**
* Recursively groups the provided data based on the given grouping state and returns the grouped result.
* Returns an array containing the grouped result.
* @internal
*/
groupDataRecursive(data, state, level, parent, metadata, grid = null, groupsRecords = [], fullResult = { data: [], metadata: [] }) {
const expressions = state.expressions;
const expansion = state.expansion;
let i = 0;
let result = [];
while (i < data.length) {
const column = grid ? grid.getColumnByName(expressions[level].fieldName) : null;
const isDate = column?.dataType === DATE_TYPE || column?.dataType === DATE_TIME_TYPE;
const isTime = column?.dataType === TIME_TYPE || column?.dataType === DATE_TIME_TYPE;
const isString = column?.dataType === STRING_TYPE;
const group = this.groupedRecordsByExpression(data, i, expressions[level], isDate, isTime, isString);
const groupRow = {
expression: expressions[level],
level,
records: cloneArray(group),
value: this.getFieldValue(group[0], expressions[level].fieldName, isDate, isTime),
groupParent: parent,
groups: [],
height: grid ? grid.renderedRowHeight : null,
column
};
if (parent) {
parent.groups.push(groupRow);
}
else {
groupsRecords.push(groupRow);
}
const hierarchy = getHierarchy(groupRow);
const expandState = expansion.find((s) => isHierarchyMatch(s.hierarchy || [{ fieldName: groupRow.expression.fieldName, value: groupRow.value }], hierarchy, expressions));
const expanded = expandState ? expandState.expanded : state.defaultExpanded;
let recursiveResult;
result.push(groupRow);
metadata.push(null);
fullResult.data.push(groupRow);
fullResult.metadata.push(null);
if (level < expressions.length - 1) {
recursiveResult = this.groupDataRecursive(group, state, level + 1, groupRow, expanded ? metadata : [], grid, groupsRecords, fullResult);
if (expanded) {
result = result.concat(recursiveResult);
}
}
else {
for (const groupItem of group) {
fullResult.metadata.push(groupRow);
fullResult.data.push(groupItem);
}
if (expanded) {
metadata.push(...fullResult.metadata.slice(fullResult.metadata.length - group.length));
result.push(...fullResult.data.slice(fullResult.data.length - group.length));
}
}
i += group.length;
}
return result;
}
/**
* Retrieves the value of the specified field from the given object, considering date and time data types.
* `key`: The key of the field to retrieve.
* `isDate`: (Optional) Indicates if the field is of type Date.
* `isTime`: (Optional) Indicates if the field is of type Time.
* Returns the value of the specified field in the data object.
* @internal
*/
getFieldValue(obj, key, isDate = false, isTime = false) {
let resolvedValue = resolveNestedPath(obj, key);
const date = parseDate(resolvedValue);
if (date && isDate && isTime) {
resolvedValue = date;
}
else if (date && isDate && !isTime) {
resolvedValue = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
}
else if (date && isTime && !isDate) {
resolvedValue = new Date(new Date().setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()));
}
return resolvedValue;
}
/**
* Groups the records in the provided data array based on the given grouping expression.
* `groupingComparer`: (Optional) A custom grouping comparator to determine the members of the group.
* Returns an array containing the records that belong to the group.
* @internal
*/
groupedRecordsByExpression(data, index, expression, isDate = false, isTime = false, isString, groupingComparer) {
const res = [];
const key = expression.fieldName;
const len = data.length;
const groupRecord = data[index];
let groupval = this.getFieldValue(groupRecord, key, isDate, isTime);
res.push(groupRecord);
index++;
const comparer = expression.groupingComparer || groupingComparer || DefaultSortingStrategy.instance().compareValues;
for (let i = index; i < len; i++) {
const currRec = data[i];
let fieldValue = this.getFieldValue(currRec, key, isDate, isTime);
if (expression.ignoreCase && isString) {
// when column's dataType is string but the value is number
fieldValue = fieldValue?.toString().toLowerCase();
groupval = groupval?.toString().toLowerCase();
}
if (comparer(fieldValue, groupval, currRec, groupRecord) === 0) {
res.push(currRec);
}
else {
break;
}
}
return res;
}
/**
* Sorts the provided data array based on the given sorting expressions.
* The method can be used when multiple sorting is performed, going through each one
* Returns a new array with the data sorted according to the sorting expressions.
* @internal
*/
sortDataRecursive(data, expressions, expressionIndex = 0, grid) {
let i;
let j;
let gbData;
let gbDataLen;
const exprsLen = expressions.length;
const dataLen = data.length;
expressionIndex = expressionIndex || 0;
if (expressionIndex >= exprsLen || dataLen <= 1) {
return data;
}
const expr = expressions[expressionIndex];
if (!expr.strategy) {
expr.strategy = DefaultSortingStrategy.instance();
}
const column = grid?.getColumnByName(expr.fieldName);
const isDate = column?.dataType === DATE_TYPE || column?.dataType === DATE_TIME_TYPE;
const isTime = column?.dataType === TIME_TYPE || column?.dataType === DATE_TIME_TYPE;
const isString = column?.dataType === STRING_TYPE;
data = expr.strategy.sort(data, expr.fieldName, expr.dir, expr.ignoreCase, this.getFieldValue, isDate, isTime, grid);
if (expressionIndex === exprsLen - 1) {
return data;
}
// in case of multiple sorting
for (i = 0; i < dataLen; i++) {
gbData = this.groupedRecordsByExpression(data, i, expr, isDate, isTime, isString, column?.groupingComparer);
gbDataLen = gbData.length;
if (gbDataLen > 1) {
gbData = this.sortDataRecursive(gbData, expressions, expressionIndex + 1, grid);
}
for (j = 0; j < gbDataLen; j++) {
data[i + j] = gbData[j];
}
i += gbDataLen - 1;
}
return data;
}
}
/**
* Represents a class implementing the IGridGroupingStrategy interface and extending the IgxSorting class.
* It provides a method to group data based on the given grouping state.
*/
class IgxGrouping extends IgxSorting {
/* blazorSuppress */
/**
* Groups the provided data based on the given grouping state.
* Returns an object containing the result of the grouping operation.
*/
groupBy(data, state, grid, groupsRecords, fullResult = { data: [], metadata: [] }) {
const metadata = [];
const grouping = this.groupDataRecursive(data, state, 0, null, metadata, grid, groupsRecords, fullResult);
grid?.groupingPerformedSubject.next();
return {
data: grouping,
metadata
};
}
}
/* csSuppress */
/**
* Represents a class implementing the IGridSortingStrategy interface with a no-operation sorting strategy.
* It performs no sorting and returns the data as it is.
*/
class NoopSortingStrategy {
static { this._instance = null; }
constructor() { }
static instance() {
return this._instance || (this._instance = new NoopSortingStrategy());
}
/* csSuppress */
sort(data) {
return data;
}
}
/**
* Represents a class extending the IgxSorting class
* Provides custom data record sorting.
*/
class IgxDataRecordSorting extends IgxSorting {
/**
* Overrides the base method to retrieve the field value from the data object instead of the record object.
* Returns the value of the specified field in the data object.
*/
getFieldValue(obj, key, isDate = false, isTime = false) {
return super.getFieldValue(obj.data, key, isDate, isTime);
}
}
/**
* Simplified data clone strategy that deep clones primitive values, dates and objects.
* Does not support circular references in objects.
*/
class DefaultDataCloneStrategy {
clone(data) {
return cloneValue(data);
}
}
/**
* Data clone strategy that is uses cache to deep clone primitive values, dates and objects.
* It allows using circular references inside object.
*/
class CachedDataCloneStrategy {
clone(data) {
return cloneValueCached(data, new Map);
}
}
/**
* @hidden
*/
const DataType = /*@__PURE__*/ mkenum({
String: 'string',
Number: 'number',
Boolean: 'boolean',
Date: 'date',
DateTime: 'dateTime',
Time: 'time',
Currency: 'currency',
Percent: 'percent',
Image: 'image'
});
/**
* @hidden
*/
const GridColumnDataType = DataType;
/**
* @hidden
*/
class DataUtil {
static sort(data, expressions, sorting = new IgxSorting(), grid) {
return sorting.sort(data, expressions, grid);
}
static treeGridSort(hierarchicalData, expressions, sorting = new IgxDataRecordSorting(), parent, grid) {
let res = [];
hierarchicalData.forEach((hr) => {
const rec = DataUtil.cloneTreeGridRecord(hr);
rec.parent = parent;
if (rec.children) {
rec.children = DataUtil.treeGridSort(rec.children, expressions, sorting, rec, grid);
}
res.push(rec);
});
res = DataUtil.sort(res, expressions, sorting, grid);
return res;
}
static cloneTreeGridRecord(hierarchicalRecord) {
const rec = {
key: hierarchicalRecord.key,
data: hierarchicalRecord.data,
children: hierarchicalRecord.children,
isFilteredOutParent: hierarchicalRecord.isFilteredOutParent,
level: hierarchicalRecord.level,
expanded: hierarchicalRecord.expanded
};
return rec;
}
static group(data, state, grouping = new IgxGrouping(), grid = null, groupsRecords = [], fullResult = { data: [], metadata: [] }) {
groupsRecords.splice(0, groupsRecords.length);
return grouping.groupBy(data, state, grid, groupsRecords, fullResult);
}
static page(data, state, dataLength) {
if (!state) {
return data;
}
const len = dataLength !== undefined ? dataLength : data.length;
const index = state.index;
const res = [];
const recordsPerPage = dataLength !== undefined && state.recordsPerPage > dataLength ? dataLength : state.recordsPerPage;
state.metadata = {
countPages: 0,
countRecords: len,
error: PagingError.None
};
if (index < 0 || isNaN(index)) {
state.metadata.error = PagingError.IncorrectPageIndex;
return res;
}
if (recordsPerPage <= 0 || isNaN(recordsPerPage)) {
state.metadata.error = PagingError.IncorrectRecordsPerPage;
return res;
}
state.metadata.countPages = Math.ceil(len / recordsPerPage);
if (!len) {
return data;
}
if (index >= state.metadata.countPages) {
state.metadata.error = PagingError.IncorrectPageIndex;
return res;
}
return data.slice(index * recordsPerPage, (index + 1) * recordsPerPage);
}
static correctPagingState(state, length) {
const maxPage = Math.ceil(length / state.recordsPerPage) - 1;
if (!isNaN(maxPage) && state.index > maxPage) {
state.index = maxPage;
}
}
static getHierarchy(gRow) {
return getHierarchy(gRow);
}
static isHierarchyMatch(h1, h2, expressions) {
return isHierarchyMatch(h1, h2, expressions);
}
/**
* Merges all changes from provided transactions into provided data collection
*
* @param data Collection to merge
* @param transactions Transactions to merge into data
* @param primaryKey Primary key of the collection, if any
* @param deleteRows Should delete rows with DELETE transaction type from data
* @returns Provided data collections updated with all provided transactions
*/
static mergeTransactions(data, transactions, primaryKey, cloneStrategy = new DefaultDataCloneStrategy(), deleteRows = false) {
data.forEach((item, index) => {
const rowId = primaryKey ? item[primaryKey] : item;
const transaction = transactions.find(t => t.id === rowId);
if (transaction && transaction.type === TransactionType.UPDATE) {
data[index] = mergeObjects(cloneStrategy.clone(data[index]), transaction.newValue);
}
});
if (deleteRows) {
transactions
.filter(t => t.type === TransactionType.DELETE)
.forEach(t => {
const index = primaryKey ? data.findIndex(d => d[primaryKey] === t.id) : data.findIndex(d => d === t.id);
if (0 <= index && index < data.length) {
data.splice(index, 1);