проблема при нажатии кнопки
This commit is contained in:
@@ -40,7 +40,7 @@ export const ApprovalMatrix: React.FC<IApprovalMatrixProps> = ({
|
|||||||
// подсвечивается пол-таблицы и непонятно, что именно переехало. Запоминаем
|
// подсвечивается пол-таблицы и непонятно, что именно переехало. Запоминаем
|
||||||
// ключи блока ДО перестановки: после неё индексы уже другие.
|
// ключи блока ДО перестановки: после неё индексы уже другие.
|
||||||
const [activeKeys, setActiveKeys] = React.useState<string[]>([]);
|
const [activeKeys, setActiveKeys] = React.useState<string[]>([]);
|
||||||
const registerRow = useFlipRows(activeKeys);
|
const registerRow = useFlipRows(activeKeys, rowKeys);
|
||||||
|
|
||||||
// Строка → шаг, которому она принадлежит. Нужно, чтобы отрисовать ячейку
|
// Строка → шаг, которому она принадлежит. Нужно, чтобы отрисовать ячейку
|
||||||
// «Шаг» один раз на весь шаг (rowSpan) и понять, можно ли двигать его выше/ниже.
|
// «Шаг» один раз на весь шаг (rowSpan) и понять, можно ли двигать его выше/ниже.
|
||||||
|
|||||||
@@ -72,14 +72,22 @@ function drop(row: HTMLTableRowElement): void {
|
|||||||
* @param activeKeys ключи строк того шага, который двигает пользователь —
|
* @param activeKeys ключи строк того шага, который двигает пользователь —
|
||||||
* только он подсвечивается и поднимается над остальными (см. ApprovalMatrix).
|
* только он подсвечивается и поднимается над остальными (см. ApprovalMatrix).
|
||||||
* Для прочих изменений состава (добавление/удаление) — пустой массив.
|
* Для прочих изменений состава (добавление/удаление) — пустой массив.
|
||||||
|
* @param rowKeys ключи строк в ТЕКУЩЕМ порядке — единственный сигнал, по
|
||||||
|
* которому решаем, действительно ли что-то переставили (см. ниже, почему
|
||||||
|
* геометрии для этого недостаточно).
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @rushstack/no-new-null -- null здесь диктует React: ref-колбэк получает именно null при отвязке узла
|
export function useFlipRows(
|
||||||
export function useFlipRows(activeKeys: string[]): (key: string) => (el: HTMLTableRowElement | null) => void {
|
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>());
|
const nodes = React.useRef(new Map<string, HTMLTableRowElement>());
|
||||||
// Колбэк на один и тот же ключ должен быть стабильным, иначе React дёргал бы
|
// Колбэк на один и тот же ключ должен быть стабильным, иначе React дёргал бы
|
||||||
// ref(null)/ref(el) на каждый рендер и карта узлов пересобиралась бы впустую.
|
// ref(null)/ref(el) на каждый рендер и карта узлов пересобиралась бы впустую.
|
||||||
const refCache = React.useRef(new Map<string, (el: HTMLTableRowElement | null) => void>());
|
const refCache = React.useRef(new Map<string, (el: HTMLTableRowElement | null) => void>());
|
||||||
const prevTops = React.useRef(new Map<string, number>());
|
const prevTops = React.useRef(new Map<string, number>());
|
||||||
|
// Порядок строк на прошлый коммит — см. проверку orderChanged ниже.
|
||||||
|
const prevRowKeys = React.useRef<string[]>([]);
|
||||||
// Свои анимации держим сами, чтобы отменять ИМЕННО их: el.getAnimations()
|
// Свои анимации держим сами, чтобы отменять ИМЕННО их: el.getAnimations()
|
||||||
// вернул бы заодно чужие (CSS-переходы и т.п.).
|
// вернул бы заодно чужие (CSS-переходы и т.п.).
|
||||||
const running = React.useRef<Animation[]>([]);
|
const running = React.useRef<Animation[]>([]);
|
||||||
@@ -101,6 +109,29 @@ export function useFlipRows(activeKeys: string[]): (key: string) => (el: HTMLTab
|
|||||||
// useEffect: инвертировать положение нужно до того, как браузер покажет кадр,
|
// useEffect: инвертировать положение нужно до того, как браузер покажет кадр,
|
||||||
// иначе будет заметен скачок.
|
// иначе будет заметен скачок.
|
||||||
React.useLayoutEffect(() => {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// Сначала запоминаем, где строка ВИДНА сейчас: при быстрых повторных кликах
|
// Сначала запоминаем, где строка ВИДНА сейчас: при быстрых повторных кликах
|
||||||
// она в этот момент ещё летит, и новая анимация обязана стартовать от её
|
// она в этот момент ещё летит, и новая анимация обязана стартовать от её
|
||||||
// текущего положения на экране, иначе будет рывок.
|
// текущего положения на экране, иначе будет рывок.
|
||||||
|
|||||||
Reference in New Issue
Block a user