-import { START, HistoryLocation } from '../types/index'
-export interface NavigationCallback {
- (to: HistoryLocation, from: HistoryLocation): void
-}
+import {
+ START,
+ HistoryLocation,
+ NavigationCallback,
+ RemoveListener,
+} from '../types/index'
export default abstract class BaseHistory {
// previousState: object
- location: HistoryLocation
+ location: HistoryLocation = START
+
+ /**
+ * Sync source with a different location.
+ * Adds an entry to the history
+ * @param to URL to go to
+ */
abstract push(to: HistoryLocation): void
+
+ /**
+ * Syncs source with a different location
+ * Replaces current entry in the history
+ * @param to URL to go to
+ */
abstract replace(to: HistoryLocation): void
- abstract listen(callback: NavigationCallback): Function
+
+ /**
+ * Notifies back whenever the location changes due to user interactions
+ * outside of the applicaiton. For example, going back/forward on a
+ * web browser
+ * @param callback callback to be called whenever the route changes
+ * @returns
+ */
+ abstract listen(callback: NavigationCallback): RemoveListener
+
/**
- * ensure the current location using the external source
- * for example, in HTML5 and hash history, that would be
+ * ensure the current location matches the external source
+ * For example, in HTML5 and hash history, that would be
* location.pathname
*/
abstract ensureLocation(): void
-
- constructor() {
- this.location = START
- }
}
-import BaseHistory, { NavigationCallback } from './base'
-import { HistoryLocation } from '../types/index'
+import BaseHistory from './base'
+import { HistoryLocation, NavigationCallback } from '../types/index'
export default class HTML5History extends BaseHistory {
private history = window.history
+ private _popStateListener:
+ | null
+ | ((this: Window, ev: PopStateEvent) => any) = null
+
constructor() {
super()
}
replace(to: HistoryLocation) {
if (to === this.location) return
+ console.log('replace', this.location, to)
this.history.replaceState(
{
- ...(this.history.state || {}),
+ replacedState: this.history.state || {},
+ from: this.location,
to,
},
'',
}
push(to: HistoryLocation) {
- // TODO resolve url
- // TODO compare current location to prevent navigation
+ // TODO: resolve url
+ // TODO: compare current location to prevent navigation
if (to === this.location) return
const state = {
from: this.location,
to,
}
- console.log('push', state)
+ console.log('push', this.location, to)
this.history.pushState(state, '', to)
this.location = to
}
- listen(callback: NavigationCallback): Function {
- window.addEventListener('popstate', ({ state }) => {
+ listen(callback: NavigationCallback) {
+ this._popStateListener = ({ state }) => {
const from = this.location
// we have the state from the old entry, not the current one being removed
- // TODO correctly parse pathname
+ // TODO: correctly parse pathname
this.location = state ? state.to : window.location.pathname
callback(this.location, from)
- })
+ }
+ window.addEventListener('popstate', this._popStateListener)
- return () => {}
+ return () => {
+ window.removeEventListener('popstate', this._popStateListener!)
+ }
}
}