- 분야: FE
- 기술 스택: React · TypeScript · DOM · Chromium
문제와 원인
SQL 채점이 끝나면 revealed=true가 되면서 포커스를 가진 “정답 확인” 버튼이 DOM에서 제거된다. 기존 연습 화면과 기록 모달은 해설만 삽입해 채점 뒤 포커스가 BODY로 이동했다.
수정 전 코드
Practice
{!isDescriptive && !revealed &&
<button className="primary-button quiz-submit" disabled={!selected.length || grading}
onClick={onGrade}>{grading ? "채점 중…" : "정답 확인"}</button>}
{!isDescriptive && revealed &&
<div className={isCorrect ? "explanation correct" : "explanation incorrect"}>
<div className="explanation-title"><span>{isCorrect ? "✓" : "!"}</span>
<strong>{isCorrect ? "정답입니다." : "다시 확인해 보세요."}</strong>
</div>
<RichContent value={currentQuestion.explanation} explanation />
</div>}
수정 후 코드
Practice
연습 컴포넌트에 이름이 “채점 결과”인 region과 resultRef를 추가했다. revealed 또는 문항 id가 바뀌면 공개된 결과 영역으로 포커스를 옮긴다.
const resultRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (revealed) resultRef.current?.focus();
}, [revealed, currentQuestion?.id]);
{revealed &&
<div ref={resultRef} tabIndex={-1} role="region" aria-label="채점 결과">
{/* 정오답 문구·해설·직접 채점 결과 */}
</div>}
검증 결과
Chrome의 375×812px, DPR 1에서 React의 원본 전후 스냅샷과 원본 CSS를 실행했다. API·저장·Markdown 표시를 대역으로 연결한 합성 SQLD 문항 1개(보기 2개·정답 1개)에 경로·버전마다 정답 1회와 오답 1회를 입력하고, Tab으로 채점 버튼에 도달해 Enter를 누른 직후 DOM 포커스를 읽었다.
| classic 경로 | 수정 전 BODY 포커스 | 수정 후 BODY 포커스 | 수정 후 결과 region 포커스 | 결과 내용의 live/status 영역 |
|---|---|---|---|---|
| 연습 | 2/2회 | 0/2회 | 2/2회 | 전후 0개 |
| 기록 모달 | 2/2회 | 2/2회 | 0/2회 | 전후 0개 |
classic 총 8회에서 정답 문구는 두 경로 모두 정답입니다.였다. 연습의 오답 문구는 다시 확인해 보세요.에서 오답입니다.로 바뀌었고, 기록 모달은 다시 확인해 보세요.를 유지했다. 수정 후 연습에는 DIV[role="region"][aria-label="채점 결과"][tabindex="-1"] 1개와 채점 버튼 0개, 기록 모달에는 결과 region 0개와 role="dialog" 1개가 있었다.
기존 연습 화면은 정답 결과를 표시해도 결과 카드에 포커스 링이 없었다.
수정 후에는 정답과 오답 모두 결과 region에 포커스 링이 나타났다.
기록 모달은 정답·해설을 표시했지만 결과 region 없이 BODY 포커스를 유지했다.
V7의 같은 화면 크기에서 경로마다 정답·오답 각 1회씩 실행한 별도 4회도 연습은 결과 region 2/2회, 기록 모달은 BODY 2/2회였다. 결과 내용의 live/status 영역은 두 경로 모두 0개였다.
별도 자동 브라우저 검사에서는 classic 경로·버전마다 정답 3회와 오답 3회를 실행했다. 연습은 BODY 6/6회에서 결과 region 6/6회로 바뀌었고, 기록 모달은 전후 BODY 6/6회였다.