diff --git a/routes/_components/IconButton.html b/routes/_components/IconButton.html index 868b4094..332b250b 100644 --- a/routes/_components/IconButton.html +++ b/routes/_components/IconButton.html @@ -4,7 +4,8 @@ aria-pressed="{{pressable ? !!pressed : ''}}" class="{{computedClass}}" :disabled - delegate-key="{{delegateKey}}" > + delegate-key="{{delegateKey}}" + focus-key="{{focusKey || ''}}" > @@ -14,6 +15,7 @@ aria-label="{{label}}" aria-pressed="{{pressable ? !!pressed : ''}}" class="{{computedClass}}" + focus-key="{{focusKey || ''}}" :disabled on:click > diff --git a/routes/_components/compose/ComposeBox.html b/routes/_components/compose/ComposeBox.html index c7a0ee65..d1261916 100644 --- a/routes/_components/compose/ComposeBox.html +++ b/routes/_components/compose/ComposeBox.html @@ -6,7 +6,7 @@ {{/if}} - + diff --git a/routes/_components/compose/ComposeInput.html b/routes/_components/compose/ComposeInput.html index 37572895..054d961d 100644 --- a/routes/_components/compose/ComposeInput.html +++ b/routes/_components/compose/ComposeInput.html @@ -43,6 +43,9 @@ setupSyncFromStore() { this.observe('text', text => { this.set({rawText: text}) + if (this.get('autoFocus')) { + this.refs.textarea.focus() + } }) }, setupSyncToStore() { diff --git a/routes/_components/status/StatusToolbar.html b/routes/_components/status/StatusToolbar.html index 4485b7bc..de55b0b4 100644 --- a/routes/_components/status/StatusToolbar.html +++ b/routes/_components/status/StatusToolbar.html @@ -4,6 +4,7 @@ href="#fa-reply" disabled="{{disableReply}}" delegateKey="{{replyKey}}" + focusKey="{{replyKey}}" /> - + {{else}} {{/if}} diff --git a/tests/spec/010-focus.js b/tests/spec/010-focus.js index 61486212..3b2e8d00 100644 --- a/tests/spec/010-focus.js +++ b/tests/spec/010-focus.js @@ -1,6 +1,6 @@ import { getNthStatus, scrollToStatus, closeDialogButton, modalDialogContents, getActiveElementClass, goBack, getUrl, - goBackButton, getActiveElementInnerText + goBackButton, getActiveElementInnerText, getNthReplyButton, getActiveElementAriaLabel, getActiveElementInsideNthStatus } from '../utils' import { foobarRole } from '../roles' @@ -14,6 +14,7 @@ test('modal preserves focus', async t => { .click(closeDialogButton) .expect(modalDialogContents.exists).notOk() .expect(getActiveElementClass()).contains('play-video-button') + .expect(getActiveElementInsideNthStatus()).eql('9') }) test('timeline preserves focus', async t => { @@ -24,6 +25,7 @@ test('timeline preserves focus', async t => { await goBack() await t.expect(getUrl()).eql('http://localhost:4002/') .expect(getActiveElementClass()).contains('status-article status-in-timeline') + .expect(getActiveElementInsideNthStatus()).eql('0') }) test('timeline link preserves focus', async t => { @@ -38,6 +40,7 @@ test('timeline link preserves focus', async t => { .click(goBackButton) .expect(getUrl()).eql('http://localhost:4002/') .expect(getActiveElementClass()).contains('status-sidebar') + .expect(getActiveElementInsideNthStatus()).eql('0') }) test('notification timeline preserves focus', async t => { @@ -49,4 +52,17 @@ test('notification timeline preserves focus', async t => { .click(goBackButton) .expect(getUrl()).eql('http://localhost:4002/notifications') .expect(getActiveElementInnerText()).eql('quux') + .expect(getActiveElementInsideNthStatus()).eql('5') +}) + +test('reply preserves focus and moves focus to the text input', async t => { + await t.useRole(foobarRole) + .click(getNthReplyButton(1)) + .expect(getUrl()).contains('/reply') + .expect(getActiveElementClass()).contains('compose-box-input') + .click(goBackButton) + .expect(getUrl()).eql('http://localhost:4002/') + .expect(getActiveElementClass()).contains('icon-button') + .expect(getActiveElementAriaLabel()).eql('Reply') + .expect(getActiveElementInsideNthStatus()).eql('1') }) diff --git a/tests/utils.js b/tests/utils.js index dc51ca83..7d8f24b5 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -53,6 +53,20 @@ export const getActiveElementInnerText = exec(() => document.activeElement && document.activeElement.innerText ) +export const getActiveElementAriaLabel = exec(() => + document.activeElement ? document.activeElement.getAttribute('aria-label') : '' +) + +export const getActiveElementInsideNthStatus = exec(() => { + let element = document.activeElement + while (element) { + if (element.hasAttribute('aria-posinset')) { + return element.getAttribute('aria-posinset') + } + element = element.parentElement + } +}) + export const goBack = exec(() => window.history.back()) export const forceOffline = exec(() => window.store.set({online: false}))