Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug][FocusTrap]: конфликтует логика при использовании нескольких FocusTrap #8244

Open
inomdzhon opened this issue Feb 5, 2025 · 0 comments · May be fixed by #8248

Comments

@inomdzhon
Copy link
Contributor

inomdzhon commented Feb 5, 2025

Описание

При использовать нескольких FocusTrap на странице возникают следующие проблемы:

  1. ломается логика работы Tab (старая проблема);

  2. может переключаться фокус с одного FocusTrap на другой FocusTrap – условием является изменения количества фокусируемых элементов.

    Такое может возникнуть при использовании одновременно двух ModalPage. После refactor: ModalRoot/ModalPage/ModalCard #6759 у ModalPage параметр autoFocus зависит от noFocusToDialog. В v6.6.0, где появилось изменение fix: fix rerender component when slide controlled changes #7485, не будет воспроизводится, т.к. в v6 модальное окно оборачивает в FocusTrap с зашитым autoFocus={false}.

    В v7 можно передать noFocusToDialog, чтобы решить проблему, но из-за этого будет теряться autoFocus, да и сам баг отстаётся, т.к. кейс с ModalPage лишь пример.

    Подробнее в секции ВидеоПроблема 2. Переключается фокус.

Версия

>=7.0.0

Видео

На видео используется Пример с воспроизведением 1 из секции ниже.

Проблема 1. Ломается логика работы Tab

2025-02-05.17.08.03.mov

Проблема 2. Переключается фокус

В втором модальном окно теряется фокус с поля ввода на кнопку в первом модальном окне – это связано с неравенством oldFocusableNodes в onMutateParentHandler() из-за появления нового фокусируемого элемента (примере, появление второй кнопки с цифрой 2)

if (!autoFocus || arraysEquals(oldFocusableNodes, focusableNodesRef.current)) {

2025-02-05.16.32.46.mov

Пример с воспроизведением

Примеры сделаны под Storybook

Пример с воспроизведением 1

Рендерим вторую модалку внутри первой, но выносим в портал.

import { useState } from '@storybook/preview-api';
import { ModalPage } from '../ModalPage/ModalPage';
import { ModalPageHeader } from '../ModalPageHeader/ModalPageHeader';
import { AppRootPortal } from '../AppRoot/AppRootPortal';

export const DoubleFocusTrap: Story = {
  render: function Render() {
    const [openInnerModal, setOpenInnerModal] = useState(false);
    const [searchText, onChangeSearch] = useState('');

    return (
      <ModalPage
        open
        id="root"
        header={<ModalPageHeader>Фильтр</ModalPageHeader>}
        settlingHeight={100}
      >
        <div style={{ padding: 16 }}>
          <button onClick={() => setOpenInnerModal(true)}>Inner modal</button>
        </div>
        <div style={{ padding: 16 }}>
          Updates here:{' '}
          {searchText.split('').map((i) => (
            <button key={i}>{i}</button>
          ))}
        </div>
        <AppRootPortal usePortal>
          <ModalPage
            id="inner"
            open={openInnerModal}
            settlingHeight={50}
            header={<ModalPageHeader>Местонахождение</ModalPageHeader>}
            onClose={() => setOpenInnerModal(false)}
          >
            <input
              style={{ padding: 16 }}
              placeholder="Поиск"
              value={searchText}
              onChange={({ target }) => onChangeSearch(String(target.value))}
            />
          </ModalPage>
        </AppRootPortal>
      </ModalPage>
    );
  },
};

Пример с воспроизведением 2

Рендерим вторую модалку рядом с первой.

import { useState } from '@storybook/preview-api';
import { ModalPage } from '../ModalPage/ModalPage';
import { ModalPageHeader } from '../ModalPageHeader/ModalPageHeader';

export const Playground: Story = {
  render: function Render() {
    const [openInnerModal, setOpenInnerModal] = useState(false);
    const [searchText, onChangeSearch] = useState('');

    return (
      <>
        <ModalPage
          open
          id="root"
          header={<ModalPageHeader>Фильтр</ModalPageHeader>}
          settlingHeight={100}
        >
          <div style={{ padding: 16 }}>
            <button onClick={() => setOpenInnerModal(true)}>Inner modal</button>
          </div>
          <div style={{ padding: 16 }}>
            Updates here:{' '}
            {searchText.split('').map((i) => (
              <button key={i}>{i}</button>
            ))}
          </div>
        </ModalPage>
        <ModalPage
          id="inner"
          open={openInnerModal}
          settlingHeight={50}
          header={<ModalPageHeader>Местонахождение</ModalPageHeader>}
          onClose={() => setOpenInnerModal(false)}
        >
          <input
            style={{ padding: 16 }}
            placeholder="Поиск"
            value={searchText}
            onChange={({ target }) => onChangeSearch(String(target.value))}
          />
        </ModalPage>
      </>
    );
  },
};

Пример как не воспроизводится, но при этом ломает модалки в текущем виде

Рендерим вторую модалку прямо в разметку первой.

import { useState } from '@storybook/preview-api';
import { ModalPage } from '../ModalPage/ModalPage';
import { ModalPageHeader } from '../ModalPageHeader/ModalPageHeader';

export const DoubleFocusTrap: Story = {
  render: function Render() {
    const [openInnerModal, setOpenInnerModal] = useState(false);
    const [searchText, onChangeSearch] = useState('');

    return (
      <ModalPage
        open
        id="root"
        header={<ModalPageHeader>Фильтр</ModalPageHeader>}
        settlingHeight={100}
      >
        <div style={{ padding: 16 }}>
          <button onClick={() => setOpenInnerModal(true)}>Inner modal</button>
        </div>
        <div style={{ padding: 16 }}>
          Updates here:{' '}
          {searchText.split('').map((i) => (
            <button key={i}>{i}</button>
          ))}
        </div>
        <ModalPage
          id="inner"
          open={openInnerModal}
          settlingHeight={50}
          header={<ModalPageHeader>Местонахождение</ModalPageHeader>}
          onClose={() => setOpenInnerModal(false)}
        >
          <input
            style={{ padding: 16 }}
            placeholder="Поиск"
            value={searchText}
            onChange={({ target }) => onChangeSearch(String(target.value))}
          />
        </ModalPage>
      </ModalPage>
    );
  },
};

@inomdzhon inomdzhon added this to VKUI Feb 5, 2025
@github-project-automation github-project-automation bot moved this to 🗃 Backlog in VKUI Feb 5, 2025
@inomdzhon inomdzhon changed the title [Bug][FocusTrap]: конфликтует логика с MutationObserver при использовании нескольких FocusTrap [Bug][FocusTrap]: конфликтует логика хука при использовании нескольких FocusTrap Feb 5, 2025
@inomdzhon inomdzhon changed the title [Bug][FocusTrap]: конфликтует логика хука при использовании нескольких FocusTrap [Bug][FocusTrap]: конфликтует логика при использовании нескольких FocusTrap Feb 5, 2025
@EldarMuhamethanov EldarMuhamethanov self-assigned this Feb 7, 2025
@EldarMuhamethanov EldarMuhamethanov moved this from 🗃 Backlog to 🔧 In progress in VKUI Feb 7, 2025
@EldarMuhamethanov EldarMuhamethanov moved this from 🔧 In progress to 👀 In Review in VKUI Feb 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: 👀 In Review
2 participants