Skip to Content
UI API

UI API

dmn.ui를 통해 앱의 UI를 확장할 수 있습니다.

UI API는 메인 윈도우에서만 사용 가능합니다. 오버레이에서 호출하면 경고만 표시되고 동작하지 않습니다.

컨텍스트 메뉴

키 메뉴 (addKeyMenuItem)

키를 우클릭할 때 표시되는 메뉴에 항목을 추가합니다.

const menuId = dmn.ui.contextMenu.addKeyMenuItem({ id: 'copy-keycode', label: '키 코드 복사', onClick: (context) => { navigator.clipboard.writeText(context.keyCode); }, });

Context 객체:

속성타입설명
keyCodestringcanonical 슬롯 식별자 (예: “D”, “LEFT CTRL+Z”, “F|NUMPAD 4”)
idstring요소 안정 ID (UUID). 재정렬·모드 전환에도 같은 키를 가리킵니다
indexnumber키 인덱스. deprecated: 현재 스냅샷에서만 유효하며 신원은 id를 사용하세요
positionKeyPosition키 위치 정보
modestring현재 키 모드

그리드 메뉴 (addGridMenuItem)

그리드 빈 공간을 우클릭할 때 표시되는 메뉴에 항목을 추가합니다. 설치된 플러그인이 늘어도 메뉴가 길어지지 않도록, 여기 추가된 항목은 플러그인 서브메뉴 하나로 묶여 표시됩니다. 자리는 등록 순서를 따르며 position은 적용되지 않습니다.

dmn.ui.contextMenu.addGridMenuItem({ id: 'add-timer', label: '타이머 추가', onClick: (context) => { createTimer(context.position); }, });

Context 객체:

속성타입설명
position{ dx, dy }클릭 위치 (그리드 좌표)
modestring현재 키 모드

메뉴 항목 옵션

{ id: "my-menu", // 플러그인 내 고유 ID label: "메뉴 항목", // 표시 텍스트 position: "bottom", // "top" | "bottom" (기본: bottom, 키 메뉴 전용) // 조건부 표시 visible: (context) => context.mode === "4key", // 조건부 비활성화 disabled: (context) => context.position.count === 0, // 클릭 핸들러 onClick: async (context) => { // ... }, }

visibledisabled는 boolean 또는 context를 받는 함수입니다. 함수가 예외를 던지면 숨김/비활성으로 처리되고(나머지 메뉴는 그대로 표시), 함수가 아닌 값은 truthiness로 판정합니다. visible0, "", null을 주면 항목이 숨겨지고, 같은 값들을 disabled에 주면 항목이 활성 상태로 남습니다.

메뉴 관리

// 메뉴 업데이트 dmn.ui.contextMenu.updateMenuItem(menuId, { label: '새로운 라벨', disabled: true, }); // 특정 메뉴 제거 dmn.ui.contextMenu.removeMenuItem(menuId); // 이 플러그인의 모든 메뉴 제거 dmn.ui.contextMenu.clearMyMenuItems();

Display Element

displayElement는 저수준(Primitive) API로, 직접적인 사용을 권장하지 않습니다. 그리드와 오버레이에 커스텀 UI 요소를 추가하려면 선언형 방식인 defineElement 사용을 강력히 권장합니다.

그리드와 오버레이에 커스텀 UI 요소를 추가합니다.

기본 사용법

const panel = dmn.ui.displayElement.add({ html: `<div style="background: #000; color: #fff; padding: 16px;"> Hello! </div>`, position: { x: 100, y: 100 }, draggable: true, }); // 제거 panel.remove();

상태 기반 템플릿

const panel = dmn.ui.displayElement.add({ position: { x: 100, y: 100 }, state: { value: 0, history: [] }, template: (state, { html }) => html` <div style="background: #000; color: #fff; padding: 16px;"> <strong>${state.value}</strong> <div style="display: flex; gap: 2px;"> ${state.history.map( (v) => html` <span style="width: 4px; height: ${v}px; background: #86EFAC;" ></span> `, )} </div> </div> `, }); // 상태 업데이트 → 자동 리렌더링 panel.setState({ value: 42, history: [10, 20, 30] });

이벤트 핸들러

dmn.ui.displayElement.add({ html: `<div>클릭하세요</div>`, position: { x: 100, y: 100 }, // 함수 직접 전달 (권장) onClick: async () => { console.log('클릭됨!'); }, onPositionChange: async (pos) => { await dmn.plugin.storage.set('position', pos); }, onDelete: async () => { console.log('삭제됨!'); }, });

인스턴스 메서드

메서드설명
setState(updates)상태 업데이트 (템플릿 리렌더링)
setData(updates)상태 업데이트 (setState 별칭)
getState()현재 상태 조회
setText(selector, text)요소 텍스트 설정
setHTML(selector, html)요소 HTML 설정
setStyle(selector, styles)스타일 적용
addClass(selector, ...classes)클래스 추가
removeClass(selector, ...classes)클래스 제거
toggleClass(selector, className)클래스 토글
query(selector)요소 검색 (Shadow DOM 포함)
update(config)메타데이터 업데이트
remove()요소 제거

앵커

특정 키에 요소를 고정합니다:

dmn.ui.displayElement.add({ html: `<div>→</div>`, position: { x: 0, y: 0 }, anchor: { keyCode: 'D', offset: { x: 70, y: 0 }, }, });

anchor.keyCode는 canonical 슬롯 식별자입니다. 멀티 키 슬롯에는 canonical 문자열을 사용하세요 (match: 'all'"LEFT CTRL+Z", match: 'any'"F|NUMPAD 4" 형태). canonical 슬롯 식별자를 참고하세요.

Shadow DOM

스타일을 격리하려면 scoped: true를 사용합니다:

dmn.ui.displayElement.add({ html: ` <style> :host { display: block; } .widget { background: red; } </style> <div class="widget">격리된 스타일</div> `, position: { x: 100, y: 100 }, scoped: true, });

클린업

dmn.plugin.registerCleanup(() => { // 모든 Display Element 제거 dmn.ui.displayElement.clearMyElements(); });

Dialog

사용자와 상호작용할 수 있는 모달 다이얼로그입니다.

alert

간단한 알림을 표시합니다. 반환된 Promise는 사용자가 확인 버튼을 누르거나 다이얼로그가 닫힐 때 resolve됩니다.

await dmn.ui.dialog.alert('저장되었습니다!'); // 사용자가 확인을 누른 뒤에 이어서 실행됨 await dmn.ui.dialog.alert('작업 완료', { confirmText: 'OK' });

confirm

확인/취소 다이얼로그를 표시합니다.

옵션: confirmText(확인 버튼 문구), cancelText(취소 버튼 문구), danger(파괴적 작업용, 확인 버튼이 위험 색상으로 표시)

const ok = await dmn.ui.dialog.confirm('진행하시겠습니까?'); if (ok) { // 확인 클릭 } // 위험한 작업 const confirmed = await dmn.ui.dialog.confirm('모든 데이터가 삭제됩니다.', { danger: true, confirmText: '삭제', cancelText: '유지', });

custom

커스텀 HTML 다이얼로그를 표시합니다. Components API와 함께 사용합니다.

const formHtml = ` <div class="flex flex-col gap-[12px]"> ${dmn.ui.components.formRow( '이름', dmn.ui.components.input({ id: 'name' }), )} ${dmn.ui.components.formRow( '테마', dmn.ui.components.dropdown({ id: 'theme', options: [ { label: '다크', value: 'dark' }, { label: '라이트', value: 'light' }, ], }), )} </div> `; const confirmed = await dmn.ui.dialog.custom(formHtml, { confirmText: '저장', showCancel: true, }); if (confirmed) { const name = document.getElementById('name').value; const theme = document.getElementById('theme').value; }

Components

Dialog 내부에서 사용할 수 있는 UI 컴포넌트입니다. 모든 컴포넌트는 HTML 문자열을 반환합니다.

button

dmn.ui.components.button('저장', { variant: 'primary', // "primary" | "danger" | "secondary" onClick: () => console.log('클릭'), });

checkbox

dmn.ui.components.checkbox({ checked: true, id: 'enabled', onChange: (checked) => console.log('상태:', checked), });

input

dmn.ui.components.input({ type: 'number', value: 50, min: 0, max: 100, width: 100, id: 'volume', onChange: (value) => console.log('값:', value), });
dmn.ui.components.dropdown({ options: [ { label: '다크', value: 'dark' }, { label: '라이트', value: 'light' }, ], selected: 'dark', id: 'theme', onChange: (value) => console.log('선택:', value), });

formRow

라벨과 컴포넌트를 수평으로 배치합니다.

dmn.ui.components.formRow('볼륨', volumeInput);

panel

컨텐츠를 감싸는 패널 컨테이너입니다.

panel은 Display Element 전용입니다. Dialog 내부에서는 사용하지 마세요.

const panelHtml = dmn.ui.components.panel(formHtml, { title: '설정', width: 400, }); dmn.ui.displayElement.add({ html: panelHtml, position: { x: 100, y: 100 }, });

Color Picker

색상 선택기를 표시합니다.

dmn.ui.pickColor({ initialColor: '#ff0000', onColorChange: (color) => { console.log('색상 변경 중:', color); }, onColorChangeComplete: (color) => { console.log('색상 선택 완료:', color); }, // 선택적 옵션 position: { x: 100, y: 100 }, // 직접 위치 지정 referenceElement: document.getElementById('my-button'), // 요소 기준 위치 });

옵션

속성타입설명
initialColorstring초기 색상 (HEX)
onColorChange(color) => void색상이 변경될 때마다 호출
onColorChangeComplete(color) => void색상 선택이 완료되었을 때 호출
onClose() => void색상 선택기가 닫힐 때 호출
position{ x, y }표시 위치 (referenceElement가 없을 때 사용)
referenceElementHTMLElement이 요소를 기준으로 위치 설정
idstring고유 ID (중복 방지용)

실전 예제: 설정 패널

// @id settings-panel if (dmn.window.type !== 'main') return; let settings = { enabled: true, theme: 'dark', volume: 50, }; async function showSettings() { const tempSettings = { ...settings }; const formHtml = ` <div class="flex flex-col gap-[12px]"> ${dmn.ui.components.formRow( '활성화', dmn.ui.components.checkbox({ checked: settings.enabled, onChange: (v) => (tempSettings.enabled = v), }), )} ${dmn.ui.components.formRow( '테마', dmn.ui.components.dropdown({ options: [ { label: '다크', value: 'dark' }, { label: '라이트', value: 'light' }, ], selected: settings.theme, onChange: (v) => (tempSettings.theme = v), }), )} ${dmn.ui.components.formRow( '볼륨', dmn.ui.components.input({ type: 'number', value: settings.volume, min: 0, max: 100, width: 60, onChange: (v) => (tempSettings.volume = parseInt(v)), }), )} </div> `; const confirmed = await dmn.ui.dialog.custom(formHtml, { confirmText: '저장', showCancel: true, }); if (confirmed) { settings = tempSettings; await dmn.plugin.storage.set('settings', settings); await dmn.ui.dialog.alert('저장되었습니다!'); } } // 그리드 메뉴에 설정 추가 dmn.ui.contextMenu.addGridMenuItem({ id: 'open-settings', label: '설정', onClick: showSettings, }); // 초기화 (async () => { const saved = await dmn.plugin.storage.get('settings'); if (saved) settings = saved; })(); dmn.plugin.registerCleanup(() => { dmn.ui.contextMenu.clearMyMenuItems(); });