Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(modal): add role option #4670

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions e2e-app/src/app/app.routes.ts
Expand Up @@ -11,6 +11,7 @@ import { DropdownPositionComponent } from './dropdown/position/dropdown-position
import { ModalAutoCloseComponent } from './modal/autoclose/modal-autoclose.component';
import { ModalFocusComponent } from './modal/focus/modal-focus.component';
import { ModalNestingComponent } from './modal/nesting/modal-nesting.component';
import { ModalRoleComponent } from './modal/role/modal-role.component';
import { ModalStackComponent } from './modal/stack/modal-stack.component';
import { ModalStackConfirmationComponent } from './modal/stack-confirmation/modal-stack-confirmation.component';
import { PopoverAutocloseComponent } from './popover/autoclose/popover-autoclose.component';
Expand Down Expand Up @@ -46,6 +47,7 @@ export const ROUTES: Routes = [
{ path: 'autoclose', component: ModalAutoCloseComponent },
{ path: 'focus', component: ModalFocusComponent },
{ path: 'nesting', component: ModalNestingComponent },
{ path: 'role', component: ModalRoleComponent },
{ path: 'stack', component: ModalStackComponent },
{ path: 'stack-confirmation', component: ModalStackConfirmationComponent },
],
Expand Down
11 changes: 11 additions & 0 deletions e2e-app/src/app/modal/role/modal-role.component.html
@@ -0,0 +1,11 @@
<h3>Modal role tests</h3>

<!-- Default modal -->
<button class="btn btn-outline-secondary" type="button" id="open-modal-default" (click)="openModal()">
Default modal
</button>

<!-- Alert dialog modal -->
<button class="btn btn-outline-secondary" type="button" id="open-modal-alertdialog" (click)="openModal('alertdialog')">
Alert dialog modal
</button>
14 changes: 14 additions & 0 deletions e2e-app/src/app/modal/role/modal-role.component.ts
@@ -0,0 +1,14 @@
import { Component } from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';

@Component({
standalone: true,
templateUrl: './modal-role.component.html',
})
export class ModalRoleComponent {
constructor(private modalService: NgbModal) {}

openModal(role?: string) {
this.modalService.open('Modal content', role ? { role } : undefined);
}
}
28 changes: 28 additions & 0 deletions e2e-app/src/app/modal/role/modal-role.e2e-spec.ts
@@ -0,0 +1,28 @@
import { expect } from '@playwright/test';
import { test, getPage, setPage } from '../../../../baseTest';
import { sendKey } from '../../tools.po';
import { waitForModalCount, SELECTOR_MODAL_WINDOW } from '../modal.po';

import { openModal } from './modal-role.po';

test.use({ testURL: 'modal/role', testSelector: 'h3:text("Modal role tests")' });
test.beforeEach(async ({ page }) => setPage(page));

test.describe('Modal', () => {
test.afterEach(async () => {
await sendKey('Escape');
await waitForModalCount(0);
});

test('should open modal with the default role', async () => {
await openModal('default');

await expect(getPage().locator(SELECTOR_MODAL_WINDOW)).toHaveAttribute('role', 'dialog');
});

test('should open modal with role="alertdialog"', async () => {
await openModal('alertdialog');

await expect(getPage().locator(SELECTOR_MODAL_WINDOW)).toHaveAttribute('role', 'alertdialog');
});
});
8 changes: 8 additions & 0 deletions e2e-app/src/app/modal/role/modal-role.po.ts
@@ -0,0 +1,8 @@
import { focusElement, sendKey } from '../../tools.po';
import { waitForModalCount } from '../modal.po';

export const openModal = async (type: string) => {
await focusElement(`#open-modal-${type}`);
await sendKey('Enter');
await waitForModalCount(1);
};
1 change: 1 addition & 0 deletions src/modal/modal-config.spec.ts
Expand Up @@ -18,6 +18,7 @@ describe('NgbModalConfig', () => {
expect(config.container).toBeUndefined();
expect(config.injector).toBeUndefined();
expect(config.fullscreen).toBe(false);
expect(config.role).toBe('dialog');
expect(config.keyboard).toBe(true);
expect(config.scrollable).toBeUndefined();
expect(config.size).toBeUndefined();
Expand Down
10 changes: 10 additions & 0 deletions src/modal/modal-config.ts
Expand Up @@ -89,6 +89,15 @@ export interface NgbModalOptions {
*/
keyboard?: boolean;

/**
* `role` attribute value to set on the modal window.
*
* Default value is `dialog`.
*
* @since 16.1.0
*/
role?: 'alertdialog' | 'dialog' | string;

/**
* Scrollable modal content (false by default).
*
Expand Down Expand Up @@ -160,6 +169,7 @@ export class NgbModalConfig implements Required<NgbModalOptions> {
fullscreen: 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | boolean | string = false;
injector: Injector;
keyboard = true;
role: 'alertdialog' | 'dialog' | string = 'dialog';
scrollable: boolean;
size: 'sm' | 'lg' | 'xl' | string;
windowClass: string;
Expand Down
1 change: 1 addition & 0 deletions src/modal/modal-ref.ts
Expand Up @@ -47,6 +47,7 @@ const WINDOW_ATTRIBUTES: string[] = [
'centered',
'fullscreen',
'keyboard',
'role',
'scrollable',
'size',
'windowClass',
Expand Down
9 changes: 9 additions & 0 deletions src/modal/modal-window.spec.ts
Expand Up @@ -62,6 +62,15 @@ describe('ngb-modal-dialog', () => {
expect(dialogEl.getAttribute('role')).toBe('document');
});

it('should render default modal window with a specified role', () => {
fixture.componentInstance.role = 'alertdialog';
fixture.detectChanges();
const dialogEl: Element = fixture.nativeElement.querySelector('.modal-dialog');

expect(fixture.nativeElement.getAttribute('role')).toBe('alertdialog');
expect(dialogEl.getAttribute('role')).toBe('document');
});

it('should render modal dialog with a specified class', () => {
fixture.componentInstance.modalDialogClass = 'custom-dialog-class';
fixture.detectChanges();
Expand Down
3 changes: 2 additions & 1 deletion src/modal/modal-window.ts
Expand Up @@ -27,11 +27,11 @@ import { isString, reflow } from '../util/util';
host: {
'[class]': '"modal d-block" + (windowClass ? " " + windowClass : "")',
'[class.fade]': 'animation',
role: 'dialog',
tabindex: '-1',
'[attr.aria-modal]': 'true',
'[attr.aria-labelledby]': 'ariaLabelledBy',
'[attr.aria-describedby]': 'ariaDescribedBy',
'[attr.role]': 'role',
},
template: `
<div
Expand Down Expand Up @@ -69,6 +69,7 @@ export class NgbModalWindow implements OnInit, OnDestroy {
@Input() centered: string;
@Input() fullscreen: string | boolean;
@Input() keyboard = true;
@Input() role: string = 'dialog';
@Input() scrollable: string;
@Input() size: string;
@Input() windowClass: string;
Expand Down