bootstrap-vue
Version:
BootstrapVue, with over 40 plugins and more than 80 custom components, custom directives, and over 300 icons, provides one of the most comprehensive implementations of Bootstrap v4 components and grid system for Vue.js. With extensive and automated WAI-AR
193 lines (186 loc) • 5.54 kB
JavaScript
import Vue from '../../utils/vue'
import normalizeSlotMixin from '../../mixins/normalize-slot'
import { concat } from '../../utils/array'
import { isEvent, isFunction, isUndefined } from '../../utils/inspect'
import { computeHref, computeRel, computeTag, isRouterLink } from '../../utils/router'
/**
* The Link component is used in many other BV components.
* As such, sharing its props makes supporting all its features easier.
* However, some components need to modify the defaults for their own purpose.
* Prefer sharing a fresh copy of the props to ensure mutations
* do not affect other component references to the props.
*
* https://github.com/vuejs/vue-router/blob/dev/src/components/link.js
* @return {{}}
*/
export const propsFactory = () => ({
href: {
type: String,
default: null
},
rel: {
type: String,
default: null
},
target: {
type: String,
default: '_self'
},
active: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
// router-link specific props
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 prop(s)
noPrefetch: {
type: Boolean,
default: false
}
})
export const props = propsFactory()
// @vue/component
export const BLink = /*#__PURE__*/ Vue.extend({
name: 'BLink',
mixins: [normalizeSlotMixin],
inheritAttrs: false,
props: propsFactory(),
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)
},
isRouterLink() {
return isRouterLink(this.computedTag)
},
computedRel() {
// We don't pass `this` as the first arg as we need reactivity of the props
return computeRel({ target: this.target, rel: this.rel })
},
computedHref() {
// We don't pass `this` as the first arg as we need reactivity of the props
return computeHref({ to: this.to, href: this.href }, this.computedTag)
},
computedProps() {
return this.isRouterLink ? { ...this.$props, tag: this.routerTag } : {}
}
},
methods: {
onClick(evt) {
const evtIsEvent = isEvent(evt)
const isRouterLink = this.isRouterLink
const suppliedHandler = this.$listeners.click
if (evtIsEvent && this.disabled) {
// Stop event from bubbling up
evt.stopPropagation()
// Kill the event loop attached to this specific `EventTarget`
// Needed to prevent `vue-router` for doing its thing
evt.stopImmediatePropagation()
} 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 && (this.disabled || (!isRouterLink && this.computedHref === '#'))) {
evt.preventDefault()
}
},
focus() {
if (this.$el && this.$el.focus) {
this.$el.focus()
}
},
blur() {
if (this.$el && this.$el.blur) {
this.$el.blur()
}
}
},
render(h) {
const tag = this.computedTag
const rel = this.computedRel
const href = this.computedHref
const isRouterLink = this.isRouterLink
const componentData = {
class: { active: this.active, disabled: this.disabled },
attrs: {
...this.$attrs,
rel,
target: this.target,
tabindex: this.disabled
? '-1'
: isUndefined(this.$attrs.tabindex)
? null
: this.$attrs.tabindex,
'aria-disabled': this.disabled ? 'true' : null
},
props: this.computedProps
}
// Add the event handlers. We must use `navtiveOn` for
// `<router-link>`/`<nuxt-link>` instead of `on`
componentData[isRouterLink ? 'nativeOn' : 'on'] = {
// Transfer all listeners (native) to the root element
...this.$listeners,
// We want to overwrite any click handler since our callback
// will invoke the user supplied handler(s) if `!this.disabled`
click: this.onClick
}
// 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)
if (href) {
componentData.attrs.href = href
} else {
// Ensure the prop HREF does not exist for router links
delete componentData.props.href
}
return h(tag, componentData, this.normalizeSlot('default'))
}
})