UNPKG

@sd-angular/core

Version:

Sd Angular Core Lib

427 lines (419 loc) 21.8 kB
import { Directive, TemplateRef, EventEmitter, Component, ViewEncapsulation, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild, ContentChild, Input, Output, HostListener, ElementRef, NgZone, ViewContainerRef, Optional, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { __classPrivateFieldGet, __classPrivateFieldSet } from 'tslib'; import { v4 } from 'uuid'; import { DeviceDetectorService } from 'ngx-device-detector'; import { BehaviorSubject, Subscription, Subject, merge, fromEvent } from 'rxjs'; import { Directionality } from '@angular/cdk/bidi'; import { OverlayConfig, Overlay } from '@angular/cdk/overlay'; import { Platform } from '@angular/cdk/platform'; import { TemplatePortal } from '@angular/cdk/portal'; import { mapTo, filter, debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators'; import { A11yModule } from '@angular/cdk/a11y'; class PopoverContentDirective { constructor(templateRef) { this.templateRef = templateRef; } } PopoverContentDirective.decorators = [ { type: Directive, args: [{ selector: '[sdPopoverContent]' },] } ]; PopoverContentDirective.ctorParameters = () => [ { type: TemplateRef } ]; var _previousPanelClass, _xPosition; class PopoverComponent { constructor(cdRef, deviceService) { this.cdRef = cdRef; this.deviceService = deviceService; this.classList = {}; this.panelId = `sd-popover-panel-${v4()}`; this.isMobileOrTablet = false; this.mouseState$ = new BehaviorSubject(false); this.trigger = null; _previousPanelClass.set(this, void 0); this.width = 'sm'; this.height = 'auto'; this.type = 'normal'; _xPosition.set(this, void 0); this.closed = new EventEmitter(); this.opened = new EventEmitter(); this.close = () => { this.mouseState$.next(false); this.closed.emit(); }; this.setPositionClasses = (pos = this.position) => { var _a; const classes = this.classList; classes['sd-popover-above'] = pos === 'above'; classes['sd-popover-below'] = pos === 'below'; (_a = this.cdRef) === null || _a === void 0 ? void 0 : _a.markForCheck(); }; this.addArrowTranslateX = (offset) => { const arrowElement = document.querySelector(`#${this.panelId} .sd-popover-arrow`); if (arrowElement && offset) { try { const style = window.getComputedStyle(arrowElement); const matrix = new WebKitCSSMatrix(style.transform); const translateX = matrix.m41 + offset; const translateY = matrix.m42; arrowElement.style.transform = `translateX(${translateX}px) translateY(${translateY}px)`; } catch (error) { } } }; this.setMouseState = (visible) => { if (!visible) { // chỉ được tắt khi đang không focus element nào bên trong panel // tránh lỗi có control mở overlay khiến cho mất mouseenter ở panel const activeElement = document.activeElement; const panelElement = this.panel.nativeElement; if (panelElement === null || panelElement === void 0 ? void 0 : panelElement.contains(activeElement)) { return; } } this.mouseState$.next(visible); }; this.isMobileOrTablet = !deviceService.isDesktop(); } set panelClass(classes) { const previousPanelClass = __classPrivateFieldGet(this, _previousPanelClass); if (previousPanelClass && previousPanelClass.length) { previousPanelClass.split(' ').forEach((className) => { this.classList[className] = false; }); } __classPrivateFieldSet(this, _previousPanelClass, classes); if (classes && classes.length) { classes.split(' ').forEach((className) => { this.classList[className] = true; }); } } ; get position() { return __classPrivateFieldGet(this, _xPosition); } set position(value) { __classPrivateFieldSet(this, _xPosition, value); this.setPositionClasses(); } _hostClick(targetElement) { var _a; if ((_a = this.panel) === null || _a === void 0 ? void 0 : _a.nativeElement) { const isInside = this.panel.nativeElement.contains(targetElement); if (!isInside) { this.setMouseState(false); } } } ngOnChanges(changes) { if (changes.type) { const type = changes.type.currentValue; const preType = changes.type.previousValue; if (preType) { this.classList[`sd-popover--${preType}`] = false; } if (type) { this.classList[`sd-popover--${type}`] = true; } } } ngOnInit() { this.setPositionClasses(); this.width = this.width || '80vw'; if (!this.isMobileOrTablet) { switch (this.width) { case 'lg': this.width = '80vw'; break; case 'md': this.width = '60vw'; break; case 'sm': this.width = '40vw'; break; } } } ngOnDestroy() { this.closed.complete(); this.opened.complete(); this.mouseState$.complete(); } } _previousPanelClass = new WeakMap(), _xPosition = new WeakMap(); PopoverComponent.decorators = [ { type: Component, args: [{ selector: 'sd-popover', template: "<ng-template let-data=\"data\">\r\n <div\r\n class=\"sd-popover-panel mat-elevation-z6\"\r\n [id]=\"panelId\"\r\n [ngClass]=\"classList\"\r\n tabindex=\"-1\"\r\n role=\"menu\"\r\n (mouseenter)=\"setMouseState(true)\"\r\n (mouseleave)=\"setMouseState(false)\"\r\n cdkTrapFocus\r\n #panel\r\n >\r\n <div class=\"sd-popover-content\">\r\n <ng-container *ngIf=\"popoverContent?.templateRef\">\r\n <ng-container *ngTemplateOutlet=\"popoverContent.templateRef; context:{data: data}\"></ng-container>\r\n </ng-container>\r\n <ng-content></ng-content>\r\n </div>\r\n <div class=\"sd-popover-arrow\"></div>\r\n </div>\r\n</ng-template>", encapsulation: ViewEncapsulation.None, exportAs: 'sdPopover', changeDetection: ChangeDetectionStrategy.OnPush, styles: [".text-black400{color:#757575}.sd-popover-panel{background:#fff;height:100%;position:relative;width:100%}.sd-popover-panel .sd-popover-arrow{clear:both;height:0;left:50%;position:absolute;width:0}.sd-popover-panel.sd-popover-above .sd-popover-arrow{border-color:#fff transparent transparent;border-style:solid;border-width:12px 12px 0;top:100%;transform:translateX(-6px) translateY(-1px)}.sd-popover-panel.sd-popover-above:after{content:\" \";display:block;height:16px;position:absolute;width:100%}.sd-popover-panel.sd-popover-below .sd-popover-arrow{border-color:transparent transparent #fff;border-style:solid;border-width:0 12px 12px;bottom:100%;transform:translateX(-6px) translateY(1px)}.sd-popover-panel.sd-popover-below:before{content:\" \";display:block;height:16px;position:absolute;width:100%}.sd-popover-panel .sd-popover-content{height:100%}.sd-popover--normal{background:#fff;color:#000}.sd-popover--normal.sd-popover-above .sd-popover-arrow{border-color:#fff transparent transparent;border-style:solid;border-width:12px 12px 0;top:100%}.sd-popover--normal.sd-popover-below .sd-popover-arrow{border-color:transparent transparent #fff;border-style:solid;border-width:0 12px 12px;bottom:100%}.sd-popover--primary{background:#e7e9ff;color:#2962ff}.sd-popover--primary.sd-popover-above .sd-popover-arrow{border-color:#e7e9ff transparent transparent;border-style:solid;border-width:12px 12px 0;top:100%}.sd-popover--primary.sd-popover-below .sd-popover-arrow{border-color:transparent transparent #e7e9ff;border-style:solid;border-width:0 12px 12px;bottom:100%}.sd-popover--info{background:#e7e9ff;color:#2962ff}.sd-popover--info.sd-popover-above .sd-popover-arrow{border-color:#e7e9ff transparent transparent;border-style:solid;border-width:12px 12px 0;top:100%}.sd-popover--info.sd-popover-below .sd-popover-arrow{border-color:transparent transparent #e7e9ff;border-style:solid;border-width:0 12px 12px;bottom:100%}.sd-popover--success{background:#dbefdc;color:#4caf50}.sd-popover--success.sd-popover-above .sd-popover-arrow{border-color:#dbefdc transparent transparent;border-style:solid;border-width:12px 12px 0;top:100%}.sd-popover--success.sd-popover-below .sd-popover-arrow{border-color:transparent transparent #dbefdc;border-style:solid;border-width:0 12px 12px;bottom:100%}.sd-popover--warning{background:#ffeacc;color:#ff9600}.sd-popover--warning.sd-popover-above .sd-popover-arrow{border-color:#ffeacc transparent transparent;border-style:solid;border-width:12px 12px 0;top:100%}.sd-popover--warning.sd-popover-below .sd-popover-arrow{border-color:transparent transparent #ffeacc;border-style:solid;border-width:0 12px 12px;bottom:100%}.sd-popover--danger{background:#fed5d0;color:#f82c13}.sd-popover--danger.sd-popover-above .sd-popover-arrow{border-color:#fed5d0 transparent transparent;border-style:solid;border-width:12px 12px 0;top:100%}.sd-popover--danger.sd-popover-below .sd-popover-arrow{border-color:transparent transparent #fed5d0;border-style:solid;border-width:0 12px 12px;bottom:100%}.sd-popover-content{padding:16px}"] },] } ]; PopoverComponent.ctorParameters = () => [ { type: ChangeDetectorRef }, { type: DeviceDetectorService } ]; PopoverComponent.propDecorators = { templateRef: [{ type: ViewChild, args: [TemplateRef,] }], panel: [{ type: ViewChild, args: ['panel',] }], popoverContent: [{ type: ContentChild, args: [PopoverContentDirective,] }], panelClass: [{ type: Input, args: ['class',] }], width: [{ type: Input }], height: [{ type: Input }], type: [{ type: Input }], position: [{ type: Input }], closed: [{ type: Output }], opened: [{ type: Output }], _hostClick: [{ type: HostListener, args: ['document:click', ['$event.target'],] }] }; var _overlayRef, _popoverClose$, _portal, _destroy$, _popover, _setIsPopoverOpen, _getPortal, _createOverlay, _setPosition, _getOverlayConfig, _subscribeToPositions, _destroyPopover; class PopoverTriggerDirective { constructor(_overlay, _element, _ngZone, viewContainerRef, elementRef, platform, _dir) { this._overlay = _overlay; this._element = _element; this._ngZone = _ngZone; this.viewContainerRef = viewContainerRef; this.elementRef = elementRef; this.platform = platform; this._dir = _dir; _overlayRef.set(this, null); _popoverClose$.set(this, Subscription.EMPTY); _portal.set(this, void 0); _destroy$.set(this, new Subject()); this.popoverOpen = false; _popover.set(this, void 0); this.disabled = false; this.popoverOpened = new EventEmitter(); this.popoverClosed = new EventEmitter(); this.openPopover = () => { if (this.popoverOpen) { return; } const overlayRef = __classPrivateFieldGet(this, _createOverlay).call(this); const overlayConfig = overlayRef.getConfig(); const positionStrategy = overlayConfig.positionStrategy; __classPrivateFieldGet(this, _setPosition).call(this, positionStrategy); overlayConfig.hasBackdrop = false; overlayRef.attach(__classPrivateFieldGet(this, _getPortal).call(this)); this.popover.trigger = this; __classPrivateFieldGet(this, _setIsPopoverOpen).call(this, true); this.popover.opened.next(this.popoverData); }; this.closePopover = () => { this.popover.closed.emit(); }; _setIsPopoverOpen.set(this, (isOpen) => { this.popoverOpen = isOpen; this.popoverOpen ? this.popoverOpened.emit() : this.popoverClosed.emit(); }); _getPortal.set(this, () => { var _a; if (!__classPrivateFieldGet(this, _portal) || __classPrivateFieldGet(this, _portal).templateRef !== this.popover.templateRef || ((_a = __classPrivateFieldGet(this, _portal).context) === null || _a === void 0 ? void 0 : _a.data) !== this.popoverData) { __classPrivateFieldSet(this, _portal, new TemplatePortal(this.popover.templateRef, this.viewContainerRef, { data: this.popoverData })); } return __classPrivateFieldGet(this, _portal); }); _createOverlay.set(this, () => { if (!__classPrivateFieldGet(this, _overlayRef)) { const config = __classPrivateFieldGet(this, _getOverlayConfig).call(this); __classPrivateFieldGet(this, _subscribeToPositions).call(this, config.positionStrategy); __classPrivateFieldSet(this, _overlayRef, this._overlay.create(config)); } return __classPrivateFieldGet(this, _overlayRef); }); _setPosition.set(this, (positionStrategy) => { let originX = "center"; let originY = "top"; let overlayX = "center"; let overlayY = "bottom"; let offsetX = 0; let offsetY = 16; switch (this.popover.position) { case "above": originY = "top"; overlayY = "bottom"; break; case "below": originY = "bottom"; overlayY = "top"; break; } positionStrategy.withPositions([ // theo input { originX, originY, overlayX, overlayY, offsetY: originY == "top" ? -offsetY : offsetY }, // giữa trên { originX: "center", originY: "top", overlayX: "center", overlayY: "bottom", offsetX, offsetY: -offsetY, }, // giữa dưới { originX: "center", originY: "bottom", overlayX: "center", overlayY: "top", offsetX, offsetY, }, ]); }); _getOverlayConfig.set(this, () => { return new OverlayConfig({ positionStrategy: this._overlay .position() .flexibleConnectedTo(this._element) .withLockedPosition() .withGrowAfterOpen() .withTransformOriginOn(".sd-popover-panel"), backdropClass: "cdk-overlay-transparent-backdrop", panelClass: this.popover.panelClass, direction: this._dir, width: this.popover.width, height: this.popover.height, }); }); _subscribeToPositions.set(this, (position) => { if (this.popover.setPositionClasses) { position.positionChanges.subscribe((change) => { var _a; const pos = change.connectionPair.overlayY === "top" ? "below" : "above"; if (this._ngZone) { this._ngZone.run(() => this.popover.setPositionClasses(pos)); } else { this.popover.setPositionClasses(pos); } const originElement = this.elementRef.nativeElement; const overlayElement = (_a = this.viewContainerRef.get(0)) === null || _a === void 0 ? void 0 : _a.rootNodes[0]; if (originElement && overlayElement) { const originRect = originElement.getBoundingClientRect(); const overlayRect = overlayElement.getBoundingClientRect(); const originLeft = originRect.left + originRect.width / 2; const overlayLeft = overlayRect.left + overlayRect.width / 2; const offset = originLeft - overlayLeft; this.popover.addArrowTranslateX(offset); } }); } }); _destroyPopover.set(this, () => { if (!__classPrivateFieldGet(this, _overlayRef) || !this.popoverOpen) { return; } __classPrivateFieldGet(this, _overlayRef).detach(); __classPrivateFieldGet(this, _setIsPopoverOpen).call(this, false); }); } get popover() { return __classPrivateFieldGet(this, _popover); } set popover(popover) { if (!popover || popover === __classPrivateFieldGet(this, _popover)) { return; } __classPrivateFieldSet(this, _popover, popover); __classPrivateFieldGet(this, _popoverClose$).unsubscribe(); if (popover) { __classPrivateFieldSet(this, _popoverClose$, popover.closed.subscribe(() => { __classPrivateFieldGet(this, _destroyPopover).call(this); })); } } ngAfterViewInit() { if (this.popover) { const nativeElement = this.elementRef.nativeElement; const hostMouseState$ = merge(fromEvent(nativeElement, "mouseenter").pipe(mapTo(true)), fromEvent(nativeElement, "mouseleave").pipe(mapTo(false))); const popoverMouseState$ = this.popover.mouseState$.pipe( // chỉ lấy luồng emit open bởi chính trigger (tránh lỗi nhiều trigger cho một popover) filter((v) => !this.popover.trigger || this.popover.trigger == this)); const mergedMouseState$ = merge(popoverMouseState$, hostMouseState$); mergedMouseState$ .pipe(debounceTime(100), distinctUntilChanged(), filter(() => this.platform.isBrowser), takeUntil(__classPrivateFieldGet(this, _destroy$))) .subscribe((visible) => { if (visible && !this.disabled) { this.openPopover(); } else { this.closePopover(); } }); } } ngOnDestroy() { __classPrivateFieldGet(this, _destroy$).next(); __classPrivateFieldGet(this, _destroy$).complete(); if (__classPrivateFieldGet(this, _overlayRef)) { __classPrivateFieldGet(this, _overlayRef).dispose(); __classPrivateFieldSet(this, _overlayRef, null); } } } _overlayRef = new WeakMap(), _popoverClose$ = new WeakMap(), _portal = new WeakMap(), _destroy$ = new WeakMap(), _popover = new WeakMap(), _setIsPopoverOpen = new WeakMap(), _getPortal = new WeakMap(), _createOverlay = new WeakMap(), _setPosition = new WeakMap(), _getOverlayConfig = new WeakMap(), _subscribeToPositions = new WeakMap(), _destroyPopover = new WeakMap(); PopoverTriggerDirective.decorators = [ { type: Directive, args: [{ selector: "[sdPopoverTriggerFor]", host: { "aria-haspopup": "true", class: "sd-popover-trigger", }, exportAs: "sdPopoverTrigger", },] } ]; PopoverTriggerDirective.ctorParameters = () => [ { type: Overlay }, { type: ElementRef }, { type: NgZone }, { type: ViewContainerRef }, { type: ElementRef }, { type: Platform }, { type: Directionality, decorators: [{ type: Optional }] } ]; PopoverTriggerDirective.propDecorators = { popover: [{ type: Input, args: ["sdPopoverTriggerFor",] }], popoverData: [{ type: Input, args: ["sdPopoverData",] }], disabled: [{ type: Input, args: ["sdPopoverDisabled",] }], popoverOpened: [{ type: Output }], popoverClosed: [{ type: Output }] }; class SdPopoverModule { } SdPopoverModule.decorators = [ { type: NgModule, args: [{ declarations: [ PopoverComponent, PopoverTriggerDirective, PopoverContentDirective ], imports: [ CommonModule, A11yModule ], exports: [ PopoverComponent, PopoverTriggerDirective, PopoverContentDirective ] },] } ]; /* * Public API Surface of superdev-angular-core */ /** * Generated bundle index. Do not edit. */ export { PopoverComponent, PopoverContentDirective, PopoverTriggerDirective, SdPopoverModule }; //# sourceMappingURL=sd-angular-core-popover.js.map