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

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 +127 -6
  5. package/dist/local-editor-harness.d.ts.map +1 -1
  6. package/dist/local-editor-harness.js +1364 -66
  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 +414 -111
  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 +153 -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';
@@ -154,7 +158,17 @@ function renderAssetBrowserContent(doc, panel, state, variant = 'list') {
154
158
  .filter((asset) => {
155
159
  if (!normalizedFilter)
156
160
  return true;
157
- return [asset.id, asset.label, asset.meta ?? '']
161
+ return [
162
+ asset.id,
163
+ asset.assetId ?? '',
164
+ asset.guid ?? '',
165
+ asset.kind ?? '',
166
+ asset.label,
167
+ asset.meta ?? '',
168
+ asset.external?.platformAssetId ?? '',
169
+ asset.external?.assetPath ?? '',
170
+ asset.external?.assetUrl ?? '',
171
+ ]
158
172
  .some(value => value.toLowerCase().includes(normalizedFilter));
159
173
  })
160
174
  .slice(0, 80);
@@ -177,19 +191,27 @@ function renderAssetBrowserContent(doc, panel, state, variant = 'list') {
177
191
  'padding:6px 8px',
178
192
  'border:1px solid var(--fps-editor-border)',
179
193
  'border-radius:3px',
180
- 'background:#243426',
194
+ 'background:var(--fps-editor-asset-card-bg)',
181
195
  'color:var(--fps-editor-text)',
196
+ 'display:flex',
197
+ 'align-items:center',
198
+ 'gap:8px',
182
199
  'font-size:12px',
183
200
  'cursor:pointer',
184
201
  ].join(';');
202
+ const icon = createLocalEditorIcon(doc, 'asset', { size: 16 });
203
+ const body = doc.createElement('div');
204
+ body.style.cssText = 'min-width:0;flex:1';
185
205
  const label = doc.createElement('div');
186
206
  label.textContent = asset.label;
187
207
  label.style.cssText = 'font-weight:800;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
188
208
  const meta = doc.createElement('div');
189
209
  meta.textContent = asset.meta ?? asset.id;
190
210
  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);
211
+ body.appendChild(label);
212
+ body.appendChild(meta);
213
+ button.appendChild(icon);
214
+ button.appendChild(body);
193
215
  list.appendChild(button);
194
216
  }
195
217
  panel.appendChild(list);
@@ -197,36 +219,21 @@ function renderAssetBrowserContent(doc, panel, state, variant = 'list') {
197
219
  function renderHistoryPanel(doc, panel, history) {
198
220
  const title = doc.createElement('h2');
199
221
  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)';
222
+ 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)';
223
+ title.appendChild(createLocalEditorIcon(doc, 'history'));
224
+ title.appendChild(createHeadingLabel(doc, `历史记录 (${entries.length})`));
202
225
  panel.appendChild(title);
203
226
  appendHistoryEntries(doc, panel, entries);
204
227
  }
205
228
  function appendHistoryEntries(doc, panel, entries) {
206
229
  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);
230
+ panel.appendChild(createEmptyState(doc, '暂无文档修改。'));
218
231
  return;
219
232
  }
220
233
  const list = doc.createElement('div');
221
234
  list.style.cssText = 'display:flex;flex-direction:column;gap:4px';
222
235
  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(';');
236
+ const item = createListItemBlock(doc);
230
237
  const label = doc.createElement('div');
231
238
  label.textContent = entry.label;
232
239
  label.style.cssText = 'font-size:12px;font-weight:900;color:var(--fps-editor-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
@@ -259,10 +266,12 @@ function formatHistoryTimestamp(createdAt) {
259
266
  ].join('');
260
267
  }
261
268
  export function renderInspectorPanel(doc, panel, state, filter = '', options = {}) {
269
+ const renderSnapshot = captureInspectorPanelRenderSnapshot(doc, panel);
262
270
  clearElement(panel);
263
271
  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)';
272
+ 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)';
273
+ title.appendChild(createLocalEditorIcon(doc, 'inspector'));
274
+ title.appendChild(createHeadingLabel(doc, 'Inspector'));
266
275
  panel.appendChild(title);
267
276
  const search = createInspectorSearchInput(doc, filter);
268
277
  panel.appendChild(search);
@@ -276,23 +285,45 @@ export function renderInspectorPanel(doc, panel, state, filter = '', options = {
276
285
  empty.textContent = '请从层级树或 Scene View 中选择一个 GameObject。';
277
286
  empty.style.cssText = 'color:var(--fps-editor-muted);line-height:1.45';
278
287
  panel.appendChild(empty);
288
+ restoreInspectorPanelRenderSnapshot(panel, renderSnapshot);
279
289
  return;
280
290
  }
281
291
  const controlRegistry = createLocalEditorBrowserInspectorControlRegistry(options.controls, options.controlConflict);
282
292
  appendInspectorSummary(doc, panel, inspectorObject, selectionCount);
283
293
  const visibleSections = filterInspectorSections(inspectorObject.sections.filter(section => section.placement !== 'summary'), filter);
284
294
  if (visibleSections.length === 0) {
285
- if (!filter.trim() && inspectorObject.sections.length === 0)
295
+ if (!filter.trim() && inspectorObject.sections.length === 0) {
296
+ restoreInspectorPanelRenderSnapshot(panel, renderSnapshot);
286
297
  return;
298
+ }
287
299
  const empty = doc.createElement('div');
288
300
  empty.textContent = '没有匹配的 Inspector 字段。';
289
301
  empty.style.cssText = 'color:var(--fps-editor-muted);line-height:1.45';
290
302
  panel.appendChild(empty);
303
+ restoreInspectorPanelRenderSnapshot(panel, renderSnapshot);
291
304
  return;
292
305
  }
293
306
  for (const section of visibleSections) {
294
307
  panel.appendChild(createInspectorSectionBlock(doc, inspectorObject, section, controlRegistry));
295
308
  }
309
+ restoreInspectorPanelRenderSnapshot(panel, renderSnapshot);
310
+ }
311
+ function captureInspectorPanelRenderSnapshot(doc, panel) {
312
+ const activeElement = doc.activeElement;
313
+ const elementConstructor = doc.defaultView?.HTMLElement;
314
+ return {
315
+ preserveScroll: !!activeElement
316
+ && (elementConstructor ? activeElement instanceof elementConstructor : activeElement instanceof HTMLElement)
317
+ && panel.contains(activeElement),
318
+ scrollTop: panel.scrollTop,
319
+ scrollLeft: panel.scrollLeft,
320
+ };
321
+ }
322
+ function restoreInspectorPanelRenderSnapshot(panel, snapshot) {
323
+ if (!snapshot.preserveScroll)
324
+ return;
325
+ panel.scrollTop = snapshot.scrollTop;
326
+ panel.scrollLeft = snapshot.scrollLeft;
296
327
  }
297
328
  function createInspectorSearchInput(doc, value) {
298
329
  const input = doc.createElement('input');
@@ -300,26 +331,24 @@ function createInspectorSearchInput(doc, value) {
300
331
  input.value = value;
301
332
  input.placeholder = 'Search...';
302
333
  input.style.cssText = [
334
+ createEditorInputStyle(),
303
335
  'width:100%',
304
336
  'height:30px',
305
- 'box-sizing:border-box',
306
337
  '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
338
  'outline:none',
314
339
  ].join(';');
315
340
  return input;
316
341
  }
317
342
  function appendInspectorSummary(doc, panel, inspectorObject, selectionCount) {
318
343
  const block = createInspectorComponentBlock(doc);
344
+ const header = doc.createElement('div');
345
+ header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0 0 8px';
319
346
  const title = doc.createElement('h3');
320
347
  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);
348
+ title.style.cssText = 'font-size:12px;margin:0;font-weight:900;color:var(--fps-editor-text-strong)';
349
+ header.appendChild(title);
350
+ header.appendChild(createInspectorAccessBadge(doc, 'readonly'));
351
+ block.appendChild(header);
323
352
  appendReadOnlyRow(doc, block, selectionCount > 1 ? 'Selected' : 'Name', inspectorObject.label ?? inspectorObject.activeId ?? inspectorObject.targetIds[0] ?? '无');
324
353
  appendReadOnlyRow(doc, block, 'Active ID', inspectorObject.activeId ?? '无');
325
354
  if (selectionCount > 1)
@@ -350,7 +379,11 @@ function createInspectorSectionBlock(doc, inspectorObject, section, controlRegis
350
379
  const block = doc.createElement('details');
351
380
  block.dataset.editorInspectorSection = section.id;
352
381
  block.open = section.collapsedByDefault !== true;
353
- block.style.cssText = createInspectorBlockStyle();
382
+ const status = resolveLocalEditorBrowserInspectorSectionStatus(section);
383
+ const { access, effect } = status;
384
+ block.dataset.editorInspectorAccess = access;
385
+ block.dataset.editorInspectorEffect = effect;
386
+ block.style.cssText = createInspectorBlockStyle(access, effect);
354
387
  const sectionTitle = doc.createElement('summary');
355
388
  sectionTitle.style.cssText = [
356
389
  'font-size:12px',
@@ -375,11 +408,13 @@ function createInspectorSectionBlock(doc, inspectorObject, section, controlRegis
375
408
  summary.style.cssText = 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-transform:none;font-size:11px';
376
409
  meta.appendChild(summary);
377
410
  }
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);
411
+ const badge = createInspectorAccessBadge(doc, access);
412
+ badge.style.flex = '0 0 auto';
413
+ meta.appendChild(badge);
414
+ const effectBadge = createInspectorEffectBadge(doc, effect, section.disabledReason);
415
+ if (effectBadge && status.showEffectBadge) {
416
+ effectBadge.style.flex = '0 0 auto';
417
+ meta.appendChild(effectBadge);
383
418
  }
384
419
  sectionTitle.appendChild(meta);
385
420
  block.appendChild(sectionTitle);
@@ -396,18 +431,133 @@ function createInspectorSectionBlock(doc, inspectorObject, section, controlRegis
396
431
  }
397
432
  function createInspectorComponentBlock(doc) {
398
433
  const block = doc.createElement('div');
399
- block.style.cssText = createInspectorBlockStyle();
434
+ block.dataset.editorInspectorAccess = 'readonly';
435
+ block.dataset.editorInspectorEffect = 'active';
436
+ block.style.cssText = createInspectorBlockStyle('readonly', 'active');
400
437
  return block;
401
438
  }
402
- function createInspectorBlockStyle() {
439
+ export function resolveLocalEditorBrowserInspectorSectionStatus(section) {
440
+ const access = getInspectorSectionAccess(section);
441
+ const effect = getInspectorSectionEffect(section);
442
+ return {
443
+ access,
444
+ effect,
445
+ showEffectBadge: effect !== 'active'
446
+ && !(access === 'runtime' && effect === 'runtime')
447
+ && !(effect === 'default' && inspectorSummaryLooksDefault(section.summary)),
448
+ };
449
+ }
450
+ function createInspectorBlockStyle(_access, _effect = 'active') {
403
451
  return [
404
452
  'margin:0 0 8px',
405
453
  'padding:9px',
406
- 'border:1px solid var(--fps-editor-border)',
454
+ 'border:1px solid var(--fps-editor-inspector-section-border, var(--fps-editor-border))',
407
455
  'border-radius:3px',
408
456
  'background:var(--fps-editor-panel-soft)',
409
457
  ].join(';');
410
458
  }
459
+ function isInspectorPropertyEditable(property) {
460
+ return property.readOnly !== true
461
+ && property.persistence === 'document'
462
+ && getInspectorPropertyEffect(property) === 'active';
463
+ }
464
+ function getInspectorPropertyAccess(property) {
465
+ if (property.persistence === 'runtime')
466
+ return 'runtime';
467
+ if (getInspectorPropertyEffect(property) === 'runtime')
468
+ return 'runtime';
469
+ return isInspectorPropertyEditable(property) ? 'editable' : 'readonly';
470
+ }
471
+ function getInspectorSectionAccess(section) {
472
+ if (section.runtimeOnly || section.persistence === 'runtime' || getInspectorSectionEffect(section) === 'runtime')
473
+ return 'runtime';
474
+ const editableCount = section.properties.filter(isInspectorPropertyEditable).length;
475
+ if (editableCount === 0)
476
+ return 'readonly';
477
+ if (editableCount === section.properties.length)
478
+ return 'editable';
479
+ return 'mixed';
480
+ }
481
+ function getInspectorPropertyEffect(property) {
482
+ if (property.effect)
483
+ return property.effect;
484
+ if (property.persistence === 'runtime')
485
+ return 'runtime';
486
+ return 'active';
487
+ }
488
+ function getInspectorSectionEffect(section) {
489
+ if (section.effect)
490
+ return section.effect;
491
+ if (section.runtimeOnly || section.persistence === 'runtime')
492
+ return 'runtime';
493
+ if (section.properties.some(isInspectorPropertyEditable))
494
+ return 'active';
495
+ const effects = new Set(section.properties.map(getInspectorPropertyEffect));
496
+ if (effects.size === 1)
497
+ return [...effects][0] ?? 'active';
498
+ if (effects.has('unsupported'))
499
+ return 'unsupported';
500
+ if (effects.has('default'))
501
+ return 'default';
502
+ if (effects.has('derived'))
503
+ return 'derived';
504
+ if (effects.has('runtime'))
505
+ return 'runtime';
506
+ return 'active';
507
+ }
508
+ function createInspectorAccessBadge(doc, access) {
509
+ const labels = {
510
+ editable: 'EDITABLE',
511
+ mixed: 'MIXED',
512
+ readonly: 'READONLY',
513
+ runtime: 'RUNTIME',
514
+ };
515
+ const badge = createBadge(doc, labels[access], {
516
+ compact: true,
517
+ tone: access === 'editable' ? 'success' : access === 'mixed' || access === 'runtime' ? 'warning' : 'default',
518
+ });
519
+ badge.title = access === 'editable'
520
+ ? 'This section writes to the document.'
521
+ : access === 'mixed'
522
+ ? 'This section contains both editable and read-only fields.'
523
+ : access === 'runtime'
524
+ ? 'Runtime-only context, not saved to the document.'
525
+ : 'Read-only information.';
526
+ return badge;
527
+ }
528
+ function createInspectorEffectBadge(doc, effect, disabledReason) {
529
+ if (effect === 'active')
530
+ return null;
531
+ const labels = {
532
+ default: 'DEFAULTS',
533
+ derived: 'DERIVED',
534
+ runtime: 'RUNTIME',
535
+ unsupported: 'NO EFFECT',
536
+ };
537
+ const badge = createBadge(doc, labels[effect], {
538
+ compact: true,
539
+ tone: effect === 'unsupported' ? 'danger' : 'warning',
540
+ });
541
+ badge.title = disabledReason ?? createInspectorEffectTitle(effect);
542
+ return badge;
543
+ }
544
+ function createInspectorEffectTitle(effect) {
545
+ switch (effect) {
546
+ case 'default':
547
+ return 'Default values are being shown; edits do not affect this object until an override is configured.';
548
+ case 'derived':
549
+ return 'Derived from document or runtime state.';
550
+ case 'runtime':
551
+ return 'Runtime-only context, not saved to the document.';
552
+ case 'unsupported':
553
+ return 'This field is visible but editing is not supported for the current object.';
554
+ default:
555
+ return 'Editable field.';
556
+ }
557
+ }
558
+ function inspectorSummaryLooksDefault(summary) {
559
+ return summary?.trim().toLowerCase() === 'defaults';
560
+ }
411
561
  function groupInspectorProperties(properties) {
412
562
  const groups = [];
413
563
  const consumed = new Set();
@@ -436,7 +586,7 @@ function groupInspectorProperties(properties) {
436
586
  for (const entry of vector)
437
587
  consumed.add(entry.path);
438
588
  const parts = basePath.split('.');
439
- groups.push({ kind: 'vector', label: toTitle(parts[parts.length - 1] ?? basePath), properties: vector });
589
+ groups.push({ kind: 'vector', label: resolveInspectorVectorGroupLabel(vector, parts[parts.length - 1] ?? basePath), properties: vector });
440
590
  }
441
591
  else {
442
592
  groups.push({ kind: 'property', property });
@@ -445,6 +595,16 @@ function groupInspectorProperties(properties) {
445
595
  }
446
596
  return groups;
447
597
  }
598
+ function resolveInspectorVectorGroupLabel(properties, fallback) {
599
+ const labels = properties.map(property => {
600
+ const match = property.label.match(/^(.*)\.(x|y|z|r|g|b)$/);
601
+ return match?.[1]?.trim() ?? '';
602
+ });
603
+ const first = labels[0];
604
+ if (first && labels.every(label => label === first))
605
+ return first;
606
+ return toTitle(fallback);
607
+ }
448
608
  const builtinInspectorControlRegistrations = [
449
609
  {
450
610
  id: 'builtin.readonly',
@@ -515,7 +675,16 @@ const builtinInspectorControlRegistrations = [
515
675
  ];
516
676
  function appendInspectorPropertyRow(doc, parent, inspectorObject, property, controlRegistry) {
517
677
  const control = renderInspectorControl(doc, controlRegistry, inspectorObject, property);
518
- parent.appendChild(createPropertyRow(doc, property.label, control));
678
+ const access = getInspectorPropertyAccess(property);
679
+ const effect = getInspectorPropertyEffect(property);
680
+ const row = createPropertyRow(doc, property.label, control);
681
+ row.dataset.editorInspectorAccess = access;
682
+ row.dataset.editorInspectorEffect = effect;
683
+ row.title = createInspectorStatusTitle(access, effect, property.tooltip ?? property.disabledReason);
684
+ const label = row.firstElementChild;
685
+ if (label)
686
+ label.style.cssText = createInspectorPropertyLabelStyle(access, effect);
687
+ parent.appendChild(row);
519
688
  }
520
689
  export function createLocalEditorBrowserInspectorControlRegistry(controls = [], conflict = 'error') {
521
690
  const registrations = new Map();
@@ -544,7 +713,7 @@ function compareInspectorControlRegistrations(left, right) {
544
713
  return (left.order ?? 0) - (right.order ?? 0) || left.id.localeCompare(right.id);
545
714
  }
546
715
  function renderInspectorControl(doc, registrations, target, property) {
547
- if (property.readOnly || property.persistence !== 'document')
716
+ if (!isInspectorPropertyEditable(property))
548
717
  return createInspectorReadonlyControl(doc, target, property);
549
718
  const context = {
550
719
  doc,
@@ -576,18 +745,145 @@ function localEditorBrowserInspectorControlSupports(registration, context) {
576
745
  function appendInspectorVectorInputs(doc, parent, inspectorObject, label, properties) {
577
746
  const wrapper = doc.createElement('div');
578
747
  wrapper.style.cssText = 'margin:8px 0';
748
+ const access = getInspectorPropertyGroupAccess(properties);
749
+ const effect = getInspectorPropertyGroupEffect(properties);
750
+ wrapper.dataset.editorInspectorAccess = access;
751
+ wrapper.dataset.editorInspectorEffect = effect;
579
752
  const labelElement = doc.createElement('div');
580
- labelElement.textContent = label;
581
- labelElement.style.cssText = 'color:var(--fps-editor-muted);font-weight:800;margin-bottom:5px';
753
+ labelElement.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:5px';
754
+ const labelText = doc.createElement('span');
755
+ labelText.textContent = label;
756
+ labelText.style.cssText = createInspectorPropertyLabelStyle(access, effect);
757
+ labelElement.appendChild(labelText);
758
+ if (access !== 'editable') {
759
+ const badge = createInspectorAccessBadge(doc, access);
760
+ badge.style.flex = '0 0 auto';
761
+ labelElement.appendChild(badge);
762
+ }
763
+ const effectBadge = createInspectorEffectBadge(doc, effect, getInspectorPropertyGroupDisabledReason(properties));
764
+ if (effectBadge && !(access === 'runtime' && effect === 'runtime')) {
765
+ effectBadge.style.flex = '0 0 auto';
766
+ labelElement.appendChild(effectBadge);
767
+ }
582
768
  wrapper.appendChild(labelElement);
583
769
  const fields = doc.createElement('div');
584
770
  fields.style.cssText = 'display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px';
585
771
  for (const property of properties) {
586
- fields.appendChild(createInspectorNumberControl(doc, inspectorObject, property));
772
+ fields.appendChild(isInspectorPropertyEditable(property)
773
+ ? createInspectorNumberControl(doc, inspectorObject, property)
774
+ : createInspectorReadonlyControl(doc, inspectorObject, property));
587
775
  }
588
776
  wrapper.appendChild(fields);
589
777
  parent.appendChild(wrapper);
590
778
  }
779
+ function getInspectorPropertyGroupAccess(properties) {
780
+ if (properties.some(property => property.persistence === 'runtime'))
781
+ return 'runtime';
782
+ const editableCount = properties.filter(isInspectorPropertyEditable).length;
783
+ if (editableCount === 0)
784
+ return 'readonly';
785
+ if (editableCount === properties.length)
786
+ return 'editable';
787
+ return 'mixed';
788
+ }
789
+ function getInspectorPropertyGroupEffect(properties) {
790
+ const effects = new Set(properties.map(getInspectorPropertyEffect));
791
+ if (effects.size === 1)
792
+ return [...effects][0] ?? 'active';
793
+ if (effects.has('unsupported'))
794
+ return 'unsupported';
795
+ if (effects.has('default'))
796
+ return 'default';
797
+ if (effects.has('derived'))
798
+ return 'derived';
799
+ if (effects.has('runtime'))
800
+ return 'runtime';
801
+ return 'active';
802
+ }
803
+ function getInspectorPropertyGroupDisabledReason(properties) {
804
+ return properties.find(property => property.disabledReason)?.disabledReason;
805
+ }
806
+ function createInspectorPropertyLabelStyle(access, effect = 'active') {
807
+ return [
808
+ `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)'}`,
809
+ 'font-weight:800',
810
+ access === 'readonly' ? 'opacity:0.82' : '',
811
+ ].filter(Boolean).join(';');
812
+ }
813
+ function createInspectorAccessTitle(access, tooltip) {
814
+ const accessLabel = access === 'editable'
815
+ ? 'Editable'
816
+ : access === 'mixed'
817
+ ? 'Mixed editable and read-only'
818
+ : access === 'runtime'
819
+ ? 'Runtime-only'
820
+ : 'Read-only';
821
+ return tooltip ? `${accessLabel}: ${tooltip}` : accessLabel;
822
+ }
823
+ function createInspectorStatusTitle(access, effect, detail) {
824
+ const parts = [createInspectorAccessTitle(access)];
825
+ if (effect !== 'active')
826
+ parts.push(createInspectorEffectTitle(effect));
827
+ if (detail)
828
+ parts.push(detail);
829
+ return [...new Set(parts)].join(' ');
830
+ }
831
+ function createInspectorReadonlyValueShell(doc, access, effect = 'active', title) {
832
+ const valueElement = doc.createElement('div');
833
+ valueElement.dataset.editorInspectorAccess = access;
834
+ valueElement.dataset.editorInspectorEffect = effect;
835
+ valueElement.title = title ?? createInspectorStatusTitle(access, effect);
836
+ const warningLike = effect === 'default' || effect === 'derived' || access === 'runtime' || effect === 'runtime';
837
+ const unsupported = effect === 'unsupported';
838
+ valueElement.style.cssText = [
839
+ 'min-width:0',
840
+ 'min-height:24px',
841
+ 'display:flex',
842
+ 'align-items:center',
843
+ 'gap:5px',
844
+ 'padding:3px 6px',
845
+ `border:1px solid ${unsupported ? 'var(--fps-editor-danger-border)' : warningLike ? 'var(--fps-editor-warn-border)' : 'var(--fps-editor-readonly-border)'}`,
846
+ 'border-radius:3px',
847
+ `background:${unsupported ? 'var(--fps-editor-danger-soft)' : warningLike ? 'var(--fps-editor-warn-soft)' : 'var(--fps-editor-readonly-bg)'}`,
848
+ `color:${unsupported ? 'var(--fps-editor-danger-text)' : warningLike ? 'var(--fps-editor-warn)' : 'var(--fps-editor-readonly-text)'}`,
849
+ warningLike ? 'font-style:italic' : '',
850
+ ].filter(Boolean).join(';');
851
+ const icon = createLocalEditorIcon(doc, 'lock', { size: 11, strokeWidth: 2.3 });
852
+ icon.style.opacity = '0.78';
853
+ valueElement.appendChild(icon);
854
+ return valueElement;
855
+ }
856
+ function appendInspectorReadonlyTextValue(doc, parent, value, access, effect = 'active', title) {
857
+ const valueText = doc.createElement('span');
858
+ valueText.textContent = value;
859
+ valueText.style.cssText = 'min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
860
+ parent.appendChild(valueText);
861
+ parent.title = title ?? createInspectorStatusTitle(access, effect);
862
+ }
863
+ function createInspectorReadonlyScalarControl(doc, value, access, effect = 'active', title) {
864
+ const valueElement = createInspectorReadonlyValueShell(doc, access, effect, title);
865
+ appendInspectorReadonlyTextValue(doc, valueElement, value, access, effect, title);
866
+ return valueElement;
867
+ }
868
+ function createInspectorReadonlyDetailsShell(doc, property, access) {
869
+ const effect = getInspectorPropertyEffect(property);
870
+ const warningLike = effect === 'default' || effect === 'derived' || access === 'runtime' || effect === 'runtime';
871
+ const unsupported = effect === 'unsupported';
872
+ const details = doc.createElement('details');
873
+ details.dataset.editorInspectorAccess = access;
874
+ details.dataset.editorInspectorEffect = effect;
875
+ details.title = createInspectorStatusTitle(access, effect, property.tooltip ?? property.disabledReason);
876
+ details.style.cssText = [
877
+ 'min-width:0',
878
+ 'max-width:100%',
879
+ 'padding:3px 6px',
880
+ `border:1px solid ${unsupported ? 'var(--fps-editor-danger-border)' : warningLike ? 'var(--fps-editor-warn-border)' : 'var(--fps-editor-readonly-border)'}`,
881
+ 'border-radius:3px',
882
+ `background:${unsupported ? 'var(--fps-editor-danger-soft)' : warningLike ? 'var(--fps-editor-warn-soft)' : 'var(--fps-editor-readonly-bg)'}`,
883
+ `color:${unsupported ? 'var(--fps-editor-danger-text)' : warningLike ? 'var(--fps-editor-warn)' : 'var(--fps-editor-readonly-text)'}`,
884
+ ].join(';');
885
+ return details;
886
+ }
591
887
  function createInspectorInputBase(doc, target, property) {
592
888
  const input = doc.createElement('input');
593
889
  input.value = property.mixed ? '' : String(property.value);
@@ -615,7 +911,14 @@ function createInspectorBooleanControl(doc, target, property) {
615
911
  const input = createInspectorInputBase(doc, target, property);
616
912
  input.type = 'checkbox';
617
913
  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';
914
+ input.style.cssText = [
915
+ 'width:16px',
916
+ 'height:16px',
917
+ 'accent-color:var(--fps-editor-accent)',
918
+ 'justify-self:start',
919
+ 'cursor:pointer',
920
+ 'box-shadow:0 0 0 1px var(--fps-editor-editable-border)',
921
+ ].join(';');
619
922
  return input;
620
923
  }
621
924
  function createInspectorEnumControl(doc, target, property) {
@@ -664,36 +967,30 @@ function createInspectorColorControl(doc, target, property) {
664
967
  const input = createInspectorInputBase(doc, target, property);
665
968
  input.type = 'color';
666
969
  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';
970
+ 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
971
  return input;
669
972
  }
670
973
  function createInspectorReadonlyControl(doc, _target, property) {
671
974
  if (!property.mixed && isExpandableInspectorValue(property.value)) {
672
975
  return createInspectorObjectReadonlyControl(doc, property);
673
976
  }
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;
977
+ const access = getInspectorPropertyAccess(property);
978
+ const effect = getInspectorPropertyEffect(property);
979
+ return createInspectorReadonlyScalarControl(doc, property.mixed ? '--' : formatLocalEditorBrowserInspectorValue(property.value), access, effect, createInspectorStatusTitle(access, effect, property.tooltip ?? property.disabledReason));
686
980
  }
687
981
  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(';');
982
+ const access = getInspectorPropertyAccess(property);
983
+ const effect = getInspectorPropertyEffect(property);
984
+ const details = createInspectorReadonlyDetailsShell(doc, property, access);
694
985
  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';
986
+ summary.style.cssText = 'cursor:pointer;display:flex;align-items:center;gap:5px;overflow:hidden;white-space:nowrap';
987
+ const icon = createLocalEditorIcon(doc, 'lock', { size: 11, strokeWidth: 2.3 });
988
+ icon.style.opacity = '0.78';
989
+ summary.appendChild(icon);
990
+ const summaryText = doc.createElement('span');
991
+ summaryText.textContent = formatInspectorObjectSummary(property.value);
992
+ summaryText.style.cssText = 'min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
993
+ summary.appendChild(summaryText);
697
994
  details.appendChild(summary);
698
995
  const pre = doc.createElement('pre');
699
996
  pre.textContent = formatLocalEditorBrowserInspectorValue(property.value);
@@ -704,15 +1001,13 @@ function createInspectorObjectReadonlyControl(doc, property) {
704
1001
  'word-break:break-word',
705
1002
  'margin:6px 0 0',
706
1003
  'padding:7px',
707
- 'border:1px solid var(--fps-editor-border)',
1004
+ `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
1005
  'border-radius:3px',
709
1006
  'background:var(--fps-editor-field)',
710
1007
  'font-size:11px',
711
1008
  'line-height:1.45',
712
1009
  ].join(';');
713
1010
  details.appendChild(pre);
714
- if (property.persistence === 'runtime')
715
- details.title = 'Runtime-only context';
716
1011
  return details;
717
1012
  }
718
1013
  export function applyLocalEditorBrowserInspectorControlBinding(element, target, property, options) {
@@ -724,30 +1019,38 @@ export function applyLocalEditorBrowserInspectorControlBinding(element, target,
724
1019
  element.dataset.serializedValueType = property.valueType;
725
1020
  element.dataset.serializedCommitMode = property.commitMode;
726
1021
  element.dataset.serializedPersistence = property.persistence;
1022
+ element.dataset.serializedEffect = getInspectorPropertyEffect(property);
1023
+ if (property.disabledReason)
1024
+ element.dataset.serializedDisabledReason = property.disabledReason;
727
1025
  if (options?.source)
728
1026
  element.dataset.serializedEditSource = options.source;
729
- element.title = property.tooltip ?? property.label;
1027
+ const access = getInspectorPropertyAccess(property);
1028
+ const effect = getInspectorPropertyEffect(property);
1029
+ element.title = access === 'editable' && effect === 'active'
1030
+ ? property.tooltip ?? property.label
1031
+ : createInspectorStatusTitle(access, effect, property.tooltip ?? property.disabledReason ?? property.label);
730
1032
  if ('disabled' in element) {
731
- element.disabled = property.readOnly === true || property.persistence !== 'document';
1033
+ element.disabled = !isInspectorPropertyEditable(property);
732
1034
  }
733
1035
  }
734
1036
  function createInspectorInputStyle() {
735
1037
  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',
1038
+ createEditorInputStyle(),
743
1039
  'padding:0 6px',
1040
+ 'background:var(--fps-editor-editable-bg)',
1041
+ 'border-color:var(--fps-editor-editable-border)',
1042
+ 'color:var(--fps-editor-text-strong)',
1043
+ 'box-shadow:var(--fps-editor-editable-shadow)',
744
1044
  ].join(';');
745
1045
  }
746
1046
  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));
1047
+ const valueElement = createInspectorReadonlyScalarControl(doc, value, 'readonly', 'active', 'Read-only summary');
1048
+ const row = createPropertyRow(doc, label, valueElement);
1049
+ row.dataset.editorInspectorAccess = 'readonly';
1050
+ const labelElement = row.firstElementChild;
1051
+ if (labelElement)
1052
+ labelElement.style.cssText = createInspectorPropertyLabelStyle('readonly', 'active');
1053
+ parent.appendChild(row);
751
1054
  }
752
1055
  function createLegacyInspectorObject(serializedObject) {
753
1056
  const targetIds = 'targetIds' in serializedObject ? serializedObject.targetIds : [serializedObject.targetId];