dockview-core 5.2.0 → 6.0.1
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.
- package/README.md +3 -1
- package/dist/cjs/api/component.api.d.ts +93 -1
- package/dist/cjs/api/component.api.js +146 -0
- package/dist/cjs/api/dockviewGroupPanelApi.d.ts +26 -0
- package/dist/cjs/api/dockviewGroupPanelApi.js +21 -1
- package/dist/cjs/api/entryPoints.js +4 -5
- package/dist/cjs/array.js +7 -8
- package/dist/cjs/dnd/dataTransfer.d.ts +2 -1
- package/dist/cjs/dnd/dataTransfer.js +5 -4
- package/dist/cjs/dnd/droptarget.d.ts +12 -0
- package/dist/cjs/dnd/droptarget.js +38 -10
- package/dist/cjs/dnd/ghost.js +1 -2
- package/dist/cjs/dockview/components/panel/content.js +5 -1
- package/dist/cjs/dockview/components/popupService.d.ts +9 -2
- package/dist/cjs/dockview/components/popupService.js +24 -9
- package/dist/cjs/dockview/components/tab/tab.d.ts +6 -1
- package/dist/cjs/dockview/components/tab/tab.js +81 -9
- package/dist/cjs/dockview/components/titlebar/tabGroupChip.d.ts +30 -0
- package/dist/cjs/dockview/components/titlebar/tabGroupChip.js +95 -0
- package/dist/cjs/dockview/components/titlebar/tabGroupIndicator.d.ts +71 -0
- package/dist/cjs/dockview/components/titlebar/tabGroupIndicator.js +471 -0
- package/dist/cjs/dockview/components/titlebar/tabGroups.d.ts +57 -0
- package/dist/cjs/dockview/components/titlebar/tabGroups.js +612 -0
- package/dist/cjs/dockview/components/titlebar/tabOverflowControl.js +1 -2
- package/dist/cjs/dockview/components/titlebar/tabs.d.ts +59 -0
- package/dist/cjs/dockview/components/titlebar/tabs.js +1227 -144
- package/dist/cjs/dockview/components/titlebar/tabsContainer.d.ts +6 -0
- package/dist/cjs/dockview/components/titlebar/tabsContainer.js +132 -14
- package/dist/cjs/dockview/contextMenu.d.ts +10 -0
- package/dist/cjs/dockview/contextMenu.js +298 -0
- package/dist/cjs/dockview/dockviewComponent.d.ts +60 -3
- package/dist/cjs/dockview/dockviewComponent.js +712 -126
- package/dist/cjs/dockview/dockviewGroupPanelModel.d.ts +83 -0
- package/dist/cjs/dockview/dockviewGroupPanelModel.js +619 -27
- package/dist/cjs/dockview/dockviewShell.d.ts +128 -0
- package/dist/cjs/dockview/dockviewShell.js +681 -0
- package/dist/cjs/dockview/events.d.ts +9 -0
- package/dist/cjs/dockview/framework.d.ts +14 -0
- package/dist/cjs/dockview/options.d.ts +92 -10
- package/dist/cjs/dockview/options.js +10 -7
- package/dist/cjs/dockview/tabGroup.d.ts +99 -0
- package/dist/cjs/dockview/tabGroup.js +219 -0
- package/dist/cjs/dockview/tabGroupAccent.d.ts +65 -0
- package/dist/cjs/dockview/tabGroupAccent.js +128 -0
- package/dist/cjs/dockview/theme.d.ts +56 -1
- package/dist/cjs/dockview/theme.js +103 -6
- package/dist/cjs/dockview/types.d.ts +2 -0
- package/dist/cjs/dom.js +19 -19
- package/dist/cjs/events.js +2 -2
- package/dist/cjs/gridview/baseComponentGridview.d.ts +1 -0
- package/dist/cjs/gridview/baseComponentGridview.js +6 -3
- package/dist/cjs/gridview/gridview.js +7 -7
- package/dist/cjs/index.d.ts +8 -5
- package/dist/cjs/index.js +6 -1
- package/dist/cjs/popoutWindow.js +3 -3
- package/dist/cjs/splitview/splitviewPanel.d.ts +1 -1
- package/dist/dockview-core.js +6942 -2777
- package/dist/dockview-core.min.js +2 -2
- package/dist/dockview-core.min.js.map +1 -1
- package/dist/dockview-core.min.noStyle.js +2 -2
- package/dist/dockview-core.min.noStyle.js.map +1 -1
- package/dist/dockview-core.noStyle.js +6940 -2775
- package/dist/esm/api/component.api.d.ts +93 -1
- package/dist/esm/api/component.api.js +118 -0
- package/dist/esm/api/dockviewGroupPanelApi.d.ts +26 -0
- package/dist/esm/api/dockviewGroupPanelApi.js +21 -1
- package/dist/esm/dnd/dataTransfer.d.ts +2 -1
- package/dist/esm/dnd/dataTransfer.js +2 -1
- package/dist/esm/dnd/droptarget.d.ts +12 -0
- package/dist/esm/dnd/droptarget.js +33 -5
- package/dist/esm/dockview/components/panel/content.js +5 -1
- package/dist/esm/dockview/components/popupService.d.ts +9 -2
- package/dist/esm/dockview/components/popupService.js +23 -9
- package/dist/esm/dockview/components/tab/tab.d.ts +6 -1
- package/dist/esm/dockview/components/tab/tab.js +83 -9
- package/dist/esm/dockview/components/titlebar/tabGroupChip.d.ts +30 -0
- package/dist/esm/dockview/components/titlebar/tabGroupChip.js +68 -0
- package/dist/esm/dockview/components/titlebar/tabGroupIndicator.d.ts +71 -0
- package/dist/esm/dockview/components/titlebar/tabGroupIndicator.js +354 -0
- package/dist/esm/dockview/components/titlebar/tabGroups.d.ts +57 -0
- package/dist/esm/dockview/components/titlebar/tabGroups.js +406 -0
- package/dist/esm/dockview/components/titlebar/tabs.d.ts +59 -0
- package/dist/esm/dockview/components/titlebar/tabs.js +1011 -99
- package/dist/esm/dockview/components/titlebar/tabsContainer.d.ts +6 -0
- package/dist/esm/dockview/components/titlebar/tabsContainer.js +105 -7
- package/dist/esm/dockview/contextMenu.d.ts +10 -0
- package/dist/esm/dockview/contextMenu.js +213 -0
- package/dist/esm/dockview/dockviewComponent.d.ts +60 -3
- package/dist/esm/dockview/dockviewComponent.js +460 -35
- package/dist/esm/dockview/dockviewGroupPanelModel.d.ts +83 -0
- package/dist/esm/dockview/dockviewGroupPanelModel.js +460 -4
- package/dist/esm/dockview/dockviewShell.d.ts +128 -0
- package/dist/esm/dockview/dockviewShell.js +621 -0
- package/dist/esm/dockview/events.d.ts +9 -0
- package/dist/esm/dockview/framework.d.ts +14 -0
- package/dist/esm/dockview/options.d.ts +92 -10
- package/dist/esm/dockview/options.js +5 -2
- package/dist/esm/dockview/tabGroup.d.ts +99 -0
- package/dist/esm/dockview/tabGroup.js +144 -0
- package/dist/esm/dockview/tabGroupAccent.d.ts +65 -0
- package/dist/esm/dockview/tabGroupAccent.js +116 -0
- package/dist/esm/dockview/theme.d.ts +56 -1
- package/dist/esm/dockview/theme.js +102 -5
- package/dist/esm/dockview/types.d.ts +2 -0
- package/dist/esm/dom.js +1 -1
- package/dist/esm/gridview/baseComponentGridview.d.ts +1 -0
- package/dist/esm/gridview/baseComponentGridview.js +4 -1
- package/dist/esm/index.d.ts +8 -5
- package/dist/esm/index.js +2 -1
- package/dist/esm/popoutWindow.js +1 -1
- package/dist/esm/splitview/splitviewPanel.d.ts +1 -1
- package/dist/package/main.cjs.js +6936 -2801
- package/dist/package/main.cjs.min.js +2 -2
- package/dist/package/main.esm.min.mjs +2 -2
- package/dist/package/main.esm.mjs +6922 -2800
- package/dist/styles/dockview.css +1945 -196
- package/package.json +5 -1
|
@@ -32,8 +32,11 @@ export class Tab extends CompositeDisposable {
|
|
|
32
32
|
this.accessor = accessor;
|
|
33
33
|
this.group = group;
|
|
34
34
|
this.content = undefined;
|
|
35
|
+
this._direction = 'horizontal';
|
|
35
36
|
this._onPointDown = new Emitter();
|
|
36
37
|
this.onPointerDown = this._onPointDown.event;
|
|
38
|
+
this._onTabClick = new Emitter();
|
|
39
|
+
this.onTabClick = this._onTabClick.event;
|
|
37
40
|
this._onDropped = new Emitter();
|
|
38
41
|
this.onDrop = this._onDropped.event;
|
|
39
42
|
this._onDragStart = new Emitter();
|
|
@@ -48,15 +51,20 @@ export class Tab extends CompositeDisposable {
|
|
|
48
51
|
this.dragHandler = new TabDragHandler(this._element, this.accessor, this.group, this.panel, !!this.accessor.options.disableDnd);
|
|
49
52
|
this.dropTarget = new Droptarget(this._element, {
|
|
50
53
|
acceptedTargetZones: ['left', 'right'],
|
|
51
|
-
overlayModel:
|
|
54
|
+
overlayModel: this._buildOverlayModel(),
|
|
52
55
|
canDisplayOverlay: (event, position) => {
|
|
56
|
+
var _a;
|
|
53
57
|
if (this.group.locked) {
|
|
54
58
|
return false;
|
|
55
59
|
}
|
|
56
60
|
const data = getPanelData();
|
|
57
61
|
if (data && this.accessor.id === data.viewId) {
|
|
58
|
-
if (this.accessor.options.tabAnimation === 'smooth'
|
|
59
|
-
|
|
62
|
+
if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
|
|
63
|
+
// When smooth reorder is enabled, the Tabs
|
|
64
|
+
// container handles all intra-accessor drops
|
|
65
|
+
// (both same-group and cross-group) via
|
|
66
|
+
// animation. Suppress the per-tab overlay so
|
|
67
|
+
// the tab is dropped *beside* rather than *on*.
|
|
60
68
|
return false;
|
|
61
69
|
}
|
|
62
70
|
return true;
|
|
@@ -66,19 +74,65 @@ export class Tab extends CompositeDisposable {
|
|
|
66
74
|
getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
|
|
67
75
|
});
|
|
68
76
|
this.onWillShowOverlay = this.dropTarget.onWillShowOverlay;
|
|
69
|
-
this.addDisposables(this._onPointDown, this._onDropped, this._onDragStart, this._onDragEnd, this.
|
|
77
|
+
this.addDisposables(this._onPointDown, this._onTabClick, this._onDropped, this._onDragStart, this._onDragEnd, this.accessor.onDidOptionsChange(() => {
|
|
78
|
+
this.dropTarget.setOverlayModel(this._buildOverlayModel());
|
|
79
|
+
}), this.dragHandler.onDragStart((event) => {
|
|
80
|
+
var _a;
|
|
70
81
|
if (event.dataTransfer) {
|
|
71
82
|
const style = getComputedStyle(this.element);
|
|
72
83
|
const newNode = this.element.cloneNode(true);
|
|
73
|
-
|
|
84
|
+
const isVertical = this._direction === 'vertical';
|
|
85
|
+
/**
|
|
86
|
+
* Properties to skip when copying computed styles for a
|
|
87
|
+
* vertical tab ghost. `writing-mode` is excluded so we
|
|
88
|
+
* can force `horizontal-tb`. Size and margin logical
|
|
89
|
+
* properties are excluded because their physical meaning
|
|
90
|
+
* flips when writing-mode changes, which would produce
|
|
91
|
+
* incorrect dimensions.
|
|
92
|
+
*/
|
|
93
|
+
const verticalSkip = new Set([
|
|
94
|
+
'writing-mode',
|
|
95
|
+
'inline-size',
|
|
96
|
+
'block-size',
|
|
97
|
+
'min-inline-size',
|
|
98
|
+
'min-block-size',
|
|
99
|
+
'max-inline-size',
|
|
100
|
+
'max-block-size',
|
|
101
|
+
'margin-inline',
|
|
102
|
+
'margin-inline-start',
|
|
103
|
+
'margin-inline-end',
|
|
104
|
+
'margin-block',
|
|
105
|
+
'margin-block-start',
|
|
106
|
+
'margin-block-end',
|
|
107
|
+
'padding-inline',
|
|
108
|
+
'padding-inline-start',
|
|
109
|
+
'padding-inline-end',
|
|
110
|
+
'padding-block',
|
|
111
|
+
'padding-block-start',
|
|
112
|
+
'padding-block-end',
|
|
113
|
+
]);
|
|
114
|
+
Array.from(style).forEach((key) => {
|
|
115
|
+
if (isVertical && verticalSkip.has(key)) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
newNode.style.setProperty(key, style.getPropertyValue(key), style.getPropertyPriority(key));
|
|
119
|
+
});
|
|
120
|
+
if (isVertical) {
|
|
121
|
+
// Force horizontal text flow and swap the physical
|
|
122
|
+
// dimensions so the ghost appears as a horizontal tab.
|
|
123
|
+
newNode.style.setProperty('writing-mode', 'horizontal-tb');
|
|
124
|
+
newNode.style.setProperty('width', style.height);
|
|
125
|
+
newNode.style.setProperty('height', style.width);
|
|
126
|
+
}
|
|
74
127
|
newNode.style.position = 'absolute';
|
|
128
|
+
newNode.classList.add('dv-tab-ghost-drag');
|
|
75
129
|
addGhostImage(event.dataTransfer, newNode, {
|
|
76
130
|
y: -10,
|
|
77
131
|
x: 30,
|
|
78
132
|
});
|
|
79
133
|
}
|
|
80
134
|
this._onDragStart.fire(event);
|
|
81
|
-
if (this.accessor.options.tabAnimation === 'smooth') {
|
|
135
|
+
if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
|
|
82
136
|
// Delay collapse to next frame so the browser
|
|
83
137
|
// captures the full drag image first
|
|
84
138
|
requestAnimationFrame(() => {
|
|
@@ -90,6 +144,10 @@ export class Tab extends CompositeDisposable {
|
|
|
90
144
|
this._onDragEnd.fire(event);
|
|
91
145
|
}), this.dragHandler, addDisposableListener(this._element, 'pointerdown', (event) => {
|
|
92
146
|
this._onPointDown.fire(event);
|
|
147
|
+
}), addDisposableListener(this._element, 'click', (event) => {
|
|
148
|
+
this._onTabClick.fire(event);
|
|
149
|
+
}), addDisposableListener(this._element, 'contextmenu', (event) => {
|
|
150
|
+
this.accessor.contextMenuController.show(this.panel, this.group, event);
|
|
93
151
|
}), this.dropTarget.onDrop((event) => {
|
|
94
152
|
this._onDropped.fire(event);
|
|
95
153
|
}), this.dropTarget);
|
|
@@ -105,11 +163,27 @@ export class Tab extends CompositeDisposable {
|
|
|
105
163
|
this.content = part;
|
|
106
164
|
this._element.appendChild(this.content.element);
|
|
107
165
|
}
|
|
166
|
+
_buildOverlayModel() {
|
|
167
|
+
var _a;
|
|
168
|
+
// 'line' themes render a 4px insertion strip at the tab edge via the
|
|
169
|
+
// anchor container's small-boundary path. 'fill' themes render a
|
|
170
|
+
// half-width highlighted area, so we disable the small-boundary path
|
|
171
|
+
// entirely (boundary = 0 ⟹ isSmall always false).
|
|
172
|
+
const smallBoundary = ((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.dndTabIndicator) === 'line'
|
|
173
|
+
? Number.POSITIVE_INFINITY
|
|
174
|
+
: 0;
|
|
175
|
+
return {
|
|
176
|
+
activationSize: { value: 50, type: 'percentage' },
|
|
177
|
+
smallWidthBoundary: smallBoundary,
|
|
178
|
+
smallHeightBoundary: smallBoundary,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
setDirection(direction) {
|
|
182
|
+
this._direction = direction;
|
|
183
|
+
this.dropTarget.setTargetZones(direction === 'vertical' ? ['top', 'bottom'] : ['left', 'right']);
|
|
184
|
+
}
|
|
108
185
|
updateDragAndDropState() {
|
|
109
186
|
this._element.draggable = !this.accessor.options.disableDnd;
|
|
110
187
|
this.dragHandler.setDisabled(!!this.accessor.options.disableDnd);
|
|
111
188
|
}
|
|
112
|
-
dispose() {
|
|
113
|
-
super.dispose();
|
|
114
|
-
}
|
|
115
189
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Event } from '../../../events';
|
|
2
|
+
import { CompositeDisposable } from '../../../lifecycle';
|
|
3
|
+
import { ITabGroup } from '../../tabGroup';
|
|
4
|
+
import { TabGroupColorPalette } from '../../tabGroupAccent';
|
|
5
|
+
import { ITabGroupChipRenderer } from '../../framework';
|
|
6
|
+
import { DockviewApi } from '../../../api/component.api';
|
|
7
|
+
export declare class TabGroupChip extends CompositeDisposable implements ITabGroupChipRenderer {
|
|
8
|
+
private readonly _palette?;
|
|
9
|
+
private readonly _element;
|
|
10
|
+
private readonly _label;
|
|
11
|
+
private _tabGroup;
|
|
12
|
+
private readonly _onClick;
|
|
13
|
+
readonly onClick: Event<MouseEvent>;
|
|
14
|
+
private readonly _onContextMenu;
|
|
15
|
+
readonly onContextMenu: Event<MouseEvent>;
|
|
16
|
+
private readonly _onDragStart;
|
|
17
|
+
readonly onDragStart: Event<DragEvent>;
|
|
18
|
+
get element(): HTMLElement;
|
|
19
|
+
constructor(_palette?: TabGroupColorPalette | undefined);
|
|
20
|
+
init(params: {
|
|
21
|
+
tabGroup: ITabGroup;
|
|
22
|
+
api: DockviewApi;
|
|
23
|
+
}): void;
|
|
24
|
+
update(params: {
|
|
25
|
+
tabGroup: ITabGroup;
|
|
26
|
+
}): void;
|
|
27
|
+
private updateColor;
|
|
28
|
+
private updateLabel;
|
|
29
|
+
private updateCollapsed;
|
|
30
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { addDisposableListener, Emitter } from '../../../events';
|
|
2
|
+
import { CompositeDisposable } from '../../../lifecycle';
|
|
3
|
+
import { toggleClass } from '../../../dom';
|
|
4
|
+
import { applyTabGroupAccent, } from '../../tabGroupAccent';
|
|
5
|
+
export class TabGroupChip extends CompositeDisposable {
|
|
6
|
+
get element() {
|
|
7
|
+
return this._element;
|
|
8
|
+
}
|
|
9
|
+
constructor(_palette) {
|
|
10
|
+
super();
|
|
11
|
+
this._palette = _palette;
|
|
12
|
+
this._onClick = new Emitter();
|
|
13
|
+
this.onClick = this._onClick.event;
|
|
14
|
+
this._onContextMenu = new Emitter();
|
|
15
|
+
this.onContextMenu = this._onContextMenu.event;
|
|
16
|
+
this._onDragStart = new Emitter();
|
|
17
|
+
this.onDragStart = this._onDragStart.event;
|
|
18
|
+
this._element = document.createElement('div');
|
|
19
|
+
this._element.className = 'dv-tab-group-chip';
|
|
20
|
+
this._element.tabIndex = 0;
|
|
21
|
+
this._element.draggable = true;
|
|
22
|
+
this._label = document.createElement('span');
|
|
23
|
+
this._label.className = 'dv-tab-group-chip-label';
|
|
24
|
+
this._element.appendChild(this._label);
|
|
25
|
+
this.addDisposables(this._onClick, this._onContextMenu, this._onDragStart, addDisposableListener(this._element, 'click', (event) => {
|
|
26
|
+
this._onClick.fire(event);
|
|
27
|
+
}), addDisposableListener(this._element, 'contextmenu', (event) => {
|
|
28
|
+
this._onContextMenu.fire(event);
|
|
29
|
+
}), addDisposableListener(this._element, 'dragstart', (event) => {
|
|
30
|
+
this._onDragStart.fire(event);
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
init(params) {
|
|
34
|
+
this._tabGroup = params.tabGroup;
|
|
35
|
+
this.updateColor(params.tabGroup.color);
|
|
36
|
+
this.updateLabel(params.tabGroup.label);
|
|
37
|
+
this.updateCollapsed(params.tabGroup.collapsed);
|
|
38
|
+
this.addDisposables(params.tabGroup.onDidChange(() => {
|
|
39
|
+
if (this._tabGroup) {
|
|
40
|
+
this.updateColor(this._tabGroup.color);
|
|
41
|
+
this.updateLabel(this._tabGroup.label);
|
|
42
|
+
}
|
|
43
|
+
}), params.tabGroup.onDidCollapseChange((collapsed) => {
|
|
44
|
+
this.updateCollapsed(collapsed);
|
|
45
|
+
}), this._onClick.event(() => {
|
|
46
|
+
var _a;
|
|
47
|
+
(_a = this._tabGroup) === null || _a === void 0 ? void 0 : _a.toggle();
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
update(params) {
|
|
51
|
+
this._tabGroup = params.tabGroup;
|
|
52
|
+
this.updateColor(params.tabGroup.color);
|
|
53
|
+
this.updateLabel(params.tabGroup.label);
|
|
54
|
+
this.updateCollapsed(params.tabGroup.collapsed);
|
|
55
|
+
}
|
|
56
|
+
updateColor(color) {
|
|
57
|
+
var _a;
|
|
58
|
+
applyTabGroupAccent(this._element, color, this._palette);
|
|
59
|
+
toggleClass(this._element, 'dv-tab-group-chip--accent-off', ((_a = this._palette) === null || _a === void 0 ? void 0 : _a.enabled) === false);
|
|
60
|
+
}
|
|
61
|
+
updateLabel(label) {
|
|
62
|
+
this._label.textContent = label;
|
|
63
|
+
toggleClass(this._label, 'dv-tab-group-chip-label--empty', !label);
|
|
64
|
+
}
|
|
65
|
+
updateCollapsed(collapsed) {
|
|
66
|
+
toggleClass(this._element, 'dv-tab-group-chip--collapsed', collapsed);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { IValueDisposable } from '../../../lifecycle';
|
|
2
|
+
import { DockviewHeaderDirection } from '../../options';
|
|
3
|
+
import { Tab } from '../tab/tab';
|
|
4
|
+
import { ITabGroup } from '../../tabGroup';
|
|
5
|
+
import { TabGroupColorPalette } from '../../tabGroupAccent';
|
|
6
|
+
export interface TabGroupIndicatorContext {
|
|
7
|
+
readonly tabsList: HTMLElement;
|
|
8
|
+
getTabGroups(): readonly ITabGroup[];
|
|
9
|
+
getActivePanelId(): string | undefined;
|
|
10
|
+
getTabMap(): Map<string, IValueDisposable<Tab>>;
|
|
11
|
+
getChipElement(groupId: string): HTMLElement | undefined;
|
|
12
|
+
getDirection(): DockviewHeaderDirection;
|
|
13
|
+
getColorPalette(): TabGroupColorPalette | undefined;
|
|
14
|
+
}
|
|
15
|
+
export interface ITabGroupIndicator {
|
|
16
|
+
readonly underlines: ReadonlyMap<string, HTMLElement>;
|
|
17
|
+
positionUnderlines(): void;
|
|
18
|
+
trackUnderlines(): void;
|
|
19
|
+
syncUnderlineElements(activeGroupIds: Set<string>): void;
|
|
20
|
+
getUnderline(groupId: string): HTMLElement | undefined;
|
|
21
|
+
dispose(): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Shared positioning logic for tab group indicators.
|
|
25
|
+
* Subclasses implement `applyShape` to control the visual output.
|
|
26
|
+
*/
|
|
27
|
+
declare abstract class BaseTabGroupIndicator implements ITabGroupIndicator {
|
|
28
|
+
protected readonly _ctx: TabGroupIndicatorContext;
|
|
29
|
+
protected readonly _underlines: Map<string, HTMLElement>;
|
|
30
|
+
private _rafId;
|
|
31
|
+
get underlines(): ReadonlyMap<string, HTMLElement>;
|
|
32
|
+
constructor(_ctx: TabGroupIndicatorContext);
|
|
33
|
+
positionUnderlines(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Continuously reposition underlines every frame for the duration
|
|
36
|
+
* of a tab transition (~200ms), so the underline tracks tab sizes.
|
|
37
|
+
*/
|
|
38
|
+
trackUnderlines(): void;
|
|
39
|
+
syncUnderlineElements(activeGroupIds: Set<string>): void;
|
|
40
|
+
getUnderline(groupId: string): HTMLElement | undefined;
|
|
41
|
+
dispose(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Apply the visual shape to the underline element.
|
|
44
|
+
* Called once per tab group per frame with the computed geometry.
|
|
45
|
+
*/
|
|
46
|
+
protected abstract applyShape(underline: HTMLElement, tg: ITabGroup, startEdge: number, span: number, containerCrossSize: number, activePanelId: string | undefined, containerRect: DOMRect, isVertical: boolean): void;
|
|
47
|
+
private _positionUnderlinesSync;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Chrome-style wrap-around indicator using SVG paths.
|
|
51
|
+
*/
|
|
52
|
+
export declare class WrapTabGroupIndicator extends BaseTabGroupIndicator {
|
|
53
|
+
private _applyStraightLine;
|
|
54
|
+
/**
|
|
55
|
+
* Chrome-style wrap-around underline: a stroked SVG path that runs
|
|
56
|
+
* along the bottom (or left edge in vertical mode), curving up and
|
|
57
|
+
* over the active tab with rounded corners.
|
|
58
|
+
*
|
|
59
|
+
* The SVG and path elements are created once per underline and reused;
|
|
60
|
+
* only the `d`, `stroke`, and viewport attributes are updated each frame.
|
|
61
|
+
*/
|
|
62
|
+
protected applyShape(underline: HTMLElement, tg: ITabGroup, groupStart: number, groupSpan: number, containerCrossSize: number, activePanelId: string | undefined, containerRect: DOMRect, isVertical: boolean): void;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Flat continuous bar indicator — no wrap-around, just a colored line
|
|
66
|
+
* spanning the full tab group width.
|
|
67
|
+
*/
|
|
68
|
+
export declare class NoneTabGroupIndicator extends BaseTabGroupIndicator {
|
|
69
|
+
protected applyShape(underline: HTMLElement, tg: ITabGroup, _startEdge: number, span: number, _containerCrossSize: number, _activePanelId: string | undefined, _containerRect: DOMRect, isVertical: boolean): void;
|
|
70
|
+
}
|
|
71
|
+
export {};
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { resolveTabGroupAccent, } from '../../tabGroupAccent';
|
|
2
|
+
/**
|
|
3
|
+
* Shared positioning logic for tab group indicators.
|
|
4
|
+
* Subclasses implement `applyShape` to control the visual output.
|
|
5
|
+
*/
|
|
6
|
+
class BaseTabGroupIndicator {
|
|
7
|
+
get underlines() {
|
|
8
|
+
return this._underlines;
|
|
9
|
+
}
|
|
10
|
+
constructor(_ctx) {
|
|
11
|
+
this._ctx = _ctx;
|
|
12
|
+
this._underlines = new Map();
|
|
13
|
+
this._rafId = null;
|
|
14
|
+
}
|
|
15
|
+
positionUnderlines() {
|
|
16
|
+
requestAnimationFrame(() => {
|
|
17
|
+
this._positionUnderlinesSync();
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Continuously reposition underlines every frame for the duration
|
|
22
|
+
* of a tab transition (~200ms), so the underline tracks tab sizes.
|
|
23
|
+
*/
|
|
24
|
+
trackUnderlines() {
|
|
25
|
+
if (this._rafId !== null) {
|
|
26
|
+
cancelAnimationFrame(this._rafId);
|
|
27
|
+
}
|
|
28
|
+
const start = performance.now();
|
|
29
|
+
const duration = 250; // slightly longer than transition to ensure we catch the end
|
|
30
|
+
const tick = () => {
|
|
31
|
+
this._positionUnderlinesSync();
|
|
32
|
+
if (performance.now() - start < duration) {
|
|
33
|
+
this._rafId = requestAnimationFrame(tick);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
this._rafId = null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
this._rafId = requestAnimationFrame(tick);
|
|
40
|
+
}
|
|
41
|
+
syncUnderlineElements(activeGroupIds) {
|
|
42
|
+
// Ensure underline elements exist for active groups
|
|
43
|
+
for (const groupId of activeGroupIds) {
|
|
44
|
+
if (!this._underlines.has(groupId)) {
|
|
45
|
+
const underline = document.createElement('div');
|
|
46
|
+
underline.className = 'dv-tab-group-underline';
|
|
47
|
+
this._ctx.tabsList.appendChild(underline);
|
|
48
|
+
this._underlines.set(groupId, underline);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Remove underlines for dissolved groups
|
|
52
|
+
for (const [groupId, el] of this._underlines) {
|
|
53
|
+
if (!activeGroupIds.has(groupId)) {
|
|
54
|
+
el.remove();
|
|
55
|
+
this._underlines.delete(groupId);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
getUnderline(groupId) {
|
|
60
|
+
return this._underlines.get(groupId);
|
|
61
|
+
}
|
|
62
|
+
dispose() {
|
|
63
|
+
if (this._rafId !== null) {
|
|
64
|
+
cancelAnimationFrame(this._rafId);
|
|
65
|
+
this._rafId = null;
|
|
66
|
+
}
|
|
67
|
+
for (const [, el] of this._underlines) {
|
|
68
|
+
el.remove();
|
|
69
|
+
}
|
|
70
|
+
this._underlines.clear();
|
|
71
|
+
}
|
|
72
|
+
_positionUnderlinesSync() {
|
|
73
|
+
const containerRect = this._ctx.tabsList.getBoundingClientRect();
|
|
74
|
+
const tabGroups = this._ctx.getTabGroups();
|
|
75
|
+
const isVertical = this._ctx.getDirection() === 'vertical';
|
|
76
|
+
const containerCrossSize = isVertical
|
|
77
|
+
? containerRect.width
|
|
78
|
+
: containerRect.height;
|
|
79
|
+
const activePanelId = this._ctx.getActivePanelId();
|
|
80
|
+
const tabMap = this._ctx.getTabMap();
|
|
81
|
+
for (const tg of tabGroups) {
|
|
82
|
+
const underline = this._underlines.get(tg.id);
|
|
83
|
+
if (!underline) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const panelIds = tg.panelIds;
|
|
87
|
+
if (panelIds.length === 0) {
|
|
88
|
+
underline.style.display = 'none';
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
underline.style.display = '';
|
|
92
|
+
const chipEl = this._ctx.getChipElement(tg.id);
|
|
93
|
+
// In vertical mode, compute top/bottom edges; in horizontal, left/right.
|
|
94
|
+
let startEdge;
|
|
95
|
+
if (chipEl) {
|
|
96
|
+
const chipRect = chipEl.getBoundingClientRect();
|
|
97
|
+
const chipStyle = getComputedStyle(chipEl);
|
|
98
|
+
const leadingMargin = isVertical
|
|
99
|
+
? Number.parseFloat(chipStyle.marginTop) || 0
|
|
100
|
+
: Number.parseFloat(chipStyle.marginLeft) || 0;
|
|
101
|
+
startEdge = isVertical
|
|
102
|
+
? chipRect.top - containerRect.top - leadingMargin
|
|
103
|
+
: chipRect.left - containerRect.left - leadingMargin;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const firstPanelId = panelIds[0];
|
|
107
|
+
const firstTabEntry = tabMap.get(firstPanelId);
|
|
108
|
+
if (firstTabEntry) {
|
|
109
|
+
const firstRect = firstTabEntry.value.element.getBoundingClientRect();
|
|
110
|
+
startEdge = isVertical
|
|
111
|
+
? firstRect.top - containerRect.top
|
|
112
|
+
: firstRect.left - containerRect.left;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
startEdge = 0;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Measure the actual last tab position (follows CSS transitions in real-time)
|
|
119
|
+
const lastPanelId = panelIds[panelIds.length - 1];
|
|
120
|
+
const lastTabEntry = tabMap.get(lastPanelId);
|
|
121
|
+
if (!lastTabEntry) {
|
|
122
|
+
if (isVertical) {
|
|
123
|
+
underline.style.top = `${startEdge}px`;
|
|
124
|
+
underline.style.height = '0px';
|
|
125
|
+
underline.style.left = '';
|
|
126
|
+
underline.style.width = '';
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
underline.style.left = `${startEdge}px`;
|
|
130
|
+
underline.style.width = '0px';
|
|
131
|
+
underline.style.top = '';
|
|
132
|
+
underline.style.height = '';
|
|
133
|
+
}
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const lastTabRect = lastTabEntry.value.element.getBoundingClientRect();
|
|
137
|
+
let endEdge = isVertical
|
|
138
|
+
? lastTabRect.bottom - containerRect.top
|
|
139
|
+
: lastTabRect.right - containerRect.left;
|
|
140
|
+
let span = endEdge - startEdge;
|
|
141
|
+
// During collapse or expand: converge both edges toward chip center
|
|
142
|
+
const isAnimating = tg.collapsed ||
|
|
143
|
+
tg.panelIds.some((pid) => {
|
|
144
|
+
const te = tabMap.get(pid);
|
|
145
|
+
return (te &&
|
|
146
|
+
te.value.element.classList.contains('dv-tab--group-expanding'));
|
|
147
|
+
});
|
|
148
|
+
if (isAnimating && chipEl) {
|
|
149
|
+
const chipRect = chipEl.getBoundingClientRect();
|
|
150
|
+
const chipCenter = isVertical
|
|
151
|
+
? chipRect.top + chipRect.height / 2 - containerRect.top
|
|
152
|
+
: chipRect.left + chipRect.width / 2 - containerRect.left;
|
|
153
|
+
// Sum of current visible tab sizes (shrinking or growing)
|
|
154
|
+
let currentTabSize = 0;
|
|
155
|
+
let fullTabSize = 0;
|
|
156
|
+
for (const pid of tg.panelIds) {
|
|
157
|
+
const te = tabMap.get(pid);
|
|
158
|
+
if (!te)
|
|
159
|
+
continue;
|
|
160
|
+
const el = te.value.element;
|
|
161
|
+
if (isVertical) {
|
|
162
|
+
currentTabSize += el.getBoundingClientRect().height;
|
|
163
|
+
fullTabSize += el.scrollHeight;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
currentTabSize += el.getBoundingClientRect().width;
|
|
167
|
+
fullTabSize += el.scrollWidth;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// progress: 0 when tabs at 0 size, 1 when fully open
|
|
171
|
+
const progress = fullTabSize > 0
|
|
172
|
+
? Math.min(1, currentTabSize / fullTabSize)
|
|
173
|
+
: 0;
|
|
174
|
+
// Interpolate start and end edges toward chip center
|
|
175
|
+
startEdge = chipCenter + (startEdge - chipCenter) * progress;
|
|
176
|
+
endEdge = chipCenter + (endEdge - chipCenter) * progress;
|
|
177
|
+
span = Math.max(0, endEdge - startEdge);
|
|
178
|
+
}
|
|
179
|
+
if (isVertical) {
|
|
180
|
+
underline.style.top = `${startEdge}px`;
|
|
181
|
+
underline.style.height = `${Math.max(0, span)}px`;
|
|
182
|
+
// Clear horizontal properties
|
|
183
|
+
underline.style.left = '';
|
|
184
|
+
underline.style.width = '';
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
underline.style.left = `${startEdge}px`;
|
|
188
|
+
underline.style.width = `${Math.max(0, span)}px`;
|
|
189
|
+
// Clear vertical properties
|
|
190
|
+
underline.style.top = '';
|
|
191
|
+
underline.style.height = '';
|
|
192
|
+
}
|
|
193
|
+
this.applyShape(underline, tg, startEdge, span, containerCrossSize, activePanelId, containerRect, isVertical);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Chrome-style wrap-around indicator using SVG paths.
|
|
199
|
+
*/
|
|
200
|
+
export class WrapTabGroupIndicator extends BaseTabGroupIndicator {
|
|
201
|
+
_applyStraightLine(svg, path, underline, t, mainSize, isVertical) {
|
|
202
|
+
if (isVertical) {
|
|
203
|
+
svg.setAttribute('width', String(t));
|
|
204
|
+
svg.setAttribute('height', String(mainSize));
|
|
205
|
+
underline.style.width = `${t}px`;
|
|
206
|
+
underline.style.height = `${mainSize}px`;
|
|
207
|
+
path.setAttribute('d', `M ${t / 2},0 L ${t / 2},${mainSize}`);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
svg.setAttribute('width', String(mainSize));
|
|
211
|
+
svg.setAttribute('height', String(t));
|
|
212
|
+
underline.style.width = `${mainSize}px`;
|
|
213
|
+
underline.style.height = `${t}px`;
|
|
214
|
+
path.setAttribute('d', `M 0,${t / 2} L ${mainSize},${t / 2}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Chrome-style wrap-around underline: a stroked SVG path that runs
|
|
219
|
+
* along the bottom (or left edge in vertical mode), curving up and
|
|
220
|
+
* over the active tab with rounded corners.
|
|
221
|
+
*
|
|
222
|
+
* The SVG and path elements are created once per underline and reused;
|
|
223
|
+
* only the `d`, `stroke`, and viewport attributes are updated each frame.
|
|
224
|
+
*/
|
|
225
|
+
applyShape(underline, tg, groupStart, groupSpan, containerCrossSize, activePanelId, containerRect, isVertical) {
|
|
226
|
+
const t = 2; // line thickness in px
|
|
227
|
+
const crossSize = containerCrossSize;
|
|
228
|
+
const mainSize = groupSpan;
|
|
229
|
+
const color = resolveTabGroupAccent(tg.color, this._ctx.getColorPalette());
|
|
230
|
+
if (mainSize <= 0 || crossSize <= 0 || color === undefined) {
|
|
231
|
+
underline.style.display = 'none';
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
underline.style.display = '';
|
|
235
|
+
// Find the active tab within this group
|
|
236
|
+
let activeTabEntry;
|
|
237
|
+
if (activePanelId && tg.panelIds.includes(activePanelId)) {
|
|
238
|
+
activeTabEntry = this._ctx.getTabMap().get(activePanelId);
|
|
239
|
+
}
|
|
240
|
+
// Ensure SVG + path child exists (created once, reused)
|
|
241
|
+
let svg = underline.firstElementChild;
|
|
242
|
+
let path;
|
|
243
|
+
if (!svg || svg.tagName !== 'svg') {
|
|
244
|
+
underline.innerHTML = '';
|
|
245
|
+
svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
246
|
+
svg.style.display = 'block';
|
|
247
|
+
path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
248
|
+
path.setAttribute('fill', 'none');
|
|
249
|
+
svg.appendChild(path);
|
|
250
|
+
underline.appendChild(svg);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
path = svg.firstElementChild;
|
|
254
|
+
}
|
|
255
|
+
path.setAttribute('stroke', color);
|
|
256
|
+
path.setAttribute('stroke-width', String(t));
|
|
257
|
+
if (!activeTabEntry) {
|
|
258
|
+
this._applyStraightLine(svg, path, underline, t, mainSize, isVertical);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const activeRect = activeTabEntry.value.element.getBoundingClientRect();
|
|
262
|
+
// Compute active tab start/end relative to the group start
|
|
263
|
+
let aStart;
|
|
264
|
+
let aEnd;
|
|
265
|
+
if (isVertical) {
|
|
266
|
+
aStart = Math.max(0, activeRect.top - containerRect.top - groupStart);
|
|
267
|
+
aEnd = Math.min(mainSize, activeRect.bottom - containerRect.top - groupStart);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
aStart = Math.max(0, activeRect.left - containerRect.left - groupStart);
|
|
271
|
+
aEnd = Math.min(mainSize, activeRect.right - containerRect.left - groupStart);
|
|
272
|
+
}
|
|
273
|
+
if (aEnd <= aStart) {
|
|
274
|
+
this._applyStraightLine(svg, path, underline, t, mainSize, isVertical);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const r = 6; // corner radius
|
|
278
|
+
const half = t / 2;
|
|
279
|
+
if (isVertical) {
|
|
280
|
+
const svgW = crossSize;
|
|
281
|
+
const svgH = mainSize;
|
|
282
|
+
svg.setAttribute('width', String(svgW));
|
|
283
|
+
svg.setAttribute('height', String(svgH));
|
|
284
|
+
underline.style.width = `${svgW}px`;
|
|
285
|
+
underline.style.height = `${svgH}px`;
|
|
286
|
+
const xLeft = half;
|
|
287
|
+
const xRight = svgW - half;
|
|
288
|
+
const d = [
|
|
289
|
+
`M ${xLeft},0`,
|
|
290
|
+
`L ${xLeft},${aStart - r}`,
|
|
291
|
+
`Q ${xLeft},${aStart} ${xLeft + r},${aStart}`,
|
|
292
|
+
`L ${xRight - r},${aStart}`,
|
|
293
|
+
`Q ${xRight},${aStart} ${xRight},${aStart + r}`,
|
|
294
|
+
`L ${xRight},${aEnd - r}`,
|
|
295
|
+
`Q ${xRight},${aEnd} ${xRight - r},${aEnd}`,
|
|
296
|
+
`L ${xLeft + r},${aEnd}`,
|
|
297
|
+
`Q ${xLeft},${aEnd} ${xLeft},${aEnd + r}`,
|
|
298
|
+
`L ${xLeft},${svgH}`,
|
|
299
|
+
].join(' ');
|
|
300
|
+
path.setAttribute('d', d);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
const svgW = mainSize;
|
|
304
|
+
const svgH = crossSize;
|
|
305
|
+
svg.setAttribute('width', String(svgW));
|
|
306
|
+
svg.setAttribute('height', String(svgH));
|
|
307
|
+
underline.style.width = `${svgW}px`;
|
|
308
|
+
underline.style.height = `${svgH}px`;
|
|
309
|
+
const yBot = svgH - half;
|
|
310
|
+
const yTop = half;
|
|
311
|
+
const d = [
|
|
312
|
+
`M 0,${yBot}`,
|
|
313
|
+
`L ${aStart - r},${yBot}`,
|
|
314
|
+
`Q ${aStart},${yBot} ${aStart},${yBot - r}`,
|
|
315
|
+
`L ${aStart},${yTop + r}`,
|
|
316
|
+
`Q ${aStart},${yTop} ${aStart + r},${yTop}`,
|
|
317
|
+
`L ${aEnd - r},${yTop}`,
|
|
318
|
+
`Q ${aEnd},${yTop} ${aEnd},${yTop + r}`,
|
|
319
|
+
`L ${aEnd},${yBot - r}`,
|
|
320
|
+
`Q ${aEnd},${yBot} ${aEnd + r},${yBot}`,
|
|
321
|
+
`L ${svgW},${yBot}`,
|
|
322
|
+
].join(' ');
|
|
323
|
+
path.setAttribute('d', d);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Flat continuous bar indicator — no wrap-around, just a colored line
|
|
329
|
+
* spanning the full tab group width.
|
|
330
|
+
*/
|
|
331
|
+
export class NoneTabGroupIndicator extends BaseTabGroupIndicator {
|
|
332
|
+
applyShape(underline, tg, _startEdge, span, _containerCrossSize, _activePanelId, _containerRect, isVertical) {
|
|
333
|
+
const t = 2; // line thickness in px
|
|
334
|
+
const color = resolveTabGroupAccent(tg.color, this._ctx.getColorPalette());
|
|
335
|
+
if (span <= 0 || color === undefined) {
|
|
336
|
+
underline.style.display = 'none';
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
underline.style.display = '';
|
|
340
|
+
// Clear any SVG content left over from a mode switch
|
|
341
|
+
if (underline.firstElementChild) {
|
|
342
|
+
underline.innerHTML = '';
|
|
343
|
+
}
|
|
344
|
+
underline.style.backgroundColor = color;
|
|
345
|
+
if (isVertical) {
|
|
346
|
+
underline.style.width = `${t}px`;
|
|
347
|
+
underline.style.height = `${span}px`;
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
underline.style.width = `${span}px`;
|
|
351
|
+
underline.style.height = `${t}px`;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|