add auto-focus to reply input text

This commit is contained in:
Nolan Lawson 2018-03-16 19:04:48 -07:00
parent 9a0071a934
commit ed0db17ca0
7 changed files with 40 additions and 4 deletions

View file

@ -4,7 +4,8 @@
aria-pressed="{{pressable ? !!pressed : ''}}" aria-pressed="{{pressable ? !!pressed : ''}}"
class="{{computedClass}}" class="{{computedClass}}"
:disabled :disabled
delegate-key="{{delegateKey}}" > delegate-key="{{delegateKey}}"
focus-key="{{focusKey || ''}}" >
<svg class="icon-button-svg"> <svg class="icon-button-svg">
<use xlink:href="{{href}}" /> <use xlink:href="{{href}}" />
</svg> </svg>
@ -14,6 +15,7 @@
aria-label="{{label}}" aria-label="{{label}}"
aria-pressed="{{pressable ? !!pressed : ''}}" aria-pressed="{{pressable ? !!pressed : ''}}"
class="{{computedClass}}" class="{{computedClass}}"
focus-key="{{focusKey || ''}}"
:disabled :disabled
on:click > on:click >
<svg class="icon-button-svg"> <svg class="icon-button-svg">

View file

@ -6,7 +6,7 @@
<ComposeContentWarning :realm :contentWarning /> <ComposeContentWarning :realm :contentWarning />
</div> </div>
{{/if}} {{/if}}
<ComposeInput :realm :text /> <ComposeInput :realm :text :autoFocus />
<ComposeLengthGauge :length :overLimit /> <ComposeLengthGauge :length :overLimit />
<ComposeToolbar :realm :postPrivacy :media :contentWarningShown /> <ComposeToolbar :realm :postPrivacy :media :contentWarningShown />
<ComposeLengthIndicator :length :overLimit /> <ComposeLengthIndicator :length :overLimit />

View file

@ -43,6 +43,9 @@
setupSyncFromStore() { setupSyncFromStore() {
this.observe('text', text => { this.observe('text', text => {
this.set({rawText: text}) this.set({rawText: text})
if (this.get('autoFocus')) {
this.refs.textarea.focus()
}
}) })
}, },
setupSyncToStore() { setupSyncToStore() {

View file

@ -4,6 +4,7 @@
href="#fa-reply" href="#fa-reply"
disabled="{{disableReply}}" disabled="{{disableReply}}"
delegateKey="{{replyKey}}" delegateKey="{{replyKey}}"
focusKey="{{replyKey}}"
/> />
<IconButton <IconButton
label="{{reblogLabel}}" label="{{reblogLabel}}"

View file

@ -7,7 +7,7 @@
timelineValue="{{params.statusId}}" timelineValue="{{params.statusId}}"
:status :status
/> />
<ComposeBox realm="{{params.statusId}}" /> <ComposeBox realm="{{params.statusId}}" autoFocus="true" />
{{else}} {{else}}
<LoadingPage /> <LoadingPage />
{{/if}} {{/if}}

View file

@ -1,6 +1,6 @@
import { import {
getNthStatus, scrollToStatus, closeDialogButton, modalDialogContents, getActiveElementClass, goBack, getUrl, getNthStatus, scrollToStatus, closeDialogButton, modalDialogContents, getActiveElementClass, goBack, getUrl,
goBackButton, getActiveElementInnerText goBackButton, getActiveElementInnerText, getNthReplyButton, getActiveElementAriaLabel, getActiveElementInsideNthStatus
} from '../utils' } from '../utils'
import { foobarRole } from '../roles' import { foobarRole } from '../roles'
@ -14,6 +14,7 @@ test('modal preserves focus', async t => {
.click(closeDialogButton) .click(closeDialogButton)
.expect(modalDialogContents.exists).notOk() .expect(modalDialogContents.exists).notOk()
.expect(getActiveElementClass()).contains('play-video-button') .expect(getActiveElementClass()).contains('play-video-button')
.expect(getActiveElementInsideNthStatus()).eql('9')
}) })
test('timeline preserves focus', async t => { test('timeline preserves focus', async t => {
@ -24,6 +25,7 @@ test('timeline preserves focus', async t => {
await goBack() await goBack()
await t.expect(getUrl()).eql('http://localhost:4002/') await t.expect(getUrl()).eql('http://localhost:4002/')
.expect(getActiveElementClass()).contains('status-article status-in-timeline') .expect(getActiveElementClass()).contains('status-article status-in-timeline')
.expect(getActiveElementInsideNthStatus()).eql('0')
}) })
test('timeline link preserves focus', async t => { test('timeline link preserves focus', async t => {
@ -38,6 +40,7 @@ test('timeline link preserves focus', async t => {
.click(goBackButton) .click(goBackButton)
.expect(getUrl()).eql('http://localhost:4002/') .expect(getUrl()).eql('http://localhost:4002/')
.expect(getActiveElementClass()).contains('status-sidebar') .expect(getActiveElementClass()).contains('status-sidebar')
.expect(getActiveElementInsideNthStatus()).eql('0')
}) })
test('notification timeline preserves focus', async t => { test('notification timeline preserves focus', async t => {
@ -49,4 +52,17 @@ test('notification timeline preserves focus', async t => {
.click(goBackButton) .click(goBackButton)
.expect(getUrl()).eql('http://localhost:4002/notifications') .expect(getUrl()).eql('http://localhost:4002/notifications')
.expect(getActiveElementInnerText()).eql('quux') .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')
}) })

View file

@ -53,6 +53,20 @@ export const getActiveElementInnerText = exec(() =>
document.activeElement && document.activeElement.innerText 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 goBack = exec(() => window.history.back())
export const forceOffline = exec(() => window.store.set({online: false})) export const forceOffline = exec(() => window.store.set({online: false}))