Skip to content

Commit 8d02f9b

Browse files
committed
Merge remote-tracking branch 'origin' into fix/menu/maxHeight
2 parents 95ddf78 + a35c86a commit 8d02f9b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1756
-802
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
"@types/testing-library__jest-dom": "5.14.2",
8383
"@typescript-eslint/eslint-plugin": "^5.13.0",
8484
"@typescript-eslint/parser": "^5.13.0",
85-
"@vitejs/plugin-react": "^4.3.1",
85+
"@vitejs/plugin-react": "^5.1.1",
8686
"@vitest/coverage-istanbul": "^2.1.1",
8787
"@vitest/coverage-v8": "^2.1.1",
8888
"@vitest/ui": "^3.1.1",

packages/common

Submodule common updated 849 files

packages/components/cascader/cascader.en-US.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ popupVisible | Boolean | - | \- | N
3838
defaultPopupVisible | Boolean | - | uncontrolled property | N
3939
prefixIcon | TElement | - | Typescript: `TNode`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
4040
readonly | Boolean | false | \- | N
41-
reserveKeyword | Boolean | false | \- | N
41+
reserveKeyword | Boolean | true | \- | N
4242
selectInputProps | Object | - | Typescript: `SelectInputProps`[SelectInput API Documents](./select-input?tab=api)[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/cascader/type.ts) | N
4343
showAllLevels | Boolean | true | \- | N
4444
size | String | medium | options: large/medium/small。Typescript: `SizeEnum`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N

packages/components/cascader/cascader.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ popupVisible | Boolean | - | 是否显示下拉框 | N
3838
defaultPopupVisible | Boolean | - | 是否显示下拉框。非受控属性 | N
3939
prefixIcon | TElement | - | 组件前置图标。TS 类型:`TNode`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
4040
readonly | Boolean | false | 只读状态,值为真会隐藏输入框,且无法打开下拉框 | N
41-
reserveKeyword | Boolean | false | 多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词 | N
41+
reserveKeyword | Boolean | true | 多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词 | N
4242
selectInputProps | Object | - | 透传 SelectInput 筛选器输入框组件的全部属性。TS 类型:`SelectInputProps`[SelectInput API Documents](./select-input?tab=api)[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/cascader/type.ts) | N
4343
showAllLevels | Boolean | true | 选中值使用完整路径,输入框在单选时也显示完整路径 | N
4444
size | String | medium | 组件尺寸。可选项:large/medium/small。TS 类型:`SizeEnum`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N

packages/components/cascader/defaultProps.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const cascaderDefaultProps: TdCascaderProps = {
1717
options: [],
1818
placeholder: undefined,
1919
readonly: false,
20-
reserveKeyword: false,
20+
reserveKeyword: true,
2121
showAllLevels: true,
2222
size: 'medium',
2323
status: 'default',

packages/components/cascader/hooks.tsx

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,29 @@
1-
import { useState, useEffect, useMemo, useRef } from 'react';
2-
1+
import { useEffect, useMemo, useRef, useState } from 'react';
32
import { isArray, isEqual, isFunction } from 'lodash-es';
43

54
import TreeStore from '@tdesign/common-js/tree-v1/tree-store';
65
import type { TypeTreeNodeData } from '@tdesign/common-js/tree-v1/types';
7-
import { getTreeValue, getCascaderValue, isEmptyValues, isValueInvalid } from './core/helper';
8-
import { treeNodesEffect, treeStoreExpendEffect } from './core/effect';
96

107
import useControlled from '../hooks/useControlled';
8+
import useDefaultProps from '../hooks/useDefaultProps';
9+
import { treeNodesEffect, treeStoreExpendEffect } from './core/effect';
10+
import { getCascaderValue, getTreeValue, isEmptyValues, isValueInvalid } from './core/helper';
11+
import { cascaderDefaultProps } from './defaultProps';
1112

13+
import type { TreeOptionData } from '../common';
1214
import type {
13-
TreeNode,
14-
TreeNodeValue,
15-
TdCascaderProps,
16-
TreeNodeModel,
1715
CascaderChangeSource,
1816
CascaderValue,
17+
TdCascaderProps,
18+
TreeNode,
19+
TreeNodeModel,
20+
TreeNodeValue,
1921
} from './interface';
2022

21-
import { TreeOptionData } from '../common';
23+
export const useCascaderContext = (originalProps: TdCascaderProps) => {
24+
const props = useDefaultProps(originalProps, cascaderDefaultProps);
25+
const { disabled, options, keys = {}, checkStrictly, lazy, multiple, reserveKeyword, valueMode, load } = props;
2226

23-
export const useCascaderContext = (props: TdCascaderProps) => {
2427
const [innerValue, setInnerValue] = useControlled(props, 'value', props.onChange);
2528
const [innerPopupVisible, setPopupVisible] = useControlled(props, 'popupVisible', props.onPopupVisibleChange);
2629

@@ -100,8 +103,6 @@ export const useCascaderContext = (props: TdCascaderProps) => {
100103
* build tree
101104
*/
102105

103-
const { disabled, options = [], keys = {}, checkStrictly = false, lazy = true, load, valueMode = 'onlyLeaf' } = props;
104-
105106
const optionCurrent = useRef([]);
106107

107108
useEffect(() => {
@@ -171,8 +172,12 @@ export const useCascaderContext = (props: TdCascaderProps) => {
171172
} else {
172173
setScopeVal(multiple ? [] : '');
173174
}
175+
176+
if (multiple && !reserveKeyword) {
177+
setInputVal('');
178+
}
174179
// eslint-disable-next-line react-hooks/exhaustive-deps
175-
}, [innerValue]);
180+
}, [innerValue, multiple, reserveKeyword]);
176181

177182
useEffect(() => {
178183
if (!treeStore) return;
@@ -187,7 +192,7 @@ export const useCascaderContext = (props: TdCascaderProps) => {
187192
useEffect(() => {
188193
if (!treeStore) return;
189194
treeStore.replaceChecked(getTreeValue(scopeVal));
190-
}, [options, scopeVal, treeStore, cascaderContext.multiple]);
195+
}, [options, scopeVal, treeStore, multiple]);
191196

192197
useEffect(() => {
193198
if (!innerPopupVisible && isFilterable) {

packages/components/form/Form.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useRef, useImperativeHandle, useEffect } from 'react';
1+
import React, { useEffect, useImperativeHandle, useRef } from 'react';
22
import classNames from 'classnames';
33
import forwardRefWithStatics from '../_util/forwardRefWithStatics';
44
import noop from '../_util/noop';
@@ -39,7 +39,6 @@ const Form = forwardRefWithStatics(
3939
resetType,
4040
rules,
4141
errorMessage = globalFormConfig.errorMessage,
42-
preventSubmitDefault,
4342
disabled,
4443
readonly,
4544
children,
@@ -77,17 +76,10 @@ const Form = forwardRefWithStatics(
7776
}
7877

7978
function onFormItemValueChange(changedValue: Record<string, unknown>) {
80-
const allFields = formInstance.getFieldsValue(true);
81-
onValuesChange(changedValue, allFields);
82-
}
83-
84-
function onKeyDownHandler(e: React.KeyboardEvent<HTMLFormElement>) {
85-
// 禁用 input 输入框回车自动提交 form
86-
if ((e.target as Element).tagName.toLowerCase() !== 'input') return;
87-
if (preventSubmitDefault && e.key === 'Enter') {
88-
e.preventDefault?.();
89-
e.stopPropagation?.();
90-
}
79+
requestAnimationFrame(() => {
80+
const allFields = formInstance.getFieldsValue(true);
81+
onValuesChange(changedValue, allFields);
82+
});
9183
}
9284

9385
return (
@@ -121,7 +113,6 @@ const Form = forwardRefWithStatics(
121113
className={formClass}
122114
onSubmit={formInstance.submit}
123115
onReset={onResetHandler}
124-
onKeyDown={onKeyDownHandler}
125116
>
126117
{children}
127118
</form>

packages/components/form/FormContext.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,14 @@ export default FormContext;
5151

5252
export const FormListContext = React.createContext<{
5353
name: NamePath;
54+
fullPath?: NamePath;
5455
rules: TdFormListProps['rules'];
55-
formListMapRef: React.RefObject<Map<any, React.RefObject<FormItemInstance>>>;
56+
formListMapRef: React.RefObject<Map<NamePath, React.RefObject<FormItemInstance>>>;
5657
initialData: TdFormListProps['initialData'];
5758
form?: InternalFormInstance;
5859
}>({
5960
name: undefined,
61+
fullPath: undefined,
6062
rules: undefined,
6163
formListMapRef: undefined,
6264
initialData: [],

packages/components/form/FormItem.tsx

Lines changed: 37 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
1-
import { flattenDeep, get, isEqual, isFunction, isObject, isString, merge, set, unset } from 'lodash-es';
21
import React, { forwardRef, ReactNode, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
32
import {
43
CheckCircleFilledIcon as TdCheckCircleFilledIcon,
54
CloseCircleFilledIcon as TdCloseCircleFilledIcon,
65
ErrorCircleFilledIcon as TdErrorCircleFilledIcon,
76
} from 'tdesign-icons-react';
7+
import { get, isEqual, isFunction, isObject, isString, set, unset } from 'lodash-es';
8+
89
import useConfig from '../hooks/useConfig';
910
import useDefaultProps from '../hooks/useDefaultProps';
1011
import useGlobalIcon from '../hooks/useGlobalIcon';
1112
import { useLocaleReceiver } from '../locale/LocalReceiver';
12-
import { ValidateStatus } from './const';
13+
import { READONLY_SUPPORTED_COMP, ValidateStatus } from './const';
1314
import { formItemDefaultProps } from './defaultProps';
1415
import { useFormContext, useFormListContext } from './FormContext';
1516
import { parseMessage, validate as validateModal } from './formModel';
1617
import { HOOK_MARK } from './hooks/useForm';
1718
import useFormItemInitialData, { ctrlKeyMap } from './hooks/useFormItemInitialData';
1819
import useFormItemStyle from './hooks/useFormItemStyle';
19-
import { calcFieldValue } from './utils';
20+
import { calcFieldValue, concatName } from './utils';
2021

2122
import type { StyledProps } from '../common';
2223
import type {
@@ -37,13 +38,14 @@ export interface FormItemProps extends TdFormItemProps, StyledProps {
3738

3839
export interface FormItemInstance {
3940
name?: NamePath;
41+
fullPath?: NamePath[];
4042
value?: any;
41-
isUpdated?: boolean;
43+
initialData?: any;
4244
isFormList?: boolean;
4345
formListMapRef?: React.MutableRefObject<Map<any, any>>;
4446
getValue?: () => any;
45-
setValue?: (newVal: any, originalData?: any) => void;
46-
setField?: (field: Omit<FieldData, 'name'>, originalData?: any) => void;
47+
setValue?: (newVal: any) => void;
48+
setField?: (field: Omit<FieldData, 'name'>) => void;
4749
validate?: (trigger?: ValidateTriggerType, showErrorMessage?: boolean) => Promise<Record<string, any>>;
4850
validateOnly?: (trigger?: ValidateTriggerType) => Promise<Record<string, any>>;
4951
resetField?: (type?: TdFormProps['resetType']) => void;
@@ -102,7 +104,10 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
102104
requiredMark = requiredMarkFromContext,
103105
} = props;
104106

105-
const { getDefaultInitialData } = useFormItemInitialData(name);
107+
const { fullPath: parentFullPath } = useFormListContext();
108+
const fullPath = concatName(parentFullPath, name);
109+
110+
const { getDefaultInitialData } = useFormItemInitialData(name, fullPath);
106111

107112
const [, forceUpdate] = useState({}); // custom render state
108113
const [freeShowErrorMessage, setFreeShowErrorMessage] = useState(undefined);
@@ -111,22 +116,16 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
111116
const [verifyStatus, setVerifyStatus] = useState('validating');
112117
const [resetValidating, setResetValidating] = useState(false);
113118
const [needResetField, setNeedResetField] = useState(false);
114-
const [formValue, setFormValue] = useState(() => {
115-
const fieldName = flattenDeep([formListName, name]);
116-
const storeValue = get(form?.store, fieldName);
117-
return (
118-
storeValue ??
119-
getDefaultInitialData({
120-
children,
121-
initialData,
122-
})
123-
);
124-
});
119+
const [formValue, setFormValue] = useState(() =>
120+
getDefaultInitialData({
121+
children,
122+
initialData,
123+
}),
124+
);
125125

126126
const formItemRef = useRef<FormItemInstance>(null); // 当前 formItem 实例
127127
const innerFormItemsRef = useRef([]);
128128
const shouldEmitChangeRef = useRef(false); // onChange 冒泡开关
129-
const isUpdatedRef = useRef(false); // 校验开关
130129
const shouldValidate = useRef(false); // 校验开关
131130
const valueRef = useRef(formValue); // 当前最新值
132131
const errorListMapRef = useRef(new Map());
@@ -171,22 +170,11 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
171170
const { setPrevStore } = form?.getInternalHooks?.(HOOK_MARK) || {};
172171
setPrevStore?.(form?.getFieldsValue?.(true));
173172
shouldEmitChangeRef.current = shouldEmitChange;
174-
isUpdatedRef.current = true;
175173
shouldValidate.current = validate;
176174
valueRef.current = newVal;
177-
178-
let fieldName = [].concat(name);
179-
let fieldValue = formValue;
180-
if (formListName) {
181-
fieldName = [].concat(formListName, name);
182-
fieldValue = get(form?.store, fieldName);
183-
}
184-
185-
fieldName = fieldName.filter((item) => item !== undefined);
186-
187-
if (!fieldName) return;
175+
const fieldValue = get(form?.store, fullPath);
188176
if (isEqual(fieldValue, newVal)) return;
189-
set(form?.store, fieldName, newVal);
177+
set(form?.store, fullPath, newVal);
190178
setFormValue(newVal);
191179
};
192180

@@ -332,7 +320,6 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
332320
// blur 下触发校验
333321
function handleItemBlur() {
334322
const filterRules = innerRules.filter((item) => item.trigger === 'blur');
335-
336323
filterRules.length && validate('blur');
337324
}
338325

@@ -429,21 +416,23 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
429416
// 记录填写 name 属性 formItem
430417
if (typeof name === 'undefined') return;
431418

432-
// formList 下特殊处理
419+
// FormList 下特殊处理
433420
if (formListName && isSameForm) {
434-
formListMapRef.current.set(name, formItemRef);
421+
formListMapRef.current.set(fullPath, formItemRef);
422+
set(form?.store, fullPath, formValue);
435423
return () => {
436424
// eslint-disable-next-line react-hooks/exhaustive-deps
437-
formListMapRef.current.delete(name);
438-
unset(form?.store, name);
425+
formListMapRef.current.delete(fullPath);
426+
unset(form?.store, fullPath);
439427
};
440428
}
441429
if (!formMapRef) return;
442-
formMapRef.current.set(name, formItemRef);
430+
formMapRef.current.set(fullPath, formItemRef);
431+
set(form?.store, fullPath, formValue);
443432
return () => {
444433
// eslint-disable-next-line react-hooks/exhaustive-deps
445-
formMapRef.current.delete(name);
446-
unset(form?.store, name);
434+
formMapRef.current.delete(fullPath);
435+
unset(form?.store, fullPath);
447436
};
448437
// eslint-disable-next-line react-hooks/exhaustive-deps
449438
}, [snakeName, formListName]);
@@ -455,18 +444,9 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
455444
// 控制是否需要校验
456445
if (!shouldValidate.current) return;
457446

458-
// value change event
459447
if (typeof name !== 'undefined' && shouldEmitChangeRef.current) {
460-
if (formListName && isSameForm) {
461-
// 整理 formItem 的值
462-
const formListValue = merge([], calcFieldValue(name, formValue));
463-
// 整理 formList 的值
464-
const fieldValue = calcFieldValue(formListName, formListValue);
465-
onFormItemValueChange?.({ ...fieldValue });
466-
} else {
467-
const fieldValue = calcFieldValue(name, formValue);
468-
onFormItemValueChange?.({ ...fieldValue });
469-
}
448+
const fieldValue = calcFieldValue(fullPath, formValue);
449+
onFormItemValueChange?.(fieldValue);
470450
}
471451

472452
const filterRules = innerRules.filter((item) => (item.trigger || 'change') === 'change');
@@ -478,8 +458,10 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
478458
// 暴露 ref 实例方法
479459
const instance: FormItemInstance = {
480460
name,
461+
fullPath,
481462
value: formValue,
482-
isUpdated: isUpdatedRef.current,
463+
initialData,
464+
isFormList: false,
483465
getValue: () => valueRef.current,
484466
setValue: (newVal: any) => updateFormValue(newVal, true, true),
485467
setField,
@@ -524,9 +506,11 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
524506
ctrlKey = ctrlKeyMap.get(child.type) || 'value';
525507
}
526508
const childProps = child.props as any;
509+
// @ts-ignore
510+
const readOnlyKey = READONLY_SUPPORTED_COMP.includes(child?.type?.displayName) ? 'readonly' : 'readOnly';
527511
return React.cloneElement(child, {
528512
disabled: disabledFromContext,
529-
readOnly: readonlyFromContext,
513+
[readOnlyKey]: readonlyFromContext,
530514
...childProps,
531515
[ctrlKey]: formValue,
532516
onChange: (value: any, ...args: any[]) => {

0 commit comments

Comments
 (0)