node_modules
coverage
.nyc_output
+.rpt2_cache
<!DOCTYPE html>
<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
- <title>Testing History HTML5</title>
-</head>
-<body>
-</body>
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <meta http-equiv="X-UA-Compatible" content="ie=edge" />
+ <title>Testing History HTML5</title>
+ </head>
+ <body>
+ <div id="app">
+ <label>
+ <input type="checkbox" onchange="cancel = !cancel" /> Cancel Next
+ Navigation
+ </label>
+ </div>
+ </body>
</html>
import { Router, HTML5History } from '../src'
import { RouteComponent } from '../src/types'
+declare global {
+ interface Window {
+ cancel: boolean
+ }
+}
+window.cancel = false
+
const component: RouteComponent = {
template: `<div>A component</div>`,
}
routes: [
{ path: '/', component },
{ path: '/users/:id', name: 'user', component },
+ { path: '/n/:n', name: 'increment', component },
{ path: '/multiple/:a/:b', name: 'user', component },
{
path: '/with-guard/:n',
next()
})
+r.beforeEach((to, from, next) => {
+ if (window.cancel) return next(false)
+ next()
+})
+
r.afterEach((to, from) => {
console.log(
`After guard: from ${from.fullPath} to ${
h.listen((to, from, { type }) => {
console.log(`popstate(${type})`, { to, from })
})
+
async function run() {
// r.push('/multiple/one/two')
// previousState: object
location: HistoryLocationNormalized = START
base: string = ''
+ paused: boolean = false
utils = utils
/**
*/
abstract replace(to: HistoryLocation): void
+ /**
+ * Goes back in history log. Should trigger any listener added via
+ * `listen`. If we are on the first entry, behaviour may change depending
+ * on implementation
+ */
+ abstract back(): void
+
/**
* Notifies back whenever the location changes due to user interactions
* outside of the applicaiton. For example, going back/forward on a
push(to: HistoryLocation, data?: HistoryState) {
// replace current entry state to add the forward value
+ // TODO: should be removed and let the user normalize the location?
+ // or make it fast so normalization on a normalized object is fast
const normalized = this.utils.normalizeLocation(to)
this.history.replaceState(
buildState(
this.location = normalized
}
+ back() {
+ // TODO: do not trigger listen
+ this.history.back()
+ }
+
listen(callback: NavigationCallback) {
// settup the listener and prepare teardown callbacks
this._listeners.push(callback)
state,
location: this.location,
})
+ if (this.paused) {
+ cs.info('Ignored beacuse paused')
+ return
+ }
const from = this.location
// we have the state from the old entry, not the current one being removed
// TODO: correctly parse pathname
* @param search
*/
export function parseQuery(search: string): HistoryQuery {
- // TODO: optimize by using a for loop
const hasLeadingIM = search[0] === '?'
const query: HistoryQuery = {}
const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&')
resolve: (params?: RouteParams) => string
record: RouteRecord // TODO: NormalizedRouteRecord?
parent: RouteMatcher | void
+ // TODO: children so they can be removed
+ // children: RouteMatcher[]
keys: string[]
}
-import { BaseHistory, HistoryLocationNormalized } from './history/base'
+import {
+ BaseHistory,
+ HistoryLocationNormalized,
+ NavigationType,
+} from './history/base'
import { RouterMatcher } from './matcher'
import {
RouteLocation,
this.matcher = new RouterMatcher(options.routes)
- this.history.listen((to, from, info) => {
+ this.history.listen(async (to, from, info) => {
const matchedRoute = this.matchLocation(to, this.currentRoute)
// console.log({ to, matchedRoute })
// TODO: navigate & guards
+ const toLocation: RouteLocationNormalized = { ...to, ...matchedRoute }
+
+ try {
+ await this.navigate(toLocation, this.currentRoute)
- this.currentRoute = {
- ...to,
- ...matchedRoute,
+ // accept current navigation
+ this.currentRoute = {
+ ...to,
+ ...matchedRoute,
+ }
+ } catch (error) {
+ // TODO: use the push/replace techieque with any navigation to
+ // preserve history when moving forward
+ if (error instanceof NavigationGuardRedirect) {
+ this.push(error.to)
+ } else {
+ // TODO: handle abort and redirect correctly
+ // if we were going back, we push and discard the rest of the history
+ if (info.type === NavigationType.back) {
+ this.history.push(from)
+ } else {
+ // TODO: go back because we cancelled, then
+ // or replace and not discard the rest of history. Check issues, there was one talking about this
+ // behaviour, maybe we can do better
+ this.history.paused = true
+ this.history.back()
+ this.history.paused = false
+ }
+ }
}
})
}
}
}
+ async push(to: RouteLocation): Promise<RouteLocationNormalized> {
+ // match the location
+ const { url, location } =
+ let url: HistoryLocationNormalized
+ let location: MatcherLocationNormalized
+ }
+
/**
* Trigger a navigation, adding an entry to the history stack. Also apply all navigation
* guards first
async push(to: RouteLocation): Promise<RouteLocationNormalized> {
let url: HistoryLocationNormalized
let location: MatcherLocationNormalized
+ // TODO: refactor into matchLocation to return location and url
if (typeof to === 'string' || 'path' in to) {
url = this.history.utils.normalizeLocation(to)
// TODO: should allow a non matching url to allow dynamic routing to work
beforeEach(guard: NavigationGuard): ListenerRemover {
this.beforeGuards.push(guard)
return () => {
- this.beforeGuards.splice(this.beforeGuards.indexOf(guard), 1)
+ const i = this.beforeGuards.indexOf(guard)
+ if (i > -1) this.beforeGuards.splice(i, 1)
}
}
afterEach(guard: PostNavigationGuard): ListenerRemover {
this.afterGuards.push(guard)
return () => {
- this.afterGuards.splice(this.afterGuards.indexOf(guard), 1)
+ const i = this.afterGuards.indexOf(guard)
+ if (i > -1) this.afterGuards.splice(i, 1)
}
}
}