mirror of
https://github.com/bitwarden/browser
synced 2026-02-18 18:33:50 +00:00
return focus to trigger without passing element
This commit is contained in:
@@ -964,18 +964,12 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
formConfig: CipherFormConfig,
|
||||
activeCollectionId?: CollectionId,
|
||||
) {
|
||||
this.vaultItemDialogRef = VaultItemDialogComponent.open(
|
||||
this.dialogService,
|
||||
{
|
||||
mode,
|
||||
formConfig,
|
||||
activeCollectionId,
|
||||
restore: this.restore,
|
||||
},
|
||||
{
|
||||
restoreFocus: this.newButtonEl(),
|
||||
},
|
||||
);
|
||||
this.vaultItemDialogRef = VaultItemDialogComponent.open(this.dialogService, {
|
||||
mode,
|
||||
formConfig,
|
||||
activeCollectionId,
|
||||
restore: this.restore,
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(this.vaultItemDialogRef.closed);
|
||||
this.vaultItemDialogRef = undefined;
|
||||
@@ -1145,7 +1139,7 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
initialTab: tab,
|
||||
limitNestedCollections: true,
|
||||
},
|
||||
restoreFocus: this.newButtonEl(),
|
||||
// Don't specify restoreFocus - let it default to capturing the currently focused element
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialog.closed);
|
||||
|
||||
@@ -237,11 +237,17 @@ export class DialogService {
|
||||
backdropClass: this.backDropClasses,
|
||||
scrollStrategy: this.defaultScrollStrategy,
|
||||
positionStrategy: config?.positionStrategy ?? new ResponsivePositionStrategy(),
|
||||
restoreFocus: true,
|
||||
injector,
|
||||
...config,
|
||||
};
|
||||
|
||||
ref.cdkDialogRefBase = this.dialog.open<R, D, C>(componentOrTemplateRef, _config);
|
||||
// Delay dialog opening to allow menus to close and restore focus to their triggers.
|
||||
// This ensures proper focus restoration when dialogs opened from menus are closed.
|
||||
setTimeout(() => {
|
||||
ref.cdkDialogRefBase = this.dialog.open<R, D, C>(componentOrTemplateRef, _config);
|
||||
}, 0);
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
@let isDrawer = dialogRef?.isDrawer;
|
||||
<section
|
||||
#dialogSection
|
||||
class="tw-flex tw-w-full tw-flex-col tw-self-center tw-overflow-hidden tw-border tw-border-solid tw-border-secondary-100 tw-bg-background tw-text-main"
|
||||
[ngClass]="[
|
||||
width,
|
||||
isDrawer ? 'tw-h-full tw-border-t-0' : 'tw-rounded-t-xl md:tw-rounded-xl tw-shadow-lg',
|
||||
]"
|
||||
cdkTrapFocus
|
||||
cdkTrapFocusAutoCapture
|
||||
[cdkTrapFocusAutoCapture]="true"
|
||||
>
|
||||
@let showHeaderBorder = bodyHasScrolledFrom().top;
|
||||
<header
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
DestroyRef,
|
||||
computed,
|
||||
signal,
|
||||
AfterViewInit,
|
||||
} from "@angular/core";
|
||||
import { toObservable } from "@angular/core/rxjs-interop";
|
||||
import { combineLatest, switchMap } from "rxjs";
|
||||
@@ -48,10 +49,11 @@ import { DialogTitleContainerDirective } from "../directives/dialog-title-contai
|
||||
SpinnerComponent,
|
||||
],
|
||||
})
|
||||
export class DialogComponent {
|
||||
export class DialogComponent implements AfterViewInit {
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly scrollableBody = viewChild.required(CdkScrollable);
|
||||
private readonly scrollBottom = viewChild.required<ElementRef<HTMLDivElement>>("scrollBottom");
|
||||
private readonly dialogSection = viewChild.required<ElementRef<HTMLElement>>("dialogSection");
|
||||
|
||||
protected dialogRef = inject(DialogRef, { optional: true });
|
||||
protected bodyHasScrolledFrom = hasScrolledFrom(this.scrollableBody);
|
||||
@@ -141,4 +143,16 @@ export class DialogComponent {
|
||||
onAnimationEnd() {
|
||||
this.animationCompleted.set(true);
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
// Use setTimeout to ensure focus is captured after Angular's change detection
|
||||
// and the dialog's focus trap has been initialized
|
||||
setTimeout(() => {
|
||||
const section = this.dialogSection().nativeElement;
|
||||
const focusableElement = section.querySelector("[cdkFocusInitial]") as HTMLElement;
|
||||
if (focusableElement && document.activeElement !== focusableElement) {
|
||||
focusableElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,6 +185,12 @@ export class MenuTriggerForDirective implements OnDestroy {
|
||||
this.isOpen = false;
|
||||
this.disposeAll();
|
||||
this.menu().closed.emit();
|
||||
|
||||
// Restore focus to the trigger button when the menu closes.
|
||||
// This ensures that when a dialog opens from a menu item, the CDK Dialog's
|
||||
// restoreFocus will capture the menu trigger button as the element to restore to.
|
||||
// The dialog's ngAfterViewInit will then explicitly move focus into the dialog.
|
||||
this.elementRef.nativeElement.focus();
|
||||
}
|
||||
|
||||
private setupClosingActions(isContextMenu: boolean) {
|
||||
|
||||
@@ -51,6 +51,7 @@ export class TooltipDirective implements OnInit {
|
||||
|
||||
private readonly isVisible = signal(false);
|
||||
private overlayRef: OverlayRef | undefined;
|
||||
private showTimeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||
private elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
|
||||
private overlay = inject(Overlay);
|
||||
private viewContainerRef = inject(ViewContainerRef);
|
||||
@@ -82,12 +83,22 @@ export class TooltipDirective implements OnInit {
|
||||
);
|
||||
|
||||
private destroyTooltip = () => {
|
||||
// Clear any pending show timeout to prevent tooltip from appearing after hide
|
||||
if (this.showTimeoutId !== undefined) {
|
||||
clearTimeout(this.showTimeoutId);
|
||||
this.showTimeoutId = undefined;
|
||||
}
|
||||
this.overlayRef?.dispose();
|
||||
this.overlayRef = undefined;
|
||||
this.isVisible.set(false);
|
||||
};
|
||||
|
||||
protected showTooltip = () => {
|
||||
// Clear any existing timeout before starting a new one
|
||||
if (this.showTimeoutId !== undefined) {
|
||||
clearTimeout(this.showTimeoutId);
|
||||
}
|
||||
|
||||
if (!this.overlayRef) {
|
||||
this.overlayRef = this.overlay.create({
|
||||
...this.defaultPopoverConfig,
|
||||
@@ -97,8 +108,9 @@ export class TooltipDirective implements OnInit {
|
||||
this.overlayRef.attach(this.tooltipPortal);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.showTimeoutId = setTimeout(() => {
|
||||
this.isVisible.set(true);
|
||||
this.showTimeoutId = undefined;
|
||||
}, TOOLTIP_DELAY_MS);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user