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.
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
@@ -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: { activationSize: { value: 50, type: 'percentage' } },
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
- data.groupId === this.group.id) {
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.dragHandler.onDragStart((event) => {
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
- Array.from(style).forEach((key) => newNode.style.setProperty(key, style.getPropertyValue(key), style.getPropertyPriority(key)));
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
+ }