@sd-angular/core
Version:
Sd Angular Core Lib
427 lines (419 loc) • 21.8 kB
JavaScript
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