vuetify
Version:
Vue Material Component Framework
277 lines (240 loc) • 6.71 kB
text/typescript
// Styles
import './VWindow.sass'
// Types
import { VNode, VNodeDirective } from 'vue/types/vnode'
import { PropType } from 'vue'
import { TouchHandlers } from 'vuetify/types'
// Directives
import Touch from '../../directives/touch'
// Components
import VBtn from '../VBtn'
import VIcon from '../VIcon'
import { BaseItemGroup } from '../VItemGroup/VItemGroup'
/* @vue/component */
export default BaseItemGroup.extend({
name: 'v-window',
directives: { Touch },
provide (): object {
return {
windowGroup: this,
}
},
props: {
activeClass: {
type: String,
default: 'v-window-item--active',
},
continuous: Boolean,
mandatory: {
type: Boolean,
default: true,
},
nextIcon: {
type: [Boolean, String],
default: '$next',
},
prevIcon: {
type: [Boolean, String],
default: '$prev',
},
reverse: {
type: Boolean,
default: undefined,
},
showArrows: Boolean,
showArrowsOnHover: Boolean,
touch: Object as PropType<TouchHandlers>,
touchless: Boolean,
value: {
required: false,
},
vertical: Boolean,
},
data () {
return {
changedByDelimiters: false,
internalHeight: undefined as undefined | string, // This can be fixed by child class.
transitionHeight: undefined as undefined | string, // Intermediate height during transition.
transitionCount: 0, // Number of windows in transition state.
isBooted: false,
isReverse: false,
}
},
computed: {
isActive (): boolean {
return this.transitionCount > 0
},
classes (): object {
return {
...BaseItemGroup.options.computed.classes.call(this),
'v-window--show-arrows-on-hover': this.showArrowsOnHover,
}
},
computedTransition (): string {
if (!this.isBooted) return ''
const axis = this.vertical ? 'y' : 'x'
const reverse = this.$vuetify.rtl && axis === 'x' ? !this.internalReverse : this.internalReverse
const direction = reverse ? '-reverse' : ''
return `v-window-${axis}${direction}-transition`
},
hasActiveItems (): boolean {
return Boolean(
this.items.find(item => !item.disabled)
)
},
hasNext (): boolean {
return this.continuous || this.internalIndex < this.items.length - 1
},
hasPrev (): boolean {
return this.continuous || this.internalIndex > 0
},
internalIndex (): number {
return this.items.findIndex((item, i) => {
return this.internalValue === this.getValue(item, i)
})
},
internalReverse (): boolean {
return this.reverse ? !this.isReverse : this.isReverse
},
},
watch: {
internalIndex: 'updateReverse',
},
mounted () {
window.requestAnimationFrame(() => (this.isBooted = true))
},
methods: {
genContainer (): VNode {
const children = [this.$slots.default]
if (this.showArrows) {
children.push(this.genControlIcons())
}
return this.$createElement('div', {
staticClass: 'v-window__container',
class: {
'v-window__container--is-active': this.isActive,
},
style: {
height: this.internalHeight || this.transitionHeight,
},
}, children)
},
genIcon (
direction: 'prev' | 'next',
icon: string,
fn: () => void
) {
return this.$createElement('div', {
staticClass: `v-window__${direction}`,
}, [
this.$createElement(VBtn, {
props: { icon: true },
attrs: {
'aria-label': this.$vuetify.lang.t(`$vuetify.carousel.${direction}`),
},
on: {
click: () => {
this.changedByDelimiters = true
fn()
},
},
}, [
this.$createElement(VIcon, {
props: { large: true },
}, icon),
]),
])
},
genControlIcons () {
const icons = []
const prevIcon = this.$vuetify.rtl
? this.nextIcon
: this.prevIcon
/* istanbul ignore else */
if (
this.hasPrev &&
prevIcon &&
typeof prevIcon === 'string'
) {
const icon = this.genIcon('prev', prevIcon, this.prev)
icon && icons.push(icon)
}
const nextIcon = this.$vuetify.rtl
? this.prevIcon
: this.nextIcon
/* istanbul ignore else */
if (
this.hasNext &&
nextIcon &&
typeof nextIcon === 'string'
) {
const icon = this.genIcon('next', nextIcon, this.next)
icon && icons.push(icon)
}
return icons
},
getNextIndex (index: number): number {
const nextIndex = (index + 1) % this.items.length
const item = this.items[nextIndex]
if (item.disabled) return this.getNextIndex(nextIndex)
return nextIndex
},
getPrevIndex (index: number): number {
const prevIndex = (index + this.items.length - 1) % this.items.length
const item = this.items[prevIndex]
if (item.disabled) return this.getPrevIndex(prevIndex)
return prevIndex
},
next () {
this.isReverse = this.$vuetify.rtl
/* istanbul ignore if */
if (!this.hasActiveItems || !this.hasNext) return
const nextIndex = this.getNextIndex(this.internalIndex)
const item = this.items[nextIndex]
this.internalValue = this.getValue(item, nextIndex)
},
prev () {
this.isReverse = !this.$vuetify.rtl
/* istanbul ignore if */
if (!this.hasActiveItems || !this.hasPrev) return
const lastIndex = this.getPrevIndex(this.internalIndex)
const item = this.items[lastIndex]
this.internalValue = this.getValue(item, lastIndex)
},
updateReverse (val: number, oldVal: number) {
if (this.changedByDelimiters) {
this.changedByDelimiters = false
return
}
this.isReverse = val < oldVal
},
},
render (h): VNode {
const data = {
staticClass: 'v-window',
class: this.classes,
directives: [] as VNodeDirective[],
}
if (!this.touchless) {
const value = this.touch || {
left: () => {
this.$vuetify.rtl ? this.prev() : this.next()
},
right: () => {
this.$vuetify.rtl ? this.next() : this.prev()
},
end: (e: TouchEvent) => {
e.stopPropagation()
},
start: (e: TouchEvent) => {
e.stopPropagation()
},
}
data.directives.push({
name: 'touch',
value,
})
}
return h('div', data, [this.genContainer()])
},
})