go back to having Timeline.html manage focus

This commit is contained in:
Nolan Lawson 2018-03-22 21:59:02 -07:00
parent 7a9cb22269
commit cd968245e1
5 changed files with 21 additions and 48 deletions

View File

@ -7,8 +7,7 @@
{{else}} {{else}}
<article class="notification-article" <article class="notification-article"
tabindex="0" tabindex="0"
aria-posinset="{{index}}" aria-setsize="{{length}}" aria-posinset="{{index}}" aria-setsize="{{length}}" >
ref:node >
<StatusHeader :notification :notificationId :status :statusId :timelineType <StatusHeader :notification :notificationId :status :statusId :timelineType
:account :accountId :uuid isStatusInNotification="true" /> :account :accountId :uuid isStatusInNotification="true" />
</article> </article>
@ -32,16 +31,8 @@
import Status from './Status.html' import Status from './Status.html'
import StatusHeader from './StatusHeader.html' import StatusHeader from './StatusHeader.html'
import { store } from '../../_store/store' import { store } from '../../_store/store'
import { restoreFocus } from '../../_utils/restoreFocus'
export default { export default {
oncreate() {
let focusSelector = this.get('focusSelector')
if (this.refs.node && focusSelector &&
this.store.getForCurrentTimeline('shouldRestoreFocus')) {
restoreFocus(this.refs.node, focusSelector)
}
},
components: { components: {
Status, Status,
StatusHeader StatusHeader

View File

@ -5,8 +5,7 @@
aria-posinset="{{index}}" aria-posinset="{{index}}"
aria-setsize="{{length}}" aria-setsize="{{length}}"
aria-label="{{ariaLabel}}" aria-label="{{ariaLabel}}"
on:recalculateHeight on:recalculateHeight >
ref:node >
{{#if showHeader}} {{#if showHeader}}
<StatusHeader :notification :notificationId :status :statusId :timelineType <StatusHeader :notification :notificationId :status :statusId :timelineType
:account :accountId :uuid :isStatusInNotification /> :account :accountId :uuid :isStatusInNotification />
@ -103,7 +102,6 @@
import { goto } from 'sapper/runtime.js' import { goto } from 'sapper/runtime.js'
import { registerClickDelegate, unregisterClickDelegate } from '../../_utils/delegate' import { registerClickDelegate, unregisterClickDelegate } from '../../_utils/delegate'
import { classname } from '../../_utils/classname' import { classname } from '../../_utils/classname'
import { restoreFocus } from '../../_utils/restoreFocus'
export default { export default {
oncreate() { oncreate() {
@ -112,11 +110,6 @@
// the whole <article> is clickable in this case // the whole <article> is clickable in this case
registerClickDelegate(delegateKey, (e) => this.onClickOrKeydown(e)) registerClickDelegate(delegateKey, (e) => this.onClickOrKeydown(e))
} }
let focusSelector = this.get('focusSelector')
if (this.refs.node && focusSelector &&
this.store.getForCurrentTimeline('shouldRestoreFocus')) {
restoreFocus(this.refs.node, focusSelector)
}
}, },
ondestroy() { ondestroy() {
let delegateKey = this.get('delegateKey') let delegateKey = this.get('delegateKey')

View File

@ -79,6 +79,7 @@
console.log('timeline oncreate()') console.log('timeline oncreate()')
this.setupFocus() this.setupFocus()
setupTimeline() setupTimeline()
this.restoreFocus()
this.setupStreaming() this.setupStreaming()
}, },
ondestroy() { ondestroy() {
@ -98,7 +99,7 @@
VirtualListComponent: (timelineType) => { VirtualListComponent: (timelineType) => {
return timelineType === 'notifications' ? NotificationVirtualListItem : StatusVirtualListItem return timelineType === 'notifications' ? NotificationVirtualListItem : StatusVirtualListItem
}, },
makeProps: ($currentInstance, timelineType, timelineValue, $lastFocusedElementSelector) => async (itemId) => { makeProps: ($currentInstance, timelineType, timelineValue) => async (itemId) => {
let res = { let res = {
timelineType, timelineType,
timelineValue timelineValue
@ -108,12 +109,6 @@
} else { } else {
res.status = await database.getStatus($currentInstance, itemId) res.status = await database.getStatus($currentInstance, itemId)
} }
if ($lastFocusedElementSelector && $lastFocusedElementSelector.includes(itemId)) {
// this selector is guaranteed to contain the statusId. false positives
// (e.g. notification id "1" matches notification id "11") are okay
// because Status.html won't be able to find the selector which is fine.
res.focusSelector = $lastFocusedElementSelector
}
return res return res
}, },
label: (timeline, $currentInstance, timelineType, timelineValue) => { label: (timeline, $currentInstance, timelineType, timelineValue) => {
@ -177,16 +172,6 @@
}, },
onScrollTopChanged(scrollTop) { onScrollTopChanged(scrollTop) {
this.set({scrollTop: scrollTop}) this.set({scrollTop: scrollTop})
if (!this.get('observedOnScrollTopChanged')) {
// ignore the first scroll top change, e.g.
// because we forced a scroll top change
this.set({observedOnScrollTopChanged: true})
} else {
// after that, don't allow statuses/notifications to call focus()
// after we've already started scrolling. that causes scrolling to
// jump around
this.store.setForCurrentTimeline({shouldRestoreFocus: false})
}
}, },
onScrollToBottom() { onScrollToBottom() {
if (!this.store.get('initialized') || if (!this.store.get('initialized') ||
@ -244,8 +229,7 @@
setupFocus() { setupFocus() {
this.onPushState = this.onPushState.bind(this) this.onPushState = this.onPushState.bind(this)
this.store.setForCurrentTimeline({ this.store.setForCurrentTimeline({
ignoreBlurEvents: false, ignoreBlurEvents: false
shouldRestoreFocus: true
}) })
window.addEventListener('pushState', this.onPushState) window.addEventListener('pushState', this.onPushState)
}, },
@ -289,7 +273,22 @@
} catch (err) { } catch (err) {
console.error('unable to clear focus', err) console.error('unable to clear focus', err)
} }
},
restoreFocus() {
let lastFocusedElementSelector = this.store.get('lastFocusedElementSelector')
if (!lastFocusedElementSelector) {
return
} }
console.log('restoreFocus', lastFocusedElementSelector)
requestAnimationFrame(() => {
requestAnimationFrame(() => {
let element = document.querySelector(lastFocusedElementSelector)
if (element) {
element.focus()
}
})
})
},
} }
} }
</script> </script>

View File

@ -18,7 +18,6 @@ export function timelineComputations (store) {
computeForTimeline(store, 'showHeader', false) computeForTimeline(store, 'showHeader', false)
computeForTimeline(store, 'shouldShowHeader', false) computeForTimeline(store, 'shouldShowHeader', false)
computeForTimeline(store, 'timelineItemIdsAreStale', false) computeForTimeline(store, 'timelineItemIdsAreStale', false)
computeForTimeline(store, 'shouldRestoreFocus', false)
store.compute('firstTimelineItemId', ['timelineItemIds'], (timelineItemIds) => { store.compute('firstTimelineItemId', ['timelineItemIds'], (timelineItemIds) => {
return timelineItemIds && timelineItemIds[0] return timelineItemIds && timelineItemIds[0]

View File

@ -1,9 +0,0 @@
export function restoreFocus (element, selector) {
// Have to check from the parent because otherwise this element itself wouldn't match.
// This is fine for <article class=status> elements because they already have a div wrapper.
let elementToFocus = element.parentElement.querySelector(selector)
console.log('restoreFocus', selector, elementToFocus)
if (elementToFocus) {
elementToFocus.focus()
}
}