mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-11 10:10:25 +01:00
[CL-312] fix dialog scroll blocking + virtual scroll (#9606)
This commit is contained in:
parent
c73ee88126
commit
fdeac58469
@ -5,7 +5,7 @@ import {
|
||||
DialogRef,
|
||||
DIALOG_SCROLL_STRATEGY,
|
||||
} from "@angular/cdk/dialog";
|
||||
import { ComponentType, Overlay, OverlayContainer } from "@angular/cdk/overlay";
|
||||
import { ComponentType, Overlay, OverlayContainer, ScrollStrategy } from "@angular/cdk/overlay";
|
||||
import {
|
||||
Inject,
|
||||
Injectable,
|
||||
@ -25,12 +25,35 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { SimpleConfigurableDialogComponent } from "./simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component";
|
||||
import { SimpleDialogOptions, Translation } from "./simple-dialog/types";
|
||||
|
||||
/**
|
||||
* The default `BlockScrollStrategy` does not work well with virtual scrolling.
|
||||
*
|
||||
* https://github.com/angular/components/issues/7390
|
||||
*/
|
||||
class CustomBlockScrollStrategy implements ScrollStrategy {
|
||||
enable() {
|
||||
document.body.classList.add("tw-overflow-hidden");
|
||||
}
|
||||
|
||||
disable() {
|
||||
document.body.classList.remove("tw-overflow-hidden");
|
||||
}
|
||||
|
||||
/** Noop */
|
||||
attach() {}
|
||||
|
||||
/** Noop */
|
||||
detach() {}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DialogService extends Dialog implements OnDestroy {
|
||||
private _destroy$ = new Subject<void>();
|
||||
|
||||
private backDropClasses = ["tw-fixed", "tw-bg-black", "tw-bg-opacity-30", "tw-inset-0"];
|
||||
|
||||
private defaultScrollStrategy = new CustomBlockScrollStrategy();
|
||||
|
||||
constructor(
|
||||
/** Parent class constructor */
|
||||
_overlay: Overlay,
|
||||
@ -73,6 +96,7 @@ export class DialogService extends Dialog implements OnDestroy {
|
||||
): DialogRef<R, C> {
|
||||
config = {
|
||||
backdropClass: this.backDropClasses,
|
||||
scrollStrategy: this.defaultScrollStrategy,
|
||||
...config,
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,61 @@
|
||||
import { ScrollingModule } from "@angular/cdk/scrolling";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { DialogModule, DialogService } from "../../../dialog";
|
||||
import { IconButtonModule } from "../../../icon-button";
|
||||
import { SectionComponent } from "../../../section";
|
||||
import { TableDataSource, TableModule } from "../../../table";
|
||||
|
||||
@Component({
|
||||
selector: "dialog-virtual-scroll-block",
|
||||
standalone: true,
|
||||
imports: [DialogModule, IconButtonModule, SectionComponent, TableModule, ScrollingModule],
|
||||
template: ` <bit-section>
|
||||
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
|
||||
<bit-table [dataSource]="dataSource">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell bitSortable="id" default>Id</th>
|
||||
<th bitCell bitSortable="name">Name</th>
|
||||
<th bitCell>Options</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body let-rows$>
|
||||
<tr bitRow *cdkVirtualFor="let r of rows$">
|
||||
<td bitCell>{{ r.id }}</td>
|
||||
<td bitCell>{{ r.name }}</td>
|
||||
<td bitCell>
|
||||
<button
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
type="button"
|
||||
aria-label="Options"
|
||||
(click)="openDefaultDialog()"
|
||||
></button>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
</bit-section>`,
|
||||
})
|
||||
export class DialogVirtualScrollBlockComponent implements OnInit {
|
||||
constructor(public dialogService: DialogService) {}
|
||||
|
||||
protected dataSource = new TableDataSource<{ id: number; name: string; other: string }>();
|
||||
|
||||
ngOnInit(): void {
|
||||
this.dataSource.data = [...Array(100).keys()].map((i) => ({
|
||||
id: i,
|
||||
name: `name-${i}`,
|
||||
other: `other-${i}`,
|
||||
}));
|
||||
}
|
||||
|
||||
async openDefaultDialog() {
|
||||
await this.dialogService.openSimpleDialog({
|
||||
type: "info",
|
||||
title: "Foo",
|
||||
content: "Bar",
|
||||
});
|
||||
}
|
||||
}
|
@ -8,7 +8,15 @@ import {
|
||||
componentWrapperDecorator,
|
||||
moduleMetadata,
|
||||
} from "@storybook/angular";
|
||||
import { userEvent, getAllByRole, getByRole, getByLabelText, fireEvent } from "@storybook/test";
|
||||
import {
|
||||
userEvent,
|
||||
getAllByRole,
|
||||
getByRole,
|
||||
getByLabelText,
|
||||
fireEvent,
|
||||
getByText,
|
||||
getAllByLabelText,
|
||||
} from "@storybook/test";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
@ -16,6 +24,7 @@ import { DialogService } from "../../dialog";
|
||||
import { LayoutComponent } from "../../layout";
|
||||
import { I18nMockService } from "../../utils/i18n-mock.service";
|
||||
|
||||
import { DialogVirtualScrollBlockComponent } from "./components/dialog-virtual-scroll-block.component";
|
||||
import { KitchenSinkForm } from "./components/kitchen-sink-form.component";
|
||||
import { KitchenSinkMainComponent } from "./components/kitchen-sink-main.component";
|
||||
import { KitchenSinkTable } from "./components/kitchen-sink-table.component";
|
||||
@ -64,7 +73,9 @@ export default {
|
||||
skipToContent: "Skip to content",
|
||||
submenu: "submenu",
|
||||
toggleCollapse: "toggle collapse",
|
||||
toggleSideNavigation: "toggle side navigation",
|
||||
toggleSideNavigation: "Toggle side navigation",
|
||||
yes: "Yes",
|
||||
no: "No",
|
||||
});
|
||||
},
|
||||
},
|
||||
@ -78,6 +89,7 @@ export default {
|
||||
[
|
||||
{ path: "", redirectTo: "bitwarden", pathMatch: "full" },
|
||||
{ path: "bitwarden", component: KitchenSinkMainComponent },
|
||||
{ path: "virtual-scroll", component: DialogVirtualScrollBlockComponent },
|
||||
],
|
||||
{ useHash: true },
|
||||
),
|
||||
@ -100,6 +112,7 @@ export const Default: Story = {
|
||||
<bit-nav-item text="Bitwarden" route="bitwarden"></bit-nav-item>
|
||||
<bit-nav-divider></bit-nav-divider>
|
||||
</bit-nav-group>
|
||||
<bit-nav-item text="Virtual Scroll" route="virtual-scroll"></bit-nav-item>
|
||||
</bit-nav-group>
|
||||
</bit-side-nav>
|
||||
<router-outlet></router-outlet>
|
||||
@ -165,3 +178,19 @@ export const EmptyTab: Story = {
|
||||
await userEvent.click(emptyTab);
|
||||
},
|
||||
};
|
||||
|
||||
export const VirtualScrollBlockingDialog: Story = {
|
||||
...Default,
|
||||
play: async (context) => {
|
||||
const canvas = context.canvasElement;
|
||||
const navItem = getByText(canvas, "Virtual Scroll");
|
||||
await userEvent.click(navItem);
|
||||
|
||||
const htmlEl = canvas.ownerDocument.documentElement;
|
||||
htmlEl.scrollTop = 2000;
|
||||
|
||||
const dialogButton = getAllByLabelText(canvas, "Options")[0];
|
||||
|
||||
await userEvent.click(dialogButton);
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user