vuetify
Version:
Vue Material Component Framework
264 lines (220 loc) • 6.47 kB
text/typescript
// Styles
import './VItemGroup.sass'
// Mixins
import Groupable from '../../mixins/groupable'
import Proxyable from '../../mixins/proxyable'
import Themeable from '../../mixins/themeable'
// Utilities
import mixins from '../../util/mixins'
import { consoleWarn } from '../../util/console'
// Types
import { VNode } from 'vue/types'
export type GroupableInstance = InstanceType<typeof Groupable> & {
id?: string
to?: any
value?: any
}
export const BaseItemGroup = mixins(
Proxyable,
Themeable
).extend({
name: 'base-item-group',
props: {
activeClass: {
type: String,
default: 'v-item--active',
},
mandatory: Boolean,
max: {
type: [Number, String],
default: null,
},
multiple: Boolean,
},
data () {
return {
// As long as a value is defined, show it
// Otherwise, check if multiple
// to determine which default to provide
internalLazyValue: this.value !== undefined
? this.value
: this.multiple ? [] : undefined,
items: [] as GroupableInstance[],
}
},
computed: {
classes (): Record<string, boolean> {
return {
'v-item-group': true,
...this.themeClasses,
}
},
selectedIndex (): number {
return (this.selectedItem && this.items.indexOf(this.selectedItem)) || -1
},
selectedItem (): GroupableInstance | undefined {
if (this.multiple) return undefined
return this.selectedItems[0]
},
selectedItems (): GroupableInstance[] {
return this.items.filter((item, index) => {
return this.toggleMethod(this.getValue(item, index))
})
},
selectedValues (): any[] {
if (this.internalValue == null) return []
return Array.isArray(this.internalValue)
? this.internalValue
: [this.internalValue]
},
toggleMethod (): (v: any) => boolean {
if (!this.multiple) {
return (v: any) => this.internalValue === v
}
const internalValue = this.internalValue
if (Array.isArray(internalValue)) {
return (v: any) => internalValue.includes(v)
}
return () => false
},
},
watch: {
internalValue: 'updateItemsState',
items: 'updateItemsState',
},
created () {
if (this.multiple && !Array.isArray(this.internalValue)) {
consoleWarn('Model must be bound to an array if the multiple property is true.', this)
}
},
methods: {
genData (): object {
return {
class: this.classes,
}
},
getValue (item: GroupableInstance, i: number): unknown {
return item.value == null || item.value === ''
? i
: item.value
},
onClick (item: GroupableInstance) {
this.updateInternalValue(
this.getValue(item, this.items.indexOf(item))
)
},
register (item: GroupableInstance) {
const index = this.items.push(item) - 1
item.$on('change', () => this.onClick(item))
// If no value provided and mandatory,
// assign first registered item
if (this.mandatory && !this.selectedValues.length) {
this.updateMandatory()
}
this.updateItem(item, index)
},
unregister (item: GroupableInstance) {
if (this._isDestroyed) return
const index = this.items.indexOf(item)
const value = this.getValue(item, index)
this.items.splice(index, 1)
const valueIndex = this.selectedValues.indexOf(value)
// Items is not selected, do nothing
if (valueIndex < 0) return
// If not mandatory, use regular update process
if (!this.mandatory) {
return this.updateInternalValue(value)
}
// Remove the value
if (this.multiple && Array.isArray(this.internalValue)) {
this.internalValue = this.internalValue.filter(v => v !== value)
} else {
this.internalValue = undefined
}
// If mandatory and we have no selection
// add the last item as value
/* istanbul ignore else */
if (!this.selectedItems.length) {
this.updateMandatory(true)
}
},
updateItem (item: GroupableInstance, index: number) {
const value = this.getValue(item, index)
item.isActive = this.toggleMethod(value)
},
// https://github.com/vuetifyjs/vuetify/issues/5352
updateItemsState () {
this.$nextTick(() => {
if (this.mandatory &&
!this.selectedItems.length
) {
return this.updateMandatory()
}
// TODO: Make this smarter so it
// doesn't have to iterate every
// child in an update
this.items.forEach(this.updateItem)
})
},
updateInternalValue (value: any) {
this.multiple
? this.updateMultiple(value)
: this.updateSingle(value)
},
updateMandatory (last?: boolean) {
if (!this.items.length) return
const items = this.items.slice()
if (last) items.reverse()
const item = items.find(item => !item.disabled)
// If no tabs are available
// aborts mandatory value
if (!item) return
const index = this.items.indexOf(item)
this.updateInternalValue(
this.getValue(item, index)
)
},
updateMultiple (value: any) {
const defaultValue = Array.isArray(this.internalValue)
? this.internalValue
: []
const internalValue = defaultValue.slice()
const index = internalValue.findIndex(val => val === value)
if (
this.mandatory &&
// Item already exists
index > -1 &&
// value would be reduced below min
internalValue.length - 1 < 1
) return
if (
// Max is set
this.max != null &&
// Item doesn't exist
index < 0 &&
// value would be increased above max
internalValue.length + 1 > this.max
) return
index > -1
? internalValue.splice(index, 1)
: internalValue.push(value)
this.internalValue = internalValue
},
updateSingle (value: any) {
const isSame = value === this.internalValue
if (this.mandatory && isSame) return
this.internalValue = isSame ? undefined : value
},
},
render (h): VNode {
return h('div', this.genData(), this.$slots.default)
},
})
export default BaseItemGroup.extend({
name: 'v-item-group',
provide (): object {
return {
itemGroup: this,
}
},
})