carbon-components-angular
Version:
Next generation components
437 lines (423 loc) • 18.6 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Optional, SkipSelf, NgZone, NgModule } from '@angular/core';
import { Subject, from, fromEvent, merge as merge$1, Subscription, Observable } from 'rxjs';
export { PLACEMENTS, Position, defaultPositions, position } from '@carbon/utils-position';
import { map } from 'rxjs/operators';
function findSiblingElem(target, direction) {
if (target[direction]) {
if (target[direction].classList.contains("disabled")) {
return findSiblingElem(target[direction], direction);
}
return target[direction];
}
}
function findNextElem(target) {
return findSiblingElem(target, "nextElementSibling");
}
function findPrevElem(target) {
return findSiblingElem(target, "previousElementSibling");
}
// check for Hight contrast mode
function HcModeChecker() {
let colorTest = "rgb(255, 0, 0)";
let htmlChecker = document.createElement("div");
htmlChecker.classList.add("hc-checker");
document.body.appendChild(htmlChecker);
if (window.getComputedStyle(htmlChecker).backgroundColor.toString() !== colorTest) {
document.body.classList.add("a11y");
}
document.body.removeChild(htmlChecker);
}
function focusNextTree(elem, rootElem = null) {
if (elem) {
let focusable = elem.querySelector("[tabindex='0']");
if (focusable) {
focusable.focus();
}
else {
focusNextElem(elem, rootElem);
}
}
}
function focusNextElem(elem, rootElem = null) {
if (elem) {
let nextElem = elem.nextElementSibling;
if (nextElem) {
let focusableElem = nextElem.querySelector("[tabindex='0']");
if (focusableElem) {
focusableElem.focus();
}
else {
focusNextElem(nextElem, rootElem);
}
}
else {
if (rootElem) {
let nextRootElem = rootElem.nextElementSibling;
if (nextRootElem) {
focusNextTree(nextRootElem, rootElem);
}
}
}
}
}
function focusPrevElem(elem, parentRef = null) {
if (elem) {
let prevElem = elem.previousElementSibling;
if (prevElem) {
let focusableElem = prevElem.querySelector("[tabindex='0']");
if (focusableElem) {
if (focusableElem.getAttribute("aria-expanded") === "true") {
let lastFocElms = prevElem.querySelectorAll("[tabindex='0']");
let arrLen = lastFocElms.length - 1;
for (let i = arrLen; i >= 0; i--) {
if (!!(lastFocElms[i].offsetWidth || lastFocElms[i].offsetHeight ||
lastFocElms[i].getClientRects().length)) {
focusableElem = lastFocElms[i];
break;
}
}
}
focusableElem.focus();
}
else {
focusPrevElem(prevElem, parentRef);
}
}
else {
if (parentRef) {
parentRef.querySelector("[tabindex='0']").focus();
}
}
}
}
class AnimationFrameServiceSingleton {
constructor(ngZone) {
this.ngZone = ngZone;
this.frameSource = new Subject();
this.tick = this.frameSource.asObservable();
this.ngZone.runOutsideAngular(() => {
this.animationFrameId = requestAnimationFrame(this.doTick.bind(this));
});
}
ngOnDestroy() {
cancelAnimationFrame(this.animationFrameId);
}
doTick(frame) {
this.frameSource.next(frame);
this.ngZone.runOutsideAngular(() => {
requestAnimationFrame(this.doTick.bind(this));
});
}
}
AnimationFrameServiceSingleton.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AnimationFrameServiceSingleton, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
AnimationFrameServiceSingleton.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AnimationFrameServiceSingleton });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AnimationFrameServiceSingleton, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: i0.NgZone }]; } });
class AnimationFrameService {
constructor(singleton) {
this.singleton = singleton;
this.tick = from(this.singleton.tick);
}
}
AnimationFrameService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AnimationFrameService, deps: [{ token: AnimationFrameServiceSingleton }], target: i0.ɵɵFactoryTarget.Injectable });
AnimationFrameService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AnimationFrameService });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: AnimationFrameService, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: AnimationFrameServiceSingleton }]; } });
// custom deep object merge
const merge = (target, ...objects) => {
for (const object of objects) {
for (const key in object) {
if (object.hasOwnProperty(key)) {
// since we're dealing just with JSON this simple check should be enough
if (object[key] instanceof Object) {
if (!target[key]) {
target[key] = {};
}
// recursively merge into the target
// most translations only run 3 or 4 levels deep, so no stack explosions
target[key] = merge(target[key], object[key]);
}
else {
target[key] = object[key];
}
}
}
}
return target;
};
/**
* Checks if a given element is scrollable.
* If the element has an overflow set as part of its computed style it can scroll.
* @param element the element to check scrollability
*/
const isScrollableElement = (element) => {
const computedStyle = getComputedStyle(element);
return (computedStyle.overflow === "auto" ||
computedStyle.overflow === "scroll" ||
computedStyle["overflow-y"] === "auto" ||
computedStyle["overflow-y"] === "scroll" ||
computedStyle["overflow-x"] === "auto" ||
computedStyle["overflow-x"] === "scroll");
};
/**
* Checks if an element is visible within a container
* @param element the element to check
* @param container the container to check
*/
const isVisibleInContainer = (element, container) => {
const elementRect = element.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
// If there exists `height: 100%` on the `html` or `body` tag of an application,
// it causes the calculation to return true if you need to scroll before the element is seen.
// In that case we calculate its visibility based on the window viewport.
if (container.tagName === "BODY" || container.tagName === "HTML") {
// This checks if element is within the top, bottom, left and right of viewport, ie. if the element is visible in
// the screen. This also takes into account partial visibility of an element.
const isAboveViewport = elementRect.top < 0 && (elementRect.top + element.clientHeight) < 0;
const isLeftOfViewport = elementRect.left < 0;
const isBelowViewport = (elementRect.bottom - element.clientHeight) > (window.innerHeight || document.documentElement.clientHeight);
const isRightOfViewport = elementRect.right > (window.innerWidth || document.documentElement.clientWidth);
const isVisibleInViewport = !(isAboveViewport || isBelowViewport || isLeftOfViewport || isRightOfViewport);
return isVisibleInViewport;
}
return (
// This also accounts for partial visibility. It will still return true if the element is partially visible inside the container.
(elementRect.bottom - element.clientHeight) <= (containerRect.bottom + (container.offsetHeight - container.clientHeight) / 2) &&
elementRect.top >= (-element.clientHeight));
};
const getScrollableParents = (node) => {
const elements = [document.body];
while (node.parentElement && node !== document.body) {
if (isScrollableElement(node)) {
elements.push(node);
}
node = node.parentElement;
}
return elements;
};
const hasScrollableParents = (node) => {
while (node.parentElement && node !== document.body) {
if (isScrollableElement(node)) {
return true;
}
node = node.parentElement;
}
return false;
};
/**
* Returns an observable that emits whenever any scrollable parent element scrolls
*
* @param node root element to start finding scrolling parents from
*/
const scrollableParentsObservable = (node) => {
const windowScroll = fromEvent(window, "scroll", { passive: true }).pipe(map(event =>
// update the event target to be something useful. In this case `body` is a sensible replacement
Object.assign({}, event, { target: document.body })));
let observables = [windowScroll];
// walk the parents and subscribe to all the scroll events we can
while (node.parentElement && node !== document.body) {
if (isScrollableElement(node)) {
observables.push(fromEvent(node, "scroll", { passive: true }));
}
node = node.parentElement;
}
return merge$1(...observables);
};
function clone(obj) {
return JSON.parse(JSON.stringify(obj));
}
function matchesAttr(el, attr, val) {
const styles = window.getComputedStyle(el);
return val.includes(styles[attr]);
}
function closestAttr(s, t, element) {
let el = element;
if (!element) {
return null;
}
do {
if (matchesAttr(el, s, t)) {
return el;
}
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
}
class ElementService {
constructor(singleton) {
this.singleton = singleton;
this.tick = from(this.singleton.tick);
}
visibility(target, parentElement = target) {
const scrollableParents = getScrollableParents(parentElement);
return this.tick.pipe(map(() => {
for (const parent of scrollableParents) {
if (!isVisibleInContainer(target, parent)) {
return {
visible: false
};
}
}
return {
visible: true
};
}));
}
}
ElementService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ElementService, deps: [{ token: AnimationFrameServiceSingleton }], target: i0.ɵɵFactoryTarget.Injectable });
ElementService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ElementService });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ElementService, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: AnimationFrameServiceSingleton }]; } });
const getEventObservable = (targetElement, eventType) => {
switch (eventType) {
case "scroll":
case "resize":
case "touchstart":
case "touchmove":
case "touchend":
return fromEvent(targetElement, eventType, { passive: true });
default:
return fromEvent(targetElement, eventType);
}
};
class DocumentService {
constructor() {
this.globalEvents = new Map();
this.documentRef = document;
this.subscriptions = new Subscription();
}
handleEvent(eventType, callback) {
if (!this.globalEvents.has(eventType)) {
if (this.documentRef) {
this.globalEvents.set(eventType, getEventObservable(this.documentRef, eventType));
}
else {
this.globalEvents.set(eventType, new Observable());
}
}
const observable = this.globalEvents.get(eventType);
this.subscriptions.add(observable.subscribe(callback));
}
handleClick(callback) {
this.handleEvent("click", callback);
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
this.globalEvents = null;
}
}
DocumentService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: DocumentService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
DocumentService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: DocumentService });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: DocumentService, decorators: [{
type: Injectable
}] });
class EventService {
constructor(documentService) {
this.documentService = documentService;
this.subscriptions = new Subscription();
this.targets = new WeakMap();
}
on(targetElement, eventType, callback) {
if (!this.targets.has(targetElement)) {
this.targets.set(targetElement, new Map());
}
const eventMap = this.targets.get(targetElement);
if (!eventMap.has(eventType)) {
eventMap.set(eventType, getEventObservable(targetElement, eventType));
}
const subscription = eventMap.get(eventType).subscribe(callback);
this.subscriptions.add(subscription);
}
onDocument(eventType, callback) {
this.documentService.handleEvent(eventType, callback);
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
}
EventService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: EventService, deps: [{ token: DocumentService }], target: i0.ɵɵFactoryTarget.Injectable });
EventService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: EventService });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: EventService, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: DocumentService }]; } });
// either provides a new instance of DocumentService, or returns the parent
function DOCUMENT_SERVICE_PROVIDER_FACTORY(parentService) {
return parentService || new DocumentService();
}
// DocumentService *must* be a singleton to ensure that we handle events and other document level settings once (and only once)
const DOCUMENT_SERVICE_PROVIDER = {
provide: DocumentService,
deps: [[new Optional(), new SkipSelf(), DocumentService]],
useFactory: DOCUMENT_SERVICE_PROVIDER_FACTORY
};
// either provides a new instance of AnimationFrameServiceSingleton, or returns the parent
function ANIMATION_FRAME_SERVICE_SINGLETON_PROVIDER_FACTORY(parentService, ngZone) {
return parentService || new AnimationFrameServiceSingleton(ngZone);
}
// AnimationFrameServiceSingleton is a singleton so we don't have a ton of duplicate RAFs firing (better for scheduling)
const ANIMATION_FRAME_SERVICE_SINGLETON_PROVIDER = {
provide: AnimationFrameServiceSingleton,
deps: [[new Optional(), new SkipSelf(), AnimationFrameServiceSingleton], NgZone],
useFactory: ANIMATION_FRAME_SERVICE_SINGLETON_PROVIDER_FACTORY
};
class UtilsModule {
}
UtilsModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: UtilsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
UtilsModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: UtilsModule });
UtilsModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: UtilsModule, providers: [
DOCUMENT_SERVICE_PROVIDER,
ANIMATION_FRAME_SERVICE_SINGLETON_PROVIDER,
AnimationFrameServiceSingleton,
DocumentService,
AnimationFrameService,
ElementService,
EventService
] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: UtilsModule, decorators: [{
type: NgModule,
args: [{
providers: [
DOCUMENT_SERVICE_PROVIDER,
ANIMATION_FRAME_SERVICE_SINGLETON_PROVIDER,
AnimationFrameServiceSingleton,
DocumentService,
AnimationFrameService,
ElementService,
EventService
]
}]
}] });
let _scrollbarWidth = -1;
function getScrollbarWidth() {
// lets not recreate this whole thing every time
if (_scrollbarWidth >= 0) {
return _scrollbarWidth;
}
// do the calculations the first time
const outer = document.createElement("div");
outer.style.visibility = "hidden";
outer.style.width = "100px";
outer.style["msOverflowStyle"] = "scrollbar"; // needed for WinJS apps
document.body.appendChild(outer);
const widthNoScroll = outer.offsetWidth;
// force scrollbars
outer.style.overflow = "scroll";
// add innerdiv
const inner = document.createElement("div");
inner.style.width = "100%";
outer.appendChild(inner);
const widthWithScroll = inner.offsetWidth;
// remove divs
outer.parentNode.removeChild(outer);
_scrollbarWidth = widthNoScroll - widthWithScroll;
return _scrollbarWidth;
}
/**
* Generated bundle index. Do not edit.
*/
export { ANIMATION_FRAME_SERVICE_SINGLETON_PROVIDER, ANIMATION_FRAME_SERVICE_SINGLETON_PROVIDER_FACTORY, AnimationFrameService, AnimationFrameServiceSingleton, DOCUMENT_SERVICE_PROVIDER, DOCUMENT_SERVICE_PROVIDER_FACTORY, DocumentService, ElementService, EventService, HcModeChecker, UtilsModule, clone, closestAttr, findNextElem, findPrevElem, focusNextElem, focusNextTree, focusPrevElem, getEventObservable, getScrollableParents, getScrollbarWidth, hasScrollableParents, isScrollableElement, isVisibleInContainer, merge, scrollableParentsObservable };
//# sourceMappingURL=carbon-components-angular-utils.mjs.map