Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
779 changes: 535 additions & 244 deletions src/table/Table.tsx

Large diffs are not rendered by default.

841 changes: 632 additions & 209 deletions src/table/components/EditableTable/index.tsx

Large diffs are not rendered by default.

971 changes: 654 additions & 317 deletions src/utils/useEditableArray/index.tsx

Large diffs are not rendered by default.

208 changes: 139 additions & 69 deletions src/utils/useEditableMap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { message } from 'antd';
import type React from 'react';
import { useCallback, useMemo, useRef } from 'react';
import { useRefFunction } from '..';
import { useIntl } from '../../provider';
import type {
ActionRenderConfig,
Expand Down Expand Up @@ -77,30 +78,41 @@ export function useEditableMap<RecordType>(
}
: undefined,
});

/** 一个用来标志的set 提供了方便的 api 来去重什么的 */
const editableKeysSet = useMemo(() => {
const keys =
editableType === 'single' ? editableKeys?.slice(0, 1) : editableKeys;
return new Set(keys);
}, [(editableKeys || []).join(','), editableType]);
editableType === 'single'
? editableKeys?.slice(0, 1) || []
: editableKeys || [];
return new Set(keys.map((key) => String(key)));
}, [editableKeys, editableType]);

/**
* 检查 key 是否在编辑列表中
* 使用 editableKeysSet 进行快速查找,性能更好
*/
const checkKeyInEditableList = useRefFunction(
(recordKey: RecordKey): boolean => {
const keyStr = String(recordKeyToString(recordKey));
return editableKeysSet.has(keyStr);
},
);

/** 这行是不是编辑状态 */
const isEditable = useCallback(
(recordKey: RecordKey) => {
if (editableKeys?.includes(recordKeyToString(recordKey))) return true;
return false;
return checkKeyInEditableList(recordKey);
},
[(editableKeys || []).join(',')],
[checkKeyInEditableList],
);

/**
* 进入编辑状态
*
* @param recordKey
* 验证是否可以开始编辑
*/
const startEditable = (recordKey: RecordKey, recordValue?: any) => {
// 如果是单行的话,不允许多行编辑
if (editableKeysSet.size > 0 && editableType === 'single') {
const validateCanStartEdit = useRefFunction((): boolean => {
// 如果是单行模式,检查是否已有编辑中的行
if (editableType === 'single' && editableKeys && editableKeys.length > 0) {
warning(
props.onlyOneLineEditorAlertMessage ||
intl.getMessage(
Expand All @@ -110,80 +122,138 @@ export function useEditableMap<RecordType>(
);
return false;
}
preEditRowRef.current =
recordValue ??
get(
props.dataSource,
Array.isArray(recordKey)
? (recordKey as string[])
: [recordKey as string],
) ??
null;
editableKeysSet.add(recordKeyToString(recordKey));
setEditableRowKeys(Array.from(editableKeysSet));
return true;
};
});

/**
* 退出编辑状态
* 进入编辑状态
*
* @param recordKey
* @param recordValue
*/
const cancelEditable = (recordKey: RecordKey) => {
// 防止多次渲染
editableKeysSet.delete(recordKeyToString(recordKey));
setEditableRowKeys(Array.from(editableKeysSet));
return true;
};
const startEditable = useRefFunction(
(recordKey: RecordKey, recordValue?: any): boolean => {
// 验证是否可以开始编辑
if (!validateCanStartEdit()) {
return false;
}

const keyStr = String(recordKeyToString(recordKey));

// 检查是否已经在编辑列表中,避免重复添加
if (checkKeyInEditableList(recordKey)) {
return true;
}

// 保存编辑前的数据
preEditRowRef.current =
recordValue ??
get(
props.dataSource,
Array.isArray(recordKey)
? (recordKey as string[])
: [recordKey as string],
) ??
null;

const onCancel = async (
recordKey: RecordKey,
editRow: RecordType & {
index?: number;
// 更新编辑 keys(不直接修改 editableKeysSet)
const newKeys =
editableType === 'single'
? [keyStr]
: [...(editableKeys || []), keyStr];

setEditableRowKeys(newKeys);
return true;
Comment on lines +160 to +166
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

请保持 editableKeys 的键类型一致

这里把 recordKey 统一转换成字符串后再写回 editableKeys。若调用方原本使用数字型 React.Key(比如 1)并依赖严格等于比较,现在会拿到字符串 '1',导致外部状态同步和 includes(1) 等判断失效。因此需要继续以原始 React.Key 形态保存,内部查找时再做字符串化即可。Based on learningsturn1code1

-      const keyStr = String(recordKeyToString(recordKey));
+      const key = recordKeyToString(recordKey);
+      const keyStr = String(key);
@@
-      const newKeys =
-        editableType === 'single'
-          ? [keyStr]
-          : [...(editableKeys || []), keyStr];
+      const newKeys =
+        editableType === 'single'
+          ? [key]
+          : [...(editableKeys || []), key];

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/utils/useEditableMap/index.tsx around lines 160 to 166, the code
currently writes back stringified record keys into editableKeys (using keyStr),
which changes the original React.Key type and breaks external equality checks;
preserve the original recordKey type when updating editableKeys (use the
original recordKey value for both the 'single' and multi branches) and only
stringify keys for internal lookup/comparison operations (e.g., when checking
includes or map keys) so external consumers still receive numbers or symbols as
they originally provided.

},
originRow: RecordType & { index?: number },
newLine?: NewLineConfig<any>,
) => {
const success = await props?.onCancel?.(
recordKey,
editRow,
originRow,
newLine,
);
if (success === false) {
return false;
);

/**
* 退出编辑状态
*
* @param recordKey
*/
const cancelEditable = useRefFunction((recordKey: RecordKey): boolean => {
const keyStr = String(recordKeyToString(recordKey));

// 检查是否在编辑列表中
if (!checkKeyInEditableList(recordKey)) {
return true;
}

// 更新编辑 keys(不直接修改 editableKeysSet)
const newKeys = (editableKeys || []).filter(
(key) => String(key) !== keyStr,
);

setEditableRowKeys(newKeys);
return true;
};
});

const onSave = async (
recordKey: RecordKey,
editRow: RecordType & {
index?: number;
/**
* 取消编辑的回调
*/
const onCancel = useRefFunction(
async (
recordKey: RecordKey,
editRow: RecordType & {
index?: number;
},
originRow: RecordType & { index?: number },
newLine?: NewLineConfig<any>,
): Promise<boolean> => {
const success = await props?.onCancel?.(
recordKey,
editRow,
originRow,
newLine,
);
if (success === false) {
return false;
}
return true;
},
originRow: RecordType & {
index?: number;
);

/**
* 保存编辑的回调
*/
const onSave = useRefFunction(
async (
recordKey: RecordKey,
editRow: RecordType & {
index?: number;
},
originRow: RecordType & {
index?: number;
},
): Promise<boolean> => {
const success = await props?.onSave?.(recordKey, editRow, originRow);
if (success === false) {
return false;
}

// 先退出编辑状态
await cancelEditable(recordKey);

// 更新数据源
const actionProps = {
data: props.dataSource,
row: editRow,
key: recordKey,
childrenColumnName: props.childrenColumnName || 'children',
};
props.setDataSource(editableRowByKey(actionProps));
return true;
},
) => {
const success = await props?.onSave?.(recordKey, editRow, originRow);
if (success === false) {
return false;
}
await cancelEditable(recordKey);
const actionProps = {
data: props.dataSource,
row: editRow,
key: recordKey,
childrenColumnName: props.childrenColumnName || 'children',
};
props.setDataSource(editableRowByKey(actionProps));
return true;
};
);

const saveText = intl.getMessage('editableTable.action.save', '保存');
const deleteText = intl.getMessage('editableTable.action.delete', '删除');
const cancelText = intl.getMessage('editableTable.action.cancel', '取消');

/**
* 渲染操作按钮
*/
const actionRender = useCallback(
(key: RecordKey, config?: ActionTypeText<RecordType>) => {
const renderConfig: ActionRenderConfig<
Expand Down Expand Up @@ -218,7 +288,7 @@ export function useEditableMap<RecordType>(
}
return [renderResult.save, renderResult.delete, renderResult.cancel];
},
[editableKeys && editableKeys.join(','), props.dataSource],
[editableKeys, props.dataSource, cancelEditable, onCancel, onSave],
);

return {
Expand Down
2 changes: 1 addition & 1 deletion tests/field/field.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1987,7 +1987,7 @@ describe('Field', () => {
});
});

it.skip(`🐴 light select dropdown toggle`, async () => {
it(`🐴 light select dropdown toggle`, async () => {
const html = render(
<Field
text="default"
Expand Down
51 changes: 50 additions & 1 deletion tests/snapshot/snapshot-demos-table-config-provider.html
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,56 @@
<td
class="qixian-table-cell"
colspan="5"
/>
>
<div
class="qixian-empty qixian-empty-normal"
>
<div
class="qixian-empty-image"
>
<svg
height="41"
viewBox="0 0 64 41"
width="64"
xmlns="http://www.w3.org/2000/svg"
>
<title>
暂无数据
</title>
<g
fill="none"
fill-rule="evenodd"
transform="translate(0 1)"
>
<ellipse
cx="32"
cy="33"
fill="#f5f5f5"
rx="32"
ry="7"
/>
<g
fill-rule="nonzero"
stroke="#d9d9d9"
>
<path
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"
/>
<path
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
fill="#fafafa"
/>
</g>
</g>
</svg>
</div>
<div
class="qixian-empty-description"
>
暂无数据
</div>
</div>
</td>
</tr>
</tbody>
</table>
Expand Down
51 changes: 50 additions & 1 deletion tests/snapshot/snapshot-demos-table-crud.html
Original file line number Diff line number Diff line change
Expand Up @@ -862,7 +862,56 @@
<td
class="ant-table-cell"
colspan="5"
/>
>
<div
class="ant-empty ant-empty-normal"
>
<div
class="ant-empty-image"
>
<svg
height="41"
viewBox="0 0 64 41"
width="64"
xmlns="http://www.w3.org/2000/svg"
>
<title>
暂无数据
</title>
<g
fill="none"
fill-rule="evenodd"
transform="translate(0 1)"
>
<ellipse
cx="32"
cy="33"
fill="#f5f5f5"
rx="32"
ry="7"
/>
<g
fill-rule="nonzero"
stroke="#d9d9d9"
>
<path
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"
/>
<path
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
fill="#fafafa"
/>
</g>
</g>
</svg>
</div>
<div
class="ant-empty-description"
>
暂无数据
</div>
</div>
</td>
</tr>
</tbody>
</table>
Expand Down
Loading
Loading