UNPKG

@ng-matero/extensions

Version:
842 lines (836 loc) 58.6 kB
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