@taiga-ui/cdk
Version:
Base library for creating Angular components and applications using Taiga UI principles regarding of actual visual appearance
412 lines (379 loc) • 13.8 kB
JavaScript
import { FormArray, FormGroup } from '@angular/forms';
import { tuiToInt } from '@taiga-ui/cdk/utils/math';
import { InjectionToken, isSignal, signal, inject, effect, DestroyRef, EnvironmentInjector, createComponent } from '@angular/core';
import { TUI_ALLOW_SIGNAL_WRITES } from '@taiga-ui/cdk/constants';
import { toSignal } from '@angular/core/rxjs-interop';
function tuiArrayRemove(array, index) {
return array.slice(0, Math.max(index, 0)).concat(array.slice(Math.max(index + 1, 0)));
}
function tuiArrayShallowEquals(a, b) {
return a.length === b.length && a.every((item, index) => item === b[index]);
}
function tuiArrayToggle(array, item, identity) {
const index = identity
? array.findIndex((it) => identity(it, item))
: array.indexOf(item);
return index === -1 ? [...array, item] : tuiArrayRemove(array, index);
}
/* internal */
const changeDateSeparator = (dateString, newDateSeparator) => dateString.replaceAll(/[^0-9A-Za-zА-Яа-я]/gi, newDateSeparator);
function tuiIsControlEmpty({ value = null }) {
return value === null || value === '' || (Array.isArray(value) && !value.length);
}
function tuiCountFilledControls(control) {
if (control instanceof FormArray) {
return control.controls.reduce((acc, nestedControl) => acc + tuiCountFilledControls(nestedControl), 0);
}
if (control instanceof FormGroup) {
return Object.values(control.controls).reduce((acc, nestedControl) => acc + tuiCountFilledControls(nestedControl), 0);
}
return tuiToInt(!tuiIsControlEmpty(control));
}
/**
* @deprecated: drop in v5.0
*/
function tuiCreateToken(defaults) {
return defaults === undefined
? new InjectionToken('')
: tuiCreateTokenFromFactory(() => defaults);
}
/**
* @deprecated: drop in v5.0
*/
function tuiCreateTokenFromFactory(factory) {
return factory ? new InjectionToken('', { factory }) : new InjectionToken('');
}
function tuiIsString(value) {
return typeof value === 'string';
}
function tuiDefaultSort(x, y) {
if (x === y) {
return 0;
}
if (tuiIsString(x) && tuiIsString(y)) {
return x.localeCompare(y);
}
return x > y ? 1 : -1;
}
function tuiDirectiveBinding(token, key, initial, options = { self: true }) {
const result = isSignal(initial) ? initial : signal(initial);
const directive = inject(token, options);
const output = directive[`${key.toString()}Change`];
// TODO: Figure out why effects are executed all the time and not just when result changes (check with Angular 18)
let previous;
effect(() => {
const value = result();
if (previous === value) {
return;
}
if (isSignal(directive[key])) {
directive[key].set(value);
}
else {
directive[key] = value;
}
directive.ngOnChanges?.({});
output?.emit?.(value);
previous = value;
}, TUI_ALLOW_SIGNAL_WRITES);
return result;
}
/** @deprecated remove in v5 */
function tuiDirectiveListener(token, key, options = { self: true }) {
const prop = inject(token, options)?.[key];
return isSignal(prop) ? prop : toSignal(prop);
}
function tuiDistanceBetweenTouches({ touches }) {
return Math.hypot((touches[0]?.clientX ?? 0) - (touches[1]?.clientX ?? 0), (touches[0]?.clientY ?? 0) - (touches[1]?.clientY ?? 0));
}
/// <reference types="@taiga-ui/tsconfig/ng-dev-mode" />
function tuiEaseInOutQuad(t) {
ngDevMode &&
console.assert(t >= 0 && t <= 1, 'Input must be between 0 and 1 inclusive but received ', t);
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
/**
* Flattens two-dimensional array and calculates resulting length
*
* @param array twi dimensional array
*/
function tuiFlatLength(array) {
return array.reduce((count, section) => count + section.length, 0);
}
const IFRAME = 'position: fixed; visibility: hidden; pointer-events: none';
const BODY = 'height: fit-content; line-height: 1em;';
const CLASS = 'tui-font-size-watcher';
function tuiFontSizeWatcher(callback, win = window) {
if (win.document.querySelector(`.${CLASS}`)) {
return () => { };
}
const iframe = win.document.createElement('iframe');
const resize = () => {
const { innerWidth, outerWidth, devicePixelRatio } = win;
iframe.width = `${innerWidth === outerWidth ? innerWidth : innerWidth / devicePixelRatio}`;
};
win.document.body.append(iframe);
win.addEventListener('resize', resize);
const doc = iframe.contentWindow?.document;
const observer = new ResizeObserver(() => callback(doc?.body.offsetHeight || 0));
iframe.setAttribute('class', CLASS);
iframe.setAttribute('style', IFRAME);
doc?.documentElement.style.setProperty('font', '-apple-system-body');
doc?.body.setAttribute('style', BODY);
doc?.body.insertAdjacentText('beforeend', '.'.repeat(1000));
observer.observe(doc?.body || iframe);
resize();
return () => {
observer.disconnect();
iframe.remove();
win.removeEventListener('resize', resize);
};
}
/**
* Extracts original array from {@link QueryList} rather than
* creating a copy like {@link QueryList.toArray} does.
* @param queryList
* @returns original array from {@link QueryList}.
*/
function tuiGetOriginalArrayFromQueryList(queryList) {
let array = [];
queryList.find((_item, _index, originalArray) => {
array = originalArray;
return true;
});
return array;
}
function tuiIsFalsy(value) {
return !value;
}
function tuiIsNumber(value) {
return typeof value === 'number';
}
function tuiIsObject(value) {
return typeof value === 'object' && !!value;
}
function tuiIsPresent(value) {
return value !== null && value !== undefined;
}
/**
* @deprecated: drop in v5.0
*/
function tuiIsValidUrl(url) {
return new RegExp(String.raw `^([a-zA-Z]+:\/\/)?` + // protocol
String.raw `((([a-z\d]([a-z\d-]*[a-z\d])*)\.)+[a-z]{2,}|localhost|` + // domain name
String.raw `((\d{1,3}\.){3}\d{1,3}))` + // OR IP (v4) address
String.raw `(\:\d+)?(\/[-a-z\d%_.~+\:]*)*` + // port and path
String.raw `(\?[)(;&a-z\d%_.~+=-]*)?` + // query string
String.raw `(\#[-a-z\d_]*)?$`, // fragment locator
'i').test(url);
}
function tuiMarkControlAsTouchedAndValidate(control) {
if (control instanceof FormArray) {
control.controls.forEach((nestedControl) => {
tuiMarkControlAsTouchedAndValidate(nestedControl);
});
}
if (control instanceof FormGroup) {
Object.values(control.controls).forEach((nestedControl) => {
tuiMarkControlAsTouchedAndValidate(nestedControl);
});
}
control.markAsTouched();
control.updateValueAndValidity();
}
/**
* Checks identity for nullable elements.
*
* @param a element a
* @param b element b
* @param handler called if both elements are not null
* @return true if either both are null or they pass identity handler
*/
function tuiNullableSame(a, b, handler) {
if (a === null) {
return b === null;
}
if (b === null) {
return false;
}
return handler(a, b);
}
/**
* Obfuscates a string by replacing certain characters with a symbol.
*
* @param value the input string to obfuscate
* @param symbol the symbol for obfuscation
* @return the obfuscated string
*
* The function determines which characters to obfuscate using a regular expression and the string's length:
* - 8 or more: show first 2 and last 2 characters
* - 4 to 7: show first and last character
* - less than 4: obfuscate all characters
* - obfuscates only alphanumeric characters
*/
function tuiObfuscate(value, symbol) {
if (!value) {
return value;
}
const match = /[\p{L}\p{N}]/gu;
let visible = 0;
let obfuscateIndexes = getObfuscateIndexes(value, match);
if (obfuscateIndexes.length >= 8) {
visible = 2;
}
else if (obfuscateIndexes.length >= 4) {
visible = 1;
}
obfuscateIndexes = obfuscateIndexes.slice(visible, obfuscateIndexes.length);
const lastIndex = (obfuscateIndexes.length) - visible;
obfuscateIndexes = obfuscateIndexes.slice(0, lastIndex < 0 ? 0 : lastIndex);
const result = value.split('');
obfuscateIndexes.forEach((index) => {
result[index] = symbol;
});
return result.join('');
}
function getObfuscateIndexes(value, match) {
if (!match) {
return Array.from({ length: value.length }).map((_, index) => index);
}
const obfuscateIndexes = [];
let matchResult;
let count = 0;
while ((matchResult = match.exec(value)) !== null && count < value.length) {
const start = matchResult.index;
const end = match.lastIndex - 1;
for (let i = start; i <= end; i++) {
obfuscateIndexes.push(i);
}
count++;
}
return obfuscateIndexes;
}
function tuiProvide(provide, useExisting, multi = false) {
return { provide, useExisting, multi };
}
function tuiProvideOptions(provide, options, fallback) {
return {
provide,
useFactory: () => ({
...(inject(provide, { optional: true, skipSelf: true }) || fallback),
...(inject(options, { optional: true }) || options),
}),
};
}
/// <reference types="@taiga-ui/tsconfig/ng-dev-mode" />
function decorateMethod(originalMethod) {
let previousArgs = [];
let originalFnWasCalledLeastAtOnce = false;
let pureValue;
return function tuiPureMethodPatched(...args) {
const isPure = originalFnWasCalledLeastAtOnce &&
previousArgs.length === args.length &&
args.every((arg, index) => arg === previousArgs[index]);
if (isPure) {
return pureValue;
}
previousArgs = args;
pureValue = originalMethod.apply(this, args);
originalFnWasCalledLeastAtOnce = true;
return pureValue;
};
}
function decorateGetter(originalGetter, propertyKey, enumerable = true) {
return function tuiPureGetterPatched() {
const value = originalGetter.call(this);
Object.defineProperty(this, propertyKey, { enumerable, value });
return value;
};
}
function tuiPure(target, propertyKeyOrContext, descriptor) {
if (typeof target === 'function') {
const context = propertyKeyOrContext;
if (context.kind === 'getter') {
return decorateGetter(target, context.name);
}
if (context.kind === 'method') {
return decorateMethod(target);
}
throw new TuiPureException();
}
// TODO(v5): drop compatibility for legacy "experimentalDecorators": true
const { get, enumerable, value } = descriptor;
const propertyKey = propertyKeyOrContext;
if (get) {
return {
configurable: true,
enumerable,
get: decorateGetter(get, propertyKey, enumerable),
};
}
if (typeof value !== 'function') {
throw new TuiPureException();
}
const original = value;
return {
configurable: true,
enumerable,
get() {
let previousArgs = [];
let originalFnWasCalledLeastAtOnce = false;
let pureValue;
const patched = (...args) => {
const isPure = originalFnWasCalledLeastAtOnce &&
previousArgs.length === args.length &&
args.every((arg, index) => arg === previousArgs[index]);
if (isPure) {
return pureValue;
}
previousArgs = args;
pureValue = original.apply(this, args);
originalFnWasCalledLeastAtOnce = true;
return pureValue;
};
Object.defineProperty(this, propertyKey, {
configurable: true,
value: patched,
});
return patched;
},
};
}
class TuiPureException extends Error {
constructor() {
super(ngDevMode ? 'tuiPure can only be used with functions or getters' : '');
}
}
/// <reference types="@taiga-ui/tsconfig/ng-dev-mode" />
/**
* Adds 'px' to the number and turns it into a string
*/
function tuiPx(value) {
ngDevMode && console.assert(Number.isFinite(value), 'Value must be finite number');
return `${value}px`;
}
function tuiUniqBy(array, key) {
return Array.from(array
.reduce((map, item) => (map.has(item[key]) ? map : map.set(item[key], item)), new Map())
.values());
}
const MAP = new InjectionToken(ngDevMode ? 'MAP' : '', {
factory: () => {
const map = new Map();
inject(DestroyRef).onDestroy(() => map.forEach((component) => component.destroy()));
return map;
},
});
function tuiWithStyles(component) {
const map = inject(MAP);
const environmentInjector = inject(EnvironmentInjector);
if (!map.has(component)) {
map.set(component, createComponent(component, { environmentInjector }));
}
return undefined;
}
/**
* Generated bundle index. Do not edit.
*/
export { TuiPureException, changeDateSeparator, tuiArrayRemove, tuiArrayShallowEquals, tuiArrayToggle, tuiCountFilledControls, tuiCreateToken, tuiCreateTokenFromFactory, tuiDefaultSort, tuiDirectiveBinding, tuiDirectiveListener, tuiDistanceBetweenTouches, tuiEaseInOutQuad, tuiFlatLength, tuiFontSizeWatcher, tuiGetOriginalArrayFromQueryList, tuiIsControlEmpty, tuiIsFalsy, tuiIsNumber, tuiIsObject, tuiIsPresent, tuiIsString, tuiIsValidUrl, tuiMarkControlAsTouchedAndValidate, tuiNullableSame, tuiObfuscate, tuiProvide, tuiProvideOptions, tuiPure, tuiPx, tuiUniqBy, tuiWithStyles };
//# sourceMappingURL=taiga-ui-cdk-utils-miscellaneous.mjs.map