@rc-component/tree
Version:
tree ui component for react
1,302 lines (1,261 loc) • 39.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var _util = require("@rc-component/util");
var React = _interopRequireWildcard(require("react"));
var _contextTypes = require("./contextTypes");
var _DropIndicator = _interopRequireDefault(require("./DropIndicator"));
var _NodeList = _interopRequireWildcard(require("./NodeList"));
var _TreeNode = _interopRequireDefault(require("./TreeNode"));
var _util2 = require("./util");
var _conductUtil = require("./utils/conductUtil");
var _keyUtil = _interopRequireDefault(require("./utils/keyUtil"));
var _treeUtil = require("./utils/treeUtil");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } // TODO: Fully accessibility support
// Reference: https://www.w3.org/WAI/ARIA/apg/patterns/treeview
const MAX_RETRY_TIMES = 10;
class Tree extends React.Component {
static defaultProps = {
prefixCls: 'rc-tree',
showLine: false,
showIcon: true,
selectable: true,
multiple: false,
checkable: false,
disabled: false,
checkStrictly: false,
draggable: false,
defaultExpandParent: true,
autoExpandParent: false,
defaultExpandAll: false,
defaultExpandedKeys: [],
defaultCheckedKeys: [],
defaultSelectedKeys: [],
dropIndicatorRender: _DropIndicator.default,
allowDrop: () => true,
expandAction: false
};
static TreeNode = _TreeNode.default;
destroyed = false;
delayedDragEnterLogic;
loadingRetryTimes = {};
state = {
keyEntities: {},
indent: null,
selectedKeys: [],
checkedKeys: [],
halfCheckedKeys: [],
loadedKeys: [],
loadingKeys: [],
expandedKeys: [],
draggingNodeKey: null,
dragChildrenKeys: [],
// dropTargetKey is the key of abstract-drop-node
// the abstract-drop-node is the real drop node when drag and drop
// not the DOM drag over node
dropTargetKey: null,
dropPosition: null,
// the drop position of abstract-drop-node, inside 0, top -1, bottom 1
dropContainerKey: null,
// the container key of abstract-drop-node if dropPosition is -1 or 1
dropLevelOffset: null,
// the drop level offset of abstract-drag-over-node
dropTargetPos: null,
// the pos of abstract-drop-node
dropAllowed: true,
// if drop to abstract-drop-node is allowed
// the abstract-drag-over-node
// if mouse is on the bottom of top dom node or no the top of the bottom dom node
// abstract-drag-over-node is the top node
dragOverNodeKey: null,
treeData: [],
flattenNodes: [],
activeKey: null,
listChanging: false,
prevProps: null,
fieldNames: (0, _treeUtil.fillFieldNames)()
};
dragStartMousePosition = null;
dragNodeProps = null;
currentMouseOverDroppableNodeKey = null;
focusedByMouse = false;
listRef = /*#__PURE__*/React.createRef();
componentDidMount() {
this.destroyed = false;
this.onUpdated();
window.addEventListener('mouseup', this.onGlobalMouseUp);
}
componentDidUpdate() {
this.onUpdated();
}
onUpdated() {
const {
activeKey,
itemScrollOffset = 0
} = this.props;
if (activeKey !== undefined && activeKey !== this.state.activeKey) {
this.setState({
activeKey
});
if (activeKey !== null) {
this.scrollTo({
key: activeKey,
offset: itemScrollOffset
});
}
}
}
componentWillUnmount() {
window.removeEventListener('dragend', this.onWindowDragEnd);
window.removeEventListener('mouseup', this.onGlobalMouseUp);
this.destroyed = true;
}
static getDerivedStateFromProps(props, prevState) {
const {
prevProps
} = prevState;
const newState = {
prevProps: props
};
function needSync(name) {
return !prevProps && props.hasOwnProperty(name) || prevProps && prevProps[name] !== props[name];
}
// ================== Tree Node ==================
let treeData;
// fieldNames
let {
fieldNames
} = prevState;
if (needSync('fieldNames')) {
fieldNames = (0, _treeUtil.fillFieldNames)(props.fieldNames);
newState.fieldNames = fieldNames;
}
// Check if `treeData` or `children` changed and save into the state.
if (needSync('treeData')) {
({
treeData
} = props);
} else if (needSync('children')) {
(0, _util.warning)(false, '`children` of Tree is deprecated. Please use `treeData` instead.');
treeData = (0, _treeUtil.convertTreeToData)(props.children);
}
// Save flatten nodes info and convert `treeData` into keyEntities
if (treeData) {
newState.treeData = treeData;
const entitiesMap = (0, _treeUtil.convertDataToEntities)(treeData, {
fieldNames
});
newState.keyEntities = {
[_NodeList.MOTION_KEY]: _NodeList.MotionEntity,
...entitiesMap.keyEntities
};
// Warning if treeNode not provide key
if (process.env.NODE_ENV !== 'production') {
(0, _treeUtil.warningWithoutKey)(treeData, fieldNames);
}
}
const keyEntities = newState.keyEntities || prevState.keyEntities;
// ================ expandedKeys =================
if (needSync('expandedKeys') || prevProps && needSync('autoExpandParent')) {
newState.expandedKeys = props.autoExpandParent || !prevProps && props.defaultExpandParent ? (0, _util2.conductExpandParent)(props.expandedKeys, keyEntities) : props.expandedKeys;
} else if (!prevProps && props.defaultExpandAll) {
const cloneKeyEntities = {
...keyEntities
};
delete cloneKeyEntities[_NodeList.MOTION_KEY];
// Only take the key who has the children to enhance the performance
const nextExpandedKeys = [];
Object.keys(cloneKeyEntities).forEach(key => {
const entity = cloneKeyEntities[key];
if (entity.children && entity.children.length) {
nextExpandedKeys.push(entity.key);
}
});
newState.expandedKeys = nextExpandedKeys;
} else if (!prevProps && props.defaultExpandedKeys) {
newState.expandedKeys = props.autoExpandParent || props.defaultExpandParent ? (0, _util2.conductExpandParent)(props.defaultExpandedKeys, keyEntities) : props.defaultExpandedKeys;
}
if (!newState.expandedKeys) {
delete newState.expandedKeys;
}
// ================ flattenNodes =================
if (treeData || newState.expandedKeys) {
const flattenNodes = (0, _treeUtil.flattenTreeData)(treeData || prevState.treeData, newState.expandedKeys || prevState.expandedKeys, fieldNames);
newState.flattenNodes = flattenNodes;
}
// ================ selectedKeys =================
if (props.selectable) {
if (needSync('selectedKeys')) {
newState.selectedKeys = (0, _util2.calcSelectedKeys)(props.selectedKeys, props);
} else if (!prevProps && props.defaultSelectedKeys) {
newState.selectedKeys = (0, _util2.calcSelectedKeys)(props.defaultSelectedKeys, props);
}
}
// ================= checkedKeys =================
if (props.checkable) {
let checkedKeyEntity;
if (needSync('checkedKeys')) {
checkedKeyEntity = (0, _util2.parseCheckedKeys)(props.checkedKeys) || {};
} else if (!prevProps && props.defaultCheckedKeys) {
checkedKeyEntity = (0, _util2.parseCheckedKeys)(props.defaultCheckedKeys) || {};
} else if (treeData) {
// If `treeData` changed, we also need check it
checkedKeyEntity = (0, _util2.parseCheckedKeys)(props.checkedKeys) || {
checkedKeys: prevState.checkedKeys,
halfCheckedKeys: prevState.halfCheckedKeys
};
}
if (checkedKeyEntity) {
let {
checkedKeys = [],
halfCheckedKeys = []
} = checkedKeyEntity;
if (!props.checkStrictly) {
const conductKeys = (0, _conductUtil.conductCheck)(checkedKeys, true, keyEntities);
({
checkedKeys,
halfCheckedKeys
} = conductKeys);
}
newState.checkedKeys = checkedKeys;
newState.halfCheckedKeys = halfCheckedKeys;
}
}
// ================= loadedKeys ==================
if (needSync('loadedKeys')) {
newState.loadedKeys = props.loadedKeys;
}
return newState;
}
onNodeDragStart = (event, nodeProps) => {
const {
expandedKeys,
keyEntities
} = this.state;
const {
onDragStart
} = this.props;
const {
eventKey
} = nodeProps;
this.dragNodeProps = nodeProps;
this.dragStartMousePosition = {
x: event.clientX,
y: event.clientY
};
const newExpandedKeys = (0, _util2.arrDel)(expandedKeys, eventKey);
this.setState({
draggingNodeKey: eventKey,
dragChildrenKeys: (0, _util2.getDragChildrenKeys)(eventKey, keyEntities),
indent: this.listRef.current.getIndentWidth()
});
this.setExpandedKeys(newExpandedKeys);
window.addEventListener('dragend', this.onWindowDragEnd);
onDragStart?.({
event,
node: (0, _treeUtil.convertNodePropsToEventData)(nodeProps)
});
};
/**
* [Legacy] Select handler is smaller than node,
* so that this will trigger when drag enter node or select handler.
* This is a little tricky if customize css without padding.
* Better for use mouse move event to refresh drag state.
* But let's just keep it to avoid event trigger logic change.
*/
onNodeDragEnter = (event, nodeProps) => {
const {
expandedKeys,
keyEntities,
dragChildrenKeys,
flattenNodes,
indent
} = this.state;
const {
onDragEnter,
onExpand,
allowDrop,
direction
} = this.props;
const {
pos,
eventKey
} = nodeProps;
// record the key of node which is latest entered, used in dragleave event.
if (this.currentMouseOverDroppableNodeKey !== eventKey) {
this.currentMouseOverDroppableNodeKey = eventKey;
}
if (!this.dragNodeProps) {
this.resetDragState();
return;
}
const {
dropPosition,
dropLevelOffset,
dropTargetKey,
dropContainerKey,
dropTargetPos,
dropAllowed,
dragOverNodeKey
} = (0, _util2.calcDropPosition)(event, this.dragNodeProps, nodeProps, indent, this.dragStartMousePosition, allowDrop, flattenNodes, keyEntities, expandedKeys, direction);
if (
// don't allow drop inside its children
dragChildrenKeys.includes(dropTargetKey) ||
// don't allow drop when drop is not allowed caculated by calcDropPosition
!dropAllowed) {
this.resetDragState();
return;
}
// Side effect for delay drag
if (!this.delayedDragEnterLogic) {
this.delayedDragEnterLogic = {};
}
Object.keys(this.delayedDragEnterLogic).forEach(key => {
clearTimeout(this.delayedDragEnterLogic[key]);
});
if (this.dragNodeProps.eventKey !== nodeProps.eventKey) {
// hoist expand logic here
// since if logic is on the bottom
// it will be blocked by abstract dragover node check
// => if you dragenter from top, you mouse will still be consider as in the top node
event.persist();
this.delayedDragEnterLogic[pos] = window.setTimeout(() => {
if (this.state.draggingNodeKey === null) {
return;
}
let newExpandedKeys = [...expandedKeys];
const entity = (0, _keyUtil.default)(keyEntities, nodeProps.eventKey);
if (entity && (entity.children || []).length) {
newExpandedKeys = (0, _util2.arrAdd)(expandedKeys, nodeProps.eventKey);
}
if (!this.props.hasOwnProperty('expandedKeys')) {
this.setExpandedKeys(newExpandedKeys);
}
onExpand?.(newExpandedKeys, {
node: (0, _treeUtil.convertNodePropsToEventData)(nodeProps),
expanded: true,
nativeEvent: event.nativeEvent
});
}, 800);
}
// Skip if drag node is self
if (this.dragNodeProps.eventKey === dropTargetKey && dropLevelOffset === 0) {
this.resetDragState();
return;
}
// Update drag over node and drag state
this.setState({
dragOverNodeKey,
dropPosition,
dropLevelOffset,
dropTargetKey,
dropContainerKey,
dropTargetPos,
dropAllowed
});
onDragEnter?.({
event,
node: (0, _treeUtil.convertNodePropsToEventData)(nodeProps),
expandedKeys
});
};
onNodeDragOver = (event, nodeProps) => {
const {
dragChildrenKeys,
flattenNodes,
keyEntities,
expandedKeys,
indent
} = this.state;
const {
onDragOver,
allowDrop,
direction
} = this.props;
if (!this.dragNodeProps) {
return;
}
const {
dropPosition,
dropLevelOffset,
dropTargetKey,
dropContainerKey,
dropTargetPos,
dropAllowed,
dragOverNodeKey
} = (0, _util2.calcDropPosition)(event, this.dragNodeProps, nodeProps, indent, this.dragStartMousePosition, allowDrop, flattenNodes, keyEntities, expandedKeys, direction);
if (dragChildrenKeys.includes(dropTargetKey) || !dropAllowed) {
// don't allow drop inside its children
// don't allow drop when drop is not allowed calculated by calcDropPosition
return;
}
// Update drag position
if (this.dragNodeProps.eventKey === dropTargetKey && dropLevelOffset === 0) {
if (!(this.state.dropPosition === null && this.state.dropLevelOffset === null && this.state.dropTargetKey === null && this.state.dropContainerKey === null && this.state.dropTargetPos === null && this.state.dropAllowed === false && this.state.dragOverNodeKey === null)) {
this.resetDragState();
}
} else if (!(dropPosition === this.state.dropPosition && dropLevelOffset === this.state.dropLevelOffset && dropTargetKey === this.state.dropTargetKey && dropContainerKey === this.state.dropContainerKey && dropTargetPos === this.state.dropTargetPos && dropAllowed === this.state.dropAllowed && dragOverNodeKey === this.state.dragOverNodeKey)) {
this.setState({
dropPosition,
dropLevelOffset,
dropTargetKey,
dropContainerKey,
dropTargetPos,
dropAllowed,
dragOverNodeKey
});
}
onDragOver?.({
event,
node: (0, _treeUtil.convertNodePropsToEventData)(nodeProps)
});
};
onNodeDragLeave = (event, nodeProps) => {
// if it is outside the droppable area
// currentMouseOverDroppableNodeKey will be updated in dragenter event when into another droppable receiver.
if (this.currentMouseOverDroppableNodeKey === nodeProps.eventKey && !event.currentTarget.contains(event.relatedTarget)) {
this.resetDragState();
this.currentMouseOverDroppableNodeKey = null;
}
const {
onDragLeave
} = this.props;
onDragLeave?.({
event,
node: (0, _treeUtil.convertNodePropsToEventData)(nodeProps)
});
};
// since stopPropagation() is called in treeNode
// if onWindowDrag is called, whice means state is keeped, drag state should be cleared
onWindowDragEnd = event => {
this.onNodeDragEnd(event, null, true);
window.removeEventListener('dragend', this.onWindowDragEnd);
};
// if onNodeDragEnd is called, onWindowDragEnd won't be called since stopPropagation() is called
onNodeDragEnd = (event, nodeProps) => {
const {
onDragEnd
} = this.props;
this.setState({
dragOverNodeKey: null
});
this.cleanDragState();
onDragEnd?.({
event,
node: (0, _treeUtil.convertNodePropsToEventData)(nodeProps)
});
this.dragNodeProps = null;
window.removeEventListener('dragend', this.onWindowDragEnd);
};
onNodeDrop = (event, _, outsideTree = false) => {
const {
dragChildrenKeys,
dropPosition,
dropTargetKey,
dropTargetPos,
dropAllowed
} = this.state;
if (!dropAllowed) {
return;
}
const {
onDrop
} = this.props;
this.setState({
dragOverNodeKey: null
});
this.cleanDragState();
if (dropTargetKey === null) return;
const abstractDropNodeProps = {
...(0, _treeUtil.getTreeNodeProps)(dropTargetKey, this.getTreeNodeRequiredProps()),
active: this.getActiveItem()?.key === dropTargetKey,
data: (0, _keyUtil.default)(this.state.keyEntities, dropTargetKey).node
};
const dropToChild = dragChildrenKeys.includes(dropTargetKey);
(0, _util.warning)(!dropToChild, "Can not drop to dragNode's children node. This is a bug of rc-tree. Please report an issue.");
const posArr = (0, _util2.posToArr)(dropTargetPos);
const dropResult = {
event,
node: (0, _treeUtil.convertNodePropsToEventData)(abstractDropNodeProps),
dragNode: this.dragNodeProps ? (0, _treeUtil.convertNodePropsToEventData)(this.dragNodeProps) : null,
dragNodesKeys: [this.dragNodeProps.eventKey].concat(dragChildrenKeys),
dropToGap: dropPosition !== 0,
dropPosition: dropPosition + Number(posArr[posArr.length - 1])
};
if (!outsideTree) {
onDrop?.(dropResult);
}
this.dragNodeProps = null;
};
resetDragState() {
this.setState({
dragOverNodeKey: null,
dropPosition: null,
dropLevelOffset: null,
dropTargetKey: null,
dropContainerKey: null,
dropTargetPos: null,
dropAllowed: false
});
}
cleanDragState = () => {
const {
draggingNodeKey
} = this.state;
if (draggingNodeKey !== null) {
this.setState({
draggingNodeKey: null,
dropPosition: null,
dropContainerKey: null,
dropTargetKey: null,
dropLevelOffset: null,
dropAllowed: true,
dragOverNodeKey: null
});
}
this.dragStartMousePosition = null;
this.currentMouseOverDroppableNodeKey = null;
};
triggerExpandActionExpand = (e, treeNode) => {
const {
expandedKeys,
flattenNodes
} = this.state;
const {
expanded,
key,
isLeaf
} = treeNode;
if (isLeaf || e.shiftKey || e.metaKey || e.ctrlKey) {
return;
}
const node = flattenNodes.filter(nodeItem => nodeItem.key === key)[0];
const eventNode = (0, _treeUtil.convertNodePropsToEventData)({
...(0, _treeUtil.getTreeNodeProps)(key, this.getTreeNodeRequiredProps()),
data: node.data
});
this.setExpandedKeys(expanded ? (0, _util2.arrDel)(expandedKeys, key) : (0, _util2.arrAdd)(expandedKeys, key));
this.onNodeExpand(e, eventNode);
};
onNodeClick = (e, treeNode) => {
const {
onClick,
expandAction
} = this.props;
if (expandAction === 'click') {
this.triggerExpandActionExpand(e, treeNode);
}
onClick?.(e, treeNode);
};
onNodeDoubleClick = (e, treeNode) => {
const {
onDoubleClick,
expandAction
} = this.props;
if (expandAction === 'doubleClick') {
this.triggerExpandActionExpand(e, treeNode);
}
onDoubleClick?.(e, treeNode);
};
onNodeSelect = (e, treeNode) => {
let {
selectedKeys
} = this.state;
const {
keyEntities,
fieldNames
} = this.state;
const {
onSelect,
multiple
} = this.props;
const {
selected
} = treeNode;
const key = treeNode[fieldNames.key];
const targetSelected = !selected;
// Update selected keys
if (!targetSelected) {
selectedKeys = (0, _util2.arrDel)(selectedKeys, key);
} else if (!multiple) {
selectedKeys = [key];
} else {
selectedKeys = (0, _util2.arrAdd)(selectedKeys, key);
}
// [Legacy] Not found related usage in doc or upper libs
const selectedNodes = selectedKeys.map(selectedKey => {
const entity = (0, _keyUtil.default)(keyEntities, selectedKey);
return entity ? entity.node : null;
}).filter(Boolean);
this.setUncontrolledState({
selectedKeys
});
onSelect?.(selectedKeys, {
event: 'select',
selected: targetSelected,
node: treeNode,
selectedNodes,
nativeEvent: e.nativeEvent
});
};
onNodeCheck = (e, treeNode, checked) => {
const {
keyEntities,
checkedKeys: oriCheckedKeys,
halfCheckedKeys: oriHalfCheckedKeys
} = this.state;
const {
checkStrictly,
onCheck
} = this.props;
const {
key
} = treeNode;
// Prepare trigger arguments
let checkedObj;
const eventObj = {
event: 'check',
node: treeNode,
checked,
nativeEvent: e.nativeEvent
};
if (checkStrictly) {
const checkedKeys = checked ? (0, _util2.arrAdd)(oriCheckedKeys, key) : (0, _util2.arrDel)(oriCheckedKeys, key);
const halfCheckedKeys = (0, _util2.arrDel)(oriHalfCheckedKeys, key);
checkedObj = {
checked: checkedKeys,
halfChecked: halfCheckedKeys
};
eventObj.checkedNodes = checkedKeys.map(checkedKey => (0, _keyUtil.default)(keyEntities, checkedKey)).filter(Boolean).map(entity => entity.node);
this.setUncontrolledState({
checkedKeys
});
} else {
// Always fill first
let {
checkedKeys,
halfCheckedKeys
} = (0, _conductUtil.conductCheck)([...oriCheckedKeys, key], true, keyEntities);
// If remove, we do it again to correction
if (!checked) {
const keySet = new Set(checkedKeys);
keySet.delete(key);
({
checkedKeys,
halfCheckedKeys
} = (0, _conductUtil.conductCheck)(Array.from(keySet), {
checked: false,
halfCheckedKeys
}, keyEntities));
}
checkedObj = checkedKeys;
// [Legacy] This is used for `rc-tree-select`
eventObj.checkedNodes = [];
eventObj.checkedNodesPositions = [];
eventObj.halfCheckedKeys = halfCheckedKeys;
checkedKeys.forEach(checkedKey => {
const entity = (0, _keyUtil.default)(keyEntities, checkedKey);
if (!entity) return;
const {
node,
pos
} = entity;
eventObj.checkedNodes.push(node);
eventObj.checkedNodesPositions.push({
node,
pos
});
});
this.setUncontrolledState({
checkedKeys
}, false, {
halfCheckedKeys
});
}
onCheck?.(checkedObj, eventObj);
};
onNodeLoad = treeNode => {
const {
key
} = treeNode;
const {
keyEntities
} = this.state;
// Skip if has children already
const entity = (0, _keyUtil.default)(keyEntities, key);
if (entity?.children?.length) {
return;
}
const loadPromise = new Promise((resolve, reject) => {
// We need to get the latest state of loading/loaded keys
this.setState(({
loadedKeys = [],
loadingKeys = []
}) => {
const {
loadData,
onLoad
} = this.props;
if (!loadData || loadedKeys.includes(key) || loadingKeys.includes(key)) {
return null;
}
// Process load data
const promise = loadData(treeNode);
promise.then(() => {
const {
loadedKeys: currentLoadedKeys
} = this.state;
const newLoadedKeys = (0, _util2.arrAdd)(currentLoadedKeys, key);
// onLoad should trigger before internal setState to avoid `loadData` trigger twice.
// https://github.com/ant-design/ant-design/issues/12464
onLoad?.(newLoadedKeys, {
event: 'load',
node: treeNode
});
this.setUncontrolledState({
loadedKeys: newLoadedKeys
});
this.setState(prevState => ({
loadingKeys: (0, _util2.arrDel)(prevState.loadingKeys, key)
}));
resolve();
}).catch(e => {
this.setState(prevState => ({
loadingKeys: (0, _util2.arrDel)(prevState.loadingKeys, key)
}));
// If exceed max retry times, we give up retry
this.loadingRetryTimes[key] = (this.loadingRetryTimes[key] || 0) + 1;
if (this.loadingRetryTimes[key] >= MAX_RETRY_TIMES) {
const {
loadedKeys: currentLoadedKeys
} = this.state;
(0, _util.warning)(false, 'Retry for `loadData` many times but still failed. No more retry.');
this.setUncontrolledState({
loadedKeys: (0, _util2.arrAdd)(currentLoadedKeys, key)
});
resolve();
}
reject(e);
});
return {
loadingKeys: (0, _util2.arrAdd)(loadingKeys, key)
};
});
});
// Not care warning if we ignore this
loadPromise.catch(() => {});
return loadPromise;
};
onNodeMouseEnter = (event, node) => {
const {
onMouseEnter
} = this.props;
onMouseEnter?.({
event,
node
});
};
onNodeMouseLeave = (event, node) => {
const {
onMouseLeave
} = this.props;
onMouseLeave?.({
event,
node
});
};
onNodeContextMenu = (event, node) => {
const {
onRightClick
} = this.props;
if (onRightClick) {
event.preventDefault();
onRightClick({
event,
node
});
}
};
onMouseDown = event => {
this.focusedByMouse = true;
const {
onMouseDown
} = this.props;
onMouseDown?.(event);
};
onGlobalMouseUp = () => {
this.focusedByMouse = false;
};
onFocus = (...args) => {
const {
onFocus,
disabled
} = this.props;
const {
activeKey,
selectedKeys,
flattenNodes
} = this.state;
if (!this.focusedByMouse && !disabled && activeKey === null) {
const visibleSelectedKey = selectedKeys.find(key => {
return flattenNodes.some(nodeItem => nodeItem.key === key);
});
if (visibleSelectedKey !== undefined) {
this.onActiveChange(visibleSelectedKey);
} else {
this.onActiveChange(flattenNodes?.[0]?.key || null);
}
}
onFocus?.(...args);
};
onBlur = (...args) => {
const {
onBlur
} = this.props;
this.onActiveChange(null);
onBlur?.(...args);
};
getTreeNodeRequiredProps = () => {
const {
expandedKeys,
selectedKeys,
loadedKeys,
loadingKeys,
checkedKeys,
halfCheckedKeys,
dragOverNodeKey,
dropPosition,
keyEntities
} = this.state;
return {
expandedKeys: expandedKeys || [],
selectedKeys: selectedKeys || [],
loadedKeys: loadedKeys || [],
loadingKeys: loadingKeys || [],
checkedKeys: checkedKeys || [],
halfCheckedKeys: halfCheckedKeys || [],
dragOverNodeKey,
dropPosition,
keyEntities: keyEntities
};
};
// =========================== Expanded ===========================
/** Set uncontrolled `expandedKeys`. This will also auto update `flattenNodes`. */
setExpandedKeys = expandedKeys => {
const {
treeData,
fieldNames
} = this.state;
const flattenNodes = (0, _treeUtil.flattenTreeData)(treeData, expandedKeys, fieldNames);
this.setUncontrolledState({
expandedKeys,
flattenNodes
}, true);
};
onNodeExpand = (e, treeNode) => {
let {
expandedKeys
} = this.state;
const {
listChanging,
fieldNames
} = this.state;
const {
onExpand,
loadData
} = this.props;
const {
expanded
} = treeNode;
const key = treeNode[fieldNames.key];
// Do nothing when motion is in progress
if (listChanging) {
return;
}
// Update selected keys
const certain = expandedKeys.includes(key);
const targetExpanded = !expanded;
(0, _util.warning)(expanded && certain || !expanded && !certain, 'Expand state not sync with index check');
expandedKeys = targetExpanded ? (0, _util2.arrAdd)(expandedKeys, key) : (0, _util2.arrDel)(expandedKeys, key);
this.setExpandedKeys(expandedKeys);
onExpand?.(expandedKeys, {
node: treeNode,
expanded: targetExpanded,
nativeEvent: e.nativeEvent
});
// Async Load data
if (targetExpanded && loadData) {
const loadPromise = this.onNodeLoad(treeNode);
if (loadPromise) {
loadPromise.then(() => {
// [Legacy] Refresh logic
const newFlattenTreeData = (0, _treeUtil.flattenTreeData)(this.state.treeData, expandedKeys, fieldNames);
this.setUncontrolledState({
flattenNodes: newFlattenTreeData
});
}).catch(() => {
const {
expandedKeys: currentExpandedKeys
} = this.state;
const expandedKeysToRestore = (0, _util2.arrDel)(currentExpandedKeys, key);
this.setExpandedKeys(expandedKeysToRestore);
});
}
}
};
onListChangeStart = () => {
this.setUncontrolledState({
listChanging: true
});
};
onListChangeEnd = () => {
setTimeout(() => {
this.setUncontrolledState({
listChanging: false
});
});
};
// =========================== Keyboard ===========================
onActiveChange = newActiveKey => {
const {
activeKey
} = this.state;
const {
onActiveChange,
itemScrollOffset = 0
} = this.props;
if (activeKey === newActiveKey) {
return;
}
this.setState({
activeKey: newActiveKey
});
if (newActiveKey !== null) {
this.scrollTo({
key: newActiveKey,
offset: itemScrollOffset
});
}
onActiveChange?.(newActiveKey);
};
getActiveItem = () => {
const {
activeKey,
flattenNodes
} = this.state;
if (activeKey === null) {
return null;
}
return flattenNodes.find(({
key
}) => key === activeKey) || null;
};
offsetActiveKey = offset => {
const {
flattenNodes,
activeKey
} = this.state;
let index = flattenNodes.findIndex(({
key
}) => key === activeKey);
// Align with index
if (index === -1 && offset < 0) {
index = flattenNodes.length;
}
index = (index + offset + flattenNodes.length) % flattenNodes.length;
const item = flattenNodes[index];
if (item) {
const {
key
} = item;
this.onActiveChange(key);
} else {
this.onActiveChange(null);
}
};
onKeyDown = event => {
const {
activeKey,
expandedKeys,
checkedKeys,
flattenNodes,
keyEntities
} = this.state;
const {
onKeyDown,
checkable,
selectable,
disabled,
loadData
} = this.props;
if (disabled) {
return;
}
// >>>>>>>>>> Direction
switch (event.key) {
case 'ArrowUp':
{
this.offsetActiveKey(-1);
event.preventDefault();
break;
}
case 'ArrowDown':
{
this.offsetActiveKey(1);
event.preventDefault();
break;
}
case 'Home':
{
this.onActiveChange(flattenNodes?.[0]?.key);
event.preventDefault();
break;
}
case 'End':
{
this.onActiveChange(flattenNodes?.[flattenNodes.length - 1]?.key);
event.preventDefault();
break;
}
}
// >>>>>>>>>> Expand & Selection
const activeItem = this.getActiveItem();
if (activeItem && activeItem.data) {
const treeNodeRequiredProps = this.getTreeNodeRequiredProps();
const eventNode = (0, _treeUtil.convertNodePropsToEventData)({
...(0, _treeUtil.getTreeNodeProps)(activeKey, treeNodeRequiredProps),
data: activeItem.data,
active: true
});
const entity = (0, _keyUtil.default)(keyEntities, activeKey);
const hasChildren = !!entity?.children?.length;
const expandable = !(0, _treeUtil.isLeafNode)(activeItem.data.isLeaf, loadData, hasChildren, eventNode.loaded);
const canCheck = checkable && !eventNode.disabled && eventNode.checkable !== false && !eventNode.disableCheckbox;
const canSelect = !checkable && selectable && !eventNode.disabled && eventNode.selectable !== false;
switch (event.key) {
// >>> Expand
case 'ArrowLeft':
{
// Collapse if possible
if (expandable && expandedKeys.includes(activeKey)) {
this.onNodeExpand({}, eventNode);
} else if (activeItem.parent) {
this.onActiveChange(activeItem.parent.key);
}
event.preventDefault();
break;
}
case 'ArrowRight':
{
// Expand if possible
if (expandable && !expandedKeys.includes(activeKey)) {
this.onNodeExpand({}, eventNode);
} else if (activeItem.children && activeItem.children.length) {
this.onActiveChange(activeItem.children[0].key);
}
event.preventDefault();
break;
}
case 'Enter':
{
if (expandable) {
event.preventDefault();
this.onNodeExpand({}, eventNode);
} else if (canCheck) {
if (!checkedKeys.includes(activeKey)) {
event.preventDefault();
this.onNodeCheck({}, eventNode, true);
}
} else if (canSelect && !eventNode.selected) {
event.preventDefault();
this.onNodeSelect({}, eventNode);
}
break;
}
case ' ':
{
if (canCheck) {
event.preventDefault();
this.onNodeCheck({}, eventNode, !checkedKeys.includes(activeKey));
} else if (canSelect) {
event.preventDefault();
this.onNodeSelect({}, eventNode);
}
break;
}
}
}
onKeyDown?.(event);
};
/**
* Only update the value which is not in props
*/
setUncontrolledState = (state, atomic = false, forceState = null) => {
if (!this.destroyed) {
let needSync = false;
let allPassed = true;
const newState = {};
Object.keys(state).forEach(name => {
if (this.props.hasOwnProperty(name)) {
allPassed = false;
return;
}
needSync = true;
newState[name] = state[name];
});
if (needSync && (!atomic || allPassed)) {
this.setState({
...newState,
...forceState
});
}
}
};
scrollTo = scroll => {
this.listRef.current.scrollTo(scroll);
};
render() {
const {
flattenNodes,
keyEntities,
draggingNodeKey,
dropLevelOffset,
dropContainerKey,
dropTargetKey,
dropPosition,
dragOverNodeKey,
indent
} = this.state;
const {
prefixCls,
className,
style,
styles,
classNames: treeClassNames,
showLine,
focusable,
tabIndex = 0,
selectable,
showIcon,
icon,
switcherIcon,
draggable,
checkable,
checkStrictly,
disabled,
motion,
loadData,
filterTreeNode,
height,
itemHeight,
scrollWidth,
virtual,
titleRender,
dropIndicatorRender,
onContextMenu,
onScroll,
direction,
rootClassName,
rootStyle
} = this.props;
const domProps = (0, _util.pickAttrs)(this.props, {
aria: true,
data: true
});
// It's better move to hooks but we just simply keep here
let draggableConfig;
if (draggable) {
if (typeof draggable === 'object') {
draggableConfig = draggable;
} else if (typeof draggable === 'function') {
draggableConfig = {
nodeDraggable: draggable
};
} else {
draggableConfig = {};
}
}
const contextValue = {
styles,
classNames: treeClassNames,
prefixCls,
selectable,
showIcon,
icon,
switcherIcon,
draggable: draggableConfig,
draggingNodeKey,
checkable,
checkStrictly,
disabled,
keyEntities,
dropLevelOffset,
dropContainerKey,
dropTargetKey,
dropPosition,
dragOverNodeKey,
indent,
direction,
dropIndicatorRender,
loadData,
filterTreeNode,
titleRender,
onNodeClick: this.onNodeClick,
onNodeDoubleClick: this.onNodeDoubleClick,
onNodeExpand: this.onNodeExpand,
onNodeSelect: this.onNodeSelect,
onNodeCheck: this.onNodeCheck,
onNodeLoad: this.onNodeLoad,
onNodeMouseEnter: this.onNodeMouseEnter,
onNodeMouseLeave: this.onNodeMouseLeave,
onNodeContextMenu: this.onNodeContextMenu,
onNodeDragStart: this.onNodeDragStart,
onNodeDragEnter: this.onNodeDragEnter,
onNodeDragOver: this.onNodeDragOver,
onNodeDragLeave: this.onNodeDragLeave,
onNodeDragEnd: this.onNodeDragEnd,
onNodeDrop: this.onNodeDrop
};
return /*#__PURE__*/React.createElement(_contextTypes.TreeContext.Provider, {
value: contextValue
}, /*#__PURE__*/React.createElement("div", {
className: (0, _clsx.clsx)(prefixCls, className, rootClassName, {
[`${prefixCls}-show-line`]: showLine
}),
style: rootStyle
}, /*#__PURE__*/React.createElement(_NodeList.default, _extends({
ref: this.listRef,
prefixCls: prefixCls,
style: style,
data: flattenNodes,
disabled: disabled,
selectable: selectable,
checkable: !!checkable,
motion: motion,
dragging: draggingNodeKey !== null,
height: height,
itemHeight: itemHeight,
virtual: virtual,
focusable: focusable,
tabIndex: tabIndex,
activeItem: this.getActiveItem(),
onFocus: this.onFocus,
onMouseDown: this.onMouseDown,
onBlur: this.onBlur,
onKeyDown: this.onKeyDown,
onActiveChange: this.onActiveChange,
onListChangeStart: this.onListChangeStart,
onListChangeEnd: this.onListChangeEnd,
onContextMenu: onContextMenu,
onScroll: onScroll,
scrollWidth: scrollWidth
}, this.getTreeNodeRequiredProps(), domProps))));
}
}
var _default = exports.default = Tree;