Skip to content

Commit 9f23c3d

Browse files
committed
fix(modal): attempt to focus when there is no focusable element on modal dialog
(cherry picked from commit 3f50a06)
1 parent 7c7fcf4 commit 9f23c3d

9 files changed

+31
-43
lines changed

projects/coreui-angular/src/lib/modal/modal-body/modal-body.component.spec.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ describe('ModalBodyComponent', () => {
99
beforeEach(async () => {
1010
await TestBed.configureTestingModule({
1111
imports: [ModalBodyComponent]
12-
})
13-
.compileComponents();
12+
}).compileComponents();
1413
});
1514

1615
beforeEach(() => {

projects/coreui-angular/src/lib/modal/modal-body/modal-body.component.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@ import { Component, HostBinding } from '@angular/core';
77
standalone: true
88
})
99
export class ModalBodyComponent {
10-
1110
@HostBinding('class')
1211
get hostClasses(): any {
1312
return {
14-
'modal-body': true,
13+
'modal-body': true
1514
};
1615
}
1716
}

projects/coreui-angular/src/lib/modal/modal-content/modal-content.component.spec.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ describe('ModalContentComponent', () => {
99
beforeEach(async () => {
1010
await TestBed.configureTestingModule({
1111
imports: [ModalContentComponent]
12-
})
13-
.compileComponents();
12+
}).compileComponents();
1413
});
1514

1615
beforeEach(() => {

projects/coreui-angular/src/lib/modal/modal-dialog/modal-dialog.component.spec.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ describe('ModalDialogComponent', () => {
99
beforeEach(async () => {
1010
await TestBed.configureTestingModule({
1111
imports: [ModalDialogComponent]
12-
})
13-
.compileComponents();
12+
}).compileComponents();
1413
});
1514

1615
beforeEach(() => {

projects/coreui-angular/src/lib/modal/modal-footer/modal-footer.component.spec.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ describe('ModalFooterComponent', () => {
99
beforeEach(async () => {
1010
await TestBed.configureTestingModule({
1111
imports: [ModalFooterComponent]
12-
})
13-
.compileComponents();
12+
}).compileComponents();
1413
});
1514

1615
beforeEach(() => {

projects/coreui-angular/src/lib/modal/modal-footer/modal-footer.component.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ import { Component, HostBinding } from '@angular/core';
66
standalone: true
77
})
88
export class ModalFooterComponent {
9-
109
@HostBinding('class')
1110
get hostClasses(): any {
1211
return {
13-
'modal-footer': true,
12+
'modal-footer': true
1413
};
1514
}
16-
1715
}

projects/coreui-angular/src/lib/modal/modal-header/modal-header.component.spec.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ describe('ModalHeaderComponent', () => {
99
beforeEach(async () => {
1010
await TestBed.configureTestingModule({
1111
imports: [ModalHeaderComponent]
12-
})
13-
.compileComponents();
12+
}).compileComponents();
1413
});
1514

1615
beforeEach(() => {

projects/coreui-angular/src/lib/modal/modal-header/modal-header.component.ts

-2
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ import { Component, HostBinding } from '@angular/core';
66
standalone: true
77
})
88
export class ModalHeaderComponent {
9-
109
@HostBinding('class')
1110
get hostClasses(): any {
1211
return {
1312
'modal-header': true
1413
};
1514
}
16-
1715
}

projects/coreui-angular/src/lib/modal/modal/modal.component.ts

+24-26
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ import { ModalDialogComponent } from '../modal-dialog/modal-dialog.component';
5454
imports: [ModalDialogComponent, ModalContentComponent, A11yModule]
5555
})
5656
export class ModalComponent implements OnInit, OnDestroy, AfterViewInit {
57-
5857
#destroyRef = inject(DestroyRef);
5958
#focusMonitor = inject(FocusMonitor);
6059

@@ -64,7 +63,7 @@ export class ModalComponent implements OnInit, OnDestroy, AfterViewInit {
6463
private hostElement: ElementRef,
6564
private modalService: ModalService,
6665
private backdropService: BackdropService
67-
) { }
66+
) {}
6867

6968
/**
7069
* Align the modal in the center or top of the screen.
@@ -110,14 +109,15 @@ export class ModalComponent implements OnInit, OnDestroy, AfterViewInit {
110109
* @type boolean
111110
* @default null
112111
*/
113-
@Input() @HostBinding('attr.aria-modal')
112+
@Input()
113+
@HostBinding('attr.aria-modal')
114114
set ariaModal(value: boolean | null) {
115115
this.#ariaModal = value;
116116
}
117117

118118
get ariaModal(): boolean | null {
119119
return this.visible || this.#ariaModal ? true : null;
120-
};
120+
}
121121

122122
#ariaModal: boolean | null = null;
123123

@@ -154,8 +154,12 @@ export class ModalComponent implements OnInit, OnDestroy, AfterViewInit {
154154
this.#activeElement = this.document.activeElement as HTMLElement;
155155
// this.#activeElement?.blur();
156156
setTimeout(() => {
157-
const focusable = this.modalContentRef.nativeElement.querySelectorAll('[tabindex]:not([tabindex="-1"]), button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled])');
158-
this.#focusMonitor.focusVia(focusable[0], 'keyboard');
157+
const focusable = this.modalContentRef.nativeElement.querySelectorAll(
158+
'[tabindex]:not([tabindex="-1"]), button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled])'
159+
);
160+
if (focusable.length) {
161+
this.#focusMonitor.focusVia(focusable[0], 'keyboard');
162+
}
159163
});
160164
} else {
161165
if (this.document.contains(this.#activeElement)) {
@@ -191,7 +195,7 @@ export class ModalComponent implements OnInit, OnDestroy, AfterViewInit {
191195
@HostBinding('attr.aria-hidden')
192196
get ariaHidden(): boolean | null {
193197
return this.visible ? null : true;
194-
};
198+
}
195199

196200
@HostBinding('attr.tabindex')
197201
get tabIndex(): string | null {
@@ -255,15 +259,13 @@ export class ModalComponent implements OnInit, OnDestroy, AfterViewInit {
255259

256260
@HostListener('click', ['$event'])
257261
public onClickHandler($event: MouseEvent): void {
258-
259262
if (this.mouseDownTarget !== $event.target) {
260263
this.mouseDownTarget = null;
261264
return;
262265
}
263266

264267
const targetElement = $event.target;
265268
if (targetElement === this.hostElement.nativeElement) {
266-
267269
if (this.backdrop === 'static') {
268270
this.setStaticBackdrop();
269271
return;
@@ -289,27 +291,23 @@ export class ModalComponent implements OnInit, OnDestroy, AfterViewInit {
289291
}
290292

291293
private stateToggleSubscribe(): void {
292-
this.modalService.modalState$
293-
.pipe(
294-
takeUntilDestroyed(this.#destroyRef)
295-
)
296-
.subscribe(
297-
(action) => {
298-
if (this === action.modal || this.id === action.id) {
299-
if ('show' in action) {
300-
this.visible = action?.show === 'toggle' ? !this.visible : action.show;
301-
}
302-
} else {
303-
if (this.visible) {
304-
this.visible = false;
305-
}
306-
}
294+
this.modalService.modalState$.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe((action) => {
295+
if (this === action.modal || this.id === action.id) {
296+
if ('show' in action) {
297+
this.visible = action?.show === 'toggle' ? !this.visible : action.show;
298+
}
299+
} else {
300+
if (this.visible) {
301+
this.visible = false;
307302
}
308-
);
303+
}
304+
});
309305
}
310306

311307
private setBackdrop(setBackdrop: boolean): void {
312-
this.#activeBackdrop = setBackdrop ? this.backdropService.setBackdrop('modal') : this.backdropService.clearBackdrop(this.#activeBackdrop);
308+
this.#activeBackdrop = setBackdrop
309+
? this.backdropService.setBackdrop('modal')
310+
: this.backdropService.clearBackdrop(this.#activeBackdrop);
313311
}
314312

315313
private setBodyStyles(open: boolean): void {

0 commit comments

Comments
 (0)