TreeNode.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  2. import _extends from "@babel/runtime/helpers/esm/extends";
  3. import { createVNode as _createVNode } from "vue";
  4. var __rest = this && this.__rest || function (s, e) {
  5. var t = {};
  6. for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
  7. if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
  8. if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
  9. }
  10. return t;
  11. };
  12. import { useInjectKeysState, useInjectTreeContext } from './contextTypes';
  13. import Indent from './Indent';
  14. import { convertNodePropsToEventData, getTreeNodeProps } from './utils/treeUtil';
  15. import { computed, defineComponent, getCurrentInstance, onMounted, onUpdated, reactive, shallowRef } from 'vue';
  16. import { treeNodeProps } from './props';
  17. import classNames from '../_util/classNames';
  18. import { warning } from '../vc-util/warning';
  19. import pickAttrs from '../_util/pickAttrs';
  20. import eagerComputed from '../_util/eagerComputed';
  21. const ICON_OPEN = 'open';
  22. const ICON_CLOSE = 'close';
  23. const defaultTitle = '---';
  24. export default defineComponent({
  25. compatConfig: {
  26. MODE: 3
  27. },
  28. name: 'ATreeNode',
  29. inheritAttrs: false,
  30. props: treeNodeProps,
  31. isTreeNode: 1,
  32. setup(props, _ref) {
  33. let {
  34. attrs,
  35. slots,
  36. expose
  37. } = _ref;
  38. warning(!('slots' in props.data), `treeData slots is deprecated, please use ${Object.keys(props.data.slots || {}).map(key => '`v-slot:' + key + '` ')}instead`);
  39. const dragNodeHighlight = shallowRef(false);
  40. const context = useInjectTreeContext();
  41. const {
  42. expandedKeysSet,
  43. selectedKeysSet,
  44. loadedKeysSet,
  45. loadingKeysSet,
  46. checkedKeysSet,
  47. halfCheckedKeysSet
  48. } = useInjectKeysState();
  49. const {
  50. dragOverNodeKey,
  51. dropPosition,
  52. keyEntities
  53. } = context.value;
  54. const mergedTreeNodeProps = computed(() => {
  55. return getTreeNodeProps(props.eventKey, {
  56. expandedKeysSet: expandedKeysSet.value,
  57. selectedKeysSet: selectedKeysSet.value,
  58. loadedKeysSet: loadedKeysSet.value,
  59. loadingKeysSet: loadingKeysSet.value,
  60. checkedKeysSet: checkedKeysSet.value,
  61. halfCheckedKeysSet: halfCheckedKeysSet.value,
  62. dragOverNodeKey,
  63. dropPosition,
  64. keyEntities
  65. });
  66. });
  67. const expanded = eagerComputed(() => mergedTreeNodeProps.value.expanded);
  68. const selected = eagerComputed(() => mergedTreeNodeProps.value.selected);
  69. const checked = eagerComputed(() => mergedTreeNodeProps.value.checked);
  70. const loaded = eagerComputed(() => mergedTreeNodeProps.value.loaded);
  71. const loading = eagerComputed(() => mergedTreeNodeProps.value.loading);
  72. const halfChecked = eagerComputed(() => mergedTreeNodeProps.value.halfChecked);
  73. const dragOver = eagerComputed(() => mergedTreeNodeProps.value.dragOver);
  74. const dragOverGapTop = eagerComputed(() => mergedTreeNodeProps.value.dragOverGapTop);
  75. const dragOverGapBottom = eagerComputed(() => mergedTreeNodeProps.value.dragOverGapBottom);
  76. const pos = eagerComputed(() => mergedTreeNodeProps.value.pos);
  77. const selectHandle = shallowRef();
  78. const hasChildren = computed(() => {
  79. const {
  80. eventKey
  81. } = props;
  82. const {
  83. keyEntities
  84. } = context.value;
  85. const {
  86. children
  87. } = keyEntities[eventKey] || {};
  88. return !!(children || []).length;
  89. });
  90. const isLeaf = computed(() => {
  91. const {
  92. isLeaf
  93. } = props;
  94. const {
  95. loadData
  96. } = context.value;
  97. const has = hasChildren.value;
  98. if (isLeaf === false) {
  99. return false;
  100. }
  101. return isLeaf || !loadData && !has || loadData && loaded.value && !has;
  102. });
  103. const nodeState = computed(() => {
  104. if (isLeaf.value) {
  105. return null;
  106. }
  107. return expanded.value ? ICON_OPEN : ICON_CLOSE;
  108. });
  109. const isDisabled = computed(() => {
  110. const {
  111. disabled
  112. } = props;
  113. const {
  114. disabled: treeDisabled
  115. } = context.value;
  116. return !!(treeDisabled || disabled);
  117. });
  118. const isCheckable = computed(() => {
  119. const {
  120. checkable
  121. } = props;
  122. const {
  123. checkable: treeCheckable
  124. } = context.value;
  125. // Return false if tree or treeNode is not checkable
  126. if (!treeCheckable || checkable === false) return false;
  127. return treeCheckable;
  128. });
  129. const isSelectable = computed(() => {
  130. const {
  131. selectable
  132. } = props;
  133. const {
  134. selectable: treeSelectable
  135. } = context.value;
  136. // Ignore when selectable is undefined or null
  137. if (typeof selectable === 'boolean') {
  138. return selectable;
  139. }
  140. return treeSelectable;
  141. });
  142. const renderArgsData = computed(() => {
  143. const {
  144. data,
  145. active,
  146. checkable,
  147. disableCheckbox,
  148. disabled,
  149. selectable
  150. } = props;
  151. return _extends(_extends({
  152. active,
  153. checkable,
  154. disableCheckbox,
  155. disabled,
  156. selectable
  157. }, data), {
  158. dataRef: data,
  159. data,
  160. isLeaf: isLeaf.value,
  161. checked: checked.value,
  162. expanded: expanded.value,
  163. loading: loading.value,
  164. selected: selected.value,
  165. halfChecked: halfChecked.value
  166. });
  167. });
  168. const instance = getCurrentInstance();
  169. const eventData = computed(() => {
  170. const {
  171. eventKey
  172. } = props;
  173. const {
  174. keyEntities
  175. } = context.value;
  176. const {
  177. parent
  178. } = keyEntities[eventKey] || {};
  179. return _extends(_extends({}, convertNodePropsToEventData(_extends({}, props, mergedTreeNodeProps.value))), {
  180. parent
  181. });
  182. });
  183. const dragNodeEvent = reactive({
  184. eventData,
  185. eventKey: computed(() => props.eventKey),
  186. selectHandle,
  187. pos,
  188. key: instance.vnode.key
  189. });
  190. expose(dragNodeEvent);
  191. const onSelectorDoubleClick = e => {
  192. const {
  193. onNodeDoubleClick
  194. } = context.value;
  195. onNodeDoubleClick(e, eventData.value);
  196. };
  197. const onSelect = e => {
  198. if (isDisabled.value) return;
  199. const {
  200. onNodeSelect
  201. } = context.value;
  202. e.preventDefault();
  203. onNodeSelect(e, eventData.value);
  204. };
  205. const onCheck = e => {
  206. if (isDisabled.value) return;
  207. const {
  208. disableCheckbox
  209. } = props;
  210. const {
  211. onNodeCheck
  212. } = context.value;
  213. if (!isCheckable.value || disableCheckbox) return;
  214. e.preventDefault();
  215. const targetChecked = !checked.value;
  216. onNodeCheck(e, eventData.value, targetChecked);
  217. };
  218. const onSelectorClick = e => {
  219. // Click trigger before select/check operation
  220. const {
  221. onNodeClick
  222. } = context.value;
  223. onNodeClick(e, eventData.value);
  224. if (isSelectable.value) {
  225. onSelect(e);
  226. } else {
  227. onCheck(e);
  228. }
  229. };
  230. const onMouseEnter = e => {
  231. const {
  232. onNodeMouseEnter
  233. } = context.value;
  234. onNodeMouseEnter(e, eventData.value);
  235. };
  236. const onMouseLeave = e => {
  237. const {
  238. onNodeMouseLeave
  239. } = context.value;
  240. onNodeMouseLeave(e, eventData.value);
  241. };
  242. const onContextmenu = e => {
  243. const {
  244. onNodeContextMenu
  245. } = context.value;
  246. onNodeContextMenu(e, eventData.value);
  247. };
  248. const onDragStart = e => {
  249. const {
  250. onNodeDragStart
  251. } = context.value;
  252. e.stopPropagation();
  253. dragNodeHighlight.value = true;
  254. onNodeDragStart(e, dragNodeEvent);
  255. try {
  256. // ie throw error
  257. // firefox-need-it
  258. e.dataTransfer.setData('text/plain', '');
  259. } catch (error) {
  260. // empty
  261. }
  262. };
  263. const onDragEnter = e => {
  264. const {
  265. onNodeDragEnter
  266. } = context.value;
  267. e.preventDefault();
  268. e.stopPropagation();
  269. onNodeDragEnter(e, dragNodeEvent);
  270. };
  271. const onDragOver = e => {
  272. const {
  273. onNodeDragOver
  274. } = context.value;
  275. e.preventDefault();
  276. e.stopPropagation();
  277. onNodeDragOver(e, dragNodeEvent);
  278. };
  279. const onDragLeave = e => {
  280. const {
  281. onNodeDragLeave
  282. } = context.value;
  283. e.stopPropagation();
  284. onNodeDragLeave(e, dragNodeEvent);
  285. };
  286. const onDragEnd = e => {
  287. const {
  288. onNodeDragEnd
  289. } = context.value;
  290. e.stopPropagation();
  291. dragNodeHighlight.value = false;
  292. onNodeDragEnd(e, dragNodeEvent);
  293. };
  294. const onDrop = e => {
  295. const {
  296. onNodeDrop
  297. } = context.value;
  298. e.preventDefault();
  299. e.stopPropagation();
  300. dragNodeHighlight.value = false;
  301. onNodeDrop(e, dragNodeEvent);
  302. };
  303. // Disabled item still can be switch
  304. const onExpand = e => {
  305. const {
  306. onNodeExpand
  307. } = context.value;
  308. if (loading.value) return;
  309. onNodeExpand(e, eventData.value);
  310. };
  311. const isDraggable = () => {
  312. const {
  313. data
  314. } = props;
  315. const {
  316. draggable
  317. } = context.value;
  318. return !!(draggable && (!draggable.nodeDraggable || draggable.nodeDraggable(data)));
  319. };
  320. // ==================== Render: Drag Handler ====================
  321. const renderDragHandler = () => {
  322. const {
  323. draggable,
  324. prefixCls
  325. } = context.value;
  326. return draggable && (draggable === null || draggable === void 0 ? void 0 : draggable.icon) ? _createVNode("span", {
  327. "class": `${prefixCls}-draggable-icon`
  328. }, [draggable.icon]) : null;
  329. };
  330. const renderSwitcherIconDom = () => {
  331. var _a, _b, _c;
  332. const {
  333. switcherIcon: switcherIconFromProps = slots.switcherIcon || ((_a = context.value.slots) === null || _a === void 0 ? void 0 : _a[(_c = (_b = props.data) === null || _b === void 0 ? void 0 : _b.slots) === null || _c === void 0 ? void 0 : _c.switcherIcon])
  334. } = props;
  335. const {
  336. switcherIcon: switcherIconFromCtx
  337. } = context.value;
  338. const switcherIcon = switcherIconFromProps || switcherIconFromCtx;
  339. // if switcherIconDom is null, no render switcher span
  340. if (typeof switcherIcon === 'function') {
  341. return switcherIcon(renderArgsData.value);
  342. }
  343. return switcherIcon;
  344. };
  345. // Load data to avoid default expanded tree without data
  346. const syncLoadData = () => {
  347. //const { expanded, loading, loaded } = props;
  348. const {
  349. loadData,
  350. onNodeLoad
  351. } = context.value;
  352. if (loading.value) {
  353. return;
  354. }
  355. // read from state to avoid loadData at same time
  356. if (loadData && expanded.value && !isLeaf.value) {
  357. // We needn't reload data when has children in sync logic
  358. // It's only needed in node expanded
  359. if (!hasChildren.value && !loaded.value) {
  360. onNodeLoad(eventData.value);
  361. }
  362. }
  363. };
  364. onMounted(() => {
  365. syncLoadData();
  366. });
  367. onUpdated(() => {
  368. // https://github.com/vueComponent/ant-design-vue/issues/4835
  369. syncLoadData();
  370. });
  371. // Switcher
  372. const renderSwitcher = () => {
  373. const {
  374. prefixCls
  375. } = context.value;
  376. // if switcherIconDom is null, no render switcher span
  377. const switcherIconDom = renderSwitcherIconDom();
  378. if (isLeaf.value) {
  379. return switcherIconDom !== false ? _createVNode("span", {
  380. "class": classNames(`${prefixCls}-switcher`, `${prefixCls}-switcher-noop`)
  381. }, [switcherIconDom]) : null;
  382. }
  383. const switcherCls = classNames(`${prefixCls}-switcher`, `${prefixCls}-switcher_${expanded.value ? ICON_OPEN : ICON_CLOSE}`);
  384. return switcherIconDom !== false ? _createVNode("span", {
  385. "onClick": onExpand,
  386. "class": switcherCls
  387. }, [switcherIconDom]) : null;
  388. };
  389. // Checkbox
  390. const renderCheckbox = () => {
  391. var _a, _b;
  392. const {
  393. disableCheckbox
  394. } = props;
  395. const {
  396. prefixCls
  397. } = context.value;
  398. const disabled = isDisabled.value;
  399. const checkable = isCheckable.value;
  400. if (!checkable) return null;
  401. return _createVNode("span", {
  402. "class": classNames(`${prefixCls}-checkbox`, checked.value && `${prefixCls}-checkbox-checked`, !checked.value && halfChecked.value && `${prefixCls}-checkbox-indeterminate`, (disabled || disableCheckbox) && `${prefixCls}-checkbox-disabled`),
  403. "onClick": onCheck
  404. }, [(_b = (_a = context.value).customCheckable) === null || _b === void 0 ? void 0 : _b.call(_a)]);
  405. };
  406. const renderIcon = () => {
  407. const {
  408. prefixCls
  409. } = context.value;
  410. return _createVNode("span", {
  411. "class": classNames(`${prefixCls}-iconEle`, `${prefixCls}-icon__${nodeState.value || 'docu'}`, loading.value && `${prefixCls}-icon_loading`)
  412. }, null);
  413. };
  414. const renderDropIndicator = () => {
  415. const {
  416. disabled,
  417. eventKey
  418. } = props;
  419. const {
  420. draggable,
  421. dropLevelOffset,
  422. dropPosition,
  423. prefixCls,
  424. indent,
  425. dropIndicatorRender,
  426. dragOverNodeKey,
  427. direction
  428. } = context.value;
  429. const rootDraggable = draggable !== false;
  430. // allowDrop is calculated in Tree.tsx, there is no need for calc it here
  431. const showIndicator = !disabled && rootDraggable && dragOverNodeKey === eventKey;
  432. return showIndicator ? dropIndicatorRender({
  433. dropPosition,
  434. dropLevelOffset,
  435. indent,
  436. prefixCls,
  437. direction
  438. }) : null;
  439. };
  440. // Icon + Title
  441. const renderSelector = () => {
  442. var _a, _b, _c, _d, _e, _f;
  443. const {
  444. // title = slots.title ||
  445. // context.value.slots?.[props.data?.slots?.title] ||
  446. // context.value.slots?.title,
  447. // selected,
  448. icon = slots.icon,
  449. // loading,
  450. data
  451. } = props;
  452. const title = slots.title || ((_a = context.value.slots) === null || _a === void 0 ? void 0 : _a[(_c = (_b = props.data) === null || _b === void 0 ? void 0 : _b.slots) === null || _c === void 0 ? void 0 : _c.title]) || ((_d = context.value.slots) === null || _d === void 0 ? void 0 : _d.title) || props.title;
  453. const {
  454. prefixCls,
  455. showIcon,
  456. icon: treeIcon,
  457. loadData
  458. // slots: contextSlots,
  459. } = context.value;
  460. const disabled = isDisabled.value;
  461. const wrapClass = `${prefixCls}-node-content-wrapper`;
  462. // Icon - Still show loading icon when loading without showIcon
  463. let $icon;
  464. if (showIcon) {
  465. const currentIcon = icon || ((_e = context.value.slots) === null || _e === void 0 ? void 0 : _e[(_f = data === null || data === void 0 ? void 0 : data.slots) === null || _f === void 0 ? void 0 : _f.icon]) || treeIcon;
  466. $icon = currentIcon ? _createVNode("span", {
  467. "class": classNames(`${prefixCls}-iconEle`, `${prefixCls}-icon__customize`)
  468. }, [typeof currentIcon === 'function' ? currentIcon(renderArgsData.value) : currentIcon]) : renderIcon();
  469. } else if (loadData && loading.value) {
  470. $icon = renderIcon();
  471. }
  472. // Title
  473. let titleNode;
  474. if (typeof title === 'function') {
  475. titleNode = title(renderArgsData.value);
  476. // } else if (contextSlots.titleRender) {
  477. // titleNode = contextSlots.titleRender(renderArgsData.value);
  478. } else {
  479. titleNode = title;
  480. }
  481. titleNode = titleNode === undefined ? defaultTitle : titleNode;
  482. const $title = _createVNode("span", {
  483. "class": `${prefixCls}-title`
  484. }, [titleNode]);
  485. return _createVNode("span", {
  486. "ref": selectHandle,
  487. "title": typeof title === 'string' ? title : '',
  488. "class": classNames(`${wrapClass}`, `${wrapClass}-${nodeState.value || 'normal'}`, !disabled && (selected.value || dragNodeHighlight.value) && `${prefixCls}-node-selected`),
  489. "onMouseenter": onMouseEnter,
  490. "onMouseleave": onMouseLeave,
  491. "onContextmenu": onContextmenu,
  492. "onClick": onSelectorClick,
  493. "onDblclick": onSelectorDoubleClick
  494. }, [$icon, $title, renderDropIndicator()]);
  495. };
  496. return () => {
  497. const _a = _extends(_extends({}, props), attrs),
  498. {
  499. eventKey,
  500. isLeaf,
  501. isStart,
  502. isEnd,
  503. domRef,
  504. active,
  505. data,
  506. onMousemove,
  507. selectable
  508. } = _a,
  509. otherProps = __rest(_a, ["eventKey", "isLeaf", "isStart", "isEnd", "domRef", "active", "data", "onMousemove", "selectable"]);
  510. const {
  511. prefixCls,
  512. filterTreeNode,
  513. keyEntities,
  514. dropContainerKey,
  515. dropTargetKey,
  516. draggingNodeKey
  517. } = context.value;
  518. const disabled = isDisabled.value;
  519. const dataOrAriaAttributeProps = pickAttrs(otherProps, {
  520. aria: true,
  521. data: true
  522. });
  523. const {
  524. level
  525. } = keyEntities[eventKey] || {};
  526. const isEndNode = isEnd[isEnd.length - 1];
  527. const mergedDraggable = isDraggable();
  528. const draggableWithoutDisabled = !disabled && mergedDraggable;
  529. const dragging = draggingNodeKey === eventKey;
  530. const ariaSelected = selectable !== undefined ? {
  531. 'aria-selected': !!selectable
  532. } : undefined;
  533. // console.log(1);
  534. return _createVNode("div", _objectSpread(_objectSpread({
  535. "ref": domRef,
  536. "class": classNames(attrs.class, `${prefixCls}-treenode`, {
  537. [`${prefixCls}-treenode-disabled`]: disabled,
  538. [`${prefixCls}-treenode-switcher-${expanded.value ? 'open' : 'close'}`]: !isLeaf,
  539. [`${prefixCls}-treenode-checkbox-checked`]: checked.value,
  540. [`${prefixCls}-treenode-checkbox-indeterminate`]: halfChecked.value,
  541. [`${prefixCls}-treenode-selected`]: selected.value,
  542. [`${prefixCls}-treenode-loading`]: loading.value,
  543. [`${prefixCls}-treenode-active`]: active,
  544. [`${prefixCls}-treenode-leaf-last`]: isEndNode,
  545. [`${prefixCls}-treenode-draggable`]: draggableWithoutDisabled,
  546. dragging,
  547. 'drop-target': dropTargetKey === eventKey,
  548. 'drop-container': dropContainerKey === eventKey,
  549. 'drag-over': !disabled && dragOver.value,
  550. 'drag-over-gap-top': !disabled && dragOverGapTop.value,
  551. 'drag-over-gap-bottom': !disabled && dragOverGapBottom.value,
  552. 'filter-node': filterTreeNode && filterTreeNode(eventData.value)
  553. }),
  554. "style": attrs.style,
  555. "draggable": draggableWithoutDisabled,
  556. "aria-grabbed": dragging,
  557. "onDragstart": draggableWithoutDisabled ? onDragStart : undefined,
  558. "onDragenter": mergedDraggable ? onDragEnter : undefined,
  559. "onDragover": mergedDraggable ? onDragOver : undefined,
  560. "onDragleave": mergedDraggable ? onDragLeave : undefined,
  561. "onDrop": mergedDraggable ? onDrop : undefined,
  562. "onDragend": mergedDraggable ? onDragEnd : undefined,
  563. "onMousemove": onMousemove
  564. }, ariaSelected), dataOrAriaAttributeProps), [_createVNode(Indent, {
  565. "prefixCls": prefixCls,
  566. "level": level,
  567. "isStart": isStart,
  568. "isEnd": isEnd
  569. }, null), renderDragHandler(), renderSwitcher(), renderCheckbox(), renderSelector()]);
  570. };
  571. }
  572. });