From 5d802094923851102557bfb2583835cc135e16b8 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Tue, 24 Mar 2020 10:15:16 +0100 Subject: [PATCH] fix(history): use current history state when replacing --- src/components/Link.ts | 5 ++- src/history/common.ts | 4 +-- src/history/html5.ts | 76 +++++++++++++++++++----------------------- 3 files changed, 41 insertions(+), 44 deletions(-) diff --git a/src/components/Link.ts b/src/components/Link.ts index 4d2f82628..460d6d8ee 100644 --- a/src/components/Link.ts +++ b/src/components/Link.ts @@ -11,6 +11,7 @@ import { import { RouteLocation, RouteLocationNormalized, Immutable } from '../types' import { isSameLocationObject, isSameRouteRecord } from '../utils' import { routerKey } from '../utils/injectionSymbols' +import { RouteRecordNormalized } from '../matcher/types' type VueUseOptions = { [k in keyof T]: Ref | T[k] @@ -33,7 +34,9 @@ export function useLink(props: UseLinkOptions) { const activeRecordIndex = computed(() => { // TODO: handle children with empty path: they should relate to their parent - const currentMatched = route.value.matched[route.value.matched.length - 1] + const currentMatched: RouteRecordNormalized | undefined = + route.value.matched[route.value.matched.length - 1] + if (!currentMatched) return -1 return currentRoute.value.matched.findIndex( isSameRouteRecord.bind(null, currentMatched) ) diff --git a/src/history/common.ts b/src/history/common.ts index 2269e9b80..506e57328 100644 --- a/src/history/common.ts +++ b/src/history/common.ts @@ -73,8 +73,8 @@ export interface RouterHistory { readonly location: HistoryLocationNormalized // readonly location: ValueContainer - push(to: RawHistoryLocation): void - replace(to: RawHistoryLocation): void + push(to: RawHistoryLocation, data?: HistoryState): void + replace(to: RawHistoryLocation, data?: HistoryState): void back(triggerListeners?: boolean): void forward(triggerListeners?: boolean): void diff --git a/src/history/html5.ts b/src/history/html5.ts index 290e04383..c523c9987 100644 --- a/src/history/html5.ts +++ b/src/history/html5.ts @@ -11,8 +11,7 @@ import { ValueContainer, } from './common' import { computeScrollPosition, ScrollToPosition } from '../utils/scroll' - -const cs = console +import { warn } from 'vue' type PopStateListener = (this: Window, ev: PopStateEvent) => any @@ -49,7 +48,8 @@ function createCurrentLocation( function useHistoryListeners( base: string, historyState: ValueContainer, - location: ValueContainer + location: ValueContainer, + replace: RouterHistory['replace'] ) { let listeners: NavigationCallback[] = [] let teardowns: Array<() => void> = [] @@ -60,22 +60,19 @@ function useHistoryListeners( const popStateHandler: PopStateListener = ({ state, }: { - state: StateEntry + state: StateEntry | null }) => { - // TODO: state can be null when using links with a `hash` in hash mode - // maybe we should trigger a plain navigation in that case - cs.info('popstate fired', state) - cs.info('currentState', historyState) + const to = createCurrentLocation(base, window.location) + + if (!state) return replace(to.fullPath) const from: HistoryLocationNormalized = location.value const fromState: StateEntry = historyState.value - const to = createCurrentLocation(base, window.location) location.value = to historyState.value = state + // ignore the popstate and reset the pauseState if (pauseState && pauseState.fullPath === from.fullPath) { - cs.info('❌ Ignored because paused for', pauseState.fullPath) - // reset pauseState pauseState = null return } @@ -84,7 +81,7 @@ function useHistoryListeners( ? state.position - fromState.position : '' const distance = deltaFromCurrent || 0 - console.log({ deltaFromCurrent }) + // console.log({ deltaFromCurrent }) // Here we could also revert the navigation by calling history.go(-distance) // this listener will have to be adapted to not trigger again and to wait for the url // to be updated before triggering the listeners. Some kind of validation function would also @@ -104,7 +101,6 @@ function useHistoryListeners( } function pauseListeners() { - cs.info(`⏸ for ${location.value.fullPath}`) pauseState = location.value } @@ -151,29 +147,29 @@ function useHistoryListeners( } } +/** + * Creates a state object + */ +function buildState( + back: HistoryLocationNormalized | null, + current: HistoryLocationNormalized, + forward: HistoryLocationNormalized | null, + replaced: boolean = false, + computeScroll: boolean = false +): StateEntry { + return { + back, + current, + forward, + replaced, + position: window.history.length, + scroll: computeScroll ? computeScrollPosition() : null, + } +} + function useHistoryStateNavigation(base: string) { const { history } = window - /** - * Creates a state object - */ - function buildState( - back: HistoryLocationNormalized | null, - current: HistoryLocationNormalized, - forward: HistoryLocationNormalized | null, - replaced: boolean = false, - computeScroll: boolean = false - ): StateEntry { - return { - back, - current, - forward, - replaced, - position: window.history.length, - scroll: computeScroll ? computeScrollPosition() : null, - } - } - // private variables let location: ValueContainer = { value: createCurrentLocation(base, window.location), @@ -205,13 +201,10 @@ function useHistoryStateNavigation(base: string) { try { // BROWSER QUIRK // NOTE: Safari throws a SecurityError when calling this function 100 times in 30 seconds - const newState: StateEntry = replace - ? { ...historyState.value, ...state } - : state - history[replace ? 'replaceState' : 'pushState'](newState, '', url) + history[replace ? 'replaceState' : 'pushState'](state, '', url) historyState.value = state } catch (err) { - cs.log('[vue-router]: Error with push/replace State', err) + warn('[vue-router]: Error with push/replace State', err) // Force the navigation, this also resets the call count window.location[replace ? 'replace' : 'assign'](url) } @@ -219,7 +212,6 @@ function useHistoryStateNavigation(base: string) { function replace(to: RawHistoryLocation, data?: HistoryState) { const normalized = normalizeHistoryLocation(to) - // cs.info('replace', location, normalized) const state: StateEntry = { ...buildState( @@ -229,6 +221,7 @@ function useHistoryStateNavigation(base: string) { historyState.value.forward, true ), + ...history.state, ...data, } if (historyState) state.position = historyState.value.position @@ -244,7 +237,7 @@ function useHistoryStateNavigation(base: string) { // as well as saving the current position // TODO: the scroll position computation should be customizable const currentState: StateEntry = { - ...historyState.value, + ...history.state, forward: normalized, scroll: computeScrollPosition(), } @@ -274,7 +267,8 @@ export default function createWebHistory(base: string = ''): RouterHistory { const historyListeners = useHistoryListeners( base, historyNavigation.state, - historyNavigation.location + historyNavigation.location, + historyNavigation.replace ) function back(triggerListeners = true) { go(-1, triggerListeners)