Skip to content

Commit

Permalink
✨ feat(sortable-tree): 新增 sortableRule 函数规则,控制拖动排序 (#46)
Browse files Browse the repository at this point in the history
* ✨ feat(sortabletree): 新增 sortableRule 函数规则,控制拖动排序

* 🔧 chore(types): 修正类型

* 🔧 chore(sortable-tree): fix returnType ReactElement -> ReactNode

---------

Co-authored-by: kangxinlin.kxl <kangxinlin.kxl@antgroup.com>
  • Loading branch information
xlkang and kangxinlin.kxl committed Jul 3, 2023
1 parent e1fea60 commit e941865
Show file tree
Hide file tree
Showing 9 changed files with 1,135 additions and 58 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ docs-dist
server
.husky/prepare-commit-msg
coverage
.vscode
12 changes: 11 additions & 1 deletion src/SortableTree/container/StoreUpdater.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { SortableTreeInstance, useSortableTree } from '../hooks/useSortableTree'
import type { ControlledState, OnTreeDataChange } from '../store';
import { useStoreApi } from '../store';

import type { RenderNodeProps, TreeData } from '../types';
import type { FlattenNode, Projected, RenderNodeProps, TreeData } from '../types';

export interface StoreUpdaterProps<T = any> extends ControlledState {
/**
Expand Down Expand Up @@ -38,6 +38,14 @@ export interface StoreUpdaterProps<T = any> extends ControlledState {
* @internal
*/
SHOW_STORE_IN_DEVTOOLS?: boolean;
/**
* 是否可拖动的函数规则,如果返回false,本次拖动会被禁用,默认允许自由拖动
*/
sortableRule?: (data: {
activeNode: FlattenNode<T>;
targetNode: FlattenNode<T>;
projected: Projected;
}) => boolean;
}

const StoreUpdater = ({
Expand All @@ -52,6 +60,7 @@ const StoreUpdater = ({
hideAdd,
indentationWidth,
disableDrag,
sortableRule,
}: StoreUpdaterProps) => {
const storeApi = useStoreApi();

Expand All @@ -67,6 +76,7 @@ const StoreUpdater = ({
useStoreUpdater('hideAdd', hideAdd);
useStoreUpdater('hideRemove', hideRemove);
useStoreUpdater('disableDrag', disableDrag);
useStoreUpdater('sortableRule', sortableRule);

useStoreUpdater('onSelectedIdsChange', onSelectedIdsChange);

Expand Down
4 changes: 2 additions & 2 deletions src/SortableTree/container/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface SortableTreeProps<T> extends StoreUpdaterProps<T>, ControlledSt

export { SortableTreeProvider } from './Provider';

export const SortableTree: <T>(props: SortableTreeProps<T>) => ReactNode = memo((props) => {
export const SortableTree = memo((props) => {
const { SHOW_STORE_IN_DEVTOOLS, className, style, ...res } = props;
return (
<ConfigProvider>
Expand All @@ -23,4 +23,4 @@ export const SortableTree: <T>(props: SortableTreeProps<T>) => ReactNode = memo(
</SortableTreeProvider>
</ConfigProvider>
);
});
}) as <T>(props: SortableTreeProps<T>) => ReactNode;
25 changes: 25 additions & 0 deletions src/SortableTree/demos/sortableRule.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* title: 自定义可拖动规则
* description: 通过函数规则自定义是否可拖动
*/
import { SortableTree } from '@ant-design/pro-editor';

import { message } from 'antd';
import { initialData } from './data';

export default () => (
<div style={{ width: 340 }}>
<SortableTree
defaultTreeData={initialData}
sortableRule={(data) => {
// 只允许平级拖动
const { activeNode, projected } = data;
const sortable = activeNode?.depth === projected?.depth;

if (!sortable) message.warning('只允许同级拖动排序');

return sortable;
}}
/>
</div>
);
39 changes: 26 additions & 13 deletions src/SortableTree/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ demo:

<code src="./demos/renderContent.tsx" ></code>
<code src="./demos/disableDrag.tsx" ></code>
<code src="./demos/sortableRule.tsx" ></code>

[//]: # '### 多选方案'
[//]: #
Expand All @@ -32,19 +33,20 @@ demo:

### 属性

| 名称 | 类型 | 描述 |
| ------------------- | ---------------------------------------------------------------- | ------------------ |
| hideAdd | `boolean` | 隐藏默认的添加按钮 |
| hideRemove | `boolean` | 隐藏默认的删除按钮 |
| disableDrag | `boolean` | 禁用拖拽 |
| indentationWidth | `number` | 缩进宽度 |
| onSelectedIdsChange | `(selectedIds: UniqueIdentifier[]) => void` | 选中 ID 变更回调 |
| treeData | `TreeData<T>` | 树的数据 |
| defaultTreeData | `TreeData<T>` | 默认数据 |
| onTreeDataChange | `(treeData: TreeData<T>,event: TreeDataDispatchPayload) => void` | 数据变更回调 |
| renderContent | `(node: FlattenNode<T>) => JSX.Element` | 渲染内容 |
| renderExtra | `(node: FlattenNode<T>) => JSX.Element` | 渲染额外项 |
| ref | `MutableRefObject<SortableTreeInstance<T>>` | 对外部暴露方法 |
| 名称 | 类型 | 描述 |
| ------------------- | ----------------------------------------------------------------------------------------------------- | ---------------------- |
| hideAdd | `boolean` | 隐藏默认的添加按钮 |
| hideRemove | `boolean` | 隐藏默认的删除按钮 |
| disableDrag | `boolean` | 禁用拖拽 |
| indentationWidth | `number` | 缩进宽度 |
| onSelectedIdsChange | `(selectedIds: UniqueIdentifier[]) => void` | 选中 ID 变更回调 |
| treeData | `TreeData<T>` | 树的数据 |
| defaultTreeData | `TreeData<T>` | 默认数据 |
| onTreeDataChange | `(treeData: TreeData<T>,event: TreeDataDispatchPayload) => void` | 数据变更回调 |
| renderContent | `(node: FlattenNode<T>) => JSX.Element` | 渲染内容 |
| renderExtra | `(node: FlattenNode<T>) => JSX.Element` | 渲染额外项 |
| ref | `MutableRefObject<SortableTreeInstance<T>>` | 对外部暴露方法 |
| sortableRule | `data: { activeNode: FlattenNode<T>; targetNode: FlattenNode<T>; projected: Projected; }) => boolean` | 控制拖动排序的规则函数 |

## TreeNode

Expand All @@ -68,6 +70,17 @@ demo:
| depth | `number` | 节点深度 |
| index | `number` | 节点在同级节点中的位置 |

## Projected

放置目标位置信息

| 名称 | 类型 | 描述 |
| -------- | ---------------------------- | -------------------------------- |
| depth | `number` | 放置目标位置深度 |
| maxDepth | `number` | 目标位置可放置最大深度 |
| minDepth | `number` | 目标位置可放置可放置最小深度 |
| parentId | `UniqueIdentifier` \| `null` | 父节点 ID,如果是根节点则为 null |

## SortableTreeInstance

SortableTree 实例对象,类型定义参考:
Expand Down
2 changes: 1 addition & 1 deletion src/SortableTree/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export * from './container';
// hooks 和相关类型定义
export { useSortableTree, type SortableTreeInstance } from './hooks/useSortableTree';
export type { TreeNodeDispatchPayload } from './store';
export type { FlattenNode, TreeData, TreeNode } from './types';
export type { FlattenNode, Projected, TreeData, TreeNode } from './types';
12 changes: 10 additions & 2 deletions src/SortableTree/store/initialState.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { RenderNodeProps, TreeData, UniqueIdentifier } from '../types';
import type { FlattenNode, Projected, RenderNodeProps, TreeData, UniqueIdentifier } from '../types';
import { TreeDataDispatchPayload } from './treeDataReducer';

export type OnTreeDataChange<T = any> = (
Expand Down Expand Up @@ -32,7 +32,7 @@ export interface ControlledState {
/**
* @title 组件状态
*/
export interface State extends ControlledState {
export interface State<T = any> extends ControlledState {
/**
* @title 树形数据
*/
Expand Down Expand Up @@ -81,6 +81,14 @@ export interface State extends ControlledState {
* @param node - 节点数据
*/
renderExtra: RenderNodeProps;
/**
* 是否可拖动的函数规则,如果返回false,本次拖动会被禁用,默认允许自由拖动
*/
sortableRule?: (data: {
activeNode: FlattenNode<T>;
targetNode: FlattenNode<T>;
projected: Projected;
}) => boolean;
}

export const initialDragState: Pick<
Expand Down
17 changes: 14 additions & 3 deletions src/SortableTree/store/slices/dndSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { StateCreator } from 'zustand/vanilla';
import { FlattenNode } from '../../types';
import { getFlattenedData } from '../../utils/utils';
import { initialDragState } from '../initialState';
import { projectedSelector } from '../selectors';
import { dataFlattenSelector, projectedSelector } from '../selectors';
import { InternalSortableTreeStore } from '../store';

export interface DndAction {
Expand Down Expand Up @@ -57,11 +57,22 @@ export const dndSlice: StateCreator<
set({ overId: over?.id ?? null });
},
handleDragEnd: ({ active, over }) => {
const { resetState, dispatchTreeData } = get();
const { resetState, dispatchTreeData, sortableRule } = get();

const dataFlatten = dataFlattenSelector(get());
const activeNode = dataFlatten.find((i) => i.id === active.id);
const targetNode = dataFlatten.find((i) => i.id === over.id);
const projected = projectedSelector(get());

if (projected && over) {
const canSort =
!sortableRule ||
sortableRule?.({
activeNode,
targetNode,
projected,
});

if (projected && over && canSort) {
dispatchTreeData({
type: 'moveNode',
projected,
Expand Down
Loading

0 comments on commit e941865

Please sign in to comment.