@eclipse-docks/core 0.7.126 → 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 (106) hide show
  1. package/dist/api/index.js +4 -4
  2. package/dist/api/services.d.ts +4 -0
  3. package/dist/api/services.d.ts.map +1 -1
  4. package/dist/{api-B3RXoez_.js → api-oPg3Bv1S.js} +422 -293
  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 +4 -5
  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/filebrowser.d.ts.map +1 -1
  13. package/dist/components/language-selector.d.ts.map +1 -1
  14. package/dist/components/layout-switcher.d.ts +1 -1
  15. package/dist/components/layout-switcher.d.ts.map +1 -1
  16. package/dist/components/npm-packages.d.ts +1 -0
  17. package/dist/components/npm-packages.d.ts.map +1 -1
  18. package/dist/components/release-history.d.ts +9 -2
  19. package/dist/components/release-history.d.ts.map +1 -1
  20. package/dist/components/tasks.d.ts.map +1 -1
  21. package/dist/core/apploader.d.ts +2 -9
  22. package/dist/core/apploader.d.ts.map +1 -1
  23. package/dist/core/contribution-names.d.ts +0 -2
  24. package/dist/core/contribution-names.d.ts.map +1 -1
  25. package/dist/core/contribution-targets.d.ts +1 -0
  26. package/dist/core/contribution-targets.d.ts.map +1 -1
  27. package/dist/core/contributionregistry.d.ts +3 -0
  28. package/dist/core/contributionregistry.d.ts.map +1 -1
  29. package/dist/core/dialogservice.d.ts +3 -1
  30. package/dist/core/dialogservice.d.ts.map +1 -1
  31. package/dist/core/dropdown-item.d.ts +43 -0
  32. package/dist/core/dropdown-item.d.ts.map +1 -0
  33. package/dist/core/dropdown-menu-utils.d.ts +5 -0
  34. package/dist/core/dropdown-menu-utils.d.ts.map +1 -1
  35. package/dist/core/layout-panels.d.ts +25 -0
  36. package/dist/core/layout-panels.d.ts.map +1 -0
  37. package/dist/dialogs/navigable-info-dialog.d.ts.map +1 -1
  38. package/dist/dialogs/version-info-dialog.d.ts +18 -2
  39. package/dist/dialogs/version-info-dialog.d.ts.map +1 -1
  40. package/dist/externals/lit.js +1 -1
  41. package/dist/externals/webawesome.js +1 -1
  42. package/dist/index.js +4 -4
  43. package/dist/layouts/standard-layout.d.ts +9 -1
  44. package/dist/layouts/standard-layout.d.ts.map +1 -1
  45. package/dist/parts/contextmenu.d.ts +5 -2
  46. package/dist/parts/contextmenu.d.ts.map +1 -1
  47. package/dist/parts/index.d.ts.map +1 -1
  48. package/dist/parts/index.js +1 -1
  49. package/dist/parts/part.d.ts +4 -0
  50. package/dist/parts/part.d.ts.map +1 -1
  51. package/dist/parts/resizable-grid.d.ts +10 -3
  52. package/dist/parts/resizable-grid.d.ts.map +1 -1
  53. package/dist/parts/tabs.d.ts +6 -0
  54. package/dist/parts/tabs.d.ts.map +1 -1
  55. package/dist/{parts-CsoNFVTw.js → parts-CAt8aeU8.js} +1361 -975
  56. package/dist/parts-CAt8aeU8.js.map +1 -0
  57. package/dist/{webawesome-Y1_HnKa_.js → webawesome-BGXn04Mw.js} +18 -10
  58. package/dist/webawesome-BGXn04Mw.js.map +1 -0
  59. package/dist/{widget-DbLusgAH.js → widget-BqQtioK1.js} +11 -5
  60. package/dist/widget-BqQtioK1.js.map +1 -0
  61. package/dist/widgets/index.js +1 -1
  62. package/dist/{widgets-DnnSZhsi.js → widgets-C9V42Mqq.js} +2 -2
  63. package/dist/{widgets-DnnSZhsi.js.map → widgets-C9V42Mqq.js.map} +1 -1
  64. package/package.json +1 -1
  65. package/src/api/services.ts +20 -0
  66. package/src/commands/filebrowser-contributions.ts +0 -16
  67. package/src/components/attribution.ts +2 -0
  68. package/src/components/command.ts +45 -119
  69. package/src/components/extensions.ts +20 -3
  70. package/src/components/filebrowser.ts +15 -12
  71. package/src/components/language-selector.ts +0 -1
  72. package/src/components/layout-switcher.ts +39 -26
  73. package/src/components/npm-packages.ts +2 -0
  74. package/src/components/release-history.ts +29 -40
  75. package/src/components/tasks.ts +0 -1
  76. package/src/contributions/default-layout-contributions.ts +7 -43
  77. package/src/contributions/default-ui-contributions.ts +4 -4
  78. package/src/core/apploader.ts +7 -54
  79. package/src/core/contribution-names.ts +0 -3
  80. package/src/core/contribution-targets.ts +1 -0
  81. package/src/core/contributionregistry.ts +19 -3
  82. package/src/core/dialogservice.ts +76 -19
  83. package/src/core/dropdown-item.ts +187 -0
  84. package/src/core/dropdown-menu-utils.ts +59 -0
  85. package/src/core/layout-panels.ts +121 -0
  86. package/src/dialogs/navigable-info-dialog.ts +5 -2
  87. package/src/dialogs/version-info-dialog.ts +163 -21
  88. package/src/icons/icons.txt +4 -2
  89. package/src/icons/layout-standard-bottom-panel.svg +4 -4
  90. package/src/icons/layout-standard-full.svg +4 -4
  91. package/src/icons/layout-standard-left-aux.svg +8 -0
  92. package/src/icons/layout-standard-left-sidebar.svg +5 -0
  93. package/src/icons/layout-standard-right-aux.svg +8 -0
  94. package/src/icons/layout-standard-right-sidebar.svg +5 -0
  95. package/src/layouts/standard-layout.ts +81 -5
  96. package/src/parts/contextmenu.ts +39 -44
  97. package/src/parts/index.ts +4 -1
  98. package/src/parts/part.ts +26 -0
  99. package/src/parts/resizable-grid.ts +125 -57
  100. package/src/parts/tabs.ts +103 -0
  101. package/dist/api-B3RXoez_.js.map +0 -1
  102. package/dist/parts-CsoNFVTw.js.map +0 -1
  103. package/dist/webawesome-Y1_HnKa_.js.map +0 -1
  104. package/dist/widget-DbLusgAH.js.map +0 -1
  105. package/src/icons/layout-standard-bottom-sidebar.svg +0 -6
  106. package/src/icons/layout-standard.svg +0 -6
@@ -2,20 +2,24 @@ import {css, html, nothing} from 'lit'
2
2
  import {customElement, property, state} from 'lit/decorators.js'
3
3
  import {DocksElement} from "./element";
4
4
  import {
5
- CommandContribution,
6
5
  Contribution,
7
6
  ContributionChangeEvent,
8
7
  contributionRegistry,
9
- getContributionDisabled,
10
- HTMLContribution,
11
- getContributionVisible,
12
8
  TOPIC_CONTRIBUTEIONS_CHANGED
13
9
  } from "../core/contributionregistry";
14
- import {unsafeHTML} from "lit/directives/unsafe-html.js";
15
10
  import {subscribe} from "../core/events";
16
11
  import {closeSiblingSubmenus} from "../core/dropdown-menu-utils";
17
12
  import {createRef, ref} from "lit/directives/ref.js";
18
- import '../components/command';
13
+ import {renderDropdownContribution, handleDropdownWaSelect} from "../core/dropdown-item";
14
+
15
+ interface VirtualPopupAnchor {
16
+ getBoundingClientRect(): DOMRect;
17
+ }
18
+
19
+ interface WaPopupElement extends HTMLElement {
20
+ anchor?: Element | string | VirtualPopupAnchor;
21
+ reposition(): void;
22
+ }
19
23
 
20
24
  @customElement('docks-contextmenu')
21
25
  export class DocksContextMenu extends DocksElement {
@@ -31,10 +35,15 @@ export class DocksContextMenu extends DocksElement {
31
35
  @state()
32
36
  private isOpen: boolean = false;
33
37
 
34
- @state()
35
- private position: { x: number, y: number } = { x: 0, y: 0 };
38
+ private menuPosition: { x: number; y: number } = { x: 0, y: 0 };
39
+
40
+ private readonly virtualAnchor: VirtualPopupAnchor = {
41
+ getBoundingClientRect: () => {
42
+ const { x, y } = this.menuPosition;
43
+ return new DOMRect(x, y, 0, 0);
44
+ },
45
+ };
36
46
 
37
- private anchorRef = createRef<HTMLElement>();
38
47
  private dropdownRef = createRef<HTMLElement>();
39
48
 
40
49
  private boundHandleDocumentPointerDown = this.handleDocumentPointerDown.bind(this);
@@ -225,43 +234,38 @@ export class DocksContextMenu extends DocksElement {
225
234
  await this.updateComplete;
226
235
  }
227
236
 
228
- this.position = position;
237
+ this.menuPosition = position;
229
238
  this.isOpen = true;
230
239
  await this.updateComplete;
240
+ this.syncVirtualPopupAnchor();
231
241
  document.addEventListener('pointerdown', this.boundHandleDocumentPointerDown, { capture: true });
232
242
  return true;
233
243
  }
234
244
 
245
+ /** Points wa-dropdown's internal wa-popup at the pointer via a VirtualElement anchor. */
246
+ private syncVirtualPopupAnchor(): void {
247
+ const dropdown = this.dropdownRef.value;
248
+ const popup = dropdown?.shadowRoot?.querySelector('wa-popup') as WaPopupElement | null;
249
+ if (!popup) return;
250
+ popup.anchor = this.virtualAnchor;
251
+ popup.reposition();
252
+ }
253
+
235
254
  private onClose() {
236
255
  if (!this.isOpen) return;
237
256
  this.isOpen = false;
238
257
  document.removeEventListener('pointerdown', this.boundHandleDocumentPointerDown, { capture: true });
239
258
  }
240
259
 
241
- private renderContribution(contribution: Contribution) {
242
- if ("command" in contribution) {
243
- const commandContribution = contribution as CommandContribution;
244
- if (!getContributionVisible(commandContribution)) {
245
- return nothing;
246
- }
247
- const disabled = getContributionDisabled(commandContribution);
248
- return html`
249
- <docks-command
250
- cmd="${commandContribution.command}"
251
- icon="${commandContribution.icon ?? ''}"
252
- .params=${commandContribution.params ?? {}}
253
- ?disabled="${disabled}">
254
- ${commandContribution.label}
255
- </docks-command>
256
- `;
257
- } else if ("component" in contribution) {
258
- const contents = (contribution as HTMLContribution).component;
259
- if (contents instanceof Function) {
260
- return contents();
261
- }
262
- return unsafeHTML(contents);
260
+ protected updated(changedProperties: Map<string, unknown>): void {
261
+ super.updated(changedProperties);
262
+ if (changedProperties.has('isOpen') && this.isOpen) {
263
+ this.syncVirtualPopupAnchor();
263
264
  }
264
- return nothing;
265
+ }
266
+
267
+ private renderContribution(contribution: Contribution) {
268
+ return renderDropdownContribution(contribution);
265
269
  }
266
270
 
267
271
  render() {
@@ -272,18 +276,9 @@ export class DocksContextMenu extends DocksElement {
272
276
  ${ref(this.dropdownRef)}
273
277
  ?open=${this.isOpen}
274
278
  @submenu-opening=${this.handleSubmenuOpening}
279
+ @wa-select=${handleDropdownWaSelect}
275
280
  @wa-after-hide=${this.onClose}>
276
-
277
- <div
278
- slot="trigger"
279
- ${ref(this.anchorRef)}
280
- style="position: fixed;
281
- left: ${this.position.x}px;
282
- top: ${this.position.y}px;
283
- width: 1px;
284
- height: 1px;
285
- pointer-events: none;">
286
- </div>
281
+ <span slot="trigger" hidden aria-hidden="true"></span>
287
282
 
288
283
  ${partContent}
289
284
  ${this.contributions.map(c => this.renderContribution(c))}
@@ -1,5 +1,8 @@
1
1
  import "../externals/webawesome"
2
+ import { installOpenDropdownNativeContextMenuGuard } from "../core/dropdown-menu-utils";
2
3
  import "./toolbar"
3
4
  import "./contextmenu"
4
5
  import "./tabs"
5
- import "./resizable-grid"
6
+ import "./resizable-grid"
7
+
8
+ installOpenDropdownNativeContextMenuGuard();
package/src/parts/part.ts CHANGED
@@ -21,6 +21,10 @@ export abstract class DocksPart extends DocksContainer {
21
21
  @property({ type: Boolean, attribute: false })
22
22
  public isEditor: boolean = false;
23
23
 
24
+ /** Compact layout for dialog/tab embedding (no fill-height shell or nested scroll). */
25
+ @property({ type: Boolean, reflect: true })
26
+ embedded = false;
27
+
24
28
  protected commandStack?: CommandStack;
25
29
 
26
30
  public getCommandStack(): CommandStack | undefined {
@@ -135,11 +139,13 @@ export abstract class DocksPart extends DocksContainer {
135
139
  return nothing;
136
140
  }
137
141
 
142
+ /** Part-level toolbar contributions (`toolbar:${partId}`). */
138
143
  private getToolbarTarget(): string | undefined {
139
144
  const contributionKey = this.tabContribution?.editorId ?? this.id ?? this.tabContribution?.name;
140
145
  return contributionKey ? `toolbar:${contributionKey}` : undefined;
141
146
  }
142
147
 
148
+ /** Part-level context-menu contributions (`contextmenu:${partId}`). */
143
149
  private getContextMenuTarget(): string | undefined {
144
150
  const contributionKey = this.tabContribution?.editorId ?? this.id ?? this.tabContribution?.name;
145
151
  return contributionKey ? `contextmenu:${contributionKey}` : undefined;
@@ -292,6 +298,26 @@ export abstract class DocksPart extends DocksContainer {
292
298
  min-height: 100%;
293
299
  }
294
300
 
301
+ :host([embedded]) {
302
+ height: auto;
303
+ }
304
+
305
+ :host([embedded]) .part-shell {
306
+ height: auto;
307
+ overflow: visible;
308
+ grid-template-rows: auto;
309
+ }
310
+
311
+ :host([embedded]) .part-content {
312
+ overflow: visible;
313
+ min-height: 0;
314
+ }
315
+
316
+ :host([embedded]) .part-content-inner {
317
+ height: auto;
318
+ min-height: 0;
319
+ }
320
+
295
321
  .part-toolbar {
296
322
  min-height: 0;
297
323
  }
@@ -46,10 +46,10 @@ export class DocksResizableGrid extends DocksElement {
46
46
  } | null = null;
47
47
 
48
48
  private resizeOverlay: HTMLDivElement | null = null;
49
- private childrenLoaded = false;
50
49
  private childStylesApplied = false;
51
50
  private mutationObserver?: MutationObserver;
52
- private settingsLoaded = false;
51
+ private refreshScheduled = false;
52
+ private layoutGeneration = 0;
53
53
 
54
54
  createRenderRoot() {
55
55
  // intentionally disabling shadow DOM for the resizable grid
@@ -59,87 +59,162 @@ export class DocksResizableGrid extends DocksElement {
59
59
  // ============= Lifecycle Methods =============
60
60
 
61
61
  protected doBeforeUI() {
62
- // Only set up observer if children not yet loaded
63
- if (!this.childrenLoaded) {
64
- // Use MutationObserver to detect when children are added
65
- this.mutationObserver = new MutationObserver(() => {
66
- if (!this.childrenLoaded) {
67
- this.loadChildren();
68
- }
69
- });
70
-
71
- this.mutationObserver.observe(this, { childList: true, subtree: false });
72
-
73
- // Also try to load immediately
74
- this.loadChildren();
62
+ this.mutationObserver = new MutationObserver(() => {
63
+ this.scheduleRefreshGridLayout();
64
+ });
65
+ this.mutationObserver.observe(this, { childList: true, subtree: false });
66
+ this.scheduleRefreshGridLayout();
67
+ }
68
+
69
+ private scheduleRefreshGridLayout() {
70
+ if (this.refreshScheduled) {
71
+ return;
75
72
  }
73
+ this.refreshScheduled = true;
74
+ queueMicrotask(() => {
75
+ this.refreshScheduled = false;
76
+ void this.refreshGridLayout();
77
+ });
76
78
  }
77
79
 
78
- private async loadChildren() {
79
- const potentialChildren = Array.from(this.children).filter(
80
- child => child.tagName !== 'STYLE' &&
81
- child.tagName !== 'SCRIPT' &&
82
- !child.classList.contains('resize-handle')
80
+ private getGridChildrenFromDom(): HTMLElement[] {
81
+ return Array.from(this.children).filter(
82
+ (child) =>
83
+ child.tagName !== 'STYLE' &&
84
+ child.tagName !== 'SCRIPT' &&
85
+ !child.classList.contains('resize-handle'),
83
86
  ) as HTMLElement[];
87
+ }
84
88
 
85
- if (potentialChildren.length === 0) {
86
- return;
89
+ private static isNestedResizableGrid(child: HTMLElement): boolean {
90
+ return child.tagName.toLowerCase() === 'docks-resizable-grid';
91
+ }
92
+
93
+ private applyChildLayoutStyles(): void {
94
+ this.gridChildren.forEach((child, index) => {
95
+ child.style.overflow = 'hidden';
96
+ child.style.height = '100%';
97
+ child.style.width = '100%';
98
+ child.style.gridColumn = this.orientation === 'horizontal' ? `${index * 2 + 1}` : '1';
99
+ child.style.gridRow = this.orientation === 'vertical' ? `${index * 2 + 1}` : '1';
100
+ if (!DocksResizableGrid.isNestedResizableGrid(child)) {
101
+ child.style.display = 'flex';
102
+ child.style.flexDirection = 'column';
103
+ }
104
+ });
105
+ }
106
+
107
+ private resolveGridSizes(childCount: number): string[] {
108
+ const parsedSizes = this.sizes?.split(',').map((s) => s.trim()).filter(Boolean) ?? [];
109
+ if (parsedSizes.length === childCount) {
110
+ return parsedSizes;
111
+ }
112
+ const equalSize = `${100 / childCount}%`;
113
+ return Array.from({ length: childCount }, () => equalSize);
114
+ }
115
+
116
+ private resolvePersistedSizes(persisted: unknown, childCount: number): string[] | null {
117
+ if (!persisted || typeof persisted !== 'object') {
118
+ return null;
87
119
  }
88
120
 
89
- // Mark as loaded and disconnect observer
90
- this.childrenLoaded = true;
91
- if (this.mutationObserver) {
92
- this.mutationObserver.disconnect();
93
- this.mutationObserver = undefined;
121
+ const record = persisted as {
122
+ sizes?: unknown;
123
+ sizesByCount?: Record<string, unknown>;
124
+ };
125
+ const byCount = record.sizesByCount?.[String(childCount)];
126
+ if (Array.isArray(byCount) && byCount.length === childCount && byCount.every((size) => typeof size === 'string')) {
127
+ return byCount;
128
+ }
129
+
130
+ if (Array.isArray(record.sizes) && record.sizes.length === childCount && record.sizes.every((size) => typeof size === 'string')) {
131
+ return record.sizes;
132
+ }
133
+
134
+ return null;
135
+ }
136
+
137
+ private async refreshGridLayout() {
138
+ const generation = ++this.layoutGeneration;
139
+ const potentialChildren = this.getGridChildrenFromDom();
140
+ if (potentialChildren.length === 0) {
141
+ this.gridChildren = [];
142
+ this.gridSizes = [];
143
+ this.childStylesApplied = false;
144
+ this.requestUpdate();
145
+ return;
94
146
  }
95
147
 
96
- // Store children references
148
+ const countChanged = potentialChildren.length !== this.gridChildren.length;
149
+ const identityChanged = countChanged
150
+ || potentialChildren.some((child, index) => child !== this.gridChildren[index]);
97
151
  this.gridChildren = potentialChildren;
98
152
 
99
- // Load persisted sizes once if available
100
- if (!this.settingsLoaded) {
101
- this.settingsLoaded = true;
102
- const persisted = await this.getDialogSetting();
103
- if (persisted && Array.isArray(persisted.sizes) && persisted.sizes.length === this.gridChildren.length) {
104
- this.gridSizes = persisted.sizes;
105
- this.requestUpdate();
106
- return;
107
- }
153
+ const persisted = await this.getDialogSetting();
154
+ if (generation !== this.layoutGeneration) {
155
+ return;
108
156
  }
109
157
 
110
- // Use sizes attribute or equal distribution (only if not restored from settings)
111
- if (this.sizes) {
112
- this.gridSizes = this.sizes.split(',').map(s => s.trim());
113
- } else {
114
- const equalSize = `${100 / this.gridChildren.length}%`;
115
- this.gridSizes = this.gridChildren.map(() => equalSize);
158
+ const restoredSizes = this.resolvePersistedSizes(persisted, this.gridChildren.length);
159
+ if (restoredSizes) {
160
+ const sizesChanged = restoredSizes.join('|') !== this.gridSizes.join('|');
161
+ this.gridSizes = restoredSizes;
162
+ if (identityChanged || sizesChanged) {
163
+ this.childStylesApplied = false;
164
+ }
165
+ this.requestUpdate();
166
+ return;
116
167
  }
117
168
 
169
+ const nextSizes = this.resolveGridSizes(this.gridChildren.length);
170
+ const sizesChanged = nextSizes.join('|') !== this.gridSizes.join('|');
171
+ this.gridSizes = nextSizes;
172
+
173
+ if (identityChanged || sizesChanged) {
174
+ this.childStylesApplied = false;
175
+ }
118
176
  this.requestUpdate();
119
177
  }
120
178
 
179
+ protected doInitUI() {
180
+ this.scheduleRefreshGridLayout();
181
+ }
182
+
121
183
  private async saveSizes() {
122
184
  if (this.gridSizes.length === 0) {
123
185
  return;
124
186
  }
125
187
 
188
+ const persisted = await this.getDialogSetting();
189
+ const existingByCount = persisted && typeof persisted === 'object'
190
+ ? (persisted as { sizesByCount?: Record<string, string[]> }).sizesByCount ?? {}
191
+ : {};
192
+
126
193
  await this.setDialogSetting({
127
194
  sizes: this.gridSizes,
128
- orientation: this.orientation
195
+ sizesByCount: {
196
+ ...existingByCount,
197
+ [String(this.gridSizes.length)]: this.gridSizes,
198
+ },
199
+ orientation: this.orientation,
129
200
  });
130
201
  }
131
202
 
132
203
  updated(changedProperties: Map<string, any>) {
133
204
  super.updated(changedProperties);
134
-
205
+
206
+ if (changedProperties.has('sizes')) {
207
+ this.scheduleRefreshGridLayout();
208
+ }
209
+
135
210
  // Only apply child styles once when children are first loaded
136
211
  // This prevents interfering with nested resizable grids during resize operations
137
- if (changedProperties.has('gridChildren') && !this.childStylesApplied && this.gridChildren.length > 0) {
212
+ if (!this.childStylesApplied && this.gridChildren.length > 0) {
138
213
  this.childStylesApplied = true;
139
-
214
+
140
215
  /**
141
216
  * Direct style manipulation is intentionally used here.
142
- *
217
+ *
143
218
  * Reasoning:
144
219
  * - Grid positioning (gridColumn/gridRow) must be computed dynamically based on
145
220
  * the number of children and orientation at runtime
@@ -149,16 +224,9 @@ export class DocksResizableGrid extends DocksElement {
149
224
  * (e.g., child at index 0 → column 1, index 1 → column 3, etc.)
150
225
  * - This is a layout container whose primary job is to programmatically position
151
226
  * its children within a CSS grid system
227
+ * - Nested resizable grids manage their own display mode and must not receive flex
152
228
  */
153
- this.gridChildren.forEach((child, index) => {
154
- child.style.overflow = 'hidden';
155
- child.style.height = '100%';
156
- child.style.width = '100%';
157
- child.style.gridColumn = this.orientation === 'horizontal' ? `${index * 2 + 1}` : '1';
158
- child.style.gridRow = this.orientation === 'vertical' ? `${index * 2 + 1}` : '1';
159
- child.style.display = 'flex';
160
- child.style.flexDirection = 'column';
161
- });
229
+ this.applyChildLayoutStyles();
162
230
  }
163
231
  }
164
232
 
package/src/parts/tabs.ts CHANGED
@@ -45,6 +45,14 @@ export class DocksTabs extends DocksContainer {
45
45
  @property({ type: Boolean, reflect: true, attribute: 'hide-tabs' })
46
46
  hideTabs: boolean = false;
47
47
 
48
+ /** Size to tab content instead of filling the parent height (e.g. About dialog). */
49
+ @property({ type: Boolean, reflect: true, attribute: 'fit-content' })
50
+ fitContent: boolean = false;
51
+
52
+ /** Fill a fixed-height parent; tab body scrolls instead of resizing the container. */
53
+ @property({ type: Boolean, reflect: true, attribute: 'fill-height' })
54
+ fillHeight: boolean = false;
55
+
48
56
  /** Tab and nav-toolbar control sizing (`s` | `m` | `l`). */
49
57
  @property({ reflect: true, attribute: 'item-size' })
50
58
  itemSize: 's' | 'm' | 'l' = 'm';
@@ -129,6 +137,12 @@ export class DocksTabs extends DocksContainer {
129
137
  return this.tabGroup.value.getAttribute("active");
130
138
  }
131
139
 
140
+ public getTabPart(tabName: string): DocksPart | null {
141
+ const tabPanel = this.getTabPanel(tabName);
142
+ if (!tabPanel) return null;
143
+ return this.getPartFromPanel(tabPanel);
144
+ }
145
+
132
146
  open(contribution: TabContribution): void {
133
147
  const existing = this.contributions.find(c => c.name === contribution.name);
134
148
  if (existing) {
@@ -472,6 +486,95 @@ export class DocksTabs extends DocksContainer {
472
486
  width: 100%;
473
487
  }
474
488
 
489
+ :host([fit-content]) {
490
+ display: flex;
491
+ flex-direction: column;
492
+ height: auto;
493
+ max-height: 100%;
494
+ min-height: 0;
495
+ }
496
+
497
+ :host([fit-content]) wa-tab-group {
498
+ display: flex;
499
+ flex-direction: column;
500
+ flex: 1 1 auto;
501
+ min-height: 0;
502
+ height: auto;
503
+ max-height: 100%;
504
+ }
505
+
506
+ :host([fit-content]) wa-tab-group::part(base) {
507
+ display: flex;
508
+ flex-direction: column;
509
+ flex: 1 1 auto;
510
+ min-height: 0;
511
+ max-height: 100%;
512
+ height: auto;
513
+ }
514
+
515
+ :host([fit-content]) wa-tab-group::part(body) {
516
+ flex: 1 1 auto;
517
+ min-height: 0;
518
+ height: auto;
519
+ overflow-x: hidden;
520
+ overflow-y: auto;
521
+ }
522
+
523
+ :host([fit-content]) wa-tab-panel[active] {
524
+ display: block;
525
+ height: auto;
526
+ overflow: visible;
527
+ }
528
+
529
+ :host([fit-content]) wa-tab-panel > * {
530
+ height: auto;
531
+ min-height: 0;
532
+ }
533
+
534
+ :host([fill-height]) {
535
+ display: flex;
536
+ flex-direction: column;
537
+ height: 100%;
538
+ min-height: 0;
539
+ max-height: 100%;
540
+ }
541
+
542
+ :host([fill-height]) wa-tab-group {
543
+ display: flex;
544
+ flex-direction: column;
545
+ flex: 1 1 auto;
546
+ min-height: 0;
547
+ height: 100%;
548
+ max-height: 100%;
549
+ }
550
+
551
+ :host([fill-height]) wa-tab-group::part(base) {
552
+ display: flex;
553
+ flex-direction: column;
554
+ flex: 1 1 auto;
555
+ min-height: 0;
556
+ max-height: 100%;
557
+ height: 100%;
558
+ }
559
+
560
+ :host([fill-height]) wa-tab-group::part(body) {
561
+ flex: 1 1 auto;
562
+ min-height: 0;
563
+ overflow-x: hidden;
564
+ overflow-y: auto;
565
+ }
566
+
567
+ :host([fill-height]) wa-tab-panel[active] {
568
+ display: block;
569
+ height: auto;
570
+ overflow: visible;
571
+ }
572
+
573
+ :host([fill-height]) wa-tab-panel > * {
574
+ height: auto;
575
+ min-height: 0;
576
+ }
577
+
475
578
  wa-tab-group {
476
579
  height: 100%;
477
580
  width: 100%;