fix(web): выделение текста, отпущенное за краем окна, не закрывает карточку (I-2); спека: перезагрузка сохраняет страницу под окном (I-1) (#37)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
901a2c1cb7
commit
16835abbf8
@ -80,7 +80,8 @@
|
||||
13. **Элемент `<dialog>`** с `showModal()`: фокус удерживается в окне, фон недоступен, Esc вызывает `cancel` (обрабатываем
|
||||
сами — закрытие по п. 12 с проверкой заметок; если `cancel` не отменяемый — браузер закрыл окно сам без активации
|
||||
пользователя, — по событию `close` окно открывается снова и закрытие идёт тем же путём). Щелчок по фону — щелчок по
|
||||
самому `<dialog>` вне области содержимого.
|
||||
самому `<dialog>` вне области содержимого, причём нажатие (`pointerdown`) тоже было на фоне (I-2: выделение текста,
|
||||
отпущенное за краем окна, иначе закрывало окно).
|
||||
Прокрутка страницы под окном на время открытия блокируется (`overflow: hidden` у `body`), содержимое окна
|
||||
прокручивается внутри, шапка окна закреплена. Ширина до 1000 px, высота до 90vh; на экранах уже 700 px — на всю
|
||||
ширину.
|
||||
|
||||
@ -80,8 +80,10 @@
|
||||
### Requirement: Карточка задачи
|
||||
Адрес `/app/issues/{key}` SHALL открывать карточку задачи из `GET /issues/{key}` модальным окном поверх страницы, с
|
||||
которой её открыли (список «Задачи» или доска); страница под окном SHALL оставаться смонтированной и сохранять фильтры,
|
||||
загруженные строки, прокрутку и раскладку. Открытый напрямую адрес (новая вкладка, закладка, перезагрузка) SHALL
|
||||
показывать окно поверх списка задач без фильтров. Окно SHALL закрываться кнопкой ✕, клавишей Esc и щелчком по фону и
|
||||
загруженные строки, прокрутку и раскладку. Перезагрузка страницы с открытым окном SHALL сохранять страницу под окном. Открытый напрямую
|
||||
адрес (новая вкладка, закладка) SHALL
|
||||
показывать окно поверх списка задач без фильтров. Окно SHALL закрываться кнопкой ✕, клавишей Esc и щелчком по фону (нажатие и отпускание — на фоне; выделение текста,
|
||||
отпущенное за краем окна, окно не закрывает) и
|
||||
возвращать на страницу, с которой его открыли, даже после переходов по связям внутри окна; «назад» браузера после
|
||||
закрытия MUST NOT открывать карточку снова. Переход по связанной задаче SHALL менять содержимое окна и адрес, «назад»
|
||||
браузера — возвращать к прежней карточке. Пока окно открыто, фокус SHALL оставаться в окне, страница под ним не
|
||||
|
||||
@ -75,6 +75,18 @@ describe("карточка окном", () => {
|
||||
expect(router.state.location.pathname).toBe("/app/issues");
|
||||
});
|
||||
|
||||
it("I-2: выделение текста внутри окна, отпущенное над фоном, окно не закрывает", async () => {
|
||||
const router = at(["/app/issues"]);
|
||||
render(<App router={router} />);
|
||||
await userEvent.click(await screen.findByRole("link", { name: "AURORA-9001" }));
|
||||
const title = await within(dialog()).findByRole("heading", { name: /AURORA-9001/ });
|
||||
fireEvent.pointerDown(title); // нажали на тексте карточки…
|
||||
fireEvent.pointerUp(dialog());
|
||||
fireEvent.click(dialog()); // …отпустили над фоном: click — на <dialog>
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
expect(router.state.location.pathname).toBe("/app/issues/AURORA-9001");
|
||||
});
|
||||
|
||||
it("с доски: окно поверх доски, вкладка «Доска» активна, после закрытия доска та же", async () => {
|
||||
const router = at(["/app/board"]);
|
||||
render(<App router={router} />);
|
||||
|
||||
@ -8,6 +8,9 @@ import { IssueCard } from "./IssueCard.tsx";
|
||||
// пользователя — cancel не отменяемый), открываем снова и просим закрытия по общему пути (блокировщик заметок).
|
||||
export function IssueModal({ issueKey, onClose }: { issueKey: string; onClose: () => void }) {
|
||||
const ref = useRef<HTMLDialogElement>(null);
|
||||
// I-2: закрывать щелчком по фону, только если и нажатие было на фоне — выделение текста, отпущенное за краем окна,
|
||||
// даёт click на самом <dialog>, но нажатие было внутри содержимого
|
||||
const downOnBackdrop = useRef(false);
|
||||
useEffect(() => {
|
||||
const d = ref.current;
|
||||
if (d && !d.open) d.showModal();
|
||||
@ -19,7 +22,12 @@ export function IssueModal({ issueKey, onClose }: { issueKey: string; onClose: (
|
||||
<dialog ref={ref} className="issue-modal" aria-labelledby="issue-modal-title" aria-label={issueKey}
|
||||
onCancel={(e) => { if (e.cancelable) { e.preventDefault(); onClose(); } }}
|
||||
onClose={() => { const d = ref.current; if (d?.isConnected) { d.showModal(); onClose(); } }}
|
||||
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
|
||||
onPointerDown={(e) => { downOnBackdrop.current = e.target === e.currentTarget; }}
|
||||
onClick={(e) => {
|
||||
const fromBackdrop = downOnBackdrop.current;
|
||||
downOnBackdrop.current = false;
|
||||
if (e.target === e.currentTarget && fromBackdrop) onClose();
|
||||
}}>
|
||||
<ErrorBoundary key={issueKey}><IssueCard issueKey={issueKey} onClose={onClose} /></ErrorBoundary>
|
||||
</dialog>
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user