UNPKG

bootstrap-vue

Version:

BootstrapVue provides one of the most comprehensive implementations of Bootstrap 4 components and grid system for Vue.js and with extensive and automated WAI-ARIA accessibility markup.

213 lines (189 loc) 5.66 kB
import { assign, keys } from '../../utils/object' import { arrayIncludes, concat } from '../../utils/array' import { mergeData } from 'vue-functional-data-merge' /** * 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 function propsFactory () { return { href: { type: String, default: null }, rel: { type: String, default: null }, target: { type: String, default: '_self' }, active: { type: Boolean, default: false }, activeClass: { type: String, default: 'active' }, append: { type: Boolean, default: false }, disabled: { type: Boolean, default: false }, event: { type: [String, Array], default: 'click' }, exact: { type: Boolean, default: false }, exactActiveClass: { type: String, default: 'active' }, replace: { type: Boolean, default: false }, routerTag: { type: String, default: 'a' }, to: { type: [String, Object], default: null } } } export const props = propsFactory() export function pickLinkProps (propsToPick) { const freshLinkProps = propsFactory() // Normalize everything to array. propsToPick = concat(propsToPick) return keys(freshLinkProps).reduce((memo, prop) => { if (arrayIncludes(propsToPick, prop)) { memo[prop] = freshLinkProps[prop] } return memo }, {}) } export function omitLinkProps (propsToOmit) { const freshLinkProps = propsFactory() // Normalize everything to array. propsToOmit = concat(propsToOmit) return keys(props).reduce((memo, prop) => { if (!arrayIncludes(propsToOmit, prop)) { memo[prop] = freshLinkProps[prop] } return memo }, {}) } export const computed = { linkProps () { let linkProps = {} let propKeys = keys(props) for (let i = 0; i < propKeys.length; i++) { const prop = propKeys[i] // Computed Vue getters are bound to the instance. linkProps[prop] = this[prop] } return linkProps } } function computeTag (props, parent) { return Boolean(parent.$router) && props.to && !props.disabled ? 'router-link' : 'a' } function computeHref ({ disabled, href, to }, tag) { // We've already checked the parent.$router in computeTag, // so router-link means live router. // When deferring to Vue Router's router-link, // don't use the href attr at all. // Must return undefined for router-link to populate href. if (tag === 'router-link') return void 0 // If href explicitly provided if (href) return href // Reconstruct href when `to` used, but no router if (to) { // Fallback to `to` prop (if `to` is a string) if (typeof to === 'string') return to // Fallback to `to.path` prop (if `to` is an object) if (typeof to === 'object' && typeof to.path === 'string') return to.path } // If nothing is provided use '#' return '#' } function computeRel ({ target, rel }) { if (target === '_blank' && rel === null) { return 'noopener' } return rel || null } function clickHandlerFactory ({ disabled, tag, href, suppliedHandler, parent }) { const isRouterLink = tag === 'router-link' return function onClick (e) { if (disabled && e instanceof Event) { // Stop event from bubbling up. e.stopPropagation() // Kill the event loop attached to this specific EventTarget. e.stopImmediatePropagation() } else { parent.$root.$emit('clicked::link', e) if (isRouterLink && e.target.__vue__) { e.target.__vue__.$emit('click', e) } if (typeof suppliedHandler === 'function') { suppliedHandler(...arguments) } } if ((!isRouterLink && href === '#') || disabled) { // Stop scroll-to-top behavior or navigation. e.preventDefault() } } } export default { functional: true, props: propsFactory(), render (h, { props, data, parent, children }) { const tag = computeTag(props, parent) const rel = computeRel(props) const href = computeHref(props, tag) const eventType = tag === 'router-link' ? 'nativeOn' : 'on' const suppliedHandler = (data[eventType] || {}).click const handlers = { click: clickHandlerFactory({ tag, href, disabled: props.disabled, suppliedHandler, parent }) } const componentData = mergeData(data, { class: [ props.active ? (props.exact ? props.exactActiveClass : props.activeClass) : null, { disabled: props.disabled } ], attrs: { rel, href, target: props.target, tabindex: props.disabled ? '-1' : (data.attrs ? data.attrs.tabindex : null), 'aria-disabled': (tag === 'a' && props.disabled) ? 'true' : null }, props: assign(props, { tag: props.routerTag }) }) // If href prop exists on router-link (even undefined or null) it fails working on SSR if (!componentData.attrs.href) { delete componentData.attrs.href } // We want to overwrite any click handler since our callback // will invoke the supplied handler if !props.disabled componentData[eventType] = assign(componentData[eventType] || {}, handlers) return h(tag, componentData, children) } }