bootstrap-vue
Version:
With more than 85 components, over 45 available plugins, several directives, and 1000+ icons, BootstrapVue provides one of the most comprehensive implementations of the Bootstrap v4 component and grid system available for Vue.js v2.6, complete with extens
231 lines (223 loc) • 6.86 kB
JavaScript
import Vue from '../../vue'
import { NAME_LINK } from '../../constants/components'
import { concat } from '../../utils/array'
import { getComponentConfig } from '../../utils/config'
import { attemptBlur, attemptFocus, isTag } from '../../utils/dom'
import { stopEvent } from '../../utils/events'
import { isBoolean, isEvent, isFunction, isUndefined } from '../../utils/inspect'
import { pluckProps } from '../../utils/props'
import { computeHref, computeRel, computeTag, isRouterLink } from '../../utils/router'
import attrsMixin from '../../mixins/attrs'
import listenersMixin from '../../mixins/listeners'
import normalizeSlotMixin from '../../mixins/normalize-slot'
// --- Props ---
// <router-link> specific props
export const routerLinkProps = {
to: {
type: [String, Object],
default: null
},
append: {
type: Boolean,
default: false
},
replace: {
type: Boolean,
default: false
},
event: {
type: [String, Array],
default: 'click'
},
activeClass: {
type: String
// default: undefined
},
exact: {
type: Boolean,
default: false
},
exactActiveClass: {
type: String
// default: undefined
},
routerTag: {
type: String,
default: 'a'
}
}
// <nuxt-link> specific props
export const nuxtLinkProps = {
prefetch: {
type: Boolean,
// Must be `null` to fall back to the value defined in the
// `nuxt.config.js` configuration file for `router.prefetchLinks`
// We convert `null` to `undefined`, so that Nuxt.js will use the
// compiled default. Vue treats `undefined` as default of `false`
// for Boolean props, so we must set it as `null` here to be a
// true tri-state prop
default: null
},
noPrefetch: {
type: Boolean,
default: false
}
}
export const props = {
href: {
type: String,
default: null
},
rel: {
type: String,
// Must be `null` if no value provided
default: null
},
target: {
type: String,
default: '_self'
},
active: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
...routerLinkProps,
...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_LINK, 'routerComponentName')
}
}
// --- Main component ---
// @vue/component
export const BLink = /*#__PURE__*/ Vue.extend({
name: NAME_LINK,
// Mixin order is important!
mixins: [attrsMixin, listenersMixin, normalizeSlotMixin],
inheritAttrs: false,
props,
computed: {
computedTag() {
// We don't pass `this` as the first arg as we need reactivity of the props
const { to, disabled, routerComponentName } = this
return computeTag({ to, disabled, routerComponentName }, this)
},
isRouterLink() {
return isRouterLink(this.computedTag)
},
computedRel() {
// We don't pass `this` as the first arg as we need reactivity of the props
const { target, rel } = this
return computeRel({ target, rel })
},
computedHref() {
// We don't pass `this` as the first arg as we need reactivity of the props
const { to, href } = this
return computeHref({ to, href })
},
computedProps() {
const { prefetch } = this
return this.isRouterLink
? {
...pluckProps({ ...routerLinkProps, ...nuxtLinkProps }, this),
// Coerce `prefetch` value `null` to be `undefined`
prefetch: isBoolean(prefetch) ? prefetch : undefined,
// Pass `router-tag` as `tag` prop
tag: this.routerTag
}
: {}
},
computedAttrs() {
const {
bvAttrs,
computedHref: href,
computedRel: rel,
disabled,
target,
routerTag,
isRouterLink
} = this
return {
...bvAttrs,
// If `href` attribute exists on `<router-link>` (even `undefined` or `null`)
// it fails working on SSR, so we explicitly add it here if needed
// (i.e. if `computedHref` is truthy)
...(href ? { href } : {}),
// We don't render `rel` or `target` on non link tags when using `vue-router`
...(isRouterLink && !isTag(routerTag, 'a') ? {} : { rel, target }),
tabindex: disabled ? '-1' : isUndefined(bvAttrs.tabindex) ? null : bvAttrs.tabindex,
'aria-disabled': disabled ? 'true' : null
}
},
computedListeners() {
return {
// Transfer all listeners (native) to the root element
...this.bvListeners,
// We want to overwrite any click handler since our callback
// will invoke the user supplied handler(s) if `!this.disabled`
click: this.onClick
}
}
},
methods: {
onClick(evt) {
const evtIsEvent = isEvent(evt)
const isRouterLink = this.isRouterLink
const suppliedHandler = this.bvListeners.click
if (evtIsEvent && this.disabled) {
// Stop event from bubbling up
// Kill the event loop attached to this specific `EventTarget`
// Needed to prevent `vue-router` for doing its thing
stopEvent(evt, { immediatePropagation: true })
} else {
/* istanbul ignore next: difficult to test, but we know it works */
if (isRouterLink && evt.currentTarget.__vue__) {
// Router links do not emit instance `click` events, so we
// add in an `$emit('click', evt)` on its Vue instance
evt.currentTarget.__vue__.$emit('click', evt)
}
// Call the suppliedHandler(s), if any provided
concat(suppliedHandler)
.filter(h => isFunction(h))
.forEach(handler => {
handler(...arguments)
})
// Emit the global `$root` click event
this.$root.$emit('clicked::link', evt)
}
// Stop scroll-to-top behavior or navigation on
// regular links when href is just '#'
if (evtIsEvent && !isRouterLink && this.computedHref === '#') {
stopEvent(evt, { propagation: false })
}
},
focus() {
attemptFocus(this.$el)
},
blur() {
attemptBlur(this.$el)
}
},
render(h) {
const { active, disabled } = this
return h(
this.computedTag,
{
class: { active, disabled },
attrs: this.computedAttrs,
props: this.computedProps,
// We must use `nativeOn` for `<router-link>`/`<nuxt-link>` instead of `on`
[this.isRouterLink ? 'nativeOn' : 'on']: this.computedListeners
},
this.normalizeSlot()
)
}
})