2024-08-02 18:19:39 +08:00

459 lines
15 KiB
JavaScript

import { computed, defineComponent, h, inject, onMounted, ref } from 'vue';
import { useMemo } from 'vooks';
import { happensIn, repeat } from 'seemly';
import { createDataKey } from "../../_utils/index.mjs";
import NTreeNodeSwitcher from "./TreeNodeSwitcher.mjs";
import NTreeNodeCheckbox from "./TreeNodeCheckbox.mjs";
import NTreeNodeContent from "./TreeNodeContent.mjs";
import { treeInjectionKey } from "./interface.mjs";
import { renderDropMark } from "./dnd.mjs";
import { isNodeDisabled } from "./utils.mjs";
const TreeNode = defineComponent({
name: 'TreeNode',
props: {
clsPrefix: {
type: String,
required: true
},
tmNode: {
type: Object,
required: true
}
},
setup(props) {
const NTree = inject(treeInjectionKey);
const {
droppingNodeParentRef,
droppingMouseNodeRef,
draggingNodeRef,
droppingPositionRef,
droppingOffsetLevelRef,
nodePropsRef,
indentRef,
blockLineRef,
checkboxPlacementRef,
checkOnClickRef,
disabledFieldRef,
showLineRef,
renderSwitcherIconRef,
overrideDefaultNodeClickBehaviorRef
} = NTree;
const checkboxDisabledRef = useMemo(() => !!props.tmNode.rawNode.checkboxDisabled);
const nodeIsDisabledRef = useMemo(() => {
return isNodeDisabled(props.tmNode, disabledFieldRef.value);
});
const disabledRef = useMemo(() => NTree.disabledRef.value || nodeIsDisabledRef.value);
const resolvedNodePropsRef = computed(() => {
const {
value: nodeProps
} = nodePropsRef;
if (!nodeProps) return undefined;
return nodeProps({
option: props.tmNode.rawNode
});
});
// used for drag and drop
const contentInstRef = ref(null);
// must be non-reactive
const contentElRef = {
value: null
};
onMounted(() => {
contentElRef.value = contentInstRef.value.$el;
});
function handleSwitcherClick() {
const callback = () => {
const {
tmNode
} = props;
if (!tmNode.isLeaf && !tmNode.shallowLoaded) {
if (!NTree.loadingKeysRef.value.has(tmNode.key)) {
NTree.loadingKeysRef.value.add(tmNode.key);
} else {
return;
}
const {
onLoadRef: {
value: onLoad
}
} = NTree;
if (onLoad) {
void onLoad(tmNode.rawNode).then(value => {
if (value !== false) {
NTree.handleSwitcherClick(tmNode);
}
}).finally(() => {
NTree.loadingKeysRef.value.delete(tmNode.key);
});
}
} else {
NTree.handleSwitcherClick(tmNode);
}
};
if (renderSwitcherIconRef.value) {
// if renderSwitcherIcon is set, icon dom may be altered before event
// bubbles to parent dom, so that target check fails. Call it in next
// event loop so that event bubble phase is finishes.
setTimeout(callback, 0);
} else {
callback();
}
}
const selectableRef = useMemo(() => !nodeIsDisabledRef.value && NTree.selectableRef.value && (NTree.internalTreeSelect ? NTree.mergedCheckStrategyRef.value !== 'child' || NTree.multipleRef.value && NTree.cascadeRef.value || props.tmNode.isLeaf : true));
const checkableRef = useMemo(() => NTree.checkableRef.value && (NTree.cascadeRef.value || NTree.mergedCheckStrategyRef.value !== 'child' || props.tmNode.isLeaf));
const checkedRef = useMemo(() => NTree.displayedCheckedKeysRef.value.includes(props.tmNode.key));
const mergedCheckOnClickRef = useMemo(() => {
const {
value: checkable
} = checkableRef;
if (!checkable) return false;
const {
value: checkOnClick
} = checkOnClickRef;
const {
tmNode
} = props;
if (typeof checkOnClick === 'boolean') {
return !tmNode.disabled && checkOnClick;
}
return checkOnClick(props.tmNode.rawNode);
});
function _handleClick(e) {
const {
value: expandOnClick
} = NTree.expandOnClickRef;
const {
value: selectable
} = selectableRef;
const {
value: mergedCheckOnClick
} = mergedCheckOnClickRef;
if (!selectable && !expandOnClick && !mergedCheckOnClick) return;
if (happensIn(e, 'checkbox') || happensIn(e, 'switcher')) return;
const {
tmNode
} = props;
if (selectable) {
NTree.handleSelect(tmNode);
}
if (expandOnClick && !tmNode.isLeaf) {
handleSwitcherClick();
}
if (mergedCheckOnClick) {
handleCheck(!checkedRef.value);
}
}
function handleNodeClick(e) {
var _a, _b;
if (happensIn(e, 'checkbox') || happensIn(e, 'switcher')) return;
if (!disabledRef.value) {
const overrideDefaultNodeClickBehavior = overrideDefaultNodeClickBehaviorRef.value;
let shouldOverride = false;
if (overrideDefaultNodeClickBehavior) {
switch (overrideDefaultNodeClickBehavior({
option: props.tmNode.rawNode
})) {
case 'toggleCheck':
shouldOverride = true;
handleCheck(!checkedRef.value);
break;
case 'toggleSelect':
shouldOverride = true;
NTree.handleSelect(props.tmNode);
break;
case 'toggleExpand':
shouldOverride = true;
handleSwitcherClick();
shouldOverride = true;
break;
case 'none':
shouldOverride = true;
shouldOverride = true;
return;
case 'default':
default:
break;
}
}
if (!shouldOverride) {
_handleClick(e);
}
}
(_b = (_a = resolvedNodePropsRef.value) === null || _a === void 0 ? void 0 : _a.onClick) === null || _b === void 0 ? void 0 : _b.call(_a, e);
}
function handleContentClick(e) {
if (blockLineRef.value) return;
handleNodeClick(e);
}
function handleLineClick(e) {
if (!blockLineRef.value) return;
handleNodeClick(e);
}
function handleCheck(checked) {
NTree.handleCheck(props.tmNode, checked);
}
// Dnd
function handleDragStart(e) {
NTree.handleDragStart({
event: e,
node: props.tmNode
});
}
function handleDragEnter(e) {
if (e.currentTarget !== e.target) {
return;
}
NTree.handleDragEnter({
event: e,
node: props.tmNode
});
}
function handleDragOver(e) {
e.preventDefault(); // if not prevent, drop event won't be fired...
NTree.handleDragOver({
event: e,
node: props.tmNode
});
}
function handleDragEnd(e) {
NTree.handleDragEnd({
event: e,
node: props.tmNode
});
}
function handleDragLeave(e) {
if (e.currentTarget !== e.target) {
return;
}
NTree.handleDragLeave({
event: e,
node: props.tmNode
});
}
function handleDrop(e) {
e.preventDefault();
if (droppingPositionRef.value !== null) {
NTree.handleDrop({
event: e,
node: props.tmNode,
dropPosition: droppingPositionRef.value
});
}
}
const indentNodes = computed(() => {
const {
clsPrefix
} = props;
const {
value: indent
} = indentRef;
if (showLineRef.value) {
const indentNodes = [];
let cursor = props.tmNode.parent;
while (cursor) {
if (cursor.isLastChild) {
indentNodes.push(h("div", {
class: `${clsPrefix}-tree-node-indent`
}, h("div", {
style: {
width: `${indent}px`
}
})));
} else {
indentNodes.push(h("div", {
class: [`${clsPrefix}-tree-node-indent`, `${clsPrefix}-tree-node-indent--show-line`]
}, h("div", {
style: {
width: `${indent}px`
}
})));
}
cursor = cursor.parent;
}
return indentNodes.reverse();
} else {
return repeat(props.tmNode.level, h("div", {
class: `${props.clsPrefix}-tree-node-indent`
}, h("div", {
style: {
width: `${indent}px`
}
})));
}
});
return {
showDropMark: useMemo(() => {
const {
value: draggingNode
} = draggingNodeRef;
if (!draggingNode) return;
const {
value: droppingPosition
} = droppingPositionRef;
if (!droppingPosition) return;
const {
value: droppingMouseNode
} = droppingMouseNodeRef;
if (!droppingMouseNode) {
return;
}
const {
tmNode
} = props;
if (tmNode.key === droppingMouseNode.key) return true;
return false;
}),
showDropMarkAsParent: useMemo(() => {
const {
value: droppingNodeParent
} = droppingNodeParentRef;
if (!droppingNodeParent) return false;
const {
tmNode
} = props;
const {
value: droppingPosition
} = droppingPositionRef;
if (droppingPosition === 'before' || droppingPosition === 'after') {
return droppingNodeParent.key === tmNode.key;
}
return false;
}),
pending: useMemo(() => NTree.pendingNodeKeyRef.value === props.tmNode.key),
loading: useMemo(() => NTree.loadingKeysRef.value.has(props.tmNode.key)),
highlight: useMemo(() => {
var _a;
return (_a = NTree.highlightKeySetRef.value) === null || _a === void 0 ? void 0 : _a.has(props.tmNode.key);
}),
checked: checkedRef,
indeterminate: useMemo(() => NTree.displayedIndeterminateKeysRef.value.includes(props.tmNode.key)),
selected: useMemo(() => NTree.mergedSelectedKeysRef.value.includes(props.tmNode.key)),
expanded: useMemo(() => NTree.mergedExpandedKeysRef.value.includes(props.tmNode.key)),
disabled: disabledRef,
checkable: checkableRef,
mergedCheckOnClick: mergedCheckOnClickRef,
checkboxDisabled: checkboxDisabledRef,
selectable: selectableRef,
expandOnClick: NTree.expandOnClickRef,
internalScrollable: NTree.internalScrollableRef,
draggable: NTree.draggableRef,
blockLine: blockLineRef,
nodeProps: resolvedNodePropsRef,
checkboxFocusable: NTree.internalCheckboxFocusableRef,
droppingPosition: droppingPositionRef,
droppingOffsetLevel: droppingOffsetLevelRef,
indent: indentRef,
checkboxPlacement: checkboxPlacementRef,
showLine: showLineRef,
contentInstRef,
contentElRef,
indentNodes,
handleCheck,
handleDrop,
handleDragStart,
handleDragEnter,
handleDragOver,
handleDragEnd,
handleDragLeave,
handleLineClick,
handleContentClick,
handleSwitcherClick
};
},
render() {
const {
tmNode,
clsPrefix,
checkable,
expandOnClick,
selectable,
selected,
checked,
highlight,
draggable,
blockLine,
indent,
indentNodes,
disabled,
pending,
internalScrollable,
nodeProps,
checkboxPlacement
} = this;
// drag start not inside
// it need to be append to node itself, not wrapper
const dragEventHandlers = draggable && !disabled ? {
onDragenter: this.handleDragEnter,
onDragleave: this.handleDragLeave,
onDragend: this.handleDragEnd,
onDrop: this.handleDrop,
onDragover: this.handleDragOver
} : undefined;
// In non virtual mode, there's no evidence that which element should be
// scrolled to, so we need data-key to query the target element.
const dataKey = internalScrollable ? createDataKey(tmNode.key) : undefined;
const checkboxOnRight = checkboxPlacement === 'right';
const checkboxNode = checkable ? h(NTreeNodeCheckbox, {
indent: indent,
right: checkboxOnRight,
focusable: this.checkboxFocusable,
disabled: disabled || this.checkboxDisabled,
clsPrefix: clsPrefix,
checked: this.checked,
indeterminate: this.indeterminate,
onCheck: this.handleCheck
}) : null;
return h("div", Object.assign({
class: `${clsPrefix}-tree-node-wrapper`
}, dragEventHandlers), h("div", Object.assign({}, blockLine ? nodeProps : undefined, {
class: [`${clsPrefix}-tree-node`, {
[`${clsPrefix}-tree-node--selected`]: selected,
[`${clsPrefix}-tree-node--checkable`]: checkable,
[`${clsPrefix}-tree-node--highlight`]: highlight,
[`${clsPrefix}-tree-node--pending`]: pending,
[`${clsPrefix}-tree-node--disabled`]: disabled,
[`${clsPrefix}-tree-node--selectable`]: selectable,
[`${clsPrefix}-tree-node--clickable`]: selectable || expandOnClick || this.mergedCheckOnClick
}, nodeProps === null || nodeProps === void 0 ? void 0 : nodeProps.class],
"data-key": dataKey,
draggable: draggable && blockLine,
onClick: this.handleLineClick,
onDragstart: draggable && blockLine && !disabled ? this.handleDragStart : undefined
}), indentNodes, tmNode.isLeaf && this.showLine ? h("div", {
class: [`${clsPrefix}-tree-node-indent`, `${clsPrefix}-tree-node-indent--show-line`, tmNode.isLeaf && `${clsPrefix}-tree-node-indent--is-leaf`, tmNode.isLastChild && `${clsPrefix}-tree-node-indent--last-child`]
}, h("div", {
style: {
width: `${indent}px`
}
})) : h(NTreeNodeSwitcher, {
clsPrefix: clsPrefix,
expanded: this.expanded,
selected: selected,
loading: this.loading,
hide: tmNode.isLeaf,
tmNode: this.tmNode,
indent: indent,
onClick: this.handleSwitcherClick
}), !checkboxOnRight ? checkboxNode : null, h(NTreeNodeContent, {
ref: "contentInstRef",
clsPrefix: clsPrefix,
checked: checked,
selected: selected,
onClick: this.handleContentClick,
nodeProps: blockLine ? undefined : nodeProps,
onDragstart: draggable && !blockLine && !disabled ? this.handleDragStart : undefined,
tmNode: tmNode
}), draggable ? this.showDropMark ? renderDropMark({
el: this.contentElRef.value,
position: this.droppingPosition,
offsetLevel: this.droppingOffsetLevel,
indent
}) : this.showDropMarkAsParent ? renderDropMark({
el: this.contentElRef.value,
position: 'inside',
offsetLevel: this.droppingOffsetLevel,
indent
}) : null : null, checkboxOnRight ? checkboxNode : null));
}
});
export default TreeNode;