@ng-matero/extensions
Version:
Angular Material Extensions
842 lines (836 loc) • 58.6 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, Directive, Inject, EventEmitter, booleanAttribute, TemplateRef, Component, ChangeDetectionStrategy, ViewEncapsulation, Input, Output, ViewChild, ContentChild, inject, Optional, NgModule } from '@angular/core';
import { DOCUMENT, CommonModule } from '@angular/common';
import * as i1 from '@angular/cdk/overlay';
import { Overlay, OverlayConfig, OverlayModule } from '@angular/cdk/overlay';
import * as i3 from '@angular/cdk/a11y';
import { CdkTrapFocus, isFakeMousedownFromScreenReader, A11yModule } from '@angular/cdk/a11y';
import { ESCAPE, hasModifierKey, ENTER, SPACE } from '@angular/cdk/keycodes';
import { Subject, Subscription, of, merge } from 'rxjs';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { TemplatePortal, DomPortalOutlet } from '@angular/cdk/portal';
import { filter, take, takeUntil } from 'rxjs/operators';
import * as i2 from '@angular/cdk/bidi';
/**
* Below are all the animations for the mtx-popover component.
* Animation duration and timing values are based on AngularJS Material.
*/
/**
* This animation controls the popover panel's entry and exit from the page.
*
* When the popover panel is added to the DOM, it scales in and fades in its border.
*
* When the popover panel is removed from the DOM, it simply fades out after a brief
* delay to display the ripple.
*/
const transformPopover = trigger('transformPopover', [
state('void', style({
opacity: 0,
transform: 'scale(0.8)',
})),
transition('void => enter', animate('120ms cubic-bezier(0, 0, 0.2, 1)', style({
opacity: 1,
transform: 'scale(1)',
}))),
transition('* => void', animate('100ms 25ms linear', style({ opacity: 0 }))),
]);
/**
* Injection token that can be used to reference instances of `MtxPopoverContent`. It serves
* as alternative token to the actual `MtxPopoverContent` class which could cause unnecessary
* retention of the class and its directive metadata.
*/
const MTX_POPOVER_CONTENT = new InjectionToken('MtxPopoverContent');
class _MtxPopoverContentBase {
constructor(_template, _componentFactoryResolver, _appRef, _injector, _viewContainerRef, _document, _changeDetectorRef) {
this._template = _template;
this._componentFactoryResolver = _componentFactoryResolver;
this._appRef = _appRef;
this._injector = _injector;
this._viewContainerRef = _viewContainerRef;
this._document = _document;
this._changeDetectorRef = _changeDetectorRef;
/** Emits when the popover content has been attached. */
this._attached = new Subject();
}
/**
* Attaches the content with a particular context.
* @docs-private
*/
attach(context = {}) {
if (!this._portal) {
this._portal = new TemplatePortal(this._template, this._viewContainerRef);
}
this.detach();
if (!this._outlet) {
this._outlet = new DomPortalOutlet(this._document.createElement('div'), this._componentFactoryResolver, this._appRef, this._injector);
}
const element = this._template.elementRef.nativeElement;
// Because we support opening the same popover from different triggers (which in turn have their
// own `OverlayRef` panel), we have to re-insert the host element every time, otherwise we
// risk it staying attached to a pane that's no longer in the DOM.
element.parentNode.insertBefore(this._outlet.outletElement, element);
// When `MtxPopoverContent` is used in an `OnPush` component, the insertion of the popover
// content via `createEmbeddedView` does not cause the content to be seen as "dirty"
// by Angular. This causes the `@ContentChildren` for popover items within the popover to
// not be updated by Angular. By explicitly marking for check here, we tell Angular that
// it needs to check for new popover items and update the `@ContentChild` in `MtxPopover`.
// @breaking-change 9.0.0 Make change detector ref required
if (this._changeDetectorRef) {
this._changeDetectorRef.markForCheck();
}
this._portal.attach(this._outlet, context);
this._attached.next();
}
/**
* Detaches the content.
* @docs-private
*/
detach() {
if (this._portal.isAttached) {
this._portal.detach();
}
}
ngOnDestroy() {
if (this._outlet) {
this._outlet.dispose();
}
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.0", ngImport: i0, type: _MtxPopoverContentBase, deps: [{ token: i0.TemplateRef }, { token: i0.ComponentFactoryResolver }, { token: i0.ApplicationRef }, { token: i0.Injector }, { token: i0.ViewContainerRef }, { token: DOCUMENT }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.0", type: _MtxPopoverContentBase, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.0", ngImport: i0, type: _MtxPopoverContentBase, decorators: [{
type: Directive
}], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ComponentFactoryResolver }, { type: i0.ApplicationRef }, { type: i0.Injector }, { type: i0.ViewContainerRef }, { type: undefined, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: i0.ChangeDetectorRef }] });
/**
* Popover content that will be rendered lazily once the popover is opened.
*/
class MtxPopoverContent extends _MtxPopoverContentBase {
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.0", ngImport: i0, type: MtxPopoverContent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.0", type: MtxPopoverContent, isStandalone: true, selector: "ng-template[mtxPopoverContent]", providers: [{ provide: MTX_POPOVER_CONTENT, useExisting: MtxPopoverContent }], usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.0", ngImport: i0, type: MtxPopoverContent, decorators: [{
type: Directive,
args: [{
selector: 'ng-template[mtxPopoverContent]',
providers: [{ provide: MTX_POPOVER_CONTENT, useExisting: MtxPopoverContent }],
standalone: true,
}]
}] });
/**
* Throws an exception for the case when popover trigger doesn't have a valid mtx-popover instance
*/
function throwMtxPopoverMissingError() {
throw Error(`mtx-popover-trigger: must pass in an mtx-popover instance.
Example:
<mtx-popover #popover="mtxPopover"></mtx-popover>
<button [mtxPopoverTriggerFor]="popover"></button>`);
}
/**
* Throws an exception for the case when popover's mtxPopoverPosition[0] value isn't valid.
* In other words, it doesn't match 'above', 'below', 'before' or 'after'.
*/
function throwMtxPopoverInvalidPositionStart() {
throw Error(`mtxPopoverPosition[0] value must be either 'above', 'below', 'before' or 'after'.
Example: <mtx-popover [position]="['below', 'after']" #popover="mtxPopover"></mtx-popover>`);
}
/**
* Throws an exception for the case when popover's mtxPopoverPosition[1] value isn't valid.
* In other words, it doesn't match 'above', 'below', 'before', 'after' or 'center'.
*/
function throwMtxPopoverInvalidPositionEnd() {
throw Error(`mtxPopoverPosition[1] value must be either 'above', 'below', 'before', 'after' or 'center'.
Example: <mtx-popover [position]="['below', 'after']" #popover="mtxPopover"></mtx-popover>`);
}
/** Injection token to be used to override the default options for `mtx-popover`. */
const MTX_POPOVER_DEFAULT_OPTIONS = new InjectionToken('mtx-popover-default-options', {
providedIn: 'root',
factory: MTX_POPOVER_DEFAULT_OPTIONS_FACTORY,
});
/** @docs-private */
function MTX_POPOVER_DEFAULT_OPTIONS_FACTORY() {
return {
backdropClass: 'cdk-overlay-transparent-backdrop',
};
}
let popoverPanelUid = 0;
class MtxPopover {
/** Popover's position. */
get position() {
return this._position;
}
set position(value) {
if (!['before', 'after', 'above', 'below'].includes(value[0])) {
throwMtxPopoverInvalidPositionStart();
}
if (!['before', 'after', 'above', 'below', 'center'].includes(value[1])) {
throwMtxPopoverInvalidPositionEnd();
}
this._position = value;
this.setPositionClasses();
}
/**
* This method takes classes set on the host mtx-popover element and applies them on the
* popover template that displays in the overlay container. Otherwise, it's difficult
* to style the containing popover from outside the component.
* @param classes list of class names
*/
set panelClass(classes) {
const previousPanelClass = this._previousPanelClass;
const newClassList = { ...this._classList };
if (previousPanelClass && previousPanelClass.length) {
previousPanelClass.split(' ').forEach((className) => {
newClassList[className] = false;
});
}
this._previousPanelClass = classes;
if (classes && classes.length) {
classes.split(' ').forEach((className) => {
newClassList[className] = true;
});
this._elementRef.nativeElement.className = '';
this.setPositionClasses();
}
this._classList = newClassList;
}
/**
* This method takes classes set on the host mtx-popover element and applies them on the
* popover template that displays in the overlay container. Otherwise, it's difficult
* to style the containing popover from outside the component.
* @deprecated Use `panelClass` instead.
* @breaking-change 8.0.0
*/
get classList() {
return this.panelClass;
}
set classList(classes) {
this.panelClass = classes;
}
constructor(_elementRef, _unusedNgZone, _defaultOptions) {
this._elementRef = _elementRef;
this._unusedNgZone = _unusedNgZone;
this._defaultOptions = _defaultOptions;
this._elevationPrefix = 'mat-elevation-z';
this._baseElevation = null;
/** Config object to be passed into the popover's class. */
this._classList = {};
/** Current state of the panel animation. */
this._panelAnimationState = 'void';
/** Emits whenever an animation on the popover completes. */
this._animationDone = new Subject();
/** Whether the popover is animating. */
this._isAnimating = false;
/** Closing disabled on popover */
this.closeDisabled = false;
/** Class or list of classes to be added to the overlay panel. */
this.overlayPanelClass = this._defaultOptions.overlayPanelClass || '';
/** Class to be added to the backdrop element. */
this.backdropClass = this._defaultOptions.backdropClass;
/** Popover's trigger event. */
this.triggerEvent = this._defaultOptions.triggerEvent ?? 'hover';
/** Popover's enter delay. */
this.enterDelay = this._defaultOptions.enterDelay ?? 100;
/** Popover's leave delay. */
this.leaveDelay = this._defaultOptions.leaveDelay ?? 100;
this._position = this._defaultOptions.position ?? ['below', 'after'];
/** Popover-panel's X offset. */
this.xOffset = this._defaultOptions.xOffset ?? 0;
/** Popover-panel's Y offset. */
this.yOffset = this._defaultOptions.yOffset ?? 0;
/** Popover-arrow's width. */
this.arrowWidth = this._defaultOptions.arrowWidth ?? 16;
/** Popover-arrow's height. */
this.arrowHeight = this._defaultOptions.arrowHeight ?? 16;
/** Popover-arrow's X offset. */
this.arrowOffsetX = this._defaultOptions.arrowOffsetX ?? 20;
/** Popover-arrow's Y offset. */
this.arrowOffsetY = this._defaultOptions.arrowOffsetY ?? 20;
/** Whether the popover arrow should be hidden. */
this.hideArrow = this._defaultOptions.hideArrow ?? false;
/** Whether popover can be closed when click the popover-panel. */
this.closeOnPanelClick = this._defaultOptions.closeOnPanelClick ?? false;
/** Whether popover can be closed when click the backdrop. */
this.closeOnBackdropClick = this._defaultOptions.closeOnBackdropClick ?? true;
/** Whether enable focus trap using `cdkTrapFocus`. */
this.focusTrapEnabled = this._defaultOptions.focusTrapEnabled ?? false;
/** Whether enable focus trap auto capture using `cdkTrapFocusAutoCapture`. */
this.focusTrapAutoCaptureEnabled = this._defaultOptions.focusTrapAutoCaptureEnabled ?? false;
/** Whether the popover has a backdrop. It will always be false if the trigger event is hover. */
this.hasBackdrop = this._defaultOptions.hasBackdrop;
/** Event emitted when the popover is closed. */
this.closed = new EventEmitter();
this.panelId = `mtx-popover-panel-${popoverPanelUid++}`;
}
ngOnInit() {
this.setPositionClasses();
}
ngOnDestroy() {
this.closed.complete();
}
/** Handle a keyboard event from the popover, delegating to the appropriate action. */
_handleKeydown(event) {
const keyCode = event.keyCode;
switch (keyCode) {
case ESCAPE:
if (!hasModifierKey(event)) {
event.preventDefault();
this.closed.emit('keydown');
}
break;
}
}
/** Close popover on click if `closeOnPanelClick` is true. */
_handleClick() {
if (this.closeOnPanelClick) {
this.closed.emit('click');
}
}
/** Disables close of popover when leaving trigger element and mouse over the popover. */
_handleMouseOver() {
if (this.triggerEvent === 'hover') {
this.closeDisabled = true;
}
}
/** Enables close of popover when mouse leaving popover element. */
_handleMouseLeave() {
if (this.triggerEvent === 'hover') {
setTimeout(() => {
this.closeDisabled = false;
this.closed.emit();
}, this.leaveDelay);
}
}
/** Sets the current styles for the popover to allow for dynamically changing settings. */
setCurrentStyles(pos = this.position) {
const left = pos[1] === 'after'
? `${this.arrowOffsetX - this.arrowWidth / 2}px`
: pos[1] === 'center'
? `calc(50% - ${this.arrowWidth / 2}px)`
: '';
const right = pos[1] === 'before' ? `${this.arrowOffsetX - this.arrowWidth / 2}px` : '';
const bottom = pos[1] === 'above'
? `${this.arrowOffsetY - this.arrowHeight / 2}px`
: pos[1] === 'center'
? `calc(50% - ${this.arrowHeight / 2}px)`
: '';
const top = pos[1] === 'below' ? `${this.arrowOffsetY - this.arrowHeight / 2}px` : '';
this.arrowStyles =
pos[0] === 'above' || pos[0] === 'below'
? {
left: this.direction === 'ltr' ? left : right,
right: this.direction === 'ltr' ? right : left,
}
: { top, bottom };
}
/**
* It's necessary to set position-based classes to ensure the popover panel animation
* folds out from the correct direction.
*/
setPositionClasses(pos = this.position) {
this._classList['mtx-popover-before-above'] = pos[0] === 'before' && pos[1] === 'above';
this._classList['mtx-popover-before-center'] = pos[0] === 'before' && pos[1] === 'center';
this._classList['mtx-popover-before-below'] = pos[0] === 'before' && pos[1] === 'below';
this._classList['mtx-popover-after-above'] = pos[0] === 'after' && pos[1] === 'above';
this._classList['mtx-popover-after-center'] = pos[0] === 'after' && pos[1] === 'center';
this._classList['mtx-popover-after-below'] = pos[0] === 'after' && pos[1] === 'below';
this._classList['mtx-popover-above-before'] = pos[0] === 'above' && pos[1] === 'before';
this._classList['mtx-popover-above-center'] = pos[0] === 'above' && pos[1] === 'center';
this._classList['mtx-popover-above-after'] = pos[0] === 'above' && pos[1] === 'after';
this._classList['mtx-popover-below-before'] = pos[0] === 'below' && pos[1] === 'before';
this._classList['mtx-popover-below-center'] = pos[0] === 'below' && pos[1] === 'center';
this._classList['mtx-popover-below-after'] = pos[0] === 'below' && pos[1] === 'after';
}
/** Sets the popover-panel's elevation. */
setElevation() {
// The base elevation depends on which version of the spec
// we're running so we have to resolve it at runtime.
if (this._baseElevation === null) {
const styles = typeof getComputedStyle === 'function'
? getComputedStyle(this._elementRef.nativeElement)
: null;
const value = styles?.getPropertyValue('--mtx-popover-base-elevation-level') || '8';
this._baseElevation = parseInt(value);
}
// The elevation starts at the base and increases by one for each level.
// Capped at 24 because that's the maximum elevation defined in the Material design spec.
const elevation = Math.min(this._baseElevation, 24);
const newElevation = `${this._elevationPrefix}${elevation}`;
const customElevation = Object.keys(this._classList).find(className => {
return className.startsWith(this._elevationPrefix);
});
if (!customElevation || customElevation === this._previousElevation) {
const newClassList = { ...this._classList };
if (this._previousElevation) {
newClassList[this._previousElevation] = false;
}
newClassList[newElevation] = true;
this._previousElevation = newElevation;
this._classList = newClassList;
}
}
/** Starts the enter animation. */
_startAnimation() {
// @breaking-change 8.0.0 Combine with _resetAnimation.
this._panelAnimationState = 'enter';
}
/** Resets the panel animation to its initial state. */
_resetAnimation() {
// @breaking-change 8.0.0 Combine with _startAnimation.
this._panelAnimationState = 'void';
}
/** Callback that is invoked when the panel animation completes. */
_onAnimationDone(event) {
this._animationDone.next(event);
this._isAnimating = false;
}
_onAnimationStart(event) {
this._isAnimating = true;
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.0", ngImport: i0, type: MtxPopover, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }, { token: MTX_POPOVER_DEFAULT_OPTIONS }], target: i0.ɵɵFactoryTarget.Component }); }
/** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.0", type: MtxPopover, isStandalone: true, selector: "mtx-popover", inputs: { backdropClass: "backdropClass", ariaLabel: ["aria-label", "ariaLabel"], ariaLabelledby: ["aria-labelledby", "ariaLabelledby"], ariaDescribedby: ["aria-describedby", "ariaDescribedby"], triggerEvent: "triggerEvent", enterDelay: "enterDelay", leaveDelay: "leaveDelay", position: "position", xOffset: "xOffset", yOffset: "yOffset", arrowWidth: "arrowWidth", arrowHeight: "arrowHeight", arrowOffsetX: "arrowOffsetX", arrowOffsetY: "arrowOffsetY", hideArrow: ["hideArrow", "hideArrow", booleanAttribute], closeOnPanelClick: ["closeOnPanelClick", "closeOnPanelClick", booleanAttribute], closeOnBackdropClick: ["closeOnBackdropClick", "closeOnBackdropClick", booleanAttribute], focusTrapEnabled: ["focusTrapEnabled", "focusTrapEnabled", booleanAttribute], focusTrapAutoCaptureEnabled: ["focusTrapAutoCaptureEnabled", "focusTrapAutoCaptureEnabled", booleanAttribute], hasBackdrop: ["hasBackdrop", "hasBackdrop", booleanAttribute], panelClass: ["class", "panelClass"], classList: "classList" }, outputs: { closed: "closed" }, queries: [{ propertyName: "lazyContent", first: true, predicate: MTX_POPOVER_CONTENT, descendants: true }], viewQueries: [{ propertyName: "templateRef", first: true, predicate: TemplateRef, descendants: true }], exportAs: ["mtxPopover"], ngImport: i0, template: "<ng-template>\n <div\n [id]=\"panelId\"\n class=\"mtx-popover-panel\"\n [class]=\"_classList\"\n [class.mtx-popover-panel-without-arrow]=\"hideArrow\"\n (keydown)=\"_handleKeydown($event)\"\n (click)=\"_handleClick()\"\n (mouseover)=\"_handleMouseOver()\"\n (mouseleave)=\"_handleMouseLeave()\"\n [@transformPopover]=\"_panelAnimationState\"\n (@transformPopover.start)=\"_onAnimationStart($event)\"\n (@transformPopover.done)=\"_onAnimationDone($event)\"\n tabindex=\"-1\"\n role=\"dialog\"\n [attr.aria-label]=\"ariaLabel || null\"\n [attr.aria-labelledby]=\"ariaLabelledby || null\"\n [attr.aria-describedby]=\"ariaDescribedby || null\"\n [cdkTrapFocus]=\"focusTrapEnabled\"\n [cdkTrapFocusAutoCapture]=\"focusTrapAutoCaptureEnabled\">\n <div class=\"mtx-popover-content\">\n <ng-content></ng-content>\n </div>\n @if (!hideArrow) {\n <div class=\"mtx-popover-direction-arrow\" [style]=\"arrowStyles\"></div>\n }\n </div>\n</ng-template>\n", styles: [".mtx-popover-panel{position:relative;max-height:calc(100vh - 48px);padding:8px;font-size:inherit;outline:0;border-radius:var(--mtx-popover-container-shape, var(--mat-app-corner-extra-small));background-color:var(--mtx-popover-background-color, var(--mat-app-surface-container));color:var(--mtx-popover-text-color, var(--mat-app-on-surface))}.mtx-popover-panel[class*=mtx-popover-below]{margin-top:calc(.5em + 2px)}.mtx-popover-panel[class*=mtx-popover-above]{margin-bottom:calc(.5em + 2px)}.mtx-popover-panel[class*=mtx-popover-before]{margin-right:calc(.5em + 2px)}[dir=rtl] .mtx-popover-panel[class*=mtx-popover-before]{margin-right:auto;margin-left:calc(.5em + 2px)}.mtx-popover-panel[class*=mtx-popover-after]{margin-left:calc(.5em + 2px)}[dir=rtl] .mtx-popover-panel[class*=mtx-popover-after]{margin-left:auto;margin-right:calc(.5em + 2px)}.mtx-popover-panel.mtx-popover-panel-without-arrow{margin:0}.mtx-popover-direction-arrow{position:absolute}.mtx-popover-direction-arrow:before,.mtx-popover-direction-arrow:after{position:absolute;display:inline-block;content:\"\";border-width:.5em;border-style:solid}.mtx-popover-direction-arrow:before{border-color:var(--mtx-popover-outline-color, var(--mat-app-surface-container))}.mtx-popover-direction-arrow:after{border-width:calc(.5em - 1px);border-color:var(--mtx-popover-background-color, var(--mat-app-surface-container))}[class*=mtx-popover-below] .mtx-popover-direction-arrow,[class*=mtx-popover-above] .mtx-popover-direction-arrow{width:1em}[class*=mtx-popover-below] .mtx-popover-direction-arrow:before,[class*=mtx-popover-below] .mtx-popover-direction-arrow:after,[class*=mtx-popover-above] .mtx-popover-direction-arrow:before,[class*=mtx-popover-above] .mtx-popover-direction-arrow:after{border-left-color:transparent;border-right-color:transparent}[class*=mtx-popover-below] .mtx-popover-direction-arrow:after,[class*=mtx-popover-above] .mtx-popover-direction-arrow:after{left:1px}[dir=rtl] [class*=mtx-popover-below] .mtx-popover-direction-arrow:after,[dir=rtl] [class*=mtx-popover-above] .mtx-popover-direction-arrow:after{right:1px;left:auto}[class*=mtx-popover-below] .mtx-popover-direction-arrow{top:0}[class*=mtx-popover-below] .mtx-popover-direction-arrow:before,[class*=mtx-popover-below] .mtx-popover-direction-arrow:after{bottom:0;border-top-width:0}[class*=mtx-popover-above] .mtx-popover-direction-arrow{bottom:0}[class*=mtx-popover-above] .mtx-popover-direction-arrow:before,[class*=mtx-popover-above] .mtx-popover-direction-arrow:after{top:0;border-bottom-width:0}[class*=mtx-popover-before] .mtx-popover-direction-arrow,[class*=mtx-popover-after] .mtx-popover-direction-arrow{height:1em}[class*=mtx-popover-before] .mtx-popover-direction-arrow:before,[class*=mtx-popover-before] .mtx-popover-direction-arrow:after,[class*=mtx-popover-after] .mtx-popover-direction-arrow:before,[class*=mtx-popover-after] .mtx-popover-direction-arrow:after{border-top-color:transparent;border-bottom-color:transparent}[class*=mtx-popover-before] .mtx-popover-direction-arrow:after,[class*=mtx-popover-after] .mtx-popover-direction-arrow:after{top:1px}[class*=mtx-popover-before] .mtx-popover-direction-arrow{right:0}[class*=mtx-popover-before] .mtx-popover-direction-arrow:before,[class*=mtx-popover-before] .mtx-popover-direction-arrow:after{left:0;border-right-width:0}[dir=rtl] [class*=mtx-popover-before] .mtx-popover-direction-arrow{right:auto;left:0}[dir=rtl] [class*=mtx-popover-before] .mtx-popover-direction-arrow:before,[dir=rtl] [class*=mtx-popover-before] .mtx-popover-direction-arrow:after{left:auto;right:0;border-left-width:0}[dir=rtl] [class*=mtx-popover-before] .mtx-popover-direction-arrow:before{border-right-width:.5em}[dir=rtl] [class*=mtx-popover-before] .mtx-popover-direction-arrow:after{border-right-width:calc(.5em - 1px)}[class*=mtx-popover-after] .mtx-popover-direction-arrow{left:0}[class*=mtx-popover-after] .mtx-popover-direction-arrow:before,[class*=mtx-popover-after] .mtx-popover-direction-arrow:after{right:0;border-left-width:0}[dir=rtl] [class*=mtx-popover-after] .mtx-popover-direction-arrow{left:auto;right:0}[dir=rtl] [class*=mtx-popover-after] .mtx-popover-direction-arrow:before,[dir=rtl] [class*=mtx-popover-after] .mtx-popover-direction-arrow:after{right:auto;left:0;border-right-width:0}[dir=rtl] [class*=mtx-popover-after] .mtx-popover-direction-arrow:before{border-left-width:.5em}[dir=rtl] [class*=mtx-popover-after] .mtx-popover-direction-arrow:after{border-left-width:calc(.5em - 1px)}\n"], dependencies: [{ kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }], animations: [transformPopover], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.0", ngImport: i0, type: MtxPopover, decorators: [{
type: Component,
args: [{ selector: 'mtx-popover', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, animations: [transformPopover], exportAs: 'mtxPopover', standalone: true, imports: [CdkTrapFocus], template: "<ng-template>\n <div\n [id]=\"panelId\"\n class=\"mtx-popover-panel\"\n [class]=\"_classList\"\n [class.mtx-popover-panel-without-arrow]=\"hideArrow\"\n (keydown)=\"_handleKeydown($event)\"\n (click)=\"_handleClick()\"\n (mouseover)=\"_handleMouseOver()\"\n (mouseleave)=\"_handleMouseLeave()\"\n [@transformPopover]=\"_panelAnimationState\"\n (@transformPopover.start)=\"_onAnimationStart($event)\"\n (@transformPopover.done)=\"_onAnimationDone($event)\"\n tabindex=\"-1\"\n role=\"dialog\"\n [attr.aria-label]=\"ariaLabel || null\"\n [attr.aria-labelledby]=\"ariaLabelledby || null\"\n [attr.aria-describedby]=\"ariaDescribedby || null\"\n [cdkTrapFocus]=\"focusTrapEnabled\"\n [cdkTrapFocusAutoCapture]=\"focusTrapAutoCaptureEnabled\">\n <div class=\"mtx-popover-content\">\n <ng-content></ng-content>\n </div>\n @if (!hideArrow) {\n <div class=\"mtx-popover-direction-arrow\" [style]=\"arrowStyles\"></div>\n }\n </div>\n</ng-template>\n", styles: [".mtx-popover-panel{position:relative;max-height:calc(100vh - 48px);padding:8px;font-size:inherit;outline:0;border-radius:var(--mtx-popover-container-shape, var(--mat-app-corner-extra-small));background-color:var(--mtx-popover-background-color, var(--mat-app-surface-container));color:var(--mtx-popover-text-color, var(--mat-app-on-surface))}.mtx-popover-panel[class*=mtx-popover-below]{margin-top:calc(.5em + 2px)}.mtx-popover-panel[class*=mtx-popover-above]{margin-bottom:calc(.5em + 2px)}.mtx-popover-panel[class*=mtx-popover-before]{margin-right:calc(.5em + 2px)}[dir=rtl] .mtx-popover-panel[class*=mtx-popover-before]{margin-right:auto;margin-left:calc(.5em + 2px)}.mtx-popover-panel[class*=mtx-popover-after]{margin-left:calc(.5em + 2px)}[dir=rtl] .mtx-popover-panel[class*=mtx-popover-after]{margin-left:auto;margin-right:calc(.5em + 2px)}.mtx-popover-panel.mtx-popover-panel-without-arrow{margin:0}.mtx-popover-direction-arrow{position:absolute}.mtx-popover-direction-arrow:before,.mtx-popover-direction-arrow:after{position:absolute;display:inline-block;content:\"\";border-width:.5em;border-style:solid}.mtx-popover-direction-arrow:before{border-color:var(--mtx-popover-outline-color, var(--mat-app-surface-container))}.mtx-popover-direction-arrow:after{border-width:calc(.5em - 1px);border-color:var(--mtx-popover-background-color, var(--mat-app-surface-container))}[class*=mtx-popover-below] .mtx-popover-direction-arrow,[class*=mtx-popover-above] .mtx-popover-direction-arrow{width:1em}[class*=mtx-popover-below] .mtx-popover-direction-arrow:before,[class*=mtx-popover-below] .mtx-popover-direction-arrow:after,[class*=mtx-popover-above] .mtx-popover-direction-arrow:before,[class*=mtx-popover-above] .mtx-popover-direction-arrow:after{border-left-color:transparent;border-right-color:transparent}[class*=mtx-popover-below] .mtx-popover-direction-arrow:after,[class*=mtx-popover-above] .mtx-popover-direction-arrow:after{left:1px}[dir=rtl] [class*=mtx-popover-below] .mtx-popover-direction-arrow:after,[dir=rtl] [class*=mtx-popover-above] .mtx-popover-direction-arrow:after{right:1px;left:auto}[class*=mtx-popover-below] .mtx-popover-direction-arrow{top:0}[class*=mtx-popover-below] .mtx-popover-direction-arrow:before,[class*=mtx-popover-below] .mtx-popover-direction-arrow:after{bottom:0;border-top-width:0}[class*=mtx-popover-above] .mtx-popover-direction-arrow{bottom:0}[class*=mtx-popover-above] .mtx-popover-direction-arrow:before,[class*=mtx-popover-above] .mtx-popover-direction-arrow:after{top:0;border-bottom-width:0}[class*=mtx-popover-before] .mtx-popover-direction-arrow,[class*=mtx-popover-after] .mtx-popover-direction-arrow{height:1em}[class*=mtx-popover-before] .mtx-popover-direction-arrow:before,[class*=mtx-popover-before] .mtx-popover-direction-arrow:after,[class*=mtx-popover-after] .mtx-popover-direction-arrow:before,[class*=mtx-popover-after] .mtx-popover-direction-arrow:after{border-top-color:transparent;border-bottom-color:transparent}[class*=mtx-popover-before] .mtx-popover-direction-arrow:after,[class*=mtx-popover-after] .mtx-popover-direction-arrow:after{top:1px}[class*=mtx-popover-before] .mtx-popover-direction-arrow{right:0}[class*=mtx-popover-before] .mtx-popover-direction-arrow:before,[class*=mtx-popover-before] .mtx-popover-direction-arrow:after{left:0;border-right-width:0}[dir=rtl] [class*=mtx-popover-before] .mtx-popover-direction-arrow{right:auto;left:0}[dir=rtl] [class*=mtx-popover-before] .mtx-popover-direction-arrow:before,[dir=rtl] [class*=mtx-popover-before] .mtx-popover-direction-arrow:after{left:auto;right:0;border-left-width:0}[dir=rtl] [class*=mtx-popover-before] .mtx-popover-direction-arrow:before{border-right-width:.5em}[dir=rtl] [class*=mtx-popover-before] .mtx-popover-direction-arrow:after{border-right-width:calc(.5em - 1px)}[class*=mtx-popover-after] .mtx-popover-direction-arrow{left:0}[class*=mtx-popover-after] .mtx-popover-direction-arrow:before,[class*=mtx-popover-after] .mtx-popover-direction-arrow:after{right:0;border-left-width:0}[dir=rtl] [class*=mtx-popover-after] .mtx-popover-direction-arrow{left:auto;right:0}[dir=rtl] [class*=mtx-popover-after] .mtx-popover-direction-arrow:before,[dir=rtl] [class*=mtx-popover-after] .mtx-popover-direction-arrow:after{right:auto;left:0;border-right-width:0}[dir=rtl] [class*=mtx-popover-after] .mtx-popover-direction-arrow:before{border-left-width:.5em}[dir=rtl] [class*=mtx-popover-after] .mtx-popover-direction-arrow:after{border-left-width:calc(.5em - 1px)}\n"] }]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.NgZone }, { type: undefined, decorators: [{
type: Inject,
args: [MTX_POPOVER_DEFAULT_OPTIONS]
}] }], propDecorators: { backdropClass: [{
type: Input
}], ariaLabel: [{
type: Input,
args: ['aria-label']
}], ariaLabelledby: [{
type: Input,
args: ['aria-labelledby']
}], ariaDescribedby: [{
type: Input,
args: ['aria-describedby']
}], triggerEvent: [{
type: Input
}], enterDelay: [{
type: Input
}], leaveDelay: [{
type: Input
}], position: [{
type: Input
}], xOffset: [{
type: Input
}], yOffset: [{
type: Input
}], arrowWidth: [{
type: Input
}], arrowHeight: [{
type: Input
}], arrowOffsetX: [{
type: Input
}], arrowOffsetY: [{
type: Input
}], hideArrow: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], closeOnPanelClick: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], closeOnBackdropClick: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], focusTrapEnabled: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], focusTrapAutoCaptureEnabled: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], hasBackdrop: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], panelClass: [{
type: Input,
args: ['class']
}], classList: [{
type: Input
}], closed: [{
type: Output
}], templateRef: [{
type: ViewChild,
args: [TemplateRef]
}], lazyContent: [{
type: ContentChild,
args: [MTX_POPOVER_CONTENT]
}] } });
/** Injection token that determines the scroll handling while the popover is open. */
const MTX_POPOVER_SCROLL_STRATEGY = new InjectionToken('mtx-popover-scroll-strategy', {
providedIn: 'root',
factory: () => {
const overlay = inject(Overlay);
return () => overlay.scrollStrategies.reposition();
},
});
/** @docs-private */
function MTX_POPOVER_SCROLL_STRATEGY_FACTORY(overlay) {
return () => overlay.scrollStrategies.reposition();
}
/** @docs-private */
const MTX_POPOVER_SCROLL_STRATEGY_FACTORY_PROVIDER = {
provide: MTX_POPOVER_SCROLL_STRATEGY,
deps: [Overlay],
useFactory: MTX_POPOVER_SCROLL_STRATEGY_FACTORY,
};
/**
* This directive is intended to be used in conjunction with an `mtx-popover` tag. It is
* responsible for toggling the display of the provided popover instance.
*/
class MtxPopoverTrigger {
/** References the popover instance that the trigger is associated with. */
get popover() {
return this._popover;
}
set popover(popover) {
if (popover === this._popover) {
return;
}
this._popover = popover;
this._popoverCloseSubscription.unsubscribe();
if (popover) {
this._popoverCloseSubscription = popover.closed.subscribe((reason) => {
this._destroyPopover(reason);
});
}
}
constructor(_overlay, _elementRef, _viewContainerRef, scrollStrategy, _dir, _changeDetectorRef, _focusMonitor) {
this._overlay = _overlay;
this._elementRef = _elementRef;
this._viewContainerRef = _viewContainerRef;
this._dir = _dir;
this._changeDetectorRef = _changeDetectorRef;
this._focusMonitor = _focusMonitor;
this._overlayRef = null;
this._popoverOpen = false;
this._halt = false;
this._positionSubscription = Subscription.EMPTY;
this._popoverCloseSubscription = Subscription.EMPTY;
this._closingActionsSubscription = Subscription.EMPTY;
// Tracking input type is necessary so it's possible to only auto-focus
// the first item of the list when the popover is opened via the keyboard
this._openedBy = undefined;
/** Event emitted when the associated popover is opened. */
this.popoverOpened = new EventEmitter();
/** Event emitted when the associated popover is closed. */
this.popoverClosed = new EventEmitter();
this._scrollStrategy = scrollStrategy;
}
ngAfterContentInit() {
this._checkPopover();
this._setCurrentConfig();
}
ngOnDestroy() {
if (this._overlayRef) {
this._overlayRef.dispose();
this._overlayRef = null;
}
this._halt = true;
this._positionSubscription.unsubscribe();
this._popoverCloseSubscription.unsubscribe();
this._closingActionsSubscription.unsubscribe();
}
_setCurrentConfig() {
if (this.triggerEvent) {
this.popover.triggerEvent = this.triggerEvent;
}
this.popover.setCurrentStyles();
}
/** Whether the popover is open. */
get popoverOpen() {
return this._popoverOpen;
}
/** The text direction of the containing app. */
get dir() {
return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr';
}
/** Handles mouse click on the trigger. */
_handleClick(event) {
if (this.popover.triggerEvent === 'click') {
this.togglePopover();
}
}
/** Handles mouse enter on the trigger. */
_handleMouseEnter(event) {
this._halt = false;
if (this.popover.triggerEvent === 'hover') {
this._mouseoverTimer = setTimeout(() => {
this.openPopover();
}, this.popover.enterDelay);
}
}
/** Handles mouse leave on the trigger. */
_handleMouseLeave(event) {
if (this.popover.triggerEvent === 'hover') {
if (this._mouseoverTimer) {
clearTimeout(this._mouseoverTimer);
this._mouseoverTimer = null;
}
if (this._popoverOpen) {
setTimeout(() => {
if (!this.popover.closeDisabled) {
this.closePopover();
}
}, this.popover.leaveDelay);
}
else {
this._halt = true;
}
}
}
/** Handles mouse presses on the trigger. */
_handleMousedown(event) {
if (!isFakeMousedownFromScreenReader(event)) {
// Since right or middle button clicks won't trigger the `click` event,
// we shouldn't consider the popover as opened by mouse in those cases.
this._openedBy = event.button === 0 ? 'mouse' : undefined;
}
}
/** Handles key presses on the trigger. */
_handleKeydown(event) {
const keyCode = event.keyCode;
// Pressing enter on the trigger will trigger the click handler later.
if (keyCode === ENTER || keyCode === SPACE) {
this._openedBy = 'keyboard';
}
}
/** Toggles the popover between the open and closed states. */
togglePopover() {
return this._popoverOpen ? this.closePopover() : this.openPopover();
}
/** Opens the popover. */
openPopover() {
if (this._popoverOpen || this._halt) {
return;
}
this._checkPopover();
const overlayRef = this._createOverlay();
const overlayConfig = overlayRef.getConfig();
this._setPosition(overlayConfig.positionStrategy);
if (this.popover.triggerEvent === 'click') {
overlayConfig.hasBackdrop = this.popover.hasBackdrop ?? true;
}
overlayRef.attach(this._getPortal());
if (this.popover.lazyContent) {
this.popover.lazyContent.attach(this.popoverData);
}
this._closingActionsSubscription = this._popoverClosingActions().subscribe(() => this.closePopover());
this._initPopover();
if (this.popover instanceof MtxPopover) {
this.popover._startAnimation();
}
}
/** Closes the popover. */
closePopover() {
this.popover.closed.emit();
}
/**
* Focuses the popover trigger.
* @param origin Source of the popover trigger's focus.
*/
focus(origin, options) {
if (this._focusMonitor && origin) {
this._focusMonitor.focusVia(this._elementRef, origin, options);
}
else {
this._elementRef.nativeElement.focus(options);
}
}
/** Removes the popover from the DOM. */
_destroyPopover(reason) {
if (!this._overlayRef || !this.popoverOpen) {
return;
}
// Clear the timeout for hover event.
if (this._mouseoverTimer) {
clearTimeout(this._mouseoverTimer);
this._mouseoverTimer = null;
}
const popover = this.popover;
this._closingActionsSubscription.unsubscribe();
this._overlayRef.detach();
this._openedBy = undefined;
if (popover instanceof MtxPopover) {
popover._resetAnimation();
if (popover.lazyContent) {
// Wait for the exit animation to finish before detaching the content.
popover._animationDone
.pipe(filter(event => event.toState === 'void'), take(1),
// Interrupt if the content got re-attached.
takeUntil(popover.lazyContent._attached))
.subscribe({
next: () => popover.lazyContent.detach(),
// No matter whether the content got re-attached, reset the popover.
complete: () => this._setIsPopoverOpen(false),
});
}
else {
this._setIsPopoverOpen(false);
}
}
else {
this._setIsPopoverOpen(false);
popover.lazyContent?.detach();
}
}
/**
* This method sets the popover state to open.
*/
_initPopover() {
this.popover.direction = this.dir;
this.popover.setElevation();
this._setIsPopoverOpen(true);
}
// set state rather than toggle to support triggers sharing a popover
_setIsPopoverOpen(isOpen) {
if (isOpen !== this._popoverOpen) {
this._popoverOpen = isOpen;
this._popoverOpen ? this.popoverOpened.emit() : this.popoverClosed.emit();
this._changeDetectorRef.markForCheck();
}
}
/**
* This method checks that a valid instance of MdPopover has been passed into
* `mtxPopoverTriggerFor`. If not, an exception is thrown.
*/
_checkPopover() {
if (!this.popover) {
throwMtxPopoverMissingError();
}
}
/**
* This method creates the overlay from the provided popover's template and saves its
* OverlayRef so that it can be attached to the DOM when openPopover is called.
*/
_createOverlay() {
if (!this._overlayRef) {
const config = this._getOverlayConfig();
this._subscribeToPositions(config.positionStrategy);
this._overlayRef = this._overlay.create(config);
}
else {
const overlayConfig = this._overlayRef.getConfig();
const positionStrategy = overlayConfig.positionStrategy;
positionStrategy.setOrigin(this._getTargetElement());
}
return this._overlayRef;
}
/**
* This method builds the configuration object needed to create the overlay, the OverlayConfig.
* @returns OverlayConfig
*/
_getOverlayConfig() {
return new OverlayConfig({
positionStrategy: this._overlay
.position()
.flexibleConnectedTo(this._getTargetElement())
.withLockedPosition()
.withGrowAfterOpen()
.withTransformOriginOn('.mtx-popover-panel'),
backdropClass: this.popover.backdropClass || 'cdk-overlay-transparent-backdrop',
panelClass: this.popover.overlayPanelClass,
scrollStrategy: this._scrollStrategy(),
direction: this._dir,
});
}
_getTargetElement() {
if (this.targetElement) {
return this.targetElement.elementRef;
}
return this._elementRef;
}
/**
* Listens to changes in the position of the overlay and sets the correct classes
* on the popover based on the new position. This ensures the animation origin is always
* correct, even if a fallback position is used for the overlay.
*/
_subscribeToPositions(position) {
this._positionSubscription = position.positionChanges.subscribe(change => {
const posX = change.connectionPair.overlayX === 'start'
? 'after'
: change.connectionPair.overlayX === 'end'
? 'before'
: 'center';
const posY = change.connectionPair.overlayY === 'top'
? 'below'
: change.connectionPair.overlayY === 'bottom'
? 'above'
: 'center';
const pos = this.popover.position[0] === 'above' || this.popover.position[0] === 'below'
? [posY, posX]
: [posX, posY];
// required for ChangeDetectionStrategy.OnPush
this._changeDetectorRef.markForCheck();
this.popover.setCurrentStyles(pos);
this.popover.setPositionClasses(pos);
});
}
/**
* Sets the appropriate positions on a position strategy
* so the overlay connects with the trigger correctly.
* @param positionStrategy Strategy whose position to update.
*/
_setPosition(positionStrategy) {
const [originX, origin2ndX, origin3rdX] = this.popover.position[0] === 'before' || this.popover.position[1] === 'after'
? ['start', 'center', 'end']
: this.popover.position[0] === 'after' || this.popover.position[1] === 'before'
? ['end', 'center', 'start']
: ['center', 'start', 'end'];
const [originY, origin2ndY, origin3rdY] = this.popover.position[0] === 'above' || this.popover.position[1] === 'below'
? ['top', 'center', 'bottom']
: this.popover.position[0] === 'below' || this.popover.position[1] === 'above'
? ['bottom', 'center', 'top']
: ['center', 'top', 'bottom'];
const [overlayX, overlayFallbackX] = this.popover.position[0] === 'below' || this.popover.position[0] === 'above'
? [originX, originX]
: this.popover.position[0] === 'before'
? ['end', 'start']
: ['start', 'end'];
const [overlayY, overlayFallbackY] = this.popover.position[0] === 'before' || this.popover.position[0] === 'after'
? [originY, originY]
: this.popover.position[0] === 'below'
? ['top', 'bottom']
: ['bottom', 'top'];
const originFallbackX = overlayX;
const originFallbackY = overlayY;
const offsetX = this.popover.xOffset && !isNaN(Number(this.popover.xOffset))
? Number(this.dir === 'ltr' ? this.popover.xOffset : -this.popover.xOffset)
: 0;
const offsetY = this.popover.yOffset && !isNaN(Number(this.popover.yOffset))
? Number(this.popover.yOffset)
: 0;
let positions = [{ originX, originY, overlayX, overlayY }];
if (this.popover.position[0] === 'above' || this.popover.position[0] === 'below') {
positions = [
{ originX, originY, overlayX, overlayY, offsetY },
{ originX: origin2ndX, originY, overlayX: origin2ndX, overlayY, offsetY },
{ originX: origin3rdX, originY, overlayX: origin3rdX, overlayY, offsetY },
{
originX,
originY: originFallbackY,
overlayX,
overlayY: overlayFallbackY,
offsetY: -offsetY,
},
{
originX: origin2ndX,
originY: originFallbackY,
overlayX: origin2ndX,
overlayY: overlayFallbackY,
offsetY: -offsetY,
},
{
originX: origin3rdX,
originY: originFallbackY,
overlayX: origin3rdX,
overlayY: overlayFallbackY,
offsetY: -offsetY,
},
];
}
if (this.popover.position[0] === 'before' || this.popover.position[0] === 'af