dockview-core 5.2.0 → 6.0.0

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 (117) hide show
  1. package/README.md +3 -1
  2. package/dist/cjs/api/component.api.d.ts +93 -1
  3. package/dist/cjs/api/component.api.js +146 -0
  4. package/dist/cjs/api/dockviewGroupPanelApi.d.ts +26 -0
  5. package/dist/cjs/api/dockviewGroupPanelApi.js +21 -1
  6. package/dist/cjs/api/entryPoints.js +4 -5
  7. package/dist/cjs/array.js +7 -8
  8. package/dist/cjs/dnd/dataTransfer.d.ts +2 -1
  9. package/dist/cjs/dnd/dataTransfer.js +5 -4
  10. package/dist/cjs/dnd/droptarget.d.ts +12 -0
  11. package/dist/cjs/dnd/droptarget.js +38 -10
  12. package/dist/cjs/dnd/ghost.js +1 -2
  13. package/dist/cjs/dockview/components/panel/content.js +5 -1
  14. package/dist/cjs/dockview/components/popupService.d.ts +9 -2
  15. package/dist/cjs/dockview/components/popupService.js +24 -9
  16. package/dist/cjs/dockview/components/tab/tab.d.ts +6 -1
  17. package/dist/cjs/dockview/components/tab/tab.js +81 -9
  18. package/dist/cjs/dockview/components/titlebar/tabGroupChip.d.ts +30 -0
  19. package/dist/cjs/dockview/components/titlebar/tabGroupChip.js +95 -0
  20. package/dist/cjs/dockview/components/titlebar/tabGroupIndicator.d.ts +71 -0
  21. package/dist/cjs/dockview/components/titlebar/tabGroupIndicator.js +471 -0
  22. package/dist/cjs/dockview/components/titlebar/tabGroups.d.ts +57 -0
  23. package/dist/cjs/dockview/components/titlebar/tabGroups.js +612 -0
  24. package/dist/cjs/dockview/components/titlebar/tabOverflowControl.js +1 -2
  25. package/dist/cjs/dockview/components/titlebar/tabs.d.ts +59 -0
  26. package/dist/cjs/dockview/components/titlebar/tabs.js +1227 -144
  27. package/dist/cjs/dockview/components/titlebar/tabsContainer.d.ts +6 -0
  28. package/dist/cjs/dockview/components/titlebar/tabsContainer.js +132 -14
  29. package/dist/cjs/dockview/contextMenu.d.ts +10 -0
  30. package/dist/cjs/dockview/contextMenu.js +298 -0
  31. package/dist/cjs/dockview/dockviewComponent.d.ts +60 -3
  32. package/dist/cjs/dockview/dockviewComponent.js +712 -126
  33. package/dist/cjs/dockview/dockviewGroupPanelModel.d.ts +83 -0
  34. package/dist/cjs/dockview/dockviewGroupPanelModel.js +619 -27
  35. package/dist/cjs/dockview/dockviewShell.d.ts +128 -0
  36. package/dist/cjs/dockview/dockviewShell.js +681 -0
  37. package/dist/cjs/dockview/events.d.ts +9 -0
  38. package/dist/cjs/dockview/framework.d.ts +14 -0
  39. package/dist/cjs/dockview/options.d.ts +92 -10
  40. package/dist/cjs/dockview/options.js +10 -7
  41. package/dist/cjs/dockview/tabGroup.d.ts +99 -0
  42. package/dist/cjs/dockview/tabGroup.js +219 -0
  43. package/dist/cjs/dockview/tabGroupAccent.d.ts +65 -0
  44. package/dist/cjs/dockview/tabGroupAccent.js +128 -0
  45. package/dist/cjs/dockview/theme.d.ts +56 -1
  46. package/dist/cjs/dockview/theme.js +103 -6
  47. package/dist/cjs/dockview/types.d.ts +2 -0
  48. package/dist/cjs/dom.js +19 -19
  49. package/dist/cjs/events.js +2 -2
  50. package/dist/cjs/gridview/baseComponentGridview.d.ts +1 -0
  51. package/dist/cjs/gridview/baseComponentGridview.js +6 -3
  52. package/dist/cjs/gridview/gridview.js +7 -7
  53. package/dist/cjs/index.d.ts +8 -5
  54. package/dist/cjs/index.js +6 -1
  55. package/dist/cjs/popoutWindow.js +3 -3
  56. package/dist/cjs/splitview/splitviewPanel.d.ts +1 -1
  57. package/dist/dockview-core.js +6942 -2777
  58. package/dist/dockview-core.min.js +2 -2
  59. package/dist/dockview-core.min.js.map +1 -1
  60. package/dist/dockview-core.min.noStyle.js +2 -2
  61. package/dist/dockview-core.min.noStyle.js.map +1 -1
  62. package/dist/dockview-core.noStyle.js +6940 -2775
  63. package/dist/esm/api/component.api.d.ts +93 -1
  64. package/dist/esm/api/component.api.js +118 -0
  65. package/dist/esm/api/dockviewGroupPanelApi.d.ts +26 -0
  66. package/dist/esm/api/dockviewGroupPanelApi.js +21 -1
  67. package/dist/esm/dnd/dataTransfer.d.ts +2 -1
  68. package/dist/esm/dnd/dataTransfer.js +2 -1
  69. package/dist/esm/dnd/droptarget.d.ts +12 -0
  70. package/dist/esm/dnd/droptarget.js +33 -5
  71. package/dist/esm/dockview/components/panel/content.js +5 -1
  72. package/dist/esm/dockview/components/popupService.d.ts +9 -2
  73. package/dist/esm/dockview/components/popupService.js +23 -9
  74. package/dist/esm/dockview/components/tab/tab.d.ts +6 -1
  75. package/dist/esm/dockview/components/tab/tab.js +83 -9
  76. package/dist/esm/dockview/components/titlebar/tabGroupChip.d.ts +30 -0
  77. package/dist/esm/dockview/components/titlebar/tabGroupChip.js +68 -0
  78. package/dist/esm/dockview/components/titlebar/tabGroupIndicator.d.ts +71 -0
  79. package/dist/esm/dockview/components/titlebar/tabGroupIndicator.js +354 -0
  80. package/dist/esm/dockview/components/titlebar/tabGroups.d.ts +57 -0
  81. package/dist/esm/dockview/components/titlebar/tabGroups.js +406 -0
  82. package/dist/esm/dockview/components/titlebar/tabs.d.ts +59 -0
  83. package/dist/esm/dockview/components/titlebar/tabs.js +1011 -99
  84. package/dist/esm/dockview/components/titlebar/tabsContainer.d.ts +6 -0
  85. package/dist/esm/dockview/components/titlebar/tabsContainer.js +105 -7
  86. package/dist/esm/dockview/contextMenu.d.ts +10 -0
  87. package/dist/esm/dockview/contextMenu.js +213 -0
  88. package/dist/esm/dockview/dockviewComponent.d.ts +60 -3
  89. package/dist/esm/dockview/dockviewComponent.js +460 -35
  90. package/dist/esm/dockview/dockviewGroupPanelModel.d.ts +83 -0
  91. package/dist/esm/dockview/dockviewGroupPanelModel.js +460 -4
  92. package/dist/esm/dockview/dockviewShell.d.ts +128 -0
  93. package/dist/esm/dockview/dockviewShell.js +621 -0
  94. package/dist/esm/dockview/events.d.ts +9 -0
  95. package/dist/esm/dockview/framework.d.ts +14 -0
  96. package/dist/esm/dockview/options.d.ts +92 -10
  97. package/dist/esm/dockview/options.js +5 -2
  98. package/dist/esm/dockview/tabGroup.d.ts +99 -0
  99. package/dist/esm/dockview/tabGroup.js +144 -0
  100. package/dist/esm/dockview/tabGroupAccent.d.ts +65 -0
  101. package/dist/esm/dockview/tabGroupAccent.js +116 -0
  102. package/dist/esm/dockview/theme.d.ts +56 -1
  103. package/dist/esm/dockview/theme.js +102 -5
  104. package/dist/esm/dockview/types.d.ts +2 -0
  105. package/dist/esm/dom.js +1 -1
  106. package/dist/esm/gridview/baseComponentGridview.d.ts +1 -0
  107. package/dist/esm/gridview/baseComponentGridview.js +4 -1
  108. package/dist/esm/index.d.ts +8 -5
  109. package/dist/esm/index.js +2 -1
  110. package/dist/esm/popoutWindow.js +1 -1
  111. package/dist/esm/splitview/splitviewPanel.d.ts +1 -1
  112. package/dist/package/main.cjs.js +6936 -2801
  113. package/dist/package/main.cjs.min.js +2 -2
  114. package/dist/package/main.esm.min.mjs +2 -2
  115. package/dist/package/main.esm.mjs +6922 -2800
  116. package/dist/styles/dockview.css +1945 -196
  117. package/package.json +5 -1
@@ -0,0 +1,57 @@
1
+ import { IDisposable, IValueDisposable } from '../../../lifecycle';
2
+ import { DockviewComponent } from '../../dockviewComponent';
3
+ import { DockviewGroupPanel } from '../../dockviewGroupPanel';
4
+ import { DockviewHeaderDirection } from '../../options';
5
+ import { Tab } from '../tab/tab';
6
+ import { ITabGroup } from '../../tabGroup';
7
+ import { ITabGroupChipRenderer } from '../../framework';
8
+ export interface TabGroupManagerContext {
9
+ readonly group: DockviewGroupPanel;
10
+ readonly accessor: DockviewComponent;
11
+ readonly tabsList: HTMLElement;
12
+ getTabs(): IValueDisposable<Tab>[];
13
+ getTabMap(): Map<string, IValueDisposable<Tab>>;
14
+ getDirection(): DockviewHeaderDirection;
15
+ }
16
+ export interface TabGroupManagerCallbacks {
17
+ onChipContextMenu(tabGroup: ITabGroup, event: MouseEvent): void;
18
+ onChipDragStart(tabGroup: ITabGroup, chip: ITabGroupChipRenderer, event: DragEvent): void;
19
+ }
20
+ export declare class TabGroupManager {
21
+ private readonly _ctx;
22
+ private readonly _callbacks;
23
+ private readonly _chipRenderers;
24
+ private _indicator;
25
+ private _skipNextCollapseAnimation;
26
+ private readonly _pendingTransitionCleanups;
27
+ get chipRenderers(): ReadonlyMap<string, {
28
+ chip: ITabGroupChipRenderer;
29
+ disposable: IDisposable;
30
+ }>;
31
+ get groupUnderlines(): ReadonlyMap<string, HTMLElement>;
32
+ get skipNextCollapseAnimation(): boolean;
33
+ set skipNextCollapseAnimation(value: boolean);
34
+ constructor(_ctx: TabGroupManagerContext, _callbacks: TabGroupManagerCallbacks);
35
+ /**
36
+ * Synchronize chip elements and CSS classes for all tab groups
37
+ * in the parent group model. Call after any tab group mutation.
38
+ */
39
+ update(): void;
40
+ /**
41
+ * Re-read the active palette and re-apply colors to chips, tabs and
42
+ * the indicator. Called when `tabGroupColors` / `tabGroupAccent`
43
+ * options change at runtime.
44
+ */
45
+ refreshAccents(): void;
46
+ positionAllChips(): void;
47
+ snapshotChipWidths(): Map<string, number>;
48
+ positionUnderlines(): void;
49
+ trackUnderlines(): void;
50
+ setGroupDragImage(event: DragEvent, tabGroup: ITabGroup, chipEl: HTMLElement): void;
51
+ cleanupTransition(panelId: string): void;
52
+ disposeAll(): void;
53
+ private _ensureIndicator;
54
+ private _ensureChipForGroup;
55
+ private _positionChipForGroup;
56
+ private _updateTabGroupClasses;
57
+ }
@@ -0,0 +1,406 @@
1
+ import { toggleClass } from '../../../dom';
2
+ import { addDisposableListener } from '../../../events';
3
+ import { CompositeDisposable, } from '../../../lifecycle';
4
+ import { applyTabGroupAccent } from '../../tabGroupAccent';
5
+ import { TabGroupChip } from './tabGroupChip';
6
+ import { NoneTabGroupIndicator, WrapTabGroupIndicator, } from './tabGroupIndicator';
7
+ const EMPTY_MAP = new Map();
8
+ export class TabGroupManager {
9
+ get chipRenderers() {
10
+ return this._chipRenderers;
11
+ }
12
+ get groupUnderlines() {
13
+ var _a, _b;
14
+ return (_b = (_a = this._indicator) === null || _a === void 0 ? void 0 : _a.underlines) !== null && _b !== void 0 ? _b : EMPTY_MAP;
15
+ }
16
+ get skipNextCollapseAnimation() {
17
+ return this._skipNextCollapseAnimation;
18
+ }
19
+ set skipNextCollapseAnimation(value) {
20
+ this._skipNextCollapseAnimation = value;
21
+ }
22
+ constructor(_ctx, _callbacks) {
23
+ this._ctx = _ctx;
24
+ this._callbacks = _callbacks;
25
+ this._chipRenderers = new Map();
26
+ this._indicator = null;
27
+ this._skipNextCollapseAnimation = false;
28
+ this._pendingTransitionCleanups = new Map();
29
+ }
30
+ /**
31
+ * Synchronize chip elements and CSS classes for all tab groups
32
+ * in the parent group model. Call after any tab group mutation.
33
+ */
34
+ update() {
35
+ const model = this._ctx.group.model;
36
+ const tabGroups = model.getTabGroups();
37
+ // Track which group IDs are still active
38
+ const activeGroupIds = new Set();
39
+ for (const tabGroup of tabGroups) {
40
+ activeGroupIds.add(tabGroup.id);
41
+ this._ensureChipForGroup(tabGroup);
42
+ this._positionChipForGroup(tabGroup);
43
+ }
44
+ // Remove chips for dissolved/destroyed groups
45
+ for (const [groupId, entry] of this._chipRenderers) {
46
+ if (!activeGroupIds.has(groupId)) {
47
+ entry.chip.element.remove();
48
+ entry.chip.dispose();
49
+ entry.disposable.dispose();
50
+ this._chipRenderers.delete(groupId);
51
+ }
52
+ }
53
+ // Update CSS classes on all tabs
54
+ this._updateTabGroupClasses();
55
+ }
56
+ /**
57
+ * Re-read the active palette and re-apply colors to chips, tabs and
58
+ * the indicator. Called when `tabGroupColors` / `tabGroupAccent`
59
+ * options change at runtime.
60
+ */
61
+ refreshAccents() {
62
+ var _a, _b;
63
+ for (const tabGroup of this._ctx.group.model.getTabGroups()) {
64
+ const entry = this._chipRenderers.get(tabGroup.id);
65
+ (_b = entry === null || entry === void 0 ? void 0 : (_a = entry.chip).update) === null || _b === void 0 ? void 0 : _b.call(_a, { tabGroup });
66
+ }
67
+ this._updateTabGroupClasses();
68
+ }
69
+ positionAllChips() {
70
+ if (this._chipRenderers.size === 0) {
71
+ return;
72
+ }
73
+ for (const tabGroup of this._ctx.group.model.getTabGroups()) {
74
+ this._positionChipForGroup(tabGroup);
75
+ }
76
+ }
77
+ snapshotChipWidths() {
78
+ const widths = new Map();
79
+ for (const [groupId, entry] of this._chipRenderers) {
80
+ widths.set(groupId, entry.chip.element.getBoundingClientRect().width);
81
+ }
82
+ return widths;
83
+ }
84
+ positionUnderlines() {
85
+ var _a;
86
+ (_a = this._indicator) === null || _a === void 0 ? void 0 : _a.positionUnderlines();
87
+ }
88
+ trackUnderlines() {
89
+ var _a;
90
+ (_a = this._indicator) === null || _a === void 0 ? void 0 : _a.trackUnderlines();
91
+ }
92
+ setGroupDragImage(event, tabGroup, chipEl) {
93
+ if (!event.dataTransfer) {
94
+ return;
95
+ }
96
+ const isVertical = this._ctx.getDirection() === 'vertical';
97
+ // Clone the entire tabs list so cloned nodes inherit all
98
+ // theme styles, CSS variables and class-based rules.
99
+ const clone = this._ctx.tabsList.cloneNode(true);
100
+ if (isVertical) {
101
+ // Force horizontal orientation for the drag ghost by
102
+ // removing vertical CSS classes and overriding writing-mode.
103
+ clone.classList.remove('dv-tabs-container-vertical', 'dv-vertical');
104
+ clone.classList.add('dv-horizontal');
105
+ clone.style.writingMode = 'horizontal-tb';
106
+ clone.style.height = `${this._ctx.tabsList.offsetWidth}px`;
107
+ }
108
+ else {
109
+ clone.style.height = `${this._ctx.tabsList.offsetHeight}px`;
110
+ }
111
+ clone.style.width = 'auto';
112
+ clone.style.overflow = 'visible';
113
+ clone.style.pointerEvents = 'none';
114
+ // Remove all elements except the chip so the drag ghost
115
+ // shows only the chip regardless of the group's expanded state.
116
+ const children = Array.from(clone.children);
117
+ const realChildren = Array.from(this._ctx.tabsList.children);
118
+ for (let i = children.length - 1; i >= 0; i--) {
119
+ const real = realChildren[i];
120
+ if (real === chipEl) {
121
+ continue; // keep the chip only
122
+ }
123
+ children[i].remove();
124
+ }
125
+ // Wrap the clone in a minimal ancestor chain so that CSS
126
+ // selectors like `.dv-groupview.dv-active-group > .dv-tabs-and-actions-container .dv-tabs-container > .dv-tab`
127
+ // match the cloned tabs and apply correct color/background.
128
+ const wrapper = document.createElement('div');
129
+ wrapper.className = 'dv-groupview dv-active-group';
130
+ wrapper.style.position = 'fixed';
131
+ wrapper.style.top = '-10000px';
132
+ wrapper.style.left = '0px';
133
+ wrapper.style.height = 'auto';
134
+ wrapper.style.width = 'auto';
135
+ wrapper.style.pointerEvents = 'none';
136
+ const actionsWrapper = document.createElement('div');
137
+ actionsWrapper.className = 'dv-tabs-and-actions-container';
138
+ actionsWrapper.style.height = 'auto';
139
+ actionsWrapper.style.width = 'auto';
140
+ wrapper.appendChild(actionsWrapper);
141
+ actionsWrapper.appendChild(clone);
142
+ // Append inside the dockview root so CSS variables are inherited
143
+ this._ctx.accessor.element.appendChild(wrapper);
144
+ // Compute cursor offset relative to the wrapper element.
145
+ // The cloned chip is the first .dv-tab-group-chip in the clone.
146
+ const clonedChip = clone.querySelector('.dv-tab-group-chip');
147
+ const chipRect = chipEl.getBoundingClientRect();
148
+ const cursorInChipX = event.clientX - chipRect.left;
149
+ const cursorInChipY = event.clientY - chipRect.top;
150
+ if (clonedChip) {
151
+ const clonedChipRect = clonedChip.getBoundingClientRect();
152
+ const wrapperRect = wrapper.getBoundingClientRect();
153
+ const offsetX = clonedChipRect.left - wrapperRect.left + cursorInChipX;
154
+ const offsetY = clonedChipRect.top - wrapperRect.top + cursorInChipY;
155
+ event.dataTransfer.setDragImage(wrapper, offsetX, offsetY);
156
+ }
157
+ else {
158
+ event.dataTransfer.setDragImage(wrapper, cursorInChipX, cursorInChipY);
159
+ }
160
+ // Clean up after the browser captures the image
161
+ requestAnimationFrame(() => {
162
+ wrapper.remove();
163
+ });
164
+ }
165
+ cleanupTransition(panelId) {
166
+ var _a;
167
+ (_a = this._pendingTransitionCleanups.get(panelId)) === null || _a === void 0 ? void 0 : _a();
168
+ this._pendingTransitionCleanups.delete(panelId);
169
+ }
170
+ disposeAll() {
171
+ var _a;
172
+ (_a = this._indicator) === null || _a === void 0 ? void 0 : _a.dispose();
173
+ this._indicator = null;
174
+ for (const [, cleanup] of this._pendingTransitionCleanups) {
175
+ cleanup();
176
+ }
177
+ this._pendingTransitionCleanups.clear();
178
+ for (const [, entry] of this._chipRenderers) {
179
+ entry.chip.element.remove();
180
+ entry.chip.dispose();
181
+ entry.disposable.dispose();
182
+ }
183
+ this._chipRenderers.clear();
184
+ }
185
+ _ensureIndicator() {
186
+ var _a, _b;
187
+ const mode = (_b = (_a = this._ctx.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabGroupIndicator) !== null && _b !== void 0 ? _b : 'wrap';
188
+ const Ctor = mode === 'none' ? NoneTabGroupIndicator : WrapTabGroupIndicator;
189
+ // Re-create if the indicator type changed (e.g. theme switch)
190
+ if (this._indicator && !(this._indicator instanceof Ctor)) {
191
+ this._indicator.dispose();
192
+ this._indicator = null;
193
+ }
194
+ if (!this._indicator) {
195
+ this._indicator = new Ctor({
196
+ tabsList: this._ctx.tabsList,
197
+ getTabGroups: () => this._ctx.group.model.getTabGroups(),
198
+ getActivePanelId: () => { var _a; return (_a = this._ctx.group.activePanel) === null || _a === void 0 ? void 0 : _a.id; },
199
+ getTabMap: () => this._ctx.getTabMap(),
200
+ getChipElement: (id) => { var _a; return (_a = this._chipRenderers.get(id)) === null || _a === void 0 ? void 0 : _a.chip.element; },
201
+ getDirection: () => this._ctx.getDirection(),
202
+ getColorPalette: () => this._ctx.accessor.tabGroupColorPalette,
203
+ });
204
+ }
205
+ }
206
+ _ensureChipForGroup(tabGroup) {
207
+ if (this._chipRenderers.has(tabGroup.id)) {
208
+ return;
209
+ }
210
+ const createChip = this._ctx.accessor.options.createTabGroupChipComponent;
211
+ const chip = createChip
212
+ ? createChip(tabGroup)
213
+ : new TabGroupChip(this._ctx.accessor.tabGroupColorPalette);
214
+ chip.init({ tabGroup, api: this._ctx.accessor.api });
215
+ const disposables = [
216
+ tabGroup.onDidChange(() => {
217
+ var _a;
218
+ (_a = chip.update) === null || _a === void 0 ? void 0 : _a.call(chip, { tabGroup });
219
+ this._updateTabGroupClasses();
220
+ }),
221
+ tabGroup.onDidPanelChange(() => {
222
+ this._positionChipForGroup(tabGroup);
223
+ this._updateTabGroupClasses();
224
+ }),
225
+ tabGroup.onDidCollapseChange(() => {
226
+ this._updateTabGroupClasses();
227
+ }),
228
+ ];
229
+ // Wire chip context menu and drag for all chip renderers
230
+ if (chip instanceof TabGroupChip) {
231
+ disposables.push(chip.onContextMenu((event) => {
232
+ this._callbacks.onChipContextMenu(tabGroup, event);
233
+ }), chip.onDragStart((event) => {
234
+ this._callbacks.onChipDragStart(tabGroup, chip, event);
235
+ }));
236
+ }
237
+ else {
238
+ disposables.push(addDisposableListener(chip.element, 'contextmenu', (event) => {
239
+ this._callbacks.onChipContextMenu(tabGroup, event);
240
+ }), addDisposableListener(chip.element, 'dragstart', (event) => {
241
+ this._callbacks.onChipDragStart(tabGroup, chip, event);
242
+ }));
243
+ }
244
+ const disposable = new CompositeDisposable(...disposables);
245
+ this._chipRenderers.set(tabGroup.id, { chip, disposable });
246
+ // Group is born collapsed (cross-group drop, layout restore, etc.):
247
+ // its tabs are about to be added without the collapsed class. Skip
248
+ // the animation in the upcoming _updateTabGroupClasses call so they
249
+ // apply the class instantly instead of transitioning from expanded.
250
+ if (tabGroup.collapsed) {
251
+ this._skipNextCollapseAnimation = true;
252
+ }
253
+ }
254
+ _positionChipForGroup(tabGroup) {
255
+ const entry = this._chipRenderers.get(tabGroup.id);
256
+ if (!entry) {
257
+ return;
258
+ }
259
+ const chipEl = entry.chip.element;
260
+ const panelIds = tabGroup.panelIds;
261
+ if (panelIds.length === 0) {
262
+ chipEl.remove();
263
+ return;
264
+ }
265
+ // Find the first tab element of this group
266
+ const firstPanelId = panelIds[0];
267
+ const firstTabEntry = this._ctx.getTabMap().get(firstPanelId);
268
+ if (!firstTabEntry) {
269
+ chipEl.remove();
270
+ return;
271
+ }
272
+ // Insert chip before the first tab of the group
273
+ const firstTabEl = firstTabEntry.value.element;
274
+ if (chipEl.nextSibling !== firstTabEl) {
275
+ this._ctx.tabsList.insertBefore(chipEl, firstTabEl);
276
+ }
277
+ }
278
+ _updateTabGroupClasses() {
279
+ var _a;
280
+ const model = this._ctx.group.model;
281
+ const tabGroups = model.getTabGroups();
282
+ const tabs = this._ctx.getTabs();
283
+ const tabMap = this._ctx.getTabMap();
284
+ let hasAnimation = false;
285
+ // Build a lookup: panelId → tabGroup
286
+ const panelGroupMap = new Map();
287
+ for (const tg of tabGroups) {
288
+ for (const pid of tg.panelIds) {
289
+ panelGroupMap.set(pid, tg);
290
+ }
291
+ }
292
+ for (const tabEntry of tabs) {
293
+ const tab = tabEntry.value;
294
+ const panelId = tab.panel.id;
295
+ const tg = panelGroupMap.get(panelId);
296
+ const isGrouped = !!tg;
297
+ toggleClass(tab.element, 'dv-tab--grouped', isGrouped);
298
+ if (tg) {
299
+ const ids = tg.panelIds;
300
+ const isFirst = ids[0] === panelId;
301
+ const isLast = ids[ids.length - 1] === panelId;
302
+ toggleClass(tab.element, 'dv-tab--group-first', isFirst);
303
+ toggleClass(tab.element, 'dv-tab--group-last', isLast);
304
+ // Expose the resolved group color as a CSS custom property
305
+ // so pure-CSS themes can use it for borders, backgrounds, etc.
306
+ applyTabGroupAccent(tab.element, tg.color, this._ctx.accessor.tabGroupColorPalette);
307
+ // Collapse / expand with animation
308
+ const isCollapsed = tab.element.classList.contains('dv-tab--group-collapsed');
309
+ if (!tg.collapsed && isCollapsed) {
310
+ // Collapsed → expanding: animate back
311
+ hasAnimation = true;
312
+ tab.element.classList.remove('dv-tab--group-collapsed');
313
+ tab.element.classList.add('dv-tab--group-expanding');
314
+ // Clean up any previous transitionend listener
315
+ // from a rapid collapse/expand cycle
316
+ (_a = this._pendingTransitionCleanups.get(panelId)) === null || _a === void 0 ? void 0 : _a();
317
+ const onEnd = () => {
318
+ tab.element.classList.remove('dv-tab--group-expanding');
319
+ tab.element.style.removeProperty('width');
320
+ tab.element.removeEventListener('transitionend', onEnd);
321
+ clearTimeout(fallbackTimer);
322
+ this._pendingTransitionCleanups.delete(panelId);
323
+ };
324
+ // Fallback in case transitionend never fires
325
+ // (e.g. element removed from DOM mid-transition)
326
+ const fallbackTimer = setTimeout(onEnd, 300);
327
+ this._pendingTransitionCleanups.set(panelId, onEnd);
328
+ tab.element.addEventListener('transitionend', onEnd);
329
+ }
330
+ }
331
+ else {
332
+ toggleClass(tab.element, 'dv-tab--group-first', false);
333
+ toggleClass(tab.element, 'dv-tab--group-last', false);
334
+ tab.element.classList.remove('dv-tab--group-collapsed', 'dv-tab--group-expanding');
335
+ tab.element.style.removeProperty('width');
336
+ tab.element.style.removeProperty('--dv-tab-group-color');
337
+ }
338
+ }
339
+ // Track active group IDs for underline/collapse handling
340
+ const activeGroupIds = new Set();
341
+ // Handle collapse animation per group
342
+ for (const tg of tabGroups) {
343
+ activeGroupIds.add(tg.id);
344
+ // Collapse animation
345
+ const hasNewCollapse = tg.collapsed &&
346
+ tg.panelIds.some((pid) => {
347
+ const te = tabMap.get(pid);
348
+ return (te &&
349
+ !te.value.element.classList.contains('dv-tab--group-collapsed'));
350
+ });
351
+ if (hasNewCollapse) {
352
+ if (this._skipNextCollapseAnimation) {
353
+ // Apply collapsed state instantly (no animation).
354
+ // Disable transitions so the CSS transition on
355
+ // dv-tab--group-collapsed doesn't fire.
356
+ const affected = [];
357
+ for (const pid of tg.panelIds) {
358
+ const te = tabMap.get(pid);
359
+ if (te) {
360
+ te.value.element.style.transition = 'none';
361
+ te.value.element.classList.add('dv-tab--group-collapsed');
362
+ affected.push(te.value.element);
363
+ }
364
+ }
365
+ if (affected.length > 0) {
366
+ void affected[0].offsetHeight; // single reflow
367
+ for (const el of affected) {
368
+ el.style.removeProperty('transition');
369
+ }
370
+ }
371
+ }
372
+ else {
373
+ hasAnimation = true;
374
+ const isVert = this._ctx.getDirection() === 'vertical';
375
+ for (const pid of tg.panelIds) {
376
+ const te = tabMap.get(pid);
377
+ if (te &&
378
+ !te.value.element.classList.contains('dv-tab--group-collapsed')) {
379
+ const rect = te.value.element.getBoundingClientRect();
380
+ if (isVert) {
381
+ te.value.element.style.height = `${rect.height}px`;
382
+ }
383
+ else {
384
+ te.value.element.style.width = `${rect.width}px`;
385
+ }
386
+ void te.value.element.offsetHeight; // force reflow
387
+ te.value.element.classList.add('dv-tab--group-collapsed');
388
+ }
389
+ }
390
+ }
391
+ }
392
+ }
393
+ this._skipNextCollapseAnimation = false;
394
+ // Sync indicator underlines and position them
395
+ this._ensureIndicator();
396
+ if (this._indicator) {
397
+ this._indicator.syncUnderlineElements(activeGroupIds);
398
+ if (hasAnimation) {
399
+ this._indicator.trackUnderlines();
400
+ }
401
+ else {
402
+ this._indicator.positionUnderlines();
403
+ }
404
+ }
405
+ }
406
+ }
@@ -15,10 +15,19 @@ export declare class Tabs extends CompositeDisposable {
15
15
  private readonly _observerDisposable;
16
16
  private readonly _scrollbar;
17
17
  private _tabs;
18
+ private readonly _tabMap;
18
19
  private selectedIndex;
19
20
  private _showTabsOverflowControl;
20
21
  private _direction;
21
22
  private _animState;
23
+ private readonly _pendingMarginCleanups;
24
+ private _pendingCollapse;
25
+ private _flipTransitionCleanup;
26
+ private _voidContainer;
27
+ private _voidContainerListeners;
28
+ private _extendedDropZone;
29
+ private _chipDragCleanup;
30
+ private readonly _tabGroupManager;
22
31
  private readonly _onTabDragStart;
23
32
  readonly onTabDragStart: Event<TabDragEvent>;
24
33
  private readonly _onDrop;
@@ -28,11 +37,19 @@ export declare class Tabs extends CompositeDisposable {
28
37
  private readonly _onOverflowTabsChange;
29
38
  readonly onOverflowTabsChange: Event<{
30
39
  tabs: string[];
40
+ tabGroups: string[];
31
41
  reset: boolean;
32
42
  }>;
33
43
  get showTabsOverflowControl(): boolean;
34
44
  set showTabsOverflowControl(value: boolean);
35
45
  get element(): HTMLElement;
46
+ set voidContainer(el: HTMLElement | null);
47
+ /**
48
+ * Handle a drop that occurred on the void container (empty header
49
+ * space to the right of the tabs). Returns `true` if the drop was
50
+ * consumed by an active group drag, `false` otherwise.
51
+ */
52
+ handleVoidDrop(): boolean;
36
53
  get panels(): string[];
37
54
  get size(): number;
38
55
  get tabs(): Tab[];
@@ -49,11 +66,53 @@ export declare class Tabs extends CompositeDisposable {
49
66
  private addTab;
50
67
  private toggleDropdown;
51
68
  updateDragAndDropState(): void;
69
+ /**
70
+ * Synchronize chip elements and CSS classes for all tab groups
71
+ * in the parent group model. Call after any tab group mutation.
72
+ */
73
+ updateTabGroups(): void;
74
+ refreshTabGroupAccent(): void;
75
+ private _handleChipDragStart;
76
+ /**
77
+ * Sets the broader container that is part of the same logical drop surface
78
+ * as this tab list (e.g. the full header element). When a dragleave from
79
+ * the tabs list lands inside this container, `_animState` is preserved so
80
+ * that external dragover listeners can continue the animation.
81
+ */
82
+ setExtendedDropZone(el: HTMLElement): void;
83
+ /**
84
+ * Allows external elements (e.g. void container, left actions) to push an
85
+ * insertion index into the animation while the cursor is outside the tabs
86
+ * list itself. Pass `null` to clear the indicator.
87
+ */
88
+ setExternalInsertionIndex(index: number | null): void;
89
+ /**
90
+ * Called when the drag cursor leaves the entire header area (not just the
91
+ * tabs list). Clears animation state for cross-group drags, which never
92
+ * receive a `dragend` event on this tab list.
93
+ */
94
+ clearExternalAnimState(): void;
52
95
  private snapshotTabPositions;
53
96
  private getAverageTabWidth;
54
97
  private handleDragOver;
98
+ /**
99
+ * Batch-remove a CSS class from multiple elements instantly,
100
+ * forcing only a single reflow for the entire batch.
101
+ */
102
+ private _removeClassInstantlyBatch;
103
+ /**
104
+ * Remove `dv-tab--dragging` from the source tab instantly so it
105
+ * regains its real width before FLIP snapshots.
106
+ */
107
+ private _uncollapsSourceTab;
55
108
  private applyDragOverTransforms;
56
109
  private resetTabTransforms;
110
+ /**
111
+ * Commit a group-drag drop: clear drag classes, move the group
112
+ * in the model, and run a FLIP animation.
113
+ */
114
+ private _commitGroupMove;
115
+ private _clearGroupDragClasses;
57
116
  private resetDragAnimation;
58
117
  private runFlipAnimation;
59
118
  }