From 3438817b9ad50f690c045f6e471150228c19fb5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Sep 2023 11:43:29 +0800 Subject: [PATCH 01/10] chore(deps-dev): bump vite-plugin-istanbul from 2.9.0 to 5.0.0 (#2488) Bumps [vite-plugin-istanbul](https://github.com/ifaxity/vite-plugin-istanbul) from 2.9.0 to 5.0.0. - [Release notes](https://github.com/ifaxity/vite-plugin-istanbul/releases) - [Changelog](https://github.com/iFaxity/vite-plugin-istanbul/blob/next/release.config.js) - [Commits](https://github.com/ifaxity/vite-plugin-istanbul/compare/v2.9.0...v5.0.0) --- updated-dependencies: - dependency-name: vite-plugin-istanbul dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 296cdd16b8..142b02b91e 100644 --- a/package.json +++ b/package.json @@ -189,7 +189,7 @@ "ts-node": "^10.4.0", "typescript": "~4.5.4", "vite": "^2.9.15", - "vite-plugin-istanbul": "^2.3.0", + "vite-plugin-istanbul": "^5.0.0", "vite-plugin-pwa": "^0.12.8", "vite-plugin-tdoc": "^2.0.1", "vitest": "^0.24.1", From e139f96d2f1614b6c48fed31a31820f367ef5836 Mon Sep 17 00:00:00 2001 From: betavs <34408516+betavs@users.noreply.github.com> Date: Thu, 28 Sep 2023 02:55:43 -0500 Subject: [PATCH 02/10] fix(cascader): the child nodes are not updated when hovering over a node (#2528) --- src/cascader/core/effect.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascader/core/effect.ts b/src/cascader/core/effect.ts index 7006c653fb..75ea42344b 100644 --- a/src/cascader/core/effect.ts +++ b/src/cascader/core/effect.ts @@ -24,7 +24,7 @@ export function expendClickEffect( if (isDisabled) return; // 点击展开节点,设置展开状态 - if (propsTrigger === trigger && !node.isLeaf()) { + if (propsTrigger === trigger) { const expanded = node.setExpanded(true); treeStore.refreshNodes(); treeStore.replaceExpanded(expanded); From 7e76f575a0b2a50afa63a6f40877ec8fb4fe8ce0 Mon Sep 17 00:00:00 2001 From: Kyrie Lin Date: Thu, 28 Sep 2023 15:56:33 +0800 Subject: [PATCH 03/10] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20formList=20?= =?UTF-8?q?=E5=B5=8C=E5=A5=97=E6=95=B0=E6=8D=AE=E8=8E=B7=E5=8F=96=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=20(#2529)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 +--- src/form/hooks/useInstance.tsx | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b402c15cb8..f0a9c92255 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,7 @@ spline: explain - `steps`: 全局配置添加步骤条的已完成图标自定义 @Zzongke ([#2491](https://github.com/Tencent/tdesign-react/pull/2491)) - `Table`: 可筛选表格,`onFilterChange` 事件新增参数 `trigger: 'filter-change' | 'confirm' | 'reset' | 'clear'`,表示触发筛选条件变化的来源 @chaishi ([#2492](https://github.com/Tencent/tdesign-react/pull/2492)) - `Form`: trigger新增`submit`选项 @honkinglin ([#2507](https://github.com/Tencent/tdesign-react/pull/2507)) -- `ImageViewer`: - - `onIndexChange` 事件新增 `trigger` 枚举值 `current` @chaishi ([#2494](https://github.com/Tencent/tdesign-react/pull/2494)) - - +- `ImageViewer`: `onIndexChange` 事件新增 `trigger` 枚举值 `current` @chaishi ([#2494](https://github.com/Tencent/tdesign-react/pull/2494)) - `Image`: - 新增 `fallback`,表示图片的兜底图,原始图片加载失败时会显示兜底图 @chaishi ([#2494](https://github.com/Tencent/tdesign-react/pull/2494)) - 新增支持 `src` 类型为 `File`,支持通过 `File` 预览图片 @chaishi ([#2494](https://github.com/Tencent/tdesign-react/pull/2494)) diff --git a/src/form/hooks/useInstance.tsx b/src/form/hooks/useInstance.tsx index 21aea8697d..74123687c9 100644 --- a/src/form/hooks/useInstance.tsx +++ b/src/form/hooks/useInstance.tsx @@ -115,7 +115,8 @@ export default function useInstance(props: TdFormProps, formRef, formMapRef: Rea const fieldsValue = {}; if (nameList === true) { - for (const [name, formItemRef] of formMapRef.current.entries()) { + // 嵌套数组子节点先添加导致外层数据覆盖因而需要倒序遍历 + for (const [name, formItemRef] of [...formMapRef.current.entries()].reverse()) { const fieldValue = calcFieldValue(name, formItemRef?.current.getValue?.()); merge(fieldsValue, fieldValue); } From d1453e12331e95fbf32e43f2d764c3ddb91c108a Mon Sep 17 00:00:00 2001 From: Kyrie Lin Date: Thu, 28 Sep 2023 16:31:39 +0800 Subject: [PATCH 04/10] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20datepicker=20?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E6=9C=88=E4=BB=BD=E5=A4=B1=E6=95=88=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20(#2531)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/date-picker/panel/PanelContent.tsx | 27 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/date-picker/panel/PanelContent.tsx b/src/date-picker/panel/PanelContent.tsx index 56a2b962f4..79e30cfc54 100644 --- a/src/date-picker/panel/PanelContent.tsx +++ b/src/date-picker/panel/PanelContent.tsx @@ -62,20 +62,29 @@ export default function PanelContent(props: PanelContentProps) { const defaultTime = '00:00:00'; - const onMonthChangeInner = useCallback((val: number) => { - onMonthChange?.(val, { partial }); + const onMonthChangeInner = useCallback( + (val: number) => { + onMonthChange?.(val, { partial }); + }, // eslint-disable-next-line - }, []); + [partial], + ); - const onYearChangeInner = useCallback((val: number) => { - onYearChange?.(val, { partial }); + const onYearChangeInner = useCallback( + (val: number) => { + onYearChange?.(val, { partial }); + }, // eslint-disable-next-line - }, []); + [partial], + ); - const onJumperClickInner = useCallback(({ trigger }) => { - onJumperClick?.({ trigger, partial }); + const onJumperClickInner = useCallback( + ({ trigger }) => { + onJumperClick?.({ trigger, partial }); + }, // eslint-disable-next-line - }, []); + [partial], + ); return (
From 6e25b2f7df6ea72b51118fc5ca90b56a84cefd2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?w=C5=AB=20y=C4=81ng?= Date: Thu, 28 Sep 2023 16:42:08 +0800 Subject: [PATCH 05/10] fix(dropdown): fix disabled api (#2532) --- src/dropdown/Dropdown.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dropdown/Dropdown.tsx b/src/dropdown/Dropdown.tsx index b473d0a20c..3924f22a2c 100644 --- a/src/dropdown/Dropdown.tsx +++ b/src/dropdown/Dropdown.tsx @@ -48,6 +48,7 @@ const Dropdown: React.FC & { }; const handleVisibleChange = (visible: boolean, context: PopupVisibleChangeContext) => { + if (disabled) return; togglePopupVisible(visible); popupProps?.onVisibleChange?.(visible, context); }; From 945b1671e9b1c6a2de18c361a2a085e27376d3e4 Mon Sep 17 00:00:00 2001 From: Kyrie Lin Date: Thu, 28 Sep 2023 17:06:29 +0800 Subject: [PATCH 06/10] chore: publish 1.2.6 (#2533) * chore: publish 1.2.6 * chore: changelog's changes --------- Co-authored-by: github-actions[bot] --- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0a9c92255..b73076fb6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ toc: false spline: explain --- + ## 🌈 1.2.6 `2023-09-28` +### 🚀 Features +- `Table`: 优化渲染次数 @chaishi ([#2514](https://github.com/Tencent/tdesign-react/pull/2514)) +- `card`: title使用`div`取代`span` 在自定义场景下更符合规范 @uyarn ([#2517](https://github.com/Tencent/tdesign-react/pull/2517)) +- `Tree`: Tree支持通过key匹配单一value指定滚动到特定位置,具体使用方式请参考示例代码 @uyarn ([#2519](https://github.com/Tencent/tdesign-react/pull/2519)) +### 🐞 Bug Fixes +- `Form`: 修复 formList 嵌套数据获取异常 @honkinglin ([#2529](https://github.com/Tencent/tdesign-react/pull/2529)) +- `Table`: 修复数据切换时 `rowspanAndColspan` 渲染问题,[issue#2513](https://github.com/Tencent/tdesign-react/issues/2513) @chaishi ([#2514](https://github.com/Tencent/tdesign-react/pull/2514)) +- `Cascader`: hover 没有子节点数据的父节点时未更新子节点 @betavs ([#2528](https://github.com/Tencent/tdesign-react/pull/2528)) +- `Datepicker`: 修复切换月份失效问题 @honkinglin ([#2531](https://github.com/Tencent/tdesign-react/pull/2531)) +- `Dropdown`: 修复`Dropdown` disabled API失效的问题 @uyarn ([#2532](https://github.com/Tencent/tdesign-react/pull/2532)) + ## 🌈 1.2.5 `2023-09-14` ### 🚀 Features - `steps`: 全局配置添加步骤条的已完成图标自定义 @Zzongke ([#2491](https://github.com/Tencent/tdesign-react/pull/2491)) diff --git a/package.json b/package.json index 142b02b91e..266b612331 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tdesign-react", "purename": "tdesign", - "version": "1.2.5", + "version": "1.2.6", "description": "TDesign Component for React", "title": "tdesign-react", "main": "lib/index.js", From 761043717af6267b639e66c5045b98b5c93976ad Mon Sep 17 00:00:00 2001 From: Zong-Ke Zhang <65376724+Zzongke@users.noreply.github.com> Date: Tue, 10 Oct 2023 10:42:17 +0800 Subject: [PATCH 07/10] =?UTF-8?q?fix(locale):=20fix=20missing=20it=5FIT?= =?UTF-8?q?=E3=80=81ru=5FRU=E3=80=81zh=5FTW=20locale=20(#2542)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Heising --- src/locale/it_IT.ts | 5 +++++ src/locale/ru_RU.ts | 5 +++++ src/locale/zh_TW.ts | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 src/locale/it_IT.ts create mode 100644 src/locale/ru_RU.ts create mode 100644 src/locale/zh_TW.ts diff --git a/src/locale/it_IT.ts b/src/locale/it_IT.ts new file mode 100644 index 0000000000..c2d6b22f75 --- /dev/null +++ b/src/locale/it_IT.ts @@ -0,0 +1,5 @@ +import itIT from '../_common/js/global-config/locale/it_IT'; +import { GlobalConfigProvider } from '../config-provider/type'; + +// 需要 GlobalConfigProvider 保证数据类型正确 +export default itIT as unknown as GlobalConfigProvider; diff --git a/src/locale/ru_RU.ts b/src/locale/ru_RU.ts new file mode 100644 index 0000000000..14e71401b0 --- /dev/null +++ b/src/locale/ru_RU.ts @@ -0,0 +1,5 @@ +import ruRU from '../_common/js/global-config/locale/ru_RU'; +import { GlobalConfigProvider } from '../config-provider/type'; + +// 需要 GlobalConfigProvider 保证数据类型正确 +export default ruRU as unknown as GlobalConfigProvider; diff --git a/src/locale/zh_TW.ts b/src/locale/zh_TW.ts new file mode 100644 index 0000000000..1b7e825f8f --- /dev/null +++ b/src/locale/zh_TW.ts @@ -0,0 +1,5 @@ +import zhTW from '../_common/js/global-config/locale/zh_TW'; +import { GlobalConfigProvider } from '../config-provider/type'; + +// 需要 GlobalConfigProvider 保证数据类型正确 +export default zhTW as unknown as GlobalConfigProvider; From 5f4f5cf53a01db079f0a93aa2c00c9cd7712c45c Mon Sep 17 00:00:00 2001 From: betavs <34408516+betavs@users.noreply.github.com> Date: Thu, 12 Oct 2023 10:26:38 +0800 Subject: [PATCH 08/10] fix(cascader): effect source abnormal (#2544) --- src/cascader/core/effect.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascader/core/effect.ts b/src/cascader/core/effect.ts index 75ea42344b..523fdc9b72 100644 --- a/src/cascader/core/effect.ts +++ b/src/cascader/core/effect.ts @@ -102,7 +102,7 @@ export function valueChangeEffect(node: TreeNode, cascaderContext: CascaderConte .map((item) => item.value), ); - setValue(resValue, 'check', node.getModel()); + setValue(resValue, node.checked ? 'uncheck' : 'check', node.getModel()); } /** From 97ad27b75c01389a1f00c94cbe0b66c7401054a8 Mon Sep 17 00:00:00 2001 From: Zong-Ke Zhang <65376724+Zzongke@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:30:47 +0800 Subject: [PATCH 09/10] feat(Timeline): timelineitem add trigger on click (#2545) * feat(Timeline): timelineitem add trigger on click * feat(timeline): timelineItem add trigger on click --------- Co-authored-by: Heising --- src/timeline/TimelineItem.tsx | 9 ++++++++- src/timeline/timeline.en-US.md | 1 + src/timeline/timeline.md | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/timeline/TimelineItem.tsx b/src/timeline/TimelineItem.tsx index a9e39bc046..38127876b4 100644 --- a/src/timeline/TimelineItem.tsx +++ b/src/timeline/TimelineItem.tsx @@ -1,5 +1,6 @@ import React, { useContext, useMemo } from 'react'; import classNames from 'classnames'; +import omit from 'lodash/omit'; import { TdTimelineItemProps } from './type'; import { StyledProps } from '../common'; import useConfig from '../hooks/useConfig'; @@ -11,6 +12,7 @@ import Loading from '../loading'; export interface TimelineItemProps extends TdTimelineItemProps, StyledProps { children?: React.ReactNode; index?: number; + onClick?: (context: { e: React.MouseEvent; item: TdTimelineItemProps }) => void; } const DefaultTheme = ['default', 'primary', 'success', 'warning', 'error']; @@ -27,6 +29,7 @@ const TimelineItem: React.FC = (props) => { content, label, loading = false, + onClick, } = props; const { theme, reverse, itemsStatus, layout, globalAlign, mode } = useContext(TimelineContext); const { classPrefix } = useConfig(); @@ -63,6 +66,10 @@ const TimelineItem: React.FC = (props) => { return ele; }, [dot, classPrefix]); + const handleClick = (e: React.MouseEvent) => { + onClick?.({ e, item: omit(props, ['children', 'index', 'onClick']) }); + }; + // 节点类名 const itemClassName = classNames( { @@ -91,7 +98,7 @@ const TimelineItem: React.FC = (props) => { }); return ( -
  • +
  • {mode === 'alternate' && label &&
    {label}
    }
    diff --git a/src/timeline/timeline.en-US.md b/src/timeline/timeline.en-US.md index e036e75ba2..bfd3864df1 100644 --- a/src/timeline/timeline.en-US.md +++ b/src/timeline/timeline.en-US.md @@ -26,3 +26,4 @@ dotColor | String | primary | Typescript:`string` | N label | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N labelAlign | String | - | options:left/right/top/bottom | N loading | Boolean | - | Whether it is in the loading state | N +onClick | Function | | Typescript:`(context: { e: MouseEvent; item: TdTimelineItemProps }) => void` [TdTimelineItemProps 详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/timeline/type.ts)
    trigger on click. | N diff --git a/src/timeline/timeline.md b/src/timeline/timeline.md index f846a179a1..a0cb2ea380 100644 --- a/src/timeline/timeline.md +++ b/src/timeline/timeline.md @@ -26,3 +26,4 @@ dotColor | String | primary | 时间轴颜色,内置 `primary/warning/error/de label | TNode | - | 标签文本内容,可完全自定义。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N labelAlign | String | - | 标签信息相对于时间轴的位置,在 `mode='alternate'` 时生效,优先级高于 `Timeline.labelAlign`。可选项:left/right/top/bottom | N loading | Boolean | - | 是否处在加载状态 | N +onClick | Function | | TS 类型:`(context: { e: MouseEvent; item: TdTimelineItemProps }) => void` [TdTimelineItemProps 详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/timeline/type.ts)
    点击时触发。 | N From 37dff16d9ea556b013ec6c3f4f828d6a9d519d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?w=C5=AB=20y=C4=81ng?= Date: Wed, 18 Oct 2023 02:21:40 -0500 Subject: [PATCH 10/10] fix(tree): fix allowFoldNodeOnFilter filter expand result (#2552) * fix(tree): support filter path show when allowFoldNodeOnFilter is true * chore: add demo * chore: update snapshot * chore: update unit test for new logic * chore: use old tree common before refactoring * chore: use old tree common before refactoring * chore: use old tree common before refactoring --- src/_common | 2 +- src/cascader/hooks.tsx | 6 +- src/common.ts | 2 +- src/tag-input/useTagList.tsx | 3 +- src/tree-select/TreeSelect.tsx | 7 +- .../__tests__/vitest-tree-select.test.jsx | 18 +--- src/tree-select/_example/lazy.jsx | 4 +- src/tree-select/_usage/index.jsx | 42 ++++----- src/tree-select/useTreeSelectUtils.ts | 6 +- src/tree/Tree.tsx | 3 - src/tree/_example/filter.jsx | 23 +++-- src/tree/_example/vscroll.jsx | 2 +- src/tree/hooks/useStore.ts | 47 +++++++++- src/tree/type.ts | 5 -- test/snap/__snapshots__/csr.test.jsx.snap | 90 ++++++++++++++++++- test/snap/__snapshots__/ssr.test.jsx.snap | 4 +- 16 files changed, 187 insertions(+), 77 deletions(-) diff --git a/src/_common b/src/_common index 4440b1f5da..b62cac6f5d 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit 4440b1f5da531bacc412c9748cab48609054f827 +Subproject commit b62cac6f5d75b1af5b527a2849bedee42e906d6d diff --git a/src/cascader/hooks.tsx b/src/cascader/hooks.tsx index 822849a797..3977da94f7 100644 --- a/src/cascader/hooks.tsx +++ b/src/cascader/hooks.tsx @@ -8,7 +8,7 @@ import { getTreeValue, getCascaderValue, isEmptyValues, isValueInvalid } from '. import { treeNodesEffect, treeStoreExpendEffect } from './core/effect'; import useControlled from '../hooks/useControlled'; -import { +import type { TreeNode, TreeNodeValue, TdCascaderProps, @@ -17,6 +17,8 @@ import { CascaderValue, } from './interface'; +import type { TypeTreeNodeData } from '../_common/js/tree/types'; + export const useCascaderContext = (props: TdCascaderProps) => { const [innerValue, setInnerValue] = useControlled(props, 'value', props.onChange); const [innerPopupVisible, setPopupVisible] = useControlled(props, 'popupVisible', props.onPopupVisibleChange); @@ -96,7 +98,7 @@ export const useCascaderContext = (props: TdCascaderProps) => { }); }, }); - store.append(options); + store.append(options as Array); setTreeStore(store); } else { treeStore.reload(options); diff --git a/src/common.ts b/src/common.ts index 6f92a5cefa..1adbe36e8a 100644 --- a/src/common.ts +++ b/src/common.ts @@ -47,7 +47,7 @@ export type OptionData = { } & PlainObject; export type TreeOptionData = { - children?: Array>; + children?: Array> | boolean; /** option label content */ label?: string | TNode; /** option search text */ diff --git a/src/tag-input/useTagList.tsx b/src/tag-input/useTagList.tsx index 37fddb031e..ada2f48ab8 100644 --- a/src/tag-input/useTagList.tsx +++ b/src/tag-input/useTagList.tsx @@ -18,6 +18,7 @@ export default function useTagList(props: TagInputProps) { props; // handle controlled property and uncontrolled property const [tagValue, setTagValue] = useControlled(props, 'value', props.onChange); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [oldInputValue, setOldInputValue] = useState(); // 点击标签关闭按钮,删除标签 @@ -58,7 +59,7 @@ export default function useTagList(props: TagInputProps) { const { e } = context; if (!tagValue || !tagValue.length) return; // 回车键删除,输入框值为空时,才允许 Backspace 删除标签 - if (!oldInputValue && ['Backspace', 'NumpadDelete'].includes(e.key)) { + if (!value && ['Backspace', 'NumpadDelete'].includes(e.key)) { const index = tagValue.length - 1; const item = tagValue[index]; const trigger = 'backspace'; diff --git a/src/tree-select/TreeSelect.tsx b/src/tree-select/TreeSelect.tsx index 82b289a735..75bed7adc0 100644 --- a/src/tree-select/TreeSelect.tsx +++ b/src/tree-select/TreeSelect.tsx @@ -93,7 +93,7 @@ const TreeSelect = forwardRef((props: TreeSelectProps, ref) => { /* ---------------------------------computed value---------------------------------------- */ - const defaultFilter = (text, option) => { + const defaultFilter = (text: string, option: TreeOptionData) => { if (!text) return true; // 过滤时会有空节点影响判断 if (!option.label && !option.value) return false; @@ -182,7 +182,6 @@ const TreeSelect = forwardRef((props: TreeSelectProps, ref) => { }); // 单选选择后收起弹框 setPopupVisible(false, { ...context, trigger: 'trigger-element-click' }); - filterInput && setFilterInput('', { trigger: 'change' }); }); const handleMultiChange = usePersistFn((value, context) => { @@ -195,12 +194,10 @@ const TreeSelect = forwardRef((props: TreeSelectProps, ref) => { trigger: value.length > normalizedValue.length ? 'check' : 'uncheck', }, ); - filterInput && setFilterInput('', { trigger: 'change' }); } }); const onInnerPopupVisibleChange: SelectInputProps['onPopupVisibleChange'] = (visible, ctx) => { - !visible && filterInput && setFilterInput('', { trigger: 'clear' }); setPopupVisible(visible, { e: ctx.e }); }; @@ -273,7 +270,7 @@ const TreeSelect = forwardRef((props: TreeSelectProps, ref) => { ref={treeRef} hover transition - filter={handleFilter} + filter={filterInput ? handleFilter : null} data={data} disabled={disabled} empty={empty} diff --git a/src/tree-select/__tests__/vitest-tree-select.test.jsx b/src/tree-select/__tests__/vitest-tree-select.test.jsx index fb9ae7a585..333c0808ff 100644 --- a/src/tree-select/__tests__/vitest-tree-select.test.jsx +++ b/src/tree-select/__tests__/vitest-tree-select.test.jsx @@ -168,6 +168,7 @@ describe('TreeSelect Component', () => { it('props.filter: priority of onSearch is higher than props.filter, props.filter is forbidden to work in this scene', async () => { const { container } = getTreeSelectMultipleMount(TreeSelect, { + // eslint-disable-next-line @typescript-eslint/no-empty-function onSearch: () => {}, filter: (filterWord, option) => !filterWord || option.label === filterWord, }); @@ -176,7 +177,7 @@ describe('TreeSelect Component', () => { simulateInputChange(inputDom1, 'tdesign-react'); await mockDelay(100); const tTreeItemNotTTreeItemHiddenDom = document.querySelectorAll('.t-tree__item:not(.t-tree__item--hidden)'); - expect(tTreeItemNotTTreeItemHiddenDom.length).toBe(6); + expect(tTreeItemNotTTreeItemHiddenDom.length).toBe(8); }); it('props.filter: multiple tree select, check filter nodes', async () => { @@ -571,21 +572,6 @@ describe('TreeSelect Component', () => { expect(onFocusFn.mock.calls[0][0].e.type).toBe('focus'); }); - it('events.inputChange: clear filter words on change', async () => { - const onInputChangeFn1 = vi.fn(); - const { container } = getTreeSelectMultipleMount( - TreeSelect, - { inputValue: 'tdesign-vue' }, - { onInputChange: onInputChangeFn1 }, - ); - fireEvent.click(container.querySelector('.t-input')); - await mockDelay(200); - fireEvent.click(document.querySelector('.t-tree__item:first-child .t-checkbox__label')); - expect(onInputChangeFn1).toHaveBeenCalled(); - expect(onInputChangeFn1.mock.calls[0][0]).toBe(''); - expect(onInputChangeFn1.mock.calls[0][1].trigger).toBe('change'); - }); - it('events.popupVisibleChange works fine', async () => { const onPopupVisibleChangeFn = vi.fn(); const { container } = getTreeSelectMultipleMount( diff --git a/src/tree-select/_example/lazy.jsx b/src/tree-select/_example/lazy.jsx index 5600182c34..9d76ecf932 100644 --- a/src/tree-select/_example/lazy.jsx +++ b/src/tree-select/_example/lazy.jsx @@ -15,7 +15,7 @@ const options = [ ]; export default function Example() { - const [value, setValue] = useState(null); + const [value, setValue] = useState(''); function loadFunc(node) { return new Promise((resolve) => { @@ -50,7 +50,7 @@ export default function Example() { treeProps={{ load: loadFunc, lazy: true }} onChange={(val) => { setValue(val); - console.log(val) + console.log(val); }} />
    diff --git a/src/tree-select/_usage/index.jsx b/src/tree-select/_usage/index.jsx index b6380304d7..6250eae3a8 100644 --- a/src/tree-select/_usage/index.jsx +++ b/src/tree-select/_usage/index.jsx @@ -3,23 +3,19 @@ */ // @ts-nocheck -import React, { useState, useEffect, useMemo } from "react"; -import BaseUsage, { - useConfigChange, - usePanelChange, -} from "@site/src/components/BaseUsage"; -import jsxToString from "react-element-to-jsx-string"; +import React, { useState, useEffect, useMemo } from 'react'; +import BaseUsage, { useConfigChange, usePanelChange } from '@site/src/components/BaseUsage'; +import jsxToString from 'react-element-to-jsx-string'; -import configProps from "./props.json"; - -import { TreeSelect } from "tdesign-react"; +import { TreeSelect } from 'tdesign-react'; +import configProps from './props.json'; export default function Usage() { const [configList, setConfigList] = useState(configProps); const { changedProps, onConfigChange } = useConfigChange(configList); - const panelList = [{ label: "tree", value: "tree" }]; + const panelList = [{ label: 'tree-select', value: 'tree-select' }]; const { panel, onPanelChange } = usePanelChange(panelList); @@ -28,30 +24,30 @@ export default function Usage() { const defaultProps = { data: [ { - label: "广东省", - value: "guangdong", + label: '广东省', + value: 'guangdong', children: [ { - label: "广州市", - value: "guangzhou", + label: '广州市', + value: 'guangzhou', }, { - label: "深圳市", - value: "shenzhen", + label: '深圳市', + value: 'shenzhen', }, ], }, { - label: "江苏省", - value: "jiangsu", + label: '江苏省', + value: 'jiangsu', children: [ { - label: "南京市", - value: "nanjing", + label: '南京市', + value: 'nanjing', }, { - label: "苏州市", - value: "suzhou", + label: '苏州市', + value: 'suzhou', }, ], }, @@ -62,7 +58,7 @@ export default function Usage() { }, [changedProps]); const jsxStr = useMemo(() => { - if (!renderComp) return ""; + if (!renderComp) return ''; return jsxToString(renderComp); }, [renderComp]); diff --git a/src/tree-select/useTreeSelectUtils.ts b/src/tree-select/useTreeSelectUtils.ts index 850fed90d3..f00e4c9a60 100644 --- a/src/tree-select/useTreeSelectUtils.ts +++ b/src/tree-select/useTreeSelectUtils.ts @@ -3,9 +3,11 @@ import type { TreeSelectValue } from './type'; import Tree, { TreeNodeValue } from '../tree'; import TreeStore from '../_common/js/tree/tree-store'; import { usePersistFn } from '../_util/usePersistFn'; -import type { NodeOptions, TreeSelectProps } from './TreeSelect'; import { treeSelectDefaultProps } from './defaultProps'; +import type { NodeOptions, TreeSelectProps } from './TreeSelect'; +import type { TypeTreeNodeData } from '../_common/js/tree/types'; + export const useTreeSelectUtils = ( { data, treeProps, valueType }: TreeSelectProps, treeRef: MutableRefObject>, @@ -15,7 +17,7 @@ export const useTreeSelectUtils = ( ...treeSelectDefaultProps.treeProps, ...treeProps, }); - store.append(data); + store.append(data as Array); return store; }, [data, treeProps]); diff --git a/src/tree/Tree.tsx b/src/tree/Tree.tsx index b9283dceb0..f80320dc78 100644 --- a/src/tree/Tree.tsx +++ b/src/tree/Tree.tsx @@ -31,9 +31,6 @@ import type { TreeInstanceFunctions, TdTreeProps } from './type'; export type TreeProps = TdTreeProps & StyledProps; -/** - * 树组件 - */ const Tree = forwardRef((props: TreeProps, ref: React.Ref) => { const { treeClassNames, transitionNames, transitionClassNames, transitionDuration, locale } = useTreeConfig(); diff --git a/src/tree/_example/filter.jsx b/src/tree/_example/filter.jsx index 15a1849fac..4f750bbf1c 100644 --- a/src/tree/_example/filter.jsx +++ b/src/tree/_example/filter.jsx @@ -92,16 +92,19 @@ const items = [ }, ]; -const DEFAULT_EXPANDED = ['1.1.1']; - export default () => { const [filterText, setFilterText] = useState(''); + const [filterText2, setFilterText2] = useState(''); const filterByText = (node) => { const rs = node.data.label.indexOf(filterText) >= 0; return rs; }; + const filterByText2 = (node) => { + const rs = node.data.label.indexOf(filterText2) >= 0; + return rs; + }; return ( @@ -109,12 +112,22 @@ export default () => { + + + + ); diff --git a/src/tree/_example/vscroll.jsx b/src/tree/_example/vscroll.jsx index 55100cfb95..280a7564d7 100644 --- a/src/tree/_example/vscroll.jsx +++ b/src/tree/_example/vscroll.jsx @@ -30,7 +30,7 @@ export default () => { }, []); const handleScroll = () => { - treeRef.current.scrollTo({ key: '3.2', behavior: 'smooth' }); + treeRef.current.scrollTo({ index: '10', behavior: 'smooth' }); }; const defaultChecked = ['1.2', '2.2']; diff --git a/src/tree/hooks/useStore.ts b/src/tree/hooks/useStore.ts index 41d4777c41..f188ae97c3 100644 --- a/src/tree/hooks/useStore.ts +++ b/src/tree/hooks/useStore.ts @@ -1,14 +1,18 @@ -import { useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import cloneDeep from 'lodash/cloneDeep'; import useUpdateEffect from '../../_util/useUpdateEffect'; +import usePrevious from '../../hooks/usePrevious'; import TreeStore from '../../_common/js/tree/tree-store'; import { usePersistFn } from '../../_util/usePersistFn'; + import type { TdTreeProps } from '../type'; import type { TypeEventState } from '../interface'; +import type { TypeTreeNodeData } from '../../_common/js/tree/types'; export function useStore(props: TdTreeProps, refresh: () => void): TreeStore { const storeRef = useRef(); - + const [filterChanged, toggleFilterChanged] = useState(false); + const [prevExpanded, changePrevExpanded] = useState(null); const { data, keys, @@ -33,8 +37,43 @@ export function useStore(props: TdTreeProps, refresh: () => void): TreeStore { allowFoldNodeOnFilter = false, } = props; + const preFilter = usePrevious(filter); + + useEffect(() => { + if (!allowFoldNodeOnFilter) return; + toggleFilterChanged(JSON.stringify(preFilter) !== JSON.stringify(filter)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filter, allowFoldNodeOnFilter]); + + // 在 update 之后检查,如果之前 filter 有变更,则检查路径节点是否需要展开 + // 如果 filter 属性被清空,则重置为开启搜索之前的结果 + const expandFilterPath = () => { + if (!allowFoldNodeOnFilter || !filterChanged) return; + // 确保 filter 属性未变更时,不会重复检查展开状态 + toggleFilterChanged(false); + const store = storeRef.current; + if (props.filter) { + if (!prevExpanded) changePrevExpanded(store.getExpanded()); // 缓存之前的展开状态 + + // 展开搜索命中节点的路径节点 + const pathValues = []; + const allNodes = store.getNodes(); + allNodes.forEach((node) => { + if (node.vmIsLocked) { + pathValues.push(node.value); + } + }); + store.setExpanded(pathValues); + } else if (prevExpanded) { + // filter 属性置空,还原最开始的展开状态 + store.replaceExpanded(prevExpanded); + changePrevExpanded(null); + } + }; + // 传入 TreeStore 中调用的,但是每次都需要使用最新的值,所以使用 usePersistFn const handleUpdate = usePersistFn(() => { + expandFilterPath(); refresh(); }); @@ -85,7 +124,7 @@ export function useStore(props: TdTreeProps, refresh: () => void): TreeStore { list = []; } - store.append(list); + store.append(list as Array); // 刷新节点,必须在配置选中之前执行 // 这样选中态联动判断才能找到父节点 @@ -124,7 +163,7 @@ export function useStore(props: TdTreeProps, refresh: () => void): TreeStore { const checked = store.getChecked(); const actived = store.getActived(); store.removeAll(); - store.append(data); + store.append(data as Array); store.setChecked(checked); store.setActived(actived); store.setExpanded(expanded); diff --git a/src/tree/type.ts b/src/tree/type.ts index d5bc2456a5..064e431dcf 100644 --- a/src/tree/type.ts +++ b/src/tree/type.ts @@ -99,11 +99,6 @@ export interface TdTreeProps { * @default [] */ expanded?: Array; - /** - * 展开的节点值,非受控属性 - * @default [] - */ - defaultExpanded?: Array; /** * 节点过滤方法,只呈现返回值为 true 的节点,泛型 `T` 表示树节点 TS 类型 */ diff --git a/test/snap/__snapshots__/csr.test.jsx.snap b/test/snap/__snapshots__/csr.test.jsx.snap index 96dd77b522..024b4f27e1 100644 --- a/test/snap/__snapshots__/csr.test.jsx.snap +++ b/test/snap/__snapshots__/csr.test.jsx.snap @@ -283164,6 +283164,47 @@ exports[`csr snapshot test > csr test src/tree/_example/filter.jsx 1`] = ` Tree Empty Data
  • +
    +
    + + + filter: + + +
    +
    + +
    +
    +
    +
    +
    +
    + Tree Empty Data +
    +
    , @@ -283213,6 +283254,47 @@ exports[`csr snapshot test > csr test src/tree/_example/filter.jsx 1`] = ` Tree Empty Data +
    +
    + + + filter: + + +
    +
    + +
    +
    +
    +
    +
    +
    + Tree Empty Data +
    +
    , "debug": [Function], @@ -286354,7 +286436,7 @@ exports[`csr snapshot test > csr test src/tree-select/_example/lazy.jsx 1`] = ` >
    csr test src/tree-select/_example/lazy.jsx 1`] = ` placeholder="请选择" readonly="" type="text" - value="null" + value="" /> csr test src/tree-select/_example/lazy.jsx 1`] = ` >
    csr test src/tree-select/_example/lazy.jsx 1`] = ` placeholder="请选择" readonly="" type="text" - value="null" + value="" /> ssr test src/tree/_example/expand-level.jsx 1`] = ` exports[`ssr snapshot test > ssr test src/tree/_example/expand-mutex.jsx 1`] = `"
    Tree Empty Data
    "`; -exports[`ssr snapshot test > ssr test src/tree/_example/filter.jsx 1`] = `"
    filter:
    Tree Empty Data
    "`; +exports[`ssr snapshot test > ssr test src/tree/_example/filter.jsx 1`] = `"
    filter:
    Tree Empty Data
    filter:
    Tree Empty Data
    "`; exports[`ssr snapshot test > ssr test src/tree/_example/icon.jsx 1`] = `"

    render 1:

    Tree Empty Data

    render 2:

    Tree Empty Data
    "`; @@ -1108,7 +1108,7 @@ exports[`ssr snapshot test > ssr test src/tree-select/_example/collapsed.jsx 1`] exports[`ssr snapshot test > ssr test src/tree-select/_example/filterable.jsx 1`] = `"
    请选择
    "`; -exports[`ssr snapshot test > ssr test src/tree-select/_example/lazy.jsx 1`] = `"
    "`; +exports[`ssr snapshot test > ssr test src/tree-select/_example/lazy.jsx 1`] = `"
    "`; exports[`ssr snapshot test > ssr test src/tree-select/_example/multiple.jsx 1`] = `"
    广州市深圳市
    "`;