@taiga-ui/cdk
Version:
Base library for creating Angular components and applications using Taiga UI principles regarding of actual visual appearance
187 lines (177 loc) • 10.4 kB
JavaScript
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
import { InjectionToken, inject, NgZone, PLATFORM_ID } from '@angular/core';
import { WA_WINDOW, WA_USER_AGENT, WA_NAVIGATOR } from '@ng-web-apis/common';
import { tuiTypedFromEvent, tuiZonefreeScheduler } from '@taiga-ui/cdk/observables';
import { tuiGetDocumentOrShadowRoot, tuiIsNativeMouseFocusable, tuiGetActualTarget } from '@taiga-ui/cdk/utils';
import { BehaviorSubject, switchMap, timer, map, startWith, share, merge, filter, takeUntil, repeat, withLatestFrom, of, take, distinctUntilChanged, fromEvent, shareReplay } from 'rxjs';
import { ɵAnimationEngine } from '@angular/animations/browser';
import { toSignal } from '@angular/core/rxjs-interop';
import { isIos } from '@ng-web-apis/platform';
import { TUI_FALSE_HANDLER } from '@taiga-ui/cdk/constants';
/**
* Element currently being removed by AnimationEngine
*/
const TUI_REMOVED_ELEMENT = new InjectionToken(ngDevMode ? 'TUI_REMOVED_ELEMENT' : '', {
factory: () => {
const stub = { onRemovalComplete: () => { } };
const element$ = new BehaviorSubject(null);
const engine = inject(ɵAnimationEngine, { optional: true }) || stub;
const { onRemovalComplete = stub.onRemovalComplete } = engine;
engine.onRemovalComplete = (element, context) => {
element$.next(element);
onRemovalComplete.call(engine, element, context);
};
return element$.pipe(switchMap((element) => timer(0).pipe(map(() => null), startWith(element))), share());
},
});
// Checks if focusout event should be considered leaving active zone
function isValidFocusout(target, removedElement = null) {
return (
// Not due to switching tabs/going to DevTools
tuiGetDocumentOrShadowRoot(target).activeElement !== target &&
// Not due to button/input becoming disabled or under disabled fieldset
!target.matches(':disabled') &&
// Not due to element being removed from DOM
!removedElement?.contains(target) &&
// Not due to scrollable element became non-scrollable
tuiIsNativeMouseFocusable(target));
}
function shadowRootActiveElement(root) {
return merge(tuiTypedFromEvent(root, 'focusin').pipe(map(({ target }) => target)), tuiTypedFromEvent(root, 'focusout').pipe(filter(({ target, relatedTarget }) => !!relatedTarget && isValidFocusout(target)), map(({ relatedTarget }) => relatedTarget)));
}
/**
* Active element on the document for ActiveZone
*/
const TUI_ACTIVE_ELEMENT = new InjectionToken(ngDevMode ? 'TUI_ACTIVE_ELEMENT' : '', {
factory: () => {
const removedElement$ = inject(TUI_REMOVED_ELEMENT);
const win = inject(WA_WINDOW);
const doc = inject(DOCUMENT);
const zone = inject(NgZone);
const focusout$ = tuiTypedFromEvent(win, 'focusout', { capture: true });
const focusin$ = tuiTypedFromEvent(win, 'focusin', { capture: true });
const blur$ = tuiTypedFromEvent(win, 'blur');
const mousedown$ = tuiTypedFromEvent(win, 'mousedown');
const mouseup$ = tuiTypedFromEvent(win, 'mouseup');
return merge(focusout$.pipe(takeUntil(mousedown$), repeat({ delay: () => mouseup$ }), withLatestFrom(removedElement$), filter(([event, removedElement]) => isValidFocusout(tuiGetActualTarget(event), removedElement)), map(([{ relatedTarget }]) => relatedTarget)), blur$.pipe(map(() => doc.activeElement), filter((element) => !!element?.matches('iframe'))), focusin$.pipe(switchMap((event) => {
const target = tuiGetActualTarget(event);
const root = tuiGetDocumentOrShadowRoot(target) || doc;
return root === doc
? of(target)
: shadowRootActiveElement(root).pipe(startWith(target));
})), mousedown$.pipe(switchMap((event) => {
const actualTargetInCurrentTime = tuiGetActualTarget(event);
return !doc.activeElement || doc.activeElement === doc.body
? of(actualTargetInCurrentTime)
: focusout$.pipe(take(1), map(
/**
* Do not use `map(() => tuiGetActualTarget(event))`
* because we have different result in runtime
*/
() => actualTargetInCurrentTime), takeUntil(timer(0, tuiZonefreeScheduler(zone))));
}))).pipe(distinctUntilChanged(), share());
},
});
const TUI_BASE_HREF = new InjectionToken(ngDevMode ? 'TUI_BASE_HREF' : '', {
factory: () => inject(DOCUMENT).querySelector('base')?.href ?? '',
});
// https://stackoverflow.com/a/11381730/2706426 http://detectmobilebrowsers.com/
const firstRegex = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series([46])0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/;
const secondRegex = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br([ev])w|bumb|bw-([nu])|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do([cp])o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly([-_])|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-([mpt])|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c([- _agpst])|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac([ \-/])|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja([tv])a|jbro|jemu|jigs|kddi|keji|kgt([ /])|klon|kpt |kwc-|kyo([ck])|le(no|xi)|lg( g|\/([klu])|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t([- ov])|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30([02])|n50([025])|n7(0([01])|10)|ne(([cm])-|on|tf|wf|wg|wt)|nok([6i])|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan([adt])|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c([-01])|47|mc|nd|ri)|sgh-|shar|sie([-m])|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel([im])|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c([- ])|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/;
const TUI_IS_MOBILE = new InjectionToken(ngDevMode ? 'TUI_IS_MOBILE' : '', {
factory: () => firstRegex.test(inject(WA_USER_AGENT).toLowerCase()) ||
secondRegex.test(inject(WA_USER_AGENT).slice(0, 4).toLowerCase()),
});
const TUI_IS_IOS = new InjectionToken(ngDevMode ? 'TUI_IS_IOS' : '', {
factory: () => isIos(inject(WA_NAVIGATOR)),
});
const TUI_IS_ANDROID = new InjectionToken(ngDevMode ? 'TUI_IS_ANDROID' : '', {
factory: () => inject(TUI_IS_MOBILE) && !inject(TUI_IS_IOS),
});
const TUI_IS_WEBKIT = new InjectionToken(ngDevMode ? 'TUI_IS_WEBKIT' : '', {
factory: () => !!inject(WA_WINDOW)?.webkitConvertPointFromNodeToPage,
});
const TUI_PLATFORM = new InjectionToken(ngDevMode ? 'TUI_PLATFORM' : '', {
factory: () => {
if (inject(TUI_IS_IOS)) {
return 'ios';
}
return inject(TUI_IS_ANDROID) ? 'android' : 'web';
},
});
const TUI_IS_TOUCH = new InjectionToken(ngDevMode ? 'TUI_IS_TOUCH' : '', {
factory: () => {
const media = inject(WA_WINDOW).matchMedia('(pointer: coarse)');
return toSignal(fromEvent(media, 'change').pipe(map(() => media.matches)), {
initialValue: media.matches,
});
},
});
/**
* @deprecated: use only {@link TUI_IS_E2E}
* Detect if app is running under Cypress
* {@link https://docs.cypress.io/faq/questions/using-cypress-faq#Is-there-any-way-to-detect-if-my-app-is-running-under-Cypress Cypress docs}
* TODO: remove in v5
*/
const TUI_IS_CYPRESS = new InjectionToken(ngDevMode ? 'TUI_IS_CYPRESS' : '', {
factory: () => !!inject(WA_WINDOW).Cypress,
});
/**
* @deprecated: use only {@link TUI_IS_E2E}
* Manually provide `true` when running tests in Playwright
* TODO: remove in v5
*/
const TUI_IS_PLAYWRIGHT = new InjectionToken(ngDevMode ? 'TUI_IS_PLAYWRIGHT' : '', {
factory: TUI_FALSE_HANDLER,
});
/**
* Detect if app is running under any of test frameworks
*/
const TUI_IS_E2E = new InjectionToken(ngDevMode ? 'TUI_IS_E2E' : '', {
factory: () => inject(TUI_IS_CYPRESS) ||
inject(TUI_IS_PLAYWRIGHT) ||
inject(WA_NAVIGATOR).webdriver,
});
const TUI_FALLBACK_VALUE = new InjectionToken(ngDevMode ? 'TUI_FALLBACK_VALUE' : '', {
factory: () => null,
});
function tuiFallbackValueProvider(useValue) {
return {
provide: TUI_FALLBACK_VALUE,
useValue,
};
}
/**
* SSR safe default empty Range
*/
const TUI_RANGE = new InjectionToken(ngDevMode ? 'TUI_RANGE' : '', {
factory: () => isPlatformBrowser(inject(PLATFORM_ID)) ? new Range() : {},
});
const TUI_WINDOW_SIZE = new InjectionToken(ngDevMode ? 'TUI_WINDOW_SIZE' : '', {
factory: () => {
const w = inject(WA_WINDOW);
return tuiTypedFromEvent(w, 'resize').pipe(startWith(null), map(() => {
const width = Math.max(w.document.documentElement.clientWidth || 0, w.innerWidth || 0, w.visualViewport?.width || 0);
const height = Math.max(w.document.documentElement.clientHeight || 0, w.innerHeight || 0, w.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),
};
}), shareReplay({ bufferSize: 1, refCount: true }));
},
});
/**
* Generated bundle index. Do not edit.
*/
export { TUI_ACTIVE_ELEMENT, TUI_BASE_HREF, TUI_FALLBACK_VALUE, TUI_IS_ANDROID, TUI_IS_CYPRESS, TUI_IS_E2E, TUI_IS_IOS, TUI_IS_MOBILE, TUI_IS_PLAYWRIGHT, TUI_IS_TOUCH, TUI_IS_WEBKIT, TUI_PLATFORM, TUI_RANGE, TUI_REMOVED_ELEMENT, TUI_WINDOW_SIZE, tuiFallbackValueProvider };
//# sourceMappingURL=taiga-ui-cdk-tokens.mjs.map