diff --git a/docs/api/README.md b/docs/api/README.md index e5a593fb0..30f99bc34 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -225,6 +225,16 @@ Since it's just a component, it works with `` and ``. Wh Globally configure `` default active class for exact matches. Also see [router-link](#router-link). +### linkExactPathActiveClass + +> X.Y.Z+ + +- type: `string` + +- default: `"router-link-exact-path-active"` + + Globally configure `` default active class for exact path matches. Also see [router-link](router-link.md). + ### scrollBehavior - type: `Function` diff --git a/examples/active-links/app.js b/examples/active-links/app.js index a1a3238d4..722bd723a 100644 --- a/examples/active-links/app.js +++ b/examples/active-links/app.js @@ -66,6 +66,8 @@ new Vue({ /about (active class on outer element) + +
  • /?foo=bar (exact path match)
  • diff --git a/src/components/link.js b/src/components/link.js index 2662d8263..99eb28923 100644 --- a/src/components/link.js +++ b/src/components/link.js @@ -19,10 +19,12 @@ export default { default: 'a' }, exact: Boolean, + exactPath: Boolean, append: Boolean, replace: Boolean, activeClass: String, exactActiveClass: String, + exactPathActiveClass: String, event: { type: eventTypes, default: 'click' @@ -36,6 +38,7 @@ export default { const classes = {} const globalActiveClass = router.options.linkActiveClass const globalExactActiveClass = router.options.linkExactActiveClass + const globalExactPathActiveClass = router.options.linkExactPathActiveClass // Support global empty active class const activeClassFallback = globalActiveClass == null ? 'router-link-active' @@ -43,12 +46,18 @@ export default { const exactActiveClassFallback = globalExactActiveClass == null ? 'router-link-exact-active' : globalExactActiveClass + const exactPathActiveClassFallback = globalExactPathActiveClass == null + ? 'router-link-exact-path-active' + : globalExactPathActiveClass const activeClass = this.activeClass == null ? activeClassFallback : this.activeClass const exactActiveClass = this.exactActiveClass == null ? exactActiveClassFallback : this.exactActiveClass + const exactPathActiveClass = this.exactPathActiveClass == null + ? exactPathActiveClassFallback + : this.exactPathActiveClass const compareTarget = location.path ? createRoute(null, location, null, router) : route @@ -58,6 +67,10 @@ export default { ? classes[exactActiveClass] : isIncludedRoute(current, compareTarget) + if (this.exactPath) { + classes[exactPathActiveClass] = isSameRoute(current, compareTarget, true) + } + const handler = e => { if (guardEvent(e)) { if (this.replace) { diff --git a/src/util/route.js b/src/util/route.js index 54a91a738..26e3eb5c0 100644 --- a/src/util/route.js +++ b/src/util/route.js @@ -70,7 +70,7 @@ function getFullPath ( return (path || '/') + stringify(query) + hash } -export function isSameRoute (a: Route, b: ?Route): boolean { +export function isSameRoute (a: Route, b: ?Route, ignoringQuery: ?boolean): boolean { if (b === START) { return a === b } else if (!b) { @@ -79,13 +79,13 @@ export function isSameRoute (a: Route, b: ?Route): boolean { return ( a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') && a.hash === b.hash && - isObjectEqual(a.query, b.query) + (ignoringQuery || (!ignoringQuery && isObjectEqual(a.query, b.query))) ) } else if (a.name && b.name) { return ( a.name === b.name && a.hash === b.hash && - isObjectEqual(a.query, b.query) && + (ignoringQuery || (!ignoringQuery && isObjectEqual(a.query, b.query))) && isObjectEqual(a.params, b.params) ) } else { diff --git a/test/e2e/specs/active-links.js b/test/e2e/specs/active-links.js index 164978762..4df5f41d3 100644 --- a/test/e2e/specs/active-links.js +++ b/test/e2e/specs/active-links.js @@ -4,7 +4,7 @@ module.exports = { browser .url('http://localhost:8080/active-links/') .waitForElementVisible('#app', 1000) - .assert.count('li a', 11) + .assert.count('li a', 12) // assert correct href with base .assert.attributeContains('li:nth-child(1) a', 'href', '/active-links/') .assert.attributeContains('li:nth-child(2) a', 'href', '/active-links/') @@ -17,6 +17,7 @@ module.exports = { .assert.attributeContains('li:nth-child(9) a', 'href', '/active-links/users/evan?foo=bar&baz=qux') .assert.attributeContains('li:nth-child(10) a', 'href', '/active-links/about') .assert.attributeContains('li:nth-child(11) a', 'href', '/active-links/about') + .assert.attributeContains('li:nth-child(12) a', 'href', '/active-links/?foo=bar') .assert.containsText('.view', 'Home') assertActiveLinks(1, [1, 2], null, [1, 2]) @@ -30,10 +31,11 @@ module.exports = { assertActiveLinks(9, [1, 3, 5, 7, 9], null, [9]) assertActiveLinks(10, [1, 10], [11], [10], [11]) assertActiveLinks(11, [1, 10], [11], [10], [11]) + assertActiveLinks(12, [], [], [], [], [12]) browser.end() - function assertActiveLinks (n, activeA, activeLI, exactActiveA, exactActiveLI) { + function assertActiveLinks (n, activeA, activeLI, exactActiveA, exactActiveLI, exactPathActiveA) { browser.click(`li:nth-child(${n}) a`) activeA.forEach(i => { browser.assert.cssClassPresent(`li:nth-child(${i}) a`, 'router-link-active') @@ -49,6 +51,10 @@ module.exports = { browser.assert.cssClassPresent(`li:nth-child(${i})`, 'router-link-exact-active') .assert.cssClassPresent(`li:nth-child(${i})`, 'router-link-active') }) + exactPathActiveA && exactPathActiveA.forEach(i => { + browser.assert.cssClassPresent(`li:nth-child(${i}) a`, 'router-link-exact-path-active') + .assert.cssClassPresent(`li:nth-child(${i}) a`, 'router-link-active') + }) } } } diff --git a/test/unit/specs/route.spec.js b/test/unit/specs/route.spec.js index 8e8118537..556d24e71 100644 --- a/test/unit/specs/route.spec.js +++ b/test/unit/specs/route.spec.js @@ -66,6 +66,34 @@ describe('Route utils', () => { expect(isSameRoute(a, b)).toBe(true) expect(isSameRoute(a, c)).toBe(false) }) + + it('can ignore query', () => { + const a = { + path: '/abc', + query: {} + } + const b = { + path: '/abc', + query: { foo: 'bar' } + } + const c = { + path: '/abc', + query: { foo: 'baz' } + } + const d = { + path: '/xyz', + query: { foo: 'bar' } + } + expect(isSameRoute(a, b)).toBe(false) + expect(isSameRoute(a, c)).toBe(false) + expect(isSameRoute(a, d)).toBe(false) + expect(isSameRoute(a, b, true)).toBe(true) + expect(isSameRoute(a, c, true)).toBe(true) + expect(isSameRoute(a, d, true)).toBe(false) + expect(isSameRoute(b, c)).toBe(false) + expect(isSameRoute(b, c, true)).toBe(true) + expect(isSameRoute(b, d, true)).toBe(false) + }) }) describe('isIncludedRoute', () => {