@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
@@ -26,21 +26,12 @@ import { publish } from "./events";
26
26
 
27
27
  const logger = createLogger('AppLoader');
28
28
 
29
- /** Layout reference: layout id string, or id + props to parameterize the layout (e.g. show-bottom-panel). */
29
+ /** Layout reference: layout id string, or id + optional props read by the layout on init. */
30
30
  export type LayoutDescriptor = string | { id: string; props?: Record<string, string | boolean> };
31
31
 
32
32
  function getLayoutIdFromApp(app: AppDefinition | undefined): string {
33
- if (!app) return 'standard';
34
- const l = app.layout ?? app.layoutId;
35
- return typeof l === 'object' ? l.id : (l ?? 'standard');
36
- }
37
-
38
- function propsToAttributes(props: Record<string, string | boolean>): Record<string, string> {
39
- const out: Record<string, string> = {};
40
- for (const [k, v] of Object.entries(props)) {
41
- out[k] = typeof v === 'boolean' ? (v ? 'true' : 'false') : v;
42
- }
43
- return out;
33
+ const layout = app?.layout ?? 'standard';
34
+ return typeof layout === 'object' ? layout.id : layout;
44
35
  }
45
36
 
46
37
  /**
@@ -177,15 +168,11 @@ export interface AppDefinition {
177
168
  releaseHistory?: ReleaseHistory | (() => ReleaseHistory | Promise<ReleaseHistory>);
178
169
 
179
170
  /**
180
- * Layout: id (string) or { id, props } to parameterize the layout.
181
- * App root is the chosen layout's component. Props are merged as attributes when rendering (e.g. show-bottom-panel).
171
+ * Layout: id (string) or { id, props }. The layout component reads props on init (e.g. panel visibility).
182
172
  * Defaults to 'standard' when omitted.
183
173
  */
184
174
  layout?: LayoutDescriptor;
185
175
 
186
- /** @deprecated Use layout (string or { id, props }) instead. */
187
- layoutId?: string;
188
-
189
176
  /**
190
177
  * Optional cleanup function.
191
178
  * Called when the app is being unloaded, before extensions are disabled.
@@ -248,8 +235,6 @@ class AppLoaderService {
248
235
  private container: HTMLElement = document.body;
249
236
  private systemRequiredExtensions: Set<string> = new Set();
250
237
  private static readonly PREFERRED_APP_KEY = 'preferredAppName';
251
- private static readonly PREFERRED_LAYOUT_KEY = 'preferredLayoutId';
252
- private preferredLayoutId?: string;
253
238
 
254
239
  /**
255
240
  * Register an application with the framework.
@@ -269,6 +254,7 @@ class AppLoaderService {
269
254
  }
270
255
  app.name = app.name ?? 'app';
271
256
  app.version = app.version ?? '0.0.0';
257
+ app.layout = app.layout ?? 'standard';
272
258
 
273
259
  if (this.apps.has(app.name)) {
274
260
  logger.warn(`App '${app.name}' is already registered. Overwriting.`);
@@ -443,7 +429,6 @@ class AppLoaderService {
443
429
 
444
430
  this.currentApp = app;
445
431
  logger.info(`App ${app.name} loaded successfully`);
446
- this.preferredLayoutId = await this.getPreferredLayoutId();
447
432
  this.updateDocumentMetadata(app);
448
433
  if (container) {
449
434
  this.dispatchLoadProgress('Rendering layout…');
@@ -485,7 +470,7 @@ class AppLoaderService {
485
470
  throw new Error('No app loaded. Call loadApp() first.');
486
471
  }
487
472
 
488
- const layoutId = this.preferredLayoutId ?? getLayoutIdFromApp(this.currentApp);
473
+ const layoutId = getLayoutIdFromApp(this.currentApp);
489
474
  const layouts = contributionRegistry.getContributions<LayoutContribution>(SYSTEM_LAYOUTS);
490
475
  let layout = layouts.find((c) => c.id === layoutId);
491
476
  if (!layout) {
@@ -501,10 +486,6 @@ class AppLoaderService {
501
486
  if (r && typeof r === 'object' && 'tag' in r && r.attributes) {
502
487
  effectiveAttributes = { ...r.attributes };
503
488
  }
504
- const appLayout = this.currentApp?.layout;
505
- if (typeof appLayout === 'object' && appLayout.id === layoutId && appLayout.props) {
506
- Object.assign(effectiveAttributes, propsToAttributes(appLayout.props));
507
- }
508
489
 
509
490
  container.innerHTML = '';
510
491
  if (typeof r === 'string') {
@@ -582,35 +563,7 @@ class AppLoaderService {
582
563
  }
583
564
 
584
565
  getCurrentLayoutId(): string {
585
- return this.preferredLayoutId ?? getLayoutIdFromApp(this.currentApp);
586
- }
587
-
588
- async getPreferredLayoutId(): Promise<string | undefined> {
589
- try {
590
- return await appSettings.get(AppLoaderService.PREFERRED_LAYOUT_KEY);
591
- } catch (error) {
592
- logger.debug(`Failed to get preferred layout ID: ${getErrorMessage(error)}`);
593
- return undefined;
594
- }
595
- }
596
-
597
- async setPreferredLayoutId(layoutId: string): Promise<void> {
598
- const layouts = this.getRegisteredLayouts();
599
- if (!layouts.some((l) => l.id === layoutId)) {
600
- throw new Error(`Layout '${layoutId}' not found.`);
601
- }
602
- try {
603
- await appSettings.set(AppLoaderService.PREFERRED_LAYOUT_KEY, layoutId);
604
- this.preferredLayoutId = layoutId;
605
- logger.info(`Set preferred layout to: ${layoutId}`);
606
- if (this.currentApp && this.container) {
607
- this.renderApp(this.container);
608
- }
609
- window.dispatchEvent(new CustomEvent('layout-changed', { detail: { layoutId } }));
610
- } catch (error) {
611
- logger.error(`Failed to persist preferred layout: ${getErrorMessage(error)}`);
612
- throw error;
613
- }
566
+ return getLayoutIdFromApp(this.currentApp);
614
567
  }
615
568
 
616
569
  /**
@@ -21,4 +21,5 @@ export const EDITOR_AREA_MAIN = "editor-area-main"
21
21
  export const SIDEBAR_MAIN = "sidebar-main"
22
22
  export const SIDEBAR_MAIN_BOTTOM = "sidebar-main-bottom"
23
23
  export const SIDEBAR_AUXILIARY = "sidebar-auxiliary"
24
+ export const SIDEBAR_AUXILIARY_BOTTOM = "sidebar-auxiliary-bottom"
24
25
  export const PANEL_BOTTOM = "panel-bottom"
@@ -60,9 +60,11 @@ export const DELETE_BUTTON: DialogButton = {
60
60
 
61
61
  export interface DialogContribution extends Contribution {
62
62
  id: string;
63
+ /** When true, `component` renders a complete `wa-dialog`. The service only mounts and manages lifecycle. */
64
+ selfContained?: boolean;
63
65
  buttons?: DialogButton[];
64
66
  component: (state?: any) => TemplateResult;
65
- onButton: (id: string, result: any, state?: any) => boolean | Promise<boolean> | void | Promise<void>;
67
+ onButton?: (id: string, result: any, state?: any) => boolean | Promise<boolean> | void | Promise<void>;
66
68
  }
67
69
 
68
70
  let dialogContainer: HTMLElement | null = null;
@@ -76,6 +78,38 @@ function getDialogContainer(): HTMLElement {
76
78
  return dialogContainer;
77
79
  }
78
80
 
81
+ type WaDialogElement = HTMLElement & { open?: boolean };
82
+
83
+ function findWaDialog(root: ParentNode): WaDialogElement | null {
84
+ if (root instanceof Element && root.tagName.toLowerCase() === 'wa-dialog') {
85
+ return root as WaDialogElement;
86
+ }
87
+
88
+ const children = root instanceof Document || root instanceof DocumentFragment || root instanceof Element
89
+ ? Array.from(root.children)
90
+ : root instanceof ShadowRoot
91
+ ? Array.from(root.children)
92
+ : [];
93
+
94
+ for (const child of children) {
95
+ if (child instanceof Element && child.tagName.toLowerCase() === 'wa-dialog') {
96
+ return child as WaDialogElement;
97
+ }
98
+ if (child instanceof Element && child.shadowRoot) {
99
+ const nested = findWaDialog(child.shadowRoot);
100
+ if (nested) {
101
+ return nested;
102
+ }
103
+ }
104
+ const nested = findWaDialog(child);
105
+ if (nested) {
106
+ return nested;
107
+ }
108
+ }
109
+
110
+ return null;
111
+ }
112
+
79
113
  class DialogService {
80
114
  private contributions: Map<string, DialogContribution> = new Map();
81
115
  private contributionsChangeScheduled = false;
@@ -111,7 +145,7 @@ class DialogService {
111
145
  continue;
112
146
  }
113
147
 
114
- if (!contribution.onButton) {
148
+ if (!contribution.onButton && !contribution.selfContained) {
115
149
  logger.warn(`Dialog contribution "${contribution.id}" has no onButton callback, skipping`);
116
150
  continue;
117
151
  }
@@ -130,13 +164,13 @@ class DialogService {
130
164
 
131
165
  return new Promise((resolve) => {
132
166
  const container = getDialogContainer();
133
- let isOpen = true;
167
+ let closed = false;
134
168
  let dialogContentElement: DocksDialogContent | null = null;
135
169
 
136
170
  const cleanup = async () => {
137
- if (!isOpen) return;
138
- isOpen = false;
139
-
171
+ if (closed) return;
172
+ closed = true;
173
+
140
174
  if (dialogContentElement) {
141
175
  try {
142
176
  await dialogContentElement.dispose();
@@ -145,50 +179,72 @@ class DialogService {
145
179
  logger.error(`Error disposing dialog content for "${dialogId}": ${errorMessage}`);
146
180
  }
147
181
  }
148
-
182
+
149
183
  try {
150
184
  const result = dialogContentElement ? dialogContentElement.getResult() : undefined;
151
- await contribution.onButton('close', result, stateWithClose);
185
+ if (contribution.onButton) {
186
+ await contribution.onButton('close', result, stateWithClose);
187
+ }
152
188
  } catch (error) {
153
189
  const errorMessage = error instanceof Error ? error.message : String(error);
154
190
  logger.error(`Error executing close callback for dialog "${dialogId}": ${errorMessage}`);
155
191
  }
156
-
192
+
157
193
  render(html``, container);
158
194
  resolve();
159
195
  };
160
196
 
197
+ const closeDialog = () => {
198
+ if (closed) return;
199
+ const dialog = findWaDialog(container);
200
+ if (dialog && dialog.open !== false) {
201
+ dialog.open = false;
202
+ return;
203
+ }
204
+ void cleanup();
205
+ };
206
+
207
+ const handleAfterHide = () => {
208
+ void cleanup();
209
+ };
210
+
161
211
  const handleButtonClick = async (buttonId: string) => {
212
+ if (!contribution.onButton) {
213
+ return;
214
+ }
162
215
  try {
163
216
  const result = dialogContentElement ? dialogContentElement.getResult() : undefined;
164
217
  const shouldClose = await contribution.onButton(buttonId, result, stateWithClose);
165
-
218
+
166
219
  if (shouldClose !== false) {
167
- cleanup();
220
+ closeDialog();
168
221
  }
169
222
  } catch (error) {
170
223
  const errorMessage = error instanceof Error ? error.message : String(error);
171
224
  logger.error(`Error executing button callback for dialog "${dialogId}": ${errorMessage}`);
172
- cleanup();
225
+ closeDialog();
173
226
  }
174
227
  };
175
228
 
176
- const buttons = contribution.buttons && contribution.buttons.length > 0
177
- ? contribution.buttons
229
+ const buttons = contribution.buttons && contribution.buttons.length > 0
230
+ ? contribution.buttons
178
231
  : [OK_BUTTON];
179
232
 
233
+ const stateWithClose = { ...state, close: closeDialog, onAfterHide: handleAfterHide };
180
234
  if (state && typeof state === 'object') {
181
- (state as any).close = cleanup;
235
+ (state as { close?: () => void }).close = closeDialog;
236
+ (state as { onAfterHide?: () => void }).onAfterHide = handleAfterHide;
182
237
  }
183
- const stateWithClose = { ...state, close: cleanup };
184
238
 
185
239
  const dialogLabel =
186
240
  state && typeof state === 'object' && typeof (state as { label?: unknown }).label === 'string'
187
241
  ? (state as { label: string }).label
188
242
  : (contribution.label || dialogId);
189
243
 
190
- const template = html`
191
- <wa-dialog label="${dialogLabel}" open @wa-request-close=${cleanup}>
244
+ const template = contribution.selfContained
245
+ ? contribution.component(stateWithClose)
246
+ : html`
247
+ <wa-dialog label="${dialogLabel}" open @wa-after-hide=${handleAfterHide}>
192
248
  <style>
193
249
  .dialog-service-content {
194
250
  display: flex;
@@ -224,7 +280,7 @@ class DialogService {
224
280
  if (cancelButton) {
225
281
  handleButtonClick(cancelButton.id);
226
282
  } else {
227
- cleanup();
283
+ closeDialog();
228
284
  }
229
285
  }}>
230
286
  ${contribution.component(state)}
@@ -232,6 +288,7 @@ class DialogService {
232
288
  <div class="dialog-service-footer">
233
289
  ${buttons.map(button => html`
234
290
  <wa-button
291
+ data-dialog-button="${button.id}"
235
292
  variant="${button.variant || 'default'}"
236
293
  ?disabled=${button.disabled}
237
294
  @click=${() => handleButtonClick(button.id)}
@@ -11,6 +11,10 @@
11
11
  * A part can expose both surfaces with the same part id but different prefixes, registering
12
12
  * different contributions on each. Feature-scoped targets (e.g. `filebrowser.create`) may also
13
13
  * be referenced explicitly from either surface when several controls share the same item set.
14
+ *
15
+ * Command-backed items rely on the parent {@link https://webawesome.com/docs/components/dropdown | wa-dropdown}
16
+ * `wa-select` event (see {@link handleDropdownWaSelect}) so the popup closes cleanly before
17
+ * native dialogs such as `showDirectoryPicker` open.
14
18
  */
15
19
  import { html, nothing, type TemplateResult } from 'lit';
16
20
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
@@ -41,28 +45,51 @@ export interface DropdownItemOptions {
41
45
  action?: (event: Event) => void;
42
46
  }
43
47
 
44
- function closeParentDropdown(event: Event): void {
45
- const dropdown = (event.target as Element | null)?.closest('wa-dropdown') as { open?: boolean } | null;
46
- if (dropdown && dropdown.open !== undefined) {
47
- dropdown.open = false;
48
+ type WaSelectDetail = {
49
+ item?: HTMLElement & { value?: string; disabled?: boolean };
50
+ };
51
+
52
+ function parseDropdownItemParams(item: HTMLElement): Record<string, unknown> {
53
+ const raw = item.getAttribute('data-params');
54
+ if (!raw) {
55
+ return {};
48
56
  }
57
+ try {
58
+ return JSON.parse(raw) as Record<string, unknown>;
59
+ } catch {
60
+ return {};
61
+ }
62
+ }
63
+
64
+ function getDropdownItemCommand(item: HTMLElement): string | undefined {
65
+ return item.getAttribute('data-cmd') || item.getAttribute('value') || undefined;
49
66
  }
50
67
 
51
- function onDropdownItemClick(options: DropdownItemOptions) {
68
+ /** Handles `wa-select` from a parent `wa-dropdown` and runs the backing command. */
69
+ export function handleDropdownWaSelect(event: CustomEvent<WaSelectDetail>): void {
70
+ const item = event.detail?.item;
71
+ if (!item || item.disabled || item.hasAttribute('disabled')) {
72
+ return;
73
+ }
74
+
75
+ const cmd = getDropdownItemCommand(item);
76
+ if (!cmd) {
77
+ return;
78
+ }
79
+
80
+ void commandRegistry.execute(
81
+ cmd,
82
+ commandRegistry.createExecutionContext(parseDropdownItemParams(item)),
83
+ );
84
+ }
85
+
86
+ function onDropdownActionClick(options: DropdownItemOptions) {
52
87
  return (event: Event) => {
53
- if (readReactiveBoolean(options.disabled, false)) return;
54
- event.stopPropagation();
55
- closeParentDropdown(event);
56
- if (options.action) {
57
- options.action(event);
88
+ if (readReactiveBoolean(options.disabled, false)) {
58
89
  return;
59
90
  }
60
- if (options.cmd) {
61
- void commandRegistry.execute(
62
- options.cmd,
63
- commandRegistry.createExecutionContext(options.params ?? {})
64
- );
65
- }
91
+ event.stopPropagation();
92
+ options.action?.(event);
66
93
  };
67
94
  }
68
95
 
@@ -76,12 +103,20 @@ function keybindingDetails(cmd?: string): TemplateResult | typeof nothing {
76
103
  /** Renders a native {@link https://webawesome.com/docs/components/dropdown-item | wa-dropdown-item}. */
77
104
  export function renderDropdownItem(options: DropdownItemOptions): TemplateResult {
78
105
  const iconSpec = options.icon ?? '';
106
+ const paramsJson =
107
+ options.params && Object.keys(options.params).length > 0
108
+ ? JSON.stringify(options.params)
109
+ : undefined;
110
+
79
111
  return html`
80
112
  <wa-dropdown-item
81
113
  slot=${options.slot ?? nothing}
82
114
  variant=${options.variant ?? 'default'}
115
+ value=${options.cmd ?? nothing}
116
+ data-cmd=${options.cmd ?? nothing}
117
+ data-params=${paramsJson ?? nothing}
83
118
  ?disabled=${readReactiveBoolean(options.disabled, false)}
84
- @click=${onDropdownItemClick(options)}>
119
+ @click=${options.action ? onDropdownActionClick(options) : nothing}>
85
120
  ${iconSpec ? icon(iconSpec, { label: options.title ?? options.label, slot: 'icon' }) : nothing}
86
121
  ${options.label}
87
122
  ${keybindingDetails(options.cmd)}
@@ -0,0 +1,121 @@
1
+ import type { LayoutDescriptor } from './apploader';
2
+ import { appSettings } from './settingsservice';
3
+ import type { DocksStandardLayout } from '../layouts/standard-layout';
4
+
5
+ export type LayoutPanelVisibility = {
6
+ showLeftSidebar: boolean;
7
+ showAuxSidebar: boolean;
8
+ showBottomPanel: boolean;
9
+ showLeftAux: boolean;
10
+ showRightAux: boolean;
11
+ };
12
+
13
+ export const DEFAULT_LAYOUT_PANEL_VISIBILITY: LayoutPanelVisibility = {
14
+ showLeftSidebar: true,
15
+ showAuxSidebar: true,
16
+ showBottomPanel: true,
17
+ showLeftAux: false,
18
+ showRightAux: false,
19
+ };
20
+
21
+ function normalizePanelVisibility(stored: Partial<LayoutPanelVisibility>): LayoutPanelVisibility {
22
+ return {
23
+ showLeftSidebar: stored.showLeftSidebar ?? DEFAULT_LAYOUT_PANEL_VISIBILITY.showLeftSidebar,
24
+ showAuxSidebar: stored.showAuxSidebar ?? DEFAULT_LAYOUT_PANEL_VISIBILITY.showAuxSidebar,
25
+ showBottomPanel: stored.showBottomPanel ?? DEFAULT_LAYOUT_PANEL_VISIBILITY.showBottomPanel,
26
+ showLeftAux: stored.showLeftAux ?? DEFAULT_LAYOUT_PANEL_VISIBILITY.showLeftAux,
27
+ showRightAux: stored.showRightAux ?? DEFAULT_LAYOUT_PANEL_VISIBILITY.showRightAux,
28
+ };
29
+ }
30
+
31
+ export const LAYOUT_PANEL_SETTINGS_KEY = 'layoutPanelVisibility';
32
+
33
+ /** @deprecated Legacy layout id persisted before panel visibility toggles. */
34
+ export const LEGACY_PREFERRED_LAYOUT_KEY = 'preferredLayoutId';
35
+
36
+ /** Maps legacy parameterized layout ids to panel visibility. */
37
+ export function layoutIdToPanelVisibility(layoutId: string): LayoutPanelVisibility {
38
+ switch (layoutId) {
39
+ case 'standard-bottom-panel':
40
+ return { ...DEFAULT_LAYOUT_PANEL_VISIBILITY, showBottomPanel: true, showLeftAux: false, showRightAux: false };
41
+ case 'standard-bottom-sidebar':
42
+ return { ...DEFAULT_LAYOUT_PANEL_VISIBILITY, showBottomPanel: false, showLeftAux: true, showRightAux: false };
43
+ case 'standard-full':
44
+ return {
45
+ ...DEFAULT_LAYOUT_PANEL_VISIBILITY,
46
+ showLeftAux: true,
47
+ showRightAux: true,
48
+ };
49
+ case 'standard':
50
+ default:
51
+ return {
52
+ ...DEFAULT_LAYOUT_PANEL_VISIBILITY,
53
+ showBottomPanel: false,
54
+ showLeftAux: false,
55
+ showRightAux: false,
56
+ };
57
+ }
58
+ }
59
+
60
+ export function resolveInitialPanelVisibilityFromApp(appLayout?: LayoutDescriptor): LayoutPanelVisibility {
61
+ if (typeof appLayout === 'object' && appLayout.props) {
62
+ return {
63
+ ...DEFAULT_LAYOUT_PANEL_VISIBILITY,
64
+ showLeftSidebar: appLayout.props.showLeftSidebar !== false,
65
+ showAuxSidebar: appLayout.props.showAuxSidebar !== false,
66
+ showBottomPanel: appLayout.props.showBottomPanel === true,
67
+ showLeftAux: appLayout.props.showLeftAux === true,
68
+ showRightAux: appLayout.props.showRightAux === true,
69
+ };
70
+ }
71
+ if (typeof appLayout === 'string') {
72
+ return layoutIdToPanelVisibility(appLayout);
73
+ }
74
+ return { ...DEFAULT_LAYOUT_PANEL_VISIBILITY };
75
+ }
76
+
77
+ export async function resolveStoredPanelVisibility(
78
+ appLayout?: LayoutDescriptor,
79
+ ): Promise<LayoutPanelVisibility> {
80
+ try {
81
+ const saved = await appSettings.get(LAYOUT_PANEL_SETTINGS_KEY) as Partial<LayoutPanelVisibility> | undefined;
82
+ if (saved && typeof saved === 'object') {
83
+ return normalizePanelVisibility(saved);
84
+ }
85
+ } catch {
86
+ // fall through to legacy/app defaults
87
+ }
88
+
89
+ try {
90
+ const legacyLayoutId = await appSettings.get(LEGACY_PREFERRED_LAYOUT_KEY) as string | undefined;
91
+ if (legacyLayoutId) {
92
+ return layoutIdToPanelVisibility(legacyLayoutId);
93
+ }
94
+ } catch {
95
+ // fall through to app defaults
96
+ }
97
+
98
+ return resolveInitialPanelVisibilityFromApp(appLayout);
99
+ }
100
+
101
+ export function findStandardLayout(container?: ParentNode | null): DocksStandardLayout | null {
102
+ const root = container ?? document.getElementById('app-root') ?? document.body;
103
+ if (!root || typeof root.querySelector !== 'function') {
104
+ return null;
105
+ }
106
+ return root.querySelector('docks-standard-layout');
107
+ }
108
+
109
+ export type LayoutPanelOption = {
110
+ key: keyof LayoutPanelVisibility;
111
+ label: string;
112
+ icon: string;
113
+ };
114
+
115
+ export const LAYOUT_PANEL_OPTIONS: LayoutPanelOption[] = [
116
+ { key: 'showLeftSidebar', label: 'Left sidebar', icon: 'docks layout-standard-left-sidebar' },
117
+ { key: 'showAuxSidebar', label: 'Right sidebar', icon: 'docks layout-standard-right-sidebar' },
118
+ { key: 'showBottomPanel', label: 'Bottom panel', icon: 'docks layout-standard-bottom-panel' },
119
+ { key: 'showLeftAux', label: 'Left Aux', icon: 'docks layout-standard-left-aux' },
120
+ { key: 'showRightAux', label: 'Right Aux', icon: 'docks layout-standard-right-aux' },
121
+ ];
@@ -85,10 +85,13 @@ export class DocksNavigableInfoDialogContent extends DocksDialogContent {
85
85
  }
86
86
 
87
87
  private handleClose() {
88
- const dialog = this.closest('wa-dialog');
89
- if (dialog && this.resolveCallback) {
88
+ if (this.resolveCallback) {
90
89
  this.resolveCallback();
91
90
  }
91
+ const dialog = this.closest('wa-dialog') as (HTMLElement & { open?: boolean }) | null;
92
+ if (dialog && dialog.open !== false) {
93
+ dialog.open = false;
94
+ }
92
95
  }
93
96
 
94
97
  static styles = [