Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…10472): dialog focuses on content on show
  • Loading branch information
BGBRWR committed Apr 11, 2024
1 parent b786be6 commit 4522279
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 70 deletions.
17 changes: 13 additions & 4 deletions src/app/components/dialog/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ const hideAnimation = animation([animate('{{transition}}', style({ transform: '{
*ngIf="maskVisible"
[class]="maskStyleClass"
[style]="maskStyle"
(focus)="containerFocus($event)"
[ngClass]="{
'p-dialog-mask': true,
'p-component-overlay p-component-overlay-enter': this.modal,
Expand Down Expand Up @@ -311,7 +310,7 @@ export class Dialog implements AfterContentInit, OnInit, OnDestroy {
*/
@Input({ transform: numberAttribute }) minY: number = 0;
/**
* When enabled, first button receives focus on show.
* When enabled, first focusable element receives focus on show.
* @group Props
*/
@Input({ transform: booleanAttribute }) focusOnShow: boolean = true;
Expand Down Expand Up @@ -599,12 +598,22 @@ export class Dialog implements AfterContentInit, OnInit, OnDestroy {
return this.header !== null ? UniqueComponentId() + '_header' : null;
}

focus() {
let focusable = DomHandler.getFocusableElement(this.container, '[autofocus]');
focus(focusParentElement = this.contentViewChild.nativeElement) {
let focusable = DomHandler.getFocusableElement(focusParentElement, '[autofocus]');
if (focusable) {
this.zone.runOutsideAngular(() => {
setTimeout(() => focusable.focus(), 5);
});
return;
}
const focusableElement = DomHandler.getFocusableElement(focusParentElement);
if (focusableElement) {
this.zone.runOutsideAngular(() => {
setTimeout(() => focusableElement.focus(), 5);
});
} else if (this.footerViewChild) {
// If the content section is empty try to focus on footer
this.focus(this.footerViewChild.nativeElement);
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/app/components/dynamicdialog/dynamicdialog-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ export class DynamicDialogConfig<T = any> {
* @group Props
*/
focusOnShow?: boolean = true;
/**
* When enabled, can only focus on elements inside the dialog.
* @group Props
*/
focusTrap?: boolean = true;
/**
* Base zIndex value to use in layering.
* @group Props
Expand Down
94 changes: 29 additions & 65 deletions src/app/components/dynamicdialog/dynamicdialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '@angular/core';
import { PrimeNGConfig, SharedModule, TranslationKeys } from 'primeng/api';
import { DomHandler } from 'primeng/dom';
import { FocusTrapModule } from 'primeng/focustrap';
import { TimesIcon } from 'primeng/icons/times';
import { WindowMaximizeIcon } from 'primeng/icons/windowmaximize';
import { WindowMinimizeIcon } from 'primeng/icons/windowminimize';
Expand Down Expand Up @@ -64,6 +65,8 @@ const hideAnimation = animation([animate('{{transition}}', style({ transform: '{
(@animation.done)="onAnimationEnd($event)"
role="dialog"
*ngIf="visible"
pFocusTrap
[pFocusTrapDisabled]="config.focusTrap === false"
[style.width]="config.width"
[style.height]="config.height"
[attr.aria-labelledby]="ariaLabelledBy"
Expand Down Expand Up @@ -93,7 +96,7 @@ const hideAnimation = animation([animate('{{transition}}', style({ transform: '{
<ng-template pDynamicDialogContent *ngIf="!contentTemplate"></ng-template>
<ng-container *ngComponentOutlet="contentTemplate"></ng-container>
</div>
<div class="p-dialog-footer" *ngIf="config.footer || footerTemplate">
<div #footer class="p-dialog-footer" *ngIf="config.footer || footerTemplate">
<ng-container *ngIf="!footerTemplate">
{{ config.footer }}
</ng-container>
Expand Down Expand Up @@ -143,6 +146,8 @@ export class DynamicDialogComponent implements AfterViewInit, OnDestroy {

@ViewChild('content') contentViewChild: Nullable<ElementRef>;

@ViewChild('footer') footerViewChild: Nullable<ElementRef>;

@ViewChild('titlebar') headerViewChild: Nullable<ElementRef>;

childComponentType: Nullable<Type<any>>;
Expand Down Expand Up @@ -217,6 +222,14 @@ export class DynamicDialogComponent implements AfterViewInit, OnDestroy {
}
}

get parentContent() {
const domElements = Array.from(this.document.getElementsByClassName('p-dialog'));
if (domElements.length > 0) {
const contentElements = domElements[domElements.length - 1].querySelector('.p-dialog-content');
if (contentElements) return Array.isArray(contentElements) ? contentElements[0] : contentElements;
}
}

get header() {
return this.config.header;
}
Expand Down Expand Up @@ -339,7 +352,7 @@ export class DynamicDialogComponent implements AfterViewInit, OnDestroy {
this.enableModality();
}

if (this.config.focusOnShow === true) {
if (this.config.focusOnShow !== false) {
this.focus();
}
break;
Expand All @@ -354,6 +367,9 @@ export class DynamicDialogComponent implements AfterViewInit, OnDestroy {

onAnimationEnd(event: AnimationEvent) {
if (event.toState === 'void') {
if (this.parentContent) {
this.focus(this.parentContent);
}
this.onContainerDestroy();
this.dialogRef.destroy();
}
Expand Down Expand Up @@ -413,45 +429,22 @@ export class DynamicDialogComponent implements AfterViewInit, OnDestroy {
}
}

onKeydown(event: KeyboardEvent) {
// tab
if (event.which === 9) {
event.preventDefault();

let focusableElements = DomHandler.getFocusableElements(this.container as HTMLDivElement);
if (focusableElements && focusableElements.length > 0) {
if (!focusableElements[0].ownerDocument.activeElement) {
focusableElements[0].focus();
} else {
let focusedIndex = focusableElements.indexOf(focusableElements[0].ownerDocument.activeElement);

if (event.shiftKey) {
if (focusedIndex == -1 || focusedIndex === 0) focusableElements[focusableElements.length - 1].focus();
else focusableElements[focusedIndex - 1].focus();
} else {
if (focusedIndex == -1 || focusedIndex === focusableElements.length - 1) focusableElements[0].focus();
else focusableElements[focusedIndex + 1].focus();
}
}
}
}
}

focus() {
const autoFocusElement = DomHandler.findSingle(this.container, '[autofocus]');
if (autoFocusElement) {
focus(focusParentElement = this.contentViewChild.nativeElement) {
let focusable = DomHandler.getFocusableElement(focusParentElement, '[autofocus]');
if (focusable) {
this.zone.runOutsideAngular(() => {
setTimeout(() => autoFocusElement.focus(), 5);
setTimeout(() => focusable.focus(), 5);
});

return;
}

const focusableElements = DomHandler.getFocusableElements(this.container);
if (focusableElements && focusableElements.length > 0) {
const focusableElement = DomHandler.getFocusableElement(focusParentElement);
if (focusableElement) {
this.zone.runOutsideAngular(() => {
setTimeout(() => focusableElements[0].focus(), 5);
setTimeout(() => focusableElement.focus(), 5);
});
} else if (this.footerViewChild) {
// If the content section is empty try to focus on footer
this.focus(this.footerViewChild.nativeElement);
}
}

Expand Down Expand Up @@ -643,11 +636,6 @@ export class DynamicDialogComponent implements AfterViewInit, OnDestroy {
}

bindGlobalListeners() {
if (this.parentDialog) {
this.parentDialog.unbindDocumentKeydownListener();
}
this.bindDocumentKeydownListener();

if (this.config.closeOnEscape !== false && this.config.closable !== false) {
this.bindDocumentEscapeListener();
}
Expand All @@ -663,34 +651,10 @@ export class DynamicDialogComponent implements AfterViewInit, OnDestroy {
}

unbindGlobalListeners() {
this.unbindDocumentKeydownListener();
this.unbindDocumentEscapeListener();
this.unbindDocumentResizeListeners();
this.unbindDocumentDragListener();
this.unbindDocumentDragEndListener();

if (this.parentDialog) {
this.parentDialog.bindDocumentKeydownListener();
}
}

bindDocumentKeydownListener() {
if (isPlatformBrowser(this.platformId)) {
if (this.documentKeydownListener) {
return;
} else {
this.zone.runOutsideAngular(() => {
this.documentKeydownListener = this.renderer.listen(this.document, 'keydown', this.onKeydown.bind(this));
});
}
}
}

unbindDocumentKeydownListener() {
if (this.documentKeydownListener) {
this.documentKeydownListener();
this.documentKeydownListener = null;
}
}

bindDocumentEscapeListener() {
Expand Down Expand Up @@ -730,7 +694,7 @@ export class DynamicDialogComponent implements AfterViewInit, OnDestroy {
}

@NgModule({
imports: [CommonModule, WindowMaximizeIcon, WindowMinimizeIcon, TimesIcon, SharedModule],
imports: [CommonModule, WindowMaximizeIcon, WindowMinimizeIcon, TimesIcon, SharedModule, FocusTrapModule],
declarations: [DynamicDialogComponent, DynamicDialogContent],
exports: [SharedModule]
})
Expand Down
2 changes: 1 addition & 1 deletion src/app/showcase/doc/apidoc/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -9132,7 +9132,7 @@
"readonly": false,
"type": "boolean",
"default": "true",
"description": "When enabled, first button receives focus on show."
"description": "When enabled, first focusable element receives focus on show."
},
{
"name": "maximizable",
Expand Down

0 comments on commit 4522279

Please sign in to comment.