Skip to content

Commit 26318e0

Browse files
committed
feat: defineElement resizeAnchor 옵션 추가
1 parent 1c773e4 commit 26318e0

7 files changed

Lines changed: 647 additions & 18 deletions

File tree

docs/plugin/api-reference/_meta.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export default {
77
"css-js": "CSS/JS",
88
presets: "프리셋 (presets)",
99
i18n: "다국어 (i18n)",
10+
plugin: "플러그인 (plugin)",
1011
};
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
````mdx
2+
---
3+
title: 플러그인 (plugin)
4+
description: dmn.plugin 네임스페이스 API 레퍼런스
5+
---
6+
7+
# 플러그인 API (dmn.plugin)
8+
9+
플러그인 생명주기, 스토리지, 선언형 UI 정의를 위한 API입니다.
10+
11+
## 메서드
12+
13+
### registerCleanup
14+
15+
플러그인 언로드 시 호출될 클린업 함수를 등록합니다.
16+
17+
```typescript
18+
dmn.plugin.registerCleanup(cleanup: () => void): void
19+
```
20+
````
21+
22+
**예시:**
23+
24+
```javascript
25+
const timer = setInterval(() => console.log("tick"), 1000);
26+
27+
dmn.plugin.registerCleanup(() => {
28+
clearInterval(timer);
29+
console.log("플러그인 정리 완료");
30+
});
31+
```
32+
33+
### storage
34+
35+
플러그인별 격리된 스토리지입니다. 자세한 내용은 [스토리지](/plugin/storage) 문서를 참고하세요.
36+
37+
```typescript
38+
dmn.plugin.storage.get(key: string): Promise<any>
39+
dmn.plugin.storage.set(key: string, value: any): Promise<void>
40+
dmn.plugin.storage.remove(key: string): Promise<void>
41+
dmn.plugin.storage.clear(): Promise<void>
42+
dmn.plugin.storage.keys(): Promise<string[]>
43+
```
44+
45+
### defineElement
46+
47+
선언형 UI를 정의합니다. 자세한 내용은 [선언형 API](/plugin/declarative-api) 문서를 참고하세요.
48+
49+
```typescript
50+
dmn.plugin.defineElement(definition: PluginDefinition): void
51+
```
52+
53+
## PluginDefinition
54+
55+
`defineElement`에 전달하는 설정 객체입니다.
56+
57+
```typescript
58+
interface PluginDefinition {
59+
/** 플러그인 이름 (컨텍스트 메뉴에 표시) */
60+
name: string;
61+
62+
/** 최대 인스턴스 개수 (0 = 무제한, 기본값) */
63+
maxInstances?: number;
64+
65+
/**
66+
* 리사이즈 앵커 (크기 변경 시 기준점)
67+
* @default "top-left"
68+
*/
69+
resizeAnchor?: ElementResizeAnchor;
70+
71+
/** 컨텍스트 메뉴 설정 */
72+
contextMenu?: {
73+
create?: string; // 생성 메뉴 라벨
74+
delete?: string; // 삭제 메뉴 라벨
75+
items?: PluginDefinitionContextMenuItem[];
76+
};
77+
78+
/** 설정 스키마 */
79+
settings?: Record<string, PluginSettingSchema>;
80+
81+
/** 다국어 메시지 */
82+
messages?: Record<string, Record<string, string>>;
83+
84+
/** 미리보기 상태 (메인 윈도우용) */
85+
previewState?: Record<string, any>;
86+
87+
/** 템플릿 함수 */
88+
template: (
89+
state: Record<string, any>,
90+
settings: Record<string, any>,
91+
helpers: DisplayElementTemplateHelpers
92+
) => DisplayElementTemplateResult | string;
93+
94+
/** 마운트 로직 (오버레이에서만 실행) */
95+
onMount?: (context: PluginDefinitionHookContext) => void | (() => void);
96+
}
97+
```
98+
99+
## ElementResizeAnchor
100+
101+
요소 크기 변경 시 기준점을 지정하는 타입입니다.
102+
103+
```typescript
104+
type ElementResizeAnchor =
105+
| "top-left" // 좌상단 (기본값)
106+
| "top-center" // 상단 중앙
107+
| "top-right" // 우상단
108+
| "center-left" // 좌측 중앙
109+
| "center" // 정중앙
110+
| "center-right" // 우측 중앙
111+
| "bottom-left" // 좌하단
112+
| "bottom-center" // 하단 중앙
113+
| "bottom-right"; // 우하단
114+
```
115+
116+
### 앵커 동작
117+
118+
크기가 변경될 때 지정한 앵커 위치가 고정되고, 다른 방향으로 확장/축소됩니다.
119+
120+
| 앵커 | 동작 |
121+
| -------------- | ---------------------------- |
122+
| `top-left` | 우측, 하단으로 확장/축소 |
123+
| `center` | 모든 방향으로 균등 확장/축소 |
124+
| `bottom-right` | 좌측, 상단으로 확장/축소 |
125+
126+
## PluginDefinitionHookContext
127+
128+
`onMount` 콜백에 전달되는 컨텍스트 객체입니다.
129+
130+
```typescript
131+
interface PluginDefinitionHookContext {
132+
/** 상태 업데이트 */
133+
setState: (updates: Record<string, any>) => void;
134+
135+
/** 현재 설정 조회 */
136+
getSettings: () => Record<string, any>;
137+
138+
/** 리사이즈 앵커 설정 */
139+
setAnchor: (anchor: ElementResizeAnchor) => void;
140+
141+
/** 현재 리사이즈 앵커 조회 */
142+
getAnchor: () => ElementResizeAnchor;
143+
144+
/**
145+
* 이벤트 훅 등록
146+
* - "key": 매핑된 키 이벤트
147+
* - "rawKey": 모든 원시 입력 이벤트
148+
*/
149+
onHook: (event: "key" | "rawKey", callback: (...args: any[]) => void) => void;
150+
151+
/** 컨텍스트 메뉴용 함수 노출 */
152+
expose: (actions: Record<string, (...args: any[]) => any>) => void;
153+
154+
/** 현재 언어 코드 */
155+
locale: string;
156+
157+
/** 번역 함수 */
158+
t: PluginTranslateFn;
159+
160+
/** 언어 변경 구독 */
161+
onLocaleChange: (listener: (locale: string) => void) => Unsubscribe;
162+
163+
/** 설정 변경 구독 */
164+
onSettingsChange: (
165+
listener: (
166+
newSettings: Record<string, any>,
167+
oldSettings: Record<string, any>
168+
) => void
169+
) => void;
170+
}
171+
```
172+
173+
### setAnchor / getAnchor
174+
175+
인스턴스별로 리사이즈 앵커를 동적으로 변경하거나 조회합니다.
176+
177+
```javascript
178+
onMount: ({ setAnchor, getAnchor, getSettings }) => {
179+
// 현재 앵커 확인
180+
console.log("현재 앵커:", getAnchor()); // "top-left" (기본값)
181+
182+
// 앵커를 중앙으로 변경
183+
setAnchor("center");
184+
185+
// 설정에 따라 앵커 변경
186+
const settings = getSettings();
187+
if (settings.expandFromCenter) {
188+
setAnchor("center");
189+
}
190+
},
191+
```
192+
193+
**앵커 우선순위:**
194+
195+
1. `setAnchor()`로 설정한 인스턴스별 앵커
196+
2. `PluginDefinition.resizeAnchor` (Definition 기본값)
197+
3. `"top-left"` (시스템 기본값)
198+
199+
## PluginSettingSchema
200+
201+
설정 스키마 정의 타입입니다.
202+
203+
```typescript
204+
interface PluginSettingSchema {
205+
type: "boolean" | "color" | "number" | "string" | "select";
206+
default: any;
207+
label: string;
208+
min?: number; // number 타입용
209+
max?: number; // number 타입용
210+
step?: number; // number 타입용
211+
options?: { label: string; value: any }[]; // select 타입용
212+
placeholder?: string; // string/number 타입용
213+
}
214+
```
215+
216+
## 예시
217+
218+
### 기본 플러그인
219+
220+
```javascript
221+
// @id simple-counter
222+
223+
dmn.plugin.defineElement({
224+
name: "Simple Counter",
225+
maxInstances: 3,
226+
resizeAnchor: "top-left",
227+
228+
settings: {
229+
textColor: {
230+
type: "color",
231+
default: "#FFFFFF",
232+
label: "텍스트 색상",
233+
},
234+
},
235+
236+
previewState: {
237+
count: 42,
238+
},
239+
240+
template: (state, settings, { html }) => html`
241+
<div style="color: ${settings.textColor}; padding: 16px;">
242+
Count: ${state.count ?? 0}
243+
</div>
244+
`,
245+
246+
onMount: ({ setState, onHook }) => {
247+
let count = 0;
248+
249+
onHook("key", ({ state }) => {
250+
if (state === "DOWN") {
251+
count++;
252+
setState({ count });
253+
}
254+
});
255+
},
256+
});
257+
```
258+
259+
### 동적 앵커 변경
260+
261+
```javascript
262+
// @id dynamic-anchor-panel
263+
264+
dmn.plugin.defineElement({
265+
name: "Dynamic Anchor Panel",
266+
resizeAnchor: "top-left", // 기본 앵커
267+
268+
settings: {
269+
expandFromCenter: {
270+
type: "boolean",
271+
default: false,
272+
label: "중앙에서 확장",
273+
},
274+
},
275+
276+
template: (state, settings, { html }) => html`
277+
<div style="padding: 16px; background: rgba(0,0,0,0.8);">
278+
앵커: ${state.currentAnchor ?? "top-left"}
279+
</div>
280+
`,
281+
282+
onMount: ({
283+
setState,
284+
getSettings,
285+
setAnchor,
286+
getAnchor,
287+
onSettingsChange,
288+
}) => {
289+
// 초기 앵커 설정
290+
const settings = getSettings();
291+
const anchor = settings.expandFromCenter ? "center" : "top-left";
292+
setAnchor(anchor);
293+
setState({ currentAnchor: anchor });
294+
295+
// 설정 변경 시 앵커 업데이트
296+
onSettingsChange((newSettings, oldSettings) => {
297+
if (newSettings.expandFromCenter !== oldSettings.expandFromCenter) {
298+
const newAnchor = newSettings.expandFromCenter ? "center" : "top-left";
299+
setAnchor(newAnchor);
300+
setState({ currentAnchor: newAnchor });
301+
}
302+
});
303+
},
304+
});
305+
```
306+
307+
```
308+
309+
```

0 commit comments

Comments
 (0)