@gitlab/ui
Version:
GitLab UI Components
338 lines (321 loc) • 11.4 kB
JavaScript
import isFunction from 'lodash/isFunction';
import isString from 'lodash/isString';
import isObject from 'lodash/isObject';
import toString from 'lodash/toString';
import isBoolean from 'lodash/isBoolean';
import concat from 'lodash/concat';
import { SafeLinkDirective, isExternalURL } from '../../../directives/safe_link/safe_link';
import { stopEvent } from '../../../utils/utils';
import { isEvent } from '../../../vendor/bootstrap-vue/src/utils/inspect';
import { stringifyQueryObj } from '../../../vendor/bootstrap-vue/src/utils/router';
import { safeVueInstance } from '../../../vendor/bootstrap-vue/src/utils/safe-vue-instance';
import { attemptFocus, attemptBlur } from '../../../vendor/bootstrap-vue/src/utils/dom';
import { linkVariantOptions, isVue3, linkVariantInline, linkVariantUnstyled } from '../../../utils/constants';
import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
//
const ANCHOR_TAG = 'a';
const NUXT_LINK_TAG = 'nuxt-link';
const VUE_ROUTER_LINK_TAG = 'router-link';
var script = {
name: 'GlLink',
directives: {
SafeLink: SafeLinkDirective
},
inheritAttrs: false,
props: {
/**
* Denotes the target URL of the link for standard links.
*/
href: {
type: String,
required: false,
default: undefined
},
/**
* When set to `true`, disables the component's functionality and places it in a disabled state.
*/
disabled: {
type: Boolean,
required: false,
default: false
},
/**
* Skips sanitization of href if true. This should be used sparingly.
* Consult security team before setting to true.
*/
isUnsafeLink: {
type: Boolean,
required: false,
default: false
},
/**
* Sets the 'rel' attribute on the rendered link.
*/
rel: {
type: String,
required: false,
default: null
},
/**
* Sets the 'target' attribute on the rendered link.
*/
target: {
type: String,
required: false,
default: null
},
/**
* When set to `true`, places the component in the active state with active styling
*/
active: {
type: Boolean,
required: false,
default: false
},
/**
* <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.
*/
to: {
type: [Object, String],
required: false,
default: undefined
},
/**
* <router-link> prop: Configure the active CSS class applied when the link is active.
*/
activeClass: {
type: String,
required: false,
default: undefined
},
/**
* <router-link> prop: Configure the active CSS class applied when the link is active with exact match.
*/
exactActiveClass: {
type: String,
required: false,
default: undefined
},
/**
* <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.
*/
replace: {
type: Boolean,
required: false,
default: false
},
/**
* <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`
*/
prefetch: {
type: Boolean,
required: false,
// 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
},
/**
* If inline variant, controls ↗ character visibility
*/
showExternalIcon: {
type: Boolean,
required: false,
default: false
},
/**
* Link variant
*/
variant: {
type: String,
required: false,
default: null,
validator: value => value && Object.hasOwn(linkVariantOptions, value)
}
},
computed: {
safeLinkConfig() {
return {
skipSanitization: this.isUnsafeLink
};
},
tag() {
const hasRouter = Boolean(safeVueInstance(this).$router);
const hasNuxt = Boolean(safeVueInstance(this).$nuxt);
if (!hasRouter || this.disabled || !this.to) {
return ANCHOR_TAG;
}
return hasNuxt ? NUXT_LINK_TAG : VUE_ROUTER_LINK_TAG;
},
isRouterLink() {
return this.tag !== ANCHOR_TAG;
},
isVue3RouterLink() {
return this.tag === VUE_ROUTER_LINK_TAG && isVue3;
},
isInlineAndHasExternalIcon() {
return this.showExternalIcon && this.variant === linkVariantInline && this.href && isExternalURL(this.target, this.href);
},
computedHref() {
const fallback = '#';
const toFallback = '/';
const {
to
} = this;
// Return `href` when explicitly provided
if (this.href) {
return this.href;
}
if (isString(to)) {
return to || toFallback;
}
// Fallback to `to.path' + `to.query` + `to.hash` prop (if `to` is an object)
if (isObject(to) && (to.path || to.query || to.hash)) {
const path = toString(to.path);
const query = stringifyQueryObj(to.query);
let hash = toString(to.hash);
hash = !hash || hash.charAt(0) === '#' ? hash : `#${hash}`;
return `${path}${query}${hash}` || fallback;
}
return fallback;
},
computedProps() {
const baseRouterLinkProps = {
to: this.to,
activeClass: this.activeClass,
exactActiveClass: this.exactActiveClass,
replace: this.replace,
...(isBoolean(this.prefetch) ? {
prefetch: this.prefetch
} : {})
};
if (this.isRouterLink) {
if (this.isVue3RouterLink) {
return baseRouterLinkProps;
}
return {
...baseRouterLinkProps,
...this.$attrs
};
}
return {
disabled: this.disabled,
...(this.disabled ? {
'aria-disabled': 'true',
tabindex: '-1'
} : {}),
rel: this.rel,
target: this.target,
href: this.computedHref,
...this.$attrs
};
},
computedListeners() {
const {
click,
...listenersWithoutClick
} = this.$listeners;
return listenersWithoutClick;
},
computedClass() {
if (this.variant === linkVariantUnstyled) {
return [];
}
return ['gl-link', linkVariantOptions[this.variant], {
disabled: this.disabled,
active: this.active,
'gl-link-inline-external': this.isInlineAndHasExternalIcon
}];
}
},
methods: {
onClick(event, navigate) {
const eventIsEvent = isEvent(event);
const suppliedHandler = this.$listeners.click;
if (eventIsEvent && this.disabled) {
// Stop event from bubbling up
// Kill the event loop attached to this specific `EventTarget`
// Needed to prevent `vue-router` from navigating
stopEvent(event, {
immediatePropagation: true
});
} else {
// Router links do not emit instance `click` events, so we
// add in an `$emit('click', event)` on its Vue instance
//
// seems not to be required for Vue3 compat build
if (this.isRouterLink) {
var _event$currentTarget$;
// eslint-disable-next-line no-underscore-dangle
(_event$currentTarget$ = event.currentTarget.__vue__) === null || _event$currentTarget$ === void 0 ? void 0 : _event$currentTarget$.$emit('click', event);
}
// Call the suppliedHandler(s), if any provided
concat([], suppliedHandler).filter(h => isFunction(h)).forEach(handler => {
// eslint-disable-next-line prefer-rest-params
handler(...arguments);
});
// this navigate function comes from Vue 3 router
// See https://router.vuejs.org/guide/advanced/extending-router-link.html#Extending-RouterLink
if (isFunction(navigate)) {
navigate(event);
}
// TODO: Remove deprecated 'clicked::link' event
this.$root.$emit('clicked::link', event);
}
// Stop scroll-to-top behavior or navigation on
// regular links when href is just '#'
if (eventIsEvent && !this.isRouterLink && this.computedHref === '#') {
stopEvent(event, {
stopPropagation: false
});
}
},
focus() {
attemptFocus(this.$el);
},
blur() {
attemptBlur(this.$el);
}
}
};
/* script */
const __vue_script__ = script;
/* template */
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.isVue3RouterLink)?_c(_vm.tag,_vm._b({tag:"component",attrs:{"custom":""},scopedSlots:_vm._u([{key:"default",fn:function(ref){
var _obj;
var routerLinkHref = ref.href;
var isActive = ref.isActive;
var isExactActive = ref.isExactActive;
var navigate = ref.navigate;return [_c('a',_vm._g(_vm._b({directives:[{name:"safe-link",rawName:"v-safe-link:[safeLinkConfig]",arg:_vm.safeLinkConfig}],class:[_vm.computedClass, ( _obj = {}, _obj[_vm.activeClass] = isActive, _obj[_vm.exactActiveClass] = isExactActive, _obj )],attrs:{"href":routerLinkHref},on:{"click":function($event){return _vm.onClick($event, navigate)}}},'a',_vm.$attrs,false),_vm.computedListeners),[_vm._t("default")],2)]}}],null,true)},'component',_vm.computedProps,false)):(_vm.isRouterLink)?_c(_vm.tag,_vm._g(_vm._b({directives:[{name:"safe-link",rawName:"v-safe-link:[safeLinkConfig]",arg:_vm.safeLinkConfig}],tag:"component",class:_vm.computedClass,nativeOn:{"click":function($event){return _vm.onClick.apply(null, arguments)}}},'component',_vm.computedProps,false),_vm.computedListeners),[_vm._t("default")],2):_c(_vm.tag,_vm._g(_vm._b({directives:[{name:"safe-link",rawName:"v-safe-link:[safeLinkConfig]",arg:_vm.safeLinkConfig}],tag:"component",class:_vm.computedClass,on:{"click":_vm.onClick}},'component',_vm.computedProps,false),_vm.computedListeners),[_vm._t("default")],2)};
var __vue_staticRenderFns__ = [];
/* style */
const __vue_inject_styles__ = undefined;
/* scoped */
const __vue_scope_id__ = undefined;
/* module identifier */
const __vue_module_identifier__ = undefined;
/* functional template */
const __vue_is_functional_template__ = false;
/* style inject */
/* style inject SSR */
/* style inject shadow dom */
const __vue_component__ = /*#__PURE__*/__vue_normalize__(
{ render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
__vue_inject_styles__,
__vue_script__,
__vue_scope_id__,
__vue_is_functional_template__,
__vue_module_identifier__,
false,
undefined,
undefined,
undefined
);
export { __vue_component__ as default };