fix(sw): focus anchored SWPopup / property panel when it opens

With anchorRef the frame renders visibility:hidden until useSWHeaderOverlay
places it, and the focus effect ran in that same commit, so focus() on the
hidden element was ignored. Focus now runs once the frame is placed; the
previous element is still captured first, so Escape / close return focus.
Align the SWPopup autoFocus default doc (true with anchorRef) and catalog.

(cherry picked from commit 65bc0dbd5df059ead06f10a3f95f2bd2734fbf2f)
This commit is contained in:
shkim4 committed 2026-10-19 17:42:37 +09:00
1 parent b7227ee963
commit b434f272e2
3 files changed
+7 -3

No files matched your search

+1 -1
View File
@@ -67,7 +67,7 @@ SW 공식 기준 Header(Figma 26:8944, 1920 × 49). 로고 · 파일 · 객체
| 컴포넌트 | 용도 | 주요 Props | 크기·동작 | 검증 |
|---|---|---|---|---|
| `SWPopup` | 설정·정보·사용자 정의 Popup 프레임 | `title`(필수), `children`, `onClose`(닫기 버튼·Escape), `closeLabel`(기본 `닫기`), `titleActions`(제목 막대 닫기 버튼 앞 추가 버튼), `footer`(본문 뒤 고정 영역, 본문이 스크롤해도 보임), `autoFocus`(열릴 때 Focus, 안에서 닫히면 원래 위치로 복귀), `anchorRef`(기준 요소 아래 고정 Overlay), `style`·`className`·`div` 속성 | 폭 314, 제목줄 30, 높이는 콘텐츠(최대 화면 − 8px, `style.maxHeight`로 더 줄일 수 있음) → 본문 스크롤. Non-modal(Dim·Portal·Focus trap 없음) | ✅ |
| `SWPopup` | 설정·정보·사용자 정의 Popup 프레임 | `title`(필수), `children`, `onClose`(닫기 버튼·Escape), `closeLabel`(기본 `닫기`), `titleActions`(제목 막대 닫기 버튼 앞 추가 버튼), `footer`(본문 뒤 고정 영역, 본문이 스크롤해도 보임), `autoFocus`(열릴 때 Focus, 안에서 닫히면 원래 위치로 복귀. 기본: `anchorRef`가 있으면 true, 없으면 false), `anchorRef`(기준 요소 아래 고정 Overlay), `style`·`className`·`div` 속성 | 폭 314, 제목줄 30, 높이는 콘텐츠(최대 화면 − 8px, `style.maxHeight`로 더 줄일 수 있음) → 본문 스크롤. Non-modal(Dim·Portal·Focus trap 없음) | ✅ |
| `SWPopupSection` | 속성창 그룹 면(제목 + 내용) | `title`, `children`, `section` 속성 | 그룹 제목 700·70% 검정, 내용 면 `--hm-sw-property-content` | ✅ |
| `SWPopupRow` | 속성창 한 줄(라벨 + 값) | `label`(필수), `htmlFor`, `children`(값 컨트롤, SW 필드는 `SWHeaderInput`), `unit`, `field`(`box` 기본 · `compact` · `none`), `numeric`(숫자 편집 On), `readOnly`, `disabled` | 행 최소 32, 라벨 72, 값 상자 28(Default·Hover 테두리, 숫자 편집 시 #0F0D0A). `compact` = 41px hug 숫자 필드 | ✅ |
| `SWHeaderPropertyPanel` | 데이터 기반 속성창(2절). 같은 프레임이며 `children`도 받음 | 2절 | 같은 DOM·class | ✅ |
@@ -327,13 +327,17 @@ export function SWPopupFrame({
if (!focus) return
const panel = panelRef.current
const previous = document.activeElement as HTMLElement | null
panel?.focus()
return () => {
// Closing with focus still inside (e.g. Escape on a field) returns it to where it came from.
const active = document.activeElement
if (panel && (active === document.body || active === null || panel.contains(active)) && previous?.isConnected) previous.focus()
}
}, [focus, panelRef])
// An anchored frame stays hidden until it is placed, and a hidden element cannot take focus.
const placed = style.visibility !== "hidden"
useLayoutEffect(() => {
if (focus && placed) panelRef.current?.focus()
}, [focus, placed, panelRef])
return (
<div ref={panelRef} id={id} tabIndex={-1} className={`hm-sw-property${className ? ` ${className}` : ""}`} data-selection-kind={kind} role="dialog" aria-modal="false" aria-labelledby={titleId}
style={{ ...style, ...customStyle }} {...rest}
@@ -13,7 +13,7 @@ export interface SWPopupProps extends Omit<HTMLAttributes<HTMLDivElement>, "titl
* the project's own elements. Nothing is restricted; the frame keeps the SW design whatever is inside.
*/
children?: ReactNode
/** Focus the popup when it opens (and return focus when it closes from inside). Default false. */
/** Focus the popup when it opens (and return focus when it closes from inside). Default: true with `anchorRef`, false without. */
autoFocus?: boolean
/** Anchor element: the popup is fixed below it and kept inside the viewport (as SWHeader does). */
anchorRef?: RefObject<HTMLElement | null>