Native Dialogs
This content is not available in your language yet.
OS 네이티브 modal — 파일 선택, 저장, 메시지 박스, 에러 popup. Electron dialog.*
API와 호환되는 응답 형식.
플랫폼 지원
섹션 제목: “플랫폼 지원”| API | macOS | Linux | Windows |
|---|---|---|---|
showMessageBox | ✓ NSAlert | ✓ GTK MessageDialog | ✓ TaskDialogIndirect, MessageBoxW fallback |
showErrorBox | ✓ NSAlert (critical) | ✓ GTK MessageDialog(error) | ✓ MessageBoxW |
showOpenDialog | ✓ NSOpenPanel | ✓ GTK FileChooserDialog | ✓ GetOpenFileNameW / IFileOpenDialog(folder) |
showSaveDialog | ✓ NSSavePanel | ✓ GTK FileChooserDialog | ✓ GetSaveFileNameW |
Linux는 GTK3 GtkMessageDialog / GtkFileChooserDialog를 사용한다.
Windows는 Win32 native dialog를 사용한다. 단, parent window에 attach되는 sheet modal은
macOS 전용이며 Linux/Windows dialog는 free-floating modal로 뜬다.
모달 동작 — Sheet vs Free-floating
섹션 제목: “모달 동작 — Sheet vs Free-floating”두 가지 modal 모드 지원 (Electron 매칭):
Sheet (부모 창 attach, macOS)
섹션 제목: “Sheet (부모 창 attach, macOS)”첫 인자에 windowId 전달하면 부모 창 타이틀바에서 슬라이드 다운하는 sheet:
import { dialog, windows } from '@suji/api';
const { windowId } = await windows.create({ title: "Editor" });const r = await dialog.showMessageBox(windowId, { message: "저장하시겠습니까?", buttons: ["저장", "취소"],});다른 창 입력은 가능 — sheet은 attach된 부모 창만 차단. macOS HIG 표준 UX.
Linux/Windows에서는 windowId 인자를 받아도 native sheet로 attach하지 않고 일반 dialog 경로를 사용한다.
macOS는 ObjC block completion handler와 nested NSApp event loop로 동기 대기해 창 입력을 차단하지 않는다.
Free-floating
섹션 제목: “Free-floating”windowId 안 넘기면 free-floating runModal (앱 전체 입력 차단):
const r = await dialog.showMessageBox({ message: "..." });빠르고 간단. 단일 창 앱이나 system-wide alert에 적합.
API
섹션 제목: “API”모든 함수는 Electron 두-인자 오버로드를 지원한다. 첫 인자로 windowId: number를 넘기면 macOS에서는 sheet, Windows/Linux에서는 free-floating 경로를 사용한다. 옵션 객체만 넘기면 free-floating.
import { dialog } from '@suji/api';
// Async (권장)dialog.showMessageBox(options): Promise<{ response, checkboxChecked }>;dialog.showMessageBox(windowId, options): Promise<...>; // macOS sheet
dialog.showErrorBox(title: string, content: string): Promise<void>;
dialog.showOpenDialog(options?): Promise<{ canceled, filePaths }>;dialog.showOpenDialog(windowId, options?): Promise<...>; // macOS sheet
dialog.showSaveDialog(options?): Promise<{ canceled, filePath }>;dialog.showSaveDialog(windowId, options?): Promise<...>; // macOS sheet
// Sync 변종 — 응답 shape만 다름 (raw 값 반환). 두-인자 오버로드 동일 지원.dialog.showMessageBoxSync(options): Promise<number>;dialog.showMessageBoxSync(windowId, options): Promise<number>;dialog.showOpenDialogSync(options?): Promise<string[] | undefined>;dialog.showOpenDialogSync(windowId, options?): Promise<string[] | undefined>;dialog.showSaveDialogSync(options?): Promise<string | undefined>;dialog.showSaveDialogSync(windowId, options?): Promise<string | undefined>;showMessageBox
섹션 제목: “showMessageBox”알림 / 확인 / 경고 dialog.
const { response, checkboxChecked } = await dialog.showMessageBox({ type: 'warning', // 'none' | 'info' | 'warning' | 'error' | 'question' title: '저장되지 않은 변경사항', // 창 타이틀 message: '변경사항을 저장하시겠습니까?', detail: '저장 안 하면 변경 내용이 사라집니다.', // 보조 텍스트 (작은 폰트) buttons: ['저장', '저장 안 함', '취소'], defaultId: 0, // Enter로 활성화될 버튼 (생략 시 첫 번째) cancelId: 2, // ESC로 활성화될 버튼 checkboxLabel: '다음에 다시 묻지 않기', // suppression checkbox (생략 가능) checkboxChecked: false, // 체크박스 초기 상태});
if (response === 0) { // 저장 클릭}NSAlertStyle 매핑
섹션 제목: “NSAlertStyle 매핑”type | NSAlertStyle | 아이콘 |
|---|---|---|
'info' | NSAlertStyleInformational | (없음) |
'warning' | NSAlertStyleWarning | ⚠️ |
'error' | NSAlertStyleCritical | ❗ |
'question' | NSAlertStyleWarning | ⚠️ (Electron과 동일 — macOS는 question 별도 스타일 없음) |
'none' (기본) | NSAlertStyleWarning | ⚠️ |
Windows 메시지 박스
섹션 제목: “Windows 메시지 박스”Windows는 가능한 경우 TaskDialogIndirect를 런타임 해상도(LoadLibraryW +
GetProcAddress)로 호출한다. SxS manifest나 OS 상태 때문에 사용할 수 없으면
MessageBoxW로 fallback한다. TaskDialog path는 커스텀 버튼 라벨을 보존하지만,
fallback path는 Win32 표준 버튼 조합(OK, OK/Cancel, Yes/No, Yes/No/Cancel)으로 축약된다.
Linux GTK 메시지 박스
섹션 제목: “Linux GTK 메시지 박스”Linux는 GtkMessageDialog를 사용한다. 버튼은 custom response id로 추가해
Electron식 button index를 보존하고, checkboxLabel은 message area에 GtkCheckButton을
붙여 checkboxChecked로 되돌린다.
showErrorBox
섹션 제목: “showErrorBox”단순 에러 popup — 단일 OK 버튼, critical 스타일.
await dialog.showErrorBox('저장 실패', '디스크 공간이 부족합니다.');showMessageBox({ type: 'error', message: content, title, buttons: ['OK'] })와 동일.
showOpenDialog
섹션 제목: “showOpenDialog”파일/폴더 선택.
const { canceled, filePaths } = await dialog.showOpenDialog({ title: '파일 선택', defaultPath: '~/Documents', // 초기 디렉토리 buttonLabel: '선택', // "Open" 대체 message: '여러 파일을 동시에 선택할 수 있습니다.', // 다이얼로그 상단 메시지 (macOS 한정) filters: [ { name: 'Images', extensions: ['jpg', 'jpeg', 'png', 'gif'] }, { name: 'Documents', extensions: ['pdf', 'txt', 'md'] }, ], properties: ['openFile', 'multiSelections'],});
if (!canceled) { for (const path of filePaths) { console.log('선택됨:', path); }}properties
섹션 제목: “properties”문자열 union 배열 — Electron과 동일.
| Property | 동작 |
|---|---|
'openFile' | 파일 선택 가능 (기본 ON) |
'openDirectory' | 폴더 선택 가능 |
'multiSelections' | 여러 항목 선택 가능 |
'showHiddenFiles' | . 시작하는 hidden 파일 표시 |
'createDirectory' | ”New Folder” 버튼 활성 |
'noResolveAliases' | symbolic link / alias를 따라가지 않고 그 자체 반환 |
'treatPackageAsDirectory' | .app/.pkg 같은 bundle을 폴더로 표시 |
filters
섹션 제목: “filters”{ name, extensions } 배열. macOS UI는 필터 그룹 dropdown을 더 이상 노출하지
않아 (Big Sur부터) 모든 그룹의 extensions가 통합되어 허용 — name 필드는
호환성용이며 화면에 표시되지 않음. Linux는 GtkFileFilter, Windows는
OPENFILENAMEW.lpstrFilter 형식으로 필터 그룹을 전달한다. 확장자는 점 없이.
extensions: ['*']이거나 빈 배열이면 모든 파일 허용.
defaultPath
섹션 제목: “defaultPath”- 슬래시로 끝나거나 (
/path/to/dir/) 기존 디렉토리면 → 초기 디렉토리만 설정. - 그 외에는
(directory, filename)으로 분리해 디렉토리 + 파일명 입력란 미리 채움.
defaultPath: '/Users/me/Documents/', // → 폴더만defaultPath: '/Users/me/Documents/draft.md', // → 폴더 + 파일명 'draft.md'defaultPath: 'output.txt', // → 파일명만 (slash 없음)showSaveDialog
섹션 제목: “showSaveDialog”저장 경로 선택.
const { canceled, filePath } = await dialog.showSaveDialog({ title: '저장', defaultPath: '~/Documents/output.pdf', buttonLabel: '내보내기', nameFieldLabel: '파일명:', filters: [{ name: 'PDF', extensions: ['pdf'] }],});
if (!canceled) { await fs.writeFile(filePath, data);}properties (Save)
섹션 제목: “properties (Save)”| Property | 동작 |
|---|---|
'showHiddenFiles' | hidden 파일 표시 |
'createDirectory' | ”New Folder” 버튼 활성 |
'treatPackageAsDirectory' | bundle을 폴더로 |
NSSavePanel은 기본적으로 overwrite confirmation을 사용자가 끄지 못하게 함 — Electron의
'showOverwriteConfirmation' 옵션은 macOS에서 항상 ON 상태로 무시됨.
Linux GTK와 Windows Win32 path는 overwrite confirmation 옵션을 native dialog에 전달한다.
Windows는 showOverwriteConfirmation이 true일 때 OFN_OVERWRITEPROMPT를 사용한다.
showsTagField
섹션 제목: “showsTagField”macOS Finder의 태그 입력 필드를 dialog 하단에 표시 (NSSavePanel 한정). 사용자가 입력한 태그는 저장 시 파일에 자동 적용. 우리 응답에는 별도 필드로 노출 안 함 — Electron도 동일.
await dialog.showSaveDialog({ showsTagField: true });응답 형식 일관성
섹션 제목: “응답 형식 일관성”// 취소 시{ canceled: true, filePaths: [] } // showOpenDialog{ canceled: true, filePath: '' } // showSaveDialog
// MessageBox는 항상 buttons 중 하나 선택{ response: 0, checkboxChecked: false }이 응답 형식은 Electron과 동일.
잘못된 옵션
섹션 제목: “잘못된 옵션”옵션 타입 불일치 (예: defaultId: "string")는 std.json parse 단계에서 실패 → 즉시
graceful 응답:
// showMessageBox parse fail{ response: 0, checkboxChecked: false, error: 'parse' }
// showOpenDialog parse fail{ canceled: true, filePaths: [], error: 'parse' }런타임 type-safe하지 않은 코드(예: TypeScript 미사용)도 crash 없이 동작.
Backend SDK
섹션 제목: “Backend SDK”Zig/Rust/Go/Node 4개 백엔드 SDK 모두 typed wrapper를 노출하며, frontend @suji/api와 동일한 응답 형식을 사용한다.
// Zigconst r = suji.dialog.messageBoxSimple("info", "안녕", &.{ "OK", "Cancel" });const r = suji.dialog.showOpenDialog("\"properties\":[\"openFile\"]");suji.dialog.showErrorBox("Error", "Something failed");// Rustuse suji::dialog::{self, MessageBoxOpts};let r = dialog::show_message_box(MessageBoxOpts { message: "저장하시겠습니까?", buttons: vec!["저장", "취소"], window_id: Some(1), // sheet on window 1 ..Default::default()});let r = dialog::show_open_dialog(r#""properties":["openFile"]"#);dialog::show_error_box("Error", "Something failed");// Goimport "github.com/ohah/suji-go/dialog"r := dialog.ShowMessageBox(dialog.MessageBoxOpts{ Message: "저장하시겠습니까?", Buttons: []string{"저장", "취소"}, WindowID: 1, // sheet})r := dialog.ShowOpenDialog(`"properties":["openFile"]`)dialog.ShowErrorBox("Error", "Something failed")// Nodeimport { dialog } from '@suji/node';const r = await dialog.showMessageBox({ message: "저장하시겠습니까?", buttons: ["저장", "취소"], windowId: 1,});const r = await dialog.showOpenDialog({ properties: ["openFile"] });await dialog.showErrorBox("Error", "Something failed");응답 형식은 모든 SDK 동일 — frontend @suji/api와 매칭.
Electron 대비 누락 항목 (현재 v1)
섹션 제목: “Electron 대비 누락 항목 (현재 v1)”Electron 대비 아직 노출하지 않거나 플랫폼별 native backend에서 무시되는 옵션들.
| 누락 항목 | Electron API | 현재 동작 | 비고 |
|---|---|---|---|
| ✅ 구현됨 (windowId 첫 인자) | — | ||
bookmarks / bookmark 반환 필드 | macOS sandbox security-scoped | 응답에서 누락 | securityScopedBookmarks: true일 때만 — 샌드박스 앱 한정 |
securityScopedBookmarks 옵션 | OpenDialog/SaveDialog | 무시 | 샌드박스 한정 |
icon: NativeImage | MessageBox 커스텀 아이콘 | NSAlert 기본 아이콘만 | — |
textWidth: number | MessageBox 메시지 폭 | macOS 기본 | — |
type: 'none' 정확 매칭 | macOS는 아이콘 없음 | NSAlertStyleWarning(경고 아이콘) | NSAlert는 항상 app icon overlay |
type: 'question' 정확 매칭 | macOS는 question 아이콘 | NSAlertStyleWarning | macOS NSAlert에 question 스타일 없음 (Electron도 동일 매핑) |
showCertificateTrustDialog | 인증서 신뢰 dialog | 미구현 | — |
Windows 전용 옵션 (promptToCreate / dontAddToRecent / noLink / normalizeAccessKeys) | Windows 한정 | 일부 미반영 | — |
알려진 한계 (구현 제약)
섹션 제목: “알려진 한계 (구현 제약)”- Sheet modal은 macOS 전용: Linux/Windows는 parent window attach 없이 free-floating dialog를 사용한다.
buttons제한: macOS NSAlert path는 16개, Windows TaskDialog path는 8개까지 커스텀 버튼을 보존한다.- Dialog 텍스트 4KB 제한: 메시지가 4KB를 초과하면 잘린다.
- filters
nameUI 미표시: macOS Big Sur+에서 NSOpenPanel은 필터 그룹 dropdown 미노출 — 모든 extensions가 통합 허용. - Windows 파일 dialog 옵션 일부 제한:
buttonLabel,message,showsTagField,treatPackageAsDirectory, alias 처리 같은 macOS 전용 옵션은 Win32 dialog에서 무시된다. - Linux 파일 dialog 옵션 일부 제한:
message,nameFieldLabel,showsTagField,treatPackageAsDirectory, alias 처리 같은 macOS 전용 옵션은 GTK dialog에서 무시된다.
잘못된 옵션을 넘겨도 crash 없이 graceful parse error 응답이 반환된다. 실제 dialog의
시각적 동작 확인은 examples/dialogs-demo를 통한 수동 검증을 권장한다.