quasar
Version:
Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
268 lines (221 loc) • 7.51 kB
JavaScript
import { h, ref, computed, inject, onBeforeUnmount, onMounted, withDirectives, getCurrentInstance } from 'vue'
import QIcon from '../icon/QIcon.js'
import Ripple from '../../directives/ripple/Ripple.js'
import { hMergeSlot } from '../../utils/private.render/render.js'
import { isKeyCode, shouldIgnoreKey } from '../../utils/private.keyboard/key-composition.js'
import { tabsKey, emptyRenderFn } from '../../utils/private.symbols/symbols.js'
import { stopAndPrevent } from '../../utils/event/event.js'
import uid from '../../utils/uid/uid.js'
import { isDeepEqual } from '../../utils/is/is.js'
let id = 0
export const useTabEmits = [ 'click', 'keydown' ]
export const useTabProps = {
icon: String,
label: [ Number, String ],
alert: [ Boolean, String ],
alertIcon: String,
name: {
type: [ Number, String ],
default: () => `t_${ id++ }`
},
noCaps: Boolean,
tabindex: [ String, Number ],
disable: Boolean,
contentClass: String,
ripple: {
type: [ Boolean, Object ],
default: true
}
}
export default function (props, slots, emit, routeData) {
const $tabs = inject(tabsKey, emptyRenderFn)
if ($tabs === emptyRenderFn) {
console.error('QTab/QRouteTab component needs to be child of QTabs')
return emptyRenderFn
}
const { proxy } = getCurrentInstance()
const blurTargetRef = ref(null)
const rootRef = ref(null)
const tabIndicatorRef = ref(null)
const ripple = computed(() => (
props.disable === true || props.ripple === false
? false
: Object.assign(
{ keyCodes: [ 13, 32 ], early: true },
props.ripple === true ? {} : props.ripple
)
))
const isActive = computed(() => $tabs.currentModel.value === props.name)
const classes = computed(() =>
'q-tab relative-position self-stretch flex flex-center text-center'
+ (
isActive.value === true
? (
' q-tab--active'
+ ($tabs.tabProps.value.activeClass ? ' ' + $tabs.tabProps.value.activeClass : '')
+ ($tabs.tabProps.value.activeColor ? ` text-${ $tabs.tabProps.value.activeColor }` : '')
+ ($tabs.tabProps.value.activeBgColor ? ` bg-${ $tabs.tabProps.value.activeBgColor }` : '')
)
: ' q-tab--inactive'
)
+ (props.icon && props.label && $tabs.tabProps.value.inlineLabel === false ? ' q-tab--full' : '')
+ (props.noCaps === true || $tabs.tabProps.value.noCaps === true ? ' q-tab--no-caps' : '')
+ (props.disable === true ? ' disabled' : ' q-focusable q-hoverable cursor-pointer')
+ (routeData !== void 0 ? routeData.linkClass.value : '')
)
const innerClass = computed(() =>
'q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable '
+ ($tabs.tabProps.value.inlineLabel === true ? 'row no-wrap q-tab__content--inline' : 'column')
+ (props.contentClass !== void 0 ? ` ${ props.contentClass }` : '')
)
const tabIndex = computed(() => (
(
props.disable === true
|| $tabs.hasFocus.value === true
|| (isActive.value === false && $tabs.hasActiveTab.value === true)
)
? -1
: props.tabindex || 0
))
function onClick (e, keyboard) {
if (keyboard !== true && blurTargetRef.value !== null) {
blurTargetRef.value.focus()
}
if (props.disable === true) {
// we should hinder native navigation though
if (routeData !== void 0 && routeData.hasRouterLink.value === true) {
stopAndPrevent(e)
}
return
}
// do we have a QTab?
if (routeData === void 0) {
$tabs.updateModel({ name: props.name })
emit('click', e)
return
}
if (routeData.hasRouterLink.value === true) {
const go = (opts = {}) => {
// if requiring to go to another route, then we
// let the QTabs route watcher do its job,
// otherwise directly select this
let hardError
const reqId = opts.to === void 0 || isDeepEqual(opts.to, props.to) === true
? ($tabs.avoidRouteWatcher = uid())
: null
return routeData.navigateToRouterLink(e, { ...opts, returnRouterError: true })
.catch(err => { hardError = err })
.then(softError => {
if (reqId === $tabs.avoidRouteWatcher) {
$tabs.avoidRouteWatcher = false
// if we don't have any hard errors or any soft errors, except for
// when navigating to the same route (on all other soft errors,
// like when navigation was aborted in a nav guard, we don't activate this tab)
if (
hardError === void 0 && (
softError === void 0
|| (softError.message !== void 0 && softError.message.startsWith('Avoided redundant navigation') === true)
)
) {
$tabs.updateModel({ name: props.name })
}
}
if (opts.returnRouterError === true) {
return hardError !== void 0 ? Promise.reject(hardError) : softError
}
})
}
emit('click', e, go)
e.defaultPrevented !== true && go()
return
}
emit('click', e)
}
function onKeydown (e) {
if (isKeyCode(e, [ 13, 32 ])) {
onClick(e, true)
}
else if (
shouldIgnoreKey(e) !== true
&& e.keyCode >= 35
&& e.keyCode <= 40
&& e.altKey !== true
&& e.metaKey !== true
) {
$tabs.onKbdNavigate(e.keyCode, proxy.$el) === true && stopAndPrevent(e)
}
emit('keydown', e)
}
function getContent () {
const
narrow = $tabs.tabProps.value.narrowIndicator,
content = [],
indicator = h('div', {
ref: tabIndicatorRef,
class: [
'q-tab__indicator',
$tabs.tabProps.value.indicatorClass
]
})
props.icon !== void 0 && content.push(
h(QIcon, {
class: 'q-tab__icon',
name: props.icon
})
)
props.label !== void 0 && content.push(
h('div', { class: 'q-tab__label' }, props.label)
)
props.alert !== false && content.push(
props.alertIcon !== void 0
? h(QIcon, {
class: 'q-tab__alert-icon',
color: props.alert !== true
? props.alert
: void 0,
name: props.alertIcon
})
: h('div', {
class: 'q-tab__alert'
+ (props.alert !== true ? ` text-${ props.alert }` : '')
})
)
narrow === true && content.push(indicator)
const node = [
h('div', { class: 'q-focus-helper', tabindex: -1, ref: blurTargetRef }),
h('div', { class: innerClass.value }, hMergeSlot(slots.default, content))
]
narrow === false && node.push(indicator)
return node
}
const tabData = {
name: computed(() => props.name),
rootRef,
tabIndicatorRef,
routeData
}
onBeforeUnmount(() => {
$tabs.unregisterTab(tabData)
})
onMounted(() => {
$tabs.registerTab(tabData)
})
function renderTab (tag, customData) {
const data = {
ref: rootRef,
class: classes.value,
tabindex: tabIndex.value,
role: 'tab',
'aria-selected': isActive.value === true ? 'true' : 'false',
'aria-disabled': props.disable === true ? 'true' : void 0,
onClick,
onKeydown,
...customData
}
return withDirectives(
h(tag, data, getContent()),
[ [ Ripple, ripple.value ] ]
)
}
return { renderTab, $tabs }
}