fix(GroupTable): prevent Input cursor jumping to end on keystroke (#4208)
Refactor updateRow/addRow/removeRow to use functional setRows(prev => ...) and ref-based onChange/duplicateNames access, making columns useMemo stable across keystrokes so Semi UI Table does not re-mount Input components.
This commit is contained in:
parent
8b22161527
commit
c20060931b
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useCallback, useMemo } from 'react';
|
import React, { useState, useCallback, useMemo, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Input,
|
Input,
|
||||||
@ -61,60 +61,63 @@ export function serializeGroupTable(rows) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function GroupTable({
|
export default function GroupTable({ groupRatio, userUsableGroups, onChange }) {
|
||||||
groupRatio,
|
|
||||||
userUsableGroups,
|
|
||||||
onChange,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [rows, setRows] = useState(() =>
|
const [rows, setRows] = useState(() =>
|
||||||
buildRows(groupRatio, userUsableGroups),
|
buildRows(groupRatio, userUsableGroups),
|
||||||
);
|
);
|
||||||
|
|
||||||
const emitChange = useCallback(
|
// Use functional setRows to keep updateRow/addRow/removeRow referentially
|
||||||
(newRows) => {
|
// stable, preventing columns useMemo from rebuilding on every keystroke
|
||||||
setRows(newRows);
|
// which causes the Input cursor to jump to end (cursor reset bug).
|
||||||
onChange?.(serializeGroupTable(newRows));
|
const onChangeRef = useRef(onChange);
|
||||||
},
|
onChangeRef.current = onChange;
|
||||||
[onChange],
|
|
||||||
);
|
const emitAndSet = useCallback((updater) => {
|
||||||
|
setRows((prev) => {
|
||||||
|
const next = typeof updater === 'function' ? updater(prev) : updater;
|
||||||
|
onChangeRef.current?.(serializeGroupTable(next));
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const updateRow = useCallback(
|
const updateRow = useCallback(
|
||||||
(id, field, value) => {
|
(id, field, value) => {
|
||||||
const next = rows.map((r) =>
|
emitAndSet((prev) =>
|
||||||
r._id === id ? { ...r, [field]: value } : r,
|
prev.map((r) => (r._id === id ? { ...r, [field]: value } : r)),
|
||||||
);
|
);
|
||||||
emitChange(next);
|
|
||||||
},
|
},
|
||||||
[rows, emitChange],
|
[emitAndSet],
|
||||||
);
|
);
|
||||||
|
|
||||||
const addRow = useCallback(() => {
|
const addRow = useCallback(() => {
|
||||||
const existingNames = new Set(rows.map((r) => r.name));
|
emitAndSet((prev) => {
|
||||||
let counter = 1;
|
const existingNames = new Set(prev.map((r) => r.name));
|
||||||
let newName = `group_${counter}`;
|
let counter = 1;
|
||||||
while (existingNames.has(newName)) {
|
let newName = `group_${counter}`;
|
||||||
counter++;
|
while (existingNames.has(newName)) {
|
||||||
newName = `group_${counter}`;
|
counter++;
|
||||||
}
|
newName = `group_${counter}`;
|
||||||
emitChange([
|
}
|
||||||
...rows,
|
return [
|
||||||
{
|
...prev,
|
||||||
_id: uid(),
|
{
|
||||||
name: newName,
|
_id: uid(),
|
||||||
ratio: 1,
|
name: newName,
|
||||||
selectable: true,
|
ratio: 1,
|
||||||
description: '',
|
selectable: true,
|
||||||
},
|
description: '',
|
||||||
]);
|
},
|
||||||
}, [rows, emitChange]);
|
];
|
||||||
|
});
|
||||||
|
}, [emitAndSet]);
|
||||||
|
|
||||||
const removeRow = useCallback(
|
const removeRow = useCallback(
|
||||||
(id) => {
|
(id) => {
|
||||||
emitChange(rows.filter((r) => r._id !== id));
|
emitAndSet((prev) => prev.filter((r) => r._id !== id));
|
||||||
},
|
},
|
||||||
[rows, emitChange],
|
[emitAndSet],
|
||||||
);
|
);
|
||||||
|
|
||||||
const groupNames = useMemo(() => rows.map((r) => r.name), [rows]);
|
const groupNames = useMemo(() => rows.map((r) => r.name), [rows]);
|
||||||
@ -127,6 +130,11 @@ export default function GroupTable({
|
|||||||
return new Set(Object.keys(counts).filter((k) => counts[k] > 1));
|
return new Set(Object.keys(counts).filter((k) => counts[k] > 1));
|
||||||
}, [groupNames]);
|
}, [groupNames]);
|
||||||
|
|
||||||
|
// Use ref so column render functions always read the latest duplicate set
|
||||||
|
// without adding duplicateNames to columns deps (which would break cursor).
|
||||||
|
const duplicateNamesRef = useRef(duplicateNames);
|
||||||
|
duplicateNamesRef.current = duplicateNames;
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@ -138,7 +146,9 @@ export default function GroupTable({
|
|||||||
<Input
|
<Input
|
||||||
size='small'
|
size='small'
|
||||||
value={record.name}
|
value={record.name}
|
||||||
status={duplicateNames.has(record.name) ? 'warning' : undefined}
|
status={
|
||||||
|
duplicateNamesRef.current.has(record.name) ? 'warning' : undefined
|
||||||
|
}
|
||||||
onChange={(v) => updateRow(record._id, 'name', v)}
|
onChange={(v) => updateRow(record._id, 'name', v)}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@ -212,7 +222,7 @@ export default function GroupTable({
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[t, duplicateNames, updateRow, removeRow],
|
[t, updateRow, removeRow],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -223,9 +233,7 @@ export default function GroupTable({
|
|||||||
rowKey='_id'
|
rowKey='_id'
|
||||||
hidePagination
|
hidePagination
|
||||||
size='small'
|
size='small'
|
||||||
empty={
|
empty={<Text type='tertiary'>{t('暂无分组,点击下方按钮添加')}</Text>}
|
||||||
<Text type='tertiary'>{t('暂无分组,点击下方按钮添加')}</Text>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<div className='mt-3 flex justify-center'>
|
<div className='mt-3 flex justify-center'>
|
||||||
<Button icon={<IconPlus />} theme='outline' onClick={addRow}>
|
<Button icon={<IconPlus />} theme='outline' onClick={addRow}>
|
||||||
@ -234,7 +242,8 @@ export default function GroupTable({
|
|||||||
</div>
|
</div>
|
||||||
{duplicateNames.size > 0 && (
|
{duplicateNames.size > 0 && (
|
||||||
<Text type='warning' size='small' className='mt-2 block'>
|
<Text type='warning' size='small' className='mt-2 block'>
|
||||||
{t('存在重复的分组名称:')}{Array.from(duplicateNames).join(', ')}
|
{t('存在重复的分组名称:')}
|
||||||
|
{Array.from(duplicateNames).join(', ')}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user