Skip to content

Commit 5dcd1e0

Browse files
feat: add navigation support
1 parent 86cfce9 commit 5dcd1e0

File tree

4 files changed

+101
-8
lines changed

4 files changed

+101
-8
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
- Just **one tiny file**
1717
- Component based (great for **async loading** and code splitting)
18+
- Supports navigation through *VueRouter*
1819
- Universal code/SSR-safe
1920
- Well tested and **documented**
2021
- Compatible with Node 8.0+
@@ -91,6 +92,8 @@ better suited for universal/SSR code and can be loaded asynchronously as well!
9192
|---| --- | --- |
9293
| target | :white_check_mark: | Can be any query selector you want (or a function that returns such). Will be passed to the scroll function |
9394
| scrollFunction | :white_check_mark: | You can define an own scroll function that will take the `target` prop as parameter and can do whatever you like. |
95+
| shouldNavigate | :white_check_mark: | If set, VueRouter will reflect navigation changes in the url(top: no hash, target: hash) |
96+
| navigationType | :white_check_mark: | Default to `push`. The navigation type of that VueRouter should use. Usually either `push` or `replace` |
9497

9598

9699
### Default scroll function explained

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"standard-version": "latest",
9494
"vue-jest": "^2.6.0",
9595
"vue-loader": "^15.4.2",
96+
"vue-router": "^3.0.1",
9697
"vue-server-renderer": "^2.5.17-beta.0",
9798
"vue-template-compiler": "^2.5.17-beta.0"
9899
},

src/index.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
export default {
22
functional: true,
3-
render(h, { children, data, props: { target, scrollFunction }, _ssrNode }) {
4-
const clickFunction = () => { scrollFunction ? scrollFunction(target) : defaultScrollFunction(target) }
3+
render(h, { children, data, props: { target, scrollFunction, shouldNavigate = false, navigationType = 'push' }, parent: { $router }, _ssrNode }) {
4+
const clickFunction = () => {
5+
const fn = scrollFunction || defaultScrollFunction
6+
fn(target, { shouldNavigate, navigationType, $router })
7+
}
58

69
return h('div', {
710
...data,
@@ -13,15 +16,22 @@ export default {
1316
}
1417
}
1518

16-
const defaultScrollFunction = async (rawTarget) => {
19+
const defaultScrollFunction = async (rawTarget, { shouldNavigate, navigationType, $router }) => {
1720
const target = (typeof rawTarget === 'function') ? await rawTarget() : rawTarget
18-
1921
// If no target given, auto scroll to top
2022
if (!target) {
21-
return window.scroll({
23+
window.scroll({
2224
top: 0,
2325
behavior: 'smooth'
2426
})
27+
if (shouldNavigate && $router) {
28+
const currentRoute = $router.currentRoute
29+
const hash = currentRoute.hash
30+
const fullPath = currentRoute.fullPath
31+
const newPath = fullPath.replace(hash, '')
32+
navigate($router, newPath, navigationType)
33+
}
34+
return
2535
}
2636

2737
const node = document.querySelector(target)
@@ -35,4 +45,10 @@ const defaultScrollFunction = async (rawTarget) => {
3545
node.scrollIntoView({
3646
behavior: 'smooth'
3747
})
48+
49+
if (shouldNavigate && $router) {
50+
navigate($router, target, navigationType)
51+
}
3852
}
53+
54+
const navigate = ($router, path, type) => { $router[type](path) }

test/ScrollNextLevel.spec.js

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/* eslint-disable no-console */
2-
import { mount } from '@vue/test-utils'
2+
import { createLocalVue, mount } from '@vue/test-utils'
33
import { render } from '@vue/server-test-utils'
44
import flushPromises from 'flush-promises'
5+
import VueRouter from 'vue-router'
56
import ScrollNextLevel from '../src'
67
import Target from './fixtures/Target'
78

@@ -45,6 +46,36 @@ describe('ScrollNextLevel', () => {
4546
})
4647
})
4748

49+
it('does navigate to path without hash when enabled and scroll to top', () => {
50+
spy = jest.spyOn(global, 'scroll')
51+
const localVue = createLocalVue()
52+
localVue.use(VueRouter)
53+
const router = new VueRouter()
54+
55+
const wrapper = mount(ScrollNextLevel, {
56+
attachToDocument: true,
57+
context: {
58+
props: {
59+
shouldNavigate: true
60+
}
61+
},
62+
localVue,
63+
router
64+
})
65+
66+
router.replace('#test')
67+
68+
expect(spy).toHaveBeenCalledTimes(0)
69+
70+
wrapper.trigger('click')
71+
72+
expect(spy).toBeCalledWith({
73+
top: 0,
74+
behavior: 'smooth'
75+
})
76+
expect(router.currentRoute.fullPath).toBe('/')
77+
})
78+
4879
it('does smooth scroll to target when present', () => {
4980
spy = jest.spyOn(Element.prototype, 'scrollIntoView')
5081

@@ -75,6 +106,44 @@ describe('ScrollNextLevel', () => {
75106
})
76107
})
77108

109+
it('does navigate to target after smooth-scrolling', () => {
110+
spy = jest.spyOn(Element.prototype, 'scrollIntoView')
111+
const localVue = createLocalVue()
112+
localVue.use(VueRouter)
113+
const router = new VueRouter()
114+
115+
const wrapper = mount(ScrollNextLevel, {
116+
attachToDocument: true,
117+
context: {
118+
props: {
119+
target: targetString,
120+
shouldNavigate: true
121+
}
122+
},
123+
slots: {
124+
default: Target
125+
},
126+
localVue,
127+
router
128+
})
129+
130+
expect(wrapper.html()).toBe('<div><div id="target">Hi</div></div>')
131+
132+
expect(wrapper.contains(targetString)).toBe(true)
133+
134+
const element = wrapper.find(targetString).element
135+
136+
expect(element.scrollIntoView).toHaveBeenCalledTimes(0)
137+
138+
wrapper.trigger('click')
139+
140+
expect(element.scrollIntoView).toBeCalledWith({
141+
behavior: 'smooth'
142+
})
143+
144+
expect(router.currentRoute.fullPath).toBe(`/${targetString}`)
145+
})
146+
78147
it('does send error if target is set but not present', () => {
79148
const weirdTarget = 'weird things here'
80149
spy = jest.spyOn(console, 'error')
@@ -161,7 +230,7 @@ describe('ScrollNextLevel', () => {
161230
})
162231
wrapper.trigger('click')
163232

164-
expect(spy).toBeCalledWith(undefined)
233+
expect(spy).toBeCalledWith(undefined, { $parent: undefined, navigationType: 'push', shouldNavigate: false })
165234
})
166235

167236
it('calls custom function with target on click', () => {
@@ -177,7 +246,11 @@ describe('ScrollNextLevel', () => {
177246
})
178247
wrapper.trigger('click')
179248

180-
expect(spy).toBeCalledWith(targetString)
249+
expect(spy).toBeCalledWith(targetString, {
250+
$parent: undefined,
251+
navigationType: 'push',
252+
shouldNavigate: false
253+
})
181254
})
182255
})
183256

0 commit comments

Comments
 (0)