TreeNode.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. import _extends from "@babel/runtime/helpers/esm/extends";
  2. import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
  3. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  4. import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
  5. import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
  6. var _excluded = ["eventKey", "className", "style", "dragOver", "dragOverGapTop", "dragOverGapBottom", "isLeaf", "isStart", "isEnd", "expanded", "selected", "checked", "halfChecked", "loading", "domRef", "active", "data", "onMouseMove", "selectable"];
  7. import React from 'react';
  8. import classNames from 'classnames';
  9. import pickAttrs from "rc-util/es/pickAttrs";
  10. import { TreeContext, UnstableContext } from "./contextTypes";
  11. import Indent from "./Indent";
  12. import getEntity from "./utils/keyUtil";
  13. import { convertNodePropsToEventData } from "./utils/treeUtil";
  14. var ICON_OPEN = 'open';
  15. var ICON_CLOSE = 'close';
  16. var defaultTitle = '---';
  17. var TreeNode = function TreeNode(props) {
  18. var _unstableContext$node, _context$filterTreeNo, _classNames4;
  19. var eventKey = props.eventKey,
  20. className = props.className,
  21. style = props.style,
  22. dragOver = props.dragOver,
  23. dragOverGapTop = props.dragOverGapTop,
  24. dragOverGapBottom = props.dragOverGapBottom,
  25. isLeaf = props.isLeaf,
  26. isStart = props.isStart,
  27. isEnd = props.isEnd,
  28. expanded = props.expanded,
  29. selected = props.selected,
  30. checked = props.checked,
  31. halfChecked = props.halfChecked,
  32. loading = props.loading,
  33. domRef = props.domRef,
  34. active = props.active,
  35. data = props.data,
  36. onMouseMove = props.onMouseMove,
  37. selectable = props.selectable,
  38. otherProps = _objectWithoutProperties(props, _excluded);
  39. var context = React.useContext(TreeContext);
  40. var unstableContext = React.useContext(UnstableContext);
  41. var selectHandleRef = React.useRef(null);
  42. var _React$useState = React.useState(false),
  43. _React$useState2 = _slicedToArray(_React$useState, 2),
  44. dragNodeHighlight = _React$useState2[0],
  45. setDragNodeHighlight = _React$useState2[1];
  46. // ======= State: Disabled State =======
  47. var isDisabled = !!(context.disabled || props.disabled || (_unstableContext$node = unstableContext.nodeDisabled) !== null && _unstableContext$node !== void 0 && _unstableContext$node.call(unstableContext, data));
  48. var isCheckable = React.useMemo(function () {
  49. // Return false if tree or treeNode is not checkable
  50. if (!context.checkable || props.checkable === false) {
  51. return false;
  52. }
  53. return context.checkable;
  54. }, [context.checkable, props.checkable]);
  55. // ======= Event Handlers: Selection and Check =======
  56. var onSelect = function onSelect(e) {
  57. if (isDisabled) {
  58. return;
  59. }
  60. context.onNodeSelect(e, convertNodePropsToEventData(props));
  61. };
  62. var onCheck = function onCheck(e) {
  63. if (isDisabled) {
  64. return;
  65. }
  66. if (!isCheckable || props.disableCheckbox) {
  67. return;
  68. }
  69. context.onNodeCheck(e, convertNodePropsToEventData(props), !checked);
  70. };
  71. // ======= State: Selectable Check =======
  72. var isSelectable = React.useMemo(function () {
  73. // Ignore when selectable is undefined or null
  74. if (typeof selectable === 'boolean') {
  75. return selectable;
  76. }
  77. return context.selectable;
  78. }, [selectable, context.selectable]);
  79. var onSelectorClick = function onSelectorClick(e) {
  80. // Click trigger before select/check operation
  81. context.onNodeClick(e, convertNodePropsToEventData(props));
  82. if (isSelectable) {
  83. onSelect(e);
  84. } else {
  85. onCheck(e);
  86. }
  87. };
  88. var onSelectorDoubleClick = function onSelectorDoubleClick(e) {
  89. context.onNodeDoubleClick(e, convertNodePropsToEventData(props));
  90. };
  91. var onMouseEnter = function onMouseEnter(e) {
  92. context.onNodeMouseEnter(e, convertNodePropsToEventData(props));
  93. };
  94. var onMouseLeave = function onMouseLeave(e) {
  95. context.onNodeMouseLeave(e, convertNodePropsToEventData(props));
  96. };
  97. var onContextMenu = function onContextMenu(e) {
  98. context.onNodeContextMenu(e, convertNodePropsToEventData(props));
  99. };
  100. // ======= Drag: Drag Enabled =======
  101. var isDraggable = React.useMemo(function () {
  102. return !!(context.draggable && (!context.draggable.nodeDraggable || context.draggable.nodeDraggable(data)));
  103. }, [context.draggable, data]);
  104. // ======= Drag: Drag Event Handlers =======
  105. var onDragStart = function onDragStart(e) {
  106. e.stopPropagation();
  107. setDragNodeHighlight(true);
  108. context.onNodeDragStart(e, props);
  109. try {
  110. // ie throw error
  111. // firefox-need-it
  112. e.dataTransfer.setData('text/plain', '');
  113. } catch (_unused) {
  114. // empty
  115. }
  116. };
  117. var onDragEnter = function onDragEnter(e) {
  118. e.preventDefault();
  119. e.stopPropagation();
  120. context.onNodeDragEnter(e, props);
  121. };
  122. var onDragOver = function onDragOver(e) {
  123. e.preventDefault();
  124. e.stopPropagation();
  125. context.onNodeDragOver(e, props);
  126. };
  127. var onDragLeave = function onDragLeave(e) {
  128. e.stopPropagation();
  129. context.onNodeDragLeave(e, props);
  130. };
  131. var onDragEnd = function onDragEnd(e) {
  132. e.stopPropagation();
  133. setDragNodeHighlight(false);
  134. context.onNodeDragEnd(e, props);
  135. };
  136. var onDrop = function onDrop(e) {
  137. e.preventDefault();
  138. e.stopPropagation();
  139. setDragNodeHighlight(false);
  140. context.onNodeDrop(e, props);
  141. };
  142. // ======= Expand: Node Expansion =======
  143. var onExpand = function onExpand(e) {
  144. if (loading) {
  145. return;
  146. }
  147. context.onNodeExpand(e, convertNodePropsToEventData(props));
  148. };
  149. // ======= State: Has Children =======
  150. var hasChildren = React.useMemo(function () {
  151. var _ref = getEntity(context.keyEntities, eventKey) || {},
  152. children = _ref.children;
  153. return Boolean((children || []).length);
  154. }, [context.keyEntities, eventKey]);
  155. // ======= State: Leaf Check =======
  156. var memoizedIsLeaf = React.useMemo(function () {
  157. if (isLeaf === false) {
  158. return false;
  159. }
  160. return isLeaf || !context.loadData && !hasChildren || context.loadData && props.loaded && !hasChildren;
  161. }, [isLeaf, context.loadData, hasChildren, props.loaded]);
  162. // ============== Effect ==============
  163. React.useEffect(function () {
  164. // Load data to avoid default expanded tree without data
  165. if (loading) {
  166. return;
  167. }
  168. // read from state to avoid loadData at same time
  169. if (typeof context.loadData === 'function' && expanded && !memoizedIsLeaf && !props.loaded) {
  170. // We needn't reload data when has children in sync logic
  171. // It's only needed in node expanded
  172. context.onNodeLoad(convertNodePropsToEventData(props));
  173. }
  174. }, [loading, context.loadData, context.onNodeLoad, expanded, memoizedIsLeaf, props]);
  175. // ==================== Render: Drag Handler ====================
  176. var dragHandlerNode = React.useMemo(function () {
  177. var _context$draggable;
  178. if (!((_context$draggable = context.draggable) !== null && _context$draggable !== void 0 && _context$draggable.icon)) {
  179. return null;
  180. }
  181. return /*#__PURE__*/React.createElement("span", {
  182. className: "".concat(context.prefixCls, "-draggable-icon")
  183. }, context.draggable.icon);
  184. }, [context.draggable]);
  185. // ====================== Render: Switcher ======================
  186. var renderSwitcherIconDom = function renderSwitcherIconDom(isInternalLeaf) {
  187. var switcherIcon = props.switcherIcon || context.switcherIcon;
  188. // if switcherIconDom is null, no render switcher span
  189. if (typeof switcherIcon === 'function') {
  190. return switcherIcon(_objectSpread(_objectSpread({}, props), {}, {
  191. isLeaf: isInternalLeaf
  192. }));
  193. }
  194. return switcherIcon;
  195. };
  196. // Switcher
  197. var renderSwitcher = function renderSwitcher() {
  198. if (memoizedIsLeaf) {
  199. // if switcherIconDom is null, no render switcher span
  200. var _switcherIconDom = renderSwitcherIconDom(true);
  201. return _switcherIconDom !== false ? /*#__PURE__*/React.createElement("span", {
  202. className: classNames("".concat(context.prefixCls, "-switcher"), "".concat(context.prefixCls, "-switcher-noop"))
  203. }, _switcherIconDom) : null;
  204. }
  205. var switcherIconDom = renderSwitcherIconDom(false);
  206. return switcherIconDom !== false ? /*#__PURE__*/React.createElement("span", {
  207. onClick: onExpand,
  208. className: classNames("".concat(context.prefixCls, "-switcher"), "".concat(context.prefixCls, "-switcher_").concat(expanded ? ICON_OPEN : ICON_CLOSE))
  209. }, switcherIconDom) : null;
  210. };
  211. // ====================== Checkbox ======================
  212. var checkboxNode = React.useMemo(function () {
  213. if (!isCheckable) {
  214. return null;
  215. }
  216. // [Legacy] Custom element should be separate with `checkable` in future
  217. var $custom = typeof isCheckable !== 'boolean' ? isCheckable : null;
  218. return /*#__PURE__*/React.createElement("span", {
  219. className: classNames("".concat(context.prefixCls, "-checkbox"), _defineProperty(_defineProperty(_defineProperty({}, "".concat(context.prefixCls, "-checkbox-checked"), checked), "".concat(context.prefixCls, "-checkbox-indeterminate"), !checked && halfChecked), "".concat(context.prefixCls, "-checkbox-disabled"), isDisabled || props.disableCheckbox)),
  220. onClick: onCheck,
  221. role: "checkbox",
  222. "aria-checked": halfChecked ? 'mixed' : checked,
  223. "aria-disabled": isDisabled || props.disableCheckbox,
  224. "aria-label": "Select ".concat(typeof props.title === 'string' ? props.title : 'tree node')
  225. }, $custom);
  226. }, [isCheckable, checked, halfChecked, isDisabled, props.disableCheckbox, props.title]);
  227. // ============== State: Node State (Open/Close) ==============
  228. var nodeState = React.useMemo(function () {
  229. if (memoizedIsLeaf) {
  230. return null;
  231. }
  232. return expanded ? ICON_OPEN : ICON_CLOSE;
  233. }, [memoizedIsLeaf, expanded]);
  234. // ==================== Render: Title + Icon ====================
  235. var iconNode = React.useMemo(function () {
  236. return /*#__PURE__*/React.createElement("span", {
  237. className: classNames("".concat(context.prefixCls, "-iconEle"), "".concat(context.prefixCls, "-icon__").concat(nodeState || 'docu'), _defineProperty({}, "".concat(context.prefixCls, "-icon_loading"), loading))
  238. });
  239. }, [context.prefixCls, nodeState, loading]);
  240. // =================== Drop Indicator ===================
  241. var dropIndicatorNode = React.useMemo(function () {
  242. var rootDraggable = Boolean(context.draggable);
  243. // allowDrop is calculated in Tree.tsx, there is no need for calc it here
  244. var showIndicator = !props.disabled && rootDraggable && context.dragOverNodeKey === eventKey;
  245. if (!showIndicator) {
  246. return null;
  247. }
  248. return context.dropIndicatorRender({
  249. dropPosition: context.dropPosition,
  250. dropLevelOffset: context.dropLevelOffset,
  251. indent: context.indent,
  252. prefixCls: context.prefixCls,
  253. direction: context.direction
  254. });
  255. }, [context.dropPosition, context.dropLevelOffset, context.indent, context.prefixCls, context.direction, context.draggable, context.dragOverNodeKey, context.dropIndicatorRender]);
  256. // Icon + Title
  257. var selectorNode = React.useMemo(function () {
  258. var _props$title = props.title,
  259. title = _props$title === void 0 ? defaultTitle : _props$title;
  260. var wrapClass = "".concat(context.prefixCls, "-node-content-wrapper");
  261. // Icon - Still show loading icon when loading without showIcon
  262. var $icon;
  263. if (context.showIcon) {
  264. var currentIcon = props.icon || context.icon;
  265. $icon = currentIcon ? /*#__PURE__*/React.createElement("span", {
  266. className: classNames("".concat(context.prefixCls, "-iconEle"), "".concat(context.prefixCls, "-icon__customize"))
  267. }, typeof currentIcon === 'function' ? currentIcon(props) : currentIcon) : iconNode;
  268. } else if (context.loadData && loading) {
  269. $icon = iconNode;
  270. }
  271. // Title
  272. var titleNode;
  273. if (typeof title === 'function') {
  274. titleNode = title(data);
  275. } else if (context.titleRender) {
  276. titleNode = context.titleRender(data);
  277. } else {
  278. titleNode = title;
  279. }
  280. return /*#__PURE__*/React.createElement("span", {
  281. ref: selectHandleRef,
  282. title: typeof title === 'string' ? title : '',
  283. className: classNames(wrapClass, "".concat(wrapClass, "-").concat(nodeState || 'normal'), _defineProperty({}, "".concat(context.prefixCls, "-node-selected"), !isDisabled && (selected || dragNodeHighlight))),
  284. onMouseEnter: onMouseEnter,
  285. onMouseLeave: onMouseLeave,
  286. onContextMenu: onContextMenu,
  287. onClick: onSelectorClick,
  288. onDoubleClick: onSelectorDoubleClick
  289. }, $icon, /*#__PURE__*/React.createElement("span", {
  290. className: "".concat(context.prefixCls, "-title")
  291. }, titleNode), dropIndicatorNode);
  292. }, [context.prefixCls, context.showIcon, props, context.icon, iconNode, context.titleRender, data, nodeState, onMouseEnter, onMouseLeave, onContextMenu, onSelectorClick, onSelectorDoubleClick]);
  293. var dataOrAriaAttributeProps = pickAttrs(otherProps, {
  294. aria: true,
  295. data: true
  296. });
  297. var _ref2 = getEntity(context.keyEntities, eventKey) || {},
  298. level = _ref2.level;
  299. var isEndNode = isEnd[isEnd.length - 1];
  300. var draggableWithoutDisabled = !isDisabled && isDraggable;
  301. var dragging = context.draggingNodeKey === eventKey;
  302. var ariaSelected = selectable !== undefined ? {
  303. 'aria-selected': !!selectable
  304. } : undefined;
  305. return /*#__PURE__*/React.createElement("div", _extends({
  306. ref: domRef,
  307. role: "treeitem",
  308. "aria-expanded": isLeaf ? undefined : expanded,
  309. className: classNames(className, "".concat(context.prefixCls, "-treenode"), (_classNames4 = {}, _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_classNames4, "".concat(context.prefixCls, "-treenode-disabled"), isDisabled), "".concat(context.prefixCls, "-treenode-switcher-").concat(expanded ? 'open' : 'close'), !isLeaf), "".concat(context.prefixCls, "-treenode-checkbox-checked"), checked), "".concat(context.prefixCls, "-treenode-checkbox-indeterminate"), halfChecked), "".concat(context.prefixCls, "-treenode-selected"), selected), "".concat(context.prefixCls, "-treenode-loading"), loading), "".concat(context.prefixCls, "-treenode-active"), active), "".concat(context.prefixCls, "-treenode-leaf-last"), isEndNode), "".concat(context.prefixCls, "-treenode-draggable"), isDraggable), "dragging", dragging), _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_classNames4, 'drop-target', context.dropTargetKey === eventKey), 'drop-container', context.dropContainerKey === eventKey), 'drag-over', !isDisabled && dragOver), 'drag-over-gap-top', !isDisabled && dragOverGapTop), 'drag-over-gap-bottom', !isDisabled && dragOverGapBottom), 'filter-node', (_context$filterTreeNo = context.filterTreeNode) === null || _context$filterTreeNo === void 0 ? void 0 : _context$filterTreeNo.call(context, convertNodePropsToEventData(props))), "".concat(context.prefixCls, "-treenode-leaf"), memoizedIsLeaf))),
  310. style: style
  311. // Draggable config
  312. ,
  313. draggable: draggableWithoutDisabled,
  314. onDragStart: draggableWithoutDisabled ? onDragStart : undefined
  315. // Drop config
  316. ,
  317. onDragEnter: isDraggable ? onDragEnter : undefined,
  318. onDragOver: isDraggable ? onDragOver : undefined,
  319. onDragLeave: isDraggable ? onDragLeave : undefined,
  320. onDrop: isDraggable ? onDrop : undefined,
  321. onDragEnd: isDraggable ? onDragEnd : undefined,
  322. onMouseMove: onMouseMove
  323. }, ariaSelected, dataOrAriaAttributeProps), /*#__PURE__*/React.createElement(Indent, {
  324. prefixCls: context.prefixCls,
  325. level: level,
  326. isStart: isStart,
  327. isEnd: isEnd
  328. }), dragHandlerNode, renderSwitcher(), checkboxNode, selectorNode);
  329. };
  330. TreeNode.isTreeNode = 1;
  331. if (process.env.NODE_ENV !== 'production') {
  332. TreeNode.displayName = 'TreeNode';
  333. }
  334. export default TreeNode;