@fps-games/editor 0.1.1-beta.1 → 0.1.1-beta.10

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 (162) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/local-editor-harness.d.ts +113 -6
  5. package/dist/local-editor-harness.d.ts.map +1 -1
  6. package/dist/local-editor-harness.js +1253 -55
  7. package/dist/local-editor-harness.js.map +1 -1
  8. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +33 -0
  9. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts.map +1 -0
  10. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js +231 -0
  11. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js.map +1 -0
  12. package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts +3 -0
  13. package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts.map +1 -1
  14. package/node_modules/@fps-games/editor-babylon/dist/editor-world.js +8 -0
  15. package/node_modules/@fps-games/editor-babylon/dist/editor-world.js.map +1 -1
  16. package/node_modules/@fps-games/editor-babylon/dist/index.d.ts +5 -0
  17. package/node_modules/@fps-games/editor-babylon/dist/index.d.ts.map +1 -1
  18. package/node_modules/@fps-games/editor-babylon/dist/index.js +5 -0
  19. package/node_modules/@fps-games/editor-babylon/dist/index.js.map +1 -1
  20. package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts +40 -0
  21. package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts.map +1 -0
  22. package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js +237 -0
  23. package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js.map +1 -0
  24. package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.d.ts +1 -1
  25. package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.d.ts.map +1 -1
  26. package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.js +15 -50
  27. package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.js.map +1 -1
  28. package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +27 -0
  29. package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
  30. package/node_modules/@fps-games/editor-babylon/dist/projection.js +368 -14
  31. package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
  32. package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.d.ts +25 -0
  33. package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.d.ts.map +1 -0
  34. package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.js +176 -0
  35. package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.js.map +1 -0
  36. package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.d.ts +18 -0
  37. package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.d.ts.map +1 -1
  38. package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.js +142 -0
  39. package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.js.map +1 -1
  40. package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.d.ts +2 -0
  41. package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.d.ts.map +1 -1
  42. package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.js +6 -0
  43. package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.js.map +1 -1
  44. package/node_modules/@fps-games/editor-babylon/dist/scene-view-measurement-controller.d.ts +20 -0
  45. package/node_modules/@fps-games/editor-babylon/dist/scene-view-measurement-controller.d.ts.map +1 -0
  46. package/node_modules/@fps-games/editor-babylon/dist/scene-view-measurement-controller.js +187 -0
  47. package/node_modules/@fps-games/editor-babylon/dist/scene-view-measurement-controller.js.map +1 -0
  48. package/node_modules/@fps-games/editor-babylon/dist/scene-view-spatial-overlay-controller.d.ts +20 -0
  49. package/node_modules/@fps-games/editor-babylon/dist/scene-view-spatial-overlay-controller.d.ts.map +1 -0
  50. package/node_modules/@fps-games/editor-babylon/dist/scene-view-spatial-overlay-controller.js +188 -0
  51. package/node_modules/@fps-games/editor-babylon/dist/scene-view-spatial-overlay-controller.js.map +1 -0
  52. package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.d.ts +22 -2
  53. package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.d.ts.map +1 -1
  54. package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.js +469 -90
  55. package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.js.map +1 -1
  56. package/node_modules/@fps-games/editor-babylon/dist/types.d.ts +20 -0
  57. package/node_modules/@fps-games/editor-babylon/dist/types.d.ts.map +1 -1
  58. package/node_modules/@fps-games/editor-babylon/package.json +4 -4
  59. package/node_modules/@fps-games/editor-browser/dist/index.d.ts +2 -0
  60. package/node_modules/@fps-games/editor-browser/dist/index.d.ts.map +1 -1
  61. package/node_modules/@fps-games/editor-browser/dist/index.js +1 -0
  62. package/node_modules/@fps-games/editor-browser/dist/index.js.map +1 -1
  63. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-context-menu.d.ts +3 -1
  64. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-context-menu.d.ts.map +1 -1
  65. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-context-menu.js +103 -48
  66. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-context-menu.js.map +1 -1
  67. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts +17 -1
  68. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts.map +1 -1
  69. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js +327 -33
  70. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js.map +1 -1
  71. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts +3 -1
  72. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts.map +1 -1
  73. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js +143 -17
  74. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js.map +1 -1
  75. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.js +0 -2
  76. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.js.map +1 -1
  77. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.d.ts.map +1 -1
  78. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js +27 -20
  79. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js.map +1 -1
  80. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts +10 -0
  81. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts.map +1 -0
  82. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js +253 -0
  83. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js.map +1 -0
  84. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts +8 -1
  85. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts.map +1 -1
  86. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +403 -110
  87. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js.map +1 -1
  88. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.d.ts +13 -3
  89. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.d.ts.map +1 -1
  90. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.js +107 -14
  91. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.js.map +1 -1
  92. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.d.ts +20 -3
  93. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.d.ts.map +1 -1
  94. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js +127 -16
  95. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js.map +1 -1
  96. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.d.ts.map +1 -1
  97. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.js +13 -4
  98. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.js.map +1 -1
  99. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-theme.d.ts +4 -0
  100. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-theme.d.ts.map +1 -1
  101. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-theme.js +114 -9
  102. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-theme.js.map +1 -1
  103. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.d.ts +11 -0
  104. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.d.ts.map +1 -0
  105. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.js +148 -0
  106. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.js.map +1 -0
  107. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +141 -3
  108. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
  109. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts +35 -0
  110. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts.map +1 -1
  111. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js +376 -2
  112. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js.map +1 -1
  113. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts +11 -6
  114. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts.map +1 -1
  115. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js +1617 -110
  116. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
  117. package/node_modules/@fps-games/editor-browser/package.json +4 -1
  118. package/node_modules/@fps-games/editor-core/dist/editor-session.d.ts +4 -1
  119. package/node_modules/@fps-games/editor-core/dist/editor-session.d.ts.map +1 -1
  120. package/node_modules/@fps-games/editor-core/dist/editor-session.js +13 -3
  121. package/node_modules/@fps-games/editor-core/dist/editor-session.js.map +1 -1
  122. package/node_modules/@fps-games/editor-core/dist/index.d.ts +3 -0
  123. package/node_modules/@fps-games/editor-core/dist/index.d.ts.map +1 -1
  124. package/node_modules/@fps-games/editor-core/dist/index.js +3 -0
  125. package/node_modules/@fps-games/editor-core/dist/index.js.map +1 -1
  126. package/node_modules/@fps-games/editor-core/dist/inspector.d.ts +6 -1
  127. package/node_modules/@fps-games/editor-core/dist/inspector.d.ts.map +1 -1
  128. package/node_modules/@fps-games/editor-core/dist/inspector.js +6 -1
  129. package/node_modules/@fps-games/editor-core/dist/inspector.js.map +1 -1
  130. package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts +7 -0
  131. package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts.map +1 -1
  132. package/node_modules/@fps-games/editor-core/dist/scene-graph.js.map +1 -1
  133. package/node_modules/@fps-games/editor-core/dist/scene-view-input.d.ts +1 -1
  134. package/node_modules/@fps-games/editor-core/dist/scene-view-input.d.ts.map +1 -1
  135. package/node_modules/@fps-games/editor-core/dist/selection-strategy.d.ts +12 -0
  136. package/node_modules/@fps-games/editor-core/dist/selection-strategy.d.ts.map +1 -0
  137. package/node_modules/@fps-games/editor-core/dist/selection-strategy.js +52 -0
  138. package/node_modules/@fps-games/editor-core/dist/selection-strategy.js.map +1 -0
  139. package/node_modules/@fps-games/editor-core/dist/transform-gizmo.d.ts +78 -2
  140. package/node_modules/@fps-games/editor-core/dist/transform-gizmo.d.ts.map +1 -1
  141. package/node_modules/@fps-games/editor-core/dist/transform-gizmo.js +81 -1
  142. package/node_modules/@fps-games/editor-core/dist/transform-gizmo.js.map +1 -1
  143. package/node_modules/@fps-games/editor-core/dist/transform-operations.d.ts +32 -0
  144. package/node_modules/@fps-games/editor-core/dist/transform-operations.d.ts.map +1 -0
  145. package/node_modules/@fps-games/editor-core/dist/transform-operations.js +125 -0
  146. package/node_modules/@fps-games/editor-core/dist/transform-operations.js.map +1 -0
  147. package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts +94 -0
  148. package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts.map +1 -0
  149. package/node_modules/@fps-games/editor-core/dist/viewport-tools.js +58 -0
  150. package/node_modules/@fps-games/editor-core/dist/viewport-tools.js.map +1 -0
  151. package/node_modules/@fps-games/editor-core/package.json +2 -2
  152. package/node_modules/@fps-games/editor-forge-play/dist/index.d.ts +1 -1
  153. package/node_modules/@fps-games/editor-forge-play/dist/index.d.ts.map +1 -1
  154. package/node_modules/@fps-games/editor-forge-play/dist/index.js +5 -1
  155. package/node_modules/@fps-games/editor-forge-play/dist/index.js.map +1 -1
  156. package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
  157. package/node_modules/@fps-games/editor-protocol/dist/index.d.ts +4 -0
  158. package/node_modules/@fps-games/editor-protocol/dist/index.d.ts.map +1 -1
  159. package/node_modules/@fps-games/editor-protocol/dist/index.js +4 -0
  160. package/node_modules/@fps-games/editor-protocol/dist/index.js.map +1 -1
  161. package/node_modules/@fps-games/editor-protocol/package.json +1 -1
  162. package/package.json +6 -6
@@ -1,12 +1,17 @@
1
- import { createAssetList, createDockTab, createPanelHeader, createPropertyRow, createToolbarButton, createTreeView, createTreeViewItem, } from './local-editor-ui-primitives';
1
+ import { createLocalEditorIcon } from './local-editor-ui-icons';
2
+ import { createBadge, createAssetList, createDockTab, createEditorInputStyle, createEmptyState, createListItemBlock, createPanelHeader, createPropertyRow, createToolbarButton, createTreeView, createTreeViewItem, } from './local-editor-ui-primitives';
2
3
  import { clearElement, toTitle } from './local-editor-ui-shared';
4
+ const DOCK_TAB_ICONS = {
5
+ assets: 'asset',
6
+ history: 'history',
7
+ };
3
8
  export function renderHierarchyPanel(doc, panel, state, rename, drop) {
4
9
  clearElement(panel);
5
- const createGroupButton = createToolbarButton(doc, '+ Group');
10
+ const createGroupButton = createToolbarButton(doc, '+ Empty', 'object');
6
11
  createGroupButton.dataset.editorHierarchyCreateGroup = 'true';
7
12
  createGroupButton.style.padding = '3px 7px';
8
13
  createGroupButton.style.fontSize = '11px';
9
- panel.appendChild(createPanelHeader(doc, 'Graph', [createGroupButton]));
14
+ panel.appendChild(createPanelHeader(doc, 'Hierarchy', [createGroupButton], 'hierarchy'));
10
15
  const list = createTreeView(doc);
11
16
  for (const item of state.hierarchy) {
12
17
  const isRenaming = rename?.id === item.id;
@@ -35,7 +40,7 @@ export function renderHierarchyPanel(doc, panel, state, rename, drop) {
35
40
  'border:1px solid var(--fps-editor-accent-strong)',
36
41
  'border-radius:2px',
37
42
  'background:var(--fps-editor-field)',
38
- 'color:#fff',
43
+ 'color:var(--fps-editor-text-strong)',
39
44
  'font-size:12px',
40
45
  'font-weight:700',
41
46
  'padding:2px 5px',
@@ -80,7 +85,7 @@ export function renderWorkbenchBottomDockPanel(doc, panel, state, activeTab, pan
80
85
  { id: 'history', title: '历史', area: 'bottom' },
81
86
  ];
82
87
  for (const tabPanel of dockTabs) {
83
- const button = createDockTab(doc, tabPanel.title, activeTab === tabPanel.id);
88
+ const button = createDockTab(doc, tabPanel.title, activeTab === tabPanel.id, DOCK_TAB_ICONS[tabPanel.id]);
84
89
  button.dataset.editorDockTab = tabPanel.id;
85
90
  tabHeader.appendChild(button);
86
91
  }
@@ -107,30 +112,29 @@ function appendDockTabs(doc, panel, activeTab) {
107
112
  history: '历史',
108
113
  };
109
114
  for (const tab of ['assets', 'history']) {
110
- const button = doc.createElement('button');
111
- button.type = 'button';
112
- button.dataset.editorDockTab = tab;
113
- button.textContent = labels[tab];
114
115
  const active = activeTab === tab;
115
- button.style.cssText = [
116
- 'border:0',
117
- 'border-bottom:2px solid transparent',
118
- `border-bottom-color:${active ? '#58a6ff' : 'transparent'}`,
119
- 'background:transparent',
120
- `color:${active ? 'var(--fps-editor-text-strong)' : 'var(--fps-editor-muted)'}`,
121
- 'font-size:12px',
122
- 'font-weight:900',
123
- 'padding:0 8px 8px',
124
- 'cursor:pointer',
125
- ].join(';');
116
+ const button = createDockTab(doc, labels[tab], active, DOCK_TAB_ICONS[tab]);
117
+ button.dataset.editorDockTab = tab;
118
+ button.style.border = '0';
119
+ button.style.borderBottom = `2px solid ${active ? 'var(--fps-editor-accent-strong)' : 'transparent'}`;
120
+ button.style.background = 'transparent';
121
+ button.style.color = active ? 'var(--fps-editor-text-strong)' : 'var(--fps-editor-muted)';
122
+ button.style.padding = '0 8px 8px';
126
123
  tabs.appendChild(button);
127
124
  }
128
125
  panel.appendChild(tabs);
129
126
  }
127
+ function createHeadingLabel(doc, text) {
128
+ const label = doc.createElement('span');
129
+ label.textContent = text;
130
+ label.style.cssText = 'min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
131
+ return label;
132
+ }
130
133
  function renderAssetBrowserContent(doc, panel, state, variant = 'list') {
131
134
  const title = doc.createElement('h2');
132
- title.textContent = '资产浏览器';
133
- title.style.cssText = 'font-size:13px;margin:0 0 8px;font-weight:800;color:var(--fps-editor-text-strong)';
135
+ title.style.cssText = 'display:flex;align-items:center;gap:6px;font-size:13px;margin:0 0 8px;font-weight:800;color:var(--fps-editor-text-strong)';
136
+ title.appendChild(createLocalEditorIcon(doc, 'asset'));
137
+ title.appendChild(createHeadingLabel(doc, '资产浏览器'));
134
138
  panel.appendChild(title);
135
139
  const filter = doc.createElement('input');
136
140
  filter.type = 'search';
@@ -177,19 +181,27 @@ function renderAssetBrowserContent(doc, panel, state, variant = 'list') {
177
181
  'padding:6px 8px',
178
182
  'border:1px solid var(--fps-editor-border)',
179
183
  'border-radius:3px',
180
- 'background:#243426',
184
+ 'background:var(--fps-editor-asset-card-bg)',
181
185
  'color:var(--fps-editor-text)',
186
+ 'display:flex',
187
+ 'align-items:center',
188
+ 'gap:8px',
182
189
  'font-size:12px',
183
190
  'cursor:pointer',
184
191
  ].join(';');
192
+ const icon = createLocalEditorIcon(doc, 'asset', { size: 16 });
193
+ const body = doc.createElement('div');
194
+ body.style.cssText = 'min-width:0;flex:1';
185
195
  const label = doc.createElement('div');
186
196
  label.textContent = asset.label;
187
197
  label.style.cssText = 'font-weight:800;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
188
198
  const meta = doc.createElement('div');
189
199
  meta.textContent = asset.meta ?? asset.id;
190
200
  meta.style.cssText = 'color:var(--fps-editor-muted);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
191
- button.appendChild(label);
192
- button.appendChild(meta);
201
+ body.appendChild(label);
202
+ body.appendChild(meta);
203
+ button.appendChild(icon);
204
+ button.appendChild(body);
193
205
  list.appendChild(button);
194
206
  }
195
207
  panel.appendChild(list);
@@ -197,36 +209,21 @@ function renderAssetBrowserContent(doc, panel, state, variant = 'list') {
197
209
  function renderHistoryPanel(doc, panel, history) {
198
210
  const title = doc.createElement('h2');
199
211
  const entries = history?.entries ?? [];
200
- title.textContent = `历史记录 (${entries.length})`;
201
- title.style.cssText = 'font-size:13px;margin:0 0 8px;font-weight:800;color:var(--fps-editor-text-strong)';
212
+ title.style.cssText = 'display:flex;align-items:center;gap:6px;font-size:13px;margin:0 0 8px;font-weight:800;color:var(--fps-editor-text-strong)';
213
+ title.appendChild(createLocalEditorIcon(doc, 'history'));
214
+ title.appendChild(createHeadingLabel(doc, `历史记录 (${entries.length})`));
202
215
  panel.appendChild(title);
203
216
  appendHistoryEntries(doc, panel, entries);
204
217
  }
205
218
  function appendHistoryEntries(doc, panel, entries) {
206
219
  if (entries.length === 0) {
207
- const empty = doc.createElement('div');
208
- empty.textContent = '暂无文档修改。';
209
- empty.style.cssText = [
210
- 'padding:8px',
211
- 'border:1px solid var(--fps-editor-border-soft)',
212
- 'border-radius:3px',
213
- 'background:var(--fps-editor-field)',
214
- 'color:var(--fps-editor-muted)',
215
- 'font-size:11px',
216
- ].join(';');
217
- panel.appendChild(empty);
220
+ panel.appendChild(createEmptyState(doc, '暂无文档修改。'));
218
221
  return;
219
222
  }
220
223
  const list = doc.createElement('div');
221
224
  list.style.cssText = 'display:flex;flex-direction:column;gap:4px';
222
225
  for (const entry of entries) {
223
- const item = doc.createElement('div');
224
- item.style.cssText = [
225
- 'padding:7px 8px',
226
- 'border:1px solid var(--fps-editor-border-soft)',
227
- 'border-radius:3px',
228
- 'background:var(--fps-editor-panel-soft)',
229
- ].join(';');
226
+ const item = createListItemBlock(doc);
230
227
  const label = doc.createElement('div');
231
228
  label.textContent = entry.label;
232
229
  label.style.cssText = 'font-size:12px;font-weight:900;color:var(--fps-editor-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
@@ -259,10 +256,12 @@ function formatHistoryTimestamp(createdAt) {
259
256
  ].join('');
260
257
  }
261
258
  export function renderInspectorPanel(doc, panel, state, filter = '', options = {}) {
259
+ const renderSnapshot = captureInspectorPanelRenderSnapshot(doc, panel);
262
260
  clearElement(panel);
263
261
  const title = doc.createElement('h2');
264
- title.textContent = 'Inspector';
265
- title.style.cssText = 'font-size:13px;margin:-8px -8px 8px;padding:7px 8px;border-bottom:1px solid var(--fps-editor-divider);background:var(--fps-editor-chrome-dark);font-weight:800;color:var(--fps-editor-text-strong)';
262
+ title.style.cssText = 'display:flex;align-items:center;gap:6px;font-size:13px;margin:-8px -8px 8px;padding:7px 8px;border-bottom:1px solid var(--fps-editor-divider);background:var(--fps-editor-chrome-dark);font-weight:800;color:var(--fps-editor-text-strong)';
263
+ title.appendChild(createLocalEditorIcon(doc, 'inspector'));
264
+ title.appendChild(createHeadingLabel(doc, 'Inspector'));
266
265
  panel.appendChild(title);
267
266
  const search = createInspectorSearchInput(doc, filter);
268
267
  panel.appendChild(search);
@@ -276,23 +275,45 @@ export function renderInspectorPanel(doc, panel, state, filter = '', options = {
276
275
  empty.textContent = '请从层级树或 Scene View 中选择一个 GameObject。';
277
276
  empty.style.cssText = 'color:var(--fps-editor-muted);line-height:1.45';
278
277
  panel.appendChild(empty);
278
+ restoreInspectorPanelRenderSnapshot(panel, renderSnapshot);
279
279
  return;
280
280
  }
281
281
  const controlRegistry = createLocalEditorBrowserInspectorControlRegistry(options.controls, options.controlConflict);
282
282
  appendInspectorSummary(doc, panel, inspectorObject, selectionCount);
283
283
  const visibleSections = filterInspectorSections(inspectorObject.sections.filter(section => section.placement !== 'summary'), filter);
284
284
  if (visibleSections.length === 0) {
285
- if (!filter.trim() && inspectorObject.sections.length === 0)
285
+ if (!filter.trim() && inspectorObject.sections.length === 0) {
286
+ restoreInspectorPanelRenderSnapshot(panel, renderSnapshot);
286
287
  return;
288
+ }
287
289
  const empty = doc.createElement('div');
288
290
  empty.textContent = '没有匹配的 Inspector 字段。';
289
291
  empty.style.cssText = 'color:var(--fps-editor-muted);line-height:1.45';
290
292
  panel.appendChild(empty);
293
+ restoreInspectorPanelRenderSnapshot(panel, renderSnapshot);
291
294
  return;
292
295
  }
293
296
  for (const section of visibleSections) {
294
297
  panel.appendChild(createInspectorSectionBlock(doc, inspectorObject, section, controlRegistry));
295
298
  }
299
+ restoreInspectorPanelRenderSnapshot(panel, renderSnapshot);
300
+ }
301
+ function captureInspectorPanelRenderSnapshot(doc, panel) {
302
+ const activeElement = doc.activeElement;
303
+ const elementConstructor = doc.defaultView?.HTMLElement;
304
+ return {
305
+ preserveScroll: !!activeElement
306
+ && (elementConstructor ? activeElement instanceof elementConstructor : activeElement instanceof HTMLElement)
307
+ && panel.contains(activeElement),
308
+ scrollTop: panel.scrollTop,
309
+ scrollLeft: panel.scrollLeft,
310
+ };
311
+ }
312
+ function restoreInspectorPanelRenderSnapshot(panel, snapshot) {
313
+ if (!snapshot.preserveScroll)
314
+ return;
315
+ panel.scrollTop = snapshot.scrollTop;
316
+ panel.scrollLeft = snapshot.scrollLeft;
296
317
  }
297
318
  function createInspectorSearchInput(doc, value) {
298
319
  const input = doc.createElement('input');
@@ -300,26 +321,24 @@ function createInspectorSearchInput(doc, value) {
300
321
  input.value = value;
301
322
  input.placeholder = 'Search...';
302
323
  input.style.cssText = [
324
+ createEditorInputStyle(),
303
325
  'width:100%',
304
326
  'height:30px',
305
- 'box-sizing:border-box',
306
327
  'margin:0 0 8px',
307
- 'border:1px solid var(--fps-editor-border)',
308
- 'border-radius:3px',
309
- 'background:var(--fps-editor-field)',
310
- 'color:var(--fps-editor-text)',
311
- 'font-size:12px',
312
- 'padding:0 8px',
313
328
  'outline:none',
314
329
  ].join(';');
315
330
  return input;
316
331
  }
317
332
  function appendInspectorSummary(doc, panel, inspectorObject, selectionCount) {
318
333
  const block = createInspectorComponentBlock(doc);
334
+ const header = doc.createElement('div');
335
+ header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0 0 8px';
319
336
  const title = doc.createElement('h3');
320
337
  title.textContent = selectionCount > 1 ? 'Selection' : 'GameObject';
321
- title.style.cssText = 'font-size:12px;margin:0 0 8px;font-weight:900;color:var(--fps-editor-text-strong)';
322
- block.appendChild(title);
338
+ title.style.cssText = 'font-size:12px;margin:0;font-weight:900;color:var(--fps-editor-text-strong)';
339
+ header.appendChild(title);
340
+ header.appendChild(createInspectorAccessBadge(doc, 'readonly'));
341
+ block.appendChild(header);
323
342
  appendReadOnlyRow(doc, block, selectionCount > 1 ? 'Selected' : 'Name', inspectorObject.label ?? inspectorObject.activeId ?? inspectorObject.targetIds[0] ?? '无');
324
343
  appendReadOnlyRow(doc, block, 'Active ID', inspectorObject.activeId ?? '无');
325
344
  if (selectionCount > 1)
@@ -350,7 +369,11 @@ function createInspectorSectionBlock(doc, inspectorObject, section, controlRegis
350
369
  const block = doc.createElement('details');
351
370
  block.dataset.editorInspectorSection = section.id;
352
371
  block.open = section.collapsedByDefault !== true;
353
- block.style.cssText = createInspectorBlockStyle();
372
+ const status = resolveLocalEditorBrowserInspectorSectionStatus(section);
373
+ const { access, effect } = status;
374
+ block.dataset.editorInspectorAccess = access;
375
+ block.dataset.editorInspectorEffect = effect;
376
+ block.style.cssText = createInspectorBlockStyle(access, effect);
354
377
  const sectionTitle = doc.createElement('summary');
355
378
  sectionTitle.style.cssText = [
356
379
  'font-size:12px',
@@ -375,11 +398,13 @@ function createInspectorSectionBlock(doc, inspectorObject, section, controlRegis
375
398
  summary.style.cssText = 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-transform:none;font-size:11px';
376
399
  meta.appendChild(summary);
377
400
  }
378
- if (section.persistence === 'runtime' || section.runtimeOnly || section.persistence === 'readonly') {
379
- const badge = doc.createElement('span');
380
- badge.textContent = section.persistence === 'runtime' || section.runtimeOnly ? 'Runtime' : 'Readonly';
381
- badge.style.cssText = 'flex:0 0 auto';
382
- meta.appendChild(badge);
401
+ const badge = createInspectorAccessBadge(doc, access);
402
+ badge.style.flex = '0 0 auto';
403
+ meta.appendChild(badge);
404
+ const effectBadge = createInspectorEffectBadge(doc, effect, section.disabledReason);
405
+ if (effectBadge && status.showEffectBadge) {
406
+ effectBadge.style.flex = '0 0 auto';
407
+ meta.appendChild(effectBadge);
383
408
  }
384
409
  sectionTitle.appendChild(meta);
385
410
  block.appendChild(sectionTitle);
@@ -396,18 +421,133 @@ function createInspectorSectionBlock(doc, inspectorObject, section, controlRegis
396
421
  }
397
422
  function createInspectorComponentBlock(doc) {
398
423
  const block = doc.createElement('div');
399
- block.style.cssText = createInspectorBlockStyle();
424
+ block.dataset.editorInspectorAccess = 'readonly';
425
+ block.dataset.editorInspectorEffect = 'active';
426
+ block.style.cssText = createInspectorBlockStyle('readonly', 'active');
400
427
  return block;
401
428
  }
402
- function createInspectorBlockStyle() {
429
+ export function resolveLocalEditorBrowserInspectorSectionStatus(section) {
430
+ const access = getInspectorSectionAccess(section);
431
+ const effect = getInspectorSectionEffect(section);
432
+ return {
433
+ access,
434
+ effect,
435
+ showEffectBadge: effect !== 'active'
436
+ && !(access === 'runtime' && effect === 'runtime')
437
+ && !(effect === 'default' && inspectorSummaryLooksDefault(section.summary)),
438
+ };
439
+ }
440
+ function createInspectorBlockStyle(_access, _effect = 'active') {
403
441
  return [
404
442
  'margin:0 0 8px',
405
443
  'padding:9px',
406
- 'border:1px solid var(--fps-editor-border)',
444
+ 'border:1px solid var(--fps-editor-inspector-section-border, var(--fps-editor-border))',
407
445
  'border-radius:3px',
408
446
  'background:var(--fps-editor-panel-soft)',
409
447
  ].join(';');
410
448
  }
449
+ function isInspectorPropertyEditable(property) {
450
+ return property.readOnly !== true
451
+ && property.persistence === 'document'
452
+ && getInspectorPropertyEffect(property) === 'active';
453
+ }
454
+ function getInspectorPropertyAccess(property) {
455
+ if (property.persistence === 'runtime')
456
+ return 'runtime';
457
+ if (getInspectorPropertyEffect(property) === 'runtime')
458
+ return 'runtime';
459
+ return isInspectorPropertyEditable(property) ? 'editable' : 'readonly';
460
+ }
461
+ function getInspectorSectionAccess(section) {
462
+ if (section.runtimeOnly || section.persistence === 'runtime' || getInspectorSectionEffect(section) === 'runtime')
463
+ return 'runtime';
464
+ const editableCount = section.properties.filter(isInspectorPropertyEditable).length;
465
+ if (editableCount === 0)
466
+ return 'readonly';
467
+ if (editableCount === section.properties.length)
468
+ return 'editable';
469
+ return 'mixed';
470
+ }
471
+ function getInspectorPropertyEffect(property) {
472
+ if (property.effect)
473
+ return property.effect;
474
+ if (property.persistence === 'runtime')
475
+ return 'runtime';
476
+ return 'active';
477
+ }
478
+ function getInspectorSectionEffect(section) {
479
+ if (section.effect)
480
+ return section.effect;
481
+ if (section.runtimeOnly || section.persistence === 'runtime')
482
+ return 'runtime';
483
+ if (section.properties.some(isInspectorPropertyEditable))
484
+ return 'active';
485
+ const effects = new Set(section.properties.map(getInspectorPropertyEffect));
486
+ if (effects.size === 1)
487
+ return [...effects][0] ?? 'active';
488
+ if (effects.has('unsupported'))
489
+ return 'unsupported';
490
+ if (effects.has('default'))
491
+ return 'default';
492
+ if (effects.has('derived'))
493
+ return 'derived';
494
+ if (effects.has('runtime'))
495
+ return 'runtime';
496
+ return 'active';
497
+ }
498
+ function createInspectorAccessBadge(doc, access) {
499
+ const labels = {
500
+ editable: 'EDITABLE',
501
+ mixed: 'MIXED',
502
+ readonly: 'READONLY',
503
+ runtime: 'RUNTIME',
504
+ };
505
+ const badge = createBadge(doc, labels[access], {
506
+ compact: true,
507
+ tone: access === 'editable' ? 'success' : access === 'mixed' || access === 'runtime' ? 'warning' : 'default',
508
+ });
509
+ badge.title = access === 'editable'
510
+ ? 'This section writes to the document.'
511
+ : access === 'mixed'
512
+ ? 'This section contains both editable and read-only fields.'
513
+ : access === 'runtime'
514
+ ? 'Runtime-only context, not saved to the document.'
515
+ : 'Read-only information.';
516
+ return badge;
517
+ }
518
+ function createInspectorEffectBadge(doc, effect, disabledReason) {
519
+ if (effect === 'active')
520
+ return null;
521
+ const labels = {
522
+ default: 'DEFAULTS',
523
+ derived: 'DERIVED',
524
+ runtime: 'RUNTIME',
525
+ unsupported: 'NO EFFECT',
526
+ };
527
+ const badge = createBadge(doc, labels[effect], {
528
+ compact: true,
529
+ tone: effect === 'unsupported' ? 'danger' : 'warning',
530
+ });
531
+ badge.title = disabledReason ?? createInspectorEffectTitle(effect);
532
+ return badge;
533
+ }
534
+ function createInspectorEffectTitle(effect) {
535
+ switch (effect) {
536
+ case 'default':
537
+ return 'Default values are being shown; edits do not affect this object until an override is configured.';
538
+ case 'derived':
539
+ return 'Derived from document or runtime state.';
540
+ case 'runtime':
541
+ return 'Runtime-only context, not saved to the document.';
542
+ case 'unsupported':
543
+ return 'This field is visible but editing is not supported for the current object.';
544
+ default:
545
+ return 'Editable field.';
546
+ }
547
+ }
548
+ function inspectorSummaryLooksDefault(summary) {
549
+ return summary?.trim().toLowerCase() === 'defaults';
550
+ }
411
551
  function groupInspectorProperties(properties) {
412
552
  const groups = [];
413
553
  const consumed = new Set();
@@ -436,7 +576,7 @@ function groupInspectorProperties(properties) {
436
576
  for (const entry of vector)
437
577
  consumed.add(entry.path);
438
578
  const parts = basePath.split('.');
439
- groups.push({ kind: 'vector', label: toTitle(parts[parts.length - 1] ?? basePath), properties: vector });
579
+ groups.push({ kind: 'vector', label: resolveInspectorVectorGroupLabel(vector, parts[parts.length - 1] ?? basePath), properties: vector });
440
580
  }
441
581
  else {
442
582
  groups.push({ kind: 'property', property });
@@ -445,6 +585,16 @@ function groupInspectorProperties(properties) {
445
585
  }
446
586
  return groups;
447
587
  }
588
+ function resolveInspectorVectorGroupLabel(properties, fallback) {
589
+ const labels = properties.map(property => {
590
+ const match = property.label.match(/^(.*)\.(x|y|z|r|g|b)$/);
591
+ return match?.[1]?.trim() ?? '';
592
+ });
593
+ const first = labels[0];
594
+ if (first && labels.every(label => label === first))
595
+ return first;
596
+ return toTitle(fallback);
597
+ }
448
598
  const builtinInspectorControlRegistrations = [
449
599
  {
450
600
  id: 'builtin.readonly',
@@ -515,7 +665,16 @@ const builtinInspectorControlRegistrations = [
515
665
  ];
516
666
  function appendInspectorPropertyRow(doc, parent, inspectorObject, property, controlRegistry) {
517
667
  const control = renderInspectorControl(doc, controlRegistry, inspectorObject, property);
518
- parent.appendChild(createPropertyRow(doc, property.label, control));
668
+ const access = getInspectorPropertyAccess(property);
669
+ const effect = getInspectorPropertyEffect(property);
670
+ const row = createPropertyRow(doc, property.label, control);
671
+ row.dataset.editorInspectorAccess = access;
672
+ row.dataset.editorInspectorEffect = effect;
673
+ row.title = createInspectorStatusTitle(access, effect, property.tooltip ?? property.disabledReason);
674
+ const label = row.firstElementChild;
675
+ if (label)
676
+ label.style.cssText = createInspectorPropertyLabelStyle(access, effect);
677
+ parent.appendChild(row);
519
678
  }
520
679
  export function createLocalEditorBrowserInspectorControlRegistry(controls = [], conflict = 'error') {
521
680
  const registrations = new Map();
@@ -544,7 +703,7 @@ function compareInspectorControlRegistrations(left, right) {
544
703
  return (left.order ?? 0) - (right.order ?? 0) || left.id.localeCompare(right.id);
545
704
  }
546
705
  function renderInspectorControl(doc, registrations, target, property) {
547
- if (property.readOnly || property.persistence !== 'document')
706
+ if (!isInspectorPropertyEditable(property))
548
707
  return createInspectorReadonlyControl(doc, target, property);
549
708
  const context = {
550
709
  doc,
@@ -576,18 +735,145 @@ function localEditorBrowserInspectorControlSupports(registration, context) {
576
735
  function appendInspectorVectorInputs(doc, parent, inspectorObject, label, properties) {
577
736
  const wrapper = doc.createElement('div');
578
737
  wrapper.style.cssText = 'margin:8px 0';
738
+ const access = getInspectorPropertyGroupAccess(properties);
739
+ const effect = getInspectorPropertyGroupEffect(properties);
740
+ wrapper.dataset.editorInspectorAccess = access;
741
+ wrapper.dataset.editorInspectorEffect = effect;
579
742
  const labelElement = doc.createElement('div');
580
- labelElement.textContent = label;
581
- labelElement.style.cssText = 'color:var(--fps-editor-muted);font-weight:800;margin-bottom:5px';
743
+ labelElement.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:5px';
744
+ const labelText = doc.createElement('span');
745
+ labelText.textContent = label;
746
+ labelText.style.cssText = createInspectorPropertyLabelStyle(access, effect);
747
+ labelElement.appendChild(labelText);
748
+ if (access !== 'editable') {
749
+ const badge = createInspectorAccessBadge(doc, access);
750
+ badge.style.flex = '0 0 auto';
751
+ labelElement.appendChild(badge);
752
+ }
753
+ const effectBadge = createInspectorEffectBadge(doc, effect, getInspectorPropertyGroupDisabledReason(properties));
754
+ if (effectBadge && !(access === 'runtime' && effect === 'runtime')) {
755
+ effectBadge.style.flex = '0 0 auto';
756
+ labelElement.appendChild(effectBadge);
757
+ }
582
758
  wrapper.appendChild(labelElement);
583
759
  const fields = doc.createElement('div');
584
760
  fields.style.cssText = 'display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px';
585
761
  for (const property of properties) {
586
- fields.appendChild(createInspectorNumberControl(doc, inspectorObject, property));
762
+ fields.appendChild(isInspectorPropertyEditable(property)
763
+ ? createInspectorNumberControl(doc, inspectorObject, property)
764
+ : createInspectorReadonlyControl(doc, inspectorObject, property));
587
765
  }
588
766
  wrapper.appendChild(fields);
589
767
  parent.appendChild(wrapper);
590
768
  }
769
+ function getInspectorPropertyGroupAccess(properties) {
770
+ if (properties.some(property => property.persistence === 'runtime'))
771
+ return 'runtime';
772
+ const editableCount = properties.filter(isInspectorPropertyEditable).length;
773
+ if (editableCount === 0)
774
+ return 'readonly';
775
+ if (editableCount === properties.length)
776
+ return 'editable';
777
+ return 'mixed';
778
+ }
779
+ function getInspectorPropertyGroupEffect(properties) {
780
+ const effects = new Set(properties.map(getInspectorPropertyEffect));
781
+ if (effects.size === 1)
782
+ return [...effects][0] ?? 'active';
783
+ if (effects.has('unsupported'))
784
+ return 'unsupported';
785
+ if (effects.has('default'))
786
+ return 'default';
787
+ if (effects.has('derived'))
788
+ return 'derived';
789
+ if (effects.has('runtime'))
790
+ return 'runtime';
791
+ return 'active';
792
+ }
793
+ function getInspectorPropertyGroupDisabledReason(properties) {
794
+ return properties.find(property => property.disabledReason)?.disabledReason;
795
+ }
796
+ function createInspectorPropertyLabelStyle(access, effect = 'active') {
797
+ return [
798
+ `color:${effect === 'unsupported' ? 'var(--fps-editor-danger-text)' : effect === 'default' || effect === 'derived' || access === 'runtime' ? 'var(--fps-editor-warn)' : access === 'editable' ? 'var(--fps-editor-muted-strong)' : 'var(--fps-editor-muted)'}`,
799
+ 'font-weight:800',
800
+ access === 'readonly' ? 'opacity:0.82' : '',
801
+ ].filter(Boolean).join(';');
802
+ }
803
+ function createInspectorAccessTitle(access, tooltip) {
804
+ const accessLabel = access === 'editable'
805
+ ? 'Editable'
806
+ : access === 'mixed'
807
+ ? 'Mixed editable and read-only'
808
+ : access === 'runtime'
809
+ ? 'Runtime-only'
810
+ : 'Read-only';
811
+ return tooltip ? `${accessLabel}: ${tooltip}` : accessLabel;
812
+ }
813
+ function createInspectorStatusTitle(access, effect, detail) {
814
+ const parts = [createInspectorAccessTitle(access)];
815
+ if (effect !== 'active')
816
+ parts.push(createInspectorEffectTitle(effect));
817
+ if (detail)
818
+ parts.push(detail);
819
+ return [...new Set(parts)].join(' ');
820
+ }
821
+ function createInspectorReadonlyValueShell(doc, access, effect = 'active', title) {
822
+ const valueElement = doc.createElement('div');
823
+ valueElement.dataset.editorInspectorAccess = access;
824
+ valueElement.dataset.editorInspectorEffect = effect;
825
+ valueElement.title = title ?? createInspectorStatusTitle(access, effect);
826
+ const warningLike = effect === 'default' || effect === 'derived' || access === 'runtime' || effect === 'runtime';
827
+ const unsupported = effect === 'unsupported';
828
+ valueElement.style.cssText = [
829
+ 'min-width:0',
830
+ 'min-height:24px',
831
+ 'display:flex',
832
+ 'align-items:center',
833
+ 'gap:5px',
834
+ 'padding:3px 6px',
835
+ `border:1px solid ${unsupported ? 'var(--fps-editor-danger-border)' : warningLike ? 'var(--fps-editor-warn-border)' : 'var(--fps-editor-readonly-border)'}`,
836
+ 'border-radius:3px',
837
+ `background:${unsupported ? 'var(--fps-editor-danger-soft)' : warningLike ? 'var(--fps-editor-warn-soft)' : 'var(--fps-editor-readonly-bg)'}`,
838
+ `color:${unsupported ? 'var(--fps-editor-danger-text)' : warningLike ? 'var(--fps-editor-warn)' : 'var(--fps-editor-readonly-text)'}`,
839
+ warningLike ? 'font-style:italic' : '',
840
+ ].filter(Boolean).join(';');
841
+ const icon = createLocalEditorIcon(doc, 'lock', { size: 11, strokeWidth: 2.3 });
842
+ icon.style.opacity = '0.78';
843
+ valueElement.appendChild(icon);
844
+ return valueElement;
845
+ }
846
+ function appendInspectorReadonlyTextValue(doc, parent, value, access, effect = 'active', title) {
847
+ const valueText = doc.createElement('span');
848
+ valueText.textContent = value;
849
+ valueText.style.cssText = 'min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
850
+ parent.appendChild(valueText);
851
+ parent.title = title ?? createInspectorStatusTitle(access, effect);
852
+ }
853
+ function createInspectorReadonlyScalarControl(doc, value, access, effect = 'active', title) {
854
+ const valueElement = createInspectorReadonlyValueShell(doc, access, effect, title);
855
+ appendInspectorReadonlyTextValue(doc, valueElement, value, access, effect, title);
856
+ return valueElement;
857
+ }
858
+ function createInspectorReadonlyDetailsShell(doc, property, access) {
859
+ const effect = getInspectorPropertyEffect(property);
860
+ const warningLike = effect === 'default' || effect === 'derived' || access === 'runtime' || effect === 'runtime';
861
+ const unsupported = effect === 'unsupported';
862
+ const details = doc.createElement('details');
863
+ details.dataset.editorInspectorAccess = access;
864
+ details.dataset.editorInspectorEffect = effect;
865
+ details.title = createInspectorStatusTitle(access, effect, property.tooltip ?? property.disabledReason);
866
+ details.style.cssText = [
867
+ 'min-width:0',
868
+ 'max-width:100%',
869
+ 'padding:3px 6px',
870
+ `border:1px solid ${unsupported ? 'var(--fps-editor-danger-border)' : warningLike ? 'var(--fps-editor-warn-border)' : 'var(--fps-editor-readonly-border)'}`,
871
+ 'border-radius:3px',
872
+ `background:${unsupported ? 'var(--fps-editor-danger-soft)' : warningLike ? 'var(--fps-editor-warn-soft)' : 'var(--fps-editor-readonly-bg)'}`,
873
+ `color:${unsupported ? 'var(--fps-editor-danger-text)' : warningLike ? 'var(--fps-editor-warn)' : 'var(--fps-editor-readonly-text)'}`,
874
+ ].join(';');
875
+ return details;
876
+ }
591
877
  function createInspectorInputBase(doc, target, property) {
592
878
  const input = doc.createElement('input');
593
879
  input.value = property.mixed ? '' : String(property.value);
@@ -615,7 +901,14 @@ function createInspectorBooleanControl(doc, target, property) {
615
901
  const input = createInspectorInputBase(doc, target, property);
616
902
  input.type = 'checkbox';
617
903
  input.checked = property.mixed ? false : property.value === true;
618
- input.style.cssText = 'width:16px;height:16px;accent-color:var(--fps-editor-accent);justify-self:start';
904
+ input.style.cssText = [
905
+ 'width:16px',
906
+ 'height:16px',
907
+ 'accent-color:var(--fps-editor-accent)',
908
+ 'justify-self:start',
909
+ 'cursor:pointer',
910
+ 'box-shadow:0 0 0 1px var(--fps-editor-editable-border)',
911
+ ].join(';');
619
912
  return input;
620
913
  }
621
914
  function createInspectorEnumControl(doc, target, property) {
@@ -664,36 +957,30 @@ function createInspectorColorControl(doc, target, property) {
664
957
  const input = createInspectorInputBase(doc, target, property);
665
958
  input.type = 'color';
666
959
  input.value = colorValueToHex(property.value);
667
- input.style.cssText = 'width:34px;height:26px;border:1px solid var(--fps-editor-border);border-radius:3px;background:transparent;padding:0';
960
+ input.style.cssText = 'width:34px;height:26px;border:1px solid var(--fps-editor-editable-border);border-radius:3px;background:var(--fps-editor-editable-bg);padding:0;box-shadow:var(--fps-editor-editable-shadow)';
668
961
  return input;
669
962
  }
670
963
  function createInspectorReadonlyControl(doc, _target, property) {
671
964
  if (!property.mixed && isExpandableInspectorValue(property.value)) {
672
965
  return createInspectorObjectReadonlyControl(doc, property);
673
966
  }
674
- const valueElement = doc.createElement('div');
675
- valueElement.textContent = property.mixed ? '--' : formatLocalEditorBrowserInspectorValue(property.value);
676
- valueElement.style.cssText = [
677
- `color:${property.persistence === 'runtime' ? 'var(--fps-editor-muted)' : 'var(--fps-editor-text)'}`,
678
- 'overflow:hidden',
679
- 'text-overflow:ellipsis',
680
- 'white-space:nowrap',
681
- property.persistence === 'runtime' ? 'font-style:italic' : '',
682
- ].filter(Boolean).join(';');
683
- if (property.persistence === 'runtime')
684
- valueElement.title = 'Runtime-only context';
685
- return valueElement;
967
+ const access = getInspectorPropertyAccess(property);
968
+ const effect = getInspectorPropertyEffect(property);
969
+ return createInspectorReadonlyScalarControl(doc, property.mixed ? '--' : formatLocalEditorBrowserInspectorValue(property.value), access, effect, createInspectorStatusTitle(access, effect, property.tooltip ?? property.disabledReason));
686
970
  }
687
971
  function createInspectorObjectReadonlyControl(doc, property) {
688
- const details = doc.createElement('details');
689
- details.style.cssText = [
690
- 'min-width:0',
691
- 'max-width:100%',
692
- `color:${property.persistence === 'runtime' ? 'var(--fps-editor-muted)' : 'var(--fps-editor-text)'}`,
693
- ].join(';');
972
+ const access = getInspectorPropertyAccess(property);
973
+ const effect = getInspectorPropertyEffect(property);
974
+ const details = createInspectorReadonlyDetailsShell(doc, property, access);
694
975
  const summary = doc.createElement('summary');
695
- summary.textContent = formatInspectorObjectSummary(property.value);
696
- summary.style.cssText = 'cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
976
+ summary.style.cssText = 'cursor:pointer;display:flex;align-items:center;gap:5px;overflow:hidden;white-space:nowrap';
977
+ const icon = createLocalEditorIcon(doc, 'lock', { size: 11, strokeWidth: 2.3 });
978
+ icon.style.opacity = '0.78';
979
+ summary.appendChild(icon);
980
+ const summaryText = doc.createElement('span');
981
+ summaryText.textContent = formatInspectorObjectSummary(property.value);
982
+ summaryText.style.cssText = 'min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
983
+ summary.appendChild(summaryText);
697
984
  details.appendChild(summary);
698
985
  const pre = doc.createElement('pre');
699
986
  pre.textContent = formatLocalEditorBrowserInspectorValue(property.value);
@@ -704,15 +991,13 @@ function createInspectorObjectReadonlyControl(doc, property) {
704
991
  'word-break:break-word',
705
992
  'margin:6px 0 0',
706
993
  'padding:7px',
707
- 'border:1px solid var(--fps-editor-border)',
994
+ `border:1px solid ${effect === 'unsupported' ? 'var(--fps-editor-danger-border)' : effect === 'default' || effect === 'derived' || effect === 'runtime' || access === 'runtime' ? 'var(--fps-editor-warn-border)' : 'var(--fps-editor-readonly-border)'}`,
708
995
  'border-radius:3px',
709
996
  'background:var(--fps-editor-field)',
710
997
  'font-size:11px',
711
998
  'line-height:1.45',
712
999
  ].join(';');
713
1000
  details.appendChild(pre);
714
- if (property.persistence === 'runtime')
715
- details.title = 'Runtime-only context';
716
1001
  return details;
717
1002
  }
718
1003
  export function applyLocalEditorBrowserInspectorControlBinding(element, target, property, options) {
@@ -724,30 +1009,38 @@ export function applyLocalEditorBrowserInspectorControlBinding(element, target,
724
1009
  element.dataset.serializedValueType = property.valueType;
725
1010
  element.dataset.serializedCommitMode = property.commitMode;
726
1011
  element.dataset.serializedPersistence = property.persistence;
1012
+ element.dataset.serializedEffect = getInspectorPropertyEffect(property);
1013
+ if (property.disabledReason)
1014
+ element.dataset.serializedDisabledReason = property.disabledReason;
727
1015
  if (options?.source)
728
1016
  element.dataset.serializedEditSource = options.source;
729
- element.title = property.tooltip ?? property.label;
1017
+ const access = getInspectorPropertyAccess(property);
1018
+ const effect = getInspectorPropertyEffect(property);
1019
+ element.title = access === 'editable' && effect === 'active'
1020
+ ? property.tooltip ?? property.label
1021
+ : createInspectorStatusTitle(access, effect, property.tooltip ?? property.disabledReason ?? property.label);
730
1022
  if ('disabled' in element) {
731
- element.disabled = property.readOnly === true || property.persistence !== 'document';
1023
+ element.disabled = !isInspectorPropertyEditable(property);
732
1024
  }
733
1025
  }
734
1026
  function createInspectorInputStyle() {
735
1027
  return [
736
- 'min-width:0',
737
- 'height:28px',
738
- 'border:1px solid var(--fps-editor-border)',
739
- 'border-radius:3px',
740
- 'background:var(--fps-editor-field)',
741
- 'color:var(--fps-editor-text)',
742
- 'font-size:12px',
1028
+ createEditorInputStyle(),
743
1029
  'padding:0 6px',
1030
+ 'background:var(--fps-editor-editable-bg)',
1031
+ 'border-color:var(--fps-editor-editable-border)',
1032
+ 'color:var(--fps-editor-text-strong)',
1033
+ 'box-shadow:var(--fps-editor-editable-shadow)',
744
1034
  ].join(';');
745
1035
  }
746
1036
  function appendReadOnlyRow(doc, parent, label, value) {
747
- const valueElement = doc.createElement('div');
748
- valueElement.textContent = value;
749
- valueElement.style.cssText = 'color:var(--fps-editor-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
750
- parent.appendChild(createPropertyRow(doc, label, valueElement));
1037
+ const valueElement = createInspectorReadonlyScalarControl(doc, value, 'readonly', 'active', 'Read-only summary');
1038
+ const row = createPropertyRow(doc, label, valueElement);
1039
+ row.dataset.editorInspectorAccess = 'readonly';
1040
+ const labelElement = row.firstElementChild;
1041
+ if (labelElement)
1042
+ labelElement.style.cssText = createInspectorPropertyLabelStyle('readonly', 'active');
1043
+ parent.appendChild(row);
751
1044
  }
752
1045
  function createLegacyInspectorObject(serializedObject) {
753
1046
  const targetIds = 'targetIds' in serializedObject ? serializedObject.targetIds : [serializedObject.targetId];