From 560ac7f96534d5c075dd71125b6b329103e6abc1 Mon Sep 17 00:00:00 2001 From: Joy Zhong Date: Wed, 15 May 2019 11:56:56 -0400 Subject: [PATCH 1/6] feat(dialog): Add Foundation#setInitialFocusEl API. --- packages/mdc-dialog/README.md | 13 ++++++++++--- packages/mdc-dialog/adapter.ts | 2 +- packages/mdc-dialog/constants.ts | 1 + packages/mdc-dialog/foundation.ts | 12 +++++++++++- test/unit/mdc-dialog/foundation.test.js | 25 ++++++++++++++++++++++++- 5 files changed, 47 insertions(+), 6 deletions(-) diff --git a/packages/mdc-dialog/README.md b/packages/mdc-dialog/README.md index fb8d7f74f28..3f944d1d012 100644 --- a/packages/mdc-dialog/README.md +++ b/packages/mdc-dialog/README.md @@ -253,7 +253,6 @@ avoiding the need to tab through to the appropriate button to confirm the choice To indicate that a button represents the default action, add the `mdc-dialog__button--default` modifier class. For example: - ```html ... @@ -336,10 +336,10 @@ Mixin | Description > *NOTE*: The `max-width` and `max-height` mixins only apply their maximum when the viewport is large enough to accommodate the specified value when accounting for the specified margin on either side. When the viewport is smaller, the dialog is sized such that the given margin is retained around the edges. ## Other Customizations -CSS Class | Description +Data Attributes | Description --- | --- -`mdc-dialog__button--default` | Optional. Add to a button to indicate that it is the default action button (see Default Action Button section above). -`mdc-dialog__element--focus` | Optional. Add to an element to indicate that it is the element to initially focus on after the dialog has opened. This is the element that should be passed in to `Adapter#setInitialFocusEl`. +`data-mdc-dialog-button-default` | Optional. Add to a button to indicate that it is the default action button (see Default Action Button section above). +`data-mdc-dialog-element-focus` | Optional. Add to an element to indicate that it is the element to initially focus on after the dialog has opened. This is the element that should be passed in to `Adapter#getInitialFocusEl`. ## `MDCDialog` Properties and Methods @@ -379,12 +379,13 @@ Method Signature | Description `addBodyClass(className: string) => void` | Adds a class to the ``. `removeBodyClass(className: string) => void` | Removes a class from the ``. `eventTargetMatches(target: EventTarget | null, selector: string) => void` | Returns `true` if the target element matches the given CSS selector, otherwise `false`. -`trapFocus(initialFocusEl?: HTMLElement) => void` | Sets up the DOM such that keyboard navigation is restricted to focusable elements within the dialog surface (see [Handling Focus Trapping](#handling-focus-trapping) below for more details). If `initialFocusEl` is set, also moves focus to that element. +`trapFocus(initialFocusEl: HTMLElement|null) => void` | Sets up the DOM such that keyboard navigation is restricted to focusable elements within the dialog surface (see [Handling Focus Trapping](#handling-focus-trapping) below for more details). Moves focus to `initialFocusEl`, if set. `releaseFocus() => void` | Removes any effects of focus trapping on the dialog surface (see [Handling Focus Trapping](#handling-focus-trapping) below for more details). +`getInitialFocusEl() => HTMLElement|null` | Returns the `data-mdc-dialog-element-focus` element to add focus to after the dialog has opened. `isContentScrollable() => boolean` | Returns `true` if `mdc-dialog__content` can be scrolled by the user, otherwise `false`. `areButtonsStacked() => boolean` | Returns `true` if `mdc-dialog__action` buttons (`mdc-dialog__button`) are stacked vertically, otherwise `false` if they are side-by-side. `getActionFromEvent(event: Event) => string \| null` | Retrieves the value of the `data-mdc-dialog-action` attribute from the given event's target, or an ancestor of the target. -`clickDefaultButton() => void` | Invokes `click()` on the `mdc-dialog__button--default` element, if one exists in the dialog. +`clickDefaultButton() => void` | Invokes `click()` on the `data-mdc-dialog-button-default` element, if one exists in the dialog. `reverseButtons() => void` | Reverses the order of action buttons in the `mdc-dialog__actions` element. Used when switching between stacked and unstacked button layouts. `notifyOpening() => void` | Broadcasts an event denoting that the dialog has just started to open. `notifyOpened() => void` | Broadcasts an event denoting that the dialog has finished opening. @@ -405,8 +406,6 @@ Method Signature | Description `setScrimClickAction(action: string)` | Sets the action reflected when the scrim is clicked. Setting to `''` disables closing the dialog via scrim click. `getAutoStackButtons() => boolean` | Returns whether stacked/unstacked action button layout is automatically handled during layout logic. `setAutoStackButtons(autoStack: boolean) => void` | Sets whether stacked/unstacked action button layout is automatically handled during layout logic. -`getInitialFocusEl() => HTMLElement|null` | Gets the element to focus on after dialog has finished opening. -`setInitialFocusEl(el: HTMLElement|null)` | Sets the element to focus on after dialog has finished opening. Passed as an argument to `Adapter#trapFocus`. `handleClick(event: MouseEvent)` | Handles `click` events on or within the dialog's root element. `handleKeydown(event: KeyboardEvent)` | Handles `keydown` events on or within the dialog's root element. `handleDocumentKeydown(event: Event)` | Handles `keydown` events on or within the document while the dialog is open. diff --git a/packages/mdc-dialog/adapter.ts b/packages/mdc-dialog/adapter.ts index 7fa1f2fbbd6..ff54538bcc5 100644 --- a/packages/mdc-dialog/adapter.ts +++ b/packages/mdc-dialog/adapter.ts @@ -40,8 +40,10 @@ export interface MDCDialogAdapter { areButtonsStacked(): boolean; getActionFromEvent(evt: Event): string | null; - trapFocus(focusElement?: HTMLElement): void; + trapFocus(focusElement: HTMLElement|null): void; releaseFocus(): void; + // Element to focus on after dialog has opened. + getInitialFocusEl(): HTMLElement|null; clickDefaultButton(): void; reverseButtons(): void; diff --git a/packages/mdc-dialog/component.ts b/packages/mdc-dialog/component.ts index ff90dab50cc..305a1a4dd7b 100644 --- a/packages/mdc-dialog/component.ts +++ b/packages/mdc-dialog/component.ts @@ -72,7 +72,6 @@ export class MDCDialog extends MDCComponent { private container_!: HTMLElement; // assigned in initialize() private content_!: HTMLElement | null; // assigned in initialize() private defaultButton_!: HTMLElement | null; // assigned in initialize() - private initialFocusEl_?: HTMLElement; // assigned in initialize() private focusTrap_!: FocusTrap; // assigned in initialSyncWithDOM() private focusTrapFactory_?: MDCDialogFocusTrapFactory; // assigned in initialize() @@ -86,7 +85,6 @@ export class MDCDialog extends MDCComponent { initialize( focusTrapFactory?: MDCDialogFocusTrapFactory, - initialFocusEl?: HTMLElement, ) { const container = this.root_.querySelector(strings.CONTAINER_SELECTOR); if (!container) { @@ -95,9 +93,8 @@ export class MDCDialog extends MDCComponent { this.container_ = container; this.content_ = this.root_.querySelector(strings.CONTENT_SELECTOR); this.buttons_ = [].slice.call(this.root_.querySelectorAll(strings.BUTTON_SELECTOR)); - this.defaultButton_ = this.root_.querySelector(strings.DEFAULT_BUTTON_SELECTOR); + this.defaultButton_ = this.root_.querySelector(`[${strings.DEFAULT_BUTTON_ATTRIBUTE}]`); this.focusTrapFactory_ = focusTrapFactory; - this.initialFocusEl_ = initialFocusEl; this.buttonRipples_ = []; for (const buttonEl of this.buttons_) { @@ -106,7 +103,8 @@ export class MDCDialog extends MDCComponent { } initialSyncWithDOM() { - this.focusTrap_ = util.createFocusTrapInstance(this.container_, this.focusTrapFactory_, this.initialFocusEl_); + this.focusTrap_ = util.createFocusTrapInstance( + this.container_, this.focusTrapFactory_, this.getInitialFocusEl_() || undefined); this.handleClick_ = this.foundation_.handleClick.bind(this.foundation_); this.handleKeydown_ = this.foundation_.handleKeydown.bind(this.foundation_); @@ -168,6 +166,7 @@ export class MDCDialog extends MDCComponent { const element = closest(evt.target as Element, `[${strings.ACTION_ATTRIBUTE}]`); return element && element.getAttribute(strings.ACTION_ATTRIBUTE); }, + getInitialFocusEl: () => this.getInitialFocusEl_(), hasClass: (className) => this.root_.classList.contains(className), isContentScrollable: () => util.isScrollable(this.content_), notifyClosed: (action) => this.emit(strings.CLOSED_EVENT, action ? {action} : {}), @@ -187,4 +186,8 @@ export class MDCDialog extends MDCComponent { }; return new MDCDialogFoundation(adapter); } + + private getInitialFocusEl_(): HTMLElement|null { + return document.querySelector(`[${strings.INITIAL_FOCUS_EL_ATTRIBUTE}]`); + } } diff --git a/packages/mdc-dialog/constants.ts b/packages/mdc-dialog/constants.ts index 7ae42f4965d..d06a0ba63a7 100644 --- a/packages/mdc-dialog/constants.ts +++ b/packages/mdc-dialog/constants.ts @@ -38,9 +38,9 @@ export const strings = { CLOSING_EVENT: 'MDCDialog:closing', CONTAINER_SELECTOR: '.mdc-dialog__container', CONTENT_SELECTOR: '.mdc-dialog__content', - DEFAULT_BUTTON_SELECTOR: '.mdc-dialog__button--default', + DEFAULT_BUTTON_ATTRIBUTE: 'data-mdc-dialog-button-default', DESTROY_ACTION: 'destroy', - INITIAL_FOCUS_EL_SELECTOR: '.mdc-dialog__element--focus', + INITIAL_FOCUS_EL_ATTRIBUTE: 'data-mdc-dialog-element-focus', OPENED_EVENT: 'MDCDialog:opened', OPENING_EVENT: 'MDCDialog:opening', SCRIM_SELECTOR: '.mdc-dialog__scrim', diff --git a/packages/mdc-dialog/foundation.ts b/packages/mdc-dialog/foundation.ts index 4496fa0b726..3f67529c44e 100644 --- a/packages/mdc-dialog/foundation.ts +++ b/packages/mdc-dialog/foundation.ts @@ -46,6 +46,7 @@ export class MDCDialogFoundation extends MDCFoundation { clickDefaultButton: () => undefined, eventTargetMatches: () => false, getActionFromEvent: () => '', + getInitialFocusEl: () => null, hasClass: () => false, isContentScrollable: () => false, notifyClosed: () => undefined, @@ -68,8 +69,6 @@ export class MDCDialogFoundation extends MDCFoundation { private scrimClickAction_ = strings.CLOSE_ACTION; private autoStackButtons_ = true; private areButtonsStacked_ = false; - // Element to focus on after dialog has finished opening. - private initialFocusEl_: HTMLElement|null = null; constructor(adapter?: Partial) { super({...MDCDialogFoundation.defaultAdapter, ...adapter}); @@ -111,7 +110,7 @@ export class MDCDialogFoundation extends MDCFoundation { this.animationTimer_ = setTimeout(() => { this.handleAnimationTimerEnd_(); - this.adapter_.trapFocus(this.initialFocusEl_ || undefined); + this.adapter_.trapFocus(this.adapter_.getInitialFocusEl()); this.adapter_.notifyOpened(); }, numbers.DIALOG_ANIMATION_OPEN_TIME_MS); }); @@ -168,14 +167,6 @@ export class MDCDialogFoundation extends MDCFoundation { this.autoStackButtons_ = autoStack; } - getInitialFocusEl(): HTMLElement|null { - return this.initialFocusEl_; - } - - setInitialFocusEl(el: HTMLElement|null) { - this.initialFocusEl_ = el; - } - layout() { if (this.layoutFrame_) { cancelAnimationFrame(this.layoutFrame_); diff --git a/test/screenshot/spec/mdc-dialog/classes/baseline-confirmation.html b/test/screenshot/spec/mdc-dialog/classes/baseline-confirmation.html index 4ab907ced6a..05badd41138 100644 --- a/test/screenshot/spec/mdc-dialog/classes/baseline-confirmation.html +++ b/test/screenshot/spec/mdc-dialog/classes/baseline-confirmation.html @@ -98,7 +98,7 @@

Confirm - diff --git a/test/unit/mdc-dialog/foundation.test.js b/test/unit/mdc-dialog/foundation.test.js index 1f7ddd6c96e..7772f1b1a03 100644 --- a/test/unit/mdc-dialog/foundation.test.js +++ b/test/unit/mdc-dialog/foundation.test.js @@ -54,7 +54,7 @@ test('default adapter returns a complete adapter implementation', () => { verifyDefaultAdapter(MDCDialogFoundation, [ 'addClass', 'removeClass', 'hasClass', 'addBodyClass', 'removeBodyClass', 'eventTargetMatches', - 'trapFocus', 'releaseFocus', + 'trapFocus', 'releaseFocus', 'getInitialFocusEl', 'isContentScrollable', 'areButtonsStacked', 'getActionFromEvent', 'clickDefaultButton', 'reverseButtons', 'notifyOpening', 'notifyOpened', 'notifyClosing', 'notifyClosed', ]); @@ -184,6 +184,8 @@ test('#open activates focus trapping on the dialog surface', () => { const {foundation, mockAdapter} = setupTest(); const clock = installClock(); + const button = document.createElement('button'); + td.when(mockAdapter.getInitialFocusEl()).thenReturn(button); foundation.open(); // Wait for application of opening class and setting of additional timeout prior to full open animation timeout @@ -191,7 +193,7 @@ test('#open activates focus trapping on the dialog surface', () => { clock.tick(100); clock.tick(numbers.DIALOG_ANIMATION_OPEN_TIME_MS); - td.verify(mockAdapter.trapFocus(undefined)); + td.verify(mockAdapter.trapFocus(button)); }); test('#close deactivates focus trapping on the dialog surface', () => { @@ -514,26 +516,3 @@ test('#getScrimClickAction reflects setting of #setScrimClickAction', () => { foundation.setScrimClickAction(action); assert.strictEqual(foundation.getScrimClickAction(), action); }); - -test('#getInitialFocusEl reflects setting of #setInitialFocusEl', () => { - const {foundation} = setupTest(); - const el = document.createElement('button'); - foundation.setInitialFocusEl(el); - assert.strictEqual(foundation.getInitialFocusEl(), el); -}); - -test('#setInitialFocusEl element is passed in as an arg to Adapter#trapFocus', () => { - const {foundation, mockAdapter} = setupTest(); - const clock = installClock(); - - const button = document.createElement('button'); - foundation.setInitialFocusEl(button); - foundation.open(); - - // Wait for application of opening class and setting of additional timeout prior to full open animation timeout - clock.runToFrame(); - clock.tick(100); - clock.tick(numbers.DIALOG_ANIMATION_OPEN_TIME_MS); - - td.verify(mockAdapter.trapFocus(button)); -}); diff --git a/test/unit/mdc-dialog/mdc-dialog.test.js b/test/unit/mdc-dialog/mdc-dialog.test.js index 47a0287f883..4705d343c1c 100644 --- a/test/unit/mdc-dialog/mdc-dialog.test.js +++ b/test/unit/mdc-dialog/mdc-dialog.test.js @@ -473,10 +473,10 @@ test('adapter#getActionFromEvent returns null when attribute is not present', () assert.isNull(action); }); -test(`adapter#clickDefaultButton invokes click() on button matching ${strings.DEFAULT_BUTTON_SELECTOR}`, () => { +test(`adapter#clickDefaultButton invokes click() on button matching ${strings.DEFAULT_BUTTON_ATTRIBUTE}`, () => { const fixture = getFixture(); const yesButton = fixture.querySelector('[data-mdc-dialog-action="yes"]'); - yesButton.classList.add(strings.DEFAULT_BUTTON_SELECTOR.slice(1)); + yesButton.setAttribute(strings.DEFAULT_BUTTON_ATTRIBUTE, 'true'); const {component} = setupTest(fixture); yesButton.click = td.func('click'); @@ -485,7 +485,7 @@ test(`adapter#clickDefaultButton invokes click() on button matching ${strings.DE td.verify(yesButton.click()); }); -test(`adapter#clickDefaultButton does nothing if nothing matches ${strings.DEFAULT_BUTTON_SELECTOR}`, () => { +test(`adapter#clickDefaultButton does nothing if nothing matches ${strings.DEFAULT_BUTTON_ATTRIBUTE}`, () => { const {component, yesButton, noButton} = setupTest(); yesButton.click = td.func('click'); noButton.click = td.func('click'); From 177c345a66715a6092ee49552ddc503c6997d823 Mon Sep 17 00:00:00 2001 From: Joy Zhong Date: Thu, 16 May 2019 13:45:16 -0400 Subject: [PATCH 3/6] feat(dialog): Minor fixes. --- packages/mdc-dialog/README.md | 4 ++-- packages/mdc-dialog/constants.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mdc-dialog/README.md b/packages/mdc-dialog/README.md index 83c1e181ab0..cdf8145cc8f 100644 --- a/packages/mdc-dialog/README.md +++ b/packages/mdc-dialog/README.md @@ -339,7 +339,7 @@ Mixin | Description Data Attributes | Description --- | --- `data-mdc-dialog-button-default` | Optional. Add to a button to indicate that it is the default action button (see Default Action Button section above). -`data-mdc-dialog-element-focus` | Optional. Add to an element to indicate that it is the element to initially focus on after the dialog has opened. This is the element that should be passed in to `Adapter#getInitialFocusEl`. +`data-mdc-dialog-initial-focus` | Optional. Add to an element to indicate that it is the element to initially focus on after the dialog has opened. ## `MDCDialog` Properties and Methods @@ -381,7 +381,7 @@ Method Signature | Description `eventTargetMatches(target: EventTarget | null, selector: string) => void` | Returns `true` if the target element matches the given CSS selector, otherwise `false`. `trapFocus(initialFocusEl: HTMLElement|null) => void` | Sets up the DOM such that keyboard navigation is restricted to focusable elements within the dialog surface (see [Handling Focus Trapping](#handling-focus-trapping) below for more details). Moves focus to `initialFocusEl`, if set. `releaseFocus() => void` | Removes any effects of focus trapping on the dialog surface (see [Handling Focus Trapping](#handling-focus-trapping) below for more details). -`getInitialFocusEl() => HTMLElement|null` | Returns the `data-mdc-dialog-element-focus` element to add focus to after the dialog has opened. +`getInitialFocusEl() => HTMLElement|null` | Returns the `data-mdc-dialog-initial-focus` element to add focus to after the dialog has opened. `isContentScrollable() => boolean` | Returns `true` if `mdc-dialog__content` can be scrolled by the user, otherwise `false`. `areButtonsStacked() => boolean` | Returns `true` if `mdc-dialog__action` buttons (`mdc-dialog__button`) are stacked vertically, otherwise `false` if they are side-by-side. `getActionFromEvent(event: Event) => string \| null` | Retrieves the value of the `data-mdc-dialog-action` attribute from the given event's target, or an ancestor of the target. diff --git a/packages/mdc-dialog/constants.ts b/packages/mdc-dialog/constants.ts index d06a0ba63a7..121215208ac 100644 --- a/packages/mdc-dialog/constants.ts +++ b/packages/mdc-dialog/constants.ts @@ -32,15 +32,15 @@ export const cssClasses = { export const strings = { ACTION_ATTRIBUTE: 'data-mdc-dialog-action', + BUTTON_DEFAULT_ATTRIBUTE: 'data-mdc-dialog-button-default', BUTTON_SELECTOR: '.mdc-dialog__button', CLOSED_EVENT: 'MDCDialog:closed', CLOSE_ACTION: 'close', CLOSING_EVENT: 'MDCDialog:closing', CONTAINER_SELECTOR: '.mdc-dialog__container', CONTENT_SELECTOR: '.mdc-dialog__content', - DEFAULT_BUTTON_ATTRIBUTE: 'data-mdc-dialog-button-default', DESTROY_ACTION: 'destroy', - INITIAL_FOCUS_EL_ATTRIBUTE: 'data-mdc-dialog-element-focus', + INITIAL_FOCUS_ATTRIBUTE: 'data-mdc-dialog-initial-focus', OPENED_EVENT: 'MDCDialog:opened', OPENING_EVENT: 'MDCDialog:opening', SCRIM_SELECTOR: '.mdc-dialog__scrim', From 1b5b54c2867e6a4cbda5fac74ed0717acbe2e9ee Mon Sep 17 00:00:00 2001 From: Joy Zhong Date: Thu, 16 May 2019 13:47:12 -0400 Subject: [PATCH 4/6] feat(dialog): Fix component.ts. --- packages/mdc-dialog/component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mdc-dialog/component.ts b/packages/mdc-dialog/component.ts index 305a1a4dd7b..8482254e1cd 100644 --- a/packages/mdc-dialog/component.ts +++ b/packages/mdc-dialog/component.ts @@ -93,7 +93,7 @@ export class MDCDialog extends MDCComponent { this.container_ = container; this.content_ = this.root_.querySelector(strings.CONTENT_SELECTOR); this.buttons_ = [].slice.call(this.root_.querySelectorAll(strings.BUTTON_SELECTOR)); - this.defaultButton_ = this.root_.querySelector(`[${strings.DEFAULT_BUTTON_ATTRIBUTE}]`); + this.defaultButton_ = this.root_.querySelector(`[${strings.BUTTON_DEFAULT_ATTRIBUTE}]`); this.focusTrapFactory_ = focusTrapFactory; this.buttonRipples_ = []; From 086e8f31f7f9c0908ef2d23aeea7d39dbff157ed Mon Sep 17 00:00:00 2001 From: Joy Zhong Date: Thu, 16 May 2019 13:48:38 -0400 Subject: [PATCH 5/6] feat(dialog): Fix mdc-dialog.test.ts. --- test/unit/mdc-dialog/mdc-dialog.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/mdc-dialog/mdc-dialog.test.js b/test/unit/mdc-dialog/mdc-dialog.test.js index 4705d343c1c..1a75fc8ffa0 100644 --- a/test/unit/mdc-dialog/mdc-dialog.test.js +++ b/test/unit/mdc-dialog/mdc-dialog.test.js @@ -473,10 +473,10 @@ test('adapter#getActionFromEvent returns null when attribute is not present', () assert.isNull(action); }); -test(`adapter#clickDefaultButton invokes click() on button matching ${strings.DEFAULT_BUTTON_ATTRIBUTE}`, () => { +test(`adapter#clickDefaultButton invokes click() on button matching ${strings.BUTTON_DEFAULT_ATTRIBUTE}`, () => { const fixture = getFixture(); const yesButton = fixture.querySelector('[data-mdc-dialog-action="yes"]'); - yesButton.setAttribute(strings.DEFAULT_BUTTON_ATTRIBUTE, 'true'); + yesButton.setAttribute(strings.BUTTON_DEFAULT_ATTRIBUTE, 'true'); const {component} = setupTest(fixture); yesButton.click = td.func('click'); @@ -485,7 +485,7 @@ test(`adapter#clickDefaultButton invokes click() on button matching ${strings.DE td.verify(yesButton.click()); }); -test(`adapter#clickDefaultButton does nothing if nothing matches ${strings.DEFAULT_BUTTON_ATTRIBUTE}`, () => { +test(`adapter#clickDefaultButton does nothing if nothing matches ${strings.BUTTON_DEFAULT_ATTRIBUTE}`, () => { const {component, yesButton, noButton} = setupTest(); yesButton.click = td.func('click'); noButton.click = td.func('click'); From 0122dcf5d1dd92219e74d341186497c0222679e2 Mon Sep 17 00:00:00 2001 From: Joy Zhong Date: Thu, 16 May 2019 14:02:45 -0400 Subject: [PATCH 6/6] feat(dialog): Fix component.ts. --- packages/mdc-dialog/component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mdc-dialog/component.ts b/packages/mdc-dialog/component.ts index 8482254e1cd..1c3c77a255f 100644 --- a/packages/mdc-dialog/component.ts +++ b/packages/mdc-dialog/component.ts @@ -188,6 +188,6 @@ export class MDCDialog extends MDCComponent { } private getInitialFocusEl_(): HTMLElement|null { - return document.querySelector(`[${strings.INITIAL_FOCUS_EL_ATTRIBUTE}]`); + return document.querySelector(`[${strings.INITIAL_FOCUS_ATTRIBUTE}]`); } }