// 중앙 UI layer 상태 — drawer는 단일 slot, modal은 stack. // drawer/modal/toast 동시 오픈 시 focus/scroll/Esc 충돌을 차단하기 위한 single source. // (toast는 별도 store. drawer가 persistent inline panel(예: xl+ meta rail)일 때는 // 여기 시스템 밖이다 — 그저 레이아웃의 일부.) type Drawer = { id: 'sidebar' | 'meta' } | null; type Modal = { id: string }; class UIState { drawer = $state(null); modalStack = $state([]); // ── Drawer (단일 slot) ────────────────────────────── openDrawer(id: 'sidebar' | 'meta') { // 새 drawer 열면 이전 drawer는 자동으로 사라진다 (단일 slot) this.drawer = { id }; } closeDrawer() { this.drawer = null; } isDrawerOpen(id: 'sidebar' | 'meta') { return this.drawer?.id === id; } // ── Modal (stack — confirm 위에 nested 가능) ───────── openModal(id: string) { this.modalStack = [...this.modalStack, { id }]; } closeTopModal() { this.modalStack = this.modalStack.slice(0, -1); } closeModal(id: string) { this.modalStack = this.modalStack.filter((m) => m.id !== id); } isModalOpen(id: string) { return this.modalStack.some((m) => m.id === id); } // 특정 modal의 stack 인덱스 (z-index 계산용; 없으면 -1) modalIndex(id: string) { return this.modalStack.findIndex((m) => m.id === id); } get topModal() { return this.modalStack[this.modalStack.length - 1] ?? null; } // ── 글로벌 Esc 핸들러 ──────────────────────────────── // 우선순위: 가장 위 modal → drawer handleEscape() { if (this.modalStack.length > 0) { this.closeTopModal(); } else if (this.drawer) { this.closeDrawer(); } } } export const ui = new UIState();