@jgjergj/vuetify-toolkit
Version:
Vuetify.js Additional components set
334 lines (331 loc) • 12.3 kB
text/typescript
import { VNode, VNodeData, PropType } from 'vue'
import { VTreeviewNodeProps, consoleError, getObjectValueByPath, getPropertyFromItem } from '../../vuetify-import'
import { VChipA } from '../../shims-vuetify'
import VTreeSelectList from './VTreeSelectList'
import treeviewScopedSlots from '../../utils/TreeviewScopedSlots'
import commonSelect from '../mixin/commonSelect'
import { mergeProps } from '../../utils/mergeProps'
import { openCloseCommands } from '../../utils/ToolbarCommand'
export default commonSelect.extend({
name: 'v-tree-select',
props: {
...VTreeviewNodeProps,
allowSelectParents: {
type: Boolean,
default: false
},
openOnePath: {
type: Boolean,
default: false
},
showFullPath: {
type: Boolean,
default: false
},
delimeter: {
type: String,
default: '/'
},
openAll: Boolean,
selectionType: {
type: String as PropType<'leaf' | 'independent'>,
default: 'leaf',
validator: (v: string) => ['leaf', 'independent'].includes(v)
},
toolbarCommands: {
type: Array,
default: function () {
return openCloseCommands(this as any)
}
},
openKeys: {
type: Array,
default: undefined
},
value: {
type: Array,
default: undefined
}
},
data: () => ({
parents: new Map(),
itemCashe: new Map(),
openCache: [] as (string|number)[],
filteredKeys: new Set<string|number>()
}),
computed: {
filteredItems (): any[] {
const items = this.buildTree(this.$props.items)
return items
},
selectedKeys (): string|number[] {
const keys: string|number[] = []
this.selectedItems.map((v: any) => {
keys.push(getObjectValueByPath(v, this.$props.itemKey, []))
})
return keys
},
listData (): Object {
const data = (commonSelect as any).options.computed.listData.call(this)
mergeProps(data.props, this.$props, VTreeviewNodeProps)
data.props.items = this.filteredItems
data.props.selectedKeys = this.selectedKeys
data.props.openCache = this.openCache
data.props.itemCache = this.itemCashe
data.props.allowSelectParents = this.$props.allowSelectParents
data.props.searchText = this.internalSearch
Object.assign(data.on, {
'update-dimensions': () => (this.$refs.menu as any).updateDimensions(),
'update:selected': (key: string | number, isSelected: boolean) => this.updateSelected(key, isSelected),
'update:open': (key: string | number, isOpen: boolean) => this.updateOpen(key, isOpen)
})
Object.assign(data.scopedSlots, treeviewScopedSlots(this.$scopedSlots))
return data
},
staticList (): VNode {
if (this.$slots['no-data'] || this.$slots['prepend-item'] || this.$slots['append-item']) {
consoleError('assert: staticList should not be called if slots are used')
}
return this.$createElement(VTreeSelectList, this.listData)
}
},
watch: {
internalSearch: {
immediate: true,
handler (val, oldVal) {
if (this.$props.autocomplete && val && val !== '') {
this.filteredKeys.clear()
this.itemCashe.forEach((item, k) => {
const comparedVal = getPropertyFromItem(item, this.$props.itemText)
const matchFilter = (typeof (comparedVal) === 'string' && this.itemMatchFilter(item))
if (matchFilter) {
this.filteredKeys.add(k)
}
})
this.filteredKeys.forEach((v) => {
this.getParentKeys(v).flatMap(pKey => this.updateOpen(pKey, true))
})
} else if (oldVal && oldVal !== '' && val === '') {
// close all open nodes
this.itemCashe.forEach((item, k) => { this.updateOpen(k, false) })
}
}
},
openKeys: {
immediate: true,
handler (val) {
if (val) {
this.openCache = val
}
}
},
value: {
immediate: true,
handler (val) {
if (val && Array.isArray(val) && val.length > 0) {
this.selectedItems = this.$props.multiple ? val : [val[0]]
} else {
this.selectedItems = []
}
}
}
},
methods: {
genListWithSlot (): VNode {
const slots = ['prepend-item', 'no-data', 'append-item']
.filter(slotName => this.$slots[slotName])
.map(slotName => this.$createElement('template', {
slot: slotName
}, this.$slots[slotName]))
return this.$createElement(VTreeSelectList, ({ ...this.listData }) as VNodeData, slots)
},
itemMatchFilter (item: any): boolean {
if (this.internalSearch && this.internalSearch != null) {
const comparedVal = getPropertyFromItem(item, this.$props.itemText)
const a = this.$props.filter(item, this.internalSearch, comparedVal) as boolean
return a
} else return true
},
getDescendantsKeys (key: string | number, descendants: (string|number)[] = []) {
const item = this.itemCashe.get(key)
if (item) {
const children = this.itemCashe.get(key).children
if (children) {
for (let i = 0; i < children.length; i++) {
const itemKey = getObjectValueByPath(children[i], this.$props.itemKey, [])
descendants.push(itemKey)
descendants = this.getDescendantsKeys(itemKey, descendants)
}
}
}
return descendants
},
getParentKeys (key: string | number):(string | number)[] {
const res:(string | number)[] = []
let k = key
while (this.parents.has(k)) {
k = this.parents.get(k)
if (k) {
res.unshift(k)
}
}
return res
},
updateSelected (key: string | number, isSelected: boolean) {
let recalculateKeys: (string|number)[] = []
const curItem = this.itemCashe.get(key)
if (curItem && this.selectedItems.indexOf(curItem) === -1 && isSelected) {
recalculateKeys.push(key)
if (this.$props.selectionType === 'leaf' && this.$props.allowSelectParents && this.$props.multiple) {
recalculateKeys = this.getDescendantsKeys(key, recalculateKeys)
}
} else if (curItem && this.selectedItems.indexOf(curItem) > -1 && !isSelected) {
recalculateKeys.push(key)
if (this.$props.selectionType === 'leaf' && this.$props.allowSelectParents && this.$props.multiple) {
recalculateKeys = this.getDescendantsKeys(key, recalculateKeys)
}
}
if (recalculateKeys.length === 0) return
// exclude disabled items
recalculateKeys = recalculateKeys.filter(v => {
const item = this.itemCashe.get(v)
return !getObjectValueByPath(item, this.$props.itemDisabled, false)
})
if (!isSelected) {
const a = this.selectedItems.filter((v) => {
const itemKey = getObjectValueByPath(v, this.$props.itemKey, [])
return !recalculateKeys.includes(itemKey)
})
this.selectItems(a)
} else {
const a = this.$props.multiple ? this.selectedItems : []
recalculateKeys.map(k => {
const item = this.itemCashe.get(k)
if (a.indexOf(item) === -1) {
a.push(item)
}
})
this.selectItems(a)
}
},
updateOpen (key: string | number, isOpen: boolean) {
if (isOpen && this.openCache.indexOf(key) === -1) {
this.openCache.push(key)
} else if (!isOpen && this.openCache.indexOf(key) > -1) {
this.openCache = this.openCache.filter(() => !this.openCache.includes(key))
}
if (this.$props.openOnePath && isOpen) {
/* close all anover nodes */
const parentKeys = this.getParentKeys(key)
this.openCache = this.openCache.filter((k) => {
return k === key || parentKeys.includes(k)
})
}
this.$emit('update:open', this.openCache)
},
buildTree (items: any[], parentkey?: string|number|undefined, forceInclude?: false): any[] {
const newItems: any[] = []
const pk = parentkey
items.map((item: any) => {
const localChildren = getObjectValueByPath(item, this.$props.itemChildren, [])
const itemKey = getObjectValueByPath(item, this.$props.itemKey, [])
this.itemCashe.set(itemKey, item)
const itemForceInclude = forceInclude || this.itemMatchFilter(item)
if (localChildren.length > 0) {
const newChildren = (this as any).buildTree(localChildren, itemKey, itemForceInclude)
if (newChildren.length > 0) {
const clone = Object.assign({}, item)
clone[this.$props.itemChildren] = newChildren
clone.hasChildren = true
newItems.push(clone)
this.parents.set(itemKey, pk)
}
} else {
this.parents.set(itemKey, pk)
if (itemForceInclude) {
const clone = Object.assign({}, item)
newItems.push(clone)
}
}
})
return newItems
},
textItem (itemkey: (string|number)): string {
let txt = ''
if (this.$props.showFullPath) {
const p = this.getParentKeys(itemkey)
p.map((v:(string|number)) => {
txt += (this as any).getText(this.itemCashe.get(v)) + this.$props.delimeter
})
}
txt += (this as any).getText(this.itemCashe.get(itemkey))
return txt
},
genChipSelection (item: object, index: number) {
const itemKey: (string|number|undefined) = getObjectValueByPath(item, this.$props.itemKey, undefined)
if (!itemKey) return
const isDisabled = false // (
// !(this as any).isInteractive ||
// (this as any).getDisabled(item)
// )
return this.$createElement(VChipA, {
staticClass: 'v-chip--select',
attrs: { tabindex: -1 },
key: itemKey,
props: {
close: this.$props.deletableChips && !isDisabled,
disabled: isDisabled,
inputValue: itemKey,
small: this.$props.smallChips,
value: this.textItem(itemKey)
},
on: {
click: (e: MouseEvent) => {
if (isDisabled) return
const pK = this.getParentKeys(itemKey)
this.$nextTick(() => {
this.openCache = pK
if (this.$data.isMenuActive) {
(this.$refs.menu as any).updateDimensions()
}
})
},
'click:close': () => {
this.selectedItems = this.selectedItems.filter(v => {
const vKey: (string|number|undefined) = getObjectValueByPath(v, this.$props.itemKey, undefined)
return vKey !== itemKey
})
this.$emit('input', this.selectedItems)
}
}
}, this.textItem(itemKey))
},
genCommaSelection (item: object, index: number, last: boolean) {
const itemKey: (string|number|undefined) = getObjectValueByPath(item, this.$props.itemKey, undefined)
if (!itemKey) return
const color = index === (this as any).selectedIndex && (this as any).computedColor
const isDisabled = false // (
// !(this as any).isInteractive ||
// (this as any).getDisabled(item)
// )
return this.$createElement('div', (this as any).setTextColor(color, {
staticClass: 'v-select__selection v-select__selection--comma',
class: {
'v-select__selection--disabled': isDisabled
},
on: {
click: (e: MouseEvent) => {
const pK = this.getParentKeys(itemKey)
this.$nextTick(() => {
this.openCache = pK
if (this.$data.isMenuActive) {
(this.$refs.menu as any).updateDimensions()
}
})
}
},
key: itemKey
}), `${this.textItem(itemKey)}${last ? '' : ', '}`)
}
}
})