@taiga-ui/cdk
Version:
Base library for creating Angular components and applications using Taiga UI principles regarding of actual visual appearance
262 lines (245 loc) • 9.3 kB
JavaScript
import { tuiIsPresent } from '@taiga-ui/cdk/utils/miscellaneous';
import { inject, ElementRef, PLATFORM_ID, isSignal, signal, untracked, DestroyRef, effect, INJECTOR } from '@angular/core';
import { coerceElement } from '@angular/cdk/coercion';
import { isPlatformBrowser } from '@angular/common';
import { WA_WINDOW } from '@ng-web-apis/common';
import { toSignal } from '@angular/core/rxjs-interop';
import { fromEvent, startWith, map } from 'rxjs';
function tuiContainsOrAfter(current, node) {
try {
return (current.contains(node) ||
!!(node.compareDocumentPosition(current) & Node.DOCUMENT_POSITION_PRECEDING));
}
catch {
return false;
}
}
function tuiIsInput(element) {
return element.matches('input');
}
function tuiIsTextarea(element) {
return element.matches('textarea');
}
function tuiIsTextfield(element) {
return tuiIsInput(element) || tuiIsTextarea(element);
}
function tuiIsElement(node) {
return !!node && 'nodeType' in node && node.nodeType === Node.ELEMENT_NODE;
}
function tuiIsHTMLElement(node) {
const defaultView = node?.ownerDocument.defaultView;
return !!node && !!defaultView && node instanceof defaultView.HTMLElement;
}
function tuiIsTextNode(node) {
return node.nodeType === Node.TEXT_NODE;
}
function tuiIsInputEvent(event) {
return 'data' in event && 'inputType' in event;
}
/**
* Gets actual target from open Shadow DOM if event happened within it
*/
function tuiGetActualTarget(event) {
return event.composedPath()[0];
}
const DEFAULT_FORMAT = 'text/plain';
/**
* Gets text from data of clipboardEvent, it also works in IE and Edge browsers
*/
function tuiGetClipboardDataText(event, format = DEFAULT_FORMAT) {
return 'clipboardData' in event && event.clipboardData !== null
? event.clipboardData.getData(format) ||
event.clipboardData.getData(DEFAULT_FORMAT)
: event.target.ownerDocument.defaultView.clipboardData.getData('text');
}
function tuiGetDocumentOrShadowRoot(node) {
return 'getRootNode' in node && node.isConnected
? node.getRootNode()
: node.ownerDocument;
}
/**
* Returns array of Elements covering edges of given element or null if at least one edge middle point is visible
*
* CAUTION: Empty array means element if offscreen i.e. covered by no elements, rather than not covered
* ```ts
* function tuiGetElementObscures(element: Element): readonly [Element, Element, Element, Element] | [] | null
* ```
*/
function tuiGetElementObscures(element) {
if (!element.getBoundingClientRect) {
return null;
}
const { left, right, top, bottom, width, height } = element.getBoundingClientRect();
if (width === 0 && height === 0) {
return null;
}
const doc = tuiGetDocumentOrShadowRoot(element);
const horizontalMiddle = Math.round(left + width / 2);
const verticalMiddle = Math.round(top + height / 2);
const elements = [
doc.elementFromPoint(horizontalMiddle, Math.round(top) + 2),
doc.elementFromPoint(horizontalMiddle, Math.round(bottom) - 2),
doc.elementFromPoint(Math.round(left) + 2, verticalMiddle),
doc.elementFromPoint(Math.round(right) - 2, verticalMiddle),
].filter(tuiIsPresent);
if (!elements.length) {
return [];
}
const filtered = elements.filter((el) => !element.contains(el) && !el.contains(element));
return filtered.length === 4 ? filtered : null;
}
/**
* Calculates offset for an element relative to it's parent several levels above
*
* @param host parent element
* @param element
* @return object with offsetTop and offsetLeft number properties
*/
function tuiGetElementOffset(host, element) {
ngDevMode && console.assert(host.contains(element), 'Host must contain element');
let { offsetTop, offsetLeft, offsetParent } = element;
while (tuiIsHTMLElement(offsetParent) && offsetParent !== host) {
offsetTop += offsetParent.offsetTop;
offsetLeft += offsetParent.offsetLeft;
offsetParent = offsetParent.offsetParent;
}
return { offsetTop, offsetLeft };
}
/**
* @description:
* cross browser way to get selected text
*
* History:
* BUG - window.getSelection() fails when text selected in a form field
* https://bugzilla.mozilla.org/show_bug.cgi?id=85686
*/
function tuiGetSelectedText({ getSelection, document }) {
return document.activeElement && tuiIsTextfield(document.activeElement)
? document.activeElement.value.slice(document.activeElement.selectionStart || 0, document.activeElement.selectionEnd || 0)
: getSelection()?.toString() || null;
}
function tuiInjectElement() {
return inject(ElementRef).nativeElement;
}
function tuiIsElementEditable(element) {
return ((tuiIsTextfield(element) && !element.readOnly && element.inputMode !== 'none') ||
Boolean(element.isContentEditable));
}
function tuiPointToClientRect(x = 0, y = 0) {
const rect = {
x,
y,
left: x,
right: x,
top: y,
bottom: y,
width: 0,
height: 0,
};
return {
...rect,
toJSON: () => rect,
};
}
function tuiValue(input, injector = inject(INJECTOR)) {
const win = injector.get(WA_WINDOW);
if (!win.tuiInputPatched && isPlatformBrowser(injector.get(PLATFORM_ID))) {
win.tuiInputPatched = true;
patch(win.HTMLInputElement.prototype);
patch(win.HTMLTextAreaElement.prototype);
patch(win.HTMLSelectElement.prototype);
}
let element = isSignal(input) ? undefined : coerceElement(input);
let cleanup = () => { };
const options = { injector };
const value = signal(element?.value || '');
const process = (el) => {
const update = () => untracked(() => value.set(el.value));
el.addEventListener('input', update, { capture: true });
el.addEventListener('tui-input', update, { capture: true });
return () => {
el.removeEventListener('input', update, { capture: true });
el.removeEventListener('tui-input', update, { capture: true });
};
};
injector.get(DestroyRef).onDestroy(() => cleanup());
if (isSignal(input)) {
effect(() => {
element = coerceElement(input());
cleanup();
if (element && !element.matches('select[multiple]')) {
value.set(element.value);
cleanup = process(element);
}
}, options);
}
else if (element && !element.matches('select[multiple]')) {
cleanup = process(element);
}
effect(() => {
const v = value();
/**
* select[multiple] elements have value of first selected option,
* but there could be more, setting value resets other selected options
*/
if (element?.matches('select[multiple]')) {
return;
}
if (element?.matches(':focus') && 'selectionStart' in element) {
const { selectionStart, selectionEnd } = element;
/**
* After programmatic updates of input's value, caret is automatically placed at the end –
* revert to the previous position
*/
element.value = v;
try {
element.setSelectionRange(selectionStart, selectionEnd);
}
catch {
// Only certain types of inputs support setSelectionRange, so we need to catch the error for unsupported ones
}
}
else if (element) {
element.value = v;
}
}, options);
return value;
}
function patch(prototype) {
const { set } = Object.getOwnPropertyDescriptor(prototype, 'value');
Object.defineProperty(prototype, 'value', {
set(detail) {
const value = this.value;
const event = new CustomEvent('tui-input', { detail, bubbles: true });
set.call(this, detail);
if (value !== detail) {
this.dispatchEvent(event);
}
},
});
}
function tuiWindowSize(window) {
return toSignal(fromEvent(window, 'resize').pipe(startWith(null), map(() => {
const width = Math.max(window.document.documentElement.clientWidth || 0, window.innerWidth || 0, window.visualViewport?.width || 0);
const height = Math.max(window.document.documentElement.clientHeight || 0, window.innerHeight || 0, window.visualViewport?.height || 0);
const rect = {
width,
height,
top: 0,
left: 0,
right: width,
bottom: height,
x: 0,
y: 0,
};
return {
...rect,
toJSON: () => JSON.stringify(rect),
};
})), { requireSync: true });
}
/**
* Generated bundle index. Do not edit.
*/
export { tuiContainsOrAfter, tuiGetActualTarget, tuiGetClipboardDataText, tuiGetDocumentOrShadowRoot, tuiGetElementObscures, tuiGetElementOffset, tuiGetSelectedText, tuiInjectElement, tuiIsElement, tuiIsElementEditable, tuiIsHTMLElement, tuiIsInput, tuiIsInputEvent, tuiIsTextNode, tuiIsTextarea, tuiIsTextfield, tuiPointToClientRect, tuiValue, tuiWindowSize };
//# sourceMappingURL=taiga-ui-cdk-utils-dom.mjs.map