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(b-link): add support 3rd party router links such as Gridsome's <g-link> (closes #2627) #5358

Merged
merged 45 commits into from May 14, 2020
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9b3ef62
feat(b-link): add support 3rd party router links such as Gridsome's `…
tmorehouse May 12, 2020
322fdc5
Update link.js
tmorehouse May 12, 2020
c4c3b0a
Update router.js
tmorehouse May 12, 2020
58aad60
Update link.js
tmorehouse May 12, 2020
e2e7153
Update router.js
jacobmllr95 May 12, 2020
cb4ac22
Update link.js
jacobmllr95 May 12, 2020
553049a
Merge branch 'dev' into blink-gridsome
tmorehouse May 12, 2020
1006ed2
Merge branch 'dev' into blink-gridsome
tmorehouse May 12, 2020
9f5c8a9
Update link.spec.js
tmorehouse May 13, 2020
0ff9889
Update link.spec.js
tmorehouse May 13, 2020
c531102
Update link.spec.js
tmorehouse May 13, 2020
43b9dd3
Update link.spec.js
tmorehouse May 13, 2020
50490af
Update router.js
tmorehouse May 13, 2020
9b35bc2
Update router.js
tmorehouse May 13, 2020
afa4358
Update link.spec.js
tmorehouse May 13, 2020
af4461d
Update link.spec.js
tmorehouse May 13, 2020
c9d8d4c
Update common-props.json
tmorehouse May 13, 2020
08a5a5a
Update link.js
tmorehouse May 13, 2020
ff65504
Update link.js
tmorehouse May 13, 2020
1fa2a58
Update common-props.json
tmorehouse May 13, 2020
98195ec
Update README.md
tmorehouse May 13, 2020
2aa3ef0
Update README.md
tmorehouse May 13, 2020
f42d245
Update avatar.js
tmorehouse May 13, 2020
510d408
Update common-props.json
tmorehouse May 13, 2020
b58dac9
Merge branch 'dev' into blink-gridsome
jacobmllr95 May 13, 2020
68f5d4c
Update README.md
tmorehouse May 13, 2020
c60e77a
Update README.md
tmorehouse May 13, 2020
0d85c33
Update README.md
tmorehouse May 13, 2020
793982e
Update common-props.json
tmorehouse May 13, 2020
19dc655
Update package.json
tmorehouse May 13, 2020
2bae70e
Update common-props.json
jacobmllr95 May 13, 2020
0c18823
Update package.json
jacobmllr95 May 13, 2020
60eb95e
Update README.md
jacobmllr95 May 13, 2020
eee6519
Update avatar.js
jacobmllr95 May 13, 2020
7998397
Update README.md
jacobmllr95 May 13, 2020
f892202
Merge remote-tracking branch 'origin/dev' into blink-gridsome
jacobmllr95 May 13, 2020
9f55cc7
Merge remote-tracking branch 'origin/dev' into blink-gridsome
jacobmllr95 May 13, 2020
fdd6a8a
Make sure to always omit `<b-link>`'s `event` prop for other components
jacobmllr95 May 13, 2020
a918a7c
Add `routerComponentName` to global config
jacobmllr95 May 13, 2020
505d638
Update pagination-nav.js
jacobmllr95 May 13, 2020
4816e2e
Update pagination-nav.js
jacobmllr95 May 13, 2020
421bbcd
Omit `routerTag` for all other components
jacobmllr95 May 13, 2020
2aa64e1
Unify link detection in other components
jacobmllr95 May 13, 2020
8952df3
Update common-props.json
tmorehouse May 14, 2020
c3f9077
Merge branch 'dev' into blink-gridsome
jacobmllr95 May 14, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 19 additions & 15 deletions docs/common-props.json
Expand Up @@ -197,44 +197,48 @@
"active": {
"description": "When set to 'true', places the component in the active state with active styling"
},
"href": {
"description": "<b-link> prop: Denotes the target URL of the link for standard a links"
},
"rel": {
"description": "Sets the 'rel' attribute on the rendered link"
"description": "<b-link> prop: Sets the 'rel' attribute on the rendered link"
},
"target": {
"description": "Sets the 'target' attribute on the rendered link"
},
"href": {
"description": "Denotes the target URL of the link for standard a links"
"description": "<b-link> prop: Sets the 'target' attribute on the rendered link"
},
"to": {
"description": "router-link prop: Denotes the target route of the link. When clicked, the value of the to prop will be passed to router.push() internally, so the value can be either a string or a Location descriptor object"
"description": "<router-link> prop: Denotes the target route of the link. When clicked, the value of the to prop will be passed to router.push() internally, so the value can be either a string or a Location descriptor object"
},
"replace": {
"description": "router-link prop: Setting the replace prop will call 'router.replace()' instead of 'router.push()' when clicked, so the navigation will not leave a history record"
"description": "<router-link> prop: Setting the replace prop will call 'router.replace()' instead of 'router.push()' when clicked, so the navigation will not leave a history record"
},
"append": {
"description": "router-link prop: Setting append prop always appends the relative path to the current path"
"description": "<router-link> prop: Setting append prop always appends the relative path to the current path"
},
"exact": {
"description": "router-link prop: The default active class matching behavior is inclusive match. Setting this prop forces the mode to exactly match the route"
"description": "<router-link> prop: The default active class matching behavior is inclusive match. Setting this prop forces the mode to exactly match the route"
},
"activeClass": {
"description": "router-link prop: Configure the active CSS class applied when the link is active. Typically you will want to set this to class name 'active'"
"description": "<router-link> prop: Configure the active CSS class applied when the link is active. Typically you will want to set this to class name 'active'"
},
"exactActiveClass": {
"description": "router-link prop: Configure the active CSS class applied when the link is active with exact match. Typically you will want to set this to class name 'active'"
"description": "<router-link> prop: Configure the active CSS class applied when the link is active with exact match. Typically you will want to set this to class name 'active'"
},
"routerTag": {
"description": "router-link prop: Specify which tag to render, and it will still listen to click events for navigation. 'router-tag' translates to the tag prop on the final rendered router-link. Typically you should use the default value"
"description": "<router-link> prop: Specify which tag to render, and it will still listen to click events for navigation. 'router-tag' translates to the tag prop on the final rendered router-link. Typically you should use the default value"
},
"event": {
"description": "router-link prop: Specify the event that triggers the link. In most cases you should leave this as the default"
"description": "<router-link> prop: Specify the event that triggers the link. In most cases you should leave this as the default"
},
"prefetch": {
"description": "nuxt-link prop: To improve the responsiveness of your Nuxt.js applications, when the link will be displayed within the viewport, Nuxt.js will automatically prefetch the code splitted page. Setting 'prefetch' to 'true' or 'false' will overwrite the default value of 'router.prefetchLinks'",
"description": "<nuxt-link> prop: To improve the responsiveness of your Nuxt.js applications, when the link will be displayed within the viewport, Nuxt.js will automatically prefetch the code splitted page. Setting 'prefetch' to 'true' or 'false' will overwrite the default value of 'router.prefetchLinks'",
"version": "2.15.0"
},
"noPrefetch": {
"description": "nuxt-link prop: To improve the responsiveness of your Nuxt.js applications, when the link will be displayed within the viewport, Nuxt.js will automatically prefetch the code splitted page. Setting 'no-prefetch' will disabled this feature for the specific link"
"description": "<nuxt-link> prop: To improve the responsiveness of your Nuxt.js applications, when the link will be displayed within the viewport, Nuxt.js will automatically prefetch the code splitted page. Setting 'no-prefetch' will disabled this feature for the specific link"
},
"routerComponentName": {
"description": "<b-link> prop: BootstrapVue auto detects between `<router-link>` and `<nuxt-link>`. In cases where you want to use a 3rd party link component based on `<router-link>`, set this prop to the component name. e.g. set it to 'g-link' if you are using Gridsome (note only `<router-link>` specific props are passed to the component)",
"version": "2.15.0"
}
}
32 changes: 31 additions & 1 deletion docs/markdown/reference/router-links/README.md
Expand Up @@ -10,7 +10,8 @@
In the following sections, we are using the `<b-link>` component to render router links. `<b-link>`
is the building block of most of BootstrapVue's _actionable_ components. You could use any other
component that supports link generation such as [`<b-link>`](/docs/components/link),
[`<b-button>`](/docs/components/button), [`<b-breadcrumb-item>`](/docs/components/breadcrumb),
[`<b-button>`](/docs/components/button), [`<b-avatar>`](/docs/components/avatar),
[`<b-breadcrumb-item>`](/docs/components/breadcrumb),
[`<b-list-group-item>`](/docs/components/list-group), [`<b-nav-item>`](/docs/components/nav),
[`<b-dropdown-item>`](/docs/components/dropdown), and
[`<b-pagination-nav>`](/docs/components/pagination-nav). Note that not all props are available on
Expand Down Expand Up @@ -203,3 +204,32 @@ disabled this feature for the specific link.
**Note:** If you have prefetching disabled in your `nuxt.config.js` configuration
(`router: { prefetchLinks: false }`), or are using a version of Nuxt.js `< 2.4.0`, then this prop
will have no effect.

## Third-party router link support

<span class="badge badge-info small">v2.15.0+</span>

BootstrapVue auto detects using `<router-link>` and `<nuxt-link>` link components. Some 3rd party
frameworks also provide customized versions of `<router-link>`, such as
[Gridsome's `<g-link>` component](https://gridsome.org/docs/linking/). BootstrapVue can support
these third party `<router-link>` compatible components via the use of the `router-component-name`
prop. All `vue-router` props (excluding `<nuxt-link>` specific props) will be passed to the
specified router link component.

**Notes:**

- The 3rd party component will only be used when the `to` prop is set.
- Not all 3rd party components support all props supported by `<router-link>`, nor do not support
fully qualified domain name URLs, nor hash only URLs. Refer to the 3rd party component
documentation for details.

### `router-component-name`

- type: `string`
- default: `undefined`
- availability: BootstrapVue 2.15.0+

Set this prop to the name of the `<router-link>` compatible component, e.g. `'g-link'` for
[Gridsome](https://gridsome.org/).

If left at the default, BootstrapVue will automatically select `<router-link>` or `<nuxt-link>`.
19 changes: 2 additions & 17 deletions src/components/avatar/avatar.js
Expand Up @@ -3,6 +3,7 @@ import pluckProps from '../../utils/pluck-props'
import { getComponentConfig } from '../../utils/config'
import { isNumber, isString, isUndefinedOrNull } from '../../utils/inspect'
import { toFloat } from '../../utils/number'
import { omit } from '../../utils/object'
import { BButton } from '../button/button'
import { BLink, props as BLinkProps } from '../link/link'
import { BIcon } from '../../icons/icon'
Expand All @@ -25,23 +26,7 @@ const DEFAULT_SIZES = {
}

// --- Props ---
const linkProps = pluckProps(
[
'href',
'rel',
'target',
'disabled',
'to',
'append',
'replace',
'activeClass',
'exact',
'exactActiveClass',
'prefetch',
'noPrefetch'
],
BLinkProps
)
const linkProps = omit(BLinkProps, ['active', 'event', 'routerTag'])
jacobmllr95 marked this conversation as resolved.
Show resolved Hide resolved

const props = {
src: {
Expand Down
4 changes: 2 additions & 2 deletions src/components/badge/badge.js
Expand Up @@ -2,12 +2,12 @@ import Vue from '../../utils/vue'
import pluckProps from '../../utils/pluck-props'
import { mergeData } from 'vue-functional-data-merge'
import { getComponentConfig } from '../../utils/config'
import { clone } from '../../utils/object'
import { omit } from '../../utils/object'
import { BLink, props as BLinkProps } from '../link/link'

const NAME = 'BBadge'

const linkProps = clone(BLinkProps)
const linkProps = omit(BLinkProps, ['event'])
delete linkProps.href.default
delete linkProps.to.default

Expand Down
5 changes: 3 additions & 2 deletions src/components/breadcrumb/breadcrumb-link.js
@@ -1,7 +1,8 @@
import Vue from '../../utils/vue'
import { mergeData } from 'vue-functional-data-merge'
import Vue from '../../utils/vue'
import pluckProps from '../../utils/pluck-props'
import { htmlOrText } from '../../utils/html'
import { omit } from '../../utils/object'
import { BLink, props as BLinkProps } from '../link/link'

export const props = {
Expand All @@ -17,7 +18,7 @@ export const props = {
type: String,
default: 'location'
},
...BLinkProps
...omit(BLinkProps, ['event'])
}

// @vue/component
Expand Down
4 changes: 2 additions & 2 deletions src/components/button/button.js
Expand Up @@ -6,7 +6,7 @@ import { concat } from '../../utils/array'
import { getComponentConfig } from '../../utils/config'
import { addClass, removeClass } from '../../utils/dom'
import { isBoolean, isEvent, isFunction } from '../../utils/inspect'
import { clone } from '../../utils/object'
import { omit } from '../../utils/object'
import { toString } from '../../utils/string'
import { BLink, props as BLinkProps } from '../link/link'

Expand All @@ -16,7 +16,7 @@ const NAME = 'BButton'

// --- Props ---

const linkProps = clone(BLinkProps)
const linkProps = omit(BLinkProps, ['event'])
delete linkProps.href.default
delete linkProps.to.default

Expand Down
4 changes: 2 additions & 2 deletions src/components/dropdown/dropdown-item.js
@@ -1,11 +1,11 @@
import Vue from '../../utils/vue'
import { requestAF } from '../../utils/dom'
import { clone } from '../../utils/object'
import { omit } from '../../utils/object'
import attrsMixin from '../../mixins/attrs'
import normalizeSlotMixin from '../../mixins/normalize-slot'
import { BLink, props as BLinkProps } from '../link/link'

export const props = clone(BLinkProps)
export const props = omit(BLinkProps, ['event'])

// @vue/component
export const BDropdownItem = /*#__PURE__*/ Vue.extend({
Expand Down
2 changes: 1 addition & 1 deletion src/components/dropdown/package.json
Expand Up @@ -97,7 +97,7 @@
},
{
"prop": "splitTo",
"description": "router-link prop: Denotes the target route of the split button. When clicked, the value of the to prop will be passed to router.push() internally, so the value can be either a string or a Location descriptor object"
"description": "<router-link> prop: Denotes the target route of the split button. When clicked, the value of the to prop will be passed to router.push() internally, so the value can be either a string or a Location descriptor object"
},
{
"prop": "splitVariant",
Expand Down
11 changes: 11 additions & 0 deletions src/components/link/README.md
Expand Up @@ -26,6 +26,17 @@ If your app is running under [Nuxt.js](https://nuxtjs.org), the
`<router-link>`. The `<nuxt-link>` component supports all the same features as `<router-link>` (as
it is a wrapper component for `<router-link>`) and more.

### Third party rounter links

BootstrapVue auto detects using `<router-link>` and `<nuxt-link>` link components. Some 3rd party
frameworks also provide customized versions of `<router-link>`, such as
[Gridsome's `<g-link>` component](https://gridsome.org/docs/linking/). `<b-link>` can support these
third party `<router-link>` compatible components via the use of the `router-component-name` prop.
All `vue-router` props (excluding `<nuxt-link>` specific props) will be passed to the specified
router link component.

Note that the 3rd party component will only be used when the `to` prop is set.

## Links with `href="#"`

Typically `<a href="#">` will cause the document to scroll to the top of page when clicked.
Expand Down
18 changes: 16 additions & 2 deletions src/components/link/link.js
@@ -1,13 +1,18 @@
import Vue from '../../utils/vue'
import pluckProps from '../../utils/pluck-props'
import { concat } from '../../utils/array'
import { getComponentConfig } from '../../utils/config'
import { attemptBlur, attemptFocus } from '../../utils/dom'
import { isBoolean, isEvent, isFunction, isUndefined } from '../../utils/inspect'
import { computeHref, computeRel, computeTag, isRouterLink } from '../../utils/router'
import attrsMixin from '../../mixins/attrs'
import listenersMixin from '../../mixins/listeners'
import normalizeSlotMixin from '../../mixins/normalize-slot'

// --- Constants ---

const NAME = 'BLink'

// --- Props ---

// <router-link> specific props
Expand Down Expand Up @@ -87,7 +92,15 @@ export const props = {
default: false
},
...routerLinkProps,
...nuxtLinkProps
...nuxtLinkProps,
// To support 3rd party router links based on `<router-link>` (i.e. `g-link` for Gridsome)
// Default is to auto choose between `<router-link>` and `<nuxt-link>`
// Gridsome doesn't provide a mechanism to auto detect and has caveats
// such as not supporting FQDN URLs or hash only URLs
routerComponentName: {
type: String,
default: () => getComponentConfig(NAME, 'routerComponentName')
}
}

// --- Main component ---
Expand All @@ -101,7 +114,8 @@ export const BLink = /*#__PURE__*/ Vue.extend({
computed: {
computedTag() {
// We don't pass `this` as the first arg as we need reactivity of the props
return computeTag({ to: this.to, disabled: this.disabled }, this)
const { to, disabled, routerComponentName } = this
return computeTag({ to, disabled, routerComponentName }, this)
},
isRouterLink() {
return isRouterLink(this.computedTag)
Expand Down
27 changes: 26 additions & 1 deletion src/components/link/link.spec.js
Expand Up @@ -326,6 +326,24 @@ describe('b-link', () => {
]
})

// Fake Gridsome `<g-link>` component
const GLink = {
name: 'GLink',
props: {
to: {
type: [String, Object],
default: ''
}
},
render(h) {
// We just us a simple A tag to render the
// fake `<g-link>` and assume `to` is a string
return h('a', { attrs: { href: this.to } }, [this.$slots.default])
}
}

localVue.component('GLink', GLink)

const App = {
router,
components: { BLink },
Expand All @@ -339,6 +357,8 @@ describe('b-link', () => {
h('b-link', { props: { to: { path: '/b' } } }, ['to-path-b']),
// regular link
h('b-link', { props: { href: '/b' } }, ['href-a']),
// g-link
h('b-link', { props: { routerComponentName: 'g-link', to: '/a' } }, ['g-link-a']),
h('router-view')
])
}
Expand All @@ -352,7 +372,7 @@ describe('b-link', () => {
expect(wrapper.vm).toBeDefined()
expect(wrapper.element.tagName).toBe('MAIN')

expect(wrapper.findAll('a').length).toBe(4)
expect(wrapper.findAll('a').length).toBe(5)

const $links = wrapper.findAll('a')

Expand All @@ -374,6 +394,11 @@ describe('b-link', () => {
expect($links.at(3).vm.$options.name).toBe('BLink')
expect($links.at(3).vm.$children.length).toBe(0)

expect($links.at(4).vm).toBeDefined()
expect($links.at(4).vm.$options.name).toBe('BLink')
expect($links.at(4).vm.$children.length).toBe(1)
expect($links.at(4).vm.$children[0].$options.name).toBe('GLink')

wrapper.destroy()
})
})
Expand Down
15 changes: 14 additions & 1 deletion src/components/link/package.json
Expand Up @@ -7,7 +7,20 @@
"components": [
{
"component": "BLink",
"props": [],
"props": [
{
"prop": "href",
"description": "Denotes the target URL of the link for standard a links"
},
{
"prop": "rel",
"description": "Sets the 'rel' attribute on the rendered link"
},
{
"prop": "target",
"description": "Sets the 'target' attribute on the rendered link"
}
],
"events": [
{
"event": "click",
Expand Down
4 changes: 2 additions & 2 deletions src/components/list-group/list-group-item.js
Expand Up @@ -3,7 +3,7 @@ import Vue from '../../utils/vue'
import pluckProps from '../../utils/pluck-props'
import { arrayIncludes } from '../../utils/array'
import { getComponentConfig } from '../../utils/config'
import { clone } from '../../utils/object'
import { omit } from '../../utils/object'
import { BLink, props as BLinkProps } from '../link/link'

// --- Constants ---
Expand All @@ -14,7 +14,7 @@ const actionTags = ['a', 'router-link', 'button', 'b-link']

// --- Props ---

const linkProps = clone(BLinkProps)
const linkProps = omit(BLinkProps, ['event'])
delete linkProps.href.default
delete linkProps.to.default

Expand Down
4 changes: 2 additions & 2 deletions src/components/nav/nav-item.js
@@ -1,11 +1,11 @@
import { mergeData } from 'vue-functional-data-merge'
import Vue from '../../utils/vue'
import { clone } from '../../utils/object'
import { omit } from '../../utils/object'
import { BLink, props as BLinkProps } from '../link/link'

// --- Props ---

export const props = clone(BLinkProps)
export const props = omit(BLinkProps, ['event'])

// --- Main component ---
// @vue/component
Expand Down