1 분 소요

발단

MUI Dialog 안에서 react-color의 SketchPicker를 사용하고 있었다. 색상 피커는 잘 열리는데, hex/RGB input을 클릭해도 커서가 생기지 않았다. 타이핑도 안 되고, JavaScript로 focus()를 강제로 줘도 소용없었다.


디버깅 과정

“input이 없는 건 아니겠지?”

document.querySelectorAll('input')

rc-editable-input 시리즈가 정상적으로 잡혔다. input은 있었다.

“혹시 disabled? pointer-events?”

console.log('disabled:', el.disabled);          // false
console.log('pointerEvents:', ...pointerEvents) // auto
console.log('visibility:', ...visibility);      // visible

전부 정상. input 자체의 문제가 아니었다.

“그럼 위에 뭔가 덮고 있나?”

const rect = input.getBoundingClientRect();
document.elementFromPoint(rect.left + rect.width/2, rect.top + rect.height/2);
// → sketch-picker div 반환 (input이 아님!)

input 좌표를 찍었는데 input이 아닌 상위 div가 반환됐다. 뭔가가 input 위를 덮고 있었다.

“sketch-picker를 무력화하면?”

document.querySelector('.sketch-picker').style.pointerEvents = 'none';

picker가 닫혀버렸다. backdrop 클릭이 통과된 것이다. 즉, backdrop이 SketchPicker 위에 올라와 있었다.

“근데 focus()는 왜 안 됐지?”

picker에 포커스가 잡히는지 확인하기 위해 Portal 렌더링 위치를 살펴봤다. Portal 대상이 choice-value-dialog — MUI Dialog의 루트 div였다.


원인: 두 가지가 동시에 문제였다

1. Portal이 MUI focus trap 밖에 렌더링되고 있었다

MUI Dialog는 MuiDialog-scrollPaper 내부만 focus trap으로 관리한다. 루트 div(choice-value-dialog)에 Portal로 렌더링하면 focus trap 으로 인식되어 MUI가 포커스를 차단한다.

MUI Dialog 루트
├── MuiBackdrop-root
├── MuiDialog-scrollPaper  ← focus trap 범위
│   └── dialog 컨텐츠
└── SketchPicker Portal    ← ❌ focus trap 밖 → 포커스 차단

2. backdrop이 SketchPicker 위를 덮고 있었다

position: fixed + top/right/bottom/left: 0 backdrop을 SketchPicker와 같은 부모 안에 두면, stacking context 충돌로 backdrop이 picker 위에 올라와 모든 클릭을 가로챈다.


왜 하나만 고쳐서는 안 됐나

상황 결과
Portal 위치만 수정 backdrop이 여전히 input을 덮음 → 클릭 불가
backdrop 구조만 수정 focus trap이 막음 → 포커스 불가
둘 다 수정 ✅ 정상 동작

해결

Portal을 MuiDialog-scrollPaper 안에 렌더링해서 focus trap 범위 안으로 들어가고, backdrop과 picker를 형제 관계로 분리한 뒤 stopPropagation으로 클릭 전파를 차단했다.

<Portal> {/* MuiDialog-scrollPaper 대상 */}
  {/* 전체 화면 backdrop */}
  <div style=
    onClick={props.onClose}
  />
  {/* picker: backdrop보다 위, 클릭 전파 차단 */}
  <div style=
    onClick={(e) => e.stopPropagation()}
  >
    <SketchPicker ... />
  </div>
</Portal>

배운 것

  • MUI Dialog 안에서 Portal 쓸 때는 반드시 MuiDialog-scrollPaper를 대상으로 해야 한다. 루트 div에 붙이면 focus trap 밖이 된다.
  • backdrop과 picker는 같은 부모 안에 두면 안 된다. stacking context 충돌이 생긴다. 형제로 분리하고 z-index + stopPropagation으로 명확히 구분해야 한다.
  • focus()도 안 먹히고, pointer-events도 정상인데 클릭이 안 된다면 focus trap을 의심하자.

카테고리:

업데이트:

댓글남기기