+
{{#if !$initialized}}
{{/if}}
@@ -55,11 +60,22 @@
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()')
+ this.onPushState = this.onPushState.bind(this)
+ this.store.setForCurrentTimeline({ignoreBlurEvents: false})
+ window.addEventListener('pushState', this.onPushState)
setupTimeline()
+ if (this.store.get('initialized')) {
+ this.restoreFocus()
+ }
+ },
+ ondestroy() {
+ console.log('ondestroy')
+ window.removeEventListener('pushState', this.onPushState)
},
data: () => ({
StatusVirtualListItem,
@@ -109,6 +125,10 @@
PseudoVirtualList,
LoadingPage
},
+ events: {
+ focusWithCapture,
+ blurWithCapture
+ },
methods: {
initialize() {
if (this.store.get('initialized') || !this.store.get('timelineItemIds')) {
@@ -117,6 +137,9 @@
console.log('timeline initialize()')
initializeTimeline()
},
+ onPushState() {
+ this.store.setForCurrentTimeline({ ignoreBlurEvents: true })
+ },
onScrollToBottom() {
if (!this.store.get('initialized') ||
this.store.get('runningUpdate') ||
@@ -124,7 +147,49 @@
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() {
+ if (this.store.get('ignoreBlurEvents')) {
+ return
+ }
+ 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()
+ }
+ })
+ })
+ }
+ },
}
}
\ No newline at end of file
diff --git a/routes/_store/mixins.js b/routes/_store/mixins.js
index efd7490..1a247c1 100644
--- a/routes/_store/mixins.js
+++ b/routes/_store/mixins.js
@@ -12,6 +12,12 @@ function timelineMixins (Store) {
let timelineData = timelines[instanceName] || {}
return (timelineData[timelineName] || {})[key]
}
+
+ Store.prototype.setForCurrentTimeline = function (obj) {
+ let instanceName = this.get('currentInstance')
+ let timelineName = this.get('currentTimeline')
+ this.setForTimeline(instanceName, timelineName, obj)
+ }
}
export function mixins (Store) {
diff --git a/routes/_store/timelineComputations.js b/routes/_store/timelineComputations.js
index 2cfc2c2..dd332fb 100644
--- a/routes/_store/timelineComputations.js
+++ b/routes/_store/timelineComputations.js
@@ -1,11 +1,20 @@
+
+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')
+ computeForTimeline(store, 'ignoreBlurEvents')
+
store.compute('lastTimelineItemId', ['timelineItemIds'], (timelineItemIds) => timelineItemIds && timelineItemIds.length && timelineItemIds[timelineItemIds.length - 1])
}
diff --git a/routes/_utils/events.js b/routes/_utils/events.js
index fc9b144..1b02ba5 100644
--- a/routes/_utils/events.js
+++ b/routes/_utils/events.js
@@ -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)
+ }
+ }
+}
\ No newline at end of file
diff --git a/routes/_utils/historyEvents.js b/routes/_utils/historyEvents.js
new file mode 100644
index 0000000..6f40b28
--- /dev/null
+++ b/routes/_utils/historyEvents.js
@@ -0,0 +1,18 @@
+// hacky way to listen for pushState/replaceState changes
+// per https://stackoverflow.com/a/25673911/680742
+
+function wrapper (type) {
+ let orig = history[type]
+ return function () {
+ let result = orig.apply(this, arguments)
+ let e = new Event(type)
+ e.arguments = arguments
+ window.dispatchEvent(e)
+ return result
+ }
+}
+
+if (process.browser) {
+ history.pushState = wrapper('pushState')
+ history.replaceState = wrapper('replaceState')
+}
\ No newline at end of file
diff --git a/templates/main.js b/templates/main.js
index c3b7df6..aabe53c 100644
--- a/templates/main.js
+++ b/templates/main.js
@@ -2,6 +2,7 @@ import { init } from 'sapper/runtime.js'
import { loadPolyfills } from '../routes/_utils/loadPolyfills'
import '../routes/_utils/offlineNotification'
import '../routes/_utils/serviceWorkerClient'
+import '../routes/_utils/historyEvents'
loadPolyfills().then(() => {
// `routes` is an array of route objects injected by Sapper