Skip to content

Commit f38a491

Browse files
authored
Iframe accessibility improvements: Add title attribute and auto-focus when UI is showing (#158)
* IFrame accessibility improvements * Re-focus previous activeElement when UI is hidden * Update '@magic-sdk/commons' dependencies
1 parent e71599e commit f38a491

File tree

5 files changed

+68
-5
lines changed

5 files changed

+68
-5
lines changed

packages/commons/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
"module": "dist/module/index.js",
1818
"types": "dist/commonjs/index.d.ts",
1919
"peerDependencies": {
20-
"@magic-sdk/provider": "^3.0.1",
21-
"@magic-sdk/types": "^2.0.1",
20+
"@magic-sdk/provider": "^4.0.2",
21+
"@magic-sdk/types": "^3.0.1",
2222
"tslib": "^2.0.3"
2323
},
2424
"gitHead": "1ef062ea699d48d5e9a9375a93b7c147632b05ca"

packages/web/src/iframe-controller.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable no-unused-expressions */
12
/* eslint-disable no-underscore-dangle */
23

34
import { ViewController, createDuplicateIframeWarning, createURL, createModalNotReadyError } from '@magic-sdk/provider';
@@ -46,6 +47,7 @@ function checkForSameSrcInstances(parameters: string) {
4647
*/
4748
export class IframeController extends ViewController {
4849
private iframe!: Promise<HTMLIFrameElement>;
50+
private activeElement: any = null;
4951

5052
protected init() {
5153
this.iframe = new Promise((resolve) => {
@@ -54,6 +56,7 @@ export class IframeController extends ViewController {
5456
const iframe = document.createElement('iframe');
5557
iframe.classList.add('magic-iframe');
5658
iframe.dataset.magicIframeLabel = createURL(this.endpoint).host;
59+
iframe.title = 'Secure Modal';
5760
iframe.src = createURL(`/send?params=${encodeURIComponent(this.parameters)}`, this.endpoint).href;
5861
applyOverlayStyles(iframe);
5962
document.body.appendChild(iframe);
@@ -76,11 +79,15 @@ export class IframeController extends ViewController {
7679
protected async showOverlay() {
7780
const iframe = await this.iframe;
7881
iframe.style.display = 'block';
82+
this.activeElement = document.activeElement;
83+
iframe.focus();
7984
}
8085

8186
protected async hideOverlay() {
8287
const iframe = await this.iframe;
8388
iframe.style.display = 'none';
89+
if (this.activeElement?.focus) this.activeElement.focus();
90+
this.activeElement = null;
8491
}
8592

8693
public async postMessage(data: any) {

packages/web/test/spec/iframe-controller/hideOverlay.spec.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,30 @@ test.beforeEach((t) => {
1111
test('Change display style to `none`', async (t) => {
1212
const overlay = createIframeController();
1313

14-
(overlay as any).iframe = { style: { display: 'block' } };
14+
(overlay as any).iframe = {
15+
style: { display: 'block' },
16+
};
1517

1618
await (overlay as any).hideOverlay();
1719

1820
t.deepEqual((overlay as any).iframe, { style: { display: 'none' } });
1921
});
22+
23+
test('If `activeElement` exists and can be focused, calls `activeElement.focus()`', async (t) => {
24+
const overlay = createIframeController();
25+
26+
const focusStub = sinon.stub();
27+
28+
(overlay as any).activeElement = {
29+
focus: focusStub,
30+
};
31+
32+
(overlay as any).iframe = {
33+
style: { display: 'block' },
34+
};
35+
36+
await (overlay as any).hideOverlay();
37+
38+
t.true(focusStub.calledOnce);
39+
t.is((overlay as any).activeElement, null);
40+
});

packages/web/test/spec/iframe-controller/init.spec.ts

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ test.serial('Appends header with style, appends body with iframe, and resolves i
5151
t.true(classListAddStub.calledWith('magic-iframe'));
5252
t.deepEqual(iframe.dataset, { magicIframeLabel: 'auth.magic.link' });
5353
t.is(iframe.src, `${MAGIC_RELAYER_FULL_URL}/send?params=${ENCODED_QUERY_PARAMS}`);
54+
t.is(iframe.title, 'Secure Modal');
5455
});
5556

5657
test.serial('Displays warning in console upon duplicate iframes', async (t) => {

packages/web/test/spec/iframe-controller/showOverlay.spec.ts

+36-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,43 @@ test.beforeEach((t) => {
1111
test('Change display style to `block`', async (t) => {
1212
const overlay = createIframeController();
1313

14-
(overlay as any).iframe = { style: { display: 'none' } };
14+
(overlay as any).iframe = {
15+
style: { display: 'none' },
16+
focus: () => {},
17+
};
1518

1619
await (overlay as any).showOverlay();
1720

18-
t.deepEqual((overlay as any).iframe, { style: { display: 'block' } });
21+
t.is((overlay as any).iframe.style.display, 'block');
22+
});
23+
24+
test('Calls `iframe.focus()`', async (t) => {
25+
const overlay = createIframeController();
26+
27+
const focusStub = sinon.stub();
28+
(overlay as any).iframe = {
29+
style: { display: 'none' },
30+
focus: focusStub,
31+
};
32+
33+
await (overlay as any).showOverlay();
34+
35+
t.true(focusStub.calledOnce);
36+
});
37+
38+
test('Saves the current `document.activeElement`', async (t) => {
39+
const overlay = createIframeController();
40+
41+
browserEnv.stub('document.activeElement', 'qwertyqwerty');
42+
43+
(overlay as any).iframe = {
44+
style: { display: 'none' },
45+
focus: () => {},
46+
};
47+
48+
t.is((overlay as any).activeElement, null);
49+
50+
await (overlay as any).showOverlay();
51+
52+
t.is((overlay as any).activeElement, 'qwertyqwerty');
1953
});

0 commit comments

Comments
 (0)