ant-design-vue/components/vc-tree/TreeNode.tsx
tangjinzhou 3aeeeb2aed
3.0 ready (#4523)
* refactor: transfer、tooltip (#4306)

* refactor(transfer): use composition api (#4135)

* refactor(transfer): use composition api

* fix: remove console

* refactor(tooltip): use composition api (#4059)

* refactor(tooltip): use composition api

* chore: useConfigInject

* fix: remove useless

* style: format code

* refactor: transfer

* refactor: tooltip

Co-authored-by: ajuner <106791576@qq.com>

* Refactor mentions (#4341)

* refactor(mentions): use compositionAPI (#4313)

* refactor: mentions

* refactor: mentions

Co-authored-by: ajuner <106791576@qq.com>

* Refactor progress (#4358)

* fix: timepicker error border not show #4331

* fix(UploadDragger): fix UploadDrager no export (#4334)

* refactor(switch): support customize checked value #4329 (#4332)

* refactor(switch): support customize checked value #4329

* test: add test case

* refactor: update props name

* refactor: update ts

* refactor: optimize

* style: uncheckedValue to unCheckedValue

* test: update snap

* feat: udpate switch ts

* docs: remove ie11

* fix: tree-select throw error when use slot title

* fix: TypeScript definition of Table interface for typescript 4.3.5 (#4353)

* fix type for typescript 4.3.5

* Update interface.ts

close #4296

* fix: dropdown submenu style error #4351
close #4351

* fix(notification): 完善notification类型 (#4346)

* refactor(progress): use composition API (#4355)

* refactor(progress): use composition API

* refactor(vc-progress): update

* refactor: progress

* refactor: progress

* fix: timepicker error border not show #4331

* fix(UploadDragger): fix UploadDrager no export (#4334)

* refactor(switch): support customize checked value #4329 (#4332)

* refactor(switch): support customize checked value #4329

* test: add test case

* refactor: update props name

* refactor: update ts

* refactor: optimize

* style: uncheckedValue to unCheckedValue

* test: update snap

* feat: udpate switch ts

* docs: remove ie11

* fix: tree-select throw error when use slot title

* fix: TypeScript definition of Table interface for typescript 4.3.5 (#4353)

* fix type for typescript 4.3.5

* Update interface.ts

close #4296

* fix: dropdown submenu style error #4351
close #4351

* fix(notification): 完善notification类型 (#4346)

* refactor(progress): use composition API (#4355)

* refactor(progress): use composition API

* refactor(vc-progress): update

* refactor: progress

* refactor: progress

Co-authored-by: Jarvis <35361626+fanhaoyuan@users.noreply.github.com>
Co-authored-by: John <John60676@qq.com>
Co-authored-by: 艾斯特洛 <axetroy.dev@gmail.com>
Co-authored-by: zanllp <qc@zanllp.cn>

* docs: add changelog

* refactor: tree

* refactor: tree

* style: lint

* refactor: tree

* 热factor: tree

* refactor: tree

* refactor: tree

* refactor: tree

* refactor: directory tree

* refactor: tree

* refactor: tree-select

* refactor: tree-select

* refactor: tree-select

* refactor: tree-select

* refactor: tree-select

* style: lint format

* refactor: tree-select

* refactor: tree-select

* refactor: tree-select

* refactor: tree-select

* refactor: tree-select

* refactor: tree-select

* fix: upload ts error

* fix: update tree title render & switchIcon

* test: update tree test

* feat: add VirtualScroll tree

* refactor: datePicker & calendar & trigger (#4522)

* style: update

* test: update calendar test

* test: update test

* test: update test

* refactor: slider

* feat: update slider css

* refactor: slider to ts

* refactor: slider to ts

* perf: update default itemHeight

* test: update

* fix: uddate ts type

* fix: update skeleton

* fix: update skeleton

* refactor: update vc-pagination

* refactor: pagination

* refactor: timeline

* refactor: steps

* refactor: collapse

* refactor: collapse

* refactor: popconfirm

* refactor: popover

* refactor: dropdown

* doc: merge doc

* chore: vite for dev (#4602)

* style: js to jsx

* doc: add site

* style: lint

* style: format ts type

* doc: update

* style: format code

* style: format site

* doc: update

* style: dmeo

* style: format scripts

* chore: remove sub-modules

* chore: update vite

* site: add site build

* test: update snap

* doc(select): add tip (#4606)

* refactor: table (#4641)

* refactor: table

* refactor: table

* refactor: table

* refactor: table

* refactor: table

* refactor: table

* refactor: table

* refactor: table

* refactor: table

* fix: column not pass to cell

* doc: uppate table

* fix: update bodyCell headerCell

* doc: remove examples

* refactor: table

* fix: table title not work

* fix: table selection

* fix: table checkStrictly

* refactor: table

* fix: table template error

* feat: table support summary

* test: update snap

* perf: table

* docs(table): fix ajax demo (#4639)

* test: update table

* refactor: remove old table

* doc: update  table doc

* doc: update doc

* doc: update select

* doc: update summary

Co-authored-by: John <John60676@qq.com>

* doc: update doc

* fix: menu arrow not work

* test: update

* doc: add next site

* style: format

* doc: update

* doc: update site script

* fix: expand icon not fixed

* feat: use renderSlot

* test: update table snap

* feat: confirm support reactively

* feat: configProvider.config

* feat: message support configprovider.config

* feat: notification support configprovider.config

* doc: update doc

* fix: typescript compile error

* style: add import eslint

* doc: update demo

* chore: set transpileOnly true

* style: fix eslint error

* test: update snap

* doc: update

* test: mock date

* test: update snap

* chore: remove gulp-typescript (#4675)

* feat: V3 form (#4678)

* chore: update husky

* perf: update formItem

* perf: useInjectFormItemContext

* fix: table ts error

* doc: add Customized Form Controls demo

* feat: export useInjectFormItemContext

* doc: update form doc

* doc: update doc

* doc: update doc

* feat: autocomplete support option slot

* doc: update

* feat: add form item rest

* style: remove omit.js

* refactor: autocomplete

* doc: add changelog to site

* doc: update site anchor

* doc: update doc layout

* test: update table test

* doc: update

* chore: udpate gulp script

* chore: udpate gulp script

* doc: add changelog

* doc: update

* test: ignore some test wait vue-test-utils

* fix: form id error #4582
close #4582

* doc: add select Responsive demo

* doc: remove temp doc

Co-authored-by: ajuner <106791576@qq.com>
Co-authored-by: Jarvis <35361626+fanhaoyuan@users.noreply.github.com>
Co-authored-by: John <John60676@qq.com>
Co-authored-by: 艾斯特洛 <axetroy.dev@gmail.com>
Co-authored-by: zanllp <qc@zanllp.cn>
Co-authored-by: Amour1688 <lcz_1996@foxmail.com>
2021-09-25 16:51:32 +08:00

523 lines
15 KiB
Vue

import { useInjectTreeContext } from './contextTypes';
import { getDataAndAria } from './util';
import Indent from './Indent';
import { convertNodePropsToEventData } from './utils/treeUtil';
import {
computed,
defineComponent,
getCurrentInstance,
onMounted,
onUpdated,
reactive,
ref,
} from 'vue';
import { treeNodeProps } from './props';
import classNames from '../_util/classNames';
import { warning } from '../vc-util/warning';
import type { DragNodeEvent, Key } from './interface';
import pick from 'lodash-es/pick';
const ICON_OPEN = 'open';
const ICON_CLOSE = 'close';
const defaultTitle = '---';
export default defineComponent({
name: 'TreeNode',
inheritAttrs: false,
props: treeNodeProps,
isTreeNode: 1,
slots: ['title', 'icon', 'switcherIcon'],
setup(props, { attrs, slots, expose }) {
warning(
!('slots' in props.data),
`treeData slots is deprecated, please use ${Object.keys(props.data.slots || {}).map(
key => '`v-slot:' + key + '` ',
)}instead`,
);
const dragNodeHighlight = ref(false);
const context = useInjectTreeContext();
const selectHandle = ref();
const hasChildren = computed(() => {
const { eventKey } = props;
const { keyEntities } = context.value;
const { children } = keyEntities[eventKey] || {};
return !!(children || []).length;
});
const isLeaf = computed(() => {
const { isLeaf, loaded } = props;
const { loadData } = context.value;
const has = hasChildren.value;
if (isLeaf === false) {
return false;
}
return isLeaf || (!loadData && !has) || (loadData && loaded && !has);
});
const nodeState = computed(() => {
const { expanded } = props;
if (isLeaf.value) {
return null;
}
return expanded ? ICON_OPEN : ICON_CLOSE;
});
const isDisabled = computed(() => {
const { disabled } = props;
const { disabled: treeDisabled } = context.value;
return !!(treeDisabled || disabled);
});
const isCheckable = computed(() => {
const { checkable } = props;
const { checkable: treeCheckable } = context.value;
// Return false if tree or treeNode is not checkable
if (!treeCheckable || checkable === false) return false;
return treeCheckable;
});
const isSelectable = computed(() => {
const { selectable } = props;
const { selectable: treeSelectable } = context.value;
// Ignore when selectable is undefined or null
if (typeof selectable === 'boolean') {
return selectable;
}
return treeSelectable;
});
const renderArgsData = computed(() => {
return {
...pick(props, [
'active',
'checkable',
'checked',
'disableCheckbox',
'disabled',
'expanded',
'isLeaf',
'loading',
'selectable',
'selected',
'halfChecked',
]),
...props.data,
dataRef: props.data,
data: props.data,
isLeaf: isLeaf.value,
};
});
const eventData = computed(() => {
return convertNodePropsToEventData(props);
});
const dragNodeEvent: DragNodeEvent = reactive({
eventData,
eventKey: computed(() => props.eventKey),
selectHandle,
pos: computed(() => props.pos),
key: getCurrentInstance().vnode.key as Key,
});
expose(dragNodeEvent);
const onSelectorDoubleClick = (e: MouseEvent) => {
const { onNodeDoubleClick } = context.value;
onNodeDoubleClick(e, eventData.value);
};
const onSelect = (e: MouseEvent) => {
if (isDisabled.value) return;
const { onNodeSelect } = context.value;
e.preventDefault();
onNodeSelect(e, eventData.value);
};
const onCheck = (e: MouseEvent) => {
if (isDisabled.value) return;
const { disableCheckbox, checked } = props;
const { onNodeCheck } = context.value;
if (!isCheckable.value || disableCheckbox) return;
e.preventDefault();
const targetChecked = !checked;
onNodeCheck(e, eventData.value, targetChecked);
};
const onSelectorClick = (e: MouseEvent) => {
// Click trigger before select/check operation
const { onNodeClick } = context.value;
onNodeClick(e, eventData.value);
if (isSelectable.value) {
onSelect(e);
} else {
onCheck(e);
}
};
const onMouseEnter = (e: MouseEvent) => {
const { onNodeMouseEnter } = context.value;
onNodeMouseEnter(e, eventData.value);
};
const onMouseLeave = (e: MouseEvent) => {
const { onNodeMouseLeave } = context.value;
onNodeMouseLeave(e, eventData.value);
};
const onContextmenu = (e: MouseEvent) => {
const { onNodeContextMenu } = context.value;
onNodeContextMenu(e, eventData.value);
};
const onDragStart = (e: DragEvent) => {
const { onNodeDragStart } = context.value;
e.stopPropagation();
dragNodeHighlight.value = true;
onNodeDragStart(e, dragNodeEvent);
try {
// ie throw error
// firefox-need-it
e.dataTransfer.setData('text/plain', '');
} catch (error) {
// empty
}
};
const onDragEnter = (e: DragEvent) => {
const { onNodeDragEnter } = context.value;
e.preventDefault();
e.stopPropagation();
onNodeDragEnter(e, dragNodeEvent);
};
const onDragOver = (e: DragEvent) => {
const { onNodeDragOver } = context.value;
e.preventDefault();
e.stopPropagation();
onNodeDragOver(e, dragNodeEvent);
};
const onDragLeave = (e: DragEvent) => {
const { onNodeDragLeave } = context.value;
e.stopPropagation();
onNodeDragLeave(e, dragNodeEvent);
};
const onDragEnd = (e: DragEvent) => {
const { onNodeDragEnd } = context.value;
e.stopPropagation();
dragNodeHighlight.value = false;
onNodeDragEnd(e, dragNodeEvent);
};
const onDrop = (e: DragEvent) => {
const { onNodeDrop } = context.value;
e.preventDefault();
e.stopPropagation();
dragNodeHighlight.value = false;
onNodeDrop(e, dragNodeEvent);
};
// Disabled item still can be switch
const onExpand = e => {
const { onNodeExpand } = context.value;
if (props.loading) return;
onNodeExpand(e, eventData.value);
};
const renderSwitcherIconDom = () => {
const {
switcherIcon: switcherIconFromProps = slots.switcherIcon ||
context.value.slots?.[props.data?.slots?.switcherIcon],
} = props;
const { switcherIcon: switcherIconFromCtx } = context.value;
const switcherIcon = switcherIconFromProps || switcherIconFromCtx;
// if switcherIconDom is null, no render switcher span
if (typeof switcherIcon === 'function') {
return switcherIcon(renderArgsData.value);
}
return switcherIcon;
};
// Load data to avoid default expanded tree without data
const syncLoadData = () => {
const { expanded, loading, loaded } = props;
const { loadData, onNodeLoad } = context.value;
if (loading) {
return;
}
// read from state to avoid loadData at same time
if (loadData && expanded && !isLeaf.value) {
// We needn't reload data when has children in sync logic
// It's only needed in node expanded
if (!hasChildren.value && !loaded) {
onNodeLoad(eventData.value);
}
}
};
onMounted(() => {
syncLoadData();
});
onUpdated(() => {
//syncLoadData();
});
// Switcher
const renderSwitcher = () => {
const { expanded } = props;
const { prefixCls } = context.value;
// if switcherIconDom is null, no render switcher span
const switcherIconDom = renderSwitcherIconDom();
if (isLeaf.value) {
return switcherIconDom !== false ? (
<span class={classNames(`${prefixCls}-switcher`, `${prefixCls}-switcher-noop`)}>
{switcherIconDom}
</span>
) : null;
}
const switcherCls = classNames(
`${prefixCls}-switcher`,
`${prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}`,
);
return switcherIconDom !== false ? (
<span onClick={onExpand} class={switcherCls}>
{switcherIconDom}
</span>
) : null;
};
// Checkbox
const renderCheckbox = () => {
const { checked, halfChecked, disableCheckbox } = props;
const { prefixCls } = context.value;
const disabled = isDisabled.value;
const checkable = isCheckable.value;
if (!checkable) return null;
return (
<span
class={classNames(
`${prefixCls}-checkbox`,
checked && `${prefixCls}-checkbox-checked`,
!checked && halfChecked && `${prefixCls}-checkbox-indeterminate`,
(disabled || disableCheckbox) && `${prefixCls}-checkbox-disabled`,
)}
onClick={onCheck}
>
{context.value.customCheckable?.()}
</span>
);
};
const renderIcon = () => {
const { loading } = props;
const { prefixCls } = context.value;
return (
<span
class={classNames(
`${prefixCls}-iconEle`,
`${prefixCls}-icon__${nodeState.value || 'docu'}`,
loading && `${prefixCls}-icon_loading`,
)}
/>
);
};
const renderDropIndicator = () => {
const { disabled, eventKey } = props;
const {
draggable,
dropLevelOffset,
dropPosition,
prefixCls,
indent,
dropIndicatorRender,
dragOverNodeKey,
direction,
} = context.value;
const mergedDraggable = draggable !== false;
// allowDrop is calculated in Tree.tsx, there is no need for calc it here
const showIndicator = !disabled && mergedDraggable && dragOverNodeKey === eventKey;
return showIndicator
? dropIndicatorRender({ dropPosition, dropLevelOffset, indent, prefixCls, direction })
: null;
};
// Icon + Title
const renderSelector = () => {
const {
// title = slots.title ||
// context.value.slots?.[props.data?.slots?.title] ||
// context.value.slots?.title,
selected,
icon = slots.icon,
loading,
data,
} = props;
const title =
slots.title ||
context.value.slots?.[props.data?.slots?.title] ||
context.value.slots?.title ||
props.title;
const {
prefixCls,
showIcon,
icon: treeIcon,
draggable,
loadData,
slots: contextSlots,
} = context.value;
const disabled = isDisabled.value;
const mergedDraggable = typeof draggable === 'function' ? draggable(data) : draggable;
const wrapClass = `${prefixCls}-node-content-wrapper`;
// Icon - Still show loading icon when loading without showIcon
let $icon;
if (showIcon) {
const currentIcon = icon || context.value.slots?.[data?.slots?.icon] || treeIcon;
$icon = currentIcon ? (
<span class={classNames(`${prefixCls}-iconEle`, `${prefixCls}-icon__customize`)}>
{typeof currentIcon === 'function' ? currentIcon(renderArgsData.value) : currentIcon}
</span>
) : (
renderIcon()
);
} else if (loadData && loading) {
$icon = renderIcon();
}
// Title
let titleNode: any;
if (typeof title === 'function') {
titleNode = title(renderArgsData.value);
} else if (contextSlots.valuetitleRender) {
titleNode = contextSlots.valuetitleRender(renderArgsData.value);
} else {
titleNode = title;
}
titleNode = titleNode === undefined ? defaultTitle : titleNode;
const $title = <span class={`${prefixCls}-title`}>{titleNode}</span>;
return (
<span
ref={selectHandle}
title={typeof title === 'string' ? title : ''}
class={classNames(
`${wrapClass}`,
`${wrapClass}-${nodeState.value || 'normal'}`,
!disabled && (selected || dragNodeHighlight.value) && `${prefixCls}-node-selected`,
!disabled && mergedDraggable && 'draggable',
)}
draggable={(!disabled && mergedDraggable) || undefined}
aria-grabbed={(!disabled && mergedDraggable) || undefined}
onMouseenter={onMouseEnter}
onMouseleave={onMouseLeave}
onContextmenu={onContextmenu}
onClick={onSelectorClick}
onDblclick={onSelectorDoubleClick}
onDragstart={mergedDraggable ? onDragStart : undefined}
>
{$icon}
{$title}
{renderDropIndicator()}
</span>
);
};
return () => {
const {
eventKey,
dragOver,
dragOverGapTop,
dragOverGapBottom,
isLeaf,
isStart,
isEnd,
expanded,
selected,
checked,
halfChecked,
loading,
domRef,
active,
data,
onMousemove,
...otherProps
} = { ...props, ...attrs };
const { prefixCls, filterTreeNode, draggable, keyEntities, dropContainerKey, dropTargetKey } =
context.value;
const disabled = isDisabled.value;
const dataOrAriaAttributeProps = getDataAndAria(otherProps);
const { level } = keyEntities[eventKey] || {};
const isEndNode = isEnd[isEnd.length - 1];
const mergedDraggable = typeof draggable === 'function' ? draggable(data) : draggable;
return (
<div
ref={domRef}
class={classNames(attrs.class, `${prefixCls}-treenode`, {
[`${prefixCls}-treenode-disabled`]: disabled,
[`${prefixCls}-treenode-switcher-${expanded ? 'open' : 'close'}`]: !isLeaf,
[`${prefixCls}-treenode-checkbox-checked`]: checked,
[`${prefixCls}-treenode-checkbox-indeterminate`]: halfChecked,
[`${prefixCls}-treenode-selected`]: selected,
[`${prefixCls}-treenode-loading`]: loading,
[`${prefixCls}-treenode-active`]: active,
[`${prefixCls}-treenode-leaf-last`]: isEndNode,
'drop-target': dropTargetKey === eventKey,
'drop-container': dropContainerKey === eventKey,
'drag-over': !disabled && dragOver,
'drag-over-gap-top': !disabled && dragOverGapTop,
'drag-over-gap-bottom': !disabled && dragOverGapBottom,
'filter-node': filterTreeNode && filterTreeNode(eventData.value),
})}
style={attrs.style}
onDragenter={mergedDraggable ? onDragEnter : undefined}
onDragover={mergedDraggable ? onDragOver : undefined}
onDragleave={mergedDraggable ? onDragLeave : undefined}
onDrop={mergedDraggable ? onDrop : undefined}
onDragend={mergedDraggable ? onDragEnd : undefined}
onMousemove={onMousemove}
{...dataOrAriaAttributeProps}
>
<Indent prefixCls={prefixCls} level={level} isStart={isStart} isEnd={isEnd} />
{renderSwitcher()}
{renderCheckbox()}
{renderSelector()}
</div>
);
};
},
});