quasar
Version:
Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
353 lines (306 loc) • 8.94 kB
JavaScript
import {
h,
computed,
watch,
onMounted,
onBeforeUnmount,
getCurrentInstance
} from 'vue'
import QBtn from '../btn/QBtn.js'
import useDark, {
useDarkProps
} from '../../composables/private.use-dark/use-dark.js'
import usePanel, {
usePanelProps,
usePanelEmits
} from '../../composables/private.use-panel/use-panel.js'
import useFullscreen, {
useFullscreenProps,
useFullscreenEmits
} from '../../composables/private.use-fullscreen/use-fullscreen.js'
import { createComponent } from '../../utils/private.create/create.js'
import { isNumber } from '../../utils/is/is.js'
import { hMergeSlot, hDir } from '../../utils/private.render/render.js'
const navigationPositionOptions = ['top', 'right', 'bottom', 'left']
const controlTypeOptions = ['regular', 'flat', 'outline', 'push', 'unelevated']
export default createComponent({
name: 'QCarousel',
props: {
...useDarkProps,
...usePanelProps,
...useFullscreenProps,
transitionPrev: {
// usePanelParentProps override
type: String,
default: 'fade'
},
transitionNext: {
// usePanelParentProps override
type: String,
default: 'fade'
},
height: String,
padding: Boolean,
controlColor: String,
controlTextColor: String,
controlType: {
type: String,
validator: v => controlTypeOptions.includes(v),
default: 'flat'
},
autoplay: [Number, Boolean],
arrows: Boolean,
prevIcon: String,
nextIcon: String,
navigation: Boolean,
navigationPosition: {
type: String,
validator: v => navigationPositionOptions.includes(v)
},
navigationIcon: String,
navigationActiveIcon: String,
thumbnails: Boolean
},
emits: [...useFullscreenEmits, ...usePanelEmits],
setup(props, { slots }) {
const {
proxy: { $q }
} = getCurrentInstance()
const isDark = useDark(props, $q)
let timer = null,
panelsLen
const {
updatePanelsList,
getPanelContent,
panelDirectives,
goToPanel,
previousPanel,
nextPanel,
getEnabledPanels,
panelIndex
} = usePanel()
const { inFullscreen } = useFullscreen()
const style = computed(() =>
inFullscreen.value !== true && props.height !== void 0
? { height: props.height }
: {}
)
const direction = computed(() =>
props.vertical === true ? 'vertical' : 'horizontal'
)
const navigationPosition = computed(
() =>
props.navigationPosition ||
(props.vertical === true ? 'right' : 'bottom')
)
const classes = computed(
() =>
`q-carousel q-panel-parent q-carousel--with${props.padding === true ? '' : 'out'}-padding` +
(inFullscreen.value === true ? ' fullscreen' : '') +
(isDark.value === true ? ' q-carousel--dark q-dark' : '') +
(props.arrows === true
? ` q-carousel--arrows-${direction.value}`
: '') +
(props.navigation === true
? ` q-carousel--navigation-${navigationPosition.value}`
: '')
)
const arrowIcons = computed(() => {
const ico = [
props.prevIcon ||
$q.iconSet.carousel[props.vertical === true ? 'up' : 'left'],
props.nextIcon ||
$q.iconSet.carousel[props.vertical === true ? 'down' : 'right']
]
return props.vertical === false && $q.lang.rtl === true
? ico.reverse()
: ico
})
const navIcon = computed(
() => props.navigationIcon || $q.iconSet.carousel.navigationIcon
)
const navActiveIcon = computed(
() => props.navigationActiveIcon || navIcon.value
)
const controlProps = computed(() => ({
color: props.controlColor,
textColor: props.controlTextColor,
round: true,
[props.controlType]: true,
dense: true
}))
watch(
() => props.modelValue,
() => {
if (props.autoplay) {
startTimer()
}
}
)
watch(
() => props.autoplay,
val => {
if (val) {
startTimer()
} else if (timer !== null) {
clearTimeout(timer)
timer = null
}
}
)
function startTimer() {
const duration =
isNumber(props.autoplay) === true ? Math.abs(props.autoplay) : 5000
if (timer !== null) clearTimeout(timer)
timer = setTimeout(() => {
timer = null
if (duration >= 0) {
nextPanel()
} else {
previousPanel()
}
}, duration)
}
onMounted(() => {
if (props.autoplay) startTimer()
})
onBeforeUnmount(() => {
if (timer !== null) clearTimeout(timer)
})
function getNavigationContainer(type, mapping) {
return h(
'div',
{
class:
'q-carousel__control q-carousel__navigation no-wrap absolute flex' +
` q-carousel__navigation--${type} q-carousel__navigation--${navigationPosition.value}` +
(props.controlColor !== void 0 ? ` text-${props.controlColor}` : '')
},
[
h(
'div',
{
class: 'q-carousel__navigation-inner flex flex-center no-wrap'
},
getEnabledPanels().map(mapping)
)
]
)
}
function getContent() {
const node = []
if (props.navigation === true) {
const fn =
slots['navigation-icon'] !== void 0
? slots['navigation-icon']
: opts =>
h(QBtn, {
key: 'nav' + opts.name,
class: `q-carousel__navigation-icon q-carousel__navigation-icon--${opts.active === true ? '' : 'in'}active`,
...opts.btnProps,
onClick: opts.onClick
})
const maxIndex = panelsLen - 1
node.push(
getNavigationContainer('buttons', (panel, index) => {
const name = panel.props.name
const active = panelIndex.value === index
return fn({
index,
maxIndex,
name,
active,
btnProps: {
icon: active === true ? navActiveIcon.value : navIcon.value,
size: 'sm',
...controlProps.value
},
onClick: () => {
goToPanel(name)
}
})
})
)
} else if (props.thumbnails === true) {
const color =
props.controlColor !== void 0 ? ` text-${props.controlColor}` : ''
node.push(
getNavigationContainer('thumbnails', panel => {
const slide = panel.props
return h('img', {
key: 'tmb#' + slide.name,
class:
`q-carousel__thumbnail q-carousel__thumbnail--${slide.name === props.modelValue ? '' : 'in'}active` +
color,
src: slide.imgSrc || slide['img-src'],
onClick: () => {
goToPanel(slide.name)
}
})
})
)
}
if (props.arrows === true && panelIndex.value >= 0) {
if (props.infinite === true || panelIndex.value > 0) {
node.push(
h(
'div',
{
key: 'prev',
class: `q-carousel__control q-carousel__arrow q-carousel__prev-arrow q-carousel__prev-arrow--${direction.value} absolute flex flex-center`
},
[
h(QBtn, {
icon: arrowIcons.value[0],
...controlProps.value,
onClick: previousPanel
})
]
)
)
}
if (props.infinite === true || panelIndex.value < panelsLen - 1) {
node.push(
h(
'div',
{
key: 'next',
class:
'q-carousel__control q-carousel__arrow q-carousel__next-arrow' +
` q-carousel__next-arrow--${direction.value} absolute flex flex-center`
},
[
h(QBtn, {
icon: arrowIcons.value[1],
...controlProps.value,
onClick: nextPanel
})
]
)
)
}
}
return hMergeSlot(slots.control, node)
}
return () => {
panelsLen = updatePanelsList(slots)
return h(
'div',
{
class: classes.value,
style: style.value
},
[
hDir(
'div',
{ class: 'q-carousel__slides-container' },
getPanelContent(),
'sl-cont',
props.swipeable,
() => panelDirectives.value
)
].concat(getContent())
)
}
}
})