123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- <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>
|