1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 06:13:38 +00:00

[PM-17663] Fix extension Fill button display on page load (#15359)

* [PM-17663] Convert vault-list-items-container inputs to signals

- Cleaned up some grouping logic
- Cleaned up strict null checks and removed eslint comment

* [PM-17663] Prefer undefined over null

* [PM-17663] Fix flashing Fill buttons
This commit is contained in:
Shane Melton
2025-07-02 13:19:17 -07:00
committed by GitHub
parent 023b057f3e
commit ece5ebe844
4 changed files with 94 additions and 105 deletions

View File

@@ -4,7 +4,7 @@
[title]="((currentURIIsBlocked$ | async) ? 'itemSuggestions' : 'autofillSuggestions') | i18n" [title]="((currentURIIsBlocked$ | async) ? 'itemSuggestions' : 'autofillSuggestions') | i18n"
[showRefresh]="showRefresh" [showRefresh]="showRefresh"
(onRefresh)="refreshCurrentTab()" (onRefresh)="refreshCurrentTab()"
[description]="(showEmptyAutofillTip$ | async) ? ('autofillSuggestionsTip' | i18n) : null" [description]="(showEmptyAutofillTip$ | async) ? ('autofillSuggestionsTip' | i18n) : undefined"
showAutofillButton showAutofillButton
[disableDescriptionMargin]="showEmptyAutofillTip$ | async" [disableDescriptionMargin]="showEmptyAutofillTip$ | async"
[primaryActionAutofill]="clickItemsToAutofillVaultView$ | async" [primaryActionAutofill]="clickItemsToAutofillVaultView$ | async"

View File

@@ -1,7 +1,7 @@
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop"; import { toSignal } from "@angular/core/rxjs-interop";
import { combineLatest, map, Observable } from "rxjs"; import { combineLatest, map, Observable, startWith } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
@@ -41,7 +41,9 @@ export class AutofillVaultListItemsComponent {
/** Flag indicating whether the login item should automatically autofill when clicked */ /** Flag indicating whether the login item should automatically autofill when clicked */
protected clickItemsToAutofillVaultView$: Observable<boolean> = protected clickItemsToAutofillVaultView$: Observable<boolean> =
this.vaultSettingsService.clickItemsToAutofillVaultView$; this.vaultSettingsService.clickItemsToAutofillVaultView$.pipe(
startWith(true), // Start with true to avoid flashing the fill button on first load
);
protected groupByType = toSignal( protected groupByType = toSignal(
this.vaultPopupItemsService.hasFilterApplied$.pipe(map((hasFilter) => !hasFilter)), this.vaultPopupItemsService.hasFilterApplied$.pipe(map((hasFilter) => !hasFilter)),
@@ -74,9 +76,7 @@ export class AutofillVaultListItemsComponent {
private vaultPopupItemsService: VaultPopupItemsService, private vaultPopupItemsService: VaultPopupItemsService,
private vaultPopupAutofillService: VaultPopupAutofillService, private vaultPopupAutofillService: VaultPopupAutofillService,
private vaultSettingsService: VaultSettingsService, private vaultSettingsService: VaultSettingsService,
) { ) {}
// TODO: Migrate logic to show Autofill policy toast PM-8144
}
/** /**
* Refreshes the current tab to re-populate the autofill ciphers. * Refreshes the current tab to re-populate the autofill ciphers.

View File

@@ -1,8 +1,8 @@
<bit-section <bit-section
*ngIf="cipherGroups$().length > 0 || description" *ngIf="cipherGroups().length > 0 || description()"
[disableMargin]="disableSectionMargin" [disableMargin]="disableSectionMargin()"
> >
<ng-container *ngIf="collapsibleKey"> <ng-container *ngIf="collapsibleKey()">
<button <button
class="tw-group/vault-section-header hover:tw-bg-primary-100 tw-rounded-md tw-pl-1 tw-w-full tw-border-x-0 tw-border-t-0 tw-border-b tw-border-solid focus-visible:tw-outline-none focus-visible:tw-ring-inset focus-visible:tw-ring-2 focus-visible:tw-ring-primary-600" class="tw-group/vault-section-header hover:tw-bg-primary-100 tw-rounded-md tw-pl-1 tw-w-full tw-border-x-0 tw-border-t-0 tw-border-b tw-border-solid focus-visible:tw-outline-none focus-visible:tw-ring-inset focus-visible:tw-ring-2 focus-visible:tw-ring-primary-600"
[ngClass]="{ [ngClass]="{
@@ -22,7 +22,7 @@
</bit-disclosure> </bit-disclosure>
</ng-container> </ng-container>
<ng-container *ngIf="!collapsibleKey"> <ng-container *ngIf="!collapsibleKey()">
<div class="tw-pl-1"> <div class="tw-pl-1">
<ng-container *ngTemplateOutlet="sectionHeader"></ng-container> <ng-container *ngTemplateOutlet="sectionHeader"></ng-container>
</div> </div>
@@ -34,10 +34,10 @@
<ng-template #sectionHeader> <ng-template #sectionHeader>
<bit-section-header class="tw-p-0.5 -tw-mx-0.5"> <bit-section-header class="tw-p-0.5 -tw-mx-0.5">
<h2 bitTypography="h6"> <h2 bitTypography="h6">
{{ title }} {{ title() }}
</h2> </h2>
<button <button
*ngIf="showRefresh" *ngIf="showRefresh()"
bitIconButton="bwi-refresh" bitIconButton="bwi-refresh"
type="button" type="button"
size="small" size="small"
@@ -48,13 +48,13 @@
<span <span
[ngClass]="{ [ngClass]="{
'group-hover/vault-section-header:tw-hidden group-focus-visible/vault-section-header:tw-hidden': 'group-hover/vault-section-header:tw-hidden group-focus-visible/vault-section-header:tw-hidden':
collapsibleKey && sectionOpenState(), collapsibleKey() && sectionOpenState(),
'tw-hidden': collapsibleKey && !sectionOpenState(), 'tw-hidden': collapsibleKey() && !sectionOpenState(),
}" }"
> >
{{ ciphers().length }} {{ ciphers().length }}
</span> </span>
<span class="tw-pr-1" *ngIf="collapsibleKey"> <span class="tw-pr-1" *ngIf="collapsibleKey()">
<i <i
class="bwi tw-text-main" class="bwi tw-text-main"
[ngClass]="{ [ngClass]="{
@@ -71,18 +71,18 @@
<ng-template #descriptionText> <ng-template #descriptionText>
<div <div
*ngIf="description" *ngIf="description()"
class="tw-text-muted tw-px-1 tw-mb-2" class="tw-text-muted tw-px-1 tw-mb-2"
[ngClass]="{ '!tw-mb-0': disableDescriptionMargin }" [ngClass]="{ '!tw-mb-0': disableDescriptionMargin() }"
bitTypography="body2" bitTypography="body2"
> >
{{ description }} {{ description() }}
</div> </div>
</ng-template> </ng-template>
<ng-template #itemGroup> <ng-template #itemGroup>
<bit-item-group> <bit-item-group>
<ng-container *ngFor="let group of cipherGroups$()"> <ng-container *ngFor="let group of cipherGroups()">
<ng-container *ngIf="group.subHeaderKey"> <ng-container *ngIf="group.subHeaderKey">
<h3 class="tw-text-muted tw-text-xs tw-font-semibold tw-pl-1 tw-mb-1 bit-compact:tw-m-0"> <h3 class="tw-text-muted tw-text-xs tw-font-semibold tw-pl-1 tw-mb-1 bit-compact:tw-m-0">
{{ group.subHeaderKey | i18n }} {{ group.subHeaderKey | i18n }}
@@ -97,7 +97,7 @@
(click)="primaryActionOnSelect(cipher)" (click)="primaryActionOnSelect(cipher)"
(dblclick)="launchCipher(cipher)" (dblclick)="launchCipher(cipher)"
[appA11yTitle]=" [appA11yTitle]="
cipherItemTitleKey(cipher) | async | i18n: cipher.name : cipher.login.username cipherItemTitleKey()(cipher) | i18n: cipher.name : cipher.login.username
" "
class="{{ itemHeightClass }}" class="{{ itemHeightClass }}"
> >
@@ -109,7 +109,7 @@
*ngIf="cipher.organizationId" *ngIf="cipher.organizationId"
slot="default-trailing" slot="default-trailing"
appOrgIcon appOrgIcon
[tierType]="cipher.organization.productTierType" [tierType]="cipher.organization!.productTierType"
[size]="'small'" [size]="'small'"
[appA11yTitle]="orgIconTooltip(cipher)" [appA11yTitle]="orgIconTooltip(cipher)"
></i> ></i>
@@ -122,7 +122,7 @@
</button> </button>
<ng-container slot="end"> <ng-container slot="end">
<bit-item-action *ngIf="!(hideAutofillButton$ | async)"> <bit-item-action *ngIf="!hideAutofillButton()">
<button <button
type="button" type="button"
bitBadge bitBadge
@@ -134,7 +134,7 @@
{{ "fill" | i18n }} {{ "fill" | i18n }}
</button> </button>
</bit-item-action> </bit-item-action>
<bit-item-action *ngIf="!showAutofillButton && cipher.canLaunch"> <bit-item-action *ngIf="!showAutofillButton() && cipher.canLaunch">
<button <button
type="button" type="button"
bitIconButton="bwi-external-link" bitIconButton="bwi-external-link"
@@ -147,8 +147,8 @@
<app-item-copy-actions [cipher]="cipher"></app-item-copy-actions> <app-item-copy-actions [cipher]="cipher"></app-item-copy-actions>
<app-item-more-options <app-item-more-options
[cipher]="cipher" [cipher]="cipher"
[hideAutofillOptions]="hideAutofillOptions$ | async" [hideAutofillOptions]="hideAutofillMenuOptions()"
[showViewOption]="primaryActionAutofill" [showViewOption]="primaryActionAutofill()"
></app-item-more-options> ></app-item-more-options>
</ng-container> </ng-container>
</bit-item> </bit-item>

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { CdkVirtualScrollViewport, ScrollingModule } from "@angular/cdk/scrolling"; import { CdkVirtualScrollViewport, ScrollingModule } from "@angular/cdk/scrolling";
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { import {
@@ -8,18 +6,17 @@ import {
Component, Component,
EventEmitter, EventEmitter,
inject, inject,
Input,
Output, Output,
Signal, Signal,
signal, signal,
ViewChild, ViewChild,
computed, computed,
OnInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
input, input,
} from "@angular/core"; } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { firstValueFrom, Observable, map } from "rxjs"; import { firstValueFrom, map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -53,7 +50,10 @@ import {
import { BrowserApi } from "../../../../../platform/browser/browser-api"; import { BrowserApi } from "../../../../../platform/browser/browser-api";
import BrowserPopupUtils from "../../../../../platform/browser/browser-popup-utils"; import BrowserPopupUtils from "../../../../../platform/browser/browser-popup-utils";
import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service";
import { VaultPopupSectionService } from "../../../services/vault-popup-section.service"; import {
VaultPopupSectionService,
PopupSectionOpen,
} from "../../../services/vault-popup-section.service";
import { PopupCipherView } from "../../../views/popup-cipher.view"; import { PopupCipherView } from "../../../views/popup-cipher.view";
import { ItemCopyActionsComponent } from "../item-copy-action/item-copy-actions.component"; import { ItemCopyActionsComponent } from "../item-copy-action/item-copy-actions.component";
import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options.component"; import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options.component";
@@ -81,17 +81,25 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options
templateUrl: "vault-list-items-container.component.html", templateUrl: "vault-list-items-container.component.html",
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class VaultListItemsContainerComponent implements OnInit, AfterViewInit { export class VaultListItemsContainerComponent implements AfterViewInit {
private compactModeService = inject(CompactModeService); private compactModeService = inject(CompactModeService);
private vaultPopupSectionService = inject(VaultPopupSectionService); private vaultPopupSectionService = inject(VaultPopupSectionService);
@ViewChild(CdkVirtualScrollViewport, { static: false }) viewPort: CdkVirtualScrollViewport; @ViewChild(CdkVirtualScrollViewport, { static: false }) viewPort!: CdkVirtualScrollViewport;
@ViewChild(DisclosureComponent) disclosure: DisclosureComponent; @ViewChild(DisclosureComponent) disclosure!: DisclosureComponent;
/** /**
* Indicates whether the section should be open or closed if collapsibleKey is provided * Indicates whether the section should be open or closed if collapsibleKey is provided
*/ */
protected sectionOpenState: Signal<boolean> | undefined; protected sectionOpenState: Signal<boolean> = computed(() => {
if (!this.collapsibleKey()) {
return true;
}
return (
this.vaultPopupSectionService.getOpenDisplayStateForSection(this.collapsibleKey()!)() ?? true
);
});
/** /**
* The class used to set the height of a bit item's inner content. * The class used to set the height of a bit item's inner content.
@@ -115,7 +123,7 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
* Timeout used to add a small delay when selecting a cipher to allow for double click to launch * Timeout used to add a small delay when selecting a cipher to allow for double click to launch
* @private * @private
*/ */
private viewCipherTimeout: number | null; private viewCipherTimeout?: number;
ciphers = input<PopupCipherView[]>([]); ciphers = input<PopupCipherView[]>([]);
@@ -123,31 +131,33 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
* If true, we will group ciphers by type (Login, Card, Identity) * If true, we will group ciphers by type (Login, Card, Identity)
* within subheadings in a single container, converted to a WritableSignal. * within subheadings in a single container, converted to a WritableSignal.
*/ */
groupByType = input<boolean>(false); groupByType = input<boolean | undefined>(false);
/** /**
* Computed signal for a grouped list of ciphers with an optional header * Computed signal for a grouped list of ciphers with an optional header
*/ */
cipherGroups$ = computed< cipherGroups = computed<
{ {
subHeaderKey?: string | null; subHeaderKey?: string;
ciphers: PopupCipherView[]; ciphers: PopupCipherView[];
}[] }[]
>(() => { >(() => {
const groups: { [key: string]: CipherView[] } = {}; // Not grouping by type, return a single group with all ciphers
if (!this.groupByType()) {
return [{ ciphers: this.ciphers() }];
}
const groups: Record<string, PopupCipherView[]> = {};
this.ciphers().forEach((cipher) => { this.ciphers().forEach((cipher) => {
let groupKey; let groupKey = "all";
switch (cipher.type) {
if (this.groupByType()) { case CipherType.Card:
switch (cipher.type) { groupKey = "cards";
case CipherType.Card: break;
groupKey = "cards"; case CipherType.Identity:
break; groupKey = "identities";
case CipherType.Identity: break;
groupKey = "identities";
break;
}
} }
if (!groups[groupKey]) { if (!groups[groupKey]) {
@@ -157,17 +167,16 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
groups[groupKey].push(cipher); groups[groupKey].push(cipher);
}); });
return Object.keys(groups).map((key) => ({ return Object.entries(groups).map(([key, ciphers]) => ({
subHeaderKey: this.groupByType ? key : "", subHeaderKey: key != "all" ? key : undefined,
ciphers: groups[key], ciphers: ciphers,
})); }));
}); });
/** /**
* Title for the vault list item section. * Title for the vault list item section.
*/ */
@Input() title = input<string | undefined>(undefined);
title: string;
/** /**
* Optionally allow the items to be collapsed. * Optionally allow the items to be collapsed.
@@ -175,21 +184,18 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
* The key must be added to the state definition in `vault-popup-section.service.ts` since the * The key must be added to the state definition in `vault-popup-section.service.ts` since the
* collapsed state is stored locally. * collapsed state is stored locally.
*/ */
@Input() collapsibleKey = input<keyof PopupSectionOpen | undefined>(undefined);
collapsibleKey: "favorites" | "allItems" | undefined;
/** /**
* Optional description for the vault list item section. Will be shown below the title even when * Optional description for the vault list item section. Will be shown below the title even when
* no ciphers are available. * no ciphers are available.
*/ */
@Input() description = input<string | undefined>(undefined);
description: string;
/** /**
* Option to show a refresh button in the section header. * Option to show a refresh button in the section header.
*/ */
@Input({ transform: booleanAttribute }) showRefresh = input(false, { transform: booleanAttribute });
showRefresh: boolean;
/** /**
* Event emitted when the refresh button is clicked. * Event emitted when the refresh button is clicked.
@@ -200,66 +206,61 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
/** /**
* Flag indicating that the current tab location is blocked * Flag indicating that the current tab location is blocked
*/ */
currentURIIsBlocked$: Observable<boolean> = currentURIIsBlocked = toSignal(this.vaultPopupAutofillService.currentTabIsOnBlocklist$);
this.vaultPopupAutofillService.currentTabIsOnBlocklist$;
/** /**
* Resolved i18n key to use for suggested cipher items * Resolved i18n key to use for suggested cipher items
*/ */
cipherItemTitleKey = (cipher: CipherView) => cipherItemTitleKey = computed(() => {
this.currentURIIsBlocked$.pipe( return (cipher: CipherView) => {
map((uriIsBlocked) => { const hasUsername = cipher.login?.username != null;
const hasUsername = cipher.login?.username != null; const key =
const key = this.primaryActionAutofill && !uriIsBlocked ? "autofillTitle" : "viewItemTitle"; this.primaryActionAutofill() && !this.currentURIIsBlocked()
return hasUsername ? `${key}WithField` : key; ? "autofillTitle"
}), : "viewItemTitle";
); return hasUsername ? `${key}WithField` : key;
};
});
/** /**
* Option to show the autofill button for each item. * Option to show the autofill button for each item.
*/ */
@Input({ transform: booleanAttribute }) showAutofillButton = input(false, { transform: booleanAttribute });
showAutofillButton: boolean;
/** /**
* Flag indicating whether the suggested cipher item autofill button should be shown or not * Flag indicating whether the suggested cipher item autofill button should be shown or not
*/ */
hideAutofillButton$ = this.currentURIIsBlocked$.pipe( hideAutofillButton = computed(
map((uriIsBlocked) => !this.showAutofillButton || uriIsBlocked || this.primaryActionAutofill), () => !this.showAutofillButton() || this.currentURIIsBlocked() || this.primaryActionAutofill(),
); );
/** /**
* Flag indicating whether the cipher item autofill options should be shown or not * Flag indicating whether the cipher item autofill menu options should be shown or not
*/ */
hideAutofillOptions$: Observable<boolean> = this.currentURIIsBlocked$.pipe( hideAutofillMenuOptions = computed(() => this.currentURIIsBlocked() || this.showAutofillButton());
map((uriIsBlocked) => uriIsBlocked || this.showAutofillButton),
);
/** /**
* Option to perform autofill operation as the primary action for autofill suggestions. * Option to perform autofill operation as the primary action for autofill suggestions.
*/ */
@Input({ transform: booleanAttribute }) primaryActionAutofill = input(false, { transform: booleanAttribute });
primaryActionAutofill: boolean;
/** /**
* Remove the bottom margin from the bit-section in this component * Remove the bottom margin from the bit-section in this component
* (used for containers at the end of the page where bottom margin is not needed) * (used for containers at the end of the page where bottom margin is not needed)
*/ */
@Input({ transform: booleanAttribute }) disableSectionMargin = input(false, { transform: booleanAttribute });
disableSectionMargin: boolean = false;
/** /**
* Remove the description margin * Remove the description margin
*/ */
@Input({ transform: booleanAttribute }) disableDescriptionMargin = input(false, { transform: booleanAttribute });
disableDescriptionMargin: boolean = false;
/** /**
* The tooltip text for the organization icon for ciphers that belong to an organization. * The tooltip text for the organization icon for ciphers that belong to an organization.
* @param cipher * @param cipher
*/ */
orgIconTooltip(cipher: PopupCipherView) { orgIconTooltip(cipher: PopupCipherView) {
if (cipher.collectionIds.length > 1) { if (cipher.collectionIds.length > 1 || !cipher.collections) {
return this.i18nService.t("nCollections", cipher.collectionIds.length); return this.i18nService.t("nCollections", cipher.collectionIds.length);
} }
@@ -279,16 +280,6 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
private accountService: AccountService, private accountService: AccountService,
) {} ) {}
ngOnInit(): void {
if (!this.collapsibleKey) {
return;
}
this.sectionOpenState = this.vaultPopupSectionService.getOpenDisplayStateForSection(
this.collapsibleKey,
);
}
async ngAfterViewInit() { async ngAfterViewInit() {
const autofillShortcut = await this.platformUtilsService.getAutofillKeyboardShortcut(); const autofillShortcut = await this.platformUtilsService.getAutofillKeyboardShortcut();
@@ -301,10 +292,8 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
} }
} }
async primaryActionOnSelect(cipher: CipherView) { primaryActionOnSelect(cipher: CipherView) {
const isBlocked = await firstValueFrom(this.currentURIIsBlocked$); return this.primaryActionAutofill() && !this.currentURIIsBlocked()
return this.primaryActionAutofill && !isBlocked
? this.doAutofill(cipher) ? this.doAutofill(cipher)
: this.onViewCipher(cipher); : this.onViewCipher(cipher);
} }
@@ -320,7 +309,7 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
// If there is a view action pending, clear it // If there is a view action pending, clear it
if (this.viewCipherTimeout != null) { if (this.viewCipherTimeout != null) {
window.clearTimeout(this.viewCipherTimeout); window.clearTimeout(this.viewCipherTimeout);
this.viewCipherTimeout = null; this.viewCipherTimeout = undefined;
} }
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
@@ -363,7 +352,7 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
}); });
} finally { } finally {
// Ensure the timeout is always cleared // Ensure the timeout is always cleared
this.viewCipherTimeout = null; this.viewCipherTimeout = undefined;
} }
}, },
cipher.canLaunch ? 200 : 0, cipher.canLaunch ? 200 : 0,
@@ -374,12 +363,12 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
* Update section open/close state based on user action * Update section open/close state based on user action
*/ */
async toggleSectionOpen() { async toggleSectionOpen() {
if (!this.collapsibleKey) { if (!this.collapsibleKey()) {
return; return;
} }
await this.vaultPopupSectionService.updateSectionOpenStoredState( await this.vaultPopupSectionService.updateSectionOpenStoredState(
this.collapsibleKey, this.collapsibleKey()!,
this.disclosure.open, this.disclosure.open,
); );
} }