vuetify
Version:
Vue.js 2 Semantic Component Framework
235 lines (198 loc) • 5.84 kB
text/typescript
// Styles
import '../../stylus/components/_item-group.styl'
// 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'
type GroupableInstance = InstanceType<typeof Groupable> & { 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 {
...this.themeClasses
}
},
selectedItems (): GroupableInstance[] {
return this.items.filter((item, index) => {
return this.toggleMethod(this.getValue(item, index))
})
},
selectedValues (): any[] {
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 () {
// https://github.com/vuetifyjs/vuetify/issues/5352
this.$nextTick(this.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: {
getValue (item: GroupableInstance, i: number): unknown {
return item.value == null || item.value === ''
? i
: item.value
},
onClick (item: GroupableInstance, index: number) {
this.updateInternalValue(
this.getValue(item, index)
)
},
register (item: GroupableInstance) {
const index = this.items.push(item) - 1
item.$on('change', () => this.onClick(item, index))
// If no value provided and mandatory,
// assign first registered item
if (this.mandatory && this.internalLazyValue == null) {
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)
},
updateItemsState () {
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 index = last ? this.items.length - 1 : 0
this.updateInternalValue(
this.getValue(this.items[index], 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 woudl 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', {
staticClass: 'v-item-group',
class: this.classes
}, this.$slots.default)
}
})
export default BaseItemGroup.extend({
name: 'v-item-group',
provide (): object {
return {
itemGroup: this
}
}
})