UNPKG

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
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);