проблема при нажатии кнопки

This commit is contained in:
gorbenko.pavel
2026-08-10 17:00:06 +05:00
parent d2e16167c7
commit 36ee7ec009
2 changed files with 34 additions and 3 deletions
@@ -40,7 +40,7 @@ export const ApprovalMatrix: React.FC<IApprovalMatrixProps> = ({
// подсвечивается пол-таблицы и непонятно, что именно переехало. Запоминаем
// ключи блока ДО перестановки: после неё индексы уже другие.
const [activeKeys, setActiveKeys] = React.useState<string[]>([]);
const registerRow = useFlipRows(activeKeys);
const registerRow = useFlipRows(activeKeys, rowKeys);
// Строка → шаг, которому она принадлежит. Нужно, чтобы отрисовать ячейку
// «Шаг» один раз на весь шаг (rowSpan) и понять, можно ли двигать его выше/ниже.
@@ -72,14 +72,22 @@ function drop(row: HTMLTableRowElement): void {
* @param activeKeys ключи строк того шага, который двигает пользователь —
* только он подсвечивается и поднимается над остальными (см. ApprovalMatrix).
* Для прочих изменений состава (добавление/удаление) — пустой массив.
* @param rowKeys ключи строк в ТЕКУЩЕМ порядке — единственный сигнал, по
* которому решаем, действительно ли что-то переставили (см. ниже, почему
* геометрии для этого недостаточно).
*/
// eslint-disable-next-line @rushstack/no-new-null -- null здесь диктует React: ref-колбэк получает именно null при отвязке узла
export function useFlipRows(activeKeys: string[]): (key: string) => (el: HTMLTableRowElement | null) => void {
export function useFlipRows(
activeKeys: string[],
rowKeys: string[]
// eslint-disable-next-line @rushstack/no-new-null -- null здесь диктует React: ref-колбэк получает именно null при отвязке узла
): (key: string) => (el: HTMLTableRowElement | null) => void {
const nodes = React.useRef(new Map<string, HTMLTableRowElement>());
// Колбэк на один и тот же ключ должен быть стабильным, иначе React дёргал бы
// ref(null)/ref(el) на каждый рендер и карта узлов пересобиралась бы впустую.
const refCache = React.useRef(new Map<string, (el: HTMLTableRowElement | null) => void>());
const prevTops = React.useRef(new Map<string, number>());
// Порядок строк на прошлый коммит — см. проверку orderChanged ниже.
const prevRowKeys = React.useRef<string[]>([]);
// Свои анимации держим сами, чтобы отменять ИМЕННО их: el.getAnimations()
// вернул бы заодно чужие (CSS-переходы и т.п.).
const running = React.useRef<Animation[]>([]);
@@ -101,6 +109,29 @@ export function useFlipRows(activeKeys: string[]): (key: string) => (el: HTMLTab
// useEffect: инвертировать положение нужно до того, как браузер покажет кадр,
// иначе будет заметен скачок.
React.useLayoutEffect(() => {
// ВАЖНО: «строка сдвинулась по пикселям» — недостаточный повод анимировать.
// Пример живого бага: заявку отправили на согласование → таблица стала
// read-only → у строк пропали колонки со стрелками/кнопками → ширина
// текстовых колонок изменилась → где-то текст перенёсся по-другому →
// строки СМЕСТИЛИСЬ ПО ВЫСОТЕ, хотя их состав и порядок не менялись вовсе.
// Раньше это трактовалось как «переезд» и запускало translateY на
// случайную величину — на кадре это выглядело как строки, которые на
// мгновение пропадают (уезжают за пределы видимой области) и выскакивает
// скролл. Настоящая перестановка меняет НАБОР/ПОРЯДОК ключей — вот это и
// проверяем, а не сам факт смещения.
const orderChanged =
rowKeys.length !== prevRowKeys.current.length || rowKeys.some((k, i) => k !== prevRowKeys.current[i]);
prevRowKeys.current = rowKeys;
if (!orderChanged) {
// Порядок тот же — просто обновляем базовую геометрию для следующего
// ДЕЙСТВИТЕЛЬНОГО сравнения, без единой анимации на этом проходе.
const current = new Map<string, number>();
nodes.current.forEach((el, key) => current.set(key, el.getBoundingClientRect().top));
prevTops.current = current;
return;
}
// Сначала запоминаем, где строка ВИДНА сейчас: при быстрых повторных кликах
// она в этот момент ещё летит, и новая анимация обязана стартовать от её
// текущего положения на экране, иначе будет рывок.