start on trying to save focus for timeline

feels like this isn't really working because I can't distinguish between blur events because the timeline is being destroyed and blur events because the user clicked and we lost focus... not sure how important this feature is, and it adds a lot of complexity in the form of extra attributes to track the focused element
This commit is contained in:
Nolan Lawson 2018-02-10 12:12:05 -08:00
parent e6bf344aec
commit 820d77a78f
4 changed files with 98 additions and 15 deletions

View File

@ -1,7 +1,8 @@
<article class="status-article {{getClasses(originalStatus, timelineType, isStatusInOwnThread)}}"
tabindex="0"
delegate-click-key="{{delegateKey}}"
delegate-keydown-key="{{delegateKey}}"
delegate-click-key="{{elementKey}}"
delegate-keydown-key="{{elementKey}}"
focus-key="{{elementKey}}"
aria-posinset="{{index}}"
aria-setsize="{{length}}"
aria-label="Status by {{originalStatus.account.display_name || originalStatus.account.username}}"
@ -94,15 +95,15 @@
export default {
oncreate() {
let delegateKey = this.get('delegateKey')
let elementKey = this.get('elementKey')
let onClickOrKeydown = this.onClickOrKeydown.bind(this)
registerDelegate('click', delegateKey, onClickOrKeydown)
registerDelegate('keydown', delegateKey, onClickOrKeydown)
registerDelegate('click', elementKey, onClickOrKeydown)
registerDelegate('keydown', elementKey, onClickOrKeydown)
},
ondestroy() {
let delegateKey = this.get('delegateKey')
unregisterDelegate('click', delegateKey)
unregisterDelegate('keydown', delegateKey)
let elementKey = this.get('elementKey')
unregisterDelegate('click', elementKey)
unregisterDelegate('keydown', elementKey)
},
components: {
StatusSidebar,
@ -147,7 +148,7 @@
computed: {
originalStatus: (status) => status.reblog ? status.reblog : status,
statusId: (originalStatus) => originalStatus.id,
delegateKey: (statusId, timelineType, timelineValue) => `status-${timelineType}-${timelineValue}-${statusId}`,
elementKey: (statusId, timelineType, timelineValue) => `status-${timelineType}-${timelineValue}-${statusId}`,
contextualStatusId: ($currentInstance, timelineType, timelineValue, status, notification) => {
return `${$currentInstance}/${timelineType}/${timelineValue}/${notification ? notification.id : ''}/${status.id}`
},

View File

@ -1,4 +1,9 @@
<div class="timeline" role="feed" aria-label="{{label}}">
<div class="timeline"
role="feed"
aria-label="{{label}}"
on:focusWithCapture="saveFocus(event)"
on:blurWithCapture="clearFocus(event)"
>
{{#if !$initialized}}
<LoadingPage />
{{/if}}
@ -55,11 +60,19 @@
import { database } from '../../_database/database'
import { initializeTimeline, fetchTimelineItemsOnScrollToBottom, setupTimeline } from '../../_actions/timeline'
import LoadingPage from '../LoadingPage.html'
import { focusWithCapture, blurWithCapture } from '../../_utils/events'
export default {
async oncreate() {
oncreate() {
console.log('timeline oncreate()')
setupTimeline()
if (this.store.get('initialized')) {
console.log('initialized!!!!')
this.restoreFocus()
}
},
ondestroy() {
console.log('ondestroy')
},
data: () => ({
StatusVirtualListItem,
@ -109,6 +122,10 @@
PseudoVirtualList,
LoadingPage
},
events: {
focusWithCapture,
blurWithCapture
},
methods: {
initialize() {
if (this.store.get('initialized') || !this.store.get('timelineItemIds')) {
@ -124,7 +141,46 @@
return
}
fetchTimelineItemsOnScrollToBottom()
}
},
saveFocus(e) {
let instanceName = this.store.get('currentInstance')
let timelineName = this.get('timeline')
let lastFocusedElementSelector
let activeElement = e.target
if (activeElement) {
let focusKey = activeElement.getAttribute('focus-key')
if (focusKey) {
lastFocusedElementSelector = `[focus-key=${focusKey}]`
}
}
console.log('saving focus to ', lastFocusedElementSelector)
this.store.setForTimeline(instanceName, timelineName, {
lastFocusedElementSelector
})
},
clearFocus() {
/*console.log('clearing focus')
let instanceName = this.store.get('currentInstance')
let timelineName = this.get('timeline')
this.store.setForTimeline(instanceName, timelineName, {
lastFocusedElementSelector: null
})*/
},
restoreFocus() {
let lastFocusedElementSelector = this.store.get('lastFocusedElementSelector')
console.log('lastFocused', lastFocusedElementSelector)
if (lastFocusedElementSelector) {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
let element = document.querySelector(lastFocusedElementSelector)
console.log('el', element)
if (element) {
element.focus()
}
})
})
}
},
}
}
</script>

View File

@ -1,11 +1,19 @@
function computeForTimeline(store, key) {
store.compute(key, ['currentTimelineData'], (currentTimelineData) => currentTimelineData[key])
}
export function timelineComputations (store) {
store.compute('currentTimelineData', ['currentInstance', 'currentTimeline', 'timelines'],
(currentInstance, currentTimeline, timelines) => {
return ((timelines && timelines[currentInstance]) || {})[currentTimeline] || {}
})
store.compute('timelineItemIds', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.timelineItemIds)
store.compute('runningUpdate', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.runningUpdate)
store.compute('initialized', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.initialized)
computeForTimeline(store, 'timelineItemIds')
computeForTimeline(store, 'runningUpdate')
computeForTimeline(store, 'initialized')
computeForTimeline(store, 'lastFocusedElementSelector')
store.compute('lastTimelineItemId', ['timelineItemIds'], (timelineItemIds) => timelineItemIds && timelineItemIds.length && timelineItemIds[timelineItemIds.length - 1])
}

View File

@ -34,3 +34,21 @@ export function mouseover (node, callback) {
}
}
}
export function focusWithCapture (node, callback) {
node.addEventListener('focus', callback, true)
return {
teardown () {
node.removeEventListener('focus', callback, true)
}
}
}
export function blurWithCapture (node, callback) {
node.addEventListener('blur', callback, true)
return {
teardown () {
node.removeEventListener('blur', callback, true)
}
}
}