@tuidom/elements 0.1.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 (89) hide show
  1. package/dist/body/bodyElement.d.ts +33 -0
  2. package/dist/body/bodyElement.js +87 -0
  3. package/dist/button/buttonElement.d.ts +18 -0
  4. package/dist/button/buttonElement.js +68 -0
  5. package/dist/completionlist/completionDetailsElement.d.ts +49 -0
  6. package/dist/completionlist/completionDetailsElement.js +165 -0
  7. package/dist/completionlist/completionItemKindIcon.d.ts +2 -0
  8. package/dist/completionlist/completionItemKindIcon.js +43 -0
  9. package/dist/completionlist/completionListElement.d.ts +102 -0
  10. package/dist/completionlist/completionListElement.js +309 -0
  11. package/dist/completionlist/completionWidgetElement.d.ts +41 -0
  12. package/dist/completionlist/completionWidgetElement.js +102 -0
  13. package/dist/contextview/contextMenuController.d.ts +23 -0
  14. package/dist/contextview/contextMenuController.js +75 -0
  15. package/dist/contextview/overlayHostElement.d.ts +23 -0
  16. package/dist/contextview/overlayHostElement.js +47 -0
  17. package/dist/editorgroup/editorTabItemElement.d.ts +41 -0
  18. package/dist/editorgroup/editorTabItemElement.js +186 -0
  19. package/dist/editorgroup/editorTabStripElement.d.ts +33 -0
  20. package/dist/editorgroup/editorTabStripElement.js +118 -0
  21. package/dist/editorpart/editorPartElement.d.ts +95 -0
  22. package/dist/editorpart/editorPartElement.js +285 -0
  23. package/dist/inputbox/inputElement.d.ts +34 -0
  24. package/dist/inputbox/inputElement.js +207 -0
  25. package/dist/inputbox/inputState.d.ts +69 -0
  26. package/dist/inputbox/inputState.js +326 -0
  27. package/dist/layout/boxContainerElement.d.ts +27 -0
  28. package/dist/layout/boxContainerElement.js +121 -0
  29. package/dist/layout/boxElement.d.ts +4 -0
  30. package/dist/layout/boxElement.js +30 -0
  31. package/dist/layout/fillerElement.d.ts +12 -0
  32. package/dist/layout/fillerElement.js +16 -0
  33. package/dist/layout/fitContentElement.d.ts +18 -0
  34. package/dist/layout/fitContentElement.js +46 -0
  35. package/dist/layout/hFlexElement.d.ts +26 -0
  36. package/dist/layout/hFlexElement.js +134 -0
  37. package/dist/layout/paddingContainerElement.d.ts +30 -0
  38. package/dist/layout/paddingContainerElement.js +88 -0
  39. package/dist/layout/sizedBoxElement.d.ts +30 -0
  40. package/dist/layout/sizedBoxElement.js +74 -0
  41. package/dist/layout/vFlexElement.d.ts +32 -0
  42. package/dist/layout/vFlexElement.js +136 -0
  43. package/dist/layout/vStackElement.d.ts +18 -0
  44. package/dist/layout/vStackElement.js +74 -0
  45. package/dist/list/listViewElement.d.ts +232 -0
  46. package/dist/list/listViewElement.js +944 -0
  47. package/dist/menu/menuBarElement.d.ts +47 -0
  48. package/dist/menu/menuBarElement.js +285 -0
  49. package/dist/menu/menuBarItemElement.d.ts +20 -0
  50. package/dist/menu/menuBarItemElement.js +68 -0
  51. package/dist/menu/popupMenuElement.d.ts +81 -0
  52. package/dist/menu/popupMenuElement.js +318 -0
  53. package/dist/menu/popupMenuItemElement.d.ts +24 -0
  54. package/dist/menu/popupMenuItemElement.js +89 -0
  55. package/dist/panel/panelContainerElement.d.ts +64 -0
  56. package/dist/panel/panelContainerElement.js +219 -0
  57. package/dist/quickpick/quickPickElement.d.ts +161 -0
  58. package/dist/quickpick/quickPickElement.js +561 -0
  59. package/dist/sash/sashElement.d.ts +30 -0
  60. package/dist/sash/sashElement.js +78 -0
  61. package/dist/scrollbar/iScrollable.d.ts +8 -0
  62. package/dist/scrollbar/iScrollable.js +1 -0
  63. package/dist/scrollbar/scrollBarRenderer.d.ts +36 -0
  64. package/dist/scrollbar/scrollBarRenderer.js +76 -0
  65. package/dist/scrollbar/scrollContainerElement.d.ts +39 -0
  66. package/dist/scrollbar/scrollContainerElement.js +91 -0
  67. package/dist/scrollbar/scrollViewport.d.ts +28 -0
  68. package/dist/scrollbar/scrollViewport.js +65 -0
  69. package/dist/scrollbar/scrollableElement.d.ts +22 -0
  70. package/dist/scrollbar/scrollableElement.js +42 -0
  71. package/dist/selectbox/selectBoxElement.d.ts +48 -0
  72. package/dist/selectbox/selectBoxElement.js +161 -0
  73. package/dist/terminal/encodeKeyForPty.d.ts +3 -0
  74. package/dist/terminal/encodeKeyForPty.js +56 -0
  75. package/dist/terminal/terminalViewElement.d.ts +17 -0
  76. package/dist/terminal/terminalViewElement.js +172 -0
  77. package/dist/text/textBlockElement.d.ts +13 -0
  78. package/dist/text/textBlockElement.js +35 -0
  79. package/dist/text/textLabelElement.d.ts +28 -0
  80. package/dist/text/textLabelElement.js +77 -0
  81. package/dist/titledpanel/titledPanelElement.d.ts +14 -0
  82. package/dist/titledpanel/titledPanelElement.js +40 -0
  83. package/dist/tree/iTreeDataProvider.d.ts +19 -0
  84. package/dist/tree/iTreeDataProvider.js +1 -0
  85. package/dist/tree/treeViewElement.d.ts +116 -0
  86. package/dist/tree/treeViewElement.js +748 -0
  87. package/dist/workbenchlayout/workbenchLayoutElement.d.ts +60 -0
  88. package/dist/workbenchlayout/workbenchLayoutElement.js +203 -0
  89. package/package.json +33 -0
@@ -0,0 +1,318 @@
1
+ import { BoxConstraints, Offset, Point, Rect, Size } from "@tuidom/core/common/geometryPromitives";
2
+ import { BORDER_THICKNESS } from "@tuidom/core/dom/borderStyle";
3
+ import { TUIKeyboardEvent } from "@tuidom/core/dom/events/tuiKeyboardEvent";
4
+ import { RenderContext, TUIElement } from "@tuidom/core/dom/tuiElement";
5
+ import { VStackElement } from "../layout/vStackElement.js";
6
+ import { PopupMenuItemElement, PopupMenuSeparatorElement } from "./popupMenuItemElement.js";
7
+ function isSeparator(entry) {
8
+ return entry.type === "separator";
9
+ }
10
+ function isSubmenu(entry) {
11
+ return entry.type === "submenu";
12
+ }
13
+ /** Индикатор строки-подменю (рисуется в колонке шортката). */
14
+ const SUBMENU_INDICATOR = "›";
15
+ /**
16
+ * Дочерний попап подменю. Вынесено из класса не для красоты: самоссылка
17
+ * `new PopupMenuElement` в теле класса заставляет esbuild/tsx завести алиас
18
+ * `_PopupMenuElement`, и `constructor.name` в приложении получает `_`-префикс —
19
+ * локаторы инспектора (`waitForNode("PopupMenuElement")`) перестают матчить.
20
+ */
21
+ function createSubmenuPopup(entries) {
22
+ return new PopupMenuElement(entries);
23
+ }
24
+ export class PopupMenuElement extends TUIElement {
25
+ entries;
26
+ selectedIndex;
27
+ onClose;
28
+ selectableIndices;
29
+ vstack;
30
+ itemElements = [];
31
+ separatorElements = [];
32
+ /** Открытое дочернее подменю (сессия — в том же overlay-слое). */
33
+ child = null;
34
+ childEntryIndex = -1;
35
+ /** Родитель в цепочке подменю; у корневого меню — null. */
36
+ parentMenu = null;
37
+ constructor(entries) {
38
+ super();
39
+ this.style = { fg: "menu.foreground", bg: "menu.background" };
40
+ this.entries = entries;
41
+ this.selectableIndices = entries.map((e, i) => (isSeparator(e) ? -1 : i)).filter((i) => i >= 0);
42
+ this.selectedIndex = this.selectableIndices.length > 0 ? this.selectableIndices[0] : -1;
43
+ const config = this.computeConfig();
44
+ this.vstack = new VStackElement();
45
+ for (let i = 0; i < entries.length; i++) {
46
+ const entry = entries[i];
47
+ const entryIndex = i;
48
+ if (isSeparator(entry)) {
49
+ const separator = new PopupMenuSeparatorElement();
50
+ this.separatorElements.push(separator);
51
+ this.vstack.addChild(separator, { width: "stretch", height: 1 });
52
+ }
53
+ else if (isSubmenu(entry)) {
54
+ const item = new PopupMenuItemElement(entry.label, config, SUBMENU_INDICATOR, entry.icon);
55
+ item.onSelect = () => {
56
+ this.openSubmenu(entryIndex);
57
+ };
58
+ item.onHover = () => {
59
+ this.selectByEntryIndex(entryIndex);
60
+ };
61
+ this.itemElements.push(item);
62
+ this.vstack.addChild(item, { width: "stretch", height: 1 });
63
+ }
64
+ else {
65
+ const item = new PopupMenuItemElement(entry.label, config, entry.shortcut, entry.icon);
66
+ item.onSelect = entry.onSelect;
67
+ item.onHover = () => {
68
+ this.selectByEntryIndex(entryIndex);
69
+ };
70
+ this.itemElements.push(item);
71
+ this.vstack.addChild(item, { width: "stretch", height: 1 });
72
+ }
73
+ }
74
+ this.appendChild(this.vstack);
75
+ this.updateItemSelectedStates();
76
+ }
77
+ /** Раздаёт стили меню (свои + всем пунктам и сепараторам). */
78
+ /** Observable state: item labels (separators as null) and the selected index. */
79
+ inspectState() {
80
+ return {
81
+ selectedIndex: this.selectedIndex,
82
+ items: this.entries.map((e) => (isSeparator(e) ? null : e.label)),
83
+ };
84
+ }
85
+ getMinIntrinsicWidth(_height) {
86
+ return this.getIntrinsicSize().width;
87
+ }
88
+ getMaxIntrinsicWidth(_height) {
89
+ return this.getIntrinsicSize().width;
90
+ }
91
+ getMinIntrinsicHeight(_width) {
92
+ return this.getIntrinsicSize().height;
93
+ }
94
+ getMaxIntrinsicHeight(_width) {
95
+ return this.getIntrinsicSize().height;
96
+ }
97
+ getIntrinsicSize() {
98
+ const innerWidth = this.vstack.getMaxIntrinsicWidth(this.entries.length);
99
+ const totalWidth = BORDER_THICKNESS * 2 + innerWidth; // borders left + right
100
+ const totalHeight = this.entries.length + BORDER_THICKNESS * 2; // border top + entries + border bottom
101
+ return new Size(totalWidth, totalHeight);
102
+ }
103
+ performLayout(constraints) {
104
+ const intrinsic = this.getIntrinsicSize();
105
+ const resultSize = constraints.constrain(intrinsic);
106
+ super.performLayout(BoxConstraints.tight(resultSize));
107
+ const innerSize = new Size(resultSize.width - BORDER_THICKNESS * 2, resultSize.height - BORDER_THICKNESS * 2);
108
+ this.layoutChild(this.vstack, BORDER_THICKNESS, BORDER_THICKNESS, BoxConstraints.tight(innerSize));
109
+ return resultSize;
110
+ }
111
+ render(context) {
112
+ const w = this.layoutSize.width;
113
+ const h = this.layoutSize.height;
114
+ this.paintOwnBackground(context);
115
+ const borderFg = this.styleVar("menu.border");
116
+ const bg = this.resolvedStyle.bg;
117
+ // Frame with T-connectors on separator rows (├───┤).
118
+ const children = this.vstack.getChildren();
119
+ const separators = [];
120
+ for (let i = 0; i < children.length; i++) {
121
+ if (children[i] instanceof PopupMenuSeparatorElement)
122
+ separators.push(1 + i);
123
+ }
124
+ context.drawBox(0, 0, w, h, { fg: borderFg, bg, separators });
125
+ // Render VStack content
126
+ this.renderChildren(context);
127
+ }
128
+ computeConfig() {
129
+ let hasIcon = false;
130
+ let hasShortcuts = false;
131
+ for (const entry of this.entries) {
132
+ if (isSeparator(entry))
133
+ continue;
134
+ if (entry.icon)
135
+ hasIcon = true;
136
+ // Индикатор подменю живёт в колонке шортката.
137
+ if (isSubmenu(entry) || entry.shortcut)
138
+ hasShortcuts = true;
139
+ }
140
+ return { hasIconColumn: hasIcon, hasShortcuts };
141
+ }
142
+ performDefaultAction(event) {
143
+ if (event.type === "keydown") {
144
+ this.handleMenuKey(event.key);
145
+ }
146
+ }
147
+ /**
148
+ * Клавиатура цепочки меню: фокус остаётся на корне, клавиши роутятся в
149
+ * глубочайшее открытое подменю. ArrowLeft/Escape закрывают один уровень
150
+ * (Escape без подменю — всё меню), ArrowRight/Enter открывают подменю.
151
+ */
152
+ handleMenuKey(key) {
153
+ if (this.child) {
154
+ // ArrowLeft закрывает ГЛУБОЧАЙШИЙ уровень — его родитель ближе всех.
155
+ if (key === "ArrowLeft" && !this.child.menu.hasOpenSubmenu()) {
156
+ this.closeChild();
157
+ return;
158
+ }
159
+ this.child.menu.handleMenuKey(key);
160
+ return;
161
+ }
162
+ if (key === "ArrowUp") {
163
+ this.moveSelection(-1);
164
+ }
165
+ else if (key === "ArrowDown") {
166
+ this.moveSelection(1);
167
+ }
168
+ else if (key === "Enter") {
169
+ this.activateSelected();
170
+ }
171
+ else if (key === "ArrowRight") {
172
+ if (this.selectedIndex >= 0 && isSubmenu(this.entries[this.selectedIndex])) {
173
+ this.openSubmenu(this.selectedIndex);
174
+ }
175
+ }
176
+ else if (key === "Escape") {
177
+ this.onClose?.();
178
+ }
179
+ }
180
+ /** Есть ли открытое подменю (на любом уровне достаточно первого). */
181
+ hasOpenSubmenu() {
182
+ return this.child !== null;
183
+ }
184
+ /** target внутри цепочки подменю этого меню (для close-on-outside владельца). */
185
+ ownsElement(target) {
186
+ let current = target;
187
+ while (current !== null) {
188
+ if (current === this)
189
+ return true;
190
+ current = current.getParent();
191
+ }
192
+ return this.child !== null && this.child.menu.ownsElement(target);
193
+ }
194
+ /** Каскадно закрывает всю цепочку открытых подменю. */
195
+ closeSubmenus() {
196
+ this.closeChild();
197
+ }
198
+ closeChild() {
199
+ // Поля нулит onClose сессии — единая точка и для внешнего закрытия.
200
+ this.child?.session.close();
201
+ }
202
+ openSubmenu(entryIndex) {
203
+ const entry = this.entries[entryIndex];
204
+ /* v8 ignore start -- defensive: вызывается только для submenu-строк */
205
+ if (!isSubmenu(entry))
206
+ return;
207
+ /* v8 ignore stop */
208
+ const layer = this.getOverlayLayer();
209
+ if (!layer)
210
+ return;
211
+ if (this.child !== null && this.childEntryIndex === entryIndex)
212
+ return; // уже открыто
213
+ this.closeChild();
214
+ const resolved = typeof entry.entries === "function" ? entry.entries() : entry.entries;
215
+ if (resolved.length === 0)
216
+ return;
217
+ // Выбор листа в подменю закрывает всю цепочку ДО действия (как у корня).
218
+ const wrapped = resolved.map((e) => {
219
+ if (e.type === "separator" || e.type === "submenu")
220
+ return e;
221
+ const original = e.onSelect;
222
+ return {
223
+ ...e,
224
+ onSelect: () => {
225
+ this.closeAll();
226
+ original?.();
227
+ },
228
+ };
229
+ });
230
+ const child = createSubmenuPopup(wrapped);
231
+ child.parentMenu = this;
232
+ child.onClose = () => {
233
+ this.closeChild();
234
+ };
235
+ // Якорь — строка-владелец: вплотную справа от родителя, первой строкой
236
+ // на уровне строки (preferBelow: false); флип у краёв — computeAnchorPosition.
237
+ const rowGlobalY = this.globalPosition.y + 1 + entryIndex;
238
+ const session = layer.openPopupSession(child, {
239
+ screenX: this.globalPosition.x,
240
+ screenY: rowGlobalY,
241
+ offsetX: this.layoutSize.width,
242
+ preferBelow: false,
243
+ }, {
244
+ visible: true,
245
+ focusOnOpen: false,
246
+ restoreFocus: false,
247
+ closeOnEscape: false,
248
+ pointerPolicy: "passthrough",
249
+ disposeOnClose: true,
250
+ onClose: () => {
251
+ child.closeSubmenus();
252
+ /* v8 ignore start -- защитный гард: к моменту onClose поле ещё указывает на закрываемое подменю (openSubmenu закрывает прежнее ДО назначения нового) */
253
+ if (this.child?.menu !== child)
254
+ return;
255
+ /* v8 ignore stop */
256
+ this.child = null;
257
+ this.childEntryIndex = -1;
258
+ },
259
+ });
260
+ this.child = { menu: child, session };
261
+ this.childEntryIndex = entryIndex;
262
+ }
263
+ /** Закрыть всю цепочку: вверх до корня; корень закрывает свою сессию. */
264
+ closeAll() {
265
+ if (this.parentMenu) {
266
+ this.parentMenu.closeAll();
267
+ return;
268
+ }
269
+ this.closeChild();
270
+ this.onClose?.();
271
+ }
272
+ updateItemSelectedStates() {
273
+ let itemIndex = 0;
274
+ for (let i = 0; i < this.entries.length; i++) {
275
+ if (!isSeparator(this.entries[i])) {
276
+ this.itemElements[itemIndex].selected = i === this.selectedIndex;
277
+ itemIndex++;
278
+ }
279
+ }
280
+ }
281
+ /**
282
+ * Moves the selection onto the item at the given entry index. Only called
283
+ * from item hover callbacks, so `index` is always a selectable entry.
284
+ */
285
+ selectByEntryIndex(index) {
286
+ // Ховер по другой строке родителя закрывает открытое подменю.
287
+ if (this.child && index !== this.childEntryIndex) {
288
+ this.closeChild();
289
+ }
290
+ if (index === this.selectedIndex)
291
+ return;
292
+ this.selectedIndex = index;
293
+ this.updateItemSelectedStates();
294
+ }
295
+ moveSelection(direction) {
296
+ if (this.selectableIndices.length === 0)
297
+ return;
298
+ const currentPos = this.selectableIndices.indexOf(this.selectedIndex);
299
+ let nextPos = currentPos + direction;
300
+ if (nextPos < 0)
301
+ nextPos = this.selectableIndices.length - 1;
302
+ if (nextPos >= this.selectableIndices.length)
303
+ nextPos = 0;
304
+ this.selectedIndex = this.selectableIndices[nextPos];
305
+ this.updateItemSelectedStates();
306
+ }
307
+ activateSelected() {
308
+ if (this.selectedIndex < 0)
309
+ return;
310
+ const entry = this.entries[this.selectedIndex];
311
+ if (isSubmenu(entry)) {
312
+ this.openSubmenu(this.selectedIndex);
313
+ }
314
+ else if (!isSeparator(entry) && entry.onSelect) {
315
+ entry.onSelect();
316
+ }
317
+ }
318
+ }
@@ -0,0 +1,24 @@
1
+ import { CompositeElement } from "@tuidom/core/dom/compositeElement";
2
+ import { RenderContext, TUIElement } from "@tuidom/core/dom/tuiElement";
3
+ export interface PopupMenuItemConfig {
4
+ hasIconColumn: boolean;
5
+ hasShortcuts: boolean;
6
+ }
7
+ export declare class PopupMenuItemElement extends CompositeElement {
8
+ readonly label: string;
9
+ readonly shortcut: string | undefined;
10
+ readonly icon: string | undefined;
11
+ onSelect?: () => void;
12
+ /** Fired when the mouse moves over this item — used to follow the cursor with the selection. */
13
+ onHover?: () => void;
14
+ constructor(label: string, config: PopupMenuItemConfig, shortcut?: string, icon?: string);
15
+ get selected(): boolean;
16
+ set selected(value: boolean);
17
+ }
18
+ export declare class PopupMenuSeparatorElement extends TUIElement {
19
+ getMinIntrinsicWidth(_height: number): number;
20
+ getMaxIntrinsicWidth(_height: number): number;
21
+ getMinIntrinsicHeight(_width: number): number;
22
+ getMaxIntrinsicHeight(_width: number): number;
23
+ render(context: RenderContext): void;
24
+ }
@@ -0,0 +1,89 @@
1
+ import { CompositeElement } from "@tuidom/core/dom/compositeElement";
2
+ import { RenderContext, TUIElement } from "@tuidom/core/dom/tuiElement";
3
+ import { HFlexElement, hflexFill, hflexFit, hflexFixed } from "../layout/hFlexElement.js";
4
+ import { TextLabelElement } from "../text/textLabelElement.js";
5
+ export class PopupMenuItemElement extends CompositeElement {
6
+ label;
7
+ shortcut;
8
+ icon;
9
+ onSelect;
10
+ /** Fired when the mouse moves over this item — used to follow the cursor with the selection. */
11
+ onHover;
12
+ constructor(label, config, shortcut, icon) {
13
+ super();
14
+ this.label = label;
15
+ this.shortcut = shortcut;
16
+ this.icon = icon;
17
+ this.addEventListener("click", (event) => {
18
+ if (event.defaultPrevented)
19
+ return;
20
+ this.onSelect?.();
21
+ });
22
+ // Follow the mouse: hovering an item moves the menu selection onto it (VS Code behavior).
23
+ this.addEventListener("mousemove", (event) => {
24
+ if (event.defaultPrevented)
25
+ return;
26
+ this.onHover?.();
27
+ });
28
+ // Обычное состояние НАСЛЕДУЕТ цвета — так селектбокс может переопределить
29
+ // фон раскрытого списка (dropdown.listBackground) одним style на корне
30
+ // попапа; выделение — when-вариант с токенами menu.selection*.
31
+ this.style = {
32
+ when: [{ states: ["selected"], fg: "menu.selectionForeground", bg: "menu.selectionBackground" }],
33
+ };
34
+ const row = new HFlexElement();
35
+ if (config.hasIconColumn) {
36
+ row.addChild(new TextLabelElement(this.icon ? this.icon + " " : " "), {
37
+ width: hflexFixed(2),
38
+ height: "fill",
39
+ });
40
+ }
41
+ else {
42
+ row.addChild(new TextLabelElement(" "), { width: hflexFixed(1), height: "fill" });
43
+ }
44
+ row.addChild(new TextLabelElement(config.hasShortcuts ? this.label + " " : this.label), {
45
+ width: hflexFill(),
46
+ height: "fill",
47
+ });
48
+ if (config.hasShortcuts && this.shortcut) {
49
+ const shortcutLabel = new TextLabelElement(" " + this.shortcut);
50
+ shortcutLabel.style = {
51
+ fg: "menu.shortcutForeground",
52
+ when: [{ states: ["in:selected"], fg: "menu.selectionForeground" }],
53
+ };
54
+ row.addChild(shortcutLabel, { width: hflexFit(), height: "fill" });
55
+ }
56
+ row.addChild(new TextLabelElement(" "), { width: hflexFixed(1), height: "fill" });
57
+ this.setRootChild(row);
58
+ }
59
+ get selected() {
60
+ return this.hasStyleState("selected");
61
+ }
62
+ set selected(value) {
63
+ this.setStyleState("selected", value);
64
+ }
65
+ }
66
+ export class PopupMenuSeparatorElement extends TUIElement {
67
+ getMinIntrinsicWidth(_height) {
68
+ return 0;
69
+ }
70
+ getMaxIntrinsicWidth(_height) {
71
+ return 0;
72
+ }
73
+ getMinIntrinsicHeight(_width) {
74
+ return 1;
75
+ }
76
+ getMaxIntrinsicHeight(_width) {
77
+ return 1;
78
+ }
79
+ render(context) {
80
+ const width = this.layoutSize.width;
81
+ for (let x = 0; x < width; x++) {
82
+ context.setCell(x, 0, {
83
+ char: "─",
84
+ fg: this.styleVar("menu.separatorBackground"),
85
+ bg: this.resolvedStyle.bg,
86
+ });
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,64 @@
1
+ import { BoxConstraints, Size } from "@tuidom/core/common/geometryPromitives";
2
+ import { RenderContext, TUIElement } from "@tuidom/core/dom/tuiElement";
3
+ /** A view hosted in the bottom Panel (e.g. Problems, Output). */
4
+ export interface PanelView {
5
+ readonly id: string;
6
+ readonly title: string;
7
+ /** The view's content element; null renders {@link placeholder} instead. */
8
+ content: TUIElement | null;
9
+ /**
10
+ * View-specific controls pinned to the right of the tab row (VS Code's
11
+ * `MenuId.ViewTitle` area) — e.g. the Output channel selector.
12
+ */
13
+ actions?: TUIElement | null;
14
+ /** Empty-state message shown when `content` is null (à la VS Code view welcome). */
15
+ readonly placeholder?: string;
16
+ }
17
+ /**
18
+ * The bottom **Panel** part (VS Code `ViewContainerLocation.Panel`): a top border
19
+ * strip + a header row of view tabs (PROBLEMS, OUTPUT, …), a left border that
20
+ * separates it from the sidebar, and the active view's content below. Views are
21
+ * registered via {@link addView}; the active one is shown. A view with no content
22
+ * element renders its {@link PanelView.placeholder} empty-state message.
23
+ *
24
+ * Tab labels are drawn dim (`panelTitle.inactiveForeground`); the active tab is
25
+ * marked with an underline. Colours are pushed by the controller (`panel.*` /
26
+ * `panelTitle.*`), mirroring how `EditorElement` receives its theme colours.
27
+ */
28
+ export declare class PanelContainerElement extends TUIElement {
29
+ /** Fired when a tab is clicked (after the active view has switched). */
30
+ onActivateView?: (id: string) => void;
31
+ private views;
32
+ private activeId;
33
+ constructor();
34
+ addView(view: PanelView): void;
35
+ /** Replaces a registered view's title-row controls (null removes them). */
36
+ setViewActions(id: string, actions: TUIElement | null): void;
37
+ /** Replaces a registered view's content element (e.g. swapping a placeholder for the real view). */
38
+ setViewContent(id: string, content: TUIElement | null): void;
39
+ setActiveView(id: string): void;
40
+ /**
41
+ * Все вкладки живут в дереве постоянно; неактивные — hidden (root и стили
42
+ * доходят до них всегда). Раньше getChildren() отдавал только активную, и
43
+ * контент/actions, прицепленные до укоренения панели, оставались с
44
+ * протухшим root — модель бага #204 (селектор каналов Output молча не
45
+ * открывал выпадашку после restore сессии).
46
+ */
47
+ private syncChildren;
48
+ getActiveViewId(): string | null;
49
+ getViewIds(): string[];
50
+ /**
51
+ * Observable panel state for the inspector: which tabs exist, which is
52
+ * active, and each tab's absolute hit geometry. Replaces the e2e helper that
53
+ * re-derived tab coordinates from TAB_INDENT/TAB_PAD by hand — a test clicks
54
+ * `tabs[i].centerX` on `tabRow` instead.
55
+ */
56
+ inspectState(): Record<string, unknown>;
57
+ /** Правая граница таб-строки — за неё контролы вкладки заезжать не должны. */
58
+ private tabsEnd;
59
+ private activeView;
60
+ /** Header tab layout: ` Title ` segments after the indent, with hit ranges. */
61
+ private tabSegments;
62
+ protected performLayout(constraints: BoxConstraints): Size;
63
+ render(context: RenderContext): void;
64
+ }