Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(scroll) - WIP - allow a different scroll element other than window #2780

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion flow/declarations.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ declare type NavigationGuard = (
declare type AfterNavigationHook = (to: Route, from: Route) => any

type Position = { x: number, y: number };
type PositionResult = Position | { selector: string, offset?: Position } | void;
type PositionResult = Position | { selector: string, offset?: Position, scrollElement?: string } | void;

declare type RouterOptions = {
routes?: Array<RouteConfig>;
Expand All @@ -39,6 +39,7 @@ declare type RouterOptions = {
linkExactActiveClass?: string;
parseQuery?: (query: string) => Object;
stringifyQuery?: (query: Object) => string;
scrollElement?: string;
scrollBehavior?: (
to: Route,
from: Route,
Expand Down
4 changes: 4 additions & 0 deletions src/history/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ export class History {
})
}

get scrollElementSelector (): ?string {
return this.router.options.scrollElement
}

confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {
const current = this.current
const abort = err => {
Expand Down
18 changes: 9 additions & 9 deletions src/history/hash.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class HashHistory extends History {
const supportsScroll = supportsPushState && expectScroll

if (supportsScroll) {
setupScroll()
setupScroll(router.options.scrollElement)
}

window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', () => {
Expand All @@ -38,7 +38,7 @@ export class HashHistory extends History {
handleScroll(this.router, route, current, true)
}
if (!supportsPushState) {
replaceHash(route.fullPath)
replaceHash(route.fullPath, this.scrollElementSelector)
}
})
})
Expand All @@ -47,7 +47,7 @@ export class HashHistory extends History {
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
pushHash(route.fullPath)
pushHash(route.fullPath, this.scrollElementSelector)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
Expand All @@ -56,7 +56,7 @@ export class HashHistory extends History {
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
replaceHash(route.fullPath)
replaceHash(route.fullPath, this.scrollElementSelector)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
Expand All @@ -69,7 +69,7 @@ export class HashHistory extends History {
ensureURL (push?: boolean) {
const current = this.current.fullPath
if (getHash() !== current) {
push ? pushHash(current) : replaceHash(current)
push ? pushHash(current, this.scrollElementSelector) : replaceHash(current, this.scrollElementSelector)
}
}

Expand Down Expand Up @@ -128,17 +128,17 @@ function getUrl (path) {
return `${base}#${path}`
}

function pushHash (path) {
function pushHash (path, scrollElementSelector?: string) {
if (supportsPushState) {
pushState(getUrl(path))
pushState(getUrl(path,), scrollElementSelector)
} else {
window.location.hash = path
}
}

function replaceHash (path) {
function replaceHash (path, scrollElementSelector?: string) {
if (supportsPushState) {
replaceState(getUrl(path))
replaceState(getUrl(path), scrollElementSelector)
} else {
window.location.replace(getUrl(path))
}
Expand Down
6 changes: 3 additions & 3 deletions src/history/html5.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class HTML5History extends History {
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
pushState(cleanPath(this.base + route.fullPath))
pushState(cleanPath(this.base + route.fullPath), this.scrollElementSelector)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
Expand All @@ -53,7 +53,7 @@ export class HTML5History extends History {
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
replaceState(cleanPath(this.base + route.fullPath))
replaceState(cleanPath(this.base + route.fullPath), this.scrollElementSelector)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
Expand All @@ -62,7 +62,7 @@ export class HTML5History extends History {
ensureURL (push?: boolean) {
if (getLocation(this.base) !== this.current.fullPath) {
const current = cleanPath(this.base + this.current.fullPath)
push ? pushState(current) : replaceState(current)
push ? pushState(current, this.scrollElementSelector) : replaceState(current, this.scrollElementSelector)
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/util/push-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ export function setStateKey (key: string) {
_key = key
}

export function pushState (url?: string, replace?: boolean) {
saveScrollPosition()
export function pushState (url?: string, scrollElementSelector?: ?string, replace?: boolean) {
saveScrollPosition(scrollElementSelector)
// try...catch the pushState call to get around Safari
// DOM Exception 18 where it limits to 100 pushState calls
const history = window.history
Expand All @@ -54,6 +54,6 @@ export function pushState (url?: string, replace?: boolean) {
}
}

export function replaceState (url?: string) {
pushState(url, true)
export function replaceState (url?: string, scrollElementSelector?: ?string) {
pushState(url, scrollElementSelector, true)
}
64 changes: 52 additions & 12 deletions src/util/scroll.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { getStateKey, setStateKey } from './push-state'

const positionStore = Object.create(null)

export function setupScroll () {
export function setupScroll (scrollElement?: ?string) {
// Fix for #1585 for Firefox
// Fix for #2195 Add optional third attribute to workaround a bug in safari https://bugs.webkit.org/show_bug.cgi?id=182678
window.history.replaceState({ key: getStateKey() }, '', window.location.href.replace(window.location.origin, ''))
window.addEventListener('popstate', e => {
saveScrollPosition()
saveScrollPosition(scrollElement)
if (e.state && e.state.key) {
setStateKey(e.state.key)
}
Expand Down Expand Up @@ -48,25 +48,59 @@ export function handleScroll (

if (typeof shouldScroll.then === 'function') {
shouldScroll.then(shouldScroll => {
scrollToPosition((shouldScroll: any), position)
if (!shouldScroll) {
return
}
const scrollElementSelector = router.options.scrollElement || shouldScroll.scrollElement || null
scrollToPosition((shouldScroll: any), position, scrollElementSelector)
}).catch(err => {
if (process.env.NODE_ENV !== 'production') {
assert(false, err.toString())
}
})
} else {
scrollToPosition(shouldScroll, position)
const scrollElementSelector = router.options.scrollElement || shouldScroll.scrollElement || null
scrollToPosition(shouldScroll, position, scrollElementSelector)
}
})
}

export function saveScrollPosition () {
const key = getStateKey()
if (key) {
positionStore[key] = {
x: window.pageXOffset,
y: window.pageYOffset
function getScrollElement (scrollElementSelector?: any): Element | WindowProxy {
let domScrollElement = window
if (!scrollElementSelector) {
return window
}

assert(typeof scrollElementSelector === 'string' || scrollElementSelector instanceof Element, 'Scroll Element must be a css selector string or a DOM element')
if (typeof scrollElementSelector === 'string') {
const customScrollElement = document.querySelector(scrollElementSelector)
if (customScrollElement) {
domScrollElement = customScrollElement
}
} else if (scrollElementSelector instanceof Element) {
return scrollElementSelector
}

return domScrollElement
}

export function saveScrollPosition (scrollElement?: string | Element | null) {
const key = getStateKey()
if (!key) {
return
}

const domScrollElement = getScrollElement(scrollElement)
let propX = 'pageXOffset'
let propY = 'pageYOffset'
if (domScrollElement instanceof Element) {
propX = 'scrollLeft'
propY = 'scrollTop'
}

positionStore[key] = {
x: domScrollElement[propX],
y: domScrollElement[propY]
}
}

Expand Down Expand Up @@ -109,7 +143,7 @@ function isNumber (v: any): boolean {
return typeof v === 'number'
}

function scrollToPosition (shouldScroll, position) {
function scrollToPosition (shouldScroll, position, scrollElementSelector?) {
const isObject = typeof shouldScroll === 'object'
if (isObject && typeof shouldScroll.selector === 'string') {
const el = document.querySelector(shouldScroll.selector)
Expand All @@ -125,6 +159,12 @@ function scrollToPosition (shouldScroll, position) {
}

if (position) {
window.scrollTo(position.x, position.y)
const scrollElement = getScrollElement(scrollElementSelector)
if (typeof scrollElement.scrollTo === 'function') {
scrollElement.scrollTo(position.x, position.y)
} else {
scrollElement.scrollTop = position.y
scrollElement.scrollLeft = position.x
}
}
}