<script>
|
import { UNCHECKED, INDETERMINATE, CHECKED } from '../constants'
|
import { onLeftClick } from '../utils'
|
import Tip from './Tip'
|
import ArrowIcon from './icons/Arrow'
|
|
let arrowPlaceholder, checkMark, minusMark
|
|
const Option = {
|
name: 'vue-treeselect--option',
|
inject: [ 'instance' ],
|
|
props: {
|
node: {
|
type: Object,
|
required: true,
|
},
|
},
|
|
computed: {
|
shouldExpand() {
|
const { instance, node } = this
|
|
return node.isBranch && instance.shouldExpand(node)
|
},
|
|
shouldShow() {
|
const { instance, node } = this
|
|
return instance.shouldShowOptionInMenu(node)
|
},
|
},
|
|
methods: {
|
renderOption() {
|
const { instance, node } = this
|
const optionClass = {
|
'vue-treeselect__option': true,
|
'vue-treeselect__option--disabled': node.isDisabled,
|
'vue-treeselect__option--selected': instance.isSelected(node),
|
'vue-treeselect__option--highlight': node.isHighlighted,
|
'vue-treeselect__option--matched': instance.localSearch.active && node.isMatched,
|
'vue-treeselect__option--hide': !this.shouldShow,
|
}
|
|
return (
|
<div class={optionClass} onMouseenter={this.handleMouseEnterOption} data-id={node.id}>
|
{this.renderArrow()}
|
{this.renderLabelContainer([
|
this.renderCheckboxContainer([
|
this.renderCheckbox(),
|
]),
|
this.renderLabel(),
|
])}
|
</div>
|
)
|
},
|
|
renderSubOptionsList() {
|
if (!this.shouldExpand) return null
|
|
return (
|
<div class="vue-treeselect__list">
|
{this.renderSubOptions()}
|
{this.renderNoChildrenTip()}
|
{this.renderLoadingChildrenTip()}
|
{this.renderLoadingChildrenErrorTip()}
|
</div>
|
)
|
},
|
|
renderArrow() {
|
const { instance, node } = this
|
|
if (instance.shouldFlattenOptions && this.shouldShow) return null
|
|
if (node.isBranch) {
|
const transitionProps = {
|
props: {
|
name: 'vue-treeselect__option-arrow--prepare',
|
appear: true,
|
},
|
}
|
const arrowClass = {
|
'vue-treeselect__option-arrow': true,
|
'vue-treeselect__option-arrow--rotated': this.shouldExpand,
|
}
|
|
return (
|
<div class="vue-treeselect__option-arrow-container" onMousedown={this.handleMouseDownOnArrow}>
|
<transition {...transitionProps}>
|
<ArrowIcon class={arrowClass} />
|
</transition>
|
</div>
|
)
|
}
|
|
// For leaf nodes, we render a placeholder to keep its label aligned to
|
// branch nodes. Unless there is no branch nodes at all (a normal
|
// non-tree select).
|
if (/*node.isLeaf && */instance.hasBranchNodes) {
|
if (!arrowPlaceholder) arrowPlaceholder = (
|
<div class="vue-treeselect__option-arrow-placeholder"> </div>
|
)
|
|
return arrowPlaceholder
|
}
|
|
return null
|
},
|
|
renderLabelContainer(children) {
|
return (
|
<div class="vue-treeselect__label-container" onMousedown={this.handleMouseDownOnLabelContainer}>
|
{children}
|
</div>
|
)
|
},
|
|
renderCheckboxContainer(children) {
|
const { instance, node } = this
|
|
if (instance.single) return null
|
if (instance.disableBranchNodes && node.isBranch) return null
|
|
return (
|
<div class="vue-treeselect__checkbox-container">
|
{children}
|
</div>
|
)
|
},
|
|
renderCheckbox() {
|
const { instance, node } = this
|
const checkedState = instance.forest.checkedStateMap[node.id]
|
const checkboxClass = {
|
'vue-treeselect__checkbox': true,
|
'vue-treeselect__checkbox--checked': checkedState === CHECKED,
|
'vue-treeselect__checkbox--indeterminate': checkedState === INDETERMINATE,
|
'vue-treeselect__checkbox--unchecked': checkedState === UNCHECKED,
|
'vue-treeselect__checkbox--disabled': node.isDisabled,
|
}
|
|
if (!checkMark) checkMark = (
|
<span class="vue-treeselect__check-mark" />
|
)
|
if (!minusMark) minusMark = (
|
<span class="vue-treeselect__minus-mark" />
|
)
|
|
return (
|
<span class={checkboxClass}>
|
{checkMark}
|
{minusMark}
|
</span>
|
)
|
},
|
|
renderLabel() {
|
const { instance, node } = this
|
const shouldShowCount = (
|
node.isBranch && (instance.localSearch.active
|
? instance.showCountOnSearchComputed
|
: instance.showCount
|
)
|
)
|
const count = shouldShowCount
|
? instance.localSearch.active
|
? instance.localSearch.countMap[node.id][instance.showCountOf]
|
: node.count[instance.showCountOf]
|
: NaN
|
const labelClassName = 'vue-treeselect__label'
|
const countClassName = 'vue-treeselect__count'
|
const customLabelRenderer = instance.$scopedSlots['option-label']
|
|
if (customLabelRenderer) return customLabelRenderer({
|
node,
|
shouldShowCount,
|
count,
|
labelClassName,
|
countClassName,
|
})
|
|
return (
|
<label class={labelClassName}>
|
{node.label}
|
{shouldShowCount && (
|
<span class={countClassName}>({count})</span>
|
)}
|
</label>
|
)
|
},
|
|
renderSubOptions() {
|
const { node } = this
|
|
if (!node.childrenStates.isLoaded) return null
|
|
return node.children.map(childNode => (
|
<Option node={childNode} key={childNode.id} />
|
))
|
},
|
|
renderNoChildrenTip() {
|
const { instance, node } = this
|
|
if (!node.childrenStates.isLoaded || node.children.length) return null
|
|
return (
|
<Tip type="no-children" icon="warning">{ instance.noChildrenText }</Tip>
|
)
|
},
|
|
renderLoadingChildrenTip() {
|
const { instance, node } = this
|
|
if (!node.childrenStates.isLoading) return null
|
|
return (
|
<Tip type="loading" icon="loader">{ instance.loadingText }</Tip>
|
)
|
},
|
|
renderLoadingChildrenErrorTip() {
|
const { instance, node } = this
|
|
if (!node.childrenStates.loadingError) return null
|
|
return (
|
<Tip type="error" icon="error">
|
{ node.childrenStates.loadingError }
|
<a class="vue-treeselect__retry" title={instance.retryTitle} onMousedown={this.handleMouseDownOnRetry}>
|
{ instance.retryText }
|
</a>
|
</Tip>
|
)
|
},
|
|
handleMouseEnterOption(evt) {
|
const { instance, node } = this
|
|
// Equivalent to `self` modifier.
|
// istanbul ignore next
|
if (evt.target !== evt.currentTarget) return
|
|
instance.setCurrentHighlightedOption(node, false)
|
},
|
|
handleMouseDownOnArrow: onLeftClick(function handleMouseDownOnOptionArrow() {
|
const { instance, node } = this
|
|
instance.toggleExpanded(node)
|
}),
|
|
handleMouseDownOnLabelContainer: onLeftClick(function handleMouseDownOnLabelContainer() {
|
const { instance, node } = this
|
|
if (node.isBranch && instance.disableBranchNodes) {
|
instance.toggleExpanded(node)
|
} else {
|
instance.select(node)
|
}
|
}),
|
|
handleMouseDownOnRetry: onLeftClick(function handleMouseDownOnRetry() {
|
const { instance, node } = this
|
|
instance.loadChildrenOptions(node)
|
}),
|
},
|
|
render() {
|
const { node } = this
|
const indentLevel = this.instance.shouldFlattenOptions ? 0 : node.level
|
const listItemClass = {
|
'vue-treeselect__list-item': true,
|
[`vue-treeselect__indent-level-${indentLevel}`]: true,
|
}
|
const transitionProps = {
|
props: {
|
name: 'vue-treeselect__list--transition',
|
},
|
}
|
|
return (
|
<div class={listItemClass}>
|
{this.renderOption()}
|
{node.isBranch && (
|
<transition {...transitionProps}>
|
{this.renderSubOptionsList()}
|
</transition>
|
)}
|
</div>
|
)
|
},
|
}
|
|
// eslint-disable-next-line vue/require-direct-export
|
export default Option
|
</script>
|