@eclipse-docks/core 0.7.127 → 0.7.130

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 (85) hide show
  1. package/dist/api/index.js +3 -3
  2. package/dist/api/services.d.ts +3 -1
  3. package/dist/api/services.d.ts.map +1 -1
  4. package/dist/{api-Da9zQcfd.js → api-oPg3Bv1S.js} +243 -264
  5. package/dist/api-oPg3Bv1S.js.map +1 -0
  6. package/dist/components/attribution.d.ts +1 -0
  7. package/dist/components/attribution.d.ts.map +1 -1
  8. package/dist/components/command.d.ts +0 -1
  9. package/dist/components/command.d.ts.map +1 -1
  10. package/dist/components/extensions.d.ts +3 -0
  11. package/dist/components/extensions.d.ts.map +1 -1
  12. package/dist/components/language-selector.d.ts.map +1 -1
  13. package/dist/components/layout-switcher.d.ts +1 -1
  14. package/dist/components/layout-switcher.d.ts.map +1 -1
  15. package/dist/components/npm-packages.d.ts +1 -0
  16. package/dist/components/npm-packages.d.ts.map +1 -1
  17. package/dist/components/release-history.d.ts +9 -2
  18. package/dist/components/release-history.d.ts.map +1 -1
  19. package/dist/components/tasks.d.ts.map +1 -1
  20. package/dist/core/apploader.d.ts +2 -9
  21. package/dist/core/apploader.d.ts.map +1 -1
  22. package/dist/core/contribution-targets.d.ts +1 -0
  23. package/dist/core/contribution-targets.d.ts.map +1 -1
  24. package/dist/core/dialogservice.d.ts +3 -1
  25. package/dist/core/dialogservice.d.ts.map +1 -1
  26. package/dist/core/dropdown-item.d.ts +8 -0
  27. package/dist/core/dropdown-item.d.ts.map +1 -1
  28. package/dist/core/layout-panels.d.ts +25 -0
  29. package/dist/core/layout-panels.d.ts.map +1 -0
  30. package/dist/dialogs/navigable-info-dialog.d.ts.map +1 -1
  31. package/dist/dialogs/version-info-dialog.d.ts +18 -2
  32. package/dist/dialogs/version-info-dialog.d.ts.map +1 -1
  33. package/dist/externals/lit.js +1 -1
  34. package/dist/externals/webawesome.js +1 -1
  35. package/dist/index.js +3 -3
  36. package/dist/layouts/standard-layout.d.ts +9 -1
  37. package/dist/layouts/standard-layout.d.ts.map +1 -1
  38. package/dist/parts/contextmenu.d.ts.map +1 -1
  39. package/dist/parts/index.js +1 -1
  40. package/dist/parts/part.d.ts +2 -0
  41. package/dist/parts/part.d.ts.map +1 -1
  42. package/dist/parts/resizable-grid.d.ts +10 -3
  43. package/dist/parts/resizable-grid.d.ts.map +1 -1
  44. package/dist/parts/tabs.d.ts +6 -0
  45. package/dist/parts/tabs.d.ts.map +1 -1
  46. package/dist/{parts-CRVxaDd-.js → parts-CAt8aeU8.js} +1050 -586
  47. package/dist/parts-CAt8aeU8.js.map +1 -0
  48. package/dist/{webawesome-Y1_HnKa_.js → webawesome-BGXn04Mw.js} +18 -10
  49. package/dist/webawesome-BGXn04Mw.js.map +1 -0
  50. package/package.json +1 -1
  51. package/src/api/services.ts +12 -0
  52. package/src/components/attribution.ts +2 -0
  53. package/src/components/command.ts +2 -8
  54. package/src/components/extensions.ts +20 -3
  55. package/src/components/language-selector.ts +0 -1
  56. package/src/components/layout-switcher.ts +39 -26
  57. package/src/components/npm-packages.ts +2 -0
  58. package/src/components/release-history.ts +29 -40
  59. package/src/components/tasks.ts +0 -1
  60. package/src/contributions/default-layout-contributions.ts +7 -43
  61. package/src/contributions/default-ui-contributions.ts +4 -4
  62. package/src/core/apploader.ts +7 -54
  63. package/src/core/contribution-targets.ts +1 -0
  64. package/src/core/dialogservice.ts +76 -19
  65. package/src/core/dropdown-item.ts +52 -17
  66. package/src/core/layout-panels.ts +121 -0
  67. package/src/dialogs/navigable-info-dialog.ts +5 -2
  68. package/src/dialogs/version-info-dialog.ts +163 -21
  69. package/src/icons/icons.txt +4 -2
  70. package/src/icons/layout-standard-bottom-panel.svg +4 -4
  71. package/src/icons/layout-standard-full.svg +4 -4
  72. package/src/icons/layout-standard-left-aux.svg +8 -0
  73. package/src/icons/layout-standard-left-sidebar.svg +5 -0
  74. package/src/icons/layout-standard-right-aux.svg +8 -0
  75. package/src/icons/layout-standard-right-sidebar.svg +5 -0
  76. package/src/layouts/standard-layout.ts +81 -5
  77. package/src/parts/contextmenu.ts +2 -1
  78. package/src/parts/part.ts +24 -0
  79. package/src/parts/resizable-grid.ts +125 -57
  80. package/src/parts/tabs.ts +103 -0
  81. package/dist/api-Da9zQcfd.js.map +0 -1
  82. package/dist/parts-CRVxaDd-.js.map +0 -1
  83. package/dist/webawesome-Y1_HnKa_.js.map +0 -1
  84. package/src/icons/layout-standard-bottom-sidebar.svg +0 -6
  85. package/src/icons/layout-standard.svg +0 -6
@@ -2,18 +2,18 @@ import { n as defaultLogger, o as rootContext, s as uiContext, t as createLogger
2
2
  import { T as toastInfo, _ as getContributionDisabled, a as TOPIC_COMMAND_REGISTERED, f as activeTasksSignal, g as contributionRegistry, h as TOPIC_CONTRIBUTEIONS_CHANGED, l as activeEditorSignal, n as watchSignal, o as commandRegistry, p as partDirtySignal, t as DocksWidget, u as activePartSignal, v as getContributionVisible, w as toastError, x as contributionTargetMappingRegistry, y as readReactiveBoolean } from "./widget-BqQtioK1.js";
3
3
  import { n as subscribe, t as publish } from "./events-B_ZxkU_G.js";
4
4
  import { a as File, i as Directory, l as TOPIC_WORKSPACE_CHANGED, m as persistenceService, o as FileContentType, p as workspaceService } from "./fs-access-DTXPsV1W.js";
5
- import "./webawesome-Y1_HnKa_.js";
5
+ import "./webawesome-BGXn04Mw.js";
6
6
  import { css, html, nothing, render } from "lit";
7
7
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
8
8
  import DOMPurify from "dompurify";
9
9
  import { marked } from "marked";
10
10
  import { customElement, property, state } from "lit/decorators.js";
11
11
  import _decorate from "@oxc-project/runtime/helpers/decorate";
12
+ import { ifDefined } from "lit/directives/if-defined.js";
12
13
  import { styleMap } from "lit/directives/style-map.js";
13
14
  import { createRef, ref } from "lit/directives/ref.js";
14
15
  import { when } from "lit/directives/when.js";
15
16
  import { repeat } from "lit/directives/repeat.js";
16
- import { ifDefined } from "lit/directives/if-defined.js";
17
17
  //#region \0rolldown/runtime.js
18
18
  var __defProp = Object.defineProperty;
19
19
  var __exportAll = (all, no_symbols) => {
@@ -45,6 +45,7 @@ var EDITOR_AREA_MAIN = "editor-area-main";
45
45
  var SIDEBAR_MAIN = "sidebar-main";
46
46
  var SIDEBAR_MAIN_BOTTOM = "sidebar-main-bottom";
47
47
  var SIDEBAR_AUXILIARY = "sidebar-auxiliary";
48
+ var SIDEBAR_AUXILIARY_BOTTOM = "sidebar-auxiliary-bottom";
48
49
  var PANEL_BOTTOM = "panel-bottom";
49
50
  //#endregion
50
51
  //#region src/core/constants.ts
@@ -56,6 +57,7 @@ var constants_exports = /* @__PURE__ */ __exportAll({
56
57
  MouseButton: () => MouseButton,
57
58
  PANEL_BOTTOM: () => PANEL_BOTTOM,
58
59
  SIDEBAR_AUXILIARY: () => SIDEBAR_AUXILIARY,
60
+ SIDEBAR_AUXILIARY_BOTTOM: () => SIDEBAR_AUXILIARY_BOTTOM,
59
61
  SIDEBAR_MAIN: () => SIDEBAR_MAIN,
60
62
  SIDEBAR_MAIN_BOTTOM: () => SIDEBAR_MAIN_BOTTOM,
61
63
  SIDEBAR_MAIN_TOOLBAR: () => SIDEBAR_MAIN_TOOLBAR,
@@ -271,7 +273,7 @@ var taskService = new TaskService();
271
273
  rootContext.put("taskService", taskService);
272
274
  //#endregion
273
275
  //#region src/core/esmsh-service.ts
274
- var logger$3 = createLogger("EsmShService");
276
+ var logger$4 = createLogger("EsmShService");
275
277
  var esmShService = new class EsmShService {
276
278
  static {
277
279
  this.ESM_SH_BASE = "https://esm.sh";
@@ -435,7 +437,7 @@ var esmShService = new class EsmShService {
435
437
  if (this.isHttpUrl(source)) return source;
436
438
  const parsed = this.parseSource(source);
437
439
  if (!parsed) {
438
- logger$3.warn(`Could not parse source identifier: ${source}`);
440
+ logger$4.warn(`Could not parse source identifier: ${source}`);
439
441
  return source;
440
442
  }
441
443
  return this.buildEsmShUrl(parsed, options);
@@ -1080,7 +1082,7 @@ var DocksDialogContent = class extends DocksElement {
1080
1082
  };
1081
1083
  //#endregion
1082
1084
  //#region src/core/dialogservice.ts
1083
- var logger$2 = createLogger("DialogService");
1085
+ var logger$3 = createLogger("DialogService");
1084
1086
  var DIALOG_CONTRIBUTION_TARGET = "dialogs";
1085
1087
  var OK_BUTTON = {
1086
1088
  id: "ok",
@@ -1106,6 +1108,20 @@ function getDialogContainer() {
1106
1108
  }
1107
1109
  return dialogContainer;
1108
1110
  }
1111
+ function findWaDialog(root) {
1112
+ if (root instanceof Element && root.tagName.toLowerCase() === "wa-dialog") return root;
1113
+ const children = root instanceof Document || root instanceof DocumentFragment || root instanceof Element ? Array.from(root.children) : root instanceof ShadowRoot ? Array.from(root.children) : [];
1114
+ for (const child of children) {
1115
+ if (child instanceof Element && child.tagName.toLowerCase() === "wa-dialog") return child;
1116
+ if (child instanceof Element && child.shadowRoot) {
1117
+ const nested = findWaDialog(child.shadowRoot);
1118
+ if (nested) return nested;
1119
+ }
1120
+ const nested = findWaDialog(child);
1121
+ if (nested) return nested;
1122
+ }
1123
+ return null;
1124
+ }
1109
1125
  var DialogService = class {
1110
1126
  constructor() {
1111
1127
  this.contributions = /* @__PURE__ */ new Map();
@@ -1126,15 +1142,15 @@ var DialogService = class {
1126
1142
  this.contributions.clear();
1127
1143
  for (const contribution of contributions) {
1128
1144
  if (!contribution.id) {
1129
- logger$2.warn("Dialog contribution missing id, skipping");
1145
+ logger$3.warn("Dialog contribution missing id, skipping");
1130
1146
  continue;
1131
1147
  }
1132
1148
  if (!contribution.component) {
1133
- logger$2.warn(`Dialog contribution "${contribution.id}" has no component function, skipping`);
1149
+ logger$3.warn(`Dialog contribution "${contribution.id}" has no component function, skipping`);
1134
1150
  continue;
1135
1151
  }
1136
- if (!contribution.onButton) {
1137
- logger$2.warn(`Dialog contribution "${contribution.id}" has no onButton callback, skipping`);
1152
+ if (!contribution.onButton && !contribution.selfContained) {
1153
+ logger$3.warn(`Dialog contribution "${contribution.id}" has no onButton callback, skipping`);
1138
1154
  continue;
1139
1155
  }
1140
1156
  this.contributions.set(contribution.id, contribution);
@@ -1143,50 +1159,68 @@ var DialogService = class {
1143
1159
  async open(dialogId, state) {
1144
1160
  const contribution = this.contributions.get(dialogId);
1145
1161
  if (!contribution) {
1146
- logger$2.error(`Dialog "${dialogId}" not found`);
1162
+ logger$3.error(`Dialog "${dialogId}" not found`);
1147
1163
  throw new Error(`Dialog "${dialogId}" not found`);
1148
1164
  }
1149
1165
  return new Promise((resolve) => {
1150
1166
  const container = getDialogContainer();
1151
- let isOpen = true;
1167
+ let closed = false;
1152
1168
  let dialogContentElement = null;
1153
1169
  const cleanup = async () => {
1154
- if (!isOpen) return;
1155
- isOpen = false;
1170
+ if (closed) return;
1171
+ closed = true;
1156
1172
  if (dialogContentElement) try {
1157
1173
  await dialogContentElement.dispose();
1158
1174
  } catch (error) {
1159
1175
  const errorMessage = error instanceof Error ? error.message : String(error);
1160
- logger$2.error(`Error disposing dialog content for "${dialogId}": ${errorMessage}`);
1176
+ logger$3.error(`Error disposing dialog content for "${dialogId}": ${errorMessage}`);
1161
1177
  }
1162
1178
  try {
1163
1179
  const result = dialogContentElement ? dialogContentElement.getResult() : void 0;
1164
- await contribution.onButton("close", result, stateWithClose);
1180
+ if (contribution.onButton) await contribution.onButton("close", result, stateWithClose);
1165
1181
  } catch (error) {
1166
1182
  const errorMessage = error instanceof Error ? error.message : String(error);
1167
- logger$2.error(`Error executing close callback for dialog "${dialogId}": ${errorMessage}`);
1183
+ logger$3.error(`Error executing close callback for dialog "${dialogId}": ${errorMessage}`);
1168
1184
  }
1169
1185
  render(html``, container);
1170
1186
  resolve();
1171
1187
  };
1188
+ const closeDialog = () => {
1189
+ if (closed) return;
1190
+ const dialog = findWaDialog(container);
1191
+ if (dialog && dialog.open !== false) {
1192
+ dialog.open = false;
1193
+ return;
1194
+ }
1195
+ cleanup();
1196
+ };
1197
+ const handleAfterHide = () => {
1198
+ cleanup();
1199
+ };
1172
1200
  const handleButtonClick = async (buttonId) => {
1201
+ if (!contribution.onButton) return;
1173
1202
  try {
1174
1203
  const result = dialogContentElement ? dialogContentElement.getResult() : void 0;
1175
- if (await contribution.onButton(buttonId, result, stateWithClose) !== false) cleanup();
1204
+ if (await contribution.onButton(buttonId, result, stateWithClose) !== false) closeDialog();
1176
1205
  } catch (error) {
1177
1206
  const errorMessage = error instanceof Error ? error.message : String(error);
1178
- logger$2.error(`Error executing button callback for dialog "${dialogId}": ${errorMessage}`);
1179
- cleanup();
1207
+ logger$3.error(`Error executing button callback for dialog "${dialogId}": ${errorMessage}`);
1208
+ closeDialog();
1180
1209
  }
1181
1210
  };
1182
1211
  const buttons = contribution.buttons && contribution.buttons.length > 0 ? contribution.buttons : [OK_BUTTON];
1183
- if (state && typeof state === "object") state.close = cleanup;
1184
1212
  const stateWithClose = {
1185
1213
  ...state,
1186
- close: cleanup
1214
+ close: closeDialog,
1215
+ onAfterHide: handleAfterHide
1187
1216
  };
1188
- render(html`
1189
- <wa-dialog label="${state && typeof state === "object" && typeof state.label === "string" ? state.label : contribution.label || dialogId}" open @wa-request-close=${cleanup}>
1217
+ if (state && typeof state === "object") {
1218
+ state.close = closeDialog;
1219
+ state.onAfterHide = handleAfterHide;
1220
+ }
1221
+ const dialogLabel = state && typeof state === "object" && typeof state.label === "string" ? state.label : contribution.label || dialogId;
1222
+ render(contribution.selfContained ? contribution.component(stateWithClose) : html`
1223
+ <wa-dialog label="${dialogLabel}" open @wa-after-hide=${handleAfterHide}>
1190
1224
  <style>
1191
1225
  .dialog-service-content {
1192
1226
  display: flex;
@@ -1218,13 +1252,14 @@ var DialogService = class {
1218
1252
  @dialog-cancel=${() => {
1219
1253
  const cancelButton = buttons.find((b) => b.id === "cancel");
1220
1254
  if (cancelButton) handleButtonClick(cancelButton.id);
1221
- else cleanup();
1255
+ else closeDialog();
1222
1256
  }}>
1223
1257
  ${contribution.component(state)}
1224
1258
 
1225
1259
  <div class="dialog-service-footer">
1226
1260
  ${buttons.map((button) => html`
1227
1261
  <wa-button
1262
+ data-dialog-button="${button.id}"
1228
1263
  variant="${button.variant || "default"}"
1229
1264
  ?disabled=${button.disabled}
1230
1265
  @click=${() => handleButtonClick(button.id)}
@@ -1747,7 +1782,7 @@ async function deleteIndexedDbWorkspaceData(directory) {
1747
1782
  }
1748
1783
  //#endregion
1749
1784
  //#region src/core/marketplaceregistry.ts
1750
- var logger$1 = createLogger("MarketplaceRegistry");
1785
+ var logger$2 = createLogger("MarketplaceRegistry");
1751
1786
  var TOPIC_MARKETPLACE_CHANGED = "events/marketplaceregistry/changed";
1752
1787
  var KEY_CATALOG_URLS = "marketplace.catalogUrls";
1753
1788
  var MarketplaceRegistry = class {
@@ -1756,7 +1791,7 @@ var MarketplaceRegistry = class {
1756
1791
  this.loadingPromises = /* @__PURE__ */ new Map();
1757
1792
  this.loadCatalogUrls().then(() => {
1758
1793
  this.refreshCatalogs().catch((err) => {
1759
- logger$1.error(`Failed to refresh catalogs on init: ${err.message}`);
1794
+ logger$2.error(`Failed to refresh catalogs on init: ${err.message}`);
1760
1795
  });
1761
1796
  });
1762
1797
  }
@@ -1765,7 +1800,7 @@ var MarketplaceRegistry = class {
1765
1800
  const urls = await appSettings.get(KEY_CATALOG_URLS);
1766
1801
  this.catalogUrls = Array.isArray(urls) ? urls : [];
1767
1802
  } catch (error) {
1768
- logger$1.error(`Failed to load catalog URLs: ${error}`);
1803
+ logger$2.error(`Failed to load catalog URLs: ${error}`);
1769
1804
  this.catalogUrls = [];
1770
1805
  }
1771
1806
  }
@@ -1779,28 +1814,28 @@ var MarketplaceRegistry = class {
1779
1814
  async addCatalogUrl(url) {
1780
1815
  if (!this.isValidUrl(url)) throw new Error(`Invalid catalog URL: ${url}`);
1781
1816
  if (this.catalogUrls.includes(url)) {
1782
- logger$1.debug(`Catalog URL already exists: ${url}`);
1817
+ logger$2.debug(`Catalog URL already exists: ${url}`);
1783
1818
  return;
1784
1819
  }
1785
1820
  this.catalogUrls.push(url);
1786
1821
  await this.saveCatalogUrls();
1787
- logger$1.debug(`Added catalog URL: ${url}`);
1822
+ logger$2.debug(`Added catalog URL: ${url}`);
1788
1823
  try {
1789
1824
  await this.refreshCatalogs();
1790
1825
  } catch (error) {
1791
- logger$1.warn(`Failed to refresh catalogs immediately after adding: ${error}`);
1826
+ logger$2.warn(`Failed to refresh catalogs immediately after adding: ${error}`);
1792
1827
  }
1793
1828
  }
1794
1829
  async addCatalogUrls(urls) {
1795
1830
  let added = 0;
1796
1831
  for (const url of urls) {
1797
1832
  if (!this.isValidUrl(url)) {
1798
- logger$1.warn(`Skipping invalid catalog URL: ${url}`);
1833
+ logger$2.warn(`Skipping invalid catalog URL: ${url}`);
1799
1834
  continue;
1800
1835
  }
1801
1836
  if (this.catalogUrls.includes(url)) continue;
1802
1837
  this.catalogUrls.push(url);
1803
- logger$1.debug(`Added catalog URL: ${url}`);
1838
+ logger$2.debug(`Added catalog URL: ${url}`);
1804
1839
  added++;
1805
1840
  }
1806
1841
  if (added === 0) return;
@@ -1808,7 +1843,7 @@ var MarketplaceRegistry = class {
1808
1843
  try {
1809
1844
  await this.refreshCatalogs();
1810
1845
  } catch (error) {
1811
- logger$1.warn(`Failed to refresh catalogs after adding URLs: ${error}`);
1846
+ logger$2.warn(`Failed to refresh catalogs after adding URLs: ${error}`);
1812
1847
  }
1813
1848
  }
1814
1849
  async removeCatalogUrl(url) {
@@ -1816,7 +1851,7 @@ var MarketplaceRegistry = class {
1816
1851
  if (index === -1) return;
1817
1852
  this.catalogUrls.splice(index, 1);
1818
1853
  await this.saveCatalogUrls();
1819
- logger$1.info(`Removed catalog URL: ${url}`);
1854
+ logger$2.info(`Removed catalog URL: ${url}`);
1820
1855
  }
1821
1856
  getCatalogUrls() {
1822
1857
  return [...this.catalogUrls];
@@ -1847,7 +1882,7 @@ var MarketplaceRegistry = class {
1847
1882
  extensions: data.extensions || []
1848
1883
  };
1849
1884
  } catch (error) {
1850
- logger$1.error(`Failed to fetch catalog from ${url}: ${error}`);
1885
+ logger$2.error(`Failed to fetch catalog from ${url}: ${error}`);
1851
1886
  throw error;
1852
1887
  } finally {
1853
1888
  this.loadingPromises.delete(url);
@@ -1858,7 +1893,7 @@ var MarketplaceRegistry = class {
1858
1893
  }
1859
1894
  async refreshCatalogs() {
1860
1895
  const promises = this.catalogUrls.map((url) => this.fetchCatalog(url).catch((error) => {
1861
- logger$1.warn(`Failed to refresh catalog ${url}: ${error.message}`);
1896
+ logger$2.warn(`Failed to refresh catalog ${url}: ${error.message}`);
1862
1897
  return null;
1863
1898
  }));
1864
1899
  const catalogs = await Promise.allSettled(promises);
@@ -1878,8 +1913,8 @@ var MarketplaceRegistry = class {
1878
1913
  });
1879
1914
  }
1880
1915
  });
1881
- logger$1.debug(`Refreshed ${this.catalogUrls.length} catalogs, ${registeredCount} extensions registered`);
1882
- if (registeredCount > 0) logger$1.info(`Marketplace: ${registeredCount} new extension(s) available`);
1916
+ logger$2.debug(`Refreshed ${this.catalogUrls.length} catalogs, ${registeredCount} extensions registered`);
1917
+ if (registeredCount > 0) logger$2.info(`Marketplace: ${registeredCount} new extension(s) available`);
1883
1918
  publish(TOPIC_MARKETPLACE_CHANGED, { type: "refreshed" });
1884
1919
  }
1885
1920
  getMarketplaceExtension(extensionId) {
@@ -1908,16 +1943,10 @@ rootContext.put("marketplaceRegistry", marketplaceRegistry);
1908
1943
  * - Application: App-specific code, gs-* components, contributions
1909
1944
  * - App Loader: Bridge between framework and application
1910
1945
  */
1911
- var logger = createLogger("AppLoader");
1946
+ var logger$1 = createLogger("AppLoader");
1912
1947
  function getLayoutIdFromApp(app) {
1913
- if (!app) return "standard";
1914
- const l = app.layout ?? app.layoutId;
1915
- return typeof l === "object" ? l.id : l ?? "standard";
1916
- }
1917
- function propsToAttributes(props) {
1918
- const out = {};
1919
- for (const [k, v] of Object.entries(props)) out[k] = typeof v === "boolean" ? v ? "true" : "false" : v;
1920
- return out;
1948
+ const layout = app?.layout ?? "standard";
1949
+ return typeof layout === "object" ? layout.id : layout;
1921
1950
  }
1922
1951
  /**
1923
1952
  * Extracts error message from an error object.
@@ -1946,9 +1975,6 @@ var appLoaderService = new class AppLoaderService {
1946
1975
  static {
1947
1976
  this.PREFERRED_APP_KEY = "preferredAppName";
1948
1977
  }
1949
- static {
1950
- this.PREFERRED_LAYOUT_KEY = "preferredLayoutId";
1951
- }
1952
1978
  /**
1953
1979
  * Register an application with the framework.
1954
1980
  * Optionally starts the apploader automatically after registration.
@@ -1967,7 +1993,8 @@ var appLoaderService = new class AppLoaderService {
1967
1993
  }
1968
1994
  app.name = app.name ?? "app";
1969
1995
  app.version = app.version ?? "0.0.0";
1970
- if (this.apps.has(app.name)) logger.warn(`App '${app.name}' is already registered. Overwriting.`);
1996
+ app.layout = app.layout ?? "standard";
1997
+ if (this.apps.has(app.name)) logger$1.warn(`App '${app.name}' is already registered. Overwriting.`);
1971
1998
  if (app.marketplaceCatalogUrls?.length) marketplaceRegistry.addCatalogUrls(app.marketplaceCatalogUrls).catch(() => {});
1972
1999
  this.apps.set(app.name, app);
1973
2000
  if (options?.defaultAppName) this.defaultAppName = options.defaultAppName;
@@ -1984,13 +2011,13 @@ var appLoaderService = new class AppLoaderService {
1984
2011
  */
1985
2012
  async start() {
1986
2013
  if (this.started) {
1987
- logger.debug("AppLoader already started");
2014
+ logger$1.debug("AppLoader already started");
1988
2015
  return;
1989
2016
  }
1990
2017
  this.started = true;
1991
2018
  const appIdFromUrl = new URLSearchParams(window.location.search).get("appId");
1992
2019
  const appIdFromPath = extractAppIdFromPath();
1993
- if (appIdFromPath) logger.info(`Extracted app ID from current page path: ${appIdFromPath}`);
2020
+ if (appIdFromPath) logger$1.info(`Extracted app ID from current page path: ${appIdFromPath}`);
1994
2021
  const appToLoad = await this.selectAppToLoad({
1995
2022
  appIdFromUrl,
1996
2023
  appIdFromPath
@@ -2018,10 +2045,10 @@ var appLoaderService = new class AppLoaderService {
2018
2045
  if (!app) throw new Error(`App '${appName}' not found. Make sure it's registered.`);
2019
2046
  this.dispatchLoadProgress("Starting…");
2020
2047
  if (this.currentApp) {
2021
- logger.info(`Disposing current app: ${this.currentApp.name}`);
2048
+ logger$1.info(`Disposing current app: ${this.currentApp.name}`);
2022
2049
  if (this.currentApp.dispose) await this.currentApp.dispose();
2023
2050
  if (this.currentApp.extensions && this.currentApp.extensions.length > 0) {
2024
- logger.info(`Disabling ${this.currentApp.extensions.length} extensions...`);
2051
+ logger$1.info(`Disabling ${this.currentApp.extensions.length} extensions...`);
2025
2052
  this.currentApp.extensions.forEach((extId) => {
2026
2053
  extensionRegistry.disable(extId);
2027
2054
  });
@@ -2037,19 +2064,19 @@ var appLoaderService = new class AppLoaderService {
2037
2064
  });
2038
2065
  }
2039
2066
  if (app.contributions) {
2040
- logger.info("Registering app contributions...");
2067
+ logger$1.info("Registering app contributions...");
2041
2068
  if (app.contributions.ui) {
2042
2069
  app.contributions.ui.forEach((contribution) => {
2043
2070
  const target = contribution.target;
2044
2071
  if (target) contributionRegistry.registerContribution(target, contribution);
2045
2072
  });
2046
- logger.info(`Registered ${app.contributions.ui.length} UI contributions`);
2073
+ logger$1.info(`Registered ${app.contributions.ui.length} UI contributions`);
2047
2074
  }
2048
2075
  if (app.contributions.extensions) {
2049
2076
  app.contributions.extensions.forEach((extension) => {
2050
2077
  extensionRegistry.registerExtension(extension);
2051
2078
  });
2052
- logger.info(`Registered ${app.contributions.extensions.length} app extensions`);
2079
+ logger$1.info(`Registered ${app.contributions.extensions.length} app extensions`);
2053
2080
  }
2054
2081
  }
2055
2082
  const extensionsSet = new Set(app.extensions || []);
@@ -2058,19 +2085,18 @@ var appLoaderService = new class AppLoaderService {
2058
2085
  if (app.extensions.length > 0) {
2059
2086
  this.dispatchLoadProgress("Enabling extensions…");
2060
2087
  await Promise.all(app.extensions.map((extId) => extensionRegistry.enableAsync(extId).catch((e) => {
2061
- logger.error(`Failed to load extension ${extId}: ${getErrorMessage(e)}`);
2088
+ logger$1.error(`Failed to load extension ${extId}: ${getErrorMessage(e)}`);
2062
2089
  })));
2063
2090
  }
2064
2091
  this.dispatchLoadProgress("Loading extensions…");
2065
2092
  await extensionRegistry.loadEnabledExtensions();
2066
2093
  if (app.initialize) {
2067
2094
  this.dispatchLoadProgress("Initializing…");
2068
- logger.info(`Initializing ${app.name}...`);
2095
+ logger$1.info(`Initializing ${app.name}...`);
2069
2096
  await app.initialize();
2070
2097
  }
2071
2098
  this.currentApp = app;
2072
- logger.info(`App ${app.name} loaded successfully`);
2073
- this.preferredLayoutId = await this.getPreferredLayoutId();
2099
+ logger$1.info(`App ${app.name} loaded successfully`);
2074
2100
  this.updateDocumentMetadata(app);
2075
2101
  if (container) {
2076
2102
  this.dispatchLoadProgress("Rendering layout…");
@@ -2103,19 +2129,17 @@ var appLoaderService = new class AppLoaderService {
2103
2129
  */
2104
2130
  renderApp(container) {
2105
2131
  if (!this.currentApp) throw new Error("No app loaded. Call loadApp() first.");
2106
- const layoutId = this.preferredLayoutId ?? getLayoutIdFromApp(this.currentApp);
2132
+ const layoutId = getLayoutIdFromApp(this.currentApp);
2107
2133
  const layouts = contributionRegistry.getContributions(SYSTEM_LAYOUTS);
2108
2134
  let layout = layouts.find((c) => c.id === layoutId);
2109
2135
  if (!layout) {
2110
- logger.warn(`Layout '${layoutId}' not found, falling back to 'standard'`);
2136
+ logger$1.warn(`Layout '${layoutId}' not found, falling back to 'standard'`);
2111
2137
  layout = layouts.find((c) => c.id === "standard");
2112
2138
  }
2113
2139
  if (!layout) throw new Error(`No layout found for layoutId '${layoutId}' and no 'standard' layout registered.`);
2114
2140
  const r = layout.component;
2115
2141
  let effectiveAttributes = {};
2116
2142
  if (r && typeof r === "object" && "tag" in r && r.attributes) effectiveAttributes = { ...r.attributes };
2117
- const appLayout = this.currentApp?.layout;
2118
- if (typeof appLayout === "object" && appLayout.id === layoutId && appLayout.props) Object.assign(effectiveAttributes, propsToAttributes(appLayout.props));
2119
2143
  container.innerHTML = "";
2120
2144
  if (typeof r === "string") {
2121
2145
  const el = document.createElement(r);
@@ -2128,7 +2152,7 @@ var appLoaderService = new class AppLoaderService {
2128
2152
  } else if (typeof r === "function") render(r(), container);
2129
2153
  else throw new Error(`Layout '${layout.id}' has invalid component.`);
2130
2154
  if (layout.onShow) requestAnimationFrame(() => {
2131
- Promise.resolve(layout.onShow()).catch((err) => logger.error(`Layout onShow failed for '${layout.id}': ${getErrorMessage(err)}`));
2155
+ Promise.resolve(layout.onShow()).catch((err) => logger$1.error(`Layout onShow failed for '${layout.id}': ${getErrorMessage(err)}`));
2132
2156
  });
2133
2157
  }
2134
2158
  /**
@@ -2150,7 +2174,7 @@ var appLoaderService = new class AppLoaderService {
2150
2174
  try {
2151
2175
  return await appSettings.get(AppLoaderService.PREFERRED_APP_KEY);
2152
2176
  } catch (error) {
2153
- logger.debug(`Failed to get preferred app ID from settings: ${getErrorMessage(error)}`);
2177
+ logger$1.debug(`Failed to get preferred app ID from settings: ${getErrorMessage(error)}`);
2154
2178
  return;
2155
2179
  }
2156
2180
  }
@@ -2162,9 +2186,9 @@ var appLoaderService = new class AppLoaderService {
2162
2186
  try {
2163
2187
  await appSettings.set(AppLoaderService.PREFERRED_APP_KEY, appId);
2164
2188
  this.defaultAppName = appId;
2165
- logger.info(`Set preferred app to: ${appId}`);
2189
+ logger$1.info(`Set preferred app to: ${appId}`);
2166
2190
  } catch (error) {
2167
- logger.error(`Failed to persist preferred app: ${getErrorMessage(error)}`);
2191
+ logger$1.error(`Failed to persist preferred app: ${getErrorMessage(error)}`);
2168
2192
  throw error;
2169
2193
  }
2170
2194
  }
@@ -2172,28 +2196,7 @@ var appLoaderService = new class AppLoaderService {
2172
2196
  return contributionRegistry.getContributions(SYSTEM_LAYOUTS);
2173
2197
  }
2174
2198
  getCurrentLayoutId() {
2175
- return this.preferredLayoutId ?? getLayoutIdFromApp(this.currentApp);
2176
- }
2177
- async getPreferredLayoutId() {
2178
- try {
2179
- return await appSettings.get(AppLoaderService.PREFERRED_LAYOUT_KEY);
2180
- } catch (error) {
2181
- logger.debug(`Failed to get preferred layout ID: ${getErrorMessage(error)}`);
2182
- return;
2183
- }
2184
- }
2185
- async setPreferredLayoutId(layoutId) {
2186
- if (!this.getRegisteredLayouts().some((l) => l.id === layoutId)) throw new Error(`Layout '${layoutId}' not found.`);
2187
- try {
2188
- await appSettings.set(AppLoaderService.PREFERRED_LAYOUT_KEY, layoutId);
2189
- this.preferredLayoutId = layoutId;
2190
- logger.info(`Set preferred layout to: ${layoutId}`);
2191
- if (this.currentApp && this.container) this.renderApp(this.container);
2192
- window.dispatchEvent(new CustomEvent("layout-changed", { detail: { layoutId } }));
2193
- } catch (error) {
2194
- logger.error(`Failed to persist preferred layout: ${getErrorMessage(error)}`);
2195
- throw error;
2196
- }
2199
+ return getLayoutIdFromApp(this.currentApp);
2197
2200
  }
2198
2201
  /**
2199
2202
  * Select which app to load based on priority:
@@ -2208,26 +2211,26 @@ var appLoaderService = new class AppLoaderService {
2208
2211
  if (appIdFromUrl) {
2209
2212
  const name = this.findAppNameBySegment(appIdFromUrl) ?? appIdFromUrl;
2210
2213
  if (this.apps.has(name)) {
2211
- logger.info(`Loading app specified by URL parameter 'appId': ${name}`);
2214
+ logger$1.info(`Loading app specified by URL parameter 'appId': ${name}`);
2212
2215
  return name;
2213
2216
  }
2214
- logger.warn(`App '${appIdFromUrl}' from URL parameter not found`);
2217
+ logger$1.warn(`App '${appIdFromUrl}' from URL parameter not found`);
2215
2218
  }
2216
2219
  if (appIdFromPath) {
2217
2220
  const name = this.findAppNameBySegment(appIdFromPath);
2218
2221
  if (name) {
2219
- logger.info(`Loading app from URL path: ${appIdFromPath}`);
2222
+ logger$1.info(`Loading app from URL path: ${appIdFromPath}`);
2220
2223
  return name;
2221
2224
  }
2222
- logger.debug(`App for path '${appIdFromPath}' not found, continuing search`);
2225
+ logger$1.debug(`App for path '${appIdFromPath}' not found, continuing search`);
2223
2226
  }
2224
2227
  const preferred = await this.getPreferredAppId();
2225
2228
  if (preferred && this.apps.has(preferred)) {
2226
- logger.info(`Loading preferred app from settings: ${preferred}`);
2229
+ logger$1.info(`Loading preferred app from settings: ${preferred}`);
2227
2230
  return preferred;
2228
2231
  }
2229
2232
  if (this.defaultAppName && this.apps.has(this.defaultAppName)) return this.defaultAppName;
2230
- if (this.defaultAppName) logger.warn(`Default app '${this.defaultAppName}' not found`);
2233
+ if (this.defaultAppName) logger$1.warn(`Default app '${this.defaultAppName}' not found`);
2231
2234
  const registeredApps = this.getRegisteredApps();
2232
2235
  if (registeredApps.length > 0) return registeredApps[0].name;
2233
2236
  }
@@ -2642,7 +2645,9 @@ var DocksNavigableInfoDialogContent = class DocksNavigableInfoDialogContent exte
2642
2645
  action.callback();
2643
2646
  }
2644
2647
  handleClose() {
2645
- if (this.closest("wa-dialog") && this.resolveCallback) this.resolveCallback();
2648
+ if (this.resolveCallback) this.resolveCallback();
2649
+ const dialog = this.closest("wa-dialog");
2650
+ if (dialog && dialog.open !== false) dialog.open = false;
2646
2651
  }
2647
2652
  static {
2648
2653
  this.styles = [...DocksDialogContent.styles, css`
@@ -2983,85 +2988,641 @@ function filebrowserDialog(mode = "either") {
2983
2988
  });
2984
2989
  }
2985
2990
  //#endregion
2986
- //#region src/dialogs/version-info-dialog.ts
2987
- var CLOSE_PRIMARY = {
2988
- ...CLOSE_BUTTON,
2989
- variant: "primary"
2990
- };
2991
- var DocksVersionInfoDialogContent = class DocksVersionInfoDialogContent extends DocksDialogContent {
2992
- static {
2993
- this.styles = [...DocksDialogContent.styles, css`
2994
- :host {
2995
- display: block;
2996
- }
2997
-
2998
- .dialog-body {
2999
- display: flex;
3000
- flex-direction: column;
3001
- gap: 0.75rem;
3002
- min-width: 32rem;
3003
- height: 600px;
3004
- }
3005
-
3006
- .dialog-body docks-tabs {
3007
- flex: 1;
3008
- min-height: 0;
3009
- }
3010
- `];
2991
+ //#region src/core/contribution-names.ts
2992
+ var VIEW_FILEBROWSER = "view.filebrowser";
2993
+ var VIEW_LOG_TERMINAL = "view.logTerminal";
2994
+ var TOOLBAR_INFO = "toolbar.info";
2995
+ var TOOLBAR_FAST_VIEWS = "toolbar.fastViews";
2996
+ var TOOLBAR_LANGUAGE_SELECTOR = "toolbar.languageSelector";
2997
+ /** Main toolbar (right): layout picker (`docks-layout-switcher`). */
2998
+ var TOOLBAR_LAYOUT_SWITCHER = "toolbar.layoutSwitcher";
2999
+ /** Main toolbar (right): dark/light theme toggle (`switch_theme`). */
3000
+ var TOOLBAR_THEME_SWITCHER = "toolbar.themeSwitcher";
3001
+ /** Main toolbar (right): document fullscreen toggle (`fullscreen`). */
3002
+ var TOOLBAR_FULLSCREEN = "toolbar.fullscreen";
3003
+ /** Bottom-center toolbar: focused part title / active part indicator (`docks-part-name`). */
3004
+ var TOOLBAR_ACTIVE_PART_NAME = "toolbar.activePartName";
3005
+ /** Bottom-center toolbar: background tasks / progress (`docks-tasks`). */
3006
+ var TOOLBAR_TASKS = "toolbar.tasks";
3007
+ /** Main sidebar header toolbar: open extensions (`open_extensions` command). */
3008
+ var SIDEBAR_TOOLBAR_EXTENSIONS = "sidebar.toolbar.extensions";
3009
+ /** Default file-type → icon mappings on `system.icons`. */
3010
+ var SYSTEM_DEFAULT_FILE_ICONS = "system.icons.default";
3011
+ var ABOUT_TAB_RELEASE = "about.tab.release";
3012
+ var ABOUT_TAB_PACKAGES = "about.tab.packages";
3013
+ var ABOUT_TAB_ATTRIBUTIONS = "about.tab.attributions";
3014
+ var ATTRIBUTION_ECLIPSE_DOCKS = "attribution.eclipse-docks";
3015
+ var CTXMENU_FILEBROWSER_RENAME = "contextmenu.filebrowser.rename";
3016
+ var CTXMENU_FILEBROWSER_DELETE = "contextmenu.filebrowser.delete";
3017
+ //#endregion
3018
+ //#region src/parts/container.ts
3019
+ var DocksContainer = class extends DocksElement {};
3020
+ //#endregion
3021
+ //#region src/parts/part.ts
3022
+ var DocksPart = class DocksPart extends DocksContainer {
3023
+ constructor(..._args) {
3024
+ super(..._args);
3025
+ this.scrollMode = "scroller";
3026
+ this.dirty = false;
3027
+ this.isEditor = false;
3028
+ this.embedded = false;
3029
+ this.onContentContextMenu = async (event) => {
3030
+ const contextMenu = this.renderRoot.querySelector("docks-contextmenu");
3031
+ if (!contextMenu) return;
3032
+ if (await contextMenu.show({
3033
+ x: event.clientX,
3034
+ y: event.clientY
3035
+ }, event)) event.preventDefault();
3036
+ };
3011
3037
  }
3012
- firstUpdated(changed) {
3013
- super.firstUpdated(changed);
3014
- const dialog = this.closest("wa-dialog");
3015
- if (dialog) {
3016
- dialog.style.setProperty("--width", "600px");
3017
- dialog.setAttribute("light-dismiss", "");
3038
+ getCommandStack() {
3039
+ return this.commandStack;
3040
+ }
3041
+ /**
3042
+ * Override this method to provide toolbar content for this part.
3043
+ * This is a lightweight alternative to registering toolbar contributions
3044
+ * for actions that are scoped to this part instance.
3045
+ *
3046
+ * IMPORTANT: Event handlers MUST use arrow functions to preserve the component's 'this' context.
3047
+ * The toolbar template is rendered in a different component (docks-toolbar), so direct method
3048
+ * references lose their binding.
3049
+ *
3050
+ * ✅ Correct:
3051
+ * @click=${() => this.myMethod()}
3052
+ * @click=${(e) => this.handleClick(e)}
3053
+ *
3054
+ * ❌ Wrong (this will be bound to the toolbar, not your component):
3055
+ * @click=${this.myMethod}
3056
+ *
3057
+ * Example:
3058
+ * ```typescript
3059
+ * protected renderToolbar() {
3060
+ * return html`
3061
+ * <wa-button @click=${() => this.save()} title="Save">
3062
+ * <wa-icon name="save"></wa-icon>
3063
+ * </wa-button>
3064
+ * `;
3065
+ * }
3066
+ * ```
3067
+ *
3068
+ * @returns TemplateResult with toolbar items, or nothing if no toolbar needed
3069
+ */
3070
+ renderToolbar() {
3071
+ return nothing;
3072
+ }
3073
+ /**
3074
+ * Activates the tab that contains this part by walking up the DOM to the first
3075
+ * docks-tabs ancestor and activating the tab panel that contains this part.
3076
+ * Crosses Shadow DOM boundaries (e.g. wa-tab-group) via getRootNode().host.
3077
+ * No-op if this part is not inside a docks-tabs / wa-tab-panel.
3078
+ */
3079
+ activateContainingTab() {
3080
+ let el = this;
3081
+ let panelName = null;
3082
+ let tabsEl = null;
3083
+ while (el) {
3084
+ const tag = el.tagName?.toLowerCase();
3085
+ if (tag === "wa-tab-panel") panelName = el.getAttribute("name");
3086
+ if (tag === "docks-tabs") {
3087
+ tabsEl = el;
3088
+ break;
3089
+ }
3090
+ const parent = el.parentElement;
3091
+ if (parent) el = parent;
3092
+ else {
3093
+ const root = el.getRootNode();
3094
+ el = root instanceof ShadowRoot ? root.host : null;
3095
+ }
3018
3096
  }
3097
+ if (tabsEl && panelName != null && panelName !== "") tabsEl.activate(panelName);
3098
+ }
3099
+ /**
3100
+ * Override this method to provide context menu content for this part.
3101
+ * This is a lightweight alternative to registering context menu contributions
3102
+ * for actions that are scoped to this part instance.
3103
+ *
3104
+ * IMPORTANT: Event handlers MUST use arrow functions to preserve the component's 'this' context.
3105
+ * The context menu is rendered in a different component (contextmenu), so direct method
3106
+ * references lose their binding.
3107
+ *
3108
+ * ✅ Correct:
3109
+ * @click=${() => this.myMethod()}
3110
+ * @click=${(e) => this.handleClick(e)}
3111
+ *
3112
+ * ❌ Wrong (this will be bound to the context menu, not your component):
3113
+ * @click=${this.myMethod}
3114
+ *
3115
+ * Example:
3116
+ * ```typescript
3117
+ * protected renderContextMenu() {
3118
+ * return html`
3119
+ * <wa-dropdown-item @click=${() => this.open()}>
3120
+ * <wa-icon name="folder-open"></wa-icon>
3121
+ * Open
3122
+ * </wa-dropdown-item>
3123
+ * <wa-divider></wa-divider>
3124
+ * <wa-dropdown-item @click=${() => this.delete()}>
3125
+ * <wa-icon name="trash"></wa-icon>
3126
+ * Delete
3127
+ * </wa-dropdown-item>
3128
+ * `;
3129
+ * }
3130
+ * ```
3131
+ *
3132
+ * @returns TemplateResult with context menu items, or nothing if no context menu needed
3133
+ */
3134
+ renderContextMenu() {
3135
+ return nothing;
3136
+ }
3137
+ renderContent() {
3138
+ return nothing;
3139
+ }
3140
+ /** Part-level toolbar contributions (`toolbar:${partId}`). */
3141
+ getToolbarTarget() {
3142
+ const contributionKey = this.tabContribution?.editorId ?? this.id ?? this.tabContribution?.name;
3143
+ return contributionKey ? `toolbar:${contributionKey}` : void 0;
3144
+ }
3145
+ /** Part-level context-menu contributions (`contextmenu:${partId}`). */
3146
+ getContextMenuTarget() {
3147
+ const contributionKey = this.tabContribution?.editorId ?? this.id ?? this.tabContribution?.name;
3148
+ return contributionKey ? `contextmenu:${contributionKey}` : void 0;
3149
+ }
3150
+ syncIsEditorCapability() {
3151
+ const next = this.save !== DocksPart.prototype.save;
3152
+ if (next === this.isEditor) return;
3153
+ this.isEditor = next;
3154
+ }
3155
+ maybeActivateForCoupledEditors() {
3156
+ const coupled = this.tabContribution?.coupledEditors;
3157
+ if (!coupled?.length) return;
3158
+ const active = activeEditorSignal.get();
3159
+ if (!(active instanceof DocksPart)) return;
3160
+ const editorId = active.tabContribution?.editorId;
3161
+ if (!editorId || !coupled.includes(editorId)) return;
3162
+ this.activateContainingTab();
3019
3163
  }
3020
3164
  render() {
3021
- const app = appLoaderService.getCurrentApp();
3165
+ const toolbarTarget = this.getToolbarTarget();
3166
+ const contextMenuTarget = this.getContextMenuTarget();
3167
+ const toolbarEnabled = this.tabContribution?.toolbar !== false;
3168
+ const contextMenuEnabled = this.tabContribution?.contextMenu !== false;
3169
+ const scrollMode = this.scrollMode;
3170
+ const scopeTokens = this.isEditor ? ["system.editors", ".system.editors"] : [];
3171
+ const content = this.renderContent();
3172
+ const contentNode = scrollMode === "scroller" ? html`
3173
+ <wa-scroller class="part-content-scroll" orientation="vertical">
3174
+ <div class="part-content-inner">${content}</div>
3175
+ </wa-scroller>
3176
+ ` : html`<div class="part-content-inner">${content}</div>`;
3022
3177
  return html`
3023
- <div class="dialog-body">
3024
- ${app?.description ? html`<small>${app.description}</small>` : ""}
3025
- <docks-tabs id=${ABOUT_TABS}></docks-tabs>
3178
+ <div class="part-shell">
3179
+ ${toolbarEnabled ? html`
3180
+ <docks-toolbar
3181
+ class="part-toolbar"
3182
+ id=${ifDefined(toolbarTarget)}
3183
+ .scopeTokens=${scopeTokens}
3184
+ .partToolbarRenderer=${() => this.renderToolbar()}>
3185
+ </docks-toolbar>
3186
+ ` : nothing}
3187
+ <div class="part-content ${scrollMode === "native" ? "native-scroll" : ""}" @contextmenu=${contextMenuEnabled ? this.onContentContextMenu : void 0}>
3188
+ ${contentNode}
3189
+ </div>
3190
+ ${contextMenuEnabled ? html`
3191
+ <docks-contextmenu
3192
+ id=${ifDefined(contextMenuTarget)}
3193
+ .scopeTokens=${scopeTokens}
3194
+ .partContextMenuRenderer=${() => this.renderContextMenu()}>
3195
+ </docks-contextmenu>
3196
+ ` : nothing}
3026
3197
  </div>
3027
3198
  `;
3028
3199
  }
3029
- };
3030
- DocksVersionInfoDialogContent = _decorate([customElement("docks-version-info-dialog-content")], DocksVersionInfoDialogContent);
3031
- contributionRegistry.registerContribution(DIALOG_CONTRIBUTION_TARGET, {
3032
- id: "version-info",
3033
- label: "About",
3034
- buttons: [CLOSE_PRIMARY],
3035
- component: () => html`<docks-version-info-dialog-content></docks-version-info-dialog-content>`,
3036
- onButton: async () => true
3037
- });
3038
- function versionInfoDialog() {
3039
- const app = appLoaderService.getCurrentApp();
3040
- return dialogService.open("version-info", { label: `About ${app?.name ?? ""} - ${app?.version ?? "0.0.0"}` });
3041
- }
3042
- //#endregion
3043
- //#region src/core/dropdown-menu-utils.ts
3044
- function isOpenDropdown(element) {
3045
- if (element.localName !== "wa-dropdown") return false;
3046
- const dropdown = element;
3047
- return dropdown.open === true || dropdown.hasAttribute("open");
3048
- }
3049
- function elementsFromEventPath(event) {
3050
- const fromComposedPath = event.composedPath().filter((node) => node instanceof Element);
3051
- if (fromComposedPath.length > 0) return fromComposedPath;
3052
- if (!(event.target instanceof Element)) return [];
3053
- const path = [];
3054
- let current = event.target;
3055
- while (current) {
3056
- path.push(current);
3057
- const root = current.getRootNode();
3058
- if (root instanceof ShadowRoot) {
3059
- current = root.host;
3060
- continue;
3061
- }
3062
- current = current.parentElement;
3063
- }
3064
- return path;
3200
+ updated(_changedProperties) {
3201
+ super.updated(_changedProperties);
3202
+ this.syncIsEditorCapability();
3203
+ if (_changedProperties.has("tabContribution")) this.maybeActivateForCoupledEditors();
3204
+ if (_changedProperties.has("dirty")) {
3205
+ if (_changedProperties.get("dirty") !== void 0) this.dispatchEvent(new CustomEvent("dirty", {
3206
+ detail: this.dirty,
3207
+ bubbles: true
3208
+ }));
3209
+ }
3210
+ }
3211
+ doClose() {}
3212
+ disconnectedCallback() {
3213
+ super.disconnectedCallback();
3214
+ }
3215
+ close() {
3216
+ this.doClose();
3217
+ }
3218
+ connectedCallback() {
3219
+ super.connectedCallback();
3220
+ this.syncIsEditorCapability();
3221
+ queueMicrotask(() => this.syncIsEditorCapability());
3222
+ this.watch(activeEditorSignal, () => this.maybeActivateForCoupledEditors());
3223
+ }
3224
+ save() {}
3225
+ isDirty() {
3226
+ return this.dirty;
3227
+ }
3228
+ markDirty(dirty) {
3229
+ this.dirty = dirty;
3230
+ partDirtySignal.set(null);
3231
+ partDirtySignal.set(this);
3232
+ activePartSignal.set(null);
3233
+ activePartSignal.set(this);
3234
+ }
3235
+ static {
3236
+ this.baseStyles = css`
3237
+ :host {
3238
+ display: block;
3239
+ }
3240
+
3241
+ .part-shell {
3242
+ display: grid;
3243
+ grid-template-rows: auto minmax(0, 1fr);
3244
+ height: 100%;
3245
+ width: 100%;
3246
+ position: relative;
3247
+ overflow: hidden;
3248
+ }
3249
+
3250
+ .part-content {
3251
+ min-height: 0;
3252
+ overflow: hidden;
3253
+ position: relative;
3254
+ }
3255
+
3256
+ .part-content.native-scroll {
3257
+ overflow: auto;
3258
+ }
3259
+
3260
+ .part-content-scroll {
3261
+ width: 100%;
3262
+ height: 100%;
3263
+ }
3264
+
3265
+ .part-content-inner {
3266
+ height: 100%;
3267
+ min-height: 100%;
3268
+ }
3269
+
3270
+ :host([embedded]) {
3271
+ height: auto;
3272
+ }
3273
+
3274
+ :host([embedded]) .part-shell {
3275
+ height: auto;
3276
+ overflow: visible;
3277
+ grid-template-rows: auto;
3278
+ }
3279
+
3280
+ :host([embedded]) .part-content {
3281
+ overflow: visible;
3282
+ min-height: 0;
3283
+ }
3284
+
3285
+ :host([embedded]) .part-content-inner {
3286
+ height: auto;
3287
+ min-height: 0;
3288
+ }
3289
+
3290
+ .part-toolbar {
3291
+ min-height: 0;
3292
+ }
3293
+ `;
3294
+ }
3295
+ static finalizeStyles(styles) {
3296
+ const own = super.finalizeStyles(styles);
3297
+ return [DocksPart.baseStyles, ...own];
3298
+ }
3299
+ };
3300
+ _decorate([property()], DocksPart.prototype, "dirty", void 0);
3301
+ _decorate([property({ attribute: false })], DocksPart.prototype, "tabContribution", void 0);
3302
+ _decorate([property({
3303
+ type: Boolean,
3304
+ attribute: false
3305
+ })], DocksPart.prototype, "isEditor", void 0);
3306
+ _decorate([property({
3307
+ type: Boolean,
3308
+ reflect: true
3309
+ })], DocksPart.prototype, "embedded", void 0);
3310
+ //#endregion
3311
+ //#region src/components/release-history.ts
3312
+ var logger = createLogger("ReleaseHistory");
3313
+ async function loadReleases(releaseHistory) {
3314
+ if (!releaseHistory) return [];
3315
+ if (typeof releaseHistory !== "function") return releaseHistory;
3316
+ try {
3317
+ return await releaseHistory();
3318
+ } catch (error) {
3319
+ logger.error(`Failed to load release history from app: ${error instanceof Error ? error.message : String(error)}`);
3320
+ return [];
3321
+ }
3322
+ }
3323
+ function semverEqual(a, b) {
3324
+ return a.replace(/^v/i, "") === b.replace(/^v/i, "");
3325
+ }
3326
+ function isNewerThan(releaseTag, currentVersion) {
3327
+ const currentParts = currentVersion.replace(/^v/i, "").split(".").map(Number);
3328
+ const releaseParts = releaseTag.replace(/^v/i, "").split(".").map(Number);
3329
+ for (let i = 0; i < Math.max(currentParts.length, releaseParts.length); i++) {
3330
+ const current = currentParts[i] || 0;
3331
+ const releasePart = releaseParts[i] || 0;
3332
+ if (releasePart > current) return true;
3333
+ if (releasePart < current) return false;
3334
+ }
3335
+ return false;
3336
+ }
3337
+ var DocksReleaseHistory = class DocksReleaseHistory extends DocksPart {
3338
+ constructor(..._args) {
3339
+ super(..._args);
3340
+ this.scrollMode = "none";
3341
+ this.appVersion = "0.0.0";
3342
+ this.releases = [];
3343
+ this.currentReleaseIndex = 0;
3344
+ }
3345
+ static {
3346
+ this.styles = [css`
3347
+ .release-panel {
3348
+ display: flex;
3349
+ flex-direction: column;
3350
+ gap: 0.5rem;
3351
+ padding: 0.75rem;
3352
+ box-sizing: border-box;
3353
+ }
3354
+
3355
+ .release-body {
3356
+ white-space: normal;
3357
+ }
3358
+ `];
3359
+ }
3360
+ doInitUI() {
3361
+ this.load();
3362
+ }
3363
+ async load() {
3364
+ const app = appLoaderService.getCurrentApp();
3365
+ this.appVersion = app?.version ?? "0.0.0";
3366
+ this.releases = await loadReleases(app?.releaseHistory);
3367
+ const currentIndex = this.releases.findIndex((release) => semverEqual(release.tag_name, this.appVersion));
3368
+ this.currentReleaseIndex = currentIndex >= 0 ? currentIndex : 0;
3369
+ this.notifyChanged();
3370
+ }
3371
+ getNavState() {
3372
+ return {
3373
+ hasMultiple: this.releases.length > 1,
3374
+ atOldest: this.currentReleaseIndex >= this.releases.length - 1,
3375
+ atNewest: this.currentReleaseIndex <= 0
3376
+ };
3377
+ }
3378
+ showOlder() {
3379
+ if (this.currentReleaseIndex < this.releases.length - 1) {
3380
+ this.currentReleaseIndex += 1;
3381
+ this.notifyChanged();
3382
+ }
3383
+ }
3384
+ showNewer() {
3385
+ if (this.currentReleaseIndex > 0) {
3386
+ this.currentReleaseIndex -= 1;
3387
+ this.notifyChanged();
3388
+ }
3389
+ }
3390
+ notifyChanged() {
3391
+ this.dispatchEvent(new CustomEvent("release-history-changed", {
3392
+ bubbles: true,
3393
+ composed: true
3394
+ }));
3395
+ }
3396
+ renderContent() {
3397
+ const release = this.releases[this.currentReleaseIndex];
3398
+ if (!release) return html`<div class="release-panel">No release history available.</div>`;
3399
+ const isCurrent = semverEqual(release.tag_name, this.appVersion);
3400
+ const showUpdateHint = !isCurrent && isNewerThan(release.tag_name, this.appVersion);
3401
+ const bodyHtml = release.body ? parseMarkdownHtml(release.body) : "";
3402
+ return html`
3403
+ <div class="release-panel">
3404
+ <p>
3405
+ <strong>Version:</strong> ${release.tag_name}${isCurrent ? " (Current)" : ""}
3406
+ </p>
3407
+ <p>
3408
+ <strong>Released:</strong>
3409
+ <wa-format-date
3410
+ .date=${release.published_at}
3411
+ month="short"
3412
+ day="numeric"
3413
+ year="numeric"
3414
+ ></wa-format-date>
3415
+ </p>
3416
+ ${showUpdateHint ? html`
3417
+ <wa-callout variant="warning">
3418
+ <wa-icon slot="icon" name="triangle-exclamation"></wa-icon>
3419
+ A newer release is available than the version you are running.
3420
+ Use <strong>Update Available</strong> in the toolbar when it appears.
3421
+ </wa-callout>
3422
+ ` : ""}
3423
+ ${bodyHtml ? html`<div class="release-body">${unsafeHTML(bodyHtml)}</div>` : ""}
3424
+ </div>
3425
+ `;
3426
+ }
3427
+ };
3428
+ _decorate([state()], DocksReleaseHistory.prototype, "appVersion", void 0);
3429
+ _decorate([state()], DocksReleaseHistory.prototype, "releases", void 0);
3430
+ _decorate([state()], DocksReleaseHistory.prototype, "currentReleaseIndex", void 0);
3431
+ DocksReleaseHistory = _decorate([customElement("docks-release-history")], DocksReleaseHistory);
3432
+ //#endregion
3433
+ //#region src/dialogs/version-info-dialog.ts
3434
+ var EMPTY_NAV = {
3435
+ hasMultiple: false,
3436
+ atOldest: true,
3437
+ atNewest: true
3438
+ };
3439
+ var DocksVersionInfoDialog = class DocksVersionInfoDialog extends DocksDialogContent {
3440
+ constructor(..._args) {
3441
+ super(..._args);
3442
+ this.label = "";
3443
+ this.activeTab = null;
3444
+ this.releaseNav = EMPTY_NAV;
3445
+ this.onReleaseHistoryChanged = () => {
3446
+ this.syncReleaseNav();
3447
+ };
3448
+ }
3449
+ static {
3450
+ this.styles = [...DocksDialogContent.styles, css`
3451
+ :host {
3452
+ display: block;
3453
+ }
3454
+
3455
+ wa-dialog {
3456
+ --width: 600px;
3457
+ }
3458
+
3459
+ wa-dialog::part(body) {
3460
+ display: flex;
3461
+ flex-direction: column;
3462
+ min-height: 0;
3463
+ overflow: hidden;
3464
+ }
3465
+
3466
+ .dialog-body {
3467
+ display: flex;
3468
+ flex-direction: column;
3469
+ gap: 0.75rem;
3470
+ min-width: 32rem;
3471
+ height: min(70vh, 36rem);
3472
+ min-height: min(70vh, 36rem);
3473
+ overflow: hidden;
3474
+ }
3475
+
3476
+ .dialog-body docks-tabs {
3477
+ flex: 1 1 auto;
3478
+ min-height: 0;
3479
+ overflow: hidden;
3480
+ }
3481
+
3482
+ .dialog-footer {
3483
+ display: flex;
3484
+ gap: 0.5rem;
3485
+ justify-content: flex-end;
3486
+ width: 100%;
3487
+ }
3488
+ `];
3489
+ }
3490
+ firstUpdated(changed) {
3491
+ super.firstUpdated(changed);
3492
+ this.addEventListener("release-history-changed", this.onReleaseHistoryChanged);
3493
+ this.updateComplete.then(() => {
3494
+ this.attachTabListener();
3495
+ this.syncActiveTab();
3496
+ this.syncReleaseNav();
3497
+ });
3498
+ }
3499
+ disconnectedCallback() {
3500
+ this.removeEventListener("release-history-changed", this.onReleaseHistoryChanged);
3501
+ super.disconnectedCallback();
3502
+ }
3503
+ attachTabListener() {
3504
+ const tabGroup = this.getAboutTabs()?.shadowRoot?.querySelector("wa-tab-group");
3505
+ if (!tabGroup || tabGroup.hasAttribute("data-version-info-listener")) return;
3506
+ tabGroup.setAttribute("data-version-info-listener", "");
3507
+ tabGroup.addEventListener("wa-tab-show", (event) => {
3508
+ const name = event.detail?.name;
3509
+ if (name) this.activeTab = name;
3510
+ });
3511
+ }
3512
+ getAboutTabs() {
3513
+ return this.renderRoot.querySelector("docks-tabs");
3514
+ }
3515
+ getReleaseHistory() {
3516
+ const part = this.getAboutTabs()?.getTabPart(ABOUT_TAB_RELEASE);
3517
+ return part instanceof DocksReleaseHistory ? part : null;
3518
+ }
3519
+ syncActiveTab() {
3520
+ const tabGroup = this.getAboutTabs()?.shadowRoot?.querySelector("wa-tab-group");
3521
+ this.activeTab = tabGroup?.getAttribute("active") || tabGroup?.active || null;
3522
+ }
3523
+ syncReleaseNav() {
3524
+ this.releaseNav = this.getReleaseHistory()?.getNavState() ?? EMPTY_NAV;
3525
+ }
3526
+ handleClose() {
3527
+ const dialog = this.renderRoot.querySelector("wa-dialog");
3528
+ if (dialog && dialog.open !== false) {
3529
+ dialog.open = false;
3530
+ return;
3531
+ }
3532
+ this.close?.();
3533
+ }
3534
+ handleAfterHide() {
3535
+ this.onAfterHide?.();
3536
+ }
3537
+ showOlderRelease() {
3538
+ this.getReleaseHistory()?.showOlder();
3539
+ }
3540
+ showNewerRelease() {
3541
+ this.getReleaseHistory()?.showNewer();
3542
+ }
3543
+ render() {
3544
+ const app = appLoaderService.getCurrentApp();
3545
+ const showReleaseNav = this.activeTab === "about.tab.release" && this.releaseNav.hasMultiple;
3546
+ return html`
3547
+ <wa-dialog
3548
+ label=${this.label}
3549
+ open
3550
+ light-dismiss
3551
+ with-footer
3552
+ @wa-after-hide=${this.handleAfterHide}
3553
+ >
3554
+ <div class="dialog-body">
3555
+ ${app?.description ? html`<small>${app.description}</small>` : ""}
3556
+ <docks-tabs id=${ABOUT_TABS} fill-height></docks-tabs>
3557
+ </div>
3558
+ <div slot="footer" class="dialog-footer">
3559
+ ${showReleaseNav ? html`
3560
+ <wa-button
3561
+ variant="default"
3562
+ ?disabled=${this.releaseNav.atOldest}
3563
+ @click=${this.showOlderRelease}
3564
+ >
3565
+ ← Previous
3566
+ </wa-button>
3567
+ <wa-button
3568
+ variant="default"
3569
+ ?disabled=${this.releaseNav.atNewest}
3570
+ @click=${this.showNewerRelease}
3571
+ >
3572
+ Next →
3573
+ </wa-button>
3574
+ ` : nothing}
3575
+ <wa-button variant="primary" @click=${this.handleClose}>Close</wa-button>
3576
+ </div>
3577
+ </wa-dialog>
3578
+ `;
3579
+ }
3580
+ };
3581
+ _decorate([property({ type: String })], DocksVersionInfoDialog.prototype, "label", void 0);
3582
+ _decorate([property({ attribute: false })], DocksVersionInfoDialog.prototype, "close", void 0);
3583
+ _decorate([property({ attribute: false })], DocksVersionInfoDialog.prototype, "onAfterHide", void 0);
3584
+ _decorate([state()], DocksVersionInfoDialog.prototype, "activeTab", void 0);
3585
+ _decorate([state()], DocksVersionInfoDialog.prototype, "releaseNav", void 0);
3586
+ DocksVersionInfoDialog = _decorate([customElement("docks-version-info-dialog")], DocksVersionInfoDialog);
3587
+ contributionRegistry.registerContribution(DIALOG_CONTRIBUTION_TARGET, {
3588
+ id: "version-info",
3589
+ label: "About",
3590
+ selfContained: true,
3591
+ component: (state) => html`
3592
+ <docks-version-info-dialog
3593
+ .label=${state?.label ?? ""}
3594
+ .close=${state?.close}
3595
+ .onAfterHide=${state?.onAfterHide}
3596
+ ></docks-version-info-dialog>
3597
+ `
3598
+ });
3599
+ function versionInfoDialog() {
3600
+ const app = appLoaderService.getCurrentApp();
3601
+ return dialogService.open("version-info", { label: `About ${app?.name ?? ""} - ${app?.version ?? "0.0.0"}` });
3602
+ }
3603
+ //#endregion
3604
+ //#region src/core/dropdown-menu-utils.ts
3605
+ function isOpenDropdown(element) {
3606
+ if (element.localName !== "wa-dropdown") return false;
3607
+ const dropdown = element;
3608
+ return dropdown.open === true || dropdown.hasAttribute("open");
3609
+ }
3610
+ function elementsFromEventPath(event) {
3611
+ const fromComposedPath = event.composedPath().filter((node) => node instanceof Element);
3612
+ if (fromComposedPath.length > 0) return fromComposedPath;
3613
+ if (!(event.target instanceof Element)) return [];
3614
+ const path = [];
3615
+ let current = event.target;
3616
+ while (current) {
3617
+ path.push(current);
3618
+ const root = current.getRootNode();
3619
+ if (root instanceof ShadowRoot) {
3620
+ current = root.host;
3621
+ continue;
3622
+ }
3623
+ current = current.parentElement;
3624
+ }
3625
+ return path;
3065
3626
  }
3066
3627
  function elementTargetsOpenDropdown(element) {
3067
3628
  if (isOpenDropdown(element)) return true;
@@ -3358,21 +3919,36 @@ DocksToolbar = _decorate([customElement("docks-toolbar")], DocksToolbar);
3358
3919
  * A part can expose both surfaces with the same part id but different prefixes, registering
3359
3920
  * different contributions on each. Feature-scoped targets (e.g. `filebrowser.create`) may also
3360
3921
  * be referenced explicitly from either surface when several controls share the same item set.
3922
+ *
3923
+ * Command-backed items rely on the parent {@link https://webawesome.com/docs/components/dropdown | wa-dropdown}
3924
+ * `wa-select` event (see {@link handleDropdownWaSelect}) so the popup closes cleanly before
3925
+ * native dialogs such as `showDirectoryPicker` open.
3361
3926
  */
3362
- function closeParentDropdown(event) {
3363
- const dropdown = event.target?.closest("wa-dropdown");
3364
- if (dropdown && dropdown.open !== void 0) dropdown.open = false;
3927
+ function parseDropdownItemParams(item) {
3928
+ const raw = item.getAttribute("data-params");
3929
+ if (!raw) return {};
3930
+ try {
3931
+ return JSON.parse(raw);
3932
+ } catch {
3933
+ return {};
3934
+ }
3935
+ }
3936
+ function getDropdownItemCommand(item) {
3937
+ return item.getAttribute("data-cmd") || item.getAttribute("value") || void 0;
3938
+ }
3939
+ /** Handles `wa-select` from a parent `wa-dropdown` and runs the backing command. */
3940
+ function handleDropdownWaSelect(event) {
3941
+ const item = event.detail?.item;
3942
+ if (!item || item.disabled || item.hasAttribute("disabled")) return;
3943
+ const cmd = getDropdownItemCommand(item);
3944
+ if (!cmd) return;
3945
+ commandRegistry.execute(cmd, commandRegistry.createExecutionContext(parseDropdownItemParams(item)));
3365
3946
  }
3366
- function onDropdownItemClick(options) {
3947
+ function onDropdownActionClick(options) {
3367
3948
  return (event) => {
3368
3949
  if (readReactiveBoolean(options.disabled, false)) return;
3369
3950
  event.stopPropagation();
3370
- closeParentDropdown(event);
3371
- if (options.action) {
3372
- options.action(event);
3373
- return;
3374
- }
3375
- if (options.cmd) commandRegistry.execute(options.cmd, commandRegistry.createExecutionContext(options.params ?? {}));
3951
+ options.action?.(event);
3376
3952
  };
3377
3953
  }
3378
3954
  function keybindingDetails(cmd) {
@@ -3384,12 +3960,16 @@ function keybindingDetails(cmd) {
3384
3960
  /** Renders a native {@link https://webawesome.com/docs/components/dropdown-item | wa-dropdown-item}. */
3385
3961
  function renderDropdownItem(options) {
3386
3962
  const iconSpec = options.icon ?? "";
3963
+ const paramsJson = options.params && Object.keys(options.params).length > 0 ? JSON.stringify(options.params) : void 0;
3387
3964
  return html`
3388
3965
  <wa-dropdown-item
3389
3966
  slot=${options.slot ?? nothing}
3390
3967
  variant=${options.variant ?? "default"}
3968
+ value=${options.cmd ?? nothing}
3969
+ data-cmd=${options.cmd ?? nothing}
3970
+ data-params=${paramsJson ?? nothing}
3391
3971
  ?disabled=${readReactiveBoolean(options.disabled, false)}
3392
- @click=${onDropdownItemClick(options)}>
3972
+ @click=${options.action ? onDropdownActionClick(options) : nothing}>
3393
3973
  ${iconSpec ? icon(iconSpec, {
3394
3974
  label: options.title ?? options.label,
3395
3975
  slot: "icon"
@@ -3567,385 +4147,118 @@ var DocksContextMenu = class DocksContextMenu extends DocksElement {
3567
4147
  }
3568
4148
  return element;
3569
4149
  }
3570
- getHostPart() {
3571
- const root = this.getRootNode();
3572
- if (root instanceof ShadowRoot && root.host instanceof HTMLElement) return root.host;
3573
- return null;
3574
- }
3575
- /**
3576
- * Triggers a click on the element under the cursor to update selection before showing context menu.
3577
- */
3578
- triggerClickUnderCursor(mouseEvent) {
3579
- const elementUnderCursor = this.getElementFromPoint(mouseEvent.clientX, mouseEvent.clientY);
3580
- if (elementUnderCursor) {
3581
- const clickEvent = new MouseEvent("click", {
3582
- bubbles: true,
3583
- cancelable: true,
3584
- view: window,
3585
- clientX: mouseEvent.clientX,
3586
- clientY: mouseEvent.clientY,
3587
- screenX: mouseEvent.screenX,
3588
- screenY: mouseEvent.screenY,
3589
- button: 0,
3590
- buttons: 0,
3591
- detail: 1,
3592
- which: 1
3593
- });
3594
- elementUnderCursor.dispatchEvent(clickEvent);
3595
- }
3596
- }
3597
- /** Opens the menu at `position`. Returns false when there is nothing to show (no thin empty panel). */
3598
- async show(position, mouseEvent) {
3599
- if (!this.hasMenuBody()) return false;
3600
- if (mouseEvent) {
3601
- this.triggerClickUnderCursor(mouseEvent);
3602
- await this.getHostPart()?.updateComplete;
3603
- }
3604
- if (this.isOpen) {
3605
- this.isOpen = false;
3606
- await this.updateComplete;
3607
- }
3608
- this.menuPosition = position;
3609
- this.isOpen = true;
3610
- await this.updateComplete;
3611
- this.syncVirtualPopupAnchor();
3612
- document.addEventListener("pointerdown", this.boundHandleDocumentPointerDown, { capture: true });
3613
- return true;
3614
- }
3615
- /** Points wa-dropdown's internal wa-popup at the pointer via a VirtualElement anchor. */
3616
- syncVirtualPopupAnchor() {
3617
- const popup = this.dropdownRef.value?.shadowRoot?.querySelector("wa-popup");
3618
- if (!popup) return;
3619
- popup.anchor = this.virtualAnchor;
3620
- popup.reposition();
3621
- }
3622
- onClose() {
3623
- if (!this.isOpen) return;
3624
- this.isOpen = false;
3625
- document.removeEventListener("pointerdown", this.boundHandleDocumentPointerDown, { capture: true });
3626
- }
3627
- updated(changedProperties) {
3628
- super.updated(changedProperties);
3629
- if (changedProperties.has("isOpen") && this.isOpen) this.syncVirtualPopupAnchor();
3630
- }
3631
- renderContribution(contribution) {
3632
- return renderDropdownContribution(contribution);
3633
- }
3634
- render() {
3635
- const partContent = this.partContextMenuRenderer ? this.partContextMenuRenderer() : nothing;
3636
- return html`
3637
- <wa-dropdown
3638
- ${ref(this.dropdownRef)}
3639
- ?open=${this.isOpen}
3640
- @submenu-opening=${this.handleSubmenuOpening}
3641
- @wa-after-hide=${this.onClose}>
3642
- <span slot="trigger" hidden aria-hidden="true"></span>
3643
-
3644
- ${partContent}
3645
- ${this.contributions.map((c) => this.renderContribution(c))}
3646
- </wa-dropdown>
3647
- `;
3648
- }
3649
- static {
3650
- this.styles = css`
3651
- :host {
3652
- position: fixed;
3653
- top: 0;
3654
- left: 0;
3655
- width: 0;
3656
- height: 0;
3657
- pointer-events: none;
3658
- z-index: 10000;
3659
- }
3660
-
3661
- wa-dropdown {
3662
- pointer-events: none;
3663
- min-width: 200px;
3664
- }
3665
-
3666
- wa-dropdown:state(open) {
3667
- pointer-events: auto;
3668
- }
3669
-
3670
- wa-dropdown::part(menu) {
3671
- min-width: 200px;
3672
- }
3673
- `;
3674
- }
3675
- };
3676
- _decorate([property({ attribute: false })], DocksContextMenu.prototype, "scopeTokens", void 0);
3677
- _decorate([property({ attribute: false })], DocksContextMenu.prototype, "partContextMenuRenderer", void 0);
3678
- _decorate([state()], DocksContextMenu.prototype, "contributions", void 0);
3679
- _decorate([state()], DocksContextMenu.prototype, "isOpen", void 0);
3680
- DocksContextMenu = _decorate([customElement("docks-contextmenu")], DocksContextMenu);
3681
- //#endregion
3682
- //#region src/parts/container.ts
3683
- var DocksContainer = class extends DocksElement {};
3684
- //#endregion
3685
- //#region src/parts/part.ts
3686
- var DocksPart = class DocksPart extends DocksContainer {
3687
- constructor(..._args) {
3688
- super(..._args);
3689
- this.scrollMode = "scroller";
3690
- this.dirty = false;
3691
- this.isEditor = false;
3692
- this.onContentContextMenu = async (event) => {
3693
- const contextMenu = this.renderRoot.querySelector("docks-contextmenu");
3694
- if (!contextMenu) return;
3695
- if (await contextMenu.show({
3696
- x: event.clientX,
3697
- y: event.clientY
3698
- }, event)) event.preventDefault();
3699
- };
3700
- }
3701
- getCommandStack() {
3702
- return this.commandStack;
3703
- }
3704
- /**
3705
- * Override this method to provide toolbar content for this part.
3706
- * This is a lightweight alternative to registering toolbar contributions
3707
- * for actions that are scoped to this part instance.
3708
- *
3709
- * IMPORTANT: Event handlers MUST use arrow functions to preserve the component's 'this' context.
3710
- * The toolbar template is rendered in a different component (docks-toolbar), so direct method
3711
- * references lose their binding.
3712
- *
3713
- * ✅ Correct:
3714
- * @click=${() => this.myMethod()}
3715
- * @click=${(e) => this.handleClick(e)}
3716
- *
3717
- * ❌ Wrong (this will be bound to the toolbar, not your component):
3718
- * @click=${this.myMethod}
3719
- *
3720
- * Example:
3721
- * ```typescript
3722
- * protected renderToolbar() {
3723
- * return html`
3724
- * <wa-button @click=${() => this.save()} title="Save">
3725
- * <wa-icon name="save"></wa-icon>
3726
- * </wa-button>
3727
- * `;
3728
- * }
3729
- * ```
3730
- *
3731
- * @returns TemplateResult with toolbar items, or nothing if no toolbar needed
3732
- */
3733
- renderToolbar() {
3734
- return nothing;
3735
- }
3736
- /**
3737
- * Activates the tab that contains this part by walking up the DOM to the first
3738
- * docks-tabs ancestor and activating the tab panel that contains this part.
3739
- * Crosses Shadow DOM boundaries (e.g. wa-tab-group) via getRootNode().host.
3740
- * No-op if this part is not inside a docks-tabs / wa-tab-panel.
3741
- */
3742
- activateContainingTab() {
3743
- let el = this;
3744
- let panelName = null;
3745
- let tabsEl = null;
3746
- while (el) {
3747
- const tag = el.tagName?.toLowerCase();
3748
- if (tag === "wa-tab-panel") panelName = el.getAttribute("name");
3749
- if (tag === "docks-tabs") {
3750
- tabsEl = el;
3751
- break;
3752
- }
3753
- const parent = el.parentElement;
3754
- if (parent) el = parent;
3755
- else {
3756
- const root = el.getRootNode();
3757
- el = root instanceof ShadowRoot ? root.host : null;
3758
- }
3759
- }
3760
- if (tabsEl && panelName != null && panelName !== "") tabsEl.activate(panelName);
3761
- }
3762
- /**
3763
- * Override this method to provide context menu content for this part.
3764
- * This is a lightweight alternative to registering context menu contributions
3765
- * for actions that are scoped to this part instance.
3766
- *
3767
- * IMPORTANT: Event handlers MUST use arrow functions to preserve the component's 'this' context.
3768
- * The context menu is rendered in a different component (contextmenu), so direct method
3769
- * references lose their binding.
3770
- *
3771
- * ✅ Correct:
3772
- * @click=${() => this.myMethod()}
3773
- * @click=${(e) => this.handleClick(e)}
3774
- *
3775
- * ❌ Wrong (this will be bound to the context menu, not your component):
3776
- * @click=${this.myMethod}
3777
- *
3778
- * Example:
3779
- * ```typescript
3780
- * protected renderContextMenu() {
3781
- * return html`
3782
- * <wa-dropdown-item @click=${() => this.open()}>
3783
- * <wa-icon name="folder-open"></wa-icon>
3784
- * Open
3785
- * </wa-dropdown-item>
3786
- * <wa-divider></wa-divider>
3787
- * <wa-dropdown-item @click=${() => this.delete()}>
3788
- * <wa-icon name="trash"></wa-icon>
3789
- * Delete
3790
- * </wa-dropdown-item>
3791
- * `;
3792
- * }
3793
- * ```
3794
- *
3795
- * @returns TemplateResult with context menu items, or nothing if no context menu needed
3796
- */
3797
- renderContextMenu() {
3798
- return nothing;
3799
- }
3800
- renderContent() {
3801
- return nothing;
3802
- }
3803
- /** Part-level toolbar contributions (`toolbar:${partId}`). */
3804
- getToolbarTarget() {
3805
- const contributionKey = this.tabContribution?.editorId ?? this.id ?? this.tabContribution?.name;
3806
- return contributionKey ? `toolbar:${contributionKey}` : void 0;
3807
- }
3808
- /** Part-level context-menu contributions (`contextmenu:${partId}`). */
3809
- getContextMenuTarget() {
3810
- const contributionKey = this.tabContribution?.editorId ?? this.id ?? this.tabContribution?.name;
3811
- return contributionKey ? `contextmenu:${contributionKey}` : void 0;
3812
- }
3813
- syncIsEditorCapability() {
3814
- const next = this.save !== DocksPart.prototype.save;
3815
- if (next === this.isEditor) return;
3816
- this.isEditor = next;
3817
- }
3818
- maybeActivateForCoupledEditors() {
3819
- const coupled = this.tabContribution?.coupledEditors;
3820
- if (!coupled?.length) return;
3821
- const active = activeEditorSignal.get();
3822
- if (!(active instanceof DocksPart)) return;
3823
- const editorId = active.tabContribution?.editorId;
3824
- if (!editorId || !coupled.includes(editorId)) return;
3825
- this.activateContainingTab();
3826
- }
3827
- render() {
3828
- const toolbarTarget = this.getToolbarTarget();
3829
- const contextMenuTarget = this.getContextMenuTarget();
3830
- const toolbarEnabled = this.tabContribution?.toolbar !== false;
3831
- const contextMenuEnabled = this.tabContribution?.contextMenu !== false;
3832
- const scrollMode = this.scrollMode;
3833
- const scopeTokens = this.isEditor ? ["system.editors", ".system.editors"] : [];
3834
- const content = this.renderContent();
3835
- const contentNode = scrollMode === "scroller" ? html`
3836
- <wa-scroller class="part-content-scroll" orientation="vertical">
3837
- <div class="part-content-inner">${content}</div>
3838
- </wa-scroller>
3839
- ` : html`<div class="part-content-inner">${content}</div>`;
3840
- return html`
3841
- <div class="part-shell">
3842
- ${toolbarEnabled ? html`
3843
- <docks-toolbar
3844
- class="part-toolbar"
3845
- id=${ifDefined(toolbarTarget)}
3846
- .scopeTokens=${scopeTokens}
3847
- .partToolbarRenderer=${() => this.renderToolbar()}>
3848
- </docks-toolbar>
3849
- ` : nothing}
3850
- <div class="part-content ${scrollMode === "native" ? "native-scroll" : ""}" @contextmenu=${contextMenuEnabled ? this.onContentContextMenu : void 0}>
3851
- ${contentNode}
3852
- </div>
3853
- ${contextMenuEnabled ? html`
3854
- <docks-contextmenu
3855
- id=${ifDefined(contextMenuTarget)}
3856
- .scopeTokens=${scopeTokens}
3857
- .partContextMenuRenderer=${() => this.renderContextMenu()}>
3858
- </docks-contextmenu>
3859
- ` : nothing}
3860
- </div>
3861
- `;
4150
+ getHostPart() {
4151
+ const root = this.getRootNode();
4152
+ if (root instanceof ShadowRoot && root.host instanceof HTMLElement) return root.host;
4153
+ return null;
3862
4154
  }
3863
- updated(_changedProperties) {
3864
- super.updated(_changedProperties);
3865
- this.syncIsEditorCapability();
3866
- if (_changedProperties.has("tabContribution")) this.maybeActivateForCoupledEditors();
3867
- if (_changedProperties.has("dirty")) {
3868
- if (_changedProperties.get("dirty") !== void 0) this.dispatchEvent(new CustomEvent("dirty", {
3869
- detail: this.dirty,
3870
- bubbles: true
3871
- }));
4155
+ /**
4156
+ * Triggers a click on the element under the cursor to update selection before showing context menu.
4157
+ */
4158
+ triggerClickUnderCursor(mouseEvent) {
4159
+ const elementUnderCursor = this.getElementFromPoint(mouseEvent.clientX, mouseEvent.clientY);
4160
+ if (elementUnderCursor) {
4161
+ const clickEvent = new MouseEvent("click", {
4162
+ bubbles: true,
4163
+ cancelable: true,
4164
+ view: window,
4165
+ clientX: mouseEvent.clientX,
4166
+ clientY: mouseEvent.clientY,
4167
+ screenX: mouseEvent.screenX,
4168
+ screenY: mouseEvent.screenY,
4169
+ button: 0,
4170
+ buttons: 0,
4171
+ detail: 1,
4172
+ which: 1
4173
+ });
4174
+ elementUnderCursor.dispatchEvent(clickEvent);
3872
4175
  }
3873
4176
  }
3874
- doClose() {}
3875
- disconnectedCallback() {
3876
- super.disconnectedCallback();
4177
+ /** Opens the menu at `position`. Returns false when there is nothing to show (no thin empty panel). */
4178
+ async show(position, mouseEvent) {
4179
+ if (!this.hasMenuBody()) return false;
4180
+ if (mouseEvent) {
4181
+ this.triggerClickUnderCursor(mouseEvent);
4182
+ await this.getHostPart()?.updateComplete;
4183
+ }
4184
+ if (this.isOpen) {
4185
+ this.isOpen = false;
4186
+ await this.updateComplete;
4187
+ }
4188
+ this.menuPosition = position;
4189
+ this.isOpen = true;
4190
+ await this.updateComplete;
4191
+ this.syncVirtualPopupAnchor();
4192
+ document.addEventListener("pointerdown", this.boundHandleDocumentPointerDown, { capture: true });
4193
+ return true;
3877
4194
  }
3878
- close() {
3879
- this.doClose();
4195
+ /** Points wa-dropdown's internal wa-popup at the pointer via a VirtualElement anchor. */
4196
+ syncVirtualPopupAnchor() {
4197
+ const popup = this.dropdownRef.value?.shadowRoot?.querySelector("wa-popup");
4198
+ if (!popup) return;
4199
+ popup.anchor = this.virtualAnchor;
4200
+ popup.reposition();
3880
4201
  }
3881
- connectedCallback() {
3882
- super.connectedCallback();
3883
- this.syncIsEditorCapability();
3884
- queueMicrotask(() => this.syncIsEditorCapability());
3885
- this.watch(activeEditorSignal, () => this.maybeActivateForCoupledEditors());
4202
+ onClose() {
4203
+ if (!this.isOpen) return;
4204
+ this.isOpen = false;
4205
+ document.removeEventListener("pointerdown", this.boundHandleDocumentPointerDown, { capture: true });
3886
4206
  }
3887
- save() {}
3888
- isDirty() {
3889
- return this.dirty;
4207
+ updated(changedProperties) {
4208
+ super.updated(changedProperties);
4209
+ if (changedProperties.has("isOpen") && this.isOpen) this.syncVirtualPopupAnchor();
3890
4210
  }
3891
- markDirty(dirty) {
3892
- this.dirty = dirty;
3893
- partDirtySignal.set(null);
3894
- partDirtySignal.set(this);
3895
- activePartSignal.set(null);
3896
- activePartSignal.set(this);
4211
+ renderContribution(contribution) {
4212
+ return renderDropdownContribution(contribution);
4213
+ }
4214
+ render() {
4215
+ const partContent = this.partContextMenuRenderer ? this.partContextMenuRenderer() : nothing;
4216
+ return html`
4217
+ <wa-dropdown
4218
+ ${ref(this.dropdownRef)}
4219
+ ?open=${this.isOpen}
4220
+ @submenu-opening=${this.handleSubmenuOpening}
4221
+ @wa-select=${handleDropdownWaSelect}
4222
+ @wa-after-hide=${this.onClose}>
4223
+ <span slot="trigger" hidden aria-hidden="true"></span>
4224
+
4225
+ ${partContent}
4226
+ ${this.contributions.map((c) => this.renderContribution(c))}
4227
+ </wa-dropdown>
4228
+ `;
3897
4229
  }
3898
4230
  static {
3899
- this.baseStyles = css`
4231
+ this.styles = css`
3900
4232
  :host {
3901
- display: block;
3902
- }
3903
-
3904
- .part-shell {
3905
- display: grid;
3906
- grid-template-rows: auto minmax(0, 1fr);
3907
- height: 100%;
3908
- width: 100%;
3909
- position: relative;
3910
- overflow: hidden;
3911
- }
3912
-
3913
- .part-content {
3914
- min-height: 0;
3915
- overflow: hidden;
3916
- position: relative;
3917
- }
3918
-
3919
- .part-content.native-scroll {
3920
- overflow: auto;
4233
+ position: fixed;
4234
+ top: 0;
4235
+ left: 0;
4236
+ width: 0;
4237
+ height: 0;
4238
+ pointer-events: none;
4239
+ z-index: 10000;
3921
4240
  }
3922
4241
 
3923
- .part-content-scroll {
3924
- width: 100%;
3925
- height: 100%;
4242
+ wa-dropdown {
4243
+ pointer-events: none;
4244
+ min-width: 200px;
3926
4245
  }
3927
4246
 
3928
- .part-content-inner {
3929
- height: 100%;
3930
- min-height: 100%;
4247
+ wa-dropdown:state(open) {
4248
+ pointer-events: auto;
3931
4249
  }
3932
-
3933
- .part-toolbar {
3934
- min-height: 0;
4250
+
4251
+ wa-dropdown::part(menu) {
4252
+ min-width: 200px;
3935
4253
  }
3936
4254
  `;
3937
4255
  }
3938
- static finalizeStyles(styles) {
3939
- const own = super.finalizeStyles(styles);
3940
- return [DocksPart.baseStyles, ...own];
3941
- }
3942
4256
  };
3943
- _decorate([property()], DocksPart.prototype, "dirty", void 0);
3944
- _decorate([property({ attribute: false })], DocksPart.prototype, "tabContribution", void 0);
3945
- _decorate([property({
3946
- type: Boolean,
3947
- attribute: false
3948
- })], DocksPart.prototype, "isEditor", void 0);
4257
+ _decorate([property({ attribute: false })], DocksContextMenu.prototype, "scopeTokens", void 0);
4258
+ _decorate([property({ attribute: false })], DocksContextMenu.prototype, "partContextMenuRenderer", void 0);
4259
+ _decorate([state()], DocksContextMenu.prototype, "contributions", void 0);
4260
+ _decorate([state()], DocksContextMenu.prototype, "isOpen", void 0);
4261
+ DocksContextMenu = _decorate([customElement("docks-contextmenu")], DocksContextMenu);
3949
4262
  //#endregion
3950
4263
  //#region src/parts/tabs.ts
3951
4264
  var _DocksTabs;
@@ -3959,6 +4272,8 @@ var DocksTabs = class DocksTabs extends DocksContainer {
3959
4272
  this.iconOnly = false;
3960
4273
  this.withToolbar = false;
3961
4274
  this.hideTabs = false;
4275
+ this.fitContent = false;
4276
+ this.fillHeight = false;
3962
4277
  this.itemSize = "m";
3963
4278
  this.contributions = [];
3964
4279
  this.tabGroup = createRef();
@@ -4011,6 +4326,11 @@ var DocksTabs = class DocksTabs extends DocksContainer {
4011
4326
  if (!this.tabGroup.value) return null;
4012
4327
  return this.tabGroup.value.getAttribute("active");
4013
4328
  }
4329
+ getTabPart(tabName) {
4330
+ const tabPanel = this.getTabPanel(tabName);
4331
+ if (!tabPanel) return null;
4332
+ return this.getPartFromPanel(tabPanel);
4333
+ }
4014
4334
  open(contribution) {
4015
4335
  if (this.contributions.find((c) => c.name === contribution.name)) {
4016
4336
  this.activate(contribution.name);
@@ -4260,6 +4580,95 @@ var DocksTabs = class DocksTabs extends DocksContainer {
4260
4580
  width: 100%;
4261
4581
  }
4262
4582
 
4583
+ :host([fit-content]) {
4584
+ display: flex;
4585
+ flex-direction: column;
4586
+ height: auto;
4587
+ max-height: 100%;
4588
+ min-height: 0;
4589
+ }
4590
+
4591
+ :host([fit-content]) wa-tab-group {
4592
+ display: flex;
4593
+ flex-direction: column;
4594
+ flex: 1 1 auto;
4595
+ min-height: 0;
4596
+ height: auto;
4597
+ max-height: 100%;
4598
+ }
4599
+
4600
+ :host([fit-content]) wa-tab-group::part(base) {
4601
+ display: flex;
4602
+ flex-direction: column;
4603
+ flex: 1 1 auto;
4604
+ min-height: 0;
4605
+ max-height: 100%;
4606
+ height: auto;
4607
+ }
4608
+
4609
+ :host([fit-content]) wa-tab-group::part(body) {
4610
+ flex: 1 1 auto;
4611
+ min-height: 0;
4612
+ height: auto;
4613
+ overflow-x: hidden;
4614
+ overflow-y: auto;
4615
+ }
4616
+
4617
+ :host([fit-content]) wa-tab-panel[active] {
4618
+ display: block;
4619
+ height: auto;
4620
+ overflow: visible;
4621
+ }
4622
+
4623
+ :host([fit-content]) wa-tab-panel > * {
4624
+ height: auto;
4625
+ min-height: 0;
4626
+ }
4627
+
4628
+ :host([fill-height]) {
4629
+ display: flex;
4630
+ flex-direction: column;
4631
+ height: 100%;
4632
+ min-height: 0;
4633
+ max-height: 100%;
4634
+ }
4635
+
4636
+ :host([fill-height]) wa-tab-group {
4637
+ display: flex;
4638
+ flex-direction: column;
4639
+ flex: 1 1 auto;
4640
+ min-height: 0;
4641
+ height: 100%;
4642
+ max-height: 100%;
4643
+ }
4644
+
4645
+ :host([fill-height]) wa-tab-group::part(base) {
4646
+ display: flex;
4647
+ flex-direction: column;
4648
+ flex: 1 1 auto;
4649
+ min-height: 0;
4650
+ max-height: 100%;
4651
+ height: 100%;
4652
+ }
4653
+
4654
+ :host([fill-height]) wa-tab-group::part(body) {
4655
+ flex: 1 1 auto;
4656
+ min-height: 0;
4657
+ overflow-x: hidden;
4658
+ overflow-y: auto;
4659
+ }
4660
+
4661
+ :host([fill-height]) wa-tab-panel[active] {
4662
+ display: block;
4663
+ height: auto;
4664
+ overflow: visible;
4665
+ }
4666
+
4667
+ :host([fill-height]) wa-tab-panel > * {
4668
+ height: auto;
4669
+ min-height: 0;
4670
+ }
4671
+
4263
4672
  wa-tab-group {
4264
4673
  height: 100%;
4265
4674
  width: 100%;
@@ -4487,6 +4896,16 @@ _decorate([property({
4487
4896
  reflect: true,
4488
4897
  attribute: "hide-tabs"
4489
4898
  })], DocksTabs.prototype, "hideTabs", void 0);
4899
+ _decorate([property({
4900
+ type: Boolean,
4901
+ reflect: true,
4902
+ attribute: "fit-content"
4903
+ })], DocksTabs.prototype, "fitContent", void 0);
4904
+ _decorate([property({
4905
+ type: Boolean,
4906
+ reflect: true,
4907
+ attribute: "fill-height"
4908
+ })], DocksTabs.prototype, "fillHeight", void 0);
4490
4909
  _decorate([property({
4491
4910
  reflect: true,
4492
4911
  attribute: "item-size"
@@ -4526,9 +4945,9 @@ var DocksResizableGrid = class DocksResizableGrid extends DocksElement {
4526
4945
  this.gridChildren = [];
4527
4946
  this.resizing = null;
4528
4947
  this.resizeOverlay = null;
4529
- this.childrenLoaded = false;
4530
4948
  this.childStylesApplied = false;
4531
- this.settingsLoaded = false;
4949
+ this.refreshScheduled = false;
4950
+ this.layoutGeneration = 0;
4532
4951
  this.handleResize = (e) => {
4533
4952
  if (!this.resizing) return;
4534
4953
  const delta = (this.orientation === "horizontal" ? e.clientX : e.clientY) - this.resizing.startPos;
@@ -4578,56 +4997,108 @@ var DocksResizableGrid = class DocksResizableGrid extends DocksElement {
4578
4997
  return this;
4579
4998
  }
4580
4999
  doBeforeUI() {
4581
- if (!this.childrenLoaded) {
4582
- this.mutationObserver = new MutationObserver(() => {
4583
- if (!this.childrenLoaded) this.loadChildren();
4584
- });
4585
- this.mutationObserver.observe(this, {
4586
- childList: true,
4587
- subtree: false
4588
- });
4589
- this.loadChildren();
4590
- }
5000
+ this.mutationObserver = new MutationObserver(() => {
5001
+ this.scheduleRefreshGridLayout();
5002
+ });
5003
+ this.mutationObserver.observe(this, {
5004
+ childList: true,
5005
+ subtree: false
5006
+ });
5007
+ this.scheduleRefreshGridLayout();
5008
+ }
5009
+ scheduleRefreshGridLayout() {
5010
+ if (this.refreshScheduled) return;
5011
+ this.refreshScheduled = true;
5012
+ queueMicrotask(() => {
5013
+ this.refreshScheduled = false;
5014
+ this.refreshGridLayout();
5015
+ });
4591
5016
  }
4592
- async loadChildren() {
4593
- const potentialChildren = Array.from(this.children).filter((child) => child.tagName !== "STYLE" && child.tagName !== "SCRIPT" && !child.classList.contains("resize-handle"));
4594
- if (potentialChildren.length === 0) return;
4595
- this.childrenLoaded = true;
4596
- if (this.mutationObserver) {
4597
- this.mutationObserver.disconnect();
4598
- this.mutationObserver = void 0;
4599
- }
4600
- this.gridChildren = potentialChildren;
4601
- if (!this.settingsLoaded) {
4602
- this.settingsLoaded = true;
4603
- const persisted = await this.getDialogSetting();
4604
- if (persisted && Array.isArray(persisted.sizes) && persisted.sizes.length === this.gridChildren.length) {
4605
- this.gridSizes = persisted.sizes;
4606
- this.requestUpdate();
4607
- return;
5017
+ getGridChildrenFromDom() {
5018
+ return Array.from(this.children).filter((child) => child.tagName !== "STYLE" && child.tagName !== "SCRIPT" && !child.classList.contains("resize-handle"));
5019
+ }
5020
+ static isNestedResizableGrid(child) {
5021
+ return child.tagName.toLowerCase() === "docks-resizable-grid";
5022
+ }
5023
+ applyChildLayoutStyles() {
5024
+ this.gridChildren.forEach((child, index) => {
5025
+ child.style.overflow = "hidden";
5026
+ child.style.height = "100%";
5027
+ child.style.width = "100%";
5028
+ child.style.gridColumn = this.orientation === "horizontal" ? `${index * 2 + 1}` : "1";
5029
+ child.style.gridRow = this.orientation === "vertical" ? `${index * 2 + 1}` : "1";
5030
+ if (!_DocksResizableGrid.isNestedResizableGrid(child)) {
5031
+ child.style.display = "flex";
5032
+ child.style.flexDirection = "column";
4608
5033
  }
5034
+ });
5035
+ }
5036
+ resolveGridSizes(childCount) {
5037
+ const parsedSizes = this.sizes?.split(",").map((s) => s.trim()).filter(Boolean) ?? [];
5038
+ if (parsedSizes.length === childCount) return parsedSizes;
5039
+ const equalSize = `${100 / childCount}%`;
5040
+ return Array.from({ length: childCount }, () => equalSize);
5041
+ }
5042
+ resolvePersistedSizes(persisted, childCount) {
5043
+ if (!persisted || typeof persisted !== "object") return null;
5044
+ const record = persisted;
5045
+ const byCount = record.sizesByCount?.[String(childCount)];
5046
+ if (Array.isArray(byCount) && byCount.length === childCount && byCount.every((size) => typeof size === "string")) return byCount;
5047
+ if (Array.isArray(record.sizes) && record.sizes.length === childCount && record.sizes.every((size) => typeof size === "string")) return record.sizes;
5048
+ return null;
5049
+ }
5050
+ async refreshGridLayout() {
5051
+ const generation = ++this.layoutGeneration;
5052
+ const potentialChildren = this.getGridChildrenFromDom();
5053
+ if (potentialChildren.length === 0) {
5054
+ this.gridChildren = [];
5055
+ this.gridSizes = [];
5056
+ this.childStylesApplied = false;
5057
+ this.requestUpdate();
5058
+ return;
4609
5059
  }
4610
- if (this.sizes) this.gridSizes = this.sizes.split(",").map((s) => s.trim());
4611
- else {
4612
- const equalSize = `${100 / this.gridChildren.length}%`;
4613
- this.gridSizes = this.gridChildren.map(() => equalSize);
5060
+ const identityChanged = potentialChildren.length !== this.gridChildren.length || potentialChildren.some((child, index) => child !== this.gridChildren[index]);
5061
+ this.gridChildren = potentialChildren;
5062
+ const persisted = await this.getDialogSetting();
5063
+ if (generation !== this.layoutGeneration) return;
5064
+ const restoredSizes = this.resolvePersistedSizes(persisted, this.gridChildren.length);
5065
+ if (restoredSizes) {
5066
+ const sizesChanged = restoredSizes.join("|") !== this.gridSizes.join("|");
5067
+ this.gridSizes = restoredSizes;
5068
+ if (identityChanged || sizesChanged) this.childStylesApplied = false;
5069
+ this.requestUpdate();
5070
+ return;
4614
5071
  }
5072
+ const nextSizes = this.resolveGridSizes(this.gridChildren.length);
5073
+ const sizesChanged = nextSizes.join("|") !== this.gridSizes.join("|");
5074
+ this.gridSizes = nextSizes;
5075
+ if (identityChanged || sizesChanged) this.childStylesApplied = false;
4615
5076
  this.requestUpdate();
4616
5077
  }
5078
+ doInitUI() {
5079
+ this.scheduleRefreshGridLayout();
5080
+ }
4617
5081
  async saveSizes() {
4618
5082
  if (this.gridSizes.length === 0) return;
5083
+ const persisted = await this.getDialogSetting();
5084
+ const existingByCount = persisted && typeof persisted === "object" ? persisted.sizesByCount ?? {} : {};
4619
5085
  await this.setDialogSetting({
4620
5086
  sizes: this.gridSizes,
5087
+ sizesByCount: {
5088
+ ...existingByCount,
5089
+ [String(this.gridSizes.length)]: this.gridSizes
5090
+ },
4621
5091
  orientation: this.orientation
4622
5092
  });
4623
5093
  }
4624
5094
  updated(changedProperties) {
4625
5095
  super.updated(changedProperties);
4626
- if (changedProperties.has("gridChildren") && !this.childStylesApplied && this.gridChildren.length > 0) {
5096
+ if (changedProperties.has("sizes")) this.scheduleRefreshGridLayout();
5097
+ if (!this.childStylesApplied && this.gridChildren.length > 0) {
4627
5098
  this.childStylesApplied = true;
4628
5099
  /**
4629
5100
  * Direct style manipulation is intentionally used here.
4630
- *
5101
+ *
4631
5102
  * Reasoning:
4632
5103
  * - Grid positioning (gridColumn/gridRow) must be computed dynamically based on
4633
5104
  * the number of children and orientation at runtime
@@ -4637,16 +5108,9 @@ var DocksResizableGrid = class DocksResizableGrid extends DocksElement {
4637
5108
  * (e.g., child at index 0 → column 1, index 1 → column 3, etc.)
4638
5109
  * - This is a layout container whose primary job is to programmatically position
4639
5110
  * its children within a CSS grid system
5111
+ * - Nested resizable grids manage their own display mode and must not receive flex
4640
5112
  */
4641
- this.gridChildren.forEach((child, index) => {
4642
- child.style.overflow = "hidden";
4643
- child.style.height = "100%";
4644
- child.style.width = "100%";
4645
- child.style.gridColumn = this.orientation === "horizontal" ? `${index * 2 + 1}` : "1";
4646
- child.style.gridRow = this.orientation === "vertical" ? `${index * 2 + 1}` : "1";
4647
- child.style.display = "flex";
4648
- child.style.flexDirection = "column";
4649
- });
5113
+ this.applyChildLayoutStyles();
4650
5114
  }
4651
5115
  }
4652
5116
  startResize(e, handleIndex) {
@@ -4765,6 +5229,6 @@ DocksResizableGrid = _DocksResizableGrid = _decorate([customElement("docks-resiz
4765
5229
  //#region src/parts/index.ts
4766
5230
  installOpenDropdownNativeContextMenuGuard();
4767
5231
  //#endregion
4768
- export { taskService as A, SIDEBAR_AUXILIARY as B, DocksElement as C, TOPIC_EXTENSIONS_CHANGED as D, keyBindingManager as E, MouseButton as F, SYSTEM_LAYOUTS as G, SIDEBAR_MAIN_BOTTOM as H, constants_exports as I, TOOLBAR_BOTTOM_CENTER as J, SYSTEM_VIEWS as K, ABOUT_TABS as L, appSettings as M, COMMAND_SAVE as N, extensionRegistry as O, HIDE_DOT_RESOURCE as P, TOOLBAR_MAIN_RIGHT as Q, EDITOR_AREA_MAIN as R, DocksDialogContent as S, sanitizeHtml as T, SIDEBAR_MAIN_TOOLBAR as U, SIDEBAR_MAIN as V, SYSTEM_ATTRIBUTIONS as W, TOOLBAR_MAIN as X, TOOLBAR_BOTTOM_END as Y, TOOLBAR_MAIN_CENTER as Z, validateFormDialogFields as _, renderDropdownContributions as a, deleteIndexedDbWorkspaceData as b, icon as c, filebrowserDialog as d, navigableInfoDialog as f, formDialogRequired as g, formDialog as h, renderDropdownContribution as i, TOPIC_SETTINGS_CHANGED as j, esmShService as k, parseIconSpec as l, infoDialog as m, DocksContainer as n, renderDropdownItem as o, confirmDialog as p, TOOLBAR_BOTTOM as q, renderCommandContribution as r, renderDropdownSubmenu as s, DocksPart as t, versionInfoDialog as u, promptDialog as v, parseMarkdownHtml as w, dialogService as x, appLoaderService as y, PANEL_BOTTOM as z };
5232
+ export { HIDE_DOT_RESOURCE as $, filebrowserDialog as A, dialogService as B, TOOLBAR_INFO as C, TOOLBAR_THEME_SWITCHER as D, TOOLBAR_TASKS as E, formDialogRequired as F, keyBindingManager as G, DocksElement as H, validateFormDialogFields as I, esmShService as J, TOPIC_EXTENSIONS_CHANGED as K, promptDialog as L, confirmDialog as M, infoDialog as N, VIEW_FILEBROWSER as O, formDialog as P, COMMAND_SAVE as Q, appLoaderService as R, TOOLBAR_FULLSCREEN as S, TOOLBAR_LAYOUT_SWITCHER as T, parseMarkdownHtml as U, DocksDialogContent as V, sanitizeHtml as W, TOPIC_SETTINGS_CHANGED as X, taskService as Y, appSettings as Z, CTXMENU_FILEBROWSER_RENAME as _, TOOLBAR_MAIN_CENTER as _t, renderDropdownItem as a, SIDEBAR_AUXILIARY as at, TOOLBAR_ACTIVE_PART_NAME as b, parseIconSpec as c, SIDEBAR_MAIN_BOTTOM as ct, DocksContainer as d, SYSTEM_LAYOUTS as dt, MouseButton as et, ABOUT_TAB_ATTRIBUTIONS as f, SYSTEM_VIEWS as ft, CTXMENU_FILEBROWSER_DELETE as g, TOOLBAR_MAIN as gt, ATTRIBUTION_ECLIPSE_DOCKS as h, TOOLBAR_BOTTOM_END as ht, renderDropdownContributions as i, PANEL_BOTTOM as it, navigableInfoDialog as j, VIEW_LOG_TERMINAL as k, versionInfoDialog as l, SIDEBAR_MAIN_TOOLBAR as lt, ABOUT_TAB_RELEASE as m, TOOLBAR_BOTTOM_CENTER as mt, renderCommandContribution as n, ABOUT_TABS as nt, renderDropdownSubmenu as o, SIDEBAR_AUXILIARY_BOTTOM as ot, ABOUT_TAB_PACKAGES as p, TOOLBAR_BOTTOM as pt, extensionRegistry as q, renderDropdownContribution as r, EDITOR_AREA_MAIN as rt, icon as s, SIDEBAR_MAIN as st, handleDropdownWaSelect as t, constants_exports as tt, DocksPart as u, SYSTEM_ATTRIBUTIONS as ut, SIDEBAR_TOOLBAR_EXTENSIONS as v, TOOLBAR_MAIN_RIGHT as vt, TOOLBAR_LANGUAGE_SELECTOR as w, TOOLBAR_FAST_VIEWS as x, SYSTEM_DEFAULT_FILE_ICONS as y, deleteIndexedDbWorkspaceData as z };
4769
5233
 
4770
- //# sourceMappingURL=parts-CRVxaDd-.js.map
5234
+ //# sourceMappingURL=parts-CAt8aeU8.js.map