@operato/property-panel 10.0.0-beta.64 → 10.0.0-beta.65

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,15 @@
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.65](https://github.com/hatiolab/operato/compare/v10.0.0-beta.64...v10.0.0-beta.65) (2026-07-10)
7
+
8
+
9
+ ### :bug: Bug Fix
10
+
11
+ * add hidden button on the component group view of inspector ([2af50f0](https://github.com/hatiolab/operato/commit/2af50f0992073c93accb77b1279998265cc2ca78))
12
+
13
+
14
+
6
15
  ## [10.0.0-beta.64](https://github.com/hatiolab/operato/compare/v10.0.0-beta.63...v10.0.0-beta.64) (2026-07-08)
7
16
 
8
17
  **Note:** Version bump only for package @operato/property-panel
@@ -35,6 +35,10 @@ export declare class InspectorController {
35
35
  /** 그룹(타입) dim-out 토글 → scene.focusFilter 재설정(편집 모드에서 캔버스 dim + pick 제외). */
36
36
  toggleTypeDim(type: string): void;
37
37
  private _applyFocusFilter;
38
+ /** 그룹 전원이 hidden 이면 true(집계). 멤버/트리 eye 규약과 동일한 아이콘 판정에 사용. */
39
+ isTypeHidden(type: string): boolean;
40
+ /** 그룹(타입) hidden 토글 → 하나라도 보이면 전원 숨김, 전원 숨김이면 전원 표시. */
41
+ toggleTypeHidden(type: string): void;
38
42
  /** 개별 컴포넌트 hidden(영구 모델 속성) 토글 — 기존 eye 동작. */
39
43
  toggleHidden(component: Component): void;
40
44
  /** 개별 컴포넌트 locked(영구 모델 속성) 토글. */
@@ -143,6 +143,24 @@ export class InspectorController {
143
143
  this._scene.focusFilter = (component) => !dimmed.has(component.state.type);
144
144
  }
145
145
  }
146
+ // ── 그룹(타입) 단위 hidden 토글 (개별 hidden 의 집계 — dim 과 달리 영구 모델 속성) ──
147
+ /** 그룹 전원이 hidden 이면 true(집계). 멤버/트리 eye 규약과 동일한 아이콘 판정에 사용. */
148
+ isTypeHidden(type) {
149
+ var _a;
150
+ const entries = (_a = this._index) === null || _a === void 0 ? void 0 : _a.byType.get(type);
151
+ return !!entries && entries.length > 0 && entries.every(e => !!e.component.get('hidden'));
152
+ }
153
+ /** 그룹(타입) hidden 토글 → 하나라도 보이면 전원 숨김, 전원 숨김이면 전원 표시. */
154
+ toggleTypeHidden(type) {
155
+ var _a, _b;
156
+ const entries = (_a = this._index) === null || _a === void 0 ? void 0 : _a.byType.get(type);
157
+ if (!entries || entries.length === 0)
158
+ return;
159
+ const target = !entries.every(e => !!e.component.get('hidden'));
160
+ for (const e of entries)
161
+ e.component.set('hidden', target);
162
+ (_b = this._onChange) === null || _b === void 0 ? void 0 : _b.call(this);
163
+ }
146
164
  /** 개별 컴포넌트 hidden(영구 모델 속성) 토글 — 기존 eye 동작. */
147
165
  toggleHidden(component) {
148
166
  var _a;
@@ -1 +1 @@
1
- {"version":3,"file":"inspector-controller.js","sourceRoot":"","sources":["../../../../src/property-panel/inspector/inspector-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Scene Inspector 컨트롤러.\n *\n * scene 과의 상호작용(구독·선택·가시성·확장 상태)과 모델 인덱스 캐시를 한 곳에 응집한다.\n * DOM 을 직접 다루지 않으며, 뷰 렌더러(트리/그룹)가 공유한다. 상태 변경 시 onChange 로\n * 호스트(LitElement)의 재렌더를 요청한다.\n */\nimport { Component, Container, Scene } from '@hatiolab/things-scene'\n\nimport { buildInspectorIndex, InspectorIndex, matchesQuery, SearchTerm } from './inspector-model.js'\n\nexport class InspectorController {\n private _scene?: Scene\n private _extendedMap = new WeakMap<Component, boolean>()\n /** 타입 그룹 뷰의 펼침 상태 — 그룹 키(type)는 안정적이라 Set 으로 관리. 기본 전부 접힘. */\n private _expandedGroups = new Set<string>()\n /** 모델링 포커스 dim 처리된 그룹(타입) 집합 — transient(비영속). */\n private _dimmedTypes = new Set<string>()\n /** shift+click 범위(볼륨) 선택의 기준점 — 마지막 단일/토글 클릭. */\n private _anchor?: Component\n private _index?: InspectorIndex\n private _onChange?: () => void\n\n /** scene 에 바인딩 — 이전 바인딩은 해제한다. onChange 는 재렌더 요청 콜백. */\n bind(scene: Scene | undefined, onChange: () => void): void {\n this.unbind()\n this._scene = scene\n this._onChange = onChange\n this._index = undefined\n\n if (scene && scene.root) {\n scene.on('selected', this._refresh, this)\n scene.on('execute', this._invalidateAndRefresh, this)\n scene.on('undo', this._invalidateAndRefresh, this)\n scene.on('redo', this._invalidateAndRefresh, this)\n this._refresh()\n }\n }\n\n unbind(): void {\n const scene = this._scene\n if (scene) {\n // 포커스 dim 은 transient — 인스펙터가 떠나면 캔버스 dim 해제.\n scene.focusFilter = undefined\n scene.off('selected', undefined, this)\n scene.off('execute', undefined, this)\n scene.off('undo', undefined, this)\n scene.off('redo', undefined, this)\n }\n this._scene = undefined\n this._index = undefined\n this._extendedMap = new WeakMap()\n this._dimmedTypes.clear()\n }\n\n get scene(): Scene | undefined {\n return this._scene\n }\n\n get selected(): Component[] {\n return this._scene?.selected || []\n }\n\n /** 캐시된 모델 인덱스 — 없으면 build. execute/undo/redo 시 무효화. */\n get index(): InspectorIndex | undefined {\n if (!this._scene?.root) return undefined\n if (!this._index) this._index = buildInspectorIndex(this._scene.root)\n return this._index\n }\n\n private _invalidateAndRefresh = (): void => {\n this._index = undefined\n this._refresh()\n }\n\n /** 선택된 컴포넌트의 조상을 펼치고 재렌더 요청. */\n private _refresh = (): void => {\n const selected = this._scene?.selected || []\n selected.forEach((component: Component) => {\n let parent = component.parent\n while (parent && !this._extendedMap.get(parent)) {\n this._extendedMap.set(parent, true)\n parent = parent.parent\n }\n })\n this._onChange?.()\n }\n\n isExtended(component: Component): boolean {\n return !!this._extendedMap.get(component)\n }\n\n toggleExtended(component: Component): void {\n if (this.isExtended(component)) {\n this._extendedMap.delete(component)\n } else {\n this._extendedMap.set(component, true)\n }\n this._onChange?.()\n }\n\n /** 트리 노드 핸들(펼침/접힘/공백) 클래스. */\n nodeHandleClass(component: Component): string {\n if (component.isContainer() && (component as Container).components.length > 0) {\n return this.isExtended(component) ? 'extended' : 'collapsed'\n }\n return 'collapsespace'\n }\n\n // ── 타입 그룹 뷰 확장 상태 ──\n\n isGroupExpanded(type: string): boolean {\n return this._expandedGroups.has(type)\n }\n\n toggleGroup(type: string): void {\n if (this._expandedGroups.has(type)) {\n this._expandedGroups.delete(type)\n } else {\n this._expandedGroups.add(type)\n }\n this._onChange?.()\n }\n\n /** 모든 그룹 접기 — \"그룹 레벨까지만 펼침\"(헤더만 표시). */\n collapseAllGroups(): void {\n if (this._expandedGroups.size === 0) return\n this._expandedGroups.clear()\n this._onChange?.()\n }\n\n /** 모든 그룹 펼치기. */\n expandAllGroups(types: string[]): void {\n types.forEach(t => this._expandedGroups.add(t))\n this._onChange?.()\n }\n\n // ── 모델링 포커스 dim (그룹 단위, transient) ──\n\n isTypeDimmed(type: string): boolean {\n return this._dimmedTypes.has(type)\n }\n\n /** 그룹(타입) dim-out 토글 → scene.focusFilter 재설정(편집 모드에서 캔버스 dim + pick 제외). */\n toggleTypeDim(type: string): void {\n if (this._dimmedTypes.has(type)) this._dimmedTypes.delete(type)\n else this._dimmedTypes.add(type)\n this._applyFocusFilter()\n this._onChange?.()\n }\n\n private _applyFocusFilter(): void {\n if (!this._scene) return\n if (this._dimmedTypes.size === 0) {\n this._scene.focusFilter = undefined\n } else {\n const dimmed = this._dimmedTypes\n // focus(정상) = dim 집합에 없는 타입. 불일치(=dim 그룹)만 캔버스에서 흐림 + pick 제외.\n this._scene.focusFilter = (component: Component) => !dimmed.has(component.state.type)\n }\n }\n\n /** 개별 컴포넌트 hidden(영구 모델 속성) 토글 — 기존 eye 동작. */\n toggleHidden(component: Component): void {\n component.set('hidden', !component.get('hidden'))\n this._onChange?.()\n }\n\n /** 개별 컴포넌트 locked(영구 모델 속성) 토글. */\n toggleLocked(component: Component): void {\n component.set('locked', !component.get('locked'))\n this._onChange?.()\n }\n\n /** 캔버스를 해당 컴포넌트로 센터 이동. */\n centerTo(component: Component): void {\n ;(this._scene as unknown as { centerTo?: (c: Component, animated?: boolean) => void })?.centerTo?.(component, true)\n }\n\n /** 선택 — append(ctrl/cmd) 시 토글식 다중 선택. anchor 를 갱신한다. */\n select(component: Component, append = false): void {\n const scene = this._scene\n if (!scene) return\n\n const selected = scene.selected\n if (append) {\n const idx = selected.findIndex((s: Component) => s === component)\n if (idx != -1) {\n selected.splice(idx, 1)\n scene.selected = [...selected]\n } else {\n scene.selected = [...selected, component]\n }\n } else {\n scene.selected = [component]\n }\n\n this._anchor = component\n this._decorate(component)\n this._onChange?.()\n }\n\n /**\n * shift+click 범위(볼륨) 선택 — anchor↔target 사이의 **보이는 트리 행**을 블록 선택한다.\n * target 이 이미 선택돼 있으면 그 범위를 블록 **해제**(additive/subtractive). anchor 는 유지.\n */\n selectRange(target: Component, terms: SearchTerm[]): void {\n const scene = this._scene\n if (!scene) return\n\n const order = this._visibleOrder(terms)\n const ti = order.indexOf(target)\n if (ti < 0) return\n\n const ai = this._anchor ? order.indexOf(this._anchor) : -1\n if (ai < 0) {\n // anchor 없음/보이지 않음 → 단일 토글로 폴백(+ anchor 설정)\n this.select(target, true)\n return\n }\n\n const [lo, hi] = ai <= ti ? [ai, ti] : [ti, ai]\n const range = order.slice(lo, hi + 1)\n const rangeSet = new Set(range)\n\n // target 이 이미 선택 상태면 블록 해제, 아니면 블록 선택.\n if (scene.selected.indexOf(target) > -1) {\n scene.selected = scene.selected.filter((c: Component) => !rangeSet.has(c))\n range.forEach(c => c.trigger('reactionreset'))\n } else {\n const cur = new Set(scene.selected)\n const next = [...scene.selected]\n range.forEach(c => {\n if (!cur.has(c)) next.push(c)\n this._decorate(c)\n })\n scene.selected = next\n }\n // anchor 는 유지 — 연속 shift+click 으로 범위 확장/축소.\n this._onChange?.()\n }\n\n /** 선택 강조. */\n private _decorate(component: Component): void {\n component.trigger('reactionreset')\n component.trigger('reaction')\n }\n\n /**\n * 현재 트리 뷰에 보이는 컴포넌트를 렌더 순서대로 반환 — 범위 선택의 기준.\n * renderNode 와 동일 규칙(shouldBeShown = 자신/자손 매치, isExtended 자식만 전개).\n */\n private _visibleOrder(terms: SearchTerm[]): Component[] {\n const root = this._scene?.root\n if (!root) return []\n\n const shown = (c: Component): boolean =>\n matchesQuery(c, terms) ||\n (c.isContainer() && (c as Container).components.some((child: Component) => shown(child)))\n\n const out: Component[] = []\n const walk = (c: Component) => {\n if (!shown(c)) return\n out.push(c)\n if (this.isExtended(c) && c.isContainer()) {\n for (const child of (c as Container).components) walk(child)\n }\n }\n walk(root)\n return out\n }\n}\n"]}
1
+ {"version":3,"file":"inspector-controller.js","sourceRoot":"","sources":["../../../../src/property-panel/inspector/inspector-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Scene Inspector 컨트롤러.\n *\n * scene 과의 상호작용(구독·선택·가시성·확장 상태)과 모델 인덱스 캐시를 한 곳에 응집한다.\n * DOM 을 직접 다루지 않으며, 뷰 렌더러(트리/그룹)가 공유한다. 상태 변경 시 onChange 로\n * 호스트(LitElement)의 재렌더를 요청한다.\n */\nimport { Component, Container, Scene } from '@hatiolab/things-scene'\n\nimport { buildInspectorIndex, InspectorIndex, matchesQuery, SearchTerm } from './inspector-model.js'\n\nexport class InspectorController {\n private _scene?: Scene\n private _extendedMap = new WeakMap<Component, boolean>()\n /** 타입 그룹 뷰의 펼침 상태 — 그룹 키(type)는 안정적이라 Set 으로 관리. 기본 전부 접힘. */\n private _expandedGroups = new Set<string>()\n /** 모델링 포커스 dim 처리된 그룹(타입) 집합 — transient(비영속). */\n private _dimmedTypes = new Set<string>()\n /** shift+click 범위(볼륨) 선택의 기준점 — 마지막 단일/토글 클릭. */\n private _anchor?: Component\n private _index?: InspectorIndex\n private _onChange?: () => void\n\n /** scene 에 바인딩 — 이전 바인딩은 해제한다. onChange 는 재렌더 요청 콜백. */\n bind(scene: Scene | undefined, onChange: () => void): void {\n this.unbind()\n this._scene = scene\n this._onChange = onChange\n this._index = undefined\n\n if (scene && scene.root) {\n scene.on('selected', this._refresh, this)\n scene.on('execute', this._invalidateAndRefresh, this)\n scene.on('undo', this._invalidateAndRefresh, this)\n scene.on('redo', this._invalidateAndRefresh, this)\n this._refresh()\n }\n }\n\n unbind(): void {\n const scene = this._scene\n if (scene) {\n // 포커스 dim 은 transient — 인스펙터가 떠나면 캔버스 dim 해제.\n scene.focusFilter = undefined\n scene.off('selected', undefined, this)\n scene.off('execute', undefined, this)\n scene.off('undo', undefined, this)\n scene.off('redo', undefined, this)\n }\n this._scene = undefined\n this._index = undefined\n this._extendedMap = new WeakMap()\n this._dimmedTypes.clear()\n }\n\n get scene(): Scene | undefined {\n return this._scene\n }\n\n get selected(): Component[] {\n return this._scene?.selected || []\n }\n\n /** 캐시된 모델 인덱스 — 없으면 build. execute/undo/redo 시 무효화. */\n get index(): InspectorIndex | undefined {\n if (!this._scene?.root) return undefined\n if (!this._index) this._index = buildInspectorIndex(this._scene.root)\n return this._index\n }\n\n private _invalidateAndRefresh = (): void => {\n this._index = undefined\n this._refresh()\n }\n\n /** 선택된 컴포넌트의 조상을 펼치고 재렌더 요청. */\n private _refresh = (): void => {\n const selected = this._scene?.selected || []\n selected.forEach((component: Component) => {\n let parent = component.parent\n while (parent && !this._extendedMap.get(parent)) {\n this._extendedMap.set(parent, true)\n parent = parent.parent\n }\n })\n this._onChange?.()\n }\n\n isExtended(component: Component): boolean {\n return !!this._extendedMap.get(component)\n }\n\n toggleExtended(component: Component): void {\n if (this.isExtended(component)) {\n this._extendedMap.delete(component)\n } else {\n this._extendedMap.set(component, true)\n }\n this._onChange?.()\n }\n\n /** 트리 노드 핸들(펼침/접힘/공백) 클래스. */\n nodeHandleClass(component: Component): string {\n if (component.isContainer() && (component as Container).components.length > 0) {\n return this.isExtended(component) ? 'extended' : 'collapsed'\n }\n return 'collapsespace'\n }\n\n // ── 타입 그룹 뷰 확장 상태 ──\n\n isGroupExpanded(type: string): boolean {\n return this._expandedGroups.has(type)\n }\n\n toggleGroup(type: string): void {\n if (this._expandedGroups.has(type)) {\n this._expandedGroups.delete(type)\n } else {\n this._expandedGroups.add(type)\n }\n this._onChange?.()\n }\n\n /** 모든 그룹 접기 — \"그룹 레벨까지만 펼침\"(헤더만 표시). */\n collapseAllGroups(): void {\n if (this._expandedGroups.size === 0) return\n this._expandedGroups.clear()\n this._onChange?.()\n }\n\n /** 모든 그룹 펼치기. */\n expandAllGroups(types: string[]): void {\n types.forEach(t => this._expandedGroups.add(t))\n this._onChange?.()\n }\n\n // ── 모델링 포커스 dim (그룹 단위, transient) ──\n\n isTypeDimmed(type: string): boolean {\n return this._dimmedTypes.has(type)\n }\n\n /** 그룹(타입) dim-out 토글 → scene.focusFilter 재설정(편집 모드에서 캔버스 dim + pick 제외). */\n toggleTypeDim(type: string): void {\n if (this._dimmedTypes.has(type)) this._dimmedTypes.delete(type)\n else this._dimmedTypes.add(type)\n this._applyFocusFilter()\n this._onChange?.()\n }\n\n private _applyFocusFilter(): void {\n if (!this._scene) return\n if (this._dimmedTypes.size === 0) {\n this._scene.focusFilter = undefined\n } else {\n const dimmed = this._dimmedTypes\n // focus(정상) = dim 집합에 없는 타입. 불일치(=dim 그룹)만 캔버스에서 흐림 + pick 제외.\n this._scene.focusFilter = (component: Component) => !dimmed.has(component.state.type)\n }\n }\n\n // ── 그룹(타입) 단위 hidden 토글 (개별 hidden 의 집계 — dim 과 달리 영구 모델 속성) ──\n\n /** 그룹 전원이 hidden 이면 true(집계). 멤버/트리 eye 규약과 동일한 아이콘 판정에 사용. */\n isTypeHidden(type: string): boolean {\n const entries = this._index?.byType.get(type)\n return !!entries && entries.length > 0 && entries.every(e => !!e.component.get('hidden'))\n }\n\n /** 그룹(타입) hidden 토글 → 하나라도 보이면 전원 숨김, 전원 숨김이면 전원 표시. */\n toggleTypeHidden(type: string): void {\n const entries = this._index?.byType.get(type)\n if (!entries || entries.length === 0) return\n const target = !entries.every(e => !!e.component.get('hidden'))\n for (const e of entries) e.component.set('hidden', target)\n this._onChange?.()\n }\n\n /** 개별 컴포넌트 hidden(영구 모델 속성) 토글 — 기존 eye 동작. */\n toggleHidden(component: Component): void {\n component.set('hidden', !component.get('hidden'))\n this._onChange?.()\n }\n\n /** 개별 컴포넌트 locked(영구 모델 속성) 토글. */\n toggleLocked(component: Component): void {\n component.set('locked', !component.get('locked'))\n this._onChange?.()\n }\n\n /** 캔버스를 해당 컴포넌트로 센터 이동. */\n centerTo(component: Component): void {\n ;(this._scene as unknown as { centerTo?: (c: Component, animated?: boolean) => void })?.centerTo?.(component, true)\n }\n\n /** 선택 — append(ctrl/cmd) 시 토글식 다중 선택. anchor 를 갱신한다. */\n select(component: Component, append = false): void {\n const scene = this._scene\n if (!scene) return\n\n const selected = scene.selected\n if (append) {\n const idx = selected.findIndex((s: Component) => s === component)\n if (idx != -1) {\n selected.splice(idx, 1)\n scene.selected = [...selected]\n } else {\n scene.selected = [...selected, component]\n }\n } else {\n scene.selected = [component]\n }\n\n this._anchor = component\n this._decorate(component)\n this._onChange?.()\n }\n\n /**\n * shift+click 범위(볼륨) 선택 — anchor↔target 사이의 **보이는 트리 행**을 블록 선택한다.\n * target 이 이미 선택돼 있으면 그 범위를 블록 **해제**(additive/subtractive). anchor 는 유지.\n */\n selectRange(target: Component, terms: SearchTerm[]): void {\n const scene = this._scene\n if (!scene) return\n\n const order = this._visibleOrder(terms)\n const ti = order.indexOf(target)\n if (ti < 0) return\n\n const ai = this._anchor ? order.indexOf(this._anchor) : -1\n if (ai < 0) {\n // anchor 없음/보이지 않음 → 단일 토글로 폴백(+ anchor 설정)\n this.select(target, true)\n return\n }\n\n const [lo, hi] = ai <= ti ? [ai, ti] : [ti, ai]\n const range = order.slice(lo, hi + 1)\n const rangeSet = new Set(range)\n\n // target 이 이미 선택 상태면 블록 해제, 아니면 블록 선택.\n if (scene.selected.indexOf(target) > -1) {\n scene.selected = scene.selected.filter((c: Component) => !rangeSet.has(c))\n range.forEach(c => c.trigger('reactionreset'))\n } else {\n const cur = new Set(scene.selected)\n const next = [...scene.selected]\n range.forEach(c => {\n if (!cur.has(c)) next.push(c)\n this._decorate(c)\n })\n scene.selected = next\n }\n // anchor 는 유지 — 연속 shift+click 으로 범위 확장/축소.\n this._onChange?.()\n }\n\n /** 선택 강조. */\n private _decorate(component: Component): void {\n component.trigger('reactionreset')\n component.trigger('reaction')\n }\n\n /**\n * 현재 트리 뷰에 보이는 컴포넌트를 렌더 순서대로 반환 — 범위 선택의 기준.\n * renderNode 와 동일 규칙(shouldBeShown = 자신/자손 매치, isExtended 자식만 전개).\n */\n private _visibleOrder(terms: SearchTerm[]): Component[] {\n const root = this._scene?.root\n if (!root) return []\n\n const shown = (c: Component): boolean =>\n matchesQuery(c, terms) ||\n (c.isContainer() && (c as Container).components.some((child: Component) => shown(child)))\n\n const out: Component[] = []\n const walk = (c: Component) => {\n if (!shown(c)) return\n out.push(c)\n if (this.isExtended(c) && c.isContainer()) {\n for (const child of (c as Container).components) walk(child)\n }\n }\n walk(root)\n return out\n }\n}\n"]}
@@ -8,6 +8,8 @@ export interface GroupViewContext {
8
8
  isGroupExpanded: (type: string) => boolean;
9
9
  /** 그룹(타입)이 모델링 포커스 dim 처리됐는지. */
10
10
  isTypeDimmed: (type: string) => boolean;
11
+ /** 그룹(타입) 전원이 hidden(숨김)인지 — 그룹 hidden 토글 아이콘 판정. */
12
+ isTypeHidden: (type: string) => boolean;
11
13
  }
12
14
  /** 타입 그룹 뷰 전체 렌더 — 타입명 오름차순. */
13
15
  export declare function renderInspectorGroupView(index: InspectorIndex | undefined, ctx: GroupViewContext): TemplateResult;
@@ -26,10 +26,18 @@ function renderGroup(type, entries, ctx) {
26
26
  const expanded = ctx.isGroupExpanded(type);
27
27
  return html `
28
28
  <div class="group">
29
- <div class="group-header" data-group-type=${type} ?dimmed=${ctx.isTypeDimmed(type)}>
29
+ <div
30
+ class="group-header"
31
+ data-group-type=${type}
32
+ ?dimmed=${ctx.isTypeDimmed(type)}
33
+ ?hidden-group=${ctx.isTypeHidden(type)}
34
+ >
30
35
  <span class=${expanded ? 'extended' : 'collapsed'}> </span>
31
36
  <span class="group-type">${highlight(type, ctx.terms)}</span>
32
37
  <span class="group-count">${members.length}</span>
38
+ <md-icon class="group-hidden" title="Show/hide this group"
39
+ >${ctx.isTypeHidden(type) ? 'visibility_off' : 'visibility'}</md-icon
40
+ >
33
41
  <md-icon class="group-dim" title="Dim this group in canvas (modeling focus)"
34
42
  >${ctx.isTypeDimmed(type) ? 'blur_on' : 'blur_off'}</md-icon
35
43
  >
@@ -1 +1 @@
1
- {"version":3,"file":"inspector-group-view.js","sourceRoot":"","sources":["../../../../src/property-panel/inspector/inspector-group-view.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAkB,MAAM,KAAK,CAAA;AAEnD,OAAO,EAA8B,YAAY,EAAc,MAAM,sBAAsB,CAAA;AAC3F,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAWpD,SAAS,YAAY,CAAC,KAAiB,EAAE,KAAmB,EAAE,QAAqB;IACjF,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAA;IACjC,OAAO,IAAI,CAAA;oDACuC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,eAAe,SAAS;;+BAE7E,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,YAAY;wCAChD,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;aACpD,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW;;eAE5C,IAAI;UACT,KAAK,CAAC,KAAK;QACX,CAAC,CAAC,IAAI,CAAA,sBAAsB,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS;QAClE,CAAC,CAAC,IAAI,CAAA,oCAAoC;;;GAGjD,CAAA;AACH,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,OAAqB,EAAE,GAAqB;IAC7E,kCAAkC;IAClC,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;IACtG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA,EAAE,CAAA;IAE3D,MAAM,QAAQ,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;IAE1C,OAAO,IAAI,CAAA;;kDAEqC,IAAI,YAAY,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC;sBAClE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW;mCACtB,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC;oCACzB,OAAO,CAAC,MAAM;;aAErC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU;;;QAGpD,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO;;GAE5F,CAAA;AACH,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,wBAAwB,CAAC,KAAiC,EAAE,GAAqB;IAC/F,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA,EAAE,CAAA;IACzB,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;IACzE,OAAO,IAAI,CAAA,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAE,EAAE,GAAG,CAAC,CAAC,EAAE,CAAA;AACpF,CAAC","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Scene Inspector 타입 그룹 뷰 렌더러.\n *\n * 계층을 무시하고 `state.type` 기준 **논리 버킷**(= \"컴포넌트 그룹\")으로 재편성해 렌더한다.\n * 그룹 헤더(타입 + 개수 + 펼침/접힘 + dim-out 토글)와 멤버 행(개별 컴포넌트)으로 구성.\n * SceneInspector shadow root 에 직접 렌더 — 멤버 행은 트리 뷰와 동일한 `.component`/eye 규약을\n * 재사용해 기존 클릭 위임(선택·hidden·lock 토글)을 그대로 쓴다. 그룹 헤더 토글은 `data-group-type` 로 처리.\n *\n * (그룹별 필터는 전역 검색의 필드 토큰 `type:...` 과 중복이라 제거됨.)\n */\nimport { Component } from '@hatiolab/things-scene'\nimport { html, nothing, TemplateResult } from 'lit'\n\nimport { IndexEntry, InspectorIndex, matchesQuery, SearchTerm } from './inspector-model.js'\nimport { highlight } from './inspector-highlight.js'\n\nexport interface GroupViewContext {\n selected: Component[]\n /** 파싱된 전역 검색어(AND). */\n terms: SearchTerm[]\n isGroupExpanded: (type: string) => boolean\n /** 그룹(타입)이 모델링 포커스 dim 처리됐는지. */\n isTypeDimmed: (type: string) => boolean\n}\n\nfunction renderMember(entry: IndexEntry, terms: SearchTerm[], selected: Component[]): TemplateResult {\n const component = entry.component\n return html`\n <div class=\"component group-member\" ?selected=${selected.indexOf(component) > -1} .component=${component}>\n <span>\n <md-icon class=\"eye\">${component.get('hidden') ? 'visibility_off' : 'visibility'}</md-icon>\n <md-icon class=\"lock\" ?locked=${!!component.get('locked')}\n >${component.get('locked') ? 'lock' : 'lock_open'}</md-icon\n >\n <pre>${' '}</pre>\n ${entry.label\n ? html`<span class=\"name\">${highlight(entry.label, terms)}</span>`\n : html`<span class=\"muted\">(no id)</span>`}\n </span>\n </div>\n `\n}\n\nfunction renderGroup(type: string, entries: IndexEntry[], ctx: GroupViewContext): TemplateResult {\n // 전역 검색으로 멤버를 좁히고, 매치 없는 그룹은 숨긴다.\n const members = ctx.terms.length ? entries.filter(e => matchesQuery(e.component, ctx.terms)) : entries\n if (ctx.terms.length && members.length === 0) return html``\n\n const expanded = ctx.isGroupExpanded(type)\n\n return html`\n <div class=\"group\">\n <div class=\"group-header\" data-group-type=${type} ?dimmed=${ctx.isTypeDimmed(type)}>\n <span class=${expanded ? 'extended' : 'collapsed'}> </span>\n <span class=\"group-type\">${highlight(type, ctx.terms)}</span>\n <span class=\"group-count\">${members.length}</span>\n <md-icon class=\"group-dim\" title=\"Dim this group in canvas (modeling focus)\"\n >${ctx.isTypeDimmed(type) ? 'blur_on' : 'blur_off'}</md-icon\n >\n </div>\n ${expanded ? members.map(entry => renderMember(entry, ctx.terms, ctx.selected)) : nothing}\n </div>\n `\n}\n\n/** 타입 그룹 뷰 전체 렌더 — 타입명 오름차순. */\nexport function renderInspectorGroupView(index: InspectorIndex | undefined, ctx: GroupViewContext): TemplateResult {\n if (!index) return html``\n const types = [...index.byType.keys()].sort((a, b) => a.localeCompare(b))\n return html`${types.map(type => renderGroup(type, index.byType.get(type)!, ctx))}`\n}\n"]}
1
+ {"version":3,"file":"inspector-group-view.js","sourceRoot":"","sources":["../../../../src/property-panel/inspector/inspector-group-view.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Scene Inspector 타입 그룹 뷰 렌더러.\n *\n * 계층을 무시하고 `state.type` 기준 **논리 버킷**(= \"컴포넌트 그룹\")으로 재편성해 렌더한다.\n * 그룹 헤더(타입 + 개수 + 펼침/접힘 + dim-out 토글)와 멤버 행(개별 컴포넌트)으로 구성.\n * SceneInspector shadow root 에 직접 렌더 — 멤버 행은 트리 뷰와 동일한 `.component`/eye 규약을\n * 재사용해 기존 클릭 위임(선택·hidden·lock 토글)을 그대로 쓴다. 그룹 헤더 토글은 `data-group-type` 로 처리.\n *\n * (그룹별 필터는 전역 검색의 필드 토큰 `type:...` 과 중복이라 제거됨.)\n */\nimport { Component } from '@hatiolab/things-scene'\nimport { html, nothing, TemplateResult } from 'lit'\n\nimport { IndexEntry, InspectorIndex, matchesQuery, SearchTerm } from './inspector-model.js'\nimport { highlight } from './inspector-highlight.js'\n\nexport interface GroupViewContext {\n selected: Component[]\n /** 파싱된 전역 검색어(AND). */\n terms: SearchTerm[]\n isGroupExpanded: (type: string) => boolean\n /** 그룹(타입)이 모델링 포커스 dim 처리됐는지. */\n isTypeDimmed: (type: string) => boolean\n /** 그룹(타입) 전원이 hidden(숨김)인지 — 그룹 hidden 토글 아이콘 판정. */\n isTypeHidden: (type: string) => boolean\n}\n\nfunction renderMember(entry: IndexEntry, terms: SearchTerm[], selected: Component[]): TemplateResult {\n const component = entry.component\n return html`\n <div class=\"component group-member\" ?selected=${selected.indexOf(component) > -1} .component=${component}>\n <span>\n <md-icon class=\"eye\">${component.get('hidden') ? 'visibility_off' : 'visibility'}</md-icon>\n <md-icon class=\"lock\" ?locked=${!!component.get('locked')}\n >${component.get('locked') ? 'lock' : 'lock_open'}</md-icon\n >\n <pre>${' '}</pre>\n ${entry.label\n ? html`<span class=\"name\">${highlight(entry.label, terms)}</span>`\n : html`<span class=\"muted\">(no id)</span>`}\n </span>\n </div>\n `\n}\n\nfunction renderGroup(type: string, entries: IndexEntry[], ctx: GroupViewContext): TemplateResult {\n // 전역 검색으로 멤버를 좁히고, 매치 없는 그룹은 숨긴다.\n const members = ctx.terms.length ? entries.filter(e => matchesQuery(e.component, ctx.terms)) : entries\n if (ctx.terms.length && members.length === 0) return html``\n\n const expanded = ctx.isGroupExpanded(type)\n\n return html`\n <div class=\"group\">\n <div\n class=\"group-header\"\n data-group-type=${type}\n ?dimmed=${ctx.isTypeDimmed(type)}\n ?hidden-group=${ctx.isTypeHidden(type)}\n >\n <span class=${expanded ? 'extended' : 'collapsed'}> </span>\n <span class=\"group-type\">${highlight(type, ctx.terms)}</span>\n <span class=\"group-count\">${members.length}</span>\n <md-icon class=\"group-hidden\" title=\"Show/hide this group\"\n >${ctx.isTypeHidden(type) ? 'visibility_off' : 'visibility'}</md-icon\n >\n <md-icon class=\"group-dim\" title=\"Dim this group in canvas (modeling focus)\"\n >${ctx.isTypeDimmed(type) ? 'blur_on' : 'blur_off'}</md-icon\n >\n </div>\n ${expanded ? members.map(entry => renderMember(entry, ctx.terms, ctx.selected)) : nothing}\n </div>\n `\n}\n\n/** 타입 그룹 뷰 전체 렌더 — 타입명 오름차순. */\nexport function renderInspectorGroupView(index: InspectorIndex | undefined, ctx: GroupViewContext): TemplateResult {\n if (!index) return html``\n const types = [...index.byType.keys()].sort((a, b) => a.localeCompare(b))\n return html`${types.map(type => renderGroup(type, index.byType.get(type)!, ctx))}`\n}\n"]}
@@ -91,7 +91,8 @@ export class SceneInspector extends LitElement {
91
91
  selected: this.controller.selected,
92
92
  terms,
93
93
  isGroupExpanded: (type) => this.controller.isGroupExpanded(type),
94
- isTypeDimmed: (type) => this.controller.isTypeDimmed(type)
94
+ isTypeDimmed: (type) => this.controller.isTypeDimmed(type),
95
+ isTypeHidden: (type) => this.controller.isTypeHidden(type)
95
96
  });
96
97
  }
97
98
  return renderInspectorTree(this.scene.root, {
@@ -164,19 +165,27 @@ export class SceneInspector extends LitElement {
164
165
  return component;
165
166
  }
166
167
  _onclick(e) {
167
- var _a, _b, _c;
168
+ var _a, _b, _c, _d, _e;
168
169
  e.stopPropagation();
169
170
  const target = e.target;
171
+ // 그룹 hidden 아이콘 클릭 → 그룹(타입) 전원 hidden 토글 (헤더 펼침보다 먼저 처리)
172
+ if (target.classList.contains('group-hidden')) {
173
+ const type = (_b = (_a = target.closest) === null || _a === void 0 ? void 0 : _a.call(target, '[data-group-type]')) === null || _b === void 0 ? void 0 : _b.getAttribute('data-group-type');
174
+ if (type != null)
175
+ this.controller.toggleTypeHidden(type);
176
+ this.requestUpdate();
177
+ return;
178
+ }
170
179
  // 그룹 dim-out 아이콘 클릭 → 그룹(타입) 포커스 dim 토글 (헤더 펼침보다 먼저 처리)
171
180
  if (target.classList.contains('group-dim')) {
172
- const type = (_b = (_a = target.closest) === null || _a === void 0 ? void 0 : _a.call(target, '[data-group-type]')) === null || _b === void 0 ? void 0 : _b.getAttribute('data-group-type');
181
+ const type = (_d = (_c = target.closest) === null || _c === void 0 ? void 0 : _c.call(target, '[data-group-type]')) === null || _d === void 0 ? void 0 : _d.getAttribute('data-group-type');
173
182
  if (type != null)
174
183
  this.controller.toggleTypeDim(type);
175
184
  this.requestUpdate();
176
185
  return;
177
186
  }
178
187
  // 타입 그룹 헤더 클릭 → 그룹 펼침/접힘 (멤버 행은 헤더의 자손이 아니므로 여기 안 걸림)
179
- const header = (_c = target.closest) === null || _c === void 0 ? void 0 : _c.call(target, '[data-group-type]');
188
+ const header = (_e = target.closest) === null || _e === void 0 ? void 0 : _e.call(target, '[data-group-type]');
180
189
  if (header) {
181
190
  const type = header.getAttribute('data-group-type');
182
191
  if (type != null)
@@ -368,15 +377,23 @@ SceneInspector.styles = [
368
377
  border-radius: 999px;
369
378
  padding: 0 6px;
370
379
  }
380
+ .group-hidden,
371
381
  .group-dim {
372
- margin: 0 4px 0 6px;
373
382
  font-size: 1em;
374
383
  opacity: 0.35;
375
384
  cursor: pointer;
376
385
  }
386
+ .group-hidden {
387
+ margin: 0 2px 0 6px;
388
+ }
389
+ .group-dim {
390
+ margin: 0 4px 0 2px;
391
+ }
392
+ .group-hidden:hover,
377
393
  .group-dim:hover {
378
394
  opacity: 0.8;
379
395
  }
396
+ .group-header[hidden-group] .group-hidden,
380
397
  .group-header[dimmed] .group-dim {
381
398
  opacity: 0.95;
382
399
  color: var(--md-sys-color-primary, #4a9eff);
@@ -1 +1 @@
1
- {"version":3,"file":"inspector.js","sourceRoot":"","sources":["../../../../src/property-panel/inspector/inspector.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * SceneInspector — 속성패널 inspector 탭 호스트.\n *\n * 얇은 호스트: 검색 상태 + 클릭 위임만 담당하고, scene 상호작용은 InspectorController,\n * 렌더는 뷰 렌더러(현재 트리)로 위임한다. (아키텍처 리팩터 P0 — 동작은 기존과 동일)\n */\nimport '@operato/input/ox-input-search.js'\n\nimport { css, html, LitElement, nothing, PropertyValues, TemplateResult } from 'lit'\nimport { property, state } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\n\nimport { Component, Scene } from '@hatiolab/things-scene'\nimport { InspectorStyles } from '@operato/styles/inspector-styles.js'\n\nimport { parseQuery } from './inspector-model.js'\nimport { InspectorController } from './inspector-controller.js'\nimport { countMatches, renderInspectorTree } from './inspector-tree-view.js'\nimport { renderInspectorGroupView } from './inspector-group-view.js'\n\ntype InspectorViewMode = 'tree' | 'group'\n\n/** 뷰 모드 선호를 세션 간 유지(localStorage). 전역 사용자 선호. */\nconst VIEW_MODE_STORAGE_KEY = 'scene-inspector.view-mode'\n\nfunction readStoredViewMode(): InspectorViewMode {\n try {\n const v = localStorage.getItem(VIEW_MODE_STORAGE_KEY)\n return v === 'group' || v === 'tree' ? v : 'tree'\n } catch {\n return 'tree'\n }\n}\n\nfunction writeStoredViewMode(mode: InspectorViewMode): void {\n try {\n localStorage.setItem(VIEW_MODE_STORAGE_KEY, mode)\n } catch {\n /* localStorage 미가용(SSR/프라이빗 등) — 무시 */\n }\n}\n\nexport class SceneInspector extends LitElement {\n static styles = [\n InspectorStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n\n color: var(--scene-inspector-color);\n }\n\n ox-input-search {\n --input-search-padding: 3px;\n --input-search-focus-border-bottom: none;\n --input-search-font: normal 16px var(--theme-font);\n\n border-radius: 999px;\n box-sizing: border-box;\n border: 1px solid var(--md-sys-color-on-secondary-container);\n padding: 0 10px;\n margin: 5px;\n }\n\n div[result] {\n flex: 1;\n\n display: flex;\n flex-direction: column;\n\n overflow-y: auto;\n }\n\n .component {\n display: block;\n overflow: hidden;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n font-size: 14px;\n }\n\n .component[selected] {\n background-color: var(--scene-inspector-selected-background-color);\n border-top: var(--scene-inspector-selected-border);\n border-bottom: var(--scene-inspector-selected-border);\n }\n [selected] .type {\n font-weight: bold;\n }\n\n span,\n i {\n display: inline-block;\n }\n\n span.type {\n text-overflow: ellipses;\n }\n\n span.name {\n background-color: var(--scene-inspector-name-background-color);\n border-radius: var(--border-radius);\n padding: 0 4px;\n color: #fff;\n font-size: 0.8em;\n }\n\n .eye {\n margin: 0 0 0 4px;\n vertical-align: middle;\n opacity: 0.7;\n font-size: 1em;\n width: 1em;\n color: var(--scene-inspector-eye-icon-color);\n cursor: pointer;\n }\n\n .lock {\n margin: 0;\n vertical-align: middle;\n opacity: 0.3;\n font-size: 1em;\n width: 1em;\n cursor: pointer;\n }\n .lock[locked] {\n opacity: 0.9;\n color: var(--md-sys-color-error, #d9534f);\n }\n\n pre {\n display: inline;\n /* 트리 인덴트(수직라인까지의 간격) 최소화 — depth 당 좁은 폭 */\n font-size: 0.6em;\n margin: 0;\n }\n\n /* 뷰 모드 툴바 */\n div[toolbar] {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 2px 8px 4px;\n }\n div[toolbar] .spacer {\n flex: 1;\n }\n div[toolbar] md-icon {\n cursor: pointer;\n opacity: 0.5;\n font-size: 18px;\n border-radius: 4px;\n padding: 2px;\n }\n div[toolbar] md-icon:hover {\n opacity: 0.9;\n }\n div[toolbar] md-icon[selected] {\n opacity: 1;\n color: var(--md-sys-color-primary, #4a9eff);\n background: rgba(0, 0, 0, 0.05);\n }\n\n /* 타입 그룹 뷰 */\n .group-header {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n padding: 2px 0;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n }\n .group-header:hover {\n background: rgba(0, 0, 0, 0.03);\n }\n .group-count {\n margin-left: auto;\n font-size: 0.72em;\n font-weight: normal;\n background: var(--scene-inspector-name-background-color);\n color: #fff;\n border-radius: 999px;\n padding: 0 6px;\n }\n .group-dim {\n margin: 0 4px 0 6px;\n font-size: 1em;\n opacity: 0.35;\n cursor: pointer;\n }\n .group-dim:hover {\n opacity: 0.8;\n }\n .group-header[dimmed] .group-dim {\n opacity: 0.95;\n color: var(--md-sys-color-primary, #4a9eff);\n }\n .group-header[dimmed] .group-type {\n opacity: 0.45;\n }\n .group-member {\n padding-left: 8px;\n }\n .muted {\n opacity: 0.4;\n font-style: italic;\n }\n /* 검색 매치 하이라이트 */\n mark {\n background: var(--md-sys-color-tertiary-container, #ffe082);\n color: inherit;\n border-radius: 2px;\n padding: 0 1px;\n }\n `\n ]\n\n @property({ type: Object }) scene?: Scene\n\n @state() private searchText: string = ''\n @state() private viewMode: InspectorViewMode = readStoredViewMode()\n\n private controller = new InspectorController()\n private show: boolean = false\n /** reveal(스크롤) 판정용 직전 선택. */\n private _lastSelected: Component[] = []\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.controller.unbind()\n }\n\n render() {\n return html`\n <ox-input-search\n .placeholder=${i18next.t('text.search with type, id or tag')}\n autofocus\n @change=${(e: Event) => (this.searchText = ((e.target as HTMLInputElement).value || '').toLowerCase())}\n ></ox-input-search>\n\n <div toolbar>\n <md-icon class=\"view-btn\" ?selected=${this.viewMode === 'tree'} title=\"Tree view\" @click=${() => this._setViewMode('tree')}>account_tree</md-icon>\n <md-icon class=\"view-btn\" ?selected=${this.viewMode === 'group'} title=\"Group by type\" @click=${() => this._setViewMode('group')}>category</md-icon>\n <span class=\"spacer\"></span>\n ${this.viewMode === 'group'\n ? html`\n <md-icon title=\"Expand all groups\" @click=${this._expandGroups}>unfold_more</md-icon>\n <md-icon title=\"Collapse to group level\" @click=${this._collapseGroups}>unfold_less</md-icon>\n `\n : nothing}\n </div>\n\n <div result>${this._renderResult()}</div>\n `\n }\n\n private _renderResult(): TemplateResult {\n if (!this.scene) return html``\n\n const terms = parseQuery(this.searchText)\n\n if (this.viewMode === 'group') {\n return renderInspectorGroupView(this.controller.index, {\n selected: this.controller.selected,\n terms,\n isGroupExpanded: (type: string) => this.controller.isGroupExpanded(type),\n isTypeDimmed: (type: string) => this.controller.isTypeDimmed(type)\n })\n }\n\n return renderInspectorTree(this.scene.root, {\n selected: this.controller.selected,\n terms,\n count: countMatches(this.scene.root, terms),\n isExtended: (c: Component) => this.controller.isExtended(c),\n nodeHandleClass: (c: Component) => this.controller.nodeHandleClass(c)\n })\n }\n\n private _setViewMode(mode: InspectorViewMode) {\n if (this.viewMode === mode) return\n this.viewMode = mode\n writeStoredViewMode(mode) // 세션 간 유지\n }\n\n private _collapseGroups = () => {\n this.controller.collapseAllGroups()\n this.requestUpdate()\n }\n\n private _expandGroups = () => {\n const index = this.controller.index\n if (index) this.controller.expandAllGroups([...index.byType.keys()])\n this.requestUpdate()\n }\n\n firstUpdated() {\n dispatchEvent(new Event('resize'))\n\n this.renderRoot.addEventListener('click', this._onclick.bind(this) as EventListener)\n this.renderRoot.addEventListener('dblclick', this._ondblclick.bind(this) as EventListener)\n }\n\n /** 외부에서 강제 재렌더용(기존 공개 메쏘드 유지). */\n refresh() {\n this.requestUpdate()\n }\n\n updated(change: PropertyValues<this>) {\n if (change.has('scene')) {\n this.controller.bind(this.scene, () => this.requestUpdate())\n }\n this._maybeReveal()\n }\n\n /**\n * 선택이 바뀌면(캔버스 등) 해당 항목을 리스트에 노출한다 — 그룹 뷰면 그룹을 펼친 뒤 스크롤.\n * scrollIntoView({block:'nearest'}) 라 이미 보이는 경우(인스펙터 클릭 등)엔 사실상 no-op.\n */\n private _maybeReveal() {\n const selected = this.controller.selected\n const changed =\n selected.length !== this._lastSelected.length || selected.some((c, i) => c !== this._lastSelected[i])\n if (!changed) return\n\n if (selected.length === 0) {\n this._lastSelected = selected\n return\n }\n\n if (this.viewMode === 'group') {\n const types = [...new Set(selected.map(c => c.state.type))]\n if (types.some(t => !this.controller.isGroupExpanded(t))) {\n this.controller.expandAllGroups(types)\n return // _lastSelected 미갱신 → 다음 updated 에서 changed 유지 → 그때 스크롤\n }\n }\n\n this._lastSelected = selected\n this.updateComplete.then(() => {\n const el = this.renderRoot.querySelector('.component[selected]') as HTMLElement | null\n el?.scrollIntoView({ block: 'nearest' })\n })\n }\n\n /** 이벤트 타겟에서 상위로 올라가며 `.component` 를 가진 요소를 찾는다. */\n private _resolveComponent(e: MouseEvent): Component | undefined {\n let targetElement = e.target as HTMLElement & { component: Component }\n let component: Component | undefined\n\n while (!component && targetElement) {\n component = targetElement.component\n\n if (component) break\n\n targetElement = targetElement.parentNode as HTMLElement & { component: Component }\n\n if (!targetElement || targetElement === this.renderRoot) break\n }\n\n return component\n }\n\n _onclick(e: MouseEvent) {\n e.stopPropagation()\n\n const target = e.target as HTMLElement\n\n // 그룹 dim-out 아이콘 클릭 → 그룹(타입) 포커스 dim 토글 (헤더 펼침보다 먼저 처리)\n if (target.classList.contains('group-dim')) {\n const type = target.closest?.('[data-group-type]')?.getAttribute('data-group-type')\n if (type != null) this.controller.toggleTypeDim(type)\n this.requestUpdate()\n return\n }\n\n // 타입 그룹 헤더 클릭 → 그룹 펼침/접힘 (멤버 행은 헤더의 자손이 아니므로 여기 안 걸림)\n const header = target.closest?.('[data-group-type]') as HTMLElement | null\n if (header) {\n const type = header.getAttribute('data-group-type')\n if (type != null) this.controller.toggleGroup(type)\n this.requestUpdate()\n return\n }\n\n const classList = (e.target as HTMLElement).classList\n const component = this._resolveComponent(e)\n\n if (component) {\n if (classList.contains('eye')) {\n this.controller.toggleHidden(component)\n } else if (classList.contains('lock')) {\n this.controller.toggleLocked(component)\n } else if (classList.contains('extended') || classList.contains('collapsed')) {\n this.controller.toggleExtended(component)\n }\n\n // shift = 범위(볼륨) 선택/블록 해제, ctrl/cmd = 개별 토글, 그 외 = 단일 선택\n if (e.shiftKey) {\n this.controller.selectRange(component, parseQuery(this.searchText))\n } else {\n this.controller.select(component, e.metaKey || e.ctrlKey)\n }\n } else {\n if (classList.contains('inspector')) {\n this.show = !this.show\n this.style.height = this.show ? '100%' : ''\n }\n }\n\n this.requestUpdate()\n }\n\n _ondblclick(e: MouseEvent) {\n e.stopPropagation()\n\n const component = this._resolveComponent(e)\n\n if (component) {\n if (component.isContainer()) {\n this.controller.toggleExtended(component)\n }\n // 캔버스를 해당 컴포넌트로 센터 이동(줌).\n this.controller.centerTo(component)\n }\n\n this.requestUpdate()\n }\n}\n"]}
1
+ {"version":3,"file":"inspector.js","sourceRoot":"","sources":["../../../../src/property-panel/inspector/inspector.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * SceneInspector — 속성패널 inspector 탭 호스트.\n *\n * 얇은 호스트: 검색 상태 + 클릭 위임만 담당하고, scene 상호작용은 InspectorController,\n * 렌더는 뷰 렌더러(현재 트리)로 위임한다. (아키텍처 리팩터 P0 — 동작은 기존과 동일)\n */\nimport '@operato/input/ox-input-search.js'\n\nimport { css, html, LitElement, nothing, PropertyValues, TemplateResult } from 'lit'\nimport { property, state } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\n\nimport { Component, Scene } from '@hatiolab/things-scene'\nimport { InspectorStyles } from '@operato/styles/inspector-styles.js'\n\nimport { parseQuery } from './inspector-model.js'\nimport { InspectorController } from './inspector-controller.js'\nimport { countMatches, renderInspectorTree } from './inspector-tree-view.js'\nimport { renderInspectorGroupView } from './inspector-group-view.js'\n\ntype InspectorViewMode = 'tree' | 'group'\n\n/** 뷰 모드 선호를 세션 간 유지(localStorage). 전역 사용자 선호. */\nconst VIEW_MODE_STORAGE_KEY = 'scene-inspector.view-mode'\n\nfunction readStoredViewMode(): InspectorViewMode {\n try {\n const v = localStorage.getItem(VIEW_MODE_STORAGE_KEY)\n return v === 'group' || v === 'tree' ? v : 'tree'\n } catch {\n return 'tree'\n }\n}\n\nfunction writeStoredViewMode(mode: InspectorViewMode): void {\n try {\n localStorage.setItem(VIEW_MODE_STORAGE_KEY, mode)\n } catch {\n /* localStorage 미가용(SSR/프라이빗 등) — 무시 */\n }\n}\n\nexport class SceneInspector extends LitElement {\n static styles = [\n InspectorStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n\n color: var(--scene-inspector-color);\n }\n\n ox-input-search {\n --input-search-padding: 3px;\n --input-search-focus-border-bottom: none;\n --input-search-font: normal 16px var(--theme-font);\n\n border-radius: 999px;\n box-sizing: border-box;\n border: 1px solid var(--md-sys-color-on-secondary-container);\n padding: 0 10px;\n margin: 5px;\n }\n\n div[result] {\n flex: 1;\n\n display: flex;\n flex-direction: column;\n\n overflow-y: auto;\n }\n\n .component {\n display: block;\n overflow: hidden;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n font-size: 14px;\n }\n\n .component[selected] {\n background-color: var(--scene-inspector-selected-background-color);\n border-top: var(--scene-inspector-selected-border);\n border-bottom: var(--scene-inspector-selected-border);\n }\n [selected] .type {\n font-weight: bold;\n }\n\n span,\n i {\n display: inline-block;\n }\n\n span.type {\n text-overflow: ellipses;\n }\n\n span.name {\n background-color: var(--scene-inspector-name-background-color);\n border-radius: var(--border-radius);\n padding: 0 4px;\n color: #fff;\n font-size: 0.8em;\n }\n\n .eye {\n margin: 0 0 0 4px;\n vertical-align: middle;\n opacity: 0.7;\n font-size: 1em;\n width: 1em;\n color: var(--scene-inspector-eye-icon-color);\n cursor: pointer;\n }\n\n .lock {\n margin: 0;\n vertical-align: middle;\n opacity: 0.3;\n font-size: 1em;\n width: 1em;\n cursor: pointer;\n }\n .lock[locked] {\n opacity: 0.9;\n color: var(--md-sys-color-error, #d9534f);\n }\n\n pre {\n display: inline;\n /* 트리 인덴트(수직라인까지의 간격) 최소화 — depth 당 좁은 폭 */\n font-size: 0.6em;\n margin: 0;\n }\n\n /* 뷰 모드 툴바 */\n div[toolbar] {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 2px 8px 4px;\n }\n div[toolbar] .spacer {\n flex: 1;\n }\n div[toolbar] md-icon {\n cursor: pointer;\n opacity: 0.5;\n font-size: 18px;\n border-radius: 4px;\n padding: 2px;\n }\n div[toolbar] md-icon:hover {\n opacity: 0.9;\n }\n div[toolbar] md-icon[selected] {\n opacity: 1;\n color: var(--md-sys-color-primary, #4a9eff);\n background: rgba(0, 0, 0, 0.05);\n }\n\n /* 타입 그룹 뷰 */\n .group-header {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n padding: 2px 0;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n }\n .group-header:hover {\n background: rgba(0, 0, 0, 0.03);\n }\n .group-count {\n margin-left: auto;\n font-size: 0.72em;\n font-weight: normal;\n background: var(--scene-inspector-name-background-color);\n color: #fff;\n border-radius: 999px;\n padding: 0 6px;\n }\n .group-hidden,\n .group-dim {\n font-size: 1em;\n opacity: 0.35;\n cursor: pointer;\n }\n .group-hidden {\n margin: 0 2px 0 6px;\n }\n .group-dim {\n margin: 0 4px 0 2px;\n }\n .group-hidden:hover,\n .group-dim:hover {\n opacity: 0.8;\n }\n .group-header[hidden-group] .group-hidden,\n .group-header[dimmed] .group-dim {\n opacity: 0.95;\n color: var(--md-sys-color-primary, #4a9eff);\n }\n .group-header[dimmed] .group-type {\n opacity: 0.45;\n }\n .group-member {\n padding-left: 8px;\n }\n .muted {\n opacity: 0.4;\n font-style: italic;\n }\n /* 검색 매치 하이라이트 */\n mark {\n background: var(--md-sys-color-tertiary-container, #ffe082);\n color: inherit;\n border-radius: 2px;\n padding: 0 1px;\n }\n `\n ]\n\n @property({ type: Object }) scene?: Scene\n\n @state() private searchText: string = ''\n @state() private viewMode: InspectorViewMode = readStoredViewMode()\n\n private controller = new InspectorController()\n private show: boolean = false\n /** reveal(스크롤) 판정용 직전 선택. */\n private _lastSelected: Component[] = []\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.controller.unbind()\n }\n\n render() {\n return html`\n <ox-input-search\n .placeholder=${i18next.t('text.search with type, id or tag')}\n autofocus\n @change=${(e: Event) => (this.searchText = ((e.target as HTMLInputElement).value || '').toLowerCase())}\n ></ox-input-search>\n\n <div toolbar>\n <md-icon class=\"view-btn\" ?selected=${this.viewMode === 'tree'} title=\"Tree view\" @click=${() => this._setViewMode('tree')}>account_tree</md-icon>\n <md-icon class=\"view-btn\" ?selected=${this.viewMode === 'group'} title=\"Group by type\" @click=${() => this._setViewMode('group')}>category</md-icon>\n <span class=\"spacer\"></span>\n ${this.viewMode === 'group'\n ? html`\n <md-icon title=\"Expand all groups\" @click=${this._expandGroups}>unfold_more</md-icon>\n <md-icon title=\"Collapse to group level\" @click=${this._collapseGroups}>unfold_less</md-icon>\n `\n : nothing}\n </div>\n\n <div result>${this._renderResult()}</div>\n `\n }\n\n private _renderResult(): TemplateResult {\n if (!this.scene) return html``\n\n const terms = parseQuery(this.searchText)\n\n if (this.viewMode === 'group') {\n return renderInspectorGroupView(this.controller.index, {\n selected: this.controller.selected,\n terms,\n isGroupExpanded: (type: string) => this.controller.isGroupExpanded(type),\n isTypeDimmed: (type: string) => this.controller.isTypeDimmed(type),\n isTypeHidden: (type: string) => this.controller.isTypeHidden(type)\n })\n }\n\n return renderInspectorTree(this.scene.root, {\n selected: this.controller.selected,\n terms,\n count: countMatches(this.scene.root, terms),\n isExtended: (c: Component) => this.controller.isExtended(c),\n nodeHandleClass: (c: Component) => this.controller.nodeHandleClass(c)\n })\n }\n\n private _setViewMode(mode: InspectorViewMode) {\n if (this.viewMode === mode) return\n this.viewMode = mode\n writeStoredViewMode(mode) // 세션 간 유지\n }\n\n private _collapseGroups = () => {\n this.controller.collapseAllGroups()\n this.requestUpdate()\n }\n\n private _expandGroups = () => {\n const index = this.controller.index\n if (index) this.controller.expandAllGroups([...index.byType.keys()])\n this.requestUpdate()\n }\n\n firstUpdated() {\n dispatchEvent(new Event('resize'))\n\n this.renderRoot.addEventListener('click', this._onclick.bind(this) as EventListener)\n this.renderRoot.addEventListener('dblclick', this._ondblclick.bind(this) as EventListener)\n }\n\n /** 외부에서 강제 재렌더용(기존 공개 메쏘드 유지). */\n refresh() {\n this.requestUpdate()\n }\n\n updated(change: PropertyValues<this>) {\n if (change.has('scene')) {\n this.controller.bind(this.scene, () => this.requestUpdate())\n }\n this._maybeReveal()\n }\n\n /**\n * 선택이 바뀌면(캔버스 등) 해당 항목을 리스트에 노출한다 — 그룹 뷰면 그룹을 펼친 뒤 스크롤.\n * scrollIntoView({block:'nearest'}) 라 이미 보이는 경우(인스펙터 클릭 등)엔 사실상 no-op.\n */\n private _maybeReveal() {\n const selected = this.controller.selected\n const changed =\n selected.length !== this._lastSelected.length || selected.some((c, i) => c !== this._lastSelected[i])\n if (!changed) return\n\n if (selected.length === 0) {\n this._lastSelected = selected\n return\n }\n\n if (this.viewMode === 'group') {\n const types = [...new Set(selected.map(c => c.state.type))]\n if (types.some(t => !this.controller.isGroupExpanded(t))) {\n this.controller.expandAllGroups(types)\n return // _lastSelected 미갱신 → 다음 updated 에서 changed 유지 → 그때 스크롤\n }\n }\n\n this._lastSelected = selected\n this.updateComplete.then(() => {\n const el = this.renderRoot.querySelector('.component[selected]') as HTMLElement | null\n el?.scrollIntoView({ block: 'nearest' })\n })\n }\n\n /** 이벤트 타겟에서 상위로 올라가며 `.component` 를 가진 요소를 찾는다. */\n private _resolveComponent(e: MouseEvent): Component | undefined {\n let targetElement = e.target as HTMLElement & { component: Component }\n let component: Component | undefined\n\n while (!component && targetElement) {\n component = targetElement.component\n\n if (component) break\n\n targetElement = targetElement.parentNode as HTMLElement & { component: Component }\n\n if (!targetElement || targetElement === this.renderRoot) break\n }\n\n return component\n }\n\n _onclick(e: MouseEvent) {\n e.stopPropagation()\n\n const target = e.target as HTMLElement\n\n // 그룹 hidden 아이콘 클릭 → 그룹(타입) 전원 hidden 토글 (헤더 펼침보다 먼저 처리)\n if (target.classList.contains('group-hidden')) {\n const type = target.closest?.('[data-group-type]')?.getAttribute('data-group-type')\n if (type != null) this.controller.toggleTypeHidden(type)\n this.requestUpdate()\n return\n }\n\n // 그룹 dim-out 아이콘 클릭 → 그룹(타입) 포커스 dim 토글 (헤더 펼침보다 먼저 처리)\n if (target.classList.contains('group-dim')) {\n const type = target.closest?.('[data-group-type]')?.getAttribute('data-group-type')\n if (type != null) this.controller.toggleTypeDim(type)\n this.requestUpdate()\n return\n }\n\n // 타입 그룹 헤더 클릭 → 그룹 펼침/접힘 (멤버 행은 헤더의 자손이 아니므로 여기 안 걸림)\n const header = target.closest?.('[data-group-type]') as HTMLElement | null\n if (header) {\n const type = header.getAttribute('data-group-type')\n if (type != null) this.controller.toggleGroup(type)\n this.requestUpdate()\n return\n }\n\n const classList = (e.target as HTMLElement).classList\n const component = this._resolveComponent(e)\n\n if (component) {\n if (classList.contains('eye')) {\n this.controller.toggleHidden(component)\n } else if (classList.contains('lock')) {\n this.controller.toggleLocked(component)\n } else if (classList.contains('extended') || classList.contains('collapsed')) {\n this.controller.toggleExtended(component)\n }\n\n // shift = 범위(볼륨) 선택/블록 해제, ctrl/cmd = 개별 토글, 그 외 = 단일 선택\n if (e.shiftKey) {\n this.controller.selectRange(component, parseQuery(this.searchText))\n } else {\n this.controller.select(component, e.metaKey || e.ctrlKey)\n }\n } else {\n if (classList.contains('inspector')) {\n this.show = !this.show\n this.style.height = this.show ? '100%' : ''\n }\n }\n\n this.requestUpdate()\n }\n\n _ondblclick(e: MouseEvent) {\n e.stopPropagation()\n\n const component = this._resolveComponent(e)\n\n if (component) {\n if (component.isContainer()) {\n this.controller.toggleExtended(component)\n }\n // 캔버스를 해당 컴포넌트로 센터 이동(줌).\n this.controller.centerTo(component)\n }\n\n this.requestUpdate()\n }\n}\n"]}