mirror of
https://github.com/bitwarden/browser
synced 2026-02-28 02:23:25 +00:00
[PM-24178] Handle focus when routed dialog closes in vault table
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||
import { ActivatedRoute, NavigationExtras, Params, Router } from "@angular/router";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
@@ -588,7 +588,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
queryParamsHandling: "merge",
|
||||
replaceUrl: true,
|
||||
state: {
|
||||
focusMainAfterNav: false,
|
||||
focusAfterNav: false,
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -812,7 +812,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
async editCipherAttachments(cipher: CipherView) {
|
||||
if (cipher.reprompt !== 0 && !(await this.passwordRepromptService.showPasswordPrompt())) {
|
||||
this.go({ cipherId: null, itemId: null });
|
||||
this.go({ cipherId: null, itemId: null }, this.configureRouterFocusToCipher(cipher.id));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -869,7 +869,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
!(await this.passwordRepromptService.showPasswordPrompt())
|
||||
) {
|
||||
// didn't pass password prompt, so don't open add / edit modal
|
||||
this.go({ cipherId: null, itemId: null });
|
||||
this.go({ cipherId: null, itemId: null }, this.configureRouterFocusToCipher(cipher.id));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -893,7 +893,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
!(await this.passwordRepromptService.showPasswordPrompt())
|
||||
) {
|
||||
// Didn't pass password prompt, so don't open add / edit modal.
|
||||
await this.go({ cipherId: null, itemId: null, action: null });
|
||||
await this.go(
|
||||
{ cipherId: null, itemId: null, action: null },
|
||||
this.configureRouterFocusToCipher(cipher.id),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -948,7 +951,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
// Clear the query params when the dialog closes
|
||||
await this.go({ cipherId: null, itemId: null, action: null });
|
||||
await this.go(
|
||||
{ cipherId: null, itemId: null, action: null },
|
||||
this.configureRouterFocusToCipher(formConfig.originalCipher?.id),
|
||||
);
|
||||
}
|
||||
|
||||
async cloneCipher(cipher: CipherView) {
|
||||
@@ -1427,7 +1433,25 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
private go(queryParams: any = null) {
|
||||
/**
|
||||
* Helper function to set up the `state.focusAfterNav` property for dialog router navigation if
|
||||
* the cipherId exists. If it doesn't exist, returns undefined.
|
||||
*
|
||||
* This ensures that when the routed dialog is closed, the focus returns to the cipher button in
|
||||
* the vault table, which allows keyboard users to continue navigating uninterrupted.
|
||||
*
|
||||
* @param cipherId id of cipher
|
||||
* @returns Partial<NavigationExtras>, specifically the state.focusAfterNav property, or undefined
|
||||
*/
|
||||
private configureRouterFocusToCipher(cipherId?: string): Partial<NavigationExtras> | undefined {
|
||||
if (cipherId) {
|
||||
return {
|
||||
state: { focusAfterNav: `#cipher-btn-${cipherId}` },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private go(queryParams: any = null, navigateOptions?: NavigationExtras) {
|
||||
if (queryParams == null) {
|
||||
queryParams = {
|
||||
type: this.activeFilter.cipherType,
|
||||
@@ -1441,6 +1465,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
queryParams: queryParams,
|
||||
queryParamsHandling: "merge",
|
||||
replaceUrl: true,
|
||||
...navigateOptions,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
[route]="product.appRoute"
|
||||
[attr.icon]="product.icon"
|
||||
[forceActiveStyles]="product.isActive"
|
||||
focusAfterNavTarget="body"
|
||||
>
|
||||
</bit-nav-item>
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
"
|
||||
class="tw-group/product-link tw-flex tw-h-24 tw-w-28 tw-flex-col tw-items-center tw-justify-center tw-rounded tw-p-1 tw-text-primary-600 tw-outline-none hover:tw-bg-background-alt hover:tw-text-primary-700 hover:tw-no-underline focus-visible:!tw-ring-2 focus-visible:!tw-ring-primary-700"
|
||||
ariaCurrentWhenActive="page"
|
||||
[state]="{
|
||||
focusAfterNav: 'body',
|
||||
}"
|
||||
>
|
||||
<i class="bwi {{ product.icon }} tw-text-4xl !tw-m-0 !tw-mb-1"></i>
|
||||
<span
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
</td>
|
||||
<td bitCell [ngClass]="RowHeightClass" class="tw-truncate">
|
||||
<div class="tw-inline-flex tw-w-full">
|
||||
<!-- Opt out of router focus manager via [state] input, since the dialog will handle focus -->
|
||||
<button
|
||||
bitLink
|
||||
class="tw-overflow-hidden tw-text-ellipsis tw-text-start tw-leading-snug"
|
||||
@@ -27,6 +28,10 @@
|
||||
type="button"
|
||||
appStopProp
|
||||
aria-haspopup="true"
|
||||
id="cipher-btn-{{ cipher.id }}"
|
||||
[state]="{
|
||||
focusAfterNav: false,
|
||||
}"
|
||||
>
|
||||
{{ cipher.name }}
|
||||
</button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||
import { ActivatedRoute, NavigationExtras, Params, Router } from "@angular/router";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
@@ -424,7 +424,7 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
queryParamsHandling: "merge",
|
||||
replaceUrl: true,
|
||||
state: {
|
||||
focusMainAfterNav: false,
|
||||
focusAfterNav: false,
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -971,7 +971,10 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
}
|
||||
|
||||
// Clear the query params when the dialog closes
|
||||
await this.go({ cipherId: null, itemId: null, action: null });
|
||||
await this.go(
|
||||
{ cipherId: null, itemId: null, action: null },
|
||||
this.configureRouterFocusToCipher(formConfig.originalCipher?.id),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1031,7 +1034,10 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
!(await this.passwordRepromptService.showPasswordPrompt())
|
||||
) {
|
||||
// didn't pass password prompt, so don't open add / edit modal
|
||||
await this.go({ cipherId: null, itemId: null, action: null });
|
||||
await this.go(
|
||||
{ cipherId: null, itemId: null, action: null },
|
||||
this.configureRouterFocusToCipher(cipher.id),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1073,7 +1079,10 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
!(await this.passwordRepromptService.showPasswordPrompt())
|
||||
) {
|
||||
// Didn't pass password prompt, so don't open add / edit modal.
|
||||
await this.go({ cipherId: null, itemId: null, action: null });
|
||||
await this.go(
|
||||
{ cipherId: null, itemId: null, action: null },
|
||||
this.configureRouterFocusToCipher(cipher.id),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1552,7 +1561,25 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
this.vaultItemsComponent?.clearSelection();
|
||||
}
|
||||
|
||||
private async go(queryParams: any = null) {
|
||||
/**
|
||||
* Helper function to set up the `state.focusAfterNav` property for dialog router navigation if
|
||||
* the cipherId exists. If it doesn't exist, returns undefined.
|
||||
*
|
||||
* This ensures that when the routed dialog is closed, the focus returns to the cipher button in
|
||||
* the vault table, which allows keyboard users to continue navigating uninterrupted.
|
||||
*
|
||||
* @param cipherId id of cipher
|
||||
* @returns Partial<NavigationExtras>, specifically the state.focusAfterNav property, or undefined
|
||||
*/
|
||||
private configureRouterFocusToCipher(cipherId?: string): Partial<NavigationExtras> | undefined {
|
||||
if (cipherId) {
|
||||
return {
|
||||
state: { focusAfterNav: `#cipher-btn-${cipherId}` },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async go(queryParams: any = null, navigateOptions?: NavigationExtras) {
|
||||
if (queryParams == null) {
|
||||
queryParams = {
|
||||
favorites: this.activeFilter.isFavorites || null,
|
||||
@@ -1568,6 +1595,7 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
queryParams: queryParams,
|
||||
queryParamsHandling: "merge",
|
||||
replaceUrl: true,
|
||||
...navigateOptions,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user