igniteui-angular
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
1,391 lines (1,376 loc) • 319 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, inject, HostListener, Input, Directive, InjectionToken, isDevMode, PLATFORM_ID, NgZone, DOCUMENT, EventEmitter, Output, Component, TemplateRef, ElementRef, ApplicationRef, ViewContainerRef, createComponent } from '@angular/core';
import { isPlatformBrowser, formatDate as formatDate$1, CurrencyPipe, FormatWidth, getLocaleDateFormat, formatPercent, formatNumber, getLocaleCurrencyCode } from '@angular/common';
import { mergeWith } from 'lodash-es';
import { Observable, NEVER, Subject, fromEvent, BehaviorSubject } from 'rxjs';
import { ɵgetDOM as _getDOM } from '@angular/platform-browser';
import { AnimationBuilder } from '@angular/animations';
import { takeUntil, filter } from 'rxjs/operators';
import { scaleOutVerTop, scaleInVerTop, AnimationUtil, fadeOut, fadeIn, slideOutTop, slideInTop, slideOutBottom, slideInBottom, scaleOutHorRight, scaleInHorRight, scaleOutHorLeft, scaleInHorLeft, scaleOutVerBottom, scaleInVerBottom } from 'igniteui-angular/animations';
/**
* 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: "21.0.2", ngImport: i0, type: IgxNavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxNavigationService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", 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() {
const nav = inject(IgxNavigationService);
this.state = nav;
}
toggleNavigationDrawer() {
this.state.toggle(this.target, true);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxNavigationToggleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxNavigationToggleDirective, isStandalone: true, selector: "[igxNavToggle]", inputs: { target: ["igxNavToggle", "target"] }, host: { listeners: { "click": "toggleNavigationDrawer()" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxNavigationToggleDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxNavToggle]',
standalone: true
}]
}], ctorParameters: () => [], 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() {
const nav = inject(IgxNavigationService);
this.state = nav;
}
closeNavigationDrawer() {
this.state.close(this.target, true);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxNavigationCloseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxNavigationCloseDirective, isStandalone: true, selector: "[igxNavClose]", inputs: { target: ["igxNavClose", "target"] }, host: { listeners: { "click": "closeNavigationDrawer()" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxNavigationCloseDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxNavClose]',
standalone: true
}]
}], ctorParameters: () => [], propDecorators: { target: [{
type: Input,
args: ['igxNavClose']
}], closeNavigationDrawer: [{
type: HostListener,
args: ['click']
}] } });
var DateRangeType;
(function (DateRangeType) {
DateRangeType[DateRangeType["After"] = 0] = "After";
DateRangeType[DateRangeType["Before"] = 1] = "Before";
DateRangeType[DateRangeType["Between"] = 2] = "Between";
DateRangeType[DateRangeType["Specific"] = 3] = "Specific";
DateRangeType[DateRangeType["Weekdays"] = 4] = "Weekdays";
DateRangeType[DateRangeType["Weekends"] = 5] = "Weekends";
})(DateRangeType || (DateRangeType = {}));
/**
* @hidden @internal
*
* Enumeration representing the possible predefined size options of the grid.
* - Small: This is the smallest size with 32px row height. Left and Right paddings are 12px. Minimal column width is 56px.
* - Medium: This is the middle size with 40px row height. Left and Right paddings are 16px. Minimal column width is 64px.
* - Large: this is the default Grid size with the lowest intense and row height equal to 50px. Left and Right paddings are 24px. Minimal column width is 80px.
*/
const Size = {
Small: '1',
Medium: '2',
Large: '3'
};
/**
* Enumeration representing the days of the week.
*/
var WEEKDAYS;
(function (WEEKDAYS) {
WEEKDAYS[WEEKDAYS["SUNDAY"] = 0] = "SUNDAY";
WEEKDAYS[WEEKDAYS["MONDAY"] = 1] = "MONDAY";
WEEKDAYS[WEEKDAYS["TUESDAY"] = 2] = "TUESDAY";
WEEKDAYS[WEEKDAYS["WEDNESDAY"] = 3] = "WEDNESDAY";
WEEKDAYS[WEEKDAYS["THURSDAY"] = 4] = "THURSDAY";
WEEKDAYS[WEEKDAYS["FRIDAY"] = 5] = "FRIDAY";
WEEKDAYS[WEEKDAYS["SATURDAY"] = 6] = "SATURDAY";
})(WEEKDAYS || (WEEKDAYS = {}));
/* 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;
};
/**
*
* @hidden @internal
*/
const getResizeObserver = () => globalThis.window?.ResizeObserver;
/**
* @hidden
*/
function cloneArray(array, deep = false) {
return deep ? (array ?? []).map(cloneValue) : (array ?? []).slice();
}
/**
* @hidden
*/
function areEqualArrays(arr1, arr2) {
if (arr1.length !== arr2.length)
return false;
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i])
return false;
}
return true;
}
/**
* 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.slice();
}
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;
};
/**
* Limits a number to a range between a minimum and a maximum value.
*
* @param number
* @param min
* @param max
* @returns: `number`
* @hidden
*/
const clamp = (number, min, max) => Math.max(min, Math.min(number, max));
/**
* Utility service taking care of various utility functions such as
* detecting browser features, general cross browser DOM manipulation, etc.
*
* @hidden @internal
*/
class PlatformUtil {
constructor() {
this.platformId = inject(PLATFORM_ID);
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 = {
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: "21.0.2", ngImport: i0, type: PlatformUtil, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: PlatformUtil, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: PlatformUtil, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
/**
* @hidden
*/
const flatten = (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(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;
});
}
// 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;
}
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;
};
function _isObject(entity) {
return entity != null && typeof entity === 'object';
}
function columnFieldPath(path) {
return path?.split('.') ?? [];
}
/**
* 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
*/
function resolveNestedPath(obj, pathParts, defaultValue) {
if (!_isObject(obj) || pathParts.length < 1) {
return defaultValue;
}
let current = obj;
for (const key of pathParts) {
if (_isObject(current) && key in current) {
current = current[key];
}
else {
return defaultValue;
}
}
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(arr) {
return arr.at(0);
}
/** Get the last item in an array */
function last(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))';
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;
}
/** @hidden */
class IgxSelectionAPIService {
constructor() {
/**
* If primaryKey is defined, then multiple selection is based on the primaryKey, and it is array of numbers, strings, etc.
* If the primaryKey is omitted, then selection is based on the item data
*/
this.selection = new Map();
}
/**
* Get current component selection.
*
* @param componentID ID of the component.
*/
get(componentID) {
return this.selection.get(componentID);
}
/**
* Set new component selection.
*
* @param componentID ID of the component.
* @param newSelection The new component selection to be set.
*/
set(componentID, newSelection) {
if (!componentID) {
throw Error('Invalid value for component id!');
}
this.selection.set(componentID, newSelection);
}
/**
* Clears selection for component.
*
* @param componentID ID of the component.
*/
clear(componentID) {
this.selection.set(componentID, this.get_empty());
}
/**
* Removes selection for a component.
* @param componentID
*/
delete(componentID) {
this.selection.delete(componentID);
}
/**
* Get current component selection length.
*
* @param componentID ID of the component.
*/
size(componentID) {
const sel = this.get(componentID);
return sel ? sel.size : 0;
}
/**
* Creates new selection that consist of the new item added to the current component selection.
* The returned collection is new Set,
* therefore if you want to update component selection you need to call in addition the set_selection() method
* or instead use the select_item() one.
*
* @param componentID ID of the component, which we add new item to.
* @param itemID ID of the item to add to component selection.
* @param sel Used internally only by the selection (add_items method) to accumulate selection for multiple items.
*
* @returns Selection after the new item is added.
*/
add_item(componentID, itemID, sel) {
if (!sel) {
sel = new Set(this.get(componentID));
}
if (sel === undefined) {
sel = this.get_empty();
}
sel.add(itemID);
return sel;
}
/**
* Creates new selection that consist of the new items added to the current component selection.
* The returned collection is new Set,
* therefore if you want to update component selection you need to call in addition the set_selection() method
* or instead use the select_items() one.
*
* @param componentID ID of the component, which we add new items to.
* @param itemIDs Array of IDs of the items to add to component selection.
* @param clearSelection If true it will clear previous selection.
*
* @returns Selection after the new items are added.
*/
add_items(componentID, itemIDs, clearSelection) {
let selection;
if (clearSelection) {
selection = this.get_empty();
}
else if (itemIDs && itemIDs.length === 0) {
selection = new Set(this.get(componentID));
}
itemIDs.forEach((item) => selection = this.add_item(componentID, item, selection));
return selection;
}
/**
* Add item to the current component selection.
*
* @param componentID ID of the component, which we add new item to.
* @param itemID ID of the item to add to component selection.
* @param sel Used internally only by the selection (select_items method) to accumulate selection for multiple items.
*/
select_item(componentID, itemID, sel) {
this.set(componentID, this.add_item(componentID, itemID, sel));
}
/**
* Add items to the current component selection.
*
* @param componentID ID of the component, which we add new items to.
* @param itemIDs Array of IDs of the items to add to component selection.
* @param clearSelection If true it will clear previous selection.
*/
select_items(componentID, itemID, clearSelection) {
this.set(componentID, this.add_items(componentID, itemID, clearSelection));
}
/**
* Creates new selection that consist of the new items excluded from the current component selection.
* The returned collection is new Set,
* therefore if you want to update component selection you need to call in addition the set_selection() method
* or instead use the deselect_item() one.
*
* @param componentID ID of the component, which we remove items from.
* @param itemID ID of the item to remove from component selection.
* @param sel Used internally only by the selection (delete_items method) to accumulate deselected items.
*
* @returns Selection after the item is removed.
*/
delete_item(componentID, itemID, sel) {
if (!sel) {
sel = new Set(this.get(componentID));
}
if (sel === undefined) {
return;
}
sel.delete(itemID);
return sel;
}
/**
* Creates new selection that consist of the new items removed to the current component selection.
* The returned collection is new Set,
* therefore if you want to update component selection you need to call in addition the set_selection() method
* or instead use the deselect_items() one.
*
* @param componentID ID of the component, which we remove items from.
* @param itemID ID of the items to remove from component selection.
*
* @returns Selection after the items are removed.
*/
delete_items(componentID, itemIDs) {
let selection;
itemIDs.forEach((deselectedItem) => selection = this.delete_item(componentID, deselectedItem, selection));
return selection;
}
/**
* Remove item from the current component selection.
*
* @param componentID ID of the component, which we remove item from.
* @param itemID ID of the item to remove from component selection.
* @param sel Used internally only by the selection (deselect_items method) to accumulate selection for multiple items.
*/
deselect_item(componentID, itemID, sel) {
this.set(componentID, this.delete_item(componentID, itemID, sel));
}
/**
* Remove items to the current component selection.
*
* @param componentID ID of the component, which we add new items to.
* @param itemIDs Array of IDs of the items to add to component selection.
*/
deselect_items(componentID, itemID, _clearSelection) {
this.set(componentID, this.delete_items(componentID, itemID));
}
/**
* Check if the item is selected in the component selection.
*
* @param componentID ID of the component.
* @param itemID ID of the item to search.
*
* @returns If item is selected.
*/
is_item_selected(componentID, itemID) {
const sel = this.get(componentID);
if (!sel) {
return false;
}
return sel.has(itemID);
}
/**
* Get first element in the selection.
* This is correct when we have only one item in the collection (for single selection purposes)
* and the method returns that item.
*
* @param componentID ID of the component.
*
* @returns First element in the set.
*/
first_item(componentID) {
const sel = this.get(componentID);
if (sel && sel.size > 0) {
return sel.values().next().value;
}
}
/**
* Returns whether all items are selected.
*
* @param componentID ID of the component.
* @param dataCount: number Number of items in the data.
*
* @returns If all items are selected.
*/
are_all_selected(componentID, dataCount) {
return dataCount > 0 && dataCount === this.size(componentID);
}
/**
* Returns whether any of the items is selected.
*
* @param componentID ID of the component.
* @param data Entire data array.
*
* @returns If there is any item selected.
*/
are_none_selected(componentID) {
return this.size(componentID) === 0;
}
/**
* Get all primary key values from a data array. If there isn't a primary key defined that the entire data is returned instead.
*
* @param data Entire data array.
* @param primaryKey Data primary key.
*
* @returns Array of identifiers, either primary key values or the entire data array.
*/
get_all_ids(data, primaryKey) {
// If primaryKey is 0, this should still map to the property
return primaryKey !== undefined && primaryKey !== null ? data.map((x) => x[primaryKey]) : data;
}
/**
* Returns empty selection collection.
*
* @returns empty set.
*/
get_empty() {
return new Set();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectionAPIService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectionAPIService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxSelectionAPIService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
/**
* Injection token is used to inject the EditorProvider token into components
*
* @hidden @internal
*/
const EDITOR_PROVIDER = new InjectionToken('EditorProvider');
const EVENT_SUFFIX = 'precise';
/**
* Touch gestures manager based on Hammer.js
* Use with caution, this will track references for single manager per element. Very TBD. Much TODO.
*
* @hidden
*/
class HammerGesturesManager {
static { this.Hammer = typeof window !== 'undefined' ? window.Hammer : null; }
constructor() {
this._zone = inject(NgZone);
this.doc = inject(DOCUMENT);
this.platformUtil = inject(PlatformUtil);
/**
* Event option defaults for each recognizer, see http://hammerjs.github.io/api/ for API listing.
*/
this.hammerOptions = {};
this._hammerManagers = [];
this.platformBrowser = this.platformUtil.isBrowser;
if (this.platformBrowser && HammerGesturesManager.Hammer) {
this.hammerOptions = {
// D.P. #447 Force TouchInput due to PointerEventInput bug (https://github.com/hammerjs/hammer.js/issues/1065)
// see https://github.com/IgniteUI/igniteui-angular/issues/447#issuecomment-324601803
inputClass: HammerGesturesManager.Hammer.TouchInput,
recognizers: [
[HammerGesturesManager.Hammer.Pan, { threshold: 0 }],
[HammerGesturesManager.Hammer.Swipe, { direction: HammerGesturesManager.Hammer.DIRECTION_HORIZONTAL }],
[HammerGesturesManager.Hammer.Tap],
[HammerGesturesManager.Hammer.Tap, { event: 'doubletap', taps: 2 }, ['tap']]
]
};
}
}
supports(eventName) {
return eventName.toLowerCase().endsWith('.' + EVENT_SUFFIX);
}
/**
* Add listener extended with options for Hammer.js. Will use defaults if none are provided.
* Modeling after other event plugins for easy future modifications.
*/
addEventListener(element, eventName, eventHandler, options = null) {
if (!this.platformBrowser) {
return;
}
// Creating the manager bind events, must be done outside of angular
return this._zone.runOutsideAngular(() => {
if (!HammerGesturesManager.Hammer) {
//no hammer
return;
}
let mc = this.getManagerForElement(element);
if (mc === null) {
// new Hammer is a shortcut for Manager with defaults
mc = new HammerGesturesManager.Hammer(element, Object.assign(this.hammerOptions, options));
this.addManagerForElement(element, mc);
}
const handler = (eventObj) => this._zone.run(() => eventHandler(eventObj));
mc.on(eventName, handler);
return () => mc.off(eventName, handler);
});
}
/**
* Add listener extended with options for Hammer.js. Will use defaults if none are provided.
* Modeling after other event plugins for easy future modifications.
*
* @param target Can be one of either window, body or document(fallback default).
*/
addGlobalEventListener(target, eventName, eventHandler) {
if (!this.platformBrowser || !HammerGesturesManager.Hammer) {
return;
}
const element = this.getGlobalEventTarget(target);
// Creating the manager bind events, must be done outside of angular
return this.addEventListener(element, eventName, eventHandler);
}
/**
* Exposes [Dom]Adapter.getGlobalEventTarget to get global event targets.
* Supported: window, document, body. Defaults to document for invalid args.
*
* @param target Target name
*/
getGlobalEventTarget(target) {
return _getDOM().getGlobalEventTarget(this.doc, target);
}
/**
* Set HammerManager options.
*
* @param element The DOM element used to create the manager on.
*
* ### Example
*
* ```ts
* manager.setManagerOption(myElem, "pan", { pointers: 1 });
* ```
*/
setManagerOption(element, event, options) {
const manager = this.getManagerForElement(element);
manager.get(event).set(options);
}
/**
* Add an element and manager map to the internal collection.
*
* @param element The DOM element used to create the manager on.
*/
addManagerForElement(element, manager) {
this._hammerManagers.push({ element, manager });
}
/**
* Get HammerManager for the element or null
*
* @param element The DOM element used to create the manager on.
*/
getManagerForElement(element) {
const result = this._hammerManagers.filter(value => value.element === element);
return result.length ? result[0].manager : null;
}
/**
* Destroys the HammerManager for the element, removing event listeners in the process.
*
* @param element The DOM element used to create the manager on.
*/
removeManagerForElement(element) {
let index = null;
for (let i = 0; i < this._hammerManagers.length; i++) {
if (element === this._hammerManagers[i].element) {
index = i;
break;
}
}
if (index !== null) {
const item = this._hammerManagers.splice(index, 1)[0];
// destroy also
item.manager.destroy();
}
}
/** Destroys all internally tracked HammerManagers, removing event listeners in the process. */
destroy() {
for (const item of this._hammerManagers) {
item.manager.destroy();
}
this._hammerManagers = [];
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: HammerGesturesManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: HammerGesturesManager }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: HammerGesturesManager, decorators: [{
type: Injectable
}], ctorParameters: () => [] });
/**
* Stripped-down HammerJS annotations.
*/
/* csSuppress */
/** @hidden @internal */
class IgxActionStripToken {
}
/* csSuppress */
/**
* Abstract class defining the contract for components that provide actions to the action strip.
* This allows the action strip to remain standalone and not be aware of specific implementations.
* @hidden @internal
*/
class IgxActionStripActionsToken {
}
/**
* Templates the default toggle icon in the picker.
*
* @remarks Can be applied to IgxDatePickerComponent, IgxTimePickerComponent, IgxDateRangePickerComponent
*
* @example
* ```html
* <igx-date-range-picker>
* <igx-picker-toggle igxSuffix>
* <igx-icon>calendar_view_day</igx-icon>
* </igx-picker-toggle>
* </igx-date-range-picker>
* ```
*/
class IgxPickerToggleComponent {
constructor() {
this.clicked = new EventEmitter();
}
onClick(event) {
// do not focus input on click
event.stopPropagation();
this.clicked.emit();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxPickerToggleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.2", type: IgxPickerToggleComponent, isStandalone: true, selector: "igx-picker-toggle", outputs: { clicked: "clicked" }, host: { listeners: { "click": "onClick($event)" } }, ngImport: i0, template: `<ng-content></ng-content>`, isInline: true }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxPickerToggleComponent, decorators: [{
type: Component,
args: [{
template: `<ng-content></ng-content>`,
selector: 'igx-picker-toggle',
standalone: true
}]
}], propDecorators: { clicked: [{
type: Output
}], onClick: [{
type: HostListener,
args: ['click', ['$event']]
}] } });
/**
* Templates the default clear icon in the picker.
*
* @remarks Can be applied to IgxDatePickerComponent, IgxTimePickerComponent, IgxDateRangePickerComponent
*
* @example
* ```html
* <igx-date-picker>
* <igx-picker-clear igxSuffix>
* <igx-icon>delete</igx-icon>
* </igx-picker-clear>
* </igx-date-picker>
* ```
*/
class IgxPickerClearComponent extends IgxPickerToggleComponent {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxPickerClearComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.2", type: IgxPickerClearComponent, isStandalone: true, selector: "igx-picker-clear", usesInheritance: true, ngImport: i0, template: `<ng-content></ng-content>`, isInline: true }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxPickerClearComponent, decorators: [{
type: Component,
args: [{
template: `<ng-content></ng-content>`,
selector: 'igx-picker-clear',
standalone: true
}]
}] });
/**
* IgxPickerActionsDirective can be used to re-template the dropdown/dialog action buttons.
*
* @remarks Can be applied to IgxDatePickerComponent, IgxTimePickerComponent, IgxDateRangePickerComponent
*
*/
class IgxPickerActionsDirective {
constructor() {
this.template = inject(TemplateRef);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxPickerActionsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxPickerActionsDirective, isStandalone: true, selector: "[igxPickerActions]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxPickerActionsDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxPickerActions]',
standalone: true
}]
}] });
/** Header orientation in `dialog` mode. */
const PickerHeaderOrientation = {
Horizontal: 'horizontal',
Vertical: 'vertical'
};
/** Calendar orientation. */
const PickerCalendarOrientation = {
Horizontal: 'horizontal',
Vertical: 'vertical'
};
/**
* This enumeration is used to configure whether the date/time picker has an editable input with drop down
* or is readonly - the date/time is selected only through a dialog.
*/
const PickerInteractionMode = {
DropDown: 'dropdown',
Dialog: 'dialog'
};
/**
* Specify a particular date, time or AmPm part.
*/
var DatePart;
(function (DatePart) {
DatePart["Date"] = "date";
DatePart["Month"] = "month";
DatePart["Year"] = "year";
DatePart["Hours"] = "hours";
DatePart["Minutes"] = "minutes";
DatePart["Seconds"] = "seconds";
DatePart["FractionalSeconds"] = "fractionalSeconds";
DatePart["AmPm"] = "ampm";
DatePart["Literal"] = "literal";
})(DatePart || (DatePart = {}));
/**
* Minimal type stubs for grid types to avoid circular dependencies.
* These are simple interfaces that core uses for typing only.
* The actual implementations are in igniteui-angular/grids.
*/
/* mustCoerceToInt */
/**
* Enumeration representing the possible positions for pinning columns.
* - Start: Columns are pinned to the start of the grid.
* - End: Columns are pinned to the end of the grid.
*/
var ColumnPinningPosition;
(function (ColumnPinningPosition) {
ColumnPinningPosition[ColumnPinningPosition["Start"] = 0] = "Start";
ColumnPinningPosition[ColumnPinningPosition["End"] = 1] = "End";
})(ColumnPinningPosition || (ColumnPinningPosition = {}));
/**
* Enumeration representing different calculation modes for grid summaries.
* - rootLevelOnly: Summaries are calculated only for the r