@operato/property-panel 10.20.0 → 10.23.0

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/src/ox-property-panel.d.ts +1 -0
  3. package/dist/src/ox-property-panel.js +15 -0
  4. package/dist/src/ox-property-panel.js.map +1 -1
  5. package/dist/src/property-panel/data-binding/data-binding-mapper.js +20 -7
  6. package/dist/src/property-panel/data-binding/data-binding-mapper.js.map +1 -1
  7. package/dist/src/property-panel/data-binding/data-binding.d.ts +4 -0
  8. package/dist/src/property-panel/data-binding/data-binding.js +167 -56
  9. package/dist/src/property-panel/data-binding/data-binding.js.map +1 -1
  10. package/dist/src/property-panel/event-handlers/event-handlers-mapper.js +37 -16
  11. package/dist/src/property-panel/event-handlers/event-handlers-mapper.js.map +1 -1
  12. package/dist/src/property-panel/event-handlers/event-handlers-popup.d.ts +1 -0
  13. package/dist/src/property-panel/event-handlers/event-handlers-popup.js +48 -0
  14. package/dist/src/property-panel/event-handlers/event-handlers-popup.js.map +1 -1
  15. package/dist/src/property-panel/event-handlers/event-handlers.d.ts +3 -0
  16. package/dist/src/property-panel/event-handlers/event-handlers.js +173 -86
  17. package/dist/src/property-panel/event-handlers/event-handlers.js.map +1 -1
  18. package/dist/src/property-panel/shapes/shapes.d.ts +2 -0
  19. package/dist/src/property-panel/shapes/shapes.js +78 -29
  20. package/dist/src/property-panel/shapes/shapes.js.map +1 -1
  21. package/dist/src/property-panel/specifics/specifics.js +62 -6
  22. package/dist/src/property-panel/specifics/specifics.js.map +1 -1
  23. package/dist/src/property-panel/styles/styles.js +61 -31
  24. package/dist/src/property-panel/styles/styles.js.map +1 -1
  25. package/dist/src/property-panel/threed/property-material3d.js +10 -9
  26. package/dist/src/property-panel/threed/property-material3d.js.map +1 -1
  27. package/dist/src/property-panel/threed/property-scene3d.js +11 -9
  28. package/dist/src/property-panel/threed/property-scene3d.js.map +1 -1
  29. package/dist/src/property-panel/threed/threed.js +41 -18
  30. package/dist/src/property-panel/threed/threed.js.map +1 -1
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +6 -6
@@ -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: 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 /* #fff 와 rgba(0,0,0,.15) 는 다크에서 흰 칸 + 안 보이는 테두리가 된다.\n 프레임워크의 --input-field-* 가족을 쓰고, 없을 때도 자립하도록 md 토큰을 기본값으로 둔다. */\n border: var(--input-field-border, 1px solid var(--md-sys-color-outline));\n background: var(--input-field-background, var(--md-sys-color-surface-container-highest));\n color: var(--input-field-color, var(--md-sys-color-on-surface));\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"]}
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: 0;\n /* 카드 외곽 — tabs (위) + toolbar (중간) + mapper (아래) 가 하나의 카드.\n * 위쪽 1px 는 toolbar 와의 internal divider. radius 6px 으로 통일. */\n border: 0;\n border-radius: 0;\n box-shadow: none;\n line-height: normal;\n background: transparent;\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 var(--md-sys-color-outline-variant);\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 /* The same label/editor split as the panel grid (9 of 24 columns for the label). */\n .row {\n display: grid;\n grid-template-columns: 9fr 15fr;\n gap: 6px 10px;\n align-items: center;\n min-width: 0;\n }\n\n .row > label {\n justify-self: end;\n text-align: right;\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface-variant);\n text-transform: capitalize;\n }\n\n .row > select,\n .row > input[type='text'] {\n width: 100%;\n min-width: 0;\n box-sizing: border-box;\n min-height: 24px;\n padding: 2px 8px;\n font-size: 0.76rem;\n border-radius: 6px;\n /* #fff 와 rgba(0,0,0,.15) 는 다크에서 흰 칸 + 안 보이는 테두리가 된다.\n 프레임워크의 --input-field-* 가족을 쓰고, 없을 때도 자립하도록 md 토큰을 기본값으로 둔다. */\n border: 1px solid transparent;\n background: var(--property-input-background, var(--md-sys-color-surface-container-high));\n color: var(--md-sys-color-on-surface);\n outline: none;\n }\n\n .row > select:hover,\n .row > input[type='text']:hover {\n border-color: var(--md-sys-color-outline-variant);\n }\n\n .row > select:focus,\n .row > input[type='text']:focus {\n border-color: var(--md-sys-color-primary);\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 var(--md-sys-color-outline-variant);\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"]}
@@ -37,6 +37,7 @@ export declare class EventHandlersPopup extends EventHandlersPopup_base {
37
37
  * 않음). action 이 script 가 아니면 자동으로 script 로 전환 + code 초기화.
38
38
  */
39
39
  private _onInsertSnippet;
40
+ private _done;
40
41
  private _dispatch;
41
42
  }
42
43
  export {};
@@ -14,6 +14,7 @@ import { css, html, LitElement } from 'lit';
14
14
  import { customElement, property, state } from 'lit/decorators.js';
15
15
  import { ScopedElementsMixin } from '@open-wc/scoped-elements';
16
16
  import { ScrollbarStyles } from '@operato/styles';
17
+ import { i18next } from '@operato/i18n';
17
18
  import { EventHandlersMapper } from './event-handlers-mapper.js';
18
19
  import { EventHandlersHelp } from './event-handlers-help.js';
19
20
  let EventHandlersPopup = class EventHandlersPopup extends ScopedElementsMixin(LitElement) {
@@ -41,6 +42,14 @@ let EventHandlersPopup = class EventHandlersPopup extends ScopedElementsMixin(Li
41
42
  ${handlers.length === 0
42
43
  ? html `<div empty><ox-i18n msgid="label.no-handlers">No handlers — add one</ox-i18n></div>`
43
44
  : html `<ul>${handlers.map((h, i) => this._renderListItem(h, i, handlers.length))}</ul>`}
45
+ <footer>
46
+ <small>
47
+ ${i18next.t('text.handlers-apply-live', {
48
+ defaultValue: 'Changes apply to the shape as you edit. Save the board to keep them.'
49
+ })}
50
+ </small>
51
+ <button @click=${() => this._done()}>${i18next.t('button.done', { defaultValue: 'Done' })}</button>
52
+ </footer>
44
53
  </div>
45
54
 
46
55
  <div editor>
@@ -188,6 +197,10 @@ let EventHandlersPopup = class EventHandlersPopup extends ScopedElementsMixin(Li
188
197
  this.handlers = next;
189
198
  this._dispatch();
190
199
  }
200
+ /* Nothing to commit - the last edit is already on the shape - so this only dismisses the popup. */
201
+ _done() {
202
+ this.dispatchEvent(new CustomEvent('close-overlay', { bubbles: true, composed: true }));
203
+ }
191
204
  _dispatch() {
192
205
  // action 이 (none) — 즉 빈 문자열 — 인 핸들러는 외부 모델에 포함하지 않는다.
193
206
  // UI 안에서는 placeholder 로 남겨두고, 사용자가 action 을 선택하면 그제서야
@@ -317,6 +330,41 @@ EventHandlersPopup.styles = [
317
330
  color: var(--md-sys-color-error, #b3261e);
318
331
  }
319
332
 
333
+ /* There is no save step: every edit already reached the shape. The footer says so, and
334
+ * offers the one thing left to do. */
335
+ [handler-list] footer {
336
+ margin-top: auto;
337
+ padding: 10px 12px 12px;
338
+ border-top: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));
339
+ display: flex;
340
+ flex-direction: column;
341
+ gap: 8px;
342
+ }
343
+
344
+ [handler-list] footer small {
345
+ font-size: 11px;
346
+ line-height: 1.35;
347
+ color: var(--md-sys-color-on-surface-variant, #49454f);
348
+ }
349
+
350
+ [handler-list] footer button {
351
+ align-self: flex-end;
352
+ height: 28px;
353
+ padding: 0 16px;
354
+ border: 0;
355
+ border-radius: 999px;
356
+ font: inherit;
357
+ font-size: 12px;
358
+ font-weight: 500;
359
+ color: var(--md-sys-color-on-primary, #fff);
360
+ background: var(--md-sys-color-primary, #6750a4);
361
+ cursor: pointer;
362
+ }
363
+
364
+ [handler-list] footer button:hover {
365
+ filter: brightness(1.08);
366
+ }
367
+
320
368
  [handler-list] [empty] {
321
369
  padding: 24px 12px;
322
370
  text-align: center;
@@ -1 +1 @@
1
- {"version":3,"file":"event-handlers-popup.js","sourceRoot":"","sources":["../../../../src/property-panel/event-handlers/event-handlers-popup.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;;AAEH,OAAO,4BAA4B,CAAA;AACnC,OAAO,0BAA0B,CAAA;AAEjC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAGlE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjD,OAAO,EAAE,mBAAmB,EAAyB,MAAM,4BAA4B,CAAA;AACvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAGrD,IAAM,kBAAkB,GAAxB,MAAM,kBAAmB,SAAQ,mBAAmB,CAAC,UAAU,CAAC;IAAhE;;QAwKsB,aAAQ,GAAuB,EAAE,CAAA;QAEnD,kBAAa,GAAW,CAAC,CAAA;IAiLpC,CAAC;IA1VC,MAAM,KAAK,cAAc;QACvB,OAAO;YACL,uBAAuB,EAAE,mBAAmB;YAC5C,qBAAqB,EAAE,iBAAiB;SACzC,CAAA;IACH,CAAC;IAsKD,MAAM;;QACJ,MAAM,QAAQ,GAAG,MAAA,IAAI,CAAC,QAAQ,mCAAI,EAAE,CAAA;QACpC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;QAE5C,OAAO,IAAI,CAAA;;;sEAGuD,QAAQ,CAAC,MAAM;4BACzD,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE;;UAE1C,QAAQ,CAAC,MAAM,KAAK,CAAC;YACrB,CAAC,CAAC,IAAI,CAAA,qFAAqF;YAC3F,CAAC,CAAC,IAAI,CAAA,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO;;;;UAIvF,OAAO;YACP,CAAC,CAAC,IAAI,CAAA;;2BAEW,OAAO;yBACT,IAAI,CAAC,KAAK;gCACH,CAAC,CAAc,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC;;aAEhE;YACH,CAAC,CAAC,IAAI,CAAA,iGAAiG;;;;;qBAK5F,OAAO;4BACA,CAAC,CAAc,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;;;KAGnE,CAAA;IACH,CAAC;IAEO,eAAe,CAAC,CAAmB,EAAE,CAAS,EAAE,KAAa;QACnE,OAAO,IAAI,CAAA;;kBAEG,CAAC,KAAK,IAAI,CAAC,aAAa;oBACtB,CAAC,CAAC,QAAQ;iBACb,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;;;kCAGb,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;kCACtC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;;;mBAGjC,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;kBACzC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;WACxC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW;;mBAE/B,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC;;;;mBAI1C,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;;mBAEzC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,EAAE;;;mBAGhD,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;;mBAEzC,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,EAAE;;;;mBAIxD,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;;;;KAIrD,CAAA;IACH,CAAC;IAEO,UAAU,CAAC,CAAmB;QACpC,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC1B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;YACpD,OAAO,WAAW,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAA;QAChF,CAAC;QACD,OAAO,CAAC,CAAC,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACzF,CAAC;IAEO,WAAW;QACjB,MAAM,IAAI,GAAqB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;QAC/D,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,CAAA;QAChD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAA;QAC7C,IAAI,CAAC,SAAS,EAAE,CAAA;IAClB,CAAC;IAEO,iBAAiB,CAAC,GAAW,EAAE,CAAQ;QAC7C,CAAC,CAAC,eAAe,EAAE,CAAA;QACnB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;QACnC,IAAI,CAAC,QAAQ;YAAE,OAAM;QACrB,MAAM,IAAI,GAAqB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAA;QACnE,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAA;QACvC,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAA;QAC7B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QACpB,IAAI,CAAC,aAAa,GAAG,GAAG,GAAG,CAAC,CAAA;QAC5B,IAAI,CAAC,SAAS,EAAE,CAAA;IAClB,CAAC;IAEO,eAAe,CAAC,GAAW,EAAE,CAAQ;QAC3C,CAAC,CAAC,eAAe,EAAE,CAAA;QACnB,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAA;QACvC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC3D,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QACpB,IAAI,CAAC,SAAS,EAAE,CAAA;IAClB,CAAC;IAEO,YAAY,CAAC,GAAW,EAAE,KAAa,EAAE,CAAQ;QACvD,CAAC,CAAC,eAAe,EAAE,CAAA;QACnB,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAA;QACvC,MAAM,MAAM,GAAG,GAAG,GAAG,KAAK,CAAA;QAC1B,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,IAAI,IAAI,CAAC,MAAM;YAAE,OAAM;QAC/C,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QAC/B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;QACzB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QACpB,IAAI,IAAI,CAAC,aAAa,KAAK,GAAG;YAAE,IAAI,CAAC,aAAa,GAAG,MAAM,CAAA;QAC3D,IAAI,CAAC,SAAS,EAAE,CAAA;IAClB,CAAC;IAEO,cAAc,CAAC,GAAW,EAAE,CAAQ;QAC1C,CAAC,CAAC,eAAe,EAAE,CAAA;QACnB,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAA;QACvC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACnB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QACpB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAA;QAC/E,IAAI,CAAC,SAAS,EAAE,CAAA;IAClB,CAAC;IAEO,iBAAiB,CAAC,CAAc;;QACtC,MAAM,OAAO,GAAG,MAAA,CAAC,CAAC,MAAM,0CAAE,OAA2B,CAAA;QACrD,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAA;QACvC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,OAAO,CAAA;QAClC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QACpB,IAAI,CAAC,SAAS,EAAE,CAAA;IAClB,CAAC;IAED;;;OAGG;IACK,gBAAgB,CAAC,CAAc;;QACrC,MAAM,OAAO,GAAG,MAAA,CAAC,CAAC,MAAM,0CAAE,IAAI,CAAA;QAC9B,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO;YAAE,OAAM;QACnD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;QAC7C,IAAI,CAAC,GAAG;YAAE,OAAM;QAChB,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAA;QACxC,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QACjD,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAA;QACrF,MAAM,WAAW,GAAqB;YACpC,GAAG,GAAG;YACN,MAAM,EAAE,QAAQ;YAChB,IAAI,EAAE,QAAQ;SACf,CAAA;QACD,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC/B,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,WAAW,CAAA;QACtC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QACpB,IAAI,CAAC,SAAS,EAAE,CAAA;IAClB,CAAC;IAEO,SAAS;QACf,sDAAsD;QACtD,sDAAsD;QACtD,eAAe;QACf,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;QAClE,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,iBAAiB,EAAE;YACjC,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;SAC5B,CAAC,CACH,CAAA;IACH,CAAC;;AAlVM,yBAAM,GAAG;IACd,eAAe;IACf,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2JF;CACF,AA9JY,CA8JZ;AAE0B;IAA1B,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;oDAAkC;AAChC;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;iDAAc;AAChC;IAAR,KAAK,EAAE;yDAA0B;AA1KvB,kBAAkB;IAD9B,aAAa,CAAC,sBAAsB,CAAC;GACzB,kBAAkB,CA2V9B","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * EventHandlers popup — N 핸들러 large editor. data-binding-popup 의 형제.\n *\n * 2-column layout:\n * Left: handler list — add / duplicate / disable / reorder / delete\n * Right: 선택된 handler 의 mapper (큰 code editor 영역)\n */\n\nimport '@material/web/icon/icon.js'\nimport '@operato/i18n/ox-i18n.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport type { Scene } from '@hatiolab/things-scene'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { EventHandlersMapper, type EventHandlerSpec } from './event-handlers-mapper.js'\nimport { EventHandlersHelp } from './event-handlers-help.js'\n\n@customElement('event-handlers-popup')\nexport class EventHandlersPopup extends ScopedElementsMixin(LitElement) {\n static get scopedElements() {\n return {\n 'event-handlers-mapper': EventHandlersMapper,\n 'event-handlers-help': EventHandlersHelp\n }\n }\n\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n width: 1400px;\n height: 680px;\n overflow: hidden;\n font-family: 'Roboto', Arial, sans-serif;\n font-size: 13px;\n color: var(--md-sys-color-on-surface, #1c1b1f);\n background: var(--md-sys-color-surface, #fff);\n }\n\n [handler-list] {\n width: 240px;\n min-width: 240px;\n border-right: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));\n display: flex;\n flex-direction: column;\n background: var(--md-sys-color-surface-container-low, #f7f2fa);\n }\n\n [handler-list] header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 12px;\n font-weight: 600;\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--md-sys-color-on-surface-variant, #49454f);\n border-bottom: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));\n }\n\n [handler-list] header md-icon {\n cursor: pointer;\n --md-icon-size: 18px;\n border-radius: 4px;\n padding: 2px;\n }\n\n [handler-list] header md-icon:hover {\n background: rgba(0, 0, 0, 0.05);\n }\n\n [handler-list] ul {\n list-style: none;\n margin: 0;\n padding: 0;\n overflow-y: auto;\n flex: 1;\n }\n\n [handler-list] li {\n padding: 8px 10px;\n cursor: pointer;\n border-bottom: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.08));\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n [handler-list] li[active] {\n background: var(--md-sys-color-secondary-container, #e8def8);\n color: var(--md-sys-color-on-secondary-container, #1d192b);\n }\n\n [handler-list] li[disabled] {\n opacity: 0.5;\n font-style: italic;\n }\n\n [handler-list] li:hover {\n background: rgba(0, 0, 0, 0.04);\n }\n\n [handler-list] li .meta {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n }\n\n [handler-list] li .trigger {\n font-weight: 600;\n font-size: 12px;\n }\n\n [handler-list] li .summary {\n font-size: 11px;\n opacity: 0.7;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n margin-top: 2px;\n }\n\n [handler-list] li md-icon {\n --md-icon-size: 16px;\n opacity: 0.5;\n cursor: pointer;\n padding: 3px;\n border-radius: 4px;\n flex-shrink: 0;\n }\n\n [handler-list] li md-icon:hover {\n opacity: 1;\n background: rgba(0, 0, 0, 0.05);\n }\n\n [handler-list] li md-icon.danger:hover {\n color: var(--md-sys-color-error, #b3261e);\n }\n\n [handler-list] [empty] {\n padding: 24px 12px;\n text-align: center;\n color: var(--md-sys-color-on-surface-variant, #49454f);\n opacity: 0.6;\n font-size: 12px;\n }\n\n [editor] {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n padding: 16px 20px;\n background: var(--md-sys-color-surface, #fff);\n }\n\n [editor] [empty] {\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n color: var(--md-sys-color-on-surface-variant, #49454f);\n font-size: 13px;\n opacity: 0.6;\n }\n\n [editor] event-handlers-mapper {\n min-height: 100%;\n /* popup 에서는 위에 toolbar 가 없다 — 상하 모두 둥근 모서리로 균형. */\n border-radius: 6px;\n }\n\n [help] {\n width: 320px;\n min-width: 320px;\n max-width: 320px;\n display: flex;\n flex-direction: column;\n }\n `\n ]\n\n @property({ type: Array }) handlers: EventHandlerSpec[] = []\n @property({ type: Object }) scene?: Scene\n @state() selectedIndex: number = 0\n\n render() {\n const handlers = this.handlers ?? []\n const current = handlers[this.selectedIndex]\n\n return html`\n <div handler-list>\n <header>\n <span><ox-i18n msgid=\"label.handlers\">Handlers</ox-i18n> (${handlers.length})</span>\n <md-icon @click=${() => this._addHandler()} title=\"add\">add</md-icon>\n </header>\n ${handlers.length === 0\n ? html`<div empty><ox-i18n msgid=\"label.no-handlers\">No handlers — add one</ox-i18n></div>`\n : html`<ul>${handlers.map((h, i) => this._renderListItem(h, i, handlers.length))}</ul>`}\n </div>\n\n <div editor>\n ${current\n ? html`\n <event-handlers-mapper\n .handler=${current}\n .scene=${this.scene}\n @value-change=${(e: CustomEvent) => this._onHandlerChanged(e)}\n ></event-handlers-mapper>\n `\n : html`<div empty><ox-i18n msgid=\"label.select-or-add-handler\">Select or add a handler</ox-i18n></div>`}\n </div>\n\n <div help>\n <event-handlers-help\n .handler=${current}\n @insert-snippet=${(e: CustomEvent) => this._onInsertSnippet(e)}\n ></event-handlers-help>\n </div>\n `\n }\n\n private _renderListItem(h: EventHandlerSpec, i: number, total: number) {\n return html`\n <li\n ?active=${i === this.selectedIndex}\n ?disabled=${h.disabled}\n @click=${() => (this.selectedIndex = i)}\n >\n <div class=\"meta\">\n <span class=\"trigger\">${h.trigger}${h.disabled ? ' (off)' : ''}</span>\n <span class=\"summary\">${this._summarize(h)}</span>\n </div>\n <md-icon\n @click=${(e: Event) => this._toggleDisabled(i, e)}\n title=${h.disabled ? 'enable' : 'disable'}\n >${h.disabled ? 'toggle_off' : 'toggle_on'}</md-icon>\n <md-icon\n @click=${(e: Event) => this._duplicateHandler(i, e)}\n title=\"duplicate\"\n >content_copy</md-icon>\n <md-icon\n @click=${(e: Event) => this._moveHandler(i, -1, e)}\n title=\"move up\"\n ?style=${i === 0 ? 'opacity:0.2;pointer-events:none' : ''}\n >arrow_upward</md-icon>\n <md-icon\n @click=${(e: Event) => this._moveHandler(i, +1, e)}\n title=\"move down\"\n ?style=${i === total - 1 ? 'opacity:0.2;pointer-events:none' : ''}\n >arrow_downward</md-icon>\n <md-icon\n class=\"danger\"\n @click=${(e: Event) => this._removeHandler(i, e)}\n title=\"delete\"\n >delete</md-icon>\n </li>\n `\n }\n\n private _summarize(h: EventHandlerSpec): string {\n if (h.action === 'script') {\n const c = (h.code || '').replace(/\\s+/g, ' ').trim()\n return 'script · ' + (c.length > 60 ? c.slice(0, 60) + '…' : (c || '(empty)'))\n }\n return [h.action || '(none)', h.target ? `→${h.target}` : ''].filter(Boolean).join(' ')\n }\n\n private _addHandler() {\n const next: EventHandlerSpec = { trigger: 'click', action: '' }\n this.handlers = [...(this.handlers || []), next]\n this.selectedIndex = this.handlers.length - 1\n this._dispatch()\n }\n\n private _duplicateHandler(idx: number, e: Event) {\n e.stopPropagation()\n const original = this.handlers[idx]\n if (!original) return\n const copy: EventHandlerSpec = JSON.parse(JSON.stringify(original))\n const next = [...(this.handlers || [])]\n next.splice(idx + 1, 0, copy)\n this.handlers = next\n this.selectedIndex = idx + 1\n this._dispatch()\n }\n\n private _toggleDisabled(idx: number, e: Event) {\n e.stopPropagation()\n const next = [...(this.handlers || [])]\n next[idx] = { ...next[idx], disabled: !next[idx].disabled }\n this.handlers = next\n this._dispatch()\n }\n\n private _moveHandler(idx: number, delta: -1 | 1, e: Event) {\n e.stopPropagation()\n const next = [...(this.handlers || [])]\n const newIdx = idx + delta\n if (newIdx < 0 || newIdx >= next.length) return\n const [m] = next.splice(idx, 1)\n next.splice(newIdx, 0, m)\n this.handlers = next\n if (this.selectedIndex === idx) this.selectedIndex = newIdx\n this._dispatch()\n }\n\n private _removeHandler(idx: number, e: Event) {\n e.stopPropagation()\n const next = [...(this.handlers || [])]\n next.splice(idx, 1)\n this.handlers = next\n this.selectedIndex = Math.max(0, Math.min(this.selectedIndex, next.length - 1))\n this._dispatch()\n }\n\n private _onHandlerChanged(e: CustomEvent) {\n const handler = e.detail?.handler as EventHandlerSpec\n if (!handler) return\n const next = [...(this.handlers || [])]\n next[this.selectedIndex] = handler\n this.handlers = next\n this._dispatch()\n }\n\n /**\n * help pane 의 snippet 버튼 → 현재 선택 handler 의 code 에 *append* (덮어쓰지\n * 않음). action 이 script 가 아니면 자동으로 script 로 전환 + code 초기화.\n */\n private _onInsertSnippet(e: CustomEvent) {\n const snippet = e.detail?.code\n if (typeof snippet !== 'string' || !snippet) return\n const cur = this.handlers[this.selectedIndex]\n if (!cur) return\n const isScript = cur.action === 'script'\n const baseCode = isScript ? (cur.code || '') : ''\n const nextCode = baseCode ? baseCode.replace(/\\s*$/, '') + '\\n\\n' + snippet : snippet\n const nextHandler: EventHandlerSpec = {\n ...cur,\n action: 'script',\n code: nextCode\n }\n const next = [...this.handlers]\n next[this.selectedIndex] = nextHandler\n this.handlers = next\n this._dispatch()\n }\n\n private _dispatch() {\n // action 이 (none) — 즉 빈 문자열 — 인 핸들러는 외부 모델에 포함하지 않는다.\n // UI 안에서는 placeholder 로 남겨두고, 사용자가 action 을 선택하면 그제서야\n // 저장 모델에 포함된다.\n const valid = (this.handlers || []).filter(h => !!h && !!h.action)\n this.dispatchEvent(\n new CustomEvent('handlers-change', {\n bubbles: true,\n composed: true,\n detail: { handlers: valid }\n })\n )\n }\n}\n"]}
1
+ {"version":3,"file":"event-handlers-popup.js","sourceRoot":"","sources":["../../../../src/property-panel/event-handlers/event-handlers-popup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * EventHandlers popup — N 핸들러 large editor. data-binding-popup 의 형제.\n *\n * 2-column layout:\n * Left: handler list — add / duplicate / disable / reorder / delete\n * Right: 선택된 handler 의 mapper (큰 code editor 영역)\n */\n\nimport '@material/web/icon/icon.js'\nimport '@operato/i18n/ox-i18n.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport type { Scene } from '@hatiolab/things-scene'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { i18next } from '@operato/i18n'\nimport { EventHandlersMapper, type EventHandlerSpec } from './event-handlers-mapper.js'\nimport { EventHandlersHelp } from './event-handlers-help.js'\n\n@customElement('event-handlers-popup')\nexport class EventHandlersPopup extends ScopedElementsMixin(LitElement) {\n static get scopedElements() {\n return {\n 'event-handlers-mapper': EventHandlersMapper,\n 'event-handlers-help': EventHandlersHelp\n }\n }\n\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n width: 1400px;\n height: 680px;\n overflow: hidden;\n font-family: 'Roboto', Arial, sans-serif;\n font-size: 13px;\n color: var(--md-sys-color-on-surface, #1c1b1f);\n background: var(--md-sys-color-surface, #fff);\n }\n\n [handler-list] {\n width: 240px;\n min-width: 240px;\n border-right: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));\n display: flex;\n flex-direction: column;\n background: var(--md-sys-color-surface-container-low, #f7f2fa);\n }\n\n [handler-list] header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 12px;\n font-weight: 600;\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--md-sys-color-on-surface-variant, #49454f);\n border-bottom: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));\n }\n\n [handler-list] header md-icon {\n cursor: pointer;\n --md-icon-size: 18px;\n border-radius: 4px;\n padding: 2px;\n }\n\n [handler-list] header md-icon:hover {\n background: rgba(0, 0, 0, 0.05);\n }\n\n [handler-list] ul {\n list-style: none;\n margin: 0;\n padding: 0;\n overflow-y: auto;\n flex: 1;\n }\n\n [handler-list] li {\n padding: 8px 10px;\n cursor: pointer;\n border-bottom: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.08));\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n [handler-list] li[active] {\n background: var(--md-sys-color-secondary-container, #e8def8);\n color: var(--md-sys-color-on-secondary-container, #1d192b);\n }\n\n [handler-list] li[disabled] {\n opacity: 0.5;\n font-style: italic;\n }\n\n [handler-list] li:hover {\n background: rgba(0, 0, 0, 0.04);\n }\n\n [handler-list] li .meta {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n }\n\n [handler-list] li .trigger {\n font-weight: 600;\n font-size: 12px;\n }\n\n [handler-list] li .summary {\n font-size: 11px;\n opacity: 0.7;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n margin-top: 2px;\n }\n\n [handler-list] li md-icon {\n --md-icon-size: 16px;\n opacity: 0.5;\n cursor: pointer;\n padding: 3px;\n border-radius: 4px;\n flex-shrink: 0;\n }\n\n [handler-list] li md-icon:hover {\n opacity: 1;\n background: rgba(0, 0, 0, 0.05);\n }\n\n [handler-list] li md-icon.danger:hover {\n color: var(--md-sys-color-error, #b3261e);\n }\n\n /* There is no save step: every edit already reached the shape. The footer says so, and\n * offers the one thing left to do. */\n [handler-list] footer {\n margin-top: auto;\n padding: 10px 12px 12px;\n border-top: 1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, 0.12));\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n\n [handler-list] footer small {\n font-size: 11px;\n line-height: 1.35;\n color: var(--md-sys-color-on-surface-variant, #49454f);\n }\n\n [handler-list] footer button {\n align-self: flex-end;\n height: 28px;\n padding: 0 16px;\n border: 0;\n border-radius: 999px;\n font: inherit;\n font-size: 12px;\n font-weight: 500;\n color: var(--md-sys-color-on-primary, #fff);\n background: var(--md-sys-color-primary, #6750a4);\n cursor: pointer;\n }\n\n [handler-list] footer button:hover {\n filter: brightness(1.08);\n }\n\n [handler-list] [empty] {\n padding: 24px 12px;\n text-align: center;\n color: var(--md-sys-color-on-surface-variant, #49454f);\n opacity: 0.6;\n font-size: 12px;\n }\n\n [editor] {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n padding: 16px 20px;\n background: var(--md-sys-color-surface, #fff);\n }\n\n [editor] [empty] {\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n color: var(--md-sys-color-on-surface-variant, #49454f);\n font-size: 13px;\n opacity: 0.6;\n }\n\n [editor] event-handlers-mapper {\n min-height: 100%;\n /* popup 에서는 위에 toolbar 가 없다 — 상하 모두 둥근 모서리로 균형. */\n border-radius: 6px;\n }\n\n [help] {\n width: 320px;\n min-width: 320px;\n max-width: 320px;\n display: flex;\n flex-direction: column;\n }\n `\n ]\n\n @property({ type: Array }) handlers: EventHandlerSpec[] = []\n @property({ type: Object }) scene?: Scene\n @state() selectedIndex: number = 0\n\n render() {\n const handlers = this.handlers ?? []\n const current = handlers[this.selectedIndex]\n\n return html`\n <div handler-list>\n <header>\n <span><ox-i18n msgid=\"label.handlers\">Handlers</ox-i18n> (${handlers.length})</span>\n <md-icon @click=${() => this._addHandler()} title=\"add\">add</md-icon>\n </header>\n ${handlers.length === 0\n ? html`<div empty><ox-i18n msgid=\"label.no-handlers\">No handlers — add one</ox-i18n></div>`\n : html`<ul>${handlers.map((h, i) => this._renderListItem(h, i, handlers.length))}</ul>`}\n <footer>\n <small>\n ${i18next.t('text.handlers-apply-live', {\n defaultValue: 'Changes apply to the shape as you edit. Save the board to keep them.'\n })}\n </small>\n <button @click=${() => this._done()}>${i18next.t('button.done', { defaultValue: 'Done' })}</button>\n </footer>\n </div>\n\n <div editor>\n ${current\n ? html`\n <event-handlers-mapper\n .handler=${current}\n .scene=${this.scene}\n @value-change=${(e: CustomEvent) => this._onHandlerChanged(e)}\n ></event-handlers-mapper>\n `\n : html`<div empty><ox-i18n msgid=\"label.select-or-add-handler\">Select or add a handler</ox-i18n></div>`}\n </div>\n\n <div help>\n <event-handlers-help\n .handler=${current}\n @insert-snippet=${(e: CustomEvent) => this._onInsertSnippet(e)}\n ></event-handlers-help>\n </div>\n `\n }\n\n private _renderListItem(h: EventHandlerSpec, i: number, total: number) {\n return html`\n <li\n ?active=${i === this.selectedIndex}\n ?disabled=${h.disabled}\n @click=${() => (this.selectedIndex = i)}\n >\n <div class=\"meta\">\n <span class=\"trigger\">${h.trigger}${h.disabled ? ' (off)' : ''}</span>\n <span class=\"summary\">${this._summarize(h)}</span>\n </div>\n <md-icon\n @click=${(e: Event) => this._toggleDisabled(i, e)}\n title=${h.disabled ? 'enable' : 'disable'}\n >${h.disabled ? 'toggle_off' : 'toggle_on'}</md-icon>\n <md-icon\n @click=${(e: Event) => this._duplicateHandler(i, e)}\n title=\"duplicate\"\n >content_copy</md-icon>\n <md-icon\n @click=${(e: Event) => this._moveHandler(i, -1, e)}\n title=\"move up\"\n ?style=${i === 0 ? 'opacity:0.2;pointer-events:none' : ''}\n >arrow_upward</md-icon>\n <md-icon\n @click=${(e: Event) => this._moveHandler(i, +1, e)}\n title=\"move down\"\n ?style=${i === total - 1 ? 'opacity:0.2;pointer-events:none' : ''}\n >arrow_downward</md-icon>\n <md-icon\n class=\"danger\"\n @click=${(e: Event) => this._removeHandler(i, e)}\n title=\"delete\"\n >delete</md-icon>\n </li>\n `\n }\n\n private _summarize(h: EventHandlerSpec): string {\n if (h.action === 'script') {\n const c = (h.code || '').replace(/\\s+/g, ' ').trim()\n return 'script · ' + (c.length > 60 ? c.slice(0, 60) + '…' : (c || '(empty)'))\n }\n return [h.action || '(none)', h.target ? `→${h.target}` : ''].filter(Boolean).join(' ')\n }\n\n private _addHandler() {\n const next: EventHandlerSpec = { trigger: 'click', action: '' }\n this.handlers = [...(this.handlers || []), next]\n this.selectedIndex = this.handlers.length - 1\n this._dispatch()\n }\n\n private _duplicateHandler(idx: number, e: Event) {\n e.stopPropagation()\n const original = this.handlers[idx]\n if (!original) return\n const copy: EventHandlerSpec = JSON.parse(JSON.stringify(original))\n const next = [...(this.handlers || [])]\n next.splice(idx + 1, 0, copy)\n this.handlers = next\n this.selectedIndex = idx + 1\n this._dispatch()\n }\n\n private _toggleDisabled(idx: number, e: Event) {\n e.stopPropagation()\n const next = [...(this.handlers || [])]\n next[idx] = { ...next[idx], disabled: !next[idx].disabled }\n this.handlers = next\n this._dispatch()\n }\n\n private _moveHandler(idx: number, delta: -1 | 1, e: Event) {\n e.stopPropagation()\n const next = [...(this.handlers || [])]\n const newIdx = idx + delta\n if (newIdx < 0 || newIdx >= next.length) return\n const [m] = next.splice(idx, 1)\n next.splice(newIdx, 0, m)\n this.handlers = next\n if (this.selectedIndex === idx) this.selectedIndex = newIdx\n this._dispatch()\n }\n\n private _removeHandler(idx: number, e: Event) {\n e.stopPropagation()\n const next = [...(this.handlers || [])]\n next.splice(idx, 1)\n this.handlers = next\n this.selectedIndex = Math.max(0, Math.min(this.selectedIndex, next.length - 1))\n this._dispatch()\n }\n\n private _onHandlerChanged(e: CustomEvent) {\n const handler = e.detail?.handler as EventHandlerSpec\n if (!handler) return\n const next = [...(this.handlers || [])]\n next[this.selectedIndex] = handler\n this.handlers = next\n this._dispatch()\n }\n\n /**\n * help pane 의 snippet 버튼 → 현재 선택 handler 의 code 에 *append* (덮어쓰지\n * 않음). action 이 script 가 아니면 자동으로 script 로 전환 + code 초기화.\n */\n private _onInsertSnippet(e: CustomEvent) {\n const snippet = e.detail?.code\n if (typeof snippet !== 'string' || !snippet) return\n const cur = this.handlers[this.selectedIndex]\n if (!cur) return\n const isScript = cur.action === 'script'\n const baseCode = isScript ? (cur.code || '') : ''\n const nextCode = baseCode ? baseCode.replace(/\\s*$/, '') + '\\n\\n' + snippet : snippet\n const nextHandler: EventHandlerSpec = {\n ...cur,\n action: 'script',\n code: nextCode\n }\n const next = [...this.handlers]\n next[this.selectedIndex] = nextHandler\n this.handlers = next\n this._dispatch()\n }\n\n /* Nothing to commit - the last edit is already on the shape - so this only dismisses the popup. */\n private _done() {\n this.dispatchEvent(new CustomEvent('close-overlay', { bubbles: true, composed: true }))\n }\n\n private _dispatch() {\n // action 이 (none) — 즉 빈 문자열 — 인 핸들러는 외부 모델에 포함하지 않는다.\n // UI 안에서는 placeholder 로 남겨두고, 사용자가 action 을 선택하면 그제서야\n // 저장 모델에 포함된다.\n const valid = (this.handlers || []).filter(h => !!h && !!h.action)\n this.dispatchEvent(\n new CustomEvent('handlers-change', {\n bubbles: true,\n composed: true,\n detail: { handlers: valid }\n })\n )\n }\n}\n"]}
@@ -22,6 +22,8 @@ export declare class PropertyEventHandlers extends PropertyEventHandlers_base {
22
22
  value?: Properties;
23
23
  scene?: Scene;
24
24
  handlerIndex: number;
25
+ private _composing;
26
+ private _composingFor?;
25
27
  _afterRender?: Function | null;
26
28
  tabs: HTMLElement;
27
29
  tabNavLeftButton: HTMLElement;
@@ -38,6 +40,7 @@ export declare class PropertyEventHandlers extends PropertyEventHandlers_base {
38
40
  'event-handlers-mapper': typeof EventHandlersMapper;
39
41
  };
40
42
  render(): import("lit-html").TemplateResult<1>;
43
+ private _startHandler;
41
44
  _setHandlerIndex(idx: number): void;
42
45
  _clearHandler(): void;
43
46
  _copyHandler(): void;
@@ -13,7 +13,7 @@ import '@material/web/icon/icon.js';
13
13
  import '@operato/help/ox-title-with-help.js';
14
14
  import '@operato/input/ox-buttons-radio.js';
15
15
  import '@operato/i18n/ox-i18n.js';
16
- import { css, html } from 'lit';
16
+ import { css, html, nothing } from 'lit';
17
17
  import { property, query, state } from 'lit/decorators.js';
18
18
  import { ScopedElementsMixin } from '@open-wc/scoped-elements';
19
19
  import { PropertyGridStyles } from '@operato/styles/property-grid-styles.js';
@@ -26,6 +26,8 @@ export class PropertyEventHandlers extends ScopedElementsMixin(AbstractProperty)
26
26
  constructor() {
27
27
  super(...arguments);
28
28
  this.handlerIndex = 0;
29
+ /* True while a new handler is being composed and is not yet in the model (no action chosen). */
30
+ this._composing = false;
29
31
  }
30
32
  /**
31
33
  * 두 속성 통합 — 신규 `eventHandlers` 우선, 없으면 legacy `event` 객체를
@@ -73,9 +75,18 @@ export class PropertyEventHandlers extends ScopedElementsMixin(AbstractProperty)
73
75
  return out;
74
76
  }
75
77
  firstUpdated() {
76
- this.tabContainer.addEventListener('scroll', () => this._onTabScroll());
78
+ /* The tab strip only exists once there is a handler; scroll does not bubble, so capture it at the root. */
79
+ this.renderRoot.addEventListener('scroll', e => e.target.id === 'tabs' && this._onTabScroll(), true);
77
80
  }
78
81
  updated(changes) {
82
+ var _a, _b;
83
+ if (changes.has('value')) {
84
+ const selected = (_b = (_a = this.scene) === null || _a === void 0 ? void 0 : _a.selected) === null || _b === void 0 ? void 0 : _b[0];
85
+ if (selected !== this._composingFor) {
86
+ this._composingFor = selected;
87
+ this._composing = false;
88
+ }
89
+ }
79
90
  if (changes.has('value')) {
80
91
  this.onValueChanged();
81
92
  }
@@ -94,59 +105,73 @@ export class PropertyEventHandlers extends ScopedElementsMixin(AbstractProperty)
94
105
  <ox-title-with-help topic="board-modeller/effects/event-handlers" msgid="label.event-handlers"
95
106
  >Event Handlers</ox-title-with-help
96
107
  >
97
- <span style="font-size:11px;opacity:0.65;margin-left:6px">(${handlers.length})</span>
108
+ ${handlers.length ? html `<span class="count">${handlers.length}</span>` : nothing}
98
109
  <md-icon
99
- style="float:right;font-size:medium;margin:0;cursor:pointer"
110
+ class="open-popup"
100
111
  @click=${() => this._openHandlersPopup()}
101
112
  title="open in popup"
102
113
  >open_in_new</md-icon
103
114
  >
104
115
  </legend>
105
116
 
106
- <div id="tab-header">
107
- <md-icon id="tab-nav-left-button" @click=${() => this._onTabScrollNavLeft()} disabled
108
- >chevron_left</md-icon
109
- >
117
+ ${handlers.length || this._composing
118
+ ? html `
119
+ <div id="tab-header">
120
+ <md-icon id="tab-nav-left-button" @click=${() => this._onTabScrollNavLeft()} disabled
121
+ >chevron_left</md-icon
122
+ >
110
123
 
111
- <ox-buttons-radio
112
- id="tabs"
113
- .value=${String(this.handlerIndex)}
114
- @change=${(e) => {
115
- e.stopPropagation();
116
- this._setHandlerIndex(e.target.value);
117
- }}
118
- >
119
- ${handlers.map((h, i) => html `
120
- <div data-value=${i} title="${h.trigger} · ${h.action || '(none)'}${h.disabled ? ' (disabled)' : ''}">
121
- ${h.trigger}
122
- </div>
123
- `)}
124
- <div data-value=${handlers.length} disabled title="add new">+</div>
125
- </ox-buttons-radio>
124
+ <ox-buttons-radio
125
+ id="tabs"
126
+ .value=${String(this.handlerIndex)}
127
+ @change=${(e) => {
128
+ e.stopPropagation();
129
+ this._setHandlerIndex(e.target.value);
130
+ }}
131
+ >
132
+ ${handlers.map((h, i) => html `
133
+ <div data-value=${i} title="${h.trigger} · ${h.action || '(none)'}${h.disabled ? ' (disabled)' : ''}">
134
+ ${i + 1}
135
+ </div>
136
+ `)}
137
+ <div data-value=${handlers.length} class="add-tab" title="add" @click=${() => (this._composing = true)}>
138
+ +
139
+ </div>
140
+ </ox-buttons-radio>
126
141
 
127
- <md-icon id="tab-nav-right-button" @click=${() => this._onTabScrollNavRight()} disabled
128
- >chevron_right</md-icon
129
- >
130
- </div>
142
+ <md-icon id="tab-nav-right-button" @click=${() => this._onTabScrollNavRight()} disabled
143
+ >chevron_right</md-icon
144
+ >
131
145
 
132
- <div handler-toolbar>
133
- <md-icon style="font-size:19px" @click=${() => this._clearHandler()} title="delete current"
134
- >delete_forever</md-icon
135
- >
136
- <md-icon @click=${() => this._pasteHandler()} title="paste">content_paste</md-icon>
137
- <md-icon style="font-size:17px" @click=${() => this._copyHandler()} title="copy"
138
- >content_copy</md-icon
139
- >
140
- </div>
146
+ <div handler-toolbar>
147
+ <md-icon style="font-size:19px" @click=${() => this._clearHandler()} title="delete current"
148
+ >delete_forever</md-icon
149
+ >
150
+ <md-icon @click=${() => this._pasteHandler()} title="paste">content_paste</md-icon>
151
+ <md-icon style="font-size:17px" @click=${() => this._copyHandler()} title="copy"
152
+ >content_copy</md-icon
153
+ >
154
+ </div>
155
+ </div>
141
156
 
142
- <event-handlers-mapper
143
- @value-change=${(e) => this._onHandlerChanged(e)}
144
- .scene=${this.scene}
145
- .handler=${current}
146
- ></event-handlers-mapper>
157
+ <event-handlers-mapper
158
+ @value-change=${(e) => this._onHandlerChanged(e)}
159
+ .scene=${this.scene}
160
+ .handler=${current}
161
+ ></event-handlers-mapper>
162
+ `
163
+ : html `
164
+ <button class="add" @click=${() => this._startHandler()}>
165
+ <md-icon>add</md-icon>${i18next.t('button.add-handler', { defaultValue: 'Add a handler' })}
166
+ </button>
167
+ `}
147
168
  </fieldset>
148
169
  `;
149
170
  }
171
+ _startHandler() {
172
+ this._composing = true;
173
+ this.handlerIndex = this.handlers.length;
174
+ }
150
175
  _setHandlerIndex(idx) {
151
176
  this.handlerIndex = isNaN(Number(idx)) ? 0 : Number(idx);
152
177
  this._onTabScroll();
@@ -286,102 +311,161 @@ PropertyEventHandlers.styles = [
286
311
  overflow: hidden;
287
312
  }
288
313
 
314
+ /* The mapper lays its rows out itself; this outer rule must not flatten it to a block,
315
+ * or the row gap it declares on its own host is lost. */
289
316
  event-handlers-mapper {
290
- display: block;
317
+ display: flex;
318
+ flex-direction: column;
319
+ gap: 6px;
291
320
  min-width: 0;
292
321
  max-width: 100%;
293
322
  box-sizing: border-box;
294
323
  }
295
324
 
296
325
  /* 카드 상단 — chevron + tabs 를 하나의 둥근 모서리 박스로 통합. */
326
+ /* Not a card: a row of chips (one per handler, plus "+"), then the fields like any other section. */
327
+ /* How many there are, as a small muted count next to the title. */
328
+ legend > .count {
329
+ margin-left: 6px;
330
+ padding: 0 6px;
331
+ border-radius: 999px;
332
+ font-size: 0.66rem;
333
+ font-weight: 500;
334
+ line-height: 16px;
335
+ color: var(--md-sys-color-on-surface-variant);
336
+ background: var(--md-sys-color-surface-container-high);
337
+ }
338
+
339
+ /* Nothing yet: one quiet way in, instead of an empty form for a handler that does not exist. */
340
+ button.add {
341
+ display: inline-flex;
342
+ align-items: center;
343
+ gap: 4px;
344
+ height: 26px;
345
+ padding: 0 10px 0 6px;
346
+ border: 1px dashed var(--md-sys-color-outline-variant);
347
+ border-radius: 999px;
348
+ background: transparent;
349
+ color: var(--md-sys-color-on-surface-variant);
350
+ font: inherit;
351
+ font-size: 0.74rem;
352
+ cursor: pointer;
353
+ }
354
+
355
+ button.add md-icon {
356
+ --md-icon-size: 16px;
357
+ }
358
+
359
+ button.add:hover {
360
+ color: var(--md-sys-color-on-surface);
361
+ border-color: var(--md-sys-color-outline);
362
+ background: var(--md-sys-color-surface-container-high);
363
+ }
364
+
365
+ /* The "+" chip is an offer until it is pressed; pressed, it is the same chip as the numbered ones. */
366
+ ox-buttons-radio > div.add-tab:not([active]) {
367
+ line-height: 20px;
368
+ border: 1px dashed var(--md-sys-color-outline-variant);
369
+ background: transparent;
370
+ }
371
+
372
+ /* The "open in popup" action is the last thing on the title line, at the far right. */
373
+ legend > md-icon.open-popup {
374
+ margin-left: auto;
375
+ --md-icon-size: 16px;
376
+ color: var(--md-sys-color-outline);
377
+ opacity: 0.6;
378
+ cursor: pointer;
379
+ }
380
+
381
+ legend > md-icon.open-popup:hover {
382
+ color: var(--md-sys-color-on-surface);
383
+ }
384
+
297
385
  #tab-header {
298
386
  display: flex;
299
- align-items: stretch;
300
- justify-content: space-between;
301
- border: 1px solid rgba(0, 0, 0, 0.18);
302
- border-radius: 6px 6px 0 0;
303
- overflow: hidden;
304
- background: var(--md-sys-color-surface, #fff);
387
+ align-items: center;
388
+ gap: 2px;
389
+ margin-bottom: 6px;
305
390
  }
306
391
 
307
392
  #tab-header > md-icon {
308
393
  padding: 0;
309
394
  margin: 0;
310
- width: 25px;
311
- height: 25px;
312
- font-size: x-large;
313
- align-self: center;
395
+ --md-icon-size: 18px;
396
+ color: var(--md-sys-color-outline);
397
+ }
398
+
399
+ #tab-header > md-icon[disabled] {
400
+ opacity: 0.3;
314
401
  }
315
402
 
316
403
  ox-buttons-radio {
317
404
  flex: 1;
318
- height: 25px;
319
- /* 외곽 border 는 #tab-header 가 들고 있다 — 여기는 0. */
320
- border: 0;
321
- border-radius: 0;
322
- text-align: center;
323
405
  display: flex;
406
+ gap: 4px;
324
407
  padding: 0;
325
408
  box-sizing: border-box;
326
409
  width: 0;
327
410
  overflow-x: hidden;
328
411
  }
329
412
 
413
+ /* Tabs are quiet chips on the strip; the open one is lifted to the card's surface. */
330
414
  ox-buttons-radio > div {
331
- background-color: rgba(0, 0, 0, 0.2);
332
- border: 1px solid rgba(0, 0, 0, 0.07);
333
- border-width: 0 0 2px 0;
334
- padding: 0 8px;
415
+ border: 0;
416
+ border-radius: 999px;
417
+ padding: 0;
335
418
  margin: 0;
336
- color: #fff;
337
- font-size: 12px;
338
- max-width: 110px;
339
- min-width: 40px;
340
- white-space: nowrap;
341
- overflow: hidden;
342
- text-overflow: ellipsis;
343
- line-height: 23px;
419
+ width: 24px;
420
+ height: 22px;
421
+ line-height: 22px;
422
+ text-align: center;
423
+ font-size: 0.72rem;
424
+ box-sizing: border-box;
425
+ color: var(--md-sys-color-on-surface-variant);
426
+ background-color: var(--md-sys-color-surface-container-high);
427
+ cursor: pointer;
344
428
  }
345
429
 
346
430
  ox-buttons-radio > div[disabled] {
347
- background-color: rgba(0, 0, 0, 0.1);
348
- }
349
-
350
- ox-buttons-radio > div[active] {
351
- border-color: rgb(242, 71, 28);
431
+ opacity: 0.4;
352
432
  }
353
433
 
434
+ ox-buttons-radio > div[active],
354
435
  ox-buttons-radio > div.iron-selected {
355
- background-color: var(--md-sys-color-surface);
356
- color: var(--md-sys-color-on-surface);
436
+ color: var(--md-sys-color-on-secondary-container);
437
+ background-color: var(--md-sys-color-secondary-container);
357
438
  }
358
439
 
359
440
  div[handler-toolbar] {
360
441
  display: flex;
361
442
  flex-direction: row-reverse;
362
- background-color: var(--md-sys-color-surface);
363
- color: var(--md-sys-color-on-surface);
364
- overflow: hidden;
365
- border: 1px solid rgba(0, 0, 0, 0.18);
366
- border-width: 0 1px;
367
- padding: 6px 6px 4px 6px;
443
+ gap: 2px;
444
+ margin-left: auto;
445
+ padding-left: 6px;
446
+ }
447
+
448
+ #tab-header > md-icon[disabled] {
449
+ display: none;
368
450
  }
369
451
 
452
+ /* Section actions are offers, not content: outline ink at half strength until hovered. */
370
453
  md-icon {
371
454
  margin-left: 5px;
372
- color: var(--md-sys-color-on-secondary-container);
373
- opacity: 0.8;
455
+ color: var(--md-sys-color-outline);
456
+ opacity: 0.6;
374
457
  cursor: pointer;
375
458
  --md-icon-size: 18px;
376
459
  }
377
460
 
378
461
  md-icon:hover {
379
- color: var(--md-sys-color-on-primary-container);
462
+ color: var(--md-sys-color-on-surface);
380
463
  opacity: 1;
381
464
  }
382
465
 
383
466
  md-icon[disabled] {
384
- color: rgba(0, 0, 0, 0.1);
467
+ color: var(--md-sys-color-outline-variant);
468
+ cursor: default;
385
469
  }
386
470
  `
387
471
  ];
@@ -394,6 +478,9 @@ __decorate([
394
478
  __decorate([
395
479
  state()
396
480
  ], PropertyEventHandlers.prototype, "handlerIndex", void 0);
481
+ __decorate([
482
+ state()
483
+ ], PropertyEventHandlers.prototype, "_composing", void 0);
397
484
  __decorate([
398
485
  state()
399
486
  ], PropertyEventHandlers.prototype, "_afterRender", void 0);