035a69559cb6c920605c0c33996eb1005233f9aa17cff63df479a7b16084e3b3d56e02137b4bac0a4164e1b137c60b672b0f5a4e8dca6ba7dc536b280b2c45-exec 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. <script>
  2. import { UNCHECKED, INDETERMINATE, CHECKED } from '../constants'
  3. import { onLeftClick } from '../utils'
  4. import Tip from './Tip'
  5. import ArrowIcon from './icons/Arrow'
  6. let arrowPlaceholder, checkMark, minusMark
  7. const Option = {
  8. name: 'vue-treeselect--option',
  9. inject: [ 'instance' ],
  10. props: {
  11. node: {
  12. type: Object,
  13. required: true,
  14. },
  15. },
  16. computed: {
  17. shouldExpand() {
  18. const { instance, node } = this
  19. return node.isBranch && instance.shouldExpand(node)
  20. },
  21. shouldShow() {
  22. const { instance, node } = this
  23. return instance.shouldShowOptionInMenu(node)
  24. },
  25. },
  26. methods: {
  27. renderOption() {
  28. const { instance, node } = this
  29. const optionClass = {
  30. 'vue-treeselect__option': true,
  31. 'vue-treeselect__option--disabled': node.isDisabled,
  32. 'vue-treeselect__option--selected': instance.isSelected(node),
  33. 'vue-treeselect__option--highlight': node.isHighlighted,
  34. 'vue-treeselect__option--matched': instance.localSearch.active && node.isMatched,
  35. 'vue-treeselect__option--hide': !this.shouldShow,
  36. }
  37. return (
  38. <div class={optionClass} onMouseenter={this.handleMouseEnterOption} data-id={node.id}>
  39. {this.renderArrow()}
  40. {this.renderLabelContainer([
  41. this.renderCheckboxContainer([
  42. this.renderCheckbox(),
  43. ]),
  44. this.renderLabel(),
  45. ])}
  46. </div>
  47. )
  48. },
  49. renderSubOptionsList() {
  50. if (!this.shouldExpand) return null
  51. return (
  52. <div class="vue-treeselect__list">
  53. {this.renderSubOptions()}
  54. {this.renderNoChildrenTip()}
  55. {this.renderLoadingChildrenTip()}
  56. {this.renderLoadingChildrenErrorTip()}
  57. </div>
  58. )
  59. },
  60. renderArrow() {
  61. const { instance, node } = this
  62. if (instance.shouldFlattenOptions && this.shouldShow) return null
  63. if (node.isBranch) {
  64. const transitionProps = {
  65. props: {
  66. name: 'vue-treeselect__option-arrow--prepare',
  67. appear: true,
  68. },
  69. }
  70. const arrowClass = {
  71. 'vue-treeselect__option-arrow': true,
  72. 'vue-treeselect__option-arrow--rotated': this.shouldExpand,
  73. }
  74. return (
  75. <div class="vue-treeselect__option-arrow-container" onMousedown={this.handleMouseDownOnArrow}>
  76. <transition {...transitionProps}>
  77. <ArrowIcon class={arrowClass} />
  78. </transition>
  79. </div>
  80. )
  81. }
  82. // For leaf nodes, we render a placeholder to keep its label aligned to
  83. // branch nodes. Unless there is no branch nodes at all (a normal
  84. // non-tree select).
  85. if (/*node.isLeaf && */instance.hasBranchNodes) {
  86. if (!arrowPlaceholder) arrowPlaceholder = (
  87. <div class="vue-treeselect__option-arrow-placeholder">&nbsp;</div>
  88. )
  89. return arrowPlaceholder
  90. }
  91. return null
  92. },
  93. renderLabelContainer(children) {
  94. return (
  95. <div class="vue-treeselect__label-container" onMousedown={this.handleMouseDownOnLabelContainer}>
  96. {children}
  97. </div>
  98. )
  99. },
  100. renderCheckboxContainer(children) {
  101. const { instance, node } = this
  102. if (instance.single) return null
  103. if (instance.disableBranchNodes && node.isBranch) return null
  104. return (
  105. <div class="vue-treeselect__checkbox-container">
  106. {children}
  107. </div>
  108. )
  109. },
  110. renderCheckbox() {
  111. const { instance, node } = this
  112. const checkedState = instance.forest.checkedStateMap[node.id]
  113. const checkboxClass = {
  114. 'vue-treeselect__checkbox': true,
  115. 'vue-treeselect__checkbox--checked': checkedState === CHECKED,
  116. 'vue-treeselect__checkbox--indeterminate': checkedState === INDETERMINATE,
  117. 'vue-treeselect__checkbox--unchecked': checkedState === UNCHECKED,
  118. 'vue-treeselect__checkbox--disabled': node.isDisabled,
  119. }
  120. if (!checkMark) checkMark = (
  121. <span class="vue-treeselect__check-mark" />
  122. )
  123. if (!minusMark) minusMark = (
  124. <span class="vue-treeselect__minus-mark" />
  125. )
  126. return (
  127. <span class={checkboxClass}>
  128. {checkMark}
  129. {minusMark}
  130. </span>
  131. )
  132. },
  133. renderLabel() {
  134. const { instance, node } = this
  135. const shouldShowCount = (
  136. node.isBranch && (instance.localSearch.active
  137. ? instance.showCountOnSearchComputed
  138. : instance.showCount
  139. )
  140. )
  141. const count = shouldShowCount
  142. ? instance.localSearch.active
  143. ? instance.localSearch.countMap[node.id][instance.showCountOf]
  144. : node.count[instance.showCountOf]
  145. : NaN
  146. const labelClassName = 'vue-treeselect__label'
  147. const countClassName = 'vue-treeselect__count'
  148. const customLabelRenderer = instance.$scopedSlots['option-label']
  149. if (customLabelRenderer) return customLabelRenderer({
  150. node,
  151. shouldShowCount,
  152. count,
  153. labelClassName,
  154. countClassName,
  155. })
  156. return (
  157. <label class={labelClassName}>
  158. {node.label}
  159. {shouldShowCount && (
  160. <span class={countClassName}>({count})</span>
  161. )}
  162. </label>
  163. )
  164. },
  165. renderSubOptions() {
  166. const { node } = this
  167. if (!node.childrenStates.isLoaded) return null
  168. return node.children.map(childNode => (
  169. <Option node={childNode} key={childNode.id} />
  170. ))
  171. },
  172. renderNoChildrenTip() {
  173. const { instance, node } = this
  174. if (!node.childrenStates.isLoaded || node.children.length) return null
  175. return (
  176. <Tip type="no-children" icon="warning">{ instance.noChildrenText }</Tip>
  177. )
  178. },
  179. renderLoadingChildrenTip() {
  180. const { instance, node } = this
  181. if (!node.childrenStates.isLoading) return null
  182. return (
  183. <Tip type="loading" icon="loader">{ instance.loadingText }</Tip>
  184. )
  185. },
  186. renderLoadingChildrenErrorTip() {
  187. const { instance, node } = this
  188. if (!node.childrenStates.loadingError) return null
  189. return (
  190. <Tip type="error" icon="error">
  191. { node.childrenStates.loadingError }
  192. <a class="vue-treeselect__retry" title={instance.retryTitle} onMousedown={this.handleMouseDownOnRetry}>
  193. { instance.retryText }
  194. </a>
  195. </Tip>
  196. )
  197. },
  198. handleMouseEnterOption(evt) {
  199. const { instance, node } = this
  200. // Equivalent to `self` modifier.
  201. // istanbul ignore next
  202. if (evt.target !== evt.currentTarget) return
  203. instance.setCurrentHighlightedOption(node, false)
  204. },
  205. handleMouseDownOnArrow: onLeftClick(function handleMouseDownOnOptionArrow() {
  206. const { instance, node } = this
  207. instance.toggleExpanded(node)
  208. }),
  209. handleMouseDownOnLabelContainer: onLeftClick(function handleMouseDownOnLabelContainer() {
  210. const { instance, node } = this
  211. if (node.isBranch && instance.disableBranchNodes) {
  212. instance.toggleExpanded(node)
  213. } else {
  214. instance.select(node)
  215. }
  216. }),
  217. handleMouseDownOnRetry: onLeftClick(function handleMouseDownOnRetry() {
  218. const { instance, node } = this
  219. instance.loadChildrenOptions(node)
  220. }),
  221. },
  222. render() {
  223. const { node } = this
  224. const indentLevel = this.instance.shouldFlattenOptions ? 0 : node.level
  225. const listItemClass = {
  226. 'vue-treeselect__list-item': true,
  227. [`vue-treeselect__indent-level-${indentLevel}`]: true,
  228. }
  229. const transitionProps = {
  230. props: {
  231. name: 'vue-treeselect__list--transition',
  232. },
  233. }
  234. return (
  235. <div class={listItemClass}>
  236. {this.renderOption()}
  237. {node.isBranch && (
  238. <transition {...transitionProps}>
  239. {this.renderSubOptionsList()}
  240. </transition>
  241. )}
  242. </div>
  243. )
  244. },
  245. }
  246. // eslint-disable-next-line vue/require-direct-export
  247. export default Option
  248. </script>