Skip to content

Commit

Permalink
feat(renderers): add restoreFocus setting
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
RomkeVdMeulen committed Jul 22, 2019
1 parent 4ea60c0 commit c5f4ad4
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 0 deletions.
9 changes: 9 additions & 0 deletions src/dialog-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ export interface DialogSettings {
* When invoked the function is passed the dialog container and the dialog overlay elements.
*/
position?: (dialogContainer: Element, dialogOverlay?: Element) => void;

/**
* This function is called when a dialog closes to restore focus to the last
* element that was focused when the dialog opened. It can be overridden in
* general settings, or on a case by case basis by providing an override when
* a particular dialog is opened.
*/
restoreFocus?: (lastActiveElement: HTMLElement) => void;
}

/**
Expand All @@ -105,4 +113,5 @@ export class DefaultDialogSettings implements DialogSettings {
public rejectOnCancel = false;
public ignoreTransitions = false;
public position?: (dialogContainer: Element, dialogOverlay: Element) => void;
public restoreFocus = (lastActiveElement: HTMLElement) => lastActiveElement.focus();
}
6 changes: 6 additions & 0 deletions src/renderers/native-dialog-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class NativeDialogRenderer implements Renderer {
private dialogCancel: (e: Event) => void;

public dialogContainer: HTMLDialogElement;
public lastActiveElement: HTMLElement;
public host: Element;
public anchor: Element;

Expand All @@ -62,6 +63,8 @@ export class NativeDialogRenderer implements Renderer {
}

private attach(dialogController: DialogController): void {
this.lastActiveElement = DOM.activeElement as HTMLElement;

const spacingWrapper = DOM.createElement('div'); // TODO: check if redundant
spacingWrapper.appendChild(this.anchor);
this.dialogContainer = DOM.createElement(containerTagName) as HTMLDialogElement;
Expand Down Expand Up @@ -92,6 +95,9 @@ export class NativeDialogRenderer implements Renderer {
if (!NativeDialogRenderer.dialogControllers.length) {
this.host.classList.remove('ux-dialog-open');
}
if (dialogController.settings.restoreFocus) {
dialogController.settings.restoreFocus(this.lastActiveElement);
}
}

private setAsActive(): void {
Expand Down
6 changes: 6 additions & 0 deletions src/renderers/ux-dialog-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class DialogRenderer implements Renderer {

public dialogContainer: HTMLElement;
public dialogOverlay: HTMLElement;
public lastActiveElement: HTMLElement;
public host: Element;
public anchor: Element;

Expand All @@ -120,6 +121,8 @@ export class DialogRenderer implements Renderer {
}

private attach(dialogController: DialogController): void {
this.lastActiveElement = DOM.activeElement as HTMLElement;

const spacingWrapper = DOM.createElement('div'); // TODO: check if redundant
spacingWrapper.appendChild(this.anchor);

Expand Down Expand Up @@ -156,6 +159,9 @@ export class DialogRenderer implements Renderer {
if (!DialogRenderer.dialogControllers.length) {
host.classList.remove('ux-dialog-open');
}
if (dialogController.settings.restoreFocus) {
dialogController.settings.restoreFocus(this.lastActiveElement);
}
}

private setAsActive(): void {
Expand Down
12 changes: 12 additions & 0 deletions test/unit/native-dialog-renderer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ describe('native-dialog-renderer.spec.ts', () => {
done();
});
});

describe('"restoreFocus"', () => {
it('and calls the given callback on close', async done => {
let restoreCalled = false;
const renderer = createRenderer({ restoreFocus: () => restoreCalled = true });
await show(done, renderer);
expect(restoreCalled).toBe(false);
await hide(done, renderer);
expect(restoreCalled).toBe(true);
done();
});
});
});

describe('on first open dialog', () => {
Expand Down
12 changes: 12 additions & 0 deletions test/unit/ux-dialog-renderer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,18 @@ describe('ux-dialog-renderer.spec.ts', () => {
done();
});
});

describe('"restoreFocus"', () => {
it('and calls the given callback on close', async done => {
let restoreCalled = false;
const renderer = createRenderer({ restoreFocus: () => restoreCalled = true });
await show(done, renderer);
expect(restoreCalled).toBe(false);
await hide(done, renderer);
expect(restoreCalled).toBe(true);
done();
});
});
});

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

0 comments on commit c5f4ad4

Please sign in to comment.