UNPKG

carbon-components-angular

Version:
437 lines (423 loc) 18.6 kB
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