Skip to content

Commit eb5b706

Browse files
chore: merge develop
2 parents 443c99d + 78cadc9 commit eb5b706

21 files changed

Lines changed: 1230 additions & 403 deletions

File tree

packages/components/affix/Affix.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ const Affix = forwardRef<AffixRef, AffixProps>((props, ref) => {
5959
let fixedTop: number | false;
6060
if (props.offsetBottom !== undefined && props.offsetTop === undefined) {
6161
const bottomThreshold = containerToBottom - (offsetBottom ?? 0);
62-
if (wrapToBottom >= bottomThreshold) {
62+
// When the container has scrolled out of the viewport
63+
// the affix element should not be fixed to avoid a negative top value
64+
if (containerToBottom > 0 && wrapToBottom >= bottomThreshold) {
6365
fixedTop = bottomThreshold - wrapHeight;
6466
} else {
6567
fixedTop = false;

packages/components/affix/_example/container.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import React, { useState } from 'react';
1+
import React, { useRef } from 'react';
22
import { Affix, Button } from 'tdesign-react';
33

44
export default function ContainerExample() {
5-
const [container, setContainer] = useState(null);
5+
const containerRef = useRef<HTMLDivElement>();
66

77
const backgroundStyle = {
88
height: '1500px',
@@ -18,7 +18,7 @@ export default function ContainerExample() {
1818

1919
return (
2020
<div
21-
ref={setContainer}
21+
ref={containerRef}
2222
style={{
2323
border: '1px solid var(--component-stroke)',
2424
borderRadius: '3px',
@@ -29,10 +29,10 @@ export default function ContainerExample() {
2929
}}
3030
>
3131
<div style={backgroundStyle}>
32-
<Affix zIndex={10} offsetTop={50} container={container}>
32+
<Affix zIndex={10} offsetTop={50} container={() => containerRef.current}>
3333
<Button>Top</Button>
3434
</Affix>
35-
<Affix offsetBottom={50} container={container}>
35+
<Affix offsetBottom={50} container={() => containerRef.current}>
3636
<Button>Bottom</Button>
3737
</Affix>
3838
</div>

packages/components/cascader/core/effect.ts

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { isNumber, isFunction, cloneDeep } from 'lodash-es';
2-
import { TreeNode, CascaderContextType, TdCascaderProps, TreeNodeValue, TreeNodeModel } from '../interface';
1+
import { cloneDeep, isFunction, isNumber } from 'lodash-es';
2+
import { pathToKey } from '@tdesign/common-js/tree-v1/tree-node-model';
33
import { getFullPathLabel, getTreeValue } from './helper';
44

5+
import type { CascaderContextType, TdCascaderProps, TreeNode, TreeNodeModel, TreeNodeValue } from '../interface';
6+
57
/**
68
* 点击item的副作用
79
* @param propsTrigger
@@ -47,15 +49,25 @@ export function expendClickEffect(
4749
}
4850

4951
if (!multiple && (node.isLeaf() || checkStrictly) && trigger === 'click') {
52+
// 当 trigger 为 hover 时,点击节点一定是关闭 panel 的操作
53+
const shouldClosePanel = !checkStrictly || propsTrigger === 'hover';
54+
55+
// 再次点击已选中节点,无需重复更新值,但仍需关闭弹窗
56+
if (node.checked) {
57+
if (shouldClosePanel) {
58+
setVisible(false, {});
59+
}
60+
return;
61+
}
62+
5063
treeStore.resetChecked();
51-
const checked = node.setChecked(!node.checked);
64+
const checked = node.setChecked(true);
5265
const [value] = checked;
5366

5467
// 非受控状态下更新状态
5568
setValue(valueType === 'single' ? value : node.getPath().map((item) => item.value), 'check', node.getModel());
5669

57-
// 当 trigger 为 hover 时 ,点击节点一定是关闭 panel 的操作
58-
if (!checkStrictly || propsTrigger === 'hover') {
70+
if (shouldClosePanel) {
5971
setVisible(false, {});
6072
}
6173
}
@@ -211,31 +223,46 @@ export const treeNodesEffect = (
211223
* @param treeStore
212224
* @param treeValue
213225
* @param expend
226+
* @param valueType
214227
*/
215228
export const treeStoreExpendEffect = (
216229
treeStore: CascaderContextType['treeStore'],
217230
value: CascaderContextType['value'],
218231
expend: TreeNodeValue[],
232+
valueType?: TdCascaderProps['valueType'],
219233
) => {
220234
const treeValue = getTreeValue(value);
221235

222236
if (!treeStore) return;
237+
238+
const allowDuplicateValue = treeStore.config?.allowDuplicateValue;
239+
223240
// init expanded, 无expend状态时设置
224241
if (Array.isArray(treeValue) && expend.length === 0) {
225242
const expandedMap = new Map();
226-
const [val] = treeValue;
227-
if (val) {
228-
expandedMap.set(val, true);
229-
const node = treeStore.getNode(val);
230-
if (!node) {
231-
treeStore.refreshNodes();
232-
return;
243+
244+
// 对于 valueType='full',value 本身就是完整路径,用它来查找节点
245+
let node = null;
246+
if (valueType === 'full' && Array.isArray(value) && value.length > 0) {
247+
node = treeStore.getNode(value as TreeNodeValue[]);
248+
} else {
249+
const [val] = treeValue;
250+
if (val) {
251+
expandedMap.set(val, true);
252+
node = treeStore.getNode(val);
233253
}
254+
}
255+
256+
if (node) {
234257
node.getParents().forEach((tn: TreeNode) => {
235-
expandedMap.set(tn.value, true);
258+
const expandedKey = allowDuplicateValue ? pathToKey(tn.getPath().map((n) => n.value)) : tn.value;
259+
expandedMap.set(expandedKey, true);
236260
});
237261
const expandedArr = Array.from(expandedMap.keys());
238262
treeStore.replaceExpanded(expandedArr);
263+
} else if (treeValue.length > 0) {
264+
treeStore.refreshNodes();
265+
return;
239266
} else {
240267
treeStore.resetExpanded();
241268
}

packages/components/cascader/core/helper.ts

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { PATH_SEPARATOR } from '@tdesign/common-js/tree-v1/tree-node-model';
12
import { isEmpty } from 'lodash-es';
2-
import {
3-
TreeNode,
3+
4+
import type {
45
CascaderContextType,
5-
TdCascaderProps,
66
CascaderValue,
7+
TdCascaderProps,
8+
TreeNode,
79
TreeNodeValue,
810
TreeOptionData,
911
} from '../interface';
@@ -15,20 +17,34 @@ import {
1517
* @returns
1618
*/
1719
export function getSingleContent(cascaderContext: CascaderContextType): string {
18-
const { value, multiple, treeStore, showAllLevels } = cascaderContext;
19-
const BooleanIsFalseExceptZero = 0;
20-
if (multiple || (!value && value !== BooleanIsFalseExceptZero)) return '';
21-
22-
if (Array.isArray(value)) return '';
23-
const node = treeStore && treeStore.getNodes(value as TreeNodeValue | TreeNode);
24-
if (!(node && node.length)) {
25-
return value as string;
20+
const { value, multiple, treeStore, showAllLevels, valueType } = cascaderContext;
21+
22+
const isEmpty = (!value && value !== 0) || (Array.isArray(value) && value.length === 0);
23+
const isInvalidFullPathValue = valueType === 'full' && !Array.isArray(value);
24+
if (multiple || isEmpty || isInvalidFullPathValue) return '';
25+
26+
const formatContent = (path: TreeNode[]) => {
27+
if (!path?.length) return '';
28+
return showAllLevels ? path.map((node) => node.label).join(` ${PATH_SEPARATOR} `) : path[path.length - 1].label;
29+
};
30+
31+
const getDefaultDisplay = (val: any) => (Array.isArray(val) ? val.join(` ${PATH_SEPARATOR} `) : String(val));
32+
33+
// valueType = 'full'
34+
if (valueType === 'full' && Array.isArray(value) && value.length > 0) {
35+
const node = treeStore?.getNode(value as string[]);
36+
const path = node?.getPath();
37+
return path?.length ? formatContent(path) : getDefaultDisplay(value);
2638
}
27-
const path = node && node[0].getPath();
28-
if (path && path.length) {
29-
return showAllLevels ? path.map((node: TreeNode) => node.label).join(' / ') : path[path.length - 1].label;
39+
40+
// valueType = 'single'
41+
const nodes = treeStore?.getNodes(value as TreeNodeValue | TreeNode);
42+
if (!nodes?.length) {
43+
return getDefaultDisplay(value);
3044
}
31-
return value as string;
45+
46+
const path = nodes[0].getPath();
47+
return path?.length ? formatContent(path) : getDefaultDisplay(value);
3248
}
3349

3450
/**
@@ -38,18 +54,23 @@ export function getSingleContent(cascaderContext: CascaderContextType): string {
3854
* @returns
3955
*/
4056
export function getMultipleContent(cascaderContext: CascaderContextType) {
41-
const { value, multiple, treeStore, showAllLevels } = cascaderContext;
57+
const { value, multiple, treeStore, showAllLevels, valueType } = cascaderContext;
4258

4359
if (!multiple) return [];
4460
if (multiple && !Array.isArray(value)) return [];
4561

46-
const node = treeStore && treeStore.getNodes(value as TreeNodeValue | TreeNode);
47-
if (!node) return [];
48-
4962
return (value as TreeNodeValue[])
5063
.map((item: TreeNodeValue) => {
51-
const node = treeStore.getNodes(item);
52-
return showAllLevels ? getFullPathLabel(node?.[0]) : node?.[0]?.label;
64+
let node: TreeNode | undefined;
65+
// valueType='full' 时,item 是路径数组,使用 getNode 获取节点
66+
if (valueType === 'full' && Array.isArray(item)) {
67+
node = treeStore?.getNode(item as string[]);
68+
} else {
69+
const nodes = treeStore?.getNodes(item);
70+
node = nodes?.[0];
71+
}
72+
if (!node) return undefined;
73+
return showAllLevels ? getFullPathLabel(node) : node?.label;
5374
})
5475
.filter((item) => !!item);
5576
}
@@ -107,20 +128,16 @@ export const getTreeValue = (value: CascaderContextType['value']) => {
107128
};
108129

109130
/**
110-
* 按数据类型计算通用数值
111-
* @param value
112-
* @param showAllLevels
113-
* @param multiple
114-
* @returns
131+
* 获取用于展开的节点值
115132
*/
116-
export const getCascaderValue = (value: CascaderValue, valueType: TdCascaderProps['valueType'], multiple: boolean) => {
117-
if (valueType === 'single') {
118-
return value;
119-
}
120-
if (multiple) {
121-
return (value as Array<CascaderValue>).map((item: TreeNodeValue[]) => item[item.length - 1]);
133+
export const getExpandValue = (value: CascaderContextType['value'], valueType?: TdCascaderProps['valueType']) => {
134+
if (valueType === 'full' && Array.isArray(value) && value.length > 0) {
135+
// valueType='full' 时,value 是完整路径数组
136+
// 返回最后一个值用于展开
137+
return value[value.length - 1] as TreeNodeValue;
122138
}
123-
return value[(value as Array<CascaderValue>).length - 1];
139+
const treeValue = getTreeValue(value);
140+
return treeValue[0];
124141
};
125142

126143
/**
@@ -141,6 +158,25 @@ export function isEmptyValues(value: unknown): boolean {
141158
* @returns boolean
142159
*/
143160
export function isValueInvalid(value: CascaderValue, cascaderContext: CascaderContextType) {
144-
const { multiple, showAllLevels } = cascaderContext;
145-
return (multiple && !Array.isArray(value)) || (!multiple && Array.isArray(value) && !showAllLevels);
161+
const { multiple, showAllLevels, valueType } = cascaderContext;
162+
163+
// 多选模式:
164+
// value 必须是数组
165+
if (multiple) {
166+
return !Array.isArray(value);
167+
}
168+
169+
// 单选模式:
170+
// valueType === 'full' 时,使用完整路径数组
171+
if (valueType === 'full') {
172+
return !Array.isArray(value);
173+
}
174+
175+
// 其它情况默认 value 为非数组
176+
// 若是数组,仅在 showAllLevels=true 时合法
177+
if (Array.isArray(value)) {
178+
return !showAllLevels;
179+
}
180+
181+
return false;
146182
}

0 commit comments

Comments
 (0)