Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | 1x 7x 7x 7x 7x 7x 6x 6x 7x 7x 7x 7x 7x 7x 7x 7x 1x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 1x 7x 7x 7x 7x 7x 6x 6x | /**
* Copyright (c) Siemens 2016 - 2025
* SPDX-License-Identifier: MIT
*/
import { ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
import { ConnectedOverlayPositionChange } from '@angular/cdk/overlay';
import { NgClass, NgTemplateOutlet } from '@angular/common';
import {
Component,
ElementRef,
inject,
Injector,
input,
OnDestroy,
OnInit,
signal,
TemplateRef,
viewChild,
DOCUMENT,
computed
} from '@angular/core';
import { calculateOverlayArrowPosition, OverlayArrowPosition } from '@siemens/element-ng/common';
import { SiIconComponent } from '@siemens/element-ng/icon';
import { SiTranslateModule } from '@siemens/element-translate-ng/translate';
import { SiPopoverDirective } from './si-popover.directive';
@Component({
selector: 'si-popover',
imports: [NgClass, NgTemplateOutlet, SiIconComponent, SiTranslateModule],
templateUrl: './si-popover.component.html',
host: {
'[id]': 'this.popoverDirective().popoverId'
}
})
export class PopoverComponent implements OnInit, OnDestroy {
readonly popoverDirective = input.required<SiPopoverDirective>();
readonly popoverWrapper = viewChild.required<ElementRef>('popoverWrapper');
/** @internal */
labelledBy: string | undefined;
/** @internal */
describedBy: string | undefined;
protected readonly positionClass = signal('');
protected readonly arrowPos = signal<OverlayArrowPosition | undefined>(undefined);
protected readonly description = computed(() => {
const description = this.popoverDirective().siPopover();
return !(description instanceof TemplateRef) ? description : undefined;
});
protected popoverTemplate: TemplateRef<any> | null = null;
protected injector = inject(Injector);
private elementRef = inject(ElementRef);
private focusTrapFactory = inject(ConfigurableFocusTrapFactory);
private focusTrap?: ConfigurableFocusTrap;
private readonly previouslyActiveElement = inject(DOCUMENT).activeElement;
ngOnInit(): void {
const popoverDirective = this.popoverDirective();
const popover = popoverDirective.siPopover();
if (popover instanceof TemplateRef) {
this.popoverTemplate = popover;
}
this.labelledBy = `__popover-title_${popoverDirective.popoverCounter}`;
this.describedBy = `__popover-body_${popoverDirective.popoverCounter}`;
this.applyFocus();
}
ngOnDestroy(): void {
this.focusTrap?.destroy();
if (
this.previouslyActiveElement &&
'focus' in this.previouslyActiveElement &&
typeof this.previouslyActiveElement.focus === 'function'
) {
this.previouslyActiveElement.focus();
}
}
/** @internal */
updateArrow(change: ConnectedOverlayPositionChange, anchor?: ElementRef): void {
const positionClass = `popover-${change.connectionPair.overlayX}-${change.connectionPair.overlayY}`;
// need two updates as class changes affect the position
this.positionClass.set(positionClass);
const arrowPos = calculateOverlayArrowPosition(change, this.elementRef, anchor);
this.arrowPos.set(arrowPos);
}
hide(): void {
this.popoverDirective().hide();
}
private applyFocus(): void {
// Using setTimeout ensures that SR first read `expanded` before we move the focus.
setTimeout(async () => {
const popoverWrapperEl = this.popoverWrapper().nativeElement;
this.focusTrap = this.focusTrapFactory.create(this.popoverWrapper().nativeElement);
const moved = await this.focusTrap.focusFirstTabbableElementWhenReady();
if (!moved) {
popoverWrapperEl.tabIndex = 0;
popoverWrapperEl.focus();
}
});
}
}
|