Skip to content

Commit c5f4ad4

Browse files
committed
feat(renderers): add restoreFocus setting
This controls where focus should be restored when a dialog closes. By default focus is restored to the last element that was active when the dialog opened.
1 parent 4ea60c0 commit c5f4ad4

File tree

5 files changed

+45
-0
lines changed

5 files changed

+45
-0
lines changed

src/dialog-settings.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ export interface DialogSettings {
9292
* When invoked the function is passed the dialog container and the dialog overlay elements.
9393
*/
9494
position?: (dialogContainer: Element, dialogOverlay?: Element) => void;
95+
96+
/**
97+
* This function is called when a dialog closes to restore focus to the last
98+
* element that was focused when the dialog opened. It can be overridden in
99+
* general settings, or on a case by case basis by providing an override when
100+
* a particular dialog is opened.
101+
*/
102+
restoreFocus?: (lastActiveElement: HTMLElement) => void;
95103
}
96104

97105
/**
@@ -105,4 +113,5 @@ export class DefaultDialogSettings implements DialogSettings {
105113
public rejectOnCancel = false;
106114
public ignoreTransitions = false;
107115
public position?: (dialogContainer: Element, dialogOverlay: Element) => void;
116+
public restoreFocus = (lastActiveElement: HTMLElement) => lastActiveElement.focus();
108117
}

src/renderers/native-dialog-renderer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export class NativeDialogRenderer implements Renderer {
4747
private dialogCancel: (e: Event) => void;
4848

4949
public dialogContainer: HTMLDialogElement;
50+
public lastActiveElement: HTMLElement;
5051
public host: Element;
5152
public anchor: Element;
5253

@@ -62,6 +63,8 @@ export class NativeDialogRenderer implements Renderer {
6263
}
6364

6465
private attach(dialogController: DialogController): void {
66+
this.lastActiveElement = DOM.activeElement as HTMLElement;
67+
6568
const spacingWrapper = DOM.createElement('div'); // TODO: check if redundant
6669
spacingWrapper.appendChild(this.anchor);
6770
this.dialogContainer = DOM.createElement(containerTagName) as HTMLDialogElement;
@@ -92,6 +95,9 @@ export class NativeDialogRenderer implements Renderer {
9295
if (!NativeDialogRenderer.dialogControllers.length) {
9396
this.host.classList.remove('ux-dialog-open');
9497
}
98+
if (dialogController.settings.restoreFocus) {
99+
dialogController.settings.restoreFocus(this.lastActiveElement);
100+
}
95101
}
96102

97103
private setAsActive(): void {

src/renderers/ux-dialog-renderer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export class DialogRenderer implements Renderer {
105105

106106
public dialogContainer: HTMLElement;
107107
public dialogOverlay: HTMLElement;
108+
public lastActiveElement: HTMLElement;
108109
public host: Element;
109110
public anchor: Element;
110111

@@ -120,6 +121,8 @@ export class DialogRenderer implements Renderer {
120121
}
121122

122123
private attach(dialogController: DialogController): void {
124+
this.lastActiveElement = DOM.activeElement as HTMLElement;
125+
123126
const spacingWrapper = DOM.createElement('div'); // TODO: check if redundant
124127
spacingWrapper.appendChild(this.anchor);
125128

@@ -156,6 +159,9 @@ export class DialogRenderer implements Renderer {
156159
if (!DialogRenderer.dialogControllers.length) {
157160
host.classList.remove('ux-dialog-open');
158161
}
162+
if (dialogController.settings.restoreFocus) {
163+
dialogController.settings.restoreFocus(this.lastActiveElement);
164+
}
159165
}
160166

161167
private setAsActive(): void {

test/unit/native-dialog-renderer.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,18 @@ describe('native-dialog-renderer.spec.ts', () => {
182182
done();
183183
});
184184
});
185+
186+
describe('"restoreFocus"', () => {
187+
it('and calls the given callback on close', async done => {
188+
let restoreCalled = false;
189+
const renderer = createRenderer({ restoreFocus: () => restoreCalled = true });
190+
await show(done, renderer);
191+
expect(restoreCalled).toBe(false);
192+
await hide(done, renderer);
193+
expect(restoreCalled).toBe(true);
194+
done();
195+
});
196+
});
185197
});
186198

187199
describe('on first open dialog', () => {

test/unit/ux-dialog-renderer.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,18 @@ describe('ux-dialog-renderer.spec.ts', () => {
185185
done();
186186
});
187187
});
188+
189+
describe('"restoreFocus"', () => {
190+
it('and calls the given callback on close', async done => {
191+
let restoreCalled = false;
192+
const renderer = createRenderer({ restoreFocus: () => restoreCalled = true });
193+
await show(done, renderer);
194+
expect(restoreCalled).toBe(false);
195+
await hide(done, renderer);
196+
expect(restoreCalled).toBe(true);
197+
done();
198+
});
199+
});
188200
});
189201

190202
describe('on first open dialog', () => {

0 commit comments

Comments
 (0)