@operato/property-panel 10.0.0-beta.58 → 10.0.0-beta.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [10.0.0-beta.59](https://github.com/hatiolab/operato/compare/v10.0.0-beta.58...v10.0.0-beta.59) (2026-06-03)
7
+
8
+
9
+ ### :bug: Bug Fix
10
+
11
+ * **property-panel/event-handlers:** popup 폭 / 높이 레이아웃 회귀 정리 ([799a2f2](https://github.com/hatiolab/operato/commit/799a2f2b5713fc6d56748f5ef8887e55c0eab8ab))
12
+
13
+
14
+ ### :rocket: New Features
15
+
16
+ * **property-panel/event-handlers:** help pane + UX 개선 + (none) 핸들러 제외 ([8c23f62](https://github.com/hatiolab/operato/commit/8c23f62008aea1dcbf4b305cce432eeda7807dd5))
17
+
18
+
19
+ ### :sparkles: Styling
20
+
21
+ * **property-panel/event-handlers:** popup + help pane 에 표준 ScrollbarStyles 적용 ([e11f4a6](https://github.com/hatiolab/operato/commit/e11f4a64a1a223498f02b9f02cdfedd441cef869))
22
+ * **property-panel/event-handlers:** popup 안 mapper 의 상단 모서리도 둥글림 ([717422c](https://github.com/hatiolab/operato/commit/717422cb96b698ae151293dcdf106c1b52a3f9e1))
23
+
24
+
25
+
6
26
  ## [10.0.0-beta.58](https://github.com/hatiolab/operato/compare/v10.0.0-beta.57...v10.0.0-beta.58) (2026-05-28)
7
27
 
8
28
 
@@ -3,6 +3,7 @@ export * from './property-panel/data-binding/data-binding.js';
3
3
  export * from './property-panel/event-handlers/event-handlers-mapper.js';
4
4
  export * from './property-panel/event-handlers/event-handlers.js';
5
5
  export * from './property-panel/event-handlers/event-handlers-popup.js';
6
+ export * from './property-panel/event-handlers/event-handlers-help.js';
6
7
  export * from './property-panel/effects/effects.js';
7
8
  export * from './property-panel/inspector/inspector.js';
8
9
  export * from './property-panel/shapes/shapes.js';
package/dist/src/index.js CHANGED
@@ -5,6 +5,7 @@ export * from './property-panel/data-binding/data-binding.js';
5
5
  export * from './property-panel/event-handlers/event-handlers-mapper.js';
6
6
  export * from './property-panel/event-handlers/event-handlers.js';
7
7
  export * from './property-panel/event-handlers/event-handlers-popup.js';
8
+ export * from './property-panel/event-handlers/event-handlers-help.js';
8
9
  export * from './property-panel/effects/effects.js';
9
10
  export * from './property-panel/inspector/inspector.js';
10
11
  export * from './property-panel/shapes/shapes.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,cAAc,+CAA+C,CAAA;AAC7D,0EAA0E;AAC1E,sEAAsE;AACtE,cAAc,0DAA0D,CAAA;AACxE,cAAc,mDAAmD,CAAA;AACjE,cAAc,yDAAyD,CAAA;AACvE,cAAc,qCAAqC,CAAA;AACnD,cAAc,yCAAyC,CAAA;AACvD,cAAc,mCAAmC,CAAA;AACjD,cAAc,yCAAyC,CAAA;AACvD,cAAc,mCAAmC,CAAA;AACjD,cAAc,mCAAmC,CAAA","sourcesContent":["export { OxPropertyPanel } from './ox-property-panel.js'\n\nexport * from './property-panel/data-binding/data-binding.js'\n// Event handlers — inline panel + popup (data-binding 패턴 동일). effects tab\n// 의 PropertyEvent fieldset 에서 inline 으로 host. open_in_new 클릭 시 popup.\nexport * from './property-panel/event-handlers/event-handlers-mapper.js'\nexport * from './property-panel/event-handlers/event-handlers.js'\nexport * from './property-panel/event-handlers/event-handlers-popup.js'\nexport * from './property-panel/effects/effects.js'\nexport * from './property-panel/inspector/inspector.js'\nexport * from './property-panel/shapes/shapes.js'\nexport * from './property-panel/specifics/specifics.js'\nexport * from './property-panel/styles/styles.js'\nexport * from './property-panel/threed/threed.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,cAAc,+CAA+C,CAAA;AAC7D,0EAA0E;AAC1E,sEAAsE;AACtE,cAAc,0DAA0D,CAAA;AACxE,cAAc,mDAAmD,CAAA;AACjE,cAAc,yDAAyD,CAAA;AACvE,cAAc,wDAAwD,CAAA;AACtE,cAAc,qCAAqC,CAAA;AACnD,cAAc,yCAAyC,CAAA;AACvD,cAAc,mCAAmC,CAAA;AACjD,cAAc,yCAAyC,CAAA;AACvD,cAAc,mCAAmC,CAAA;AACjD,cAAc,mCAAmC,CAAA","sourcesContent":["export { OxPropertyPanel } from './ox-property-panel.js'\n\nexport * from './property-panel/data-binding/data-binding.js'\n// Event handlers — inline panel + popup (data-binding 패턴 동일). effects tab\n// 의 PropertyEvent fieldset 에서 inline 으로 host. open_in_new 클릭 시 popup.\nexport * from './property-panel/event-handlers/event-handlers-mapper.js'\nexport * from './property-panel/event-handlers/event-handlers.js'\nexport * from './property-panel/event-handlers/event-handlers-popup.js'\nexport * from './property-panel/event-handlers/event-handlers-help.js'\nexport * from './property-panel/effects/effects.js'\nexport * from './property-panel/inspector/inspector.js'\nexport * from './property-panel/shapes/shapes.js'\nexport * from './property-panel/specifics/specifics.js'\nexport * from './property-panel/styles/styles.js'\nexport * from './property-panel/threed/threed.js'\n"]}
@@ -17,7 +17,7 @@ export declare class PropertyEventHover extends PropertyEventHover_base {
17
17
  };
18
18
  firstUpdated(): void;
19
19
  render(): import("lit-html").TemplateResult<1>;
20
- _getPlaceHoder(action: string): "" | "SCENE-100" | "http://www.hatiolab.com/";
20
+ _getPlaceHoder(action: string): "" | "http://www.hatiolab.com/" | "SCENE-100";
21
21
  _getTargetList(action: string): {
22
22
  value: string;
23
23
  description?: string;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * EventHandlers help pane — popup 의 우측 docs 컬럼.
5
+ *
6
+ * 현재 선택된 handler 의 trigger / action / target / value (또는 script vars)
7
+ * 의미를 *동적으로* 안내. script 모드에서는 자주 쓰는 코드 snippet 을
8
+ * 삽입할 수 있는 버튼도 제공.
9
+ *
10
+ * 다국어 — 1차안은 한국어 hard-code (5 언어 동시 유지 부담 회피).
11
+ * 추후 안정화 후 i18n 키로 분리.
12
+ */
13
+ import '@material/web/icon/icon.js';
14
+ import { LitElement } from 'lit';
15
+ import type { EventHandlerSpec } from './event-handlers-mapper.js';
16
+ export declare class EventHandlersHelp extends LitElement {
17
+ static styles: import("lit").CSSResult[];
18
+ handler?: EventHandlerSpec;
19
+ render(): import("lit-html").TemplateResult<1>;
20
+ private _renderDeclarativeHelp;
21
+ private _renderScriptHelp;
22
+ private _emitSnippet;
23
+ }
@@ -0,0 +1,356 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * EventHandlers help pane — popup 의 우측 docs 컬럼.
5
+ *
6
+ * 현재 선택된 handler 의 trigger / action / target / value (또는 script vars)
7
+ * 의미를 *동적으로* 안내. script 모드에서는 자주 쓰는 코드 snippet 을
8
+ * 삽입할 수 있는 버튼도 제공.
9
+ *
10
+ * 다국어 — 1차안은 한국어 hard-code (5 언어 동시 유지 부담 회피).
11
+ * 추후 안정화 후 i18n 키로 분리.
12
+ */
13
+ import { __decorate } from "tslib";
14
+ import '@material/web/icon/icon.js';
15
+ import { css, html, LitElement } from 'lit';
16
+ import { property } from 'lit/decorators.js';
17
+ import { ScrollbarStyles } from '@operato/styles';
18
+ const TRIGGER_DESC = {
19
+ click: {
20
+ summary: '마우스 클릭 (터치 환경에선 tap 과 동일).',
21
+ example: '가장 일반적인 버튼/아이콘 액션에 사용.'
22
+ },
23
+ tap: {
24
+ summary: 'click 의 alias (legacy 호환). 신규 등록은 click 권장.'
25
+ },
26
+ dblclick: {
27
+ summary: '더블 클릭. 빠른 연속 2회 클릭으로만 발화.'
28
+ },
29
+ mouseenter: {
30
+ summary: '포인터가 컴포넌트 영역에 *진입* 한 순간 1회.',
31
+ example: 'hover 강조 / tooltip 표시에 사용.'
32
+ },
33
+ hover: {
34
+ summary: 'mouseenter 의 alias (legacy). 신규 등록은 mouseenter 권장.'
35
+ },
36
+ mouseleave: {
37
+ summary: '포인터가 컴포넌트 영역을 *이탈* 한 순간 1회.',
38
+ example: 'mouseenter 와 짝지어 강조 해제에 사용.'
39
+ },
40
+ longpress: {
41
+ summary: '약 500ms 이상 누름. 클릭과 별개로 발화.'
42
+ },
43
+ mousedown: {
44
+ summary: '버튼 누름 시작 시점. mouseup 과 짝지어 pressed 패턴 가능.'
45
+ },
46
+ mouseup: {
47
+ summary: '버튼 누름 해제 시점.'
48
+ },
49
+ pressed: {
50
+ summary: 'mousedown ↔ mouseup 페어 자동 매핑 (legacy 호환).'
51
+ }
52
+ };
53
+ const ACTION_DESC = {
54
+ '': {
55
+ summary: '액션이 선택되지 않음. 핸들러는 등록되지만 아무 동작도 하지 않는다.'
56
+ },
57
+ script: {
58
+ summary: '자유 JavaScript 코드. component / scene / event / targets / value 를 변수로 받아 임의 동작을 수행.',
59
+ example: 'await 사용 가능 — 비동기 동작도 그대로 작성.'
60
+ },
61
+ 'data-toggle': {
62
+ summary: 'target 컴포넌트의 data 값을 truthy ↔ falsy 로 토글.',
63
+ example: 'value 필드는 무시. target 미설정 시 자기 자신.'
64
+ },
65
+ 'data-tristate': {
66
+ summary: 'target 의 data 값을 0 → 1 → 2 → 0 순환.',
67
+ example: '세 상태 토글 (off / on / mixed 같은) 용.'
68
+ },
69
+ 'data-spreading': {
70
+ summary: 'target 들에 value 를 분배 (배열 또는 매핑).',
71
+ example: 'value 가 배열이면 target N개에 각각 매칭.'
72
+ },
73
+ 'data-set': {
74
+ summary: 'target 의 data 를 value 로 설정. value 는 정적 값 또는 accessor.',
75
+ example: '예) value=1 / value=accessor.someField'
76
+ },
77
+ 'partial-data-set': {
78
+ summary: 'target.data 가 객체일 때, value 의 키들만 partial merge.',
79
+ example: 'value 는 객체 또는 객체를 가리키는 accessor.'
80
+ },
81
+ 'value-set': {
82
+ summary: 'target 의 value 속성을 value 로 설정.',
83
+ example: 'data 가 아니라 value 속성을 다루는 컴포넌트용.'
84
+ },
85
+ 'partial-value-set': {
86
+ summary: 'target.value 가 객체일 때, partial merge.'
87
+ },
88
+ 'info-window': {
89
+ summary: 'target 의 info-window (팝업) 를 띄움.',
90
+ example: 'target 컴포넌트의 popup 정보가 미리 정의되어 있어야 함.'
91
+ },
92
+ emphasize: {
93
+ summary: 'target 을 시각적으로 강조 (애니메이션).',
94
+ example: 'mouseenter / mouseleave 와 짝지어 emphasize/restore 패턴 자주 사용.'
95
+ }
96
+ };
97
+ const SCRIPT_VARS = [
98
+ { name: 'component', desc: '핸들러가 등록된 현재 컴포넌트.' },
99
+ { name: 'scene', desc: '루트 scene. scene.root 로 다른 컴포넌트 검색.' },
100
+ { name: 'event', desc: '원본 DOM 이벤트 (또는 합성 이벤트) 객체.' },
101
+ { name: 'targets', desc: 'target selector 로 찾은 컴포넌트 배열. target 비면 빈 배열.' },
102
+ { name: 'value', desc: '핸들러의 value 필드 (정적 또는 accessor).' }
103
+ ];
104
+ const SNIPPETS = [
105
+ {
106
+ label: 'console.log',
107
+ code: `console.log('event handler', { component, event })`
108
+ },
109
+ {
110
+ label: 'data 토글',
111
+ code: `component.data = !component.data`
112
+ },
113
+ {
114
+ label: 'targets 에 값 세팅',
115
+ code: `targets.forEach(t => { t.data = value })`
116
+ },
117
+ {
118
+ label: 'findById 로 특정 컴포넌트 변경',
119
+ code: `// '#someId' 는 대상 컴포넌트의 id 로 바꿔 쓴다.
120
+ const target = scene.root.findAll('#someId', component)[0]
121
+ if (target) {
122
+ target.data = component.data
123
+ }`
124
+ },
125
+ {
126
+ label: 'async fetch + data 반영',
127
+ code: `const res = await fetch('/api/something')
128
+ const json = await res.json()
129
+ component.data = json`
130
+ }
131
+ ];
132
+ export class EventHandlersHelp extends LitElement {
133
+ render() {
134
+ var _a;
135
+ const h = this.handler || { trigger: 'click', action: '' };
136
+ const trigDesc = TRIGGER_DESC[h.trigger] || { summary: '(unknown trigger)' };
137
+ const actDesc = ACTION_DESC[(_a = h.action) !== null && _a !== void 0 ? _a : ''] || { summary: '(unknown action)' };
138
+ const isScript = h.action === 'script';
139
+ return html `
140
+ <section>
141
+ <h4>Trigger</h4>
142
+ <div class="name">${h.trigger}</div>
143
+ <p class="summary">${trigDesc.summary}</p>
144
+ ${trigDesc.example ? html `<div class="example">${trigDesc.example}</div>` : ''}
145
+ </section>
146
+
147
+ <section>
148
+ <h4>Action</h4>
149
+ <div class="name">${h.action || '(none)'}</div>
150
+ <p class="summary">${actDesc.summary}</p>
151
+ ${actDesc.example ? html `<div class="example">${actDesc.example}</div>` : ''}
152
+ </section>
153
+
154
+ ${isScript ? this._renderScriptHelp() : this._renderDeclarativeHelp()}
155
+ `;
156
+ }
157
+ _renderDeclarativeHelp() {
158
+ return html `
159
+ <section>
160
+ <h4>Target selector</h4>
161
+ <div class="selector-grid">
162
+ <code>#id</code><span>id 일치</span>
163
+ <code>.class</code><span>class 일치 (공백 구분 다중)</span>
164
+ <code>&lt;타입&gt;</code><span>type 일치 (예: rect, button)</span>
165
+ <code>(self)</code><span>현재 컴포넌트 자기 자신</span>
166
+ <code>(all)</code><span>모든 컴포넌트</span>
167
+ <code>(root)</code><span>루트 컴포넌트</span>
168
+ <code>(parent)</code><span>부모</span>
169
+ <code>(children)</code><span>직접 자식들</span>
170
+ <code>(siblings)</code><span>형제들 (자기 제외)</span>
171
+ </div>
172
+ <div class="example" style="margin-top:6px">
173
+ 비워두면 매칭 없음 — 자기 자신을 대상으로 하려면 <code style="background:transparent;padding:0">(self)</code>.
174
+ </div>
175
+ </section>
176
+
177
+ <section>
178
+ <h4>Value</h4>
179
+ <p class="summary">정적 값 또는 accessor 표현식. accessor 인 경우 component.access(value) 로 평가되어 컴포넌트 컨텍스트의 데이터를 참조한다.</p>
180
+ </section>
181
+ `;
182
+ }
183
+ _renderScriptHelp() {
184
+ return html `
185
+ <section>
186
+ <h4>Script 변수</h4>
187
+ <ul class="vars">
188
+ ${SCRIPT_VARS.map(v => html `
189
+ <li><code>${v.name}</code>${v.desc}</li>
190
+ `)}
191
+ </ul>
192
+ <div class="example" style="margin-top:8px">
193
+ <code style="background:transparent;padding:0">await</code> 키워드 사용 시 스크립트는 비동기로 실행. 결과 wait 후 다음 핸들러가 영향 받지는 않는다 (각 핸들러는 독립 실행).
194
+ </div>
195
+ </section>
196
+
197
+ <section>
198
+ <h4>Snippet 삽입</h4>
199
+ <div class="snippet-list">
200
+ ${SNIPPETS.map(s => html `
201
+ <button
202
+ type="button"
203
+ class="snippet-btn"
204
+ @click=${() => this._emitSnippet(s.code)}
205
+ title="현재 스크립트 영역에 삽입 (덮어쓰지 않고 끝에 추가)"
206
+ >
207
+ <span>${s.label}</span>
208
+ <md-icon>add</md-icon>
209
+ </button>
210
+ `)}
211
+ </div>
212
+ </section>
213
+ `;
214
+ }
215
+ _emitSnippet(code) {
216
+ this.dispatchEvent(new CustomEvent('insert-snippet', {
217
+ bubbles: true,
218
+ composed: true,
219
+ detail: { code }
220
+ }));
221
+ }
222
+ }
223
+ EventHandlersHelp.styles = [
224
+ ScrollbarStyles,
225
+ css `
226
+ :host {
227
+ display: flex;
228
+ flex-direction: column;
229
+ width: 100%;
230
+ height: 100%;
231
+ box-sizing: border-box;
232
+ background: var(--md-sys-color-surface-container-lowest, #fffbfe);
233
+ border-left: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));
234
+ padding: 16px 18px;
235
+ overflow-y: auto;
236
+ font-family: 'Roboto', Arial, sans-serif;
237
+ font-size: 12.5px;
238
+ color: var(--md-sys-color-on-surface, #1c1b1f);
239
+ line-height: 1.5;
240
+ min-width: 0;
241
+ }
242
+
243
+ h4 {
244
+ margin: 0 0 6px 0;
245
+ font-size: 11px;
246
+ font-weight: 700;
247
+ letter-spacing: 0.5px;
248
+ text-transform: uppercase;
249
+ color: var(--md-sys-color-on-surface-variant, #49454f);
250
+ }
251
+
252
+ section {
253
+ margin-bottom: 18px;
254
+ }
255
+
256
+ .name {
257
+ display: inline-block;
258
+ padding: 2px 6px;
259
+ margin-bottom: 6px;
260
+ background: var(--md-sys-color-secondary-container, #e8def8);
261
+ color: var(--md-sys-color-on-secondary-container, #1d192b);
262
+ border-radius: 4px;
263
+ font-weight: 600;
264
+ font-size: 11.5px;
265
+ }
266
+
267
+ .summary {
268
+ margin: 0 0 6px 0;
269
+ }
270
+
271
+ .example {
272
+ margin: 4px 0 0 0;
273
+ padding: 6px 8px;
274
+ background: rgba(0, 0, 0, 0.04);
275
+ border-left: 2px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.2));
276
+ font-size: 11.5px;
277
+ color: var(--md-sys-color-on-surface-variant, #49454f);
278
+ }
279
+
280
+ ul.vars {
281
+ list-style: none;
282
+ margin: 0;
283
+ padding: 0;
284
+ }
285
+
286
+ ul.vars li {
287
+ padding: 4px 0;
288
+ border-bottom: 1px dashed var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.08));
289
+ }
290
+
291
+ ul.vars li:last-child {
292
+ border-bottom: none;
293
+ }
294
+
295
+ ul.vars code {
296
+ display: inline-block;
297
+ padding: 1px 5px;
298
+ margin-right: 6px;
299
+ background: var(--md-sys-color-surface-container-high, #ede7f6);
300
+ border-radius: 3px;
301
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
302
+ font-size: 11px;
303
+ color: var(--md-sys-color-on-surface, #1c1b1f);
304
+ }
305
+
306
+ .snippet-list {
307
+ display: flex;
308
+ flex-direction: column;
309
+ gap: 6px;
310
+ }
311
+
312
+ .snippet-btn {
313
+ display: flex;
314
+ align-items: center;
315
+ justify-content: space-between;
316
+ gap: 6px;
317
+ padding: 6px 10px;
318
+ border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.18));
319
+ border-radius: 4px;
320
+ background: var(--md-sys-color-surface, #fff);
321
+ color: var(--md-sys-color-on-surface, #1c1b1f);
322
+ font-size: 12px;
323
+ cursor: pointer;
324
+ text-align: left;
325
+ }
326
+
327
+ .snippet-btn:hover {
328
+ background: var(--md-sys-color-surface-container-low, #f7f2fa);
329
+ }
330
+
331
+ .snippet-btn md-icon {
332
+ --md-icon-size: 16px;
333
+ opacity: 0.7;
334
+ }
335
+
336
+ .selector-grid {
337
+ display: grid;
338
+ grid-template-columns: max-content 1fr;
339
+ column-gap: 10px;
340
+ row-gap: 4px;
341
+ font-size: 11.5px;
342
+ }
343
+
344
+ .selector-grid code {
345
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
346
+ background: var(--md-sys-color-surface-container-high, #ede7f6);
347
+ padding: 1px 5px;
348
+ border-radius: 3px;
349
+ }
350
+ `
351
+ ];
352
+ __decorate([
353
+ property({ type: Object })
354
+ ], EventHandlersHelp.prototype, "handler", void 0);
355
+ customElements.define('event-handlers-help', EventHandlersHelp);
356
+ //# sourceMappingURL=event-handlers-help.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-handlers-help.js","sourceRoot":"","sources":["../../../../src/property-panel/event-handlers/event-handlers-help.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;;AAEH,OAAO,4BAA4B,CAAA;AAEnC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAE5C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAMjD,MAAM,YAAY,GAA8B;IAC9C,KAAK,EAAE;QACL,OAAO,EAAE,4BAA4B;QACrC,OAAO,EAAE,wBAAwB;KAClC;IACD,GAAG,EAAE;QACH,OAAO,EAAE,6CAA6C;KACvD;IACD,QAAQ,EAAE;QACR,OAAO,EAAE,2BAA2B;KACrC;IACD,UAAU,EAAE;QACV,OAAO,EAAE,6BAA6B;QACtC,OAAO,EAAE,4BAA4B;KACtC;IACD,KAAK,EAAE;QACL,OAAO,EAAE,oDAAoD;KAC9D;IACD,UAAU,EAAE;QACV,OAAO,EAAE,6BAA6B;QACtC,OAAO,EAAE,6BAA6B;KACvC;IACD,SAAS,EAAE;QACT,OAAO,EAAE,4BAA4B;KACtC;IACD,SAAS,EAAE;QACT,OAAO,EAAE,2CAA2C;KACrD;IACD,OAAO,EAAE;QACP,OAAO,EAAE,cAAc;KACxB;IACD,OAAO,EAAE;QACP,OAAO,EAAE,2CAA2C;KACrD;CACF,CAAA;AAED,MAAM,WAAW,GAA8B;IAC7C,EAAE,EAAE;QACF,OAAO,EAAE,wCAAwC;KAClD;IACD,MAAM,EAAE;QACN,OAAO,EAAE,mFAAmF;QAC5F,OAAO,EAAE,+BAA+B;KACzC;IACD,aAAa,EAAE;QACb,OAAO,EAAE,2CAA2C;QACpD,OAAO,EAAE,mCAAmC;KAC7C;IACD,eAAe,EAAE;QACf,OAAO,EAAE,oCAAoC;QAC7C,OAAO,EAAE,kCAAkC;KAC5C;IACD,gBAAgB,EAAE;QAChB,OAAO,EAAE,kCAAkC;QAC3C,OAAO,EAAE,gCAAgC;KAC1C;IACD,UAAU,EAAE;QACV,OAAO,EAAE,uDAAuD;QAChE,OAAO,EAAE,uCAAuC;KACjD;IACD,kBAAkB,EAAE;QAClB,OAAO,EAAE,iDAAiD;QAC1D,OAAO,EAAE,kCAAkC;KAC5C;IACD,WAAW,EAAE;QACX,OAAO,EAAE,gCAAgC;QACzC,OAAO,EAAE,iCAAiC;KAC3C;IACD,mBAAmB,EAAE;QACnB,OAAO,EAAE,sCAAsC;KAChD;IACD,aAAa,EAAE;QACb,OAAO,EAAE,iCAAiC;QAC1C,OAAO,EAAE,uCAAuC;KACjD;IACD,SAAS,EAAE;QACT,OAAO,EAAE,4BAA4B;QACrC,OAAO,EAAE,2DAA2D;KACrE;CACF,CAAA;AAED,MAAM,WAAW,GAAG;IAClB,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,mBAAmB,EAAE;IAChD,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,oCAAoC,EAAE;IAC7D,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,4BAA4B,EAAE;IACrD,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,+CAA+C,EAAE;IAC1E,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,iCAAiC,EAAE;CAC3D,CAAA;AAED,MAAM,QAAQ,GAAsC;IAClD;QACE,KAAK,EAAE,aAAa;QACpB,IAAI,EAAE,oDAAoD;KAC3D;IACD;QACE,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,kCAAkC;KACzC;IACD;QACE,KAAK,EAAE,gBAAgB;QACvB,IAAI,EAAE,0CAA0C;KACjD;IACD;QACE,KAAK,EAAE,uBAAuB;QAC9B,IAAI,EAAE;;;;EAIR;KACC;IACD;QACE,KAAK,EAAE,uBAAuB;QAC9B,IAAI,EAAE;;sBAEY;KACnB;CACF,CAAA;AAED,MAAM,OAAO,iBAAkB,SAAQ,UAAU;IAqI/C,MAAM;;QACJ,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;QAC1D,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAA;QAC5E,MAAM,OAAO,GAAG,WAAW,CAAC,MAAA,CAAC,CAAC,MAAM,mCAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAA;QAC9E,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAA;QAEtC,OAAO,IAAI,CAAA;;;4BAGa,CAAC,CAAC,OAAO;6BACR,QAAQ,CAAC,OAAO;UACnC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA,wBAAwB,QAAQ,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE;;;;;4BAK1D,CAAC,CAAC,MAAM,IAAI,QAAQ;6BACnB,OAAO,CAAC,OAAO;UAClC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA,wBAAwB,OAAO,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE;;;QAG5E,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,sBAAsB,EAAE;KACtE,CAAA;IACH,CAAC;IAEO,sBAAsB;QAC5B,OAAO,IAAI,CAAA;;;;;;;;;;;;;;;;;;;;;;;KAuBV,CAAA;IACH,CAAC;IAEO,iBAAiB;QACvB,OAAO,IAAI,CAAA;;;;YAIH,WAAW,CAAC,GAAG,CACf,CAAC,CAAC,EAAE,CAAC,IAAI,CAAA;0BACK,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,IAAI;aACnC,CACF;;;;;;;;;;YAUC,QAAQ,CAAC,GAAG,CACZ,CAAC,CAAC,EAAE,CAAC,IAAI,CAAA;;;;yBAII,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC;;;wBAGhC,CAAC,CAAC,KAAK;;;aAGlB,CACF;;;KAGN,CAAA;IACH,CAAC;IAEO,YAAY,CAAC,IAAY;QAC/B,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,gBAAgB,EAAE;YAChC,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,EAAE,IAAI,EAAE;SACjB,CAAC,CACH,CAAA;IACH,CAAC;;AArOM,wBAAM,GAAG;IACd,eAAe;IACf,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA6HF;CACF,CAAA;AAE2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;kDAA2B;AAsGxD,cAAc,CAAC,MAAM,CAAC,qBAAqB,EAAE,iBAAiB,CAAC,CAAA","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * EventHandlers help pane — popup 의 우측 docs 컬럼.\n *\n * 현재 선택된 handler 의 trigger / action / target / value (또는 script vars)\n * 의미를 *동적으로* 안내. script 모드에서는 자주 쓰는 코드 snippet 을\n * 삽입할 수 있는 버튼도 제공.\n *\n * 다국어 — 1차안은 한국어 hard-code (5 언어 동시 유지 부담 회피).\n * 추후 안정화 후 i18n 키로 분리.\n */\n\nimport '@material/web/icon/icon.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { property } from 'lit/decorators.js'\n\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport type { EventHandlerSpec } from './event-handlers-mapper.js'\n\ntype DescEntry = { summary: string; example?: string }\n\nconst TRIGGER_DESC: Record<string, DescEntry> = {\n click: {\n summary: '마우스 클릭 (터치 환경에선 tap 과 동일).',\n example: '가장 일반적인 버튼/아이콘 액션에 사용.'\n },\n tap: {\n summary: 'click 의 alias (legacy 호환). 신규 등록은 click 권장.'\n },\n dblclick: {\n summary: '더블 클릭. 빠른 연속 2회 클릭으로만 발화.'\n },\n mouseenter: {\n summary: '포인터가 컴포넌트 영역에 *진입* 한 순간 1회.',\n example: 'hover 강조 / tooltip 표시에 사용.'\n },\n hover: {\n summary: 'mouseenter 의 alias (legacy). 신규 등록은 mouseenter 권장.'\n },\n mouseleave: {\n summary: '포인터가 컴포넌트 영역을 *이탈* 한 순간 1회.',\n example: 'mouseenter 와 짝지어 강조 해제에 사용.'\n },\n longpress: {\n summary: '약 500ms 이상 누름. 클릭과 별개로 발화.'\n },\n mousedown: {\n summary: '버튼 누름 시작 시점. mouseup 과 짝지어 pressed 패턴 가능.'\n },\n mouseup: {\n summary: '버튼 누름 해제 시점.'\n },\n pressed: {\n summary: 'mousedown ↔ mouseup 페어 자동 매핑 (legacy 호환).'\n }\n}\n\nconst ACTION_DESC: Record<string, DescEntry> = {\n '': {\n summary: '액션이 선택되지 않음. 핸들러는 등록되지만 아무 동작도 하지 않는다.'\n },\n script: {\n summary: '자유 JavaScript 코드. component / scene / event / targets / value 를 변수로 받아 임의 동작을 수행.',\n example: 'await 사용 가능 — 비동기 동작도 그대로 작성.'\n },\n 'data-toggle': {\n summary: 'target 컴포넌트의 data 값을 truthy ↔ falsy 로 토글.',\n example: 'value 필드는 무시. target 미설정 시 자기 자신.'\n },\n 'data-tristate': {\n summary: 'target 의 data 값을 0 → 1 → 2 → 0 순환.',\n example: '세 상태 토글 (off / on / mixed 같은) 용.'\n },\n 'data-spreading': {\n summary: 'target 들에 value 를 분배 (배열 또는 매핑).',\n example: 'value 가 배열이면 target N개에 각각 매칭.'\n },\n 'data-set': {\n summary: 'target 의 data 를 value 로 설정. value 는 정적 값 또는 accessor.',\n example: '예) value=1 / value=accessor.someField'\n },\n 'partial-data-set': {\n summary: 'target.data 가 객체일 때, value 의 키들만 partial merge.',\n example: 'value 는 객체 또는 객체를 가리키는 accessor.'\n },\n 'value-set': {\n summary: 'target 의 value 속성을 value 로 설정.',\n example: 'data 가 아니라 value 속성을 다루는 컴포넌트용.'\n },\n 'partial-value-set': {\n summary: 'target.value 가 객체일 때, partial merge.'\n },\n 'info-window': {\n summary: 'target 의 info-window (팝업) 를 띄움.',\n example: 'target 컴포넌트의 popup 정보가 미리 정의되어 있어야 함.'\n },\n emphasize: {\n summary: 'target 을 시각적으로 강조 (애니메이션).',\n example: 'mouseenter / mouseleave 와 짝지어 emphasize/restore 패턴 자주 사용.'\n }\n}\n\nconst SCRIPT_VARS = [\n { name: 'component', desc: '핸들러가 등록된 현재 컴포넌트.' },\n { name: 'scene', desc: '루트 scene. scene.root 로 다른 컴포넌트 검색.' },\n { name: 'event', desc: '원본 DOM 이벤트 (또는 합성 이벤트) 객체.' },\n { name: 'targets', desc: 'target selector 로 찾은 컴포넌트 배열. target 비면 빈 배열.' },\n { name: 'value', desc: '핸들러의 value 필드 (정적 또는 accessor).' }\n]\n\nconst SNIPPETS: { label: string; code: string }[] = [\n {\n label: 'console.log',\n code: `console.log('event handler', { component, event })`\n },\n {\n label: 'data 토글',\n code: `component.data = !component.data`\n },\n {\n label: 'targets 에 값 세팅',\n code: `targets.forEach(t => { t.data = value })`\n },\n {\n label: 'findById 로 특정 컴포넌트 변경',\n code: `// '#someId' 는 대상 컴포넌트의 id 로 바꿔 쓴다.\nconst target = scene.root.findAll('#someId', component)[0]\nif (target) {\n target.data = component.data\n}`\n },\n {\n label: 'async fetch + data 반영',\n code: `const res = await fetch('/api/something')\nconst json = await res.json()\ncomponent.data = json`\n }\n]\n\nexport class EventHandlersHelp extends LitElement {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n background: var(--md-sys-color-surface-container-lowest, #fffbfe);\n border-left: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));\n padding: 16px 18px;\n overflow-y: auto;\n font-family: 'Roboto', Arial, sans-serif;\n font-size: 12.5px;\n color: var(--md-sys-color-on-surface, #1c1b1f);\n line-height: 1.5;\n min-width: 0;\n }\n\n h4 {\n margin: 0 0 6px 0;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.5px;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, #49454f);\n }\n\n section {\n margin-bottom: 18px;\n }\n\n .name {\n display: inline-block;\n padding: 2px 6px;\n margin-bottom: 6px;\n background: var(--md-sys-color-secondary-container, #e8def8);\n color: var(--md-sys-color-on-secondary-container, #1d192b);\n border-radius: 4px;\n font-weight: 600;\n font-size: 11.5px;\n }\n\n .summary {\n margin: 0 0 6px 0;\n }\n\n .example {\n margin: 4px 0 0 0;\n padding: 6px 8px;\n background: rgba(0, 0, 0, 0.04);\n border-left: 2px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.2));\n font-size: 11.5px;\n color: var(--md-sys-color-on-surface-variant, #49454f);\n }\n\n ul.vars {\n list-style: none;\n margin: 0;\n padding: 0;\n }\n\n ul.vars li {\n padding: 4px 0;\n border-bottom: 1px dashed var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.08));\n }\n\n ul.vars li:last-child {\n border-bottom: none;\n }\n\n ul.vars code {\n display: inline-block;\n padding: 1px 5px;\n margin-right: 6px;\n background: var(--md-sys-color-surface-container-high, #ede7f6);\n border-radius: 3px;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n color: var(--md-sys-color-on-surface, #1c1b1f);\n }\n\n .snippet-list {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n .snippet-btn {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n padding: 6px 10px;\n border: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.18));\n border-radius: 4px;\n background: var(--md-sys-color-surface, #fff);\n color: var(--md-sys-color-on-surface, #1c1b1f);\n font-size: 12px;\n cursor: pointer;\n text-align: left;\n }\n\n .snippet-btn:hover {\n background: var(--md-sys-color-surface-container-low, #f7f2fa);\n }\n\n .snippet-btn md-icon {\n --md-icon-size: 16px;\n opacity: 0.7;\n }\n\n .selector-grid {\n display: grid;\n grid-template-columns: max-content 1fr;\n column-gap: 10px;\n row-gap: 4px;\n font-size: 11.5px;\n }\n\n .selector-grid code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n background: var(--md-sys-color-surface-container-high, #ede7f6);\n padding: 1px 5px;\n border-radius: 3px;\n }\n `\n ]\n\n @property({ type: Object }) handler?: EventHandlerSpec\n\n render() {\n const h = this.handler || { trigger: 'click', action: '' }\n const trigDesc = TRIGGER_DESC[h.trigger] || { summary: '(unknown trigger)' }\n const actDesc = ACTION_DESC[h.action ?? ''] || { summary: '(unknown action)' }\n const isScript = h.action === 'script'\n\n return html`\n <section>\n <h4>Trigger</h4>\n <div class=\"name\">${h.trigger}</div>\n <p class=\"summary\">${trigDesc.summary}</p>\n ${trigDesc.example ? html`<div class=\"example\">${trigDesc.example}</div>` : ''}\n </section>\n\n <section>\n <h4>Action</h4>\n <div class=\"name\">${h.action || '(none)'}</div>\n <p class=\"summary\">${actDesc.summary}</p>\n ${actDesc.example ? html`<div class=\"example\">${actDesc.example}</div>` : ''}\n </section>\n\n ${isScript ? this._renderScriptHelp() : this._renderDeclarativeHelp()}\n `\n }\n\n private _renderDeclarativeHelp() {\n return html`\n <section>\n <h4>Target selector</h4>\n <div class=\"selector-grid\">\n <code>#id</code><span>id 일치</span>\n <code>.class</code><span>class 일치 (공백 구분 다중)</span>\n <code>&lt;타입&gt;</code><span>type 일치 (예: rect, button)</span>\n <code>(self)</code><span>현재 컴포넌트 자기 자신</span>\n <code>(all)</code><span>모든 컴포넌트</span>\n <code>(root)</code><span>루트 컴포넌트</span>\n <code>(parent)</code><span>부모</span>\n <code>(children)</code><span>직접 자식들</span>\n <code>(siblings)</code><span>형제들 (자기 제외)</span>\n </div>\n <div class=\"example\" style=\"margin-top:6px\">\n 비워두면 매칭 없음 — 자기 자신을 대상으로 하려면 <code style=\"background:transparent;padding:0\">(self)</code>.\n </div>\n </section>\n\n <section>\n <h4>Value</h4>\n <p class=\"summary\">정적 값 또는 accessor 표현식. accessor 인 경우 component.access(value) 로 평가되어 컴포넌트 컨텍스트의 데이터를 참조한다.</p>\n </section>\n `\n }\n\n private _renderScriptHelp() {\n return html`\n <section>\n <h4>Script 변수</h4>\n <ul class=\"vars\">\n ${SCRIPT_VARS.map(\n v => html`\n <li><code>${v.name}</code>${v.desc}</li>\n `\n )}\n </ul>\n <div class=\"example\" style=\"margin-top:8px\">\n <code style=\"background:transparent;padding:0\">await</code> 키워드 사용 시 스크립트는 비동기로 실행. 결과 wait 후 다음 핸들러가 영향 받지는 않는다 (각 핸들러는 독립 실행).\n </div>\n </section>\n\n <section>\n <h4>Snippet 삽입</h4>\n <div class=\"snippet-list\">\n ${SNIPPETS.map(\n s => html`\n <button\n type=\"button\"\n class=\"snippet-btn\"\n @click=${() => this._emitSnippet(s.code)}\n title=\"현재 스크립트 영역에 삽입 (덮어쓰지 않고 끝에 추가)\"\n >\n <span>${s.label}</span>\n <md-icon>add</md-icon>\n </button>\n `\n )}\n </div>\n </section>\n `\n }\n\n private _emitSnippet(code: string) {\n this.dispatchEvent(\n new CustomEvent('insert-snippet', {\n bubbles: true,\n composed: true,\n detail: { code }\n })\n )\n }\n}\n\ncustomElements.define('event-handlers-help', EventHandlersHelp)\n"]}
@@ -75,11 +75,19 @@ export class EventHandlersMapper extends LitElement {
75
75
  @change=${(e) => this._update('target', e.target.value)}
76
76
  />
77
77
  </div>
78
- <div>
79
- <label style="display:block;margin-bottom:4px">
78
+ <div class="row">
79
+ <label><ox-i18n msgid="label.value">Value</ox-i18n></label>
80
+ <input type="text"
81
+ placeholder="optional · script 안 value 변수로 전달"
82
+ .value=${h.value !== undefined ? String(h.value) : ''}
83
+ @change=${(e) => this._update('value', e.target.value)}
84
+ />
85
+ </div>
86
+ <div script-field>
87
+ <div class="script-header">
80
88
  <ox-i18n msgid="label.script">Script</ox-i18n>
81
- &nbsp;<small style="opacity:0.7">vars: component, scene, event, targets, value · await 지원</small>
82
- </label>
89
+ <small>vars: component, scene, event, targets, value · await 지원</small>
90
+ </div>
83
91
  <ox-input-code
84
92
  mode="javascript"
85
93
  .value=${h.code || ''}
@@ -129,11 +137,52 @@ EventHandlersMapper.styles = [
129
137
  display: flex;
130
138
  flex-direction: column;
131
139
  gap: 6px;
132
- padding: 7px;
133
- border: 1px solid rgba(0, 0, 0, 0.2);
134
- border-width: 0 1px 1px 1px;
140
+ padding: 10px 10px 10px 10px;
141
+ /* 카드 외곽 — tabs (위) + toolbar (중간) + mapper (아래) 가 하나의 카드.
142
+ * 위쪽 1px toolbar 와의 internal divider. radius 6px 으로 통일. */
143
+ border: 1px solid rgba(0, 0, 0, 0.18);
144
+ border-radius: 0 0 6px 6px;
145
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
135
146
  line-height: 2;
136
147
  background: var(--md-sys-color-surface, #fff);
148
+ min-width: 0;
149
+ width: 100%;
150
+ box-sizing: border-box;
151
+ overflow: hidden;
152
+ flex: 1;
153
+ min-height: 0;
154
+ }
155
+
156
+ [script-field] {
157
+ display: flex;
158
+ flex-direction: column;
159
+ min-height: 0;
160
+ flex: 1;
161
+ margin-top: 6px;
162
+ padding-top: 10px;
163
+ /* trigger/action/target/value 입력 묶음과 code editor 사이의 구분선.
164
+ * dashed 대신 solid + 옅은 opacity 로 modern. */
165
+ border-top: 1px solid rgba(0, 0, 0, 0.08);
166
+ }
167
+
168
+ [script-field] > .script-header {
169
+ display: flex;
170
+ align-items: baseline;
171
+ gap: 8px;
172
+ margin-bottom: 6px;
173
+ font-size: 10.5px;
174
+ font-weight: 700;
175
+ text-transform: uppercase;
176
+ letter-spacing: 0.6px;
177
+ color: var(--md-sys-color-on-surface-variant, #49454f);
178
+ }
179
+
180
+ [script-field] > .script-header small {
181
+ font-weight: 400;
182
+ text-transform: none;
183
+ letter-spacing: 0;
184
+ opacity: 0.65;
185
+ font-size: 10.5px;
137
186
  }
138
187
 
139
188
  .row {
@@ -141,11 +190,13 @@ EventHandlersMapper.styles = [
141
190
  grid-template-columns: 80px 1fr;
142
191
  gap: 6px;
143
192
  align-items: center;
193
+ min-width: 0;
144
194
  }
145
195
 
146
196
  .row > select,
147
197
  .row > input[type='text'] {
148
198
  width: 100%;
199
+ min-width: 0;
149
200
  box-sizing: border-box;
150
201
  padding: 3px 6px;
151
202
  font-size: 13px;
@@ -155,9 +206,20 @@ EventHandlersMapper.styles = [
155
206
  }
156
207
 
157
208
  ox-input-code {
209
+ /* display 는 *지정하지 않는다* — outside selector 가 ox-input-code 의
210
+ * :host { display: flex; flex-direction: column } 를 덮어쓰면, 그 안의
211
+ * .cm-editor { flex: 1 } 가 작동을 멈춰 텍스트 라인 1개 크기로 축소된다. */
212
+ width: 100%;
213
+ max-width: 100%;
214
+ min-width: 0;
215
+ box-sizing: border-box;
216
+ overflow: hidden;
158
217
  min-height: 180px;
159
218
  border: 1px solid rgba(0, 0, 0, 0.15);
160
219
  border-radius: 4px;
220
+ /* popup 모드에서 script-field 의 남은 세로 공간을 모두 차지 — 아래 빈공간 X.
221
+ * inline 모드에서는 script-field 가 grow 하지 않으므로 min-height 180px 만 사용. */
222
+ flex: 1;
161
223
  }
162
224
 
163
225
  label {
@@ -1 +1 @@
1
- {"version":3,"file":"event-handlers-mapper.js","sourceRoot":"","sources":["../../../../src/property-panel/event-handlers/event-handlers-mapper.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * Single EventHandler editor — trigger / action / (code 또는 target+value).\n *\n * 'script' 도 action 의 한 값. action='script' 시 code 필드를, 다른 declarative\n * action 시 target+value 필드를 노출. action 미선택 (= '') 시 no-op.\n */\n\nimport '@operato/input/ox-input-code.js'\nimport '@operato/i18n/ox-i18n.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { property } from 'lit/decorators.js'\n\nimport type { Scene } from '@hatiolab/things-scene'\n\n/** EventHandler model — things-scene 의 EventHandler interface 와 호환. */\nexport type EventHandlerSpec = {\n trigger: string // click | dblclick | mouseenter | mouseleave | longpress | mousedown | mouseup\n action?: string // '' | 'script' | 'data-toggle' | 'data-set' | 'emphasize' | ...\n code?: string // action='script' 시 JS 코드\n target?: string // selector\n value?: any\n options?: Record<string, any>\n disabled?: boolean\n}\n\nconst TRIGGERS = [\n 'click', 'dblclick',\n 'mouseenter', 'mouseleave',\n 'longpress',\n 'mousedown', 'mouseup'\n]\n\n/** 'script' 는 action 의 한 값. */\nconst ACTIONS = [\n '',\n 'script',\n 'data-toggle',\n 'data-tristate',\n 'data-spreading',\n 'data-set',\n 'partial-data-set',\n 'value-set',\n 'partial-value-set',\n 'info-window',\n 'emphasize'\n]\n\nexport class EventHandlersMapper extends LitElement {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 7px;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-width: 0 1px 1px 1px;\n line-height: 2;\n background: var(--md-sys-color-surface, #fff);\n }\n\n .row {\n display: grid;\n grid-template-columns: 80px 1fr;\n gap: 6px;\n align-items: center;\n }\n\n .row > select,\n .row > input[type='text'] {\n width: 100%;\n box-sizing: border-box;\n padding: 3px 6px;\n font-size: 13px;\n border-radius: 4px;\n border: 1px solid rgba(0, 0, 0, 0.15);\n background: #fff;\n }\n\n ox-input-code {\n min-height: 180px;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 4px;\n }\n\n label {\n font-size: 12px;\n color: var(--md-sys-color-on-surface-variant, #49454f);\n }\n `\n ]\n\n @property({ type: Object }) handler: EventHandlerSpec = { trigger: 'click', action: '' }\n @property({ type: Object }) scene?: Scene\n\n render() {\n const h = this.handler || { trigger: 'click', action: '' }\n return html`\n <div class=\"row\">\n <label><ox-i18n msgid=\"label.trigger\">Trigger</ox-i18n></label>\n <select\n .value=${h.trigger || 'click'}\n @change=${(e: Event) => this._update('trigger', (e.target as HTMLSelectElement).value)}\n >\n ${TRIGGERS.map(t => html`<option value=${t} ?selected=${h.trigger === t}>${t}</option>`)}\n </select>\n </div>\n\n <div class=\"row\">\n <label><ox-i18n msgid=\"label.action\">Action</ox-i18n></label>\n <select\n .value=${h.action ?? ''}\n @change=${(e: Event) => this._update('action', (e.target as HTMLSelectElement).value)}\n >\n ${ACTIONS.map(a => html`<option value=${a} ?selected=${(h.action ?? '') === a}>${a || '(none)'}</option>`)}\n </select>\n </div>\n\n ${h.action === 'script'\n ? this._renderScriptFields(h)\n : (h.action ? this._renderActionFields(h) : null)}\n `\n }\n\n private _renderScriptFields(h: EventHandlerSpec) {\n return html`\n <div class=\"row\">\n <label><ox-i18n msgid=\"label.target\">Target</ox-i18n></label>\n <input type=\"text\"\n placeholder=\"optional selector (#id, .class, ...)\"\n .value=${h.target || ''}\n @change=${(e: Event) => this._update('target', (e.target as HTMLInputElement).value)}\n />\n </div>\n <div>\n <label style=\"display:block;margin-bottom:4px\">\n <ox-i18n msgid=\"label.script\">Script</ox-i18n>\n &nbsp;<small style=\"opacity:0.7\">vars: component, scene, event, targets, value · await 지원</small>\n </label>\n <ox-input-code\n mode=\"javascript\"\n .value=${h.code || ''}\n @change=${(e: any) => this._update('code', e.detail?.value ?? e.target?.value ?? '')}\n ></ox-input-code>\n </div>\n `\n }\n\n private _renderActionFields(h: EventHandlerSpec) {\n return html`\n <div class=\"row\">\n <label><ox-i18n msgid=\"label.target\">Target</ox-i18n></label>\n <input type=\"text\"\n placeholder=\"selector (#id, .class, ...)\"\n .value=${h.target || ''}\n @change=${(e: Event) => this._update('target', (e.target as HTMLInputElement).value)}\n />\n </div>\n <div class=\"row\">\n <label><ox-i18n msgid=\"label.value\">Value</ox-i18n></label>\n <input type=\"text\"\n placeholder=\"value or accessor\"\n .value=${h.value !== undefined ? String(h.value) : ''}\n @change=${(e: Event) => this._update('value', (e.target as HTMLInputElement).value)}\n />\n </div>\n `\n }\n\n private _update(key: keyof EventHandlerSpec, value: any) {\n const next: EventHandlerSpec = { ...(this.handler || { trigger: 'click', action: '' }), [key]: value }\n // action 전환 시 비-script 진입은 code 정리. script 진입 시 target/value 는\n // 의미가 호환 (target=findAll selector, value=script 입력) 이라 유지.\n if (key === 'action' && value !== 'script') {\n delete next.code\n }\n this.handler = next\n this.dispatchEvent(\n new CustomEvent('value-change', {\n bubbles: true,\n composed: true,\n detail: { handler: next, changed: { [key]: value } }\n })\n )\n }\n}\n\ncustomElements.define('event-handlers-mapper', EventHandlersMapper)\n"]}
1
+ {"version":3,"file":"event-handlers-mapper.js","sourceRoot":"","sources":["../../../../src/property-panel/event-handlers/event-handlers-mapper.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * Single EventHandler editor — trigger / action / (code 또는 target+value).\n *\n * 'script' 도 action 의 한 값. action='script' 시 code 필드를, 다른 declarative\n * action 시 target+value 필드를 노출. action 미선택 (= '') 시 no-op.\n */\n\nimport '@operato/input/ox-input-code.js'\nimport '@operato/i18n/ox-i18n.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { property } from 'lit/decorators.js'\n\nimport type { Scene } from '@hatiolab/things-scene'\n\n/** EventHandler model — things-scene 의 EventHandler interface 와 호환. */\nexport type EventHandlerSpec = {\n trigger: string // click | dblclick | mouseenter | mouseleave | longpress | mousedown | mouseup\n action?: string // '' | 'script' | 'data-toggle' | 'data-set' | 'emphasize' | ...\n code?: string // action='script' 시 JS 코드\n target?: string // selector\n value?: any\n options?: Record<string, any>\n disabled?: boolean\n}\n\nconst TRIGGERS = [\n 'click', 'dblclick',\n 'mouseenter', 'mouseleave',\n 'longpress',\n 'mousedown', 'mouseup'\n]\n\n/** 'script' 는 action 의 한 값. */\nconst ACTIONS = [\n '',\n 'script',\n 'data-toggle',\n 'data-tristate',\n 'data-spreading',\n 'data-set',\n 'partial-data-set',\n 'value-set',\n 'partial-value-set',\n 'info-window',\n 'emphasize'\n]\n\nexport class EventHandlersMapper extends LitElement {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 10px 10px 10px;\n /* 카드 외곽 — tabs (위) + toolbar (중간) + mapper (아래) 가 하나의 카드.\n * 위쪽 1px 는 toolbar 와의 internal divider. radius 6px 으로 통일. */\n border: 1px solid rgba(0, 0, 0, 0.18);\n border-radius: 0 0 6px 6px;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);\n line-height: 2;\n background: var(--md-sys-color-surface, #fff);\n min-width: 0;\n width: 100%;\n box-sizing: border-box;\n overflow: hidden;\n flex: 1;\n min-height: 0;\n }\n\n [script-field] {\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1;\n margin-top: 6px;\n padding-top: 10px;\n /* trigger/action/target/value 입력 묶음과 code editor 사이의 구분선.\n * dashed 대신 solid + 옅은 opacity 로 modern. */\n border-top: 1px solid rgba(0, 0, 0, 0.08);\n }\n\n [script-field] > .script-header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n margin-bottom: 6px;\n font-size: 10.5px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.6px;\n color: var(--md-sys-color-on-surface-variant, #49454f);\n }\n\n [script-field] > .script-header small {\n font-weight: 400;\n text-transform: none;\n letter-spacing: 0;\n opacity: 0.65;\n font-size: 10.5px;\n }\n\n .row {\n display: grid;\n grid-template-columns: 80px 1fr;\n gap: 6px;\n align-items: center;\n min-width: 0;\n }\n\n .row > select,\n .row > input[type='text'] {\n width: 100%;\n min-width: 0;\n box-sizing: border-box;\n padding: 3px 6px;\n font-size: 13px;\n border-radius: 4px;\n border: 1px solid rgba(0, 0, 0, 0.15);\n background: #fff;\n }\n\n ox-input-code {\n /* display 는 *지정하지 않는다* — outside selector 가 ox-input-code 의\n * :host { display: flex; flex-direction: column } 를 덮어쓰면, 그 안의\n * .cm-editor { flex: 1 } 가 작동을 멈춰 텍스트 라인 1개 크기로 축소된다. */\n width: 100%;\n max-width: 100%;\n min-width: 0;\n box-sizing: border-box;\n overflow: hidden;\n min-height: 180px;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 4px;\n /* popup 모드에서 script-field 의 남은 세로 공간을 모두 차지 — 아래 빈공간 X.\n * inline 모드에서는 script-field 가 grow 하지 않으므로 min-height 180px 만 사용. */\n flex: 1;\n }\n\n label {\n font-size: 12px;\n color: var(--md-sys-color-on-surface-variant, #49454f);\n }\n `\n ]\n\n @property({ type: Object }) handler: EventHandlerSpec = { trigger: 'click', action: '' }\n @property({ type: Object }) scene?: Scene\n\n render() {\n const h = this.handler || { trigger: 'click', action: '' }\n return html`\n <div class=\"row\">\n <label><ox-i18n msgid=\"label.trigger\">Trigger</ox-i18n></label>\n <select\n .value=${h.trigger || 'click'}\n @change=${(e: Event) => this._update('trigger', (e.target as HTMLSelectElement).value)}\n >\n ${TRIGGERS.map(t => html`<option value=${t} ?selected=${h.trigger === t}>${t}</option>`)}\n </select>\n </div>\n\n <div class=\"row\">\n <label><ox-i18n msgid=\"label.action\">Action</ox-i18n></label>\n <select\n .value=${h.action ?? ''}\n @change=${(e: Event) => this._update('action', (e.target as HTMLSelectElement).value)}\n >\n ${ACTIONS.map(a => html`<option value=${a} ?selected=${(h.action ?? '') === a}>${a || '(none)'}</option>`)}\n </select>\n </div>\n\n ${h.action === 'script'\n ? this._renderScriptFields(h)\n : (h.action ? this._renderActionFields(h) : null)}\n `\n }\n\n private _renderScriptFields(h: EventHandlerSpec) {\n return html`\n <div class=\"row\">\n <label><ox-i18n msgid=\"label.target\">Target</ox-i18n></label>\n <input type=\"text\"\n placeholder=\"optional selector (#id, .class, ...)\"\n .value=${h.target || ''}\n @change=${(e: Event) => this._update('target', (e.target as HTMLInputElement).value)}\n />\n </div>\n <div class=\"row\">\n <label><ox-i18n msgid=\"label.value\">Value</ox-i18n></label>\n <input type=\"text\"\n placeholder=\"optional · script 안 value 변수로 전달\"\n .value=${h.value !== undefined ? String(h.value) : ''}\n @change=${(e: Event) => this._update('value', (e.target as HTMLInputElement).value)}\n />\n </div>\n <div script-field>\n <div class=\"script-header\">\n <ox-i18n msgid=\"label.script\">Script</ox-i18n>\n <small>vars: component, scene, event, targets, value · await 지원</small>\n </div>\n <ox-input-code\n mode=\"javascript\"\n .value=${h.code || ''}\n @change=${(e: any) => this._update('code', e.detail?.value ?? e.target?.value ?? '')}\n ></ox-input-code>\n </div>\n `\n }\n\n private _renderActionFields(h: EventHandlerSpec) {\n return html`\n <div class=\"row\">\n <label><ox-i18n msgid=\"label.target\">Target</ox-i18n></label>\n <input type=\"text\"\n placeholder=\"selector (#id, .class, ...)\"\n .value=${h.target || ''}\n @change=${(e: Event) => this._update('target', (e.target as HTMLInputElement).value)}\n />\n </div>\n <div class=\"row\">\n <label><ox-i18n msgid=\"label.value\">Value</ox-i18n></label>\n <input type=\"text\"\n placeholder=\"value or accessor\"\n .value=${h.value !== undefined ? String(h.value) : ''}\n @change=${(e: Event) => this._update('value', (e.target as HTMLInputElement).value)}\n />\n </div>\n `\n }\n\n private _update(key: keyof EventHandlerSpec, value: any) {\n const next: EventHandlerSpec = { ...(this.handler || { trigger: 'click', action: '' }), [key]: value }\n // action 전환 시 비-script 진입은 code 정리. script 진입 시 target/value 는\n // 의미가 호환 (target=findAll selector, value=script 입력) 이라 유지.\n if (key === 'action' && value !== 'script') {\n delete next.code\n }\n this.handler = next\n this.dispatchEvent(\n new CustomEvent('value-change', {\n bubbles: true,\n composed: true,\n detail: { handler: next, changed: { [key]: value } }\n })\n )\n }\n}\n\ncustomElements.define('event-handlers-mapper', EventHandlersMapper)\n"]}