},
})
-r.push({
- hash: '#hey',
-})
+// r.push({
+// hash: '#hey',
+// })
HistoryLocation,
NavigationCallback,
RemoveListener,
+ HistoryURL,
} from '../types/index'
export default abstract class BaseHistory {
*/
abstract listen(callback: NavigationCallback): RemoveListener
+ /**
+ * Transforms a URL into an object
+ * @param location location to normalize
+ * @param currentLocation current location, to reuse params and location
+ */
+ abstract parseURL(location: HistoryLocation): HistoryURL
+
/**
* ensure the current location matches the external source
* For example, in HTML5 and hash history, that would be
NavigationCallback,
HistoryState,
NavigationType,
+ HistoryURL,
} from '../types/index'
const cs = consola.withTag('html5')
return teardown
}
+ parseURL(location: string): HistoryURL {
+ let path = '',
+ search: HistoryURL['search'] = {},
+ searchString = '',
+ hash = ''
+
+ // Could use URL and URLSearchParams but IE 11 doesn't support it
+ const searchPos = location.indexOf('?')
+ const hashPos = location.indexOf(location, searchPos > -1 ? searchPos : 0)
+ if (searchPos > -1) {
+ path = location.slice(0, searchPos)
+ searchString = location.slice(
+ searchPos + 1,
+ hashPos > -1 ? hashPos : location.length - 1
+ )
+
+ // TODO: properly do this in a util function
+ search = searchString.split('&').reduce((search, entry) => {
+ const [key, value] = entry.split('=')
+ search[key] = value
+ return search
+ }, search)
+ }
+
+ if (hashPos > -1) {
+ path = path || location.slice(0, hashPos)
+ hash = location.slice(hashPos, location.length - 1)
+ }
+
+ path = path || location
+
+ return {
+ path,
+ // TODO: transform searchString
+ search,
+ hash,
+ }
+ }
+
destroy() {
for (const teardown of this._teardowns) teardown()
this._teardowns = []
*/
push(to: RouterLocation) {
// TODO: resolve URL
- const path = this.matcher.resolve(to, this.currentRoute)
+ const location = this.matcher.resolve(to, this.currentRoute)
// TODO: call hooks, guards
- this.history.push(path)
+ this.history.push(location.fullPath)
+ this.currentRoute = location
}
getRouteRecord(location: RouterLocation) {}
this.matchers = routes.map(generateMatcher)
}
- /**
- * Normalize a RouterLocation into an object that is easier to handle
- * @param location location to normalize
- * @param currentLocation current location, to reuse params and location
- */
- normalize(
- location: Readonly<RouterLocation>,
- currentLocation: Readonly<RouterLocationNormalized>
- ): RouterLocationNormalized {
- return {} as RouterLocationNormalized
- }
-
/**
* Transforms a RouterLocation object into a URL string. If a string is
* passed, it returns the string itself
*/
resolve(
location: Readonly<RouterLocation>,
- currentLocation: RouterLocationNormalized
- ): string {
- if (typeof location === 'string') return location
+ currentLocation: Readonly<RouterLocationNormalized>
+ ): RouterLocationNormalized {
+ if (typeof location === 'string')
+ return {
+ path: location,
+ fullPath: location,
+ // TODO: resolve params, query and hash
+ params: {},
+ query: {},
+ hash: '',
+ }
if ('path' in location) {
// TODO: warn missing params
- return (
- location.path + stringifyQuery(location.query) + (location.hash || '')
- )
+ // TODO: extract query and hash? warn about presence
+ return {
+ path: location.path,
+ query: location.query || {},
+ hash: location.hash || '',
+ params: {},
+ fullPath:
+ location.path +
+ stringifyQuery(location.query) +
+ (location.hash || ''),
+ }
}
let matcher: RouteMatcher | void
m => m.record.name === currentLocation.name
)
} else {
- matcher = this.matchers.find(
- m => m.record.path === currentLocation.path
- )
+ matcher = this.matchers.find(m => m.re.test(currentLocation.path))
}
// return '/using current location'
} else {
if (!matcher) {
// TODO: error
- throw new Error('No match for' + location)
+ throw new Error(
+ 'No match for' + JSON.stringify({ ...currentLocation, ...location })
+ )
}
- return matcher.resolve(location.params || {})
+ // TODO: try catch to show missing params
+ const fullPath = matcher.resolve(location.params || {})
+ return {
+ path: fullPath, // TODO: extract path path, query, hash
+ fullPath,
+ query: {},
+ params: {},
+ hash: '',
+ }
}
}
export interface RouterLocationNormalized {
path: string
+ fullPath: string
name?: string
params: RouteParams
query: TODO
- hash: TODO
+ hash: string
}
export type HistoryLocation = string
+export interface HistoryURL {
+ path: string
+ search: Record<string, string>
+ hash: string
+}
// pushState clones the state passed and do not accept everything
// it doesn't accept symbols, nor functions. It also ignores Symbols as keys
params: {},
query: {},
hash: '',
+ fullPath: '/',
}
export enum NavigationType {