UNPKG

naive-ui

Version:

A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast

1,150 lines 59.4 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.treeProps = exports.treeSharedProps = void 0; exports.createTreeMateOptions = createTreeMateOptions; const seemly_1 = require("seemly"); const treemate_1 = require("treemate"); const vooks_1 = require("vooks"); const vue_1 = require("vue"); const vueuc_1 = require("vueuc"); const _internal_1 = require("../../_internal"); const _mixins_1 = require("../../_mixins"); const _utils_1 = require("../../_utils"); const empty_1 = require("../../empty"); const interface_1 = require("../../tree-select/src/interface"); const styles_1 = require("../styles"); const dnd_1 = require("./dnd"); const interface_2 = require("./interface"); const keyboard_1 = require("./keyboard"); const MotionWrapper_1 = __importDefault(require("./MotionWrapper")); const index_cssr_1 = __importDefault(require("./styles/index.cssr")); const TreeNode_1 = __importDefault(require("./TreeNode")); const utils_1 = require("./utils"); function createTreeMateOptions(keyField, childrenField, disabledField, getChildren) { const settledGetChildren = getChildren || ((node) => { return node[childrenField]; }); return { getIsGroup() { return false; }, getKey(node) { return node[keyField]; }, getChildren: settledGetChildren, getDisabled(node) { return !!(node[disabledField] || node.checkboxDisabled); } }; } exports.treeSharedProps = { allowCheckingNotLoaded: Boolean, filter: Function, defaultExpandAll: Boolean, expandedKeys: Array, keyField: { type: String, default: 'key' }, labelField: { type: String, default: 'label' }, childrenField: { type: String, default: 'children' }, disabledField: { type: String, default: 'disabled' }, defaultExpandedKeys: { type: Array, default: () => [] }, indent: { type: Number, default: 24 }, indeterminateKeys: Array, renderSwitcherIcon: Function, onUpdateIndeterminateKeys: [Function, Array], 'onUpdate:indeterminateKeys': [Function, Array], onUpdateExpandedKeys: [Function, Array], 'onUpdate:expandedKeys': [Function, Array], overrideDefaultNodeClickBehavior: Function }; exports.treeProps = Object.assign(Object.assign(Object.assign(Object.assign({}, _mixins_1.useTheme.props), { accordion: Boolean, showIrrelevantNodes: { type: Boolean, default: true }, data: { type: Array, default: () => [] }, expandOnDragenter: { type: Boolean, default: true }, expandOnClick: Boolean, checkOnClick: { type: [Boolean, Function], default: false }, cancelable: { type: Boolean, default: true }, checkable: Boolean, draggable: Boolean, blockNode: Boolean, blockLine: Boolean, showLine: Boolean, disabled: Boolean, checkedKeys: Array, defaultCheckedKeys: { type: Array, default: () => [] }, selectedKeys: Array, defaultSelectedKeys: { type: Array, default: () => [] }, multiple: Boolean, pattern: { type: String, default: '' }, onLoad: Function, cascade: Boolean, selectable: { type: Boolean, default: true }, scrollbarProps: Object, allowDrop: { type: Function, default: dnd_1.defaultAllowDrop }, animated: { type: Boolean, default: true }, checkboxPlacement: { type: String, default: 'left' }, virtualScroll: Boolean, watchProps: Array, renderLabel: Function, renderPrefix: Function, renderSuffix: Function, nodeProps: Function, keyboard: { type: Boolean, default: true }, getChildren: Function, onDragenter: [Function, Array], onDragleave: [Function, Array], onDragend: [Function, Array], onDragstart: [Function, Array], onDragover: [Function, Array], onDrop: [Function, Array], onUpdateCheckedKeys: [Function, Array], 'onUpdate:checkedKeys': [Function, Array], onUpdateSelectedKeys: [Function, Array], 'onUpdate:selectedKeys': [Function, Array] }), exports.treeSharedProps), { // internal props for tree-select internalTreeSelect: Boolean, internalScrollable: Boolean, internalScrollablePadding: String, // use it to display internalRenderEmpty: Function, internalHighlightKeySet: Object, internalUnifySelectCheck: Boolean, internalCheckboxFocusable: { type: Boolean, default: true }, internalFocusable: { // Make tree-select take over keyboard operations type: Boolean, default: true }, checkStrategy: { type: String, default: 'all' }, /** * @deprecated */ leafOnly: Boolean }); exports.default = (0, vue_1.defineComponent)({ name: 'Tree', props: exports.treeProps, slots: Object, setup(props) { if (process.env.NODE_ENV !== 'production') { (0, vue_1.watchEffect)(() => { if (props.leafOnly) { (0, _utils_1.warnOnce)('tree', '`leaf-only` is deprecated, please use `check-strategy="child"` instead'); } }); } const { mergedClsPrefixRef, inlineThemeDisabled, mergedRtlRef } = (0, _mixins_1.useConfig)(props); const rtlEnabledRef = (0, _mixins_1.useRtl)('Tree', mergedRtlRef, mergedClsPrefixRef); const themeRef = (0, _mixins_1.useTheme)('Tree', '-tree', index_cssr_1.default, styles_1.treeLight, props, mergedClsPrefixRef); const selfElRef = (0, vue_1.ref)(null); const scrollbarInstRef = (0, vue_1.ref)(null); const virtualListInstRef = (0, vue_1.ref)(null); function getScrollContainer() { var _a; return (_a = virtualListInstRef.value) === null || _a === void 0 ? void 0 : _a.listElRef; } function getScrollContent() { var _a; return (_a = virtualListInstRef.value) === null || _a === void 0 ? void 0 : _a.itemsElRef; } const mergedFilterRef = (0, vue_1.computed)(() => { const { filter } = props; if (filter) return filter; const { labelField } = props; return (pattern, node) => { if (!pattern.length) return true; const label = node[labelField]; if (typeof label === 'string') { return label.toLowerCase().includes(pattern.toLowerCase()); } return false; }; }); const filteredTreeInfoRef = (0, vue_1.computed)(() => { const { pattern } = props; if (!pattern) { return { filteredTree: props.data, highlightKeySet: null, expandedKeys: undefined }; } if (!pattern.length || !mergedFilterRef.value) { return { filteredTree: props.data, highlightKeySet: null, expandedKeys: undefined }; } return (0, utils_1.filterTree)(props.data, mergedFilterRef.value, pattern, props.keyField, props.childrenField); }); // We don't expect data source to change so we just determine it once const displayTreeMateRef = (0, vue_1.computed)(() => (0, treemate_1.createTreeMate)(props.showIrrelevantNodes ? props.data : filteredTreeInfoRef.value.filteredTree, createTreeMateOptions(props.keyField, props.childrenField, props.disabledField, props.getChildren))); const treeSelectInjection = (0, vue_1.inject)(interface_1.treeSelectInjectionKey, null); const dataTreeMateRef = props.internalTreeSelect ? treeSelectInjection.dataTreeMate : (0, vue_1.computed)(() => props.showIrrelevantNodes ? displayTreeMateRef.value : (0, treemate_1.createTreeMate)(props.data, createTreeMateOptions(props.keyField, props.childrenField, props.disabledField, props.getChildren))); const { watchProps } = props; const uncontrolledCheckedKeysRef = (0, vue_1.ref)([]); if (watchProps === null || watchProps === void 0 ? void 0 : watchProps.includes('defaultCheckedKeys')) { (0, vue_1.watchEffect)(() => { uncontrolledCheckedKeysRef.value = props.defaultCheckedKeys; }); } else { uncontrolledCheckedKeysRef.value = props.defaultCheckedKeys; } const controlledCheckedKeysRef = (0, vue_1.toRef)(props, 'checkedKeys'); const mergedCheckedKeysRef = (0, vooks_1.useMergedState)(controlledCheckedKeysRef, uncontrolledCheckedKeysRef); const checkedStatusRef = (0, vue_1.computed)(() => { const value = dataTreeMateRef.value.getCheckedKeys(mergedCheckedKeysRef.value, { cascade: props.cascade, allowNotLoaded: props.allowCheckingNotLoaded }); return value; }); const mergedCheckStrategyRef = (0, utils_1.useMergedCheckStrategy)(props); const displayedCheckedKeysRef = (0, vue_1.computed)(() => { return checkedStatusRef.value.checkedKeys; }); const displayedIndeterminateKeysRef = (0, vue_1.computed)(() => { const { indeterminateKeys } = props; if (indeterminateKeys !== undefined) return indeterminateKeys; return checkedStatusRef.value.indeterminateKeys; }); const uncontrolledSelectedKeysRef = (0, vue_1.ref)([]); if (watchProps === null || watchProps === void 0 ? void 0 : watchProps.includes('defaultSelectedKeys')) { (0, vue_1.watchEffect)(() => { uncontrolledSelectedKeysRef.value = props.defaultSelectedKeys; }); } else { uncontrolledSelectedKeysRef.value = props.defaultSelectedKeys; } const controlledSelectedKeysRef = (0, vue_1.toRef)(props, 'selectedKeys'); const mergedSelectedKeysRef = (0, vooks_1.useMergedState)(controlledSelectedKeysRef, uncontrolledSelectedKeysRef); const uncontrolledExpandedKeysRef = (0, vue_1.ref)([]); const initUncontrolledExpandedKeys = (keys) => { uncontrolledExpandedKeysRef.value = props.defaultExpandAll ? dataTreeMateRef.value.getNonLeafKeys() : keys === undefined ? props.defaultExpandedKeys : keys; }; if (watchProps === null || watchProps === void 0 ? void 0 : watchProps.includes('defaultExpandedKeys')) { // if watching defaultExpandedKeys, we use access props.defaultExpandedKeys inside initiator (0, vue_1.watchEffect)(() => { initUncontrolledExpandedKeys(undefined); }); } else { // We by default watchEffect since if defaultExpandAll is true, we should remain tree expand if data changes (0, vue_1.watchEffect)(() => { initUncontrolledExpandedKeys(props.defaultExpandedKeys); }); } const controlledExpandedKeysRef = (0, vue_1.toRef)(props, 'expandedKeys'); const mergedExpandedKeysRef = (0, vooks_1.useMergedState)(controlledExpandedKeysRef, uncontrolledExpandedKeysRef); const fNodesRef = (0, vue_1.computed)(() => displayTreeMateRef.value.getFlattenedNodes(mergedExpandedKeysRef.value)); const { pendingNodeKeyRef, handleKeydown } = (0, keyboard_1.useKeyboard)({ props, mergedCheckedKeysRef, mergedSelectedKeysRef, fNodesRef, mergedExpandedKeysRef, handleCheck, handleSelect, handleSwitcherClick }); let expandTimerId = null; let nodeKeyToBeExpanded = null; const uncontrolledHighlightKeySetRef = (0, vue_1.ref)(new Set()); const controlledHighlightKeySetRef = (0, vue_1.computed)(() => { return (props.internalHighlightKeySet || filteredTreeInfoRef.value.highlightKeySet); }); const mergedHighlightKeySetRef = (0, vooks_1.useMergedState)(controlledHighlightKeySetRef, uncontrolledHighlightKeySetRef); const loadingKeysRef = (0, vue_1.ref)(new Set()); const expandedNonLoadingKeysRef = (0, vue_1.computed)(() => { return mergedExpandedKeysRef.value.filter(key => !loadingKeysRef.value.has(key)); }); let dragStartX = 0; const draggingNodeRef = (0, vue_1.ref)(null); const droppingNodeRef = (0, vue_1.ref)(null); const droppingMouseNodeRef = (0, vue_1.ref)(null); const droppingPositionRef = (0, vue_1.ref)(null); const droppingOffsetLevelRef = (0, vue_1.ref)(0); const droppingNodeParentRef = (0, vue_1.computed)(() => { const { value: droppingNode } = droppingNodeRef; if (!droppingNode) return null; // May avoid overlap between line mark of first child & rect mark of parent // if (droppingNode.isFirstChild && droppingPositionRef.value === 'before') { // return null // } return droppingNode.parent; }); // shallow watch data let isDataReset = false; (0, vue_1.watch)((0, vue_1.toRef)(props, 'data'), () => { isDataReset = true; void (0, vue_1.nextTick)(() => { isDataReset = false; }); loadingKeysRef.value.clear(); pendingNodeKeyRef.value = null; resetDndState(); }, { deep: false }); let expandAnimationDisabled = false; const disableExpandAnimationForOneTick = () => { expandAnimationDisabled = true; void (0, vue_1.nextTick)(() => { expandAnimationDisabled = false; }); }; let memoizedExpandedKeys; (0, vue_1.watch)((0, vue_1.toRef)(props, 'pattern'), (value, oldValue) => { if (props.showIrrelevantNodes) { memoizedExpandedKeys = undefined; if (value) { const { expandedKeys: expandedKeysAfterChange, highlightKeySet } = (0, utils_1.keysWithFilter)(props.data, props.pattern, props.keyField, props.childrenField, mergedFilterRef.value); uncontrolledHighlightKeySetRef.value = highlightKeySet; disableExpandAnimationForOneTick(); doUpdateExpandedKeys(expandedKeysAfterChange, getOptionsByKeys(expandedKeysAfterChange), { node: null, action: 'filter' }); } else { uncontrolledHighlightKeySetRef.value = new Set(); } } else { if (!value.length) { if (memoizedExpandedKeys !== undefined) { disableExpandAnimationForOneTick(); doUpdateExpandedKeys(memoizedExpandedKeys, getOptionsByKeys(memoizedExpandedKeys), { node: null, action: 'filter' }); } } else { if (!oldValue.length) { memoizedExpandedKeys = mergedExpandedKeysRef.value; } const { expandedKeys } = filteredTreeInfoRef.value; if (expandedKeys !== undefined) { disableExpandAnimationForOneTick(); doUpdateExpandedKeys(expandedKeys, getOptionsByKeys(expandedKeys), { node: null, action: 'filter' }); } } } }); function triggerLoading(node) { return __awaiter(this, void 0, void 0, function* () { const { onLoad } = props; if (!onLoad) { if (process.env.NODE_ENV !== 'production') { (0, _utils_1.warn)('tree', 'There is unloaded node in data but props.onLoad is not specified.'); } yield Promise.resolve(); return; } const { value: loadingKeys } = loadingKeysRef; if (!loadingKeys.has(node.key)) { loadingKeys.add(node.key); try { const loadResult = yield onLoad(node.rawNode); if (loadResult === false) { resetDragExpandState(); } } catch (loadError) { console.error(loadError); resetDragExpandState(); } loadingKeys.delete(node.key); } }); } (0, vue_1.watchEffect)(() => { var _a; const { value: displayTreeMate } = displayTreeMateRef; if (!displayTreeMate) return; const { getNode } = displayTreeMate; (_a = mergedExpandedKeysRef.value) === null || _a === void 0 ? void 0 : _a.forEach((key) => { const node = getNode(key); if (node && !node.shallowLoaded) { void triggerLoading(node); } }); }); // animation in progress const aipRef = (0, vue_1.ref)(false); // animation flattened nodes const afNodesRef = (0, vue_1.ref)([]); // Note: Since the virtual list depends on min height, if there's a node // whose height starts from 0, the virtual list will have a wrong height // during animation. This will seldom cause wired scrollbar status. It is // fixable and need some changes in vueuc, I've no time so I just leave it // here. Maybe the bug won't be fixed during the life time of the project. (0, vue_1.watch)(expandedNonLoadingKeysRef, (value, prevValue) => { if (!props.animated || expandAnimationDisabled) { void (0, vue_1.nextTick)(syncScrollbar); return; } if (isDataReset) { return; } const nodeHeight = (0, seemly_1.depx)(themeRef.value.self.nodeHeight); const prevVSet = new Set(prevValue); let addedKey = null; let removedKey = null; for (const expandedKey of value) { if (!prevVSet.has(expandedKey)) { if (addedKey !== null) return; // multi expand, not triggered by click addedKey = expandedKey; } } const currentVSet = new Set(value); for (const expandedKey of prevValue) { if (!currentVSet.has(expandedKey)) { if (removedKey !== null) return; // multi collapse, not triggered by click removedKey = expandedKey; } } if (addedKey === null && removedKey === null) { // 1. multi action, not triggered by click // 2. no action, don't know what happened return; } const { virtualScroll } = props; const viewportHeight = (virtualScroll ? virtualListInstRef.value.listElRef : selfElRef.value).offsetHeight; const viewportItemCount = Math.ceil(viewportHeight / nodeHeight) + 1; // play add animation let baseExpandedKeys; if (addedKey !== null) { baseExpandedKeys = prevValue; } if (removedKey !== null) { if (baseExpandedKeys === undefined) { baseExpandedKeys = value; } else { baseExpandedKeys = baseExpandedKeys.filter(key => key !== removedKey); } } aipRef.value = true; afNodesRef.value = displayTreeMateRef.value.getFlattenedNodes(baseExpandedKeys); if (addedKey !== null) { const expandedNodeIndex = afNodesRef.value.findIndex(node => node.key === addedKey); if (~expandedNodeIndex) { const children = afNodesRef.value[expandedNodeIndex] .children; // sometimes user will pass leaf keys in if (children) { const expandedChildren = (0, treemate_1.flatten)(children, value); afNodesRef.value.splice(expandedNodeIndex + 1, 0, { __motion: true, mode: 'expand', height: virtualScroll ? expandedChildren.length * nodeHeight : undefined, nodes: virtualScroll ? expandedChildren.slice(0, viewportItemCount) : expandedChildren }); } } } if (removedKey !== null) { const collapsedNodeIndex = afNodesRef.value.findIndex(node => node.key === removedKey); if (~collapsedNodeIndex) { const collapsedNodeChildren = afNodesRef.value[collapsedNodeIndex].children; // Sometime the whole tree is change, remove a key doesn't mean it is collapsed, // but maybe children removed if (!collapsedNodeChildren) return; // play remove animation aipRef.value = true; const collapsedChildren = (0, treemate_1.flatten)(collapsedNodeChildren, value); afNodesRef.value.splice(collapsedNodeIndex + 1, 0, { __motion: true, mode: 'collapse', height: virtualScroll ? collapsedChildren.length * nodeHeight : undefined, nodes: virtualScroll ? collapsedChildren.slice(0, viewportItemCount) : collapsedChildren }); } } }); const getFIndexRef = (0, vue_1.computed)(() => { return (0, treemate_1.createIndexGetter)(fNodesRef.value); }); const mergedFNodesRef = (0, vue_1.computed)(() => { if (aipRef.value) return afNodesRef.value; else return fNodesRef.value; }); function syncScrollbar() { const { value: scrollbarInst } = scrollbarInstRef; if (scrollbarInst) scrollbarInst.sync(); } function handleAfterEnter() { aipRef.value = false; if (props.virtualScroll) { // If virtual scroll, we won't listen to resize during animation, so // resize callback of virtual list won't be called and as a result // scrollbar won't sync. We need to sync scrollbar manually. void (0, vue_1.nextTick)(syncScrollbar); } } function getOptionsByKeys(keys) { const { getNode } = dataTreeMateRef.value; return keys.map(key => { var _a; return ((_a = getNode(key)) === null || _a === void 0 ? void 0 : _a.rawNode) || null; }); } function doUpdateExpandedKeys(value, option, meta) { const { 'onUpdate:expandedKeys': _onUpdateExpandedKeys, onUpdateExpandedKeys } = props; uncontrolledExpandedKeysRef.value = value; if (_onUpdateExpandedKeys) { (0, _utils_1.call)(_onUpdateExpandedKeys, value, option, meta); } if (onUpdateExpandedKeys) { (0, _utils_1.call)(onUpdateExpandedKeys, value, option, meta); } } function doUpdateCheckedKeys(value, option, meta) { const { 'onUpdate:checkedKeys': _onUpdateCheckedKeys, onUpdateCheckedKeys } = props; uncontrolledCheckedKeysRef.value = value; if (onUpdateCheckedKeys) { (0, _utils_1.call)(onUpdateCheckedKeys, value, option, meta); } if (_onUpdateCheckedKeys) { (0, _utils_1.call)(_onUpdateCheckedKeys, value, option, meta); } } function doUpdateIndeterminateKeys(value, option) { const { 'onUpdate:indeterminateKeys': _onUpdateIndeterminateKeys, onUpdateIndeterminateKeys } = props; if (_onUpdateIndeterminateKeys) { (0, _utils_1.call)(_onUpdateIndeterminateKeys, value, option); } if (onUpdateIndeterminateKeys) { (0, _utils_1.call)(onUpdateIndeterminateKeys, value, option); } } function doUpdateSelectedKeys(value, option, meta) { const { 'onUpdate:selectedKeys': _onUpdateSelectedKeys, onUpdateSelectedKeys } = props; uncontrolledSelectedKeysRef.value = value; if (onUpdateSelectedKeys) { (0, _utils_1.call)(onUpdateSelectedKeys, value, option, meta); } if (_onUpdateSelectedKeys) { (0, _utils_1.call)(_onUpdateSelectedKeys, value, option, meta); } } // Drag & Drop function doDragEnter(info) { const { onDragenter } = props; if (onDragenter) (0, _utils_1.call)(onDragenter, info); } function doDragLeave(info) { const { onDragleave } = props; if (onDragleave) (0, _utils_1.call)(onDragleave, info); } function doDragEnd(info) { const { onDragend } = props; if (onDragend) (0, _utils_1.call)(onDragend, info); } function doDragStart(info) { const { onDragstart } = props; if (onDragstart) (0, _utils_1.call)(onDragstart, info); } function doDragOver(info) { const { onDragover } = props; if (onDragover) (0, _utils_1.call)(onDragover, info); } function doDrop(info) { const { onDrop } = props; if (onDrop) (0, _utils_1.call)(onDrop, info); } function resetDndState() { resetDragState(); resetDropState(); } function resetDragState() { draggingNodeRef.value = null; } function resetDropState() { droppingOffsetLevelRef.value = 0; droppingNodeRef.value = null; droppingMouseNodeRef.value = null; droppingPositionRef.value = null; resetDragExpandState(); } function resetDragExpandState() { if (expandTimerId) { window.clearTimeout(expandTimerId); expandTimerId = null; } nodeKeyToBeExpanded = null; } function handleCheck(node, checked) { // We don't guard for leaf only since we have done it in view layer if (props.disabled || (0, utils_1.isNodeDisabled)(node, props.disabledField)) { return; } if (props.internalUnifySelectCheck && !props.multiple) { handleSelect(node); return; } const checkedAction = checked ? 'check' : 'uncheck'; const { checkedKeys, indeterminateKeys } = dataTreeMateRef.value[checkedAction](node.key, displayedCheckedKeysRef.value, { cascade: props.cascade, checkStrategy: mergedCheckStrategyRef.value, allowNotLoaded: props.allowCheckingNotLoaded }); doUpdateCheckedKeys(checkedKeys, getOptionsByKeys(checkedKeys), { node: node.rawNode, action: checkedAction }); doUpdateIndeterminateKeys(indeterminateKeys, getOptionsByKeys(indeterminateKeys)); } function toggleExpand(node) { if (props.disabled) return; const { key } = node; const { value: mergedExpandedKeys } = mergedExpandedKeysRef; const index = mergedExpandedKeys.findIndex(expandNodeId => expandNodeId === key); if (~index) { const expandedKeysAfterChange = Array.from(mergedExpandedKeys); expandedKeysAfterChange.splice(index, 1); doUpdateExpandedKeys(expandedKeysAfterChange, getOptionsByKeys(expandedKeysAfterChange), { node: node.rawNode, action: 'collapse' }); } else { const nodeToBeExpanded = displayTreeMateRef.value.getNode(key); if (!nodeToBeExpanded || nodeToBeExpanded.isLeaf) { return; } let nextKeys; if (props.accordion) { const siblingKeySet = new Set(node.siblings.map(({ key }) => key)); nextKeys = mergedExpandedKeys.filter((expandedKey) => { return !siblingKeySet.has(expandedKey); }); nextKeys.push(key); } else { nextKeys = mergedExpandedKeys.concat(key); } doUpdateExpandedKeys(nextKeys, getOptionsByKeys(nextKeys), { node: node.rawNode, action: 'expand' }); } } function handleSwitcherClick(node) { if (props.disabled || aipRef.value) return; toggleExpand(node); } function handleSelect(node) { if (props.disabled || !props.selectable) { return; } pendingNodeKeyRef.value = node.key; if (props.internalUnifySelectCheck) { const { value: { checkedKeys, indeterminateKeys } } = checkedStatusRef; if (props.multiple) { handleCheck(node, !(checkedKeys.includes(node.key) || indeterminateKeys.includes(node.key))); } else { doUpdateCheckedKeys([node.key], getOptionsByKeys([node.key]), { node: node.rawNode, action: 'check' }); } } if (props.multiple) { const selectedKeys = Array.from(mergedSelectedKeysRef.value); const index = selectedKeys.findIndex(key => key === node.key); if (~index) { if (props.cancelable) { selectedKeys.splice(index, 1); } } else if (!~index) { selectedKeys.push(node.key); } doUpdateSelectedKeys(selectedKeys, getOptionsByKeys(selectedKeys), { node: node.rawNode, action: ~index ? 'unselect' : 'select' }); } else { const selectedKeys = mergedSelectedKeysRef.value; if (selectedKeys.includes(node.key)) { if (props.cancelable) { doUpdateSelectedKeys([], [], { node: node.rawNode, action: 'unselect' }); } } else { doUpdateSelectedKeys([node.key], getOptionsByKeys([node.key]), { node: node.rawNode, action: 'select' }); } } } function expandDragEnterNode(node) { if (expandTimerId) { window.clearTimeout(expandTimerId); expandTimerId = null; } // Don't expand leaf node. if (node.isLeaf) return; nodeKeyToBeExpanded = node.key; const expand = () => { if (nodeKeyToBeExpanded !== node.key) return; const { value: droppingMouseNode } = droppingMouseNodeRef; if (droppingMouseNode && droppingMouseNode.key === node.key && !mergedExpandedKeysRef.value.includes(node.key)) { const nextKeys = mergedExpandedKeysRef.value.concat(node.key); doUpdateExpandedKeys(nextKeys, getOptionsByKeys(nextKeys), { node: node.rawNode, action: 'expand' }); } expandTimerId = null; nodeKeyToBeExpanded = null; }; if (!node.shallowLoaded) { expandTimerId = window.setTimeout(() => { void triggerLoading(node).then(() => { expand(); }); }, 1000); } else { expandTimerId = window.setTimeout(() => { expand(); }, 1000); } } // Dnd function handleDragEnter({ event, node }) { // node should be a tmNode if (!props.draggable || props.disabled || (0, utils_1.isNodeDisabled)(node, props.disabledField)) { return; } handleDragOver({ event, node }, false); doDragEnter({ event, node: node.rawNode }); } function handleDragLeave({ event, node }) { if (!props.draggable || props.disabled || (0, utils_1.isNodeDisabled)(node, props.disabledField)) { return; } doDragLeave({ event, node: node.rawNode }); } function handleDragLeaveTree(e) { if (e.target !== e.currentTarget) return; resetDropState(); } // Dragend is ok, we don't need to add global listener to reset drag status function handleDragEnd({ event, node }) { resetDndState(); if (!props.draggable || props.disabled || (0, utils_1.isNodeDisabled)(node, props.disabledField)) { return; } doDragEnd({ event, node: node.rawNode }); } function handleDragStart({ event, node }) { var _a; if (!props.draggable || props.disabled || (0, utils_1.isNodeDisabled)(node, props.disabledField)) { return; } // Most of time, the image will block user's view if (utils_1.emptyImage) { (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.setDragImage(utils_1.emptyImage, 0, 0); } dragStartX = event.clientX; draggingNodeRef.value = node; doDragStart({ event, node: node.rawNode }); } function handleDragOver({ event, node }, emit = true) { var _a; if (!props.draggable || props.disabled || (0, utils_1.isNodeDisabled)(node, props.disabledField)) { return; } const { value: draggingNode } = draggingNodeRef; if (!draggingNode) return; const { allowDrop, indent } = props; if (emit) doDragOver({ event, node: node.rawNode }); // Update dropping node const el = event.currentTarget; const { height: elOffsetHeight, top: elClientTop } = el.getBoundingClientRect(); const eventOffsetY = event.clientY - elClientTop; let mousePosition; const allowDropInside = allowDrop({ node: node.rawNode, dropPosition: 'inside', phase: 'drag' }); if (allowDropInside) { if (eventOffsetY <= 8) { mousePosition = 'before'; } else if (eventOffsetY >= elOffsetHeight - 8) { mousePosition = 'after'; } else { mousePosition = 'inside'; } } else { if (eventOffsetY <= elOffsetHeight / 2) { mousePosition = 'before'; } else { mousePosition = 'after'; } } const { value: getFindex } = getFIndexRef; /** determine the drop position and drop node */ /** the dropping node needn't to be the mouse hovering node! */ /** * if there is something i've learned from implementing a complex * drag & drop. that is never write unit test before you really figure * out what behavior is exactly you want. */ let finalDropNode; let finalDropPosition; const hoverNodeFIndex = getFindex(node.key); if (hoverNodeFIndex === null) { resetDropState(); return; } let mouseAtExpandedNonLeafNode = false; if (mousePosition === 'inside') { finalDropNode = node; finalDropPosition = 'inside'; } else { if (mousePosition === 'before') { if (node.isFirstChild) { finalDropNode = node; finalDropPosition = 'before'; } else { finalDropNode = fNodesRef.value[hoverNodeFIndex - 1]; finalDropPosition = 'after'; } } else { finalDropNode = node; finalDropPosition = 'after'; } } // If the node is non-leaf and it is expanded, we don't allow it to // drop after it and change it to drop before its next view sibling if (!finalDropNode.isLeaf && mergedExpandedKeysRef.value.includes(finalDropNode.key)) { mouseAtExpandedNonLeafNode = true; if (finalDropPosition === 'after') { finalDropNode = fNodesRef.value[hoverNodeFIndex + 1]; if (!finalDropNode) { // maybe there is no next view sibling when non-leaf node has no // children and it is the last node in the tree finalDropNode = node; finalDropPosition = 'inside'; } else { finalDropPosition = 'before'; } } } const droppingMouseNode = finalDropNode; droppingMouseNodeRef.value = droppingMouseNode; // This is a speacial case, user is dragging a last child itself, so we // only view it as they are trying to drop after it. // There are some relevant codes in bailout 1's child branch. // Also, the expand bailout should have a high priority. If it's non-leaf // node and expanded, keep its origin drop position if (!mouseAtExpandedNonLeafNode && draggingNode.isLastChild && draggingNode.key === finalDropNode.key) { finalDropPosition = 'after'; } if (finalDropPosition === 'after') { let offset = dragStartX - event.clientX; // drag left => > 0 let offsetLevel = 0; while (offset >= indent / 2 // divide by 2 to make it easier to trigger && finalDropNode.parent !== null && finalDropNode.isLastChild && offsetLevel < 1) { offset -= indent; offsetLevel += 1; finalDropNode = finalDropNode.parent; } droppingOffsetLevelRef.value = offsetLevel; } else { droppingOffsetLevelRef.value = 0; } // Bailout 1 // Drag self into self // Drag it into direct parent if (draggingNode.contains(finalDropNode) || (finalDropPosition === 'inside' && ((_a = draggingNode.parent) === null || _a === void 0 ? void 0 : _a.key) === finalDropNode.key)) { if (draggingNode.key === droppingMouseNode.key && draggingNode.key === finalDropNode.key) { // This is special case that we want ui to show a mark to guide user // to start dragging. Nor they will think nothing happens. // However this is an invalid drop, we need to guard it inside // handleDrop } else { resetDropState(); return; } } // Bailout 3 if (!allowDrop({ node: finalDropNode.rawNode, dropPosition: finalDropPosition, phase: 'drag' })) { resetDropState(); return; } if (draggingNode.key === finalDropNode.key) { // don't expand when drag on itself resetDragExpandState(); } else { if (nodeKeyToBeExpanded !== finalDropNode.key) { if (finalDropPosition === 'inside') { if (props.expandOnDragenter) { expandDragEnterNode(finalDropNode); // Bailout 4 // not try to loading if (!finalDropNode.shallowLoaded && nodeKeyToBeExpanded !== finalDropNode.key) { resetDndState(); return; } } else { // Bailout 5 // never expands on drag if (!finalDropNode.shallowLoaded) { resetDndState(); return; } } } else { resetDragExpandState(); } } else { if (finalDropPosition !== 'inside') { resetDragExpandState(); } } } droppingPositionRef.value = finalDropPosition; droppingNodeRef.value = finalDropNode; } function handleDrop({ event, node, dropPosition }) { if (!props.draggable || props.disabled || (0, utils_1.isNodeDisabled)(node, props.disabledField)) { return; } const { value: draggingNode } = draggingNodeRef; const { value: droppingNode } = droppingNodeRef; const { value: droppingPosition } = droppingPositionRef; if (!draggingNode || !droppingNode || !droppingPosition) { return; } // Bailout 1 if (!props.allowDrop({ node: droppingNode.rawNode, dropPosition: droppingPosition, phase: 'drag' })) { return; } // Bailout 2 // This is a special case to guard since we want ui to show the status // but not to emit a event if (draggingNode.key === droppingNode.key) { return; } // Bailout 3 // insert before its next node // insert after its prev node if (droppingPosition === 'before') { const nextNode = draggingNode.getNext({ includeDisabled: true }); if (nextNode) { if (nextNode.key === droppingNode.key) { resetDropState(); return; } } } if (droppingPosition === 'after') { const prevNode = draggingNode.getPrev({ includeDisabled: true }); if (prevNode) { if (prevNode.key === droppingNode.key) { resetDropState(); return; } } } doDrop({ event, node: droppingNode.rawNode, dragNode: draggingNode.rawNode, dropPosition }); resetDndState(); } function handleScroll() { syncScrollbar(); } function handleResize() { syncScrollbar(); } function handleFocusout(e) { var _a; if (props.virtualScroll || props.internalScrollable) { const { value: scrollbarInst } = scrollbarInstRef; if ((_a = scrollbarInst === null || scrollbarInst === void 0 ? void 0 : scrollbarInst.containerRef) === null || _a === void 0 ? void 0 : _a.contains(e.relatedTarget)) { return; } pendingNodeKeyRef.value = null; } else { const { value: selfEl } = selfElRef; if (selfEl === null || selfEl === void 0 ? void 0 : selfEl.contains(e.relatedTarget)) return; pendingNodeKeyRef.value = null; } } (0, vue_1.watch)(pendingNodeKeyRef, (value) => { var _a, _b; if (value === null) return; if (props.virtualScroll) { (_a = virtualListInstRef.value) === null || _a === void 0 ? void 0 : _a.scrollTo({ key: value }); } else if (props.internalScrollable) { const { value: scrollbarInst } = scrollbarInstRef; if (scrollbarInst === null) return; const targetEl = (_b = scrollbarInst.contentRef) === null || _b === void 0 ? void 0 : _b.querySelector(`[data-key="${(0, _utils_1.createDataKey)(value)}"]`); if (!targetEl) return; scrollbarInst.scrollTo({ el: targetEl }); } }); (0, vue_1.provide)(interface_2.treeInjectionKey, { loadingKeysRef, highlightKeySetRef: mergedHighlightKeySetRef, displayedCheckedKeysRef, displayedIndeterminateKeysRef, mergedSelectedKeysRef, mergedExpandedKeysRef, mergedThemeRef: themeRef, mergedCheckStrategyRef, nodePropsRef: (0, vue_1.toRef)(props, 'nodeProps'), disabledRef: (0, vue_1.toRef)(props, 'disabled'), checkableRef: (0, vue_1.toRef)(props, 'checkable'), selectableRef: (0, vue_1.toRef)(props, 'selectable'), expandOnClickRef: (0, vue_1.toRef)(props, 'expandOnClick'), onLoadRef: (0, vue_1.toRef)(props, 'onLoad'), draggableRef: (0, vue_1.toRef)(props, 'draggable'), blockLineRef: (0, vue_1.toRef)(props, 'blockLine'), indentRef: (0, vue_1.toRef)(props, 'indent'), cascadeRef: (0, vue_1.toRef)(props, 'cascade'), checkOnClickRef: (0, vue_1.toRef)(props, 'checkOnClick'), checkboxPlacementRef: props.checkboxPlacement, droppingMouseNodeRef, droppingNodeParentRef, draggingNodeRef, droppingPositionRef, droppingOffsetLevelRef, fNodesRef, pendingNodeKeyRef, showLineRef: (0, vue_1.toRef)(props, 'showLine'), disabledFieldRef: