@tuidom/core 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 (91) hide show
  1. package/dist/backend/iTerminalBackend.d.ts +41 -0
  2. package/dist/backend/iTerminalBackend.js +1 -0
  3. package/dist/common/colorUtils.d.ts +19 -0
  4. package/dist/common/colorUtils.js +30 -0
  5. package/dist/common/displayLine.d.ts +72 -0
  6. package/dist/common/displayLine.js +190 -0
  7. package/dist/common/disposable.d.ts +9 -0
  8. package/dist/common/disposable.js +18 -0
  9. package/dist/common/geometryPromitives.d.ts +47 -0
  10. package/dist/common/geometryPromitives.js +115 -0
  11. package/dist/common/iTerminalSurface.d.ts +68 -0
  12. package/dist/common/iTerminalSurface.js +8 -0
  13. package/dist/common/measureTextWidth.d.ts +21 -0
  14. package/dist/common/measureTextWidth.js +43 -0
  15. package/dist/common/styleFlags.d.ts +17 -0
  16. package/dist/common/styleFlags.js +16 -0
  17. package/dist/common/textTruncation.d.ts +30 -0
  18. package/dist/common/textTruncation.js +124 -0
  19. package/dist/common/typingUtils.d.ts +1 -0
  20. package/dist/common/typingUtils.js +3 -0
  21. package/dist/common/unicodeWidth.d.ts +18 -0
  22. package/dist/common/unicodeWidth.js +339 -0
  23. package/dist/dom/borderStyle.d.ts +31 -0
  24. package/dist/dom/borderStyle.js +40 -0
  25. package/dist/dom/compositeElement.d.ts +20 -0
  26. package/dist/dom/compositeElement.js +44 -0
  27. package/dist/dom/events/contextMenuEventSource.d.ts +17 -0
  28. package/dist/dom/events/contextMenuEventSource.js +44 -0
  29. package/dist/dom/events/focusManager.d.ts +13 -0
  30. package/dist/dom/events/focusManager.js +56 -0
  31. package/dist/dom/events/mouseEventDispatcher.d.ts +24 -0
  32. package/dist/dom/events/mouseEventDispatcher.js +177 -0
  33. package/dist/dom/events/tuiEventBase.d.ts +25 -0
  34. package/dist/dom/events/tuiEventBase.js +39 -0
  35. package/dist/dom/events/tuiFocusEvent.d.ts +6 -0
  36. package/dist/dom/events/tuiFocusEvent.js +8 -0
  37. package/dist/dom/events/tuiKeyboardEvent.d.ts +21 -0
  38. package/dist/dom/events/tuiKeyboardEvent.js +20 -0
  39. package/dist/dom/events/tuiMouseEvent.d.ts +40 -0
  40. package/dist/dom/events/tuiMouseEvent.js +32 -0
  41. package/dist/dom/events/tuiPasteEvent.d.ts +10 -0
  42. package/dist/dom/events/tuiPasteEvent.js +13 -0
  43. package/dist/dom/overlayLayer.d.ts +92 -0
  44. package/dist/dom/overlayLayer.js +343 -0
  45. package/dist/dom/styles/index.d.ts +4 -0
  46. package/dist/dom/styles/index.js +2 -0
  47. package/dist/dom/styles/styleTokens.d.ts +114 -0
  48. package/dist/dom/styles/styleTokens.js +122 -0
  49. package/dist/dom/styles/tuiStyle.d.ts +75 -0
  50. package/dist/dom/styles/tuiStyle.js +121 -0
  51. package/dist/dom/tuiApplication.d.ts +57 -0
  52. package/dist/dom/tuiApplication.js +231 -0
  53. package/dist/dom/tuiElement.d.ts +528 -0
  54. package/dist/dom/tuiElement.js +1168 -0
  55. package/dist/dom/tuiSelector.d.ts +9 -0
  56. package/dist/dom/tuiSelector.js +82 -0
  57. package/dist/dom/validateTree.d.ts +42 -0
  58. package/dist/dom/validateTree.js +125 -0
  59. package/dist/input/convertToken.d.ts +3 -0
  60. package/dist/input/convertToken.js +114 -0
  61. package/dist/input/keyEvent.d.ts +46 -0
  62. package/dist/input/keyEvent.js +26 -0
  63. package/dist/input/keyInputParser.d.ts +72 -0
  64. package/dist/input/keyInputParser.js +249 -0
  65. package/dist/input/mouseTracking.d.ts +18 -0
  66. package/dist/input/mouseTracking.js +18 -0
  67. package/dist/input/parseInput.d.ts +13 -0
  68. package/dist/input/parseInput.js +17 -0
  69. package/dist/input/rawTerminalToken.d.ts +142 -0
  70. package/dist/input/rawTerminalToken.js +2 -0
  71. package/dist/input/serializeKey.d.ts +14 -0
  72. package/dist/input/serializeKey.js +179 -0
  73. package/dist/input/serializeMouse.d.ts +26 -0
  74. package/dist/input/serializeMouse.js +38 -0
  75. package/dist/input/tokenize.d.ts +59 -0
  76. package/dist/input/tokenize.js +681 -0
  77. package/dist/rendering/cell.d.ts +24 -0
  78. package/dist/rendering/cell.js +46 -0
  79. package/dist/rendering/damage.d.ts +35 -0
  80. package/dist/rendering/damage.js +99 -0
  81. package/dist/rendering/grid.d.ts +49 -0
  82. package/dist/rendering/grid.js +216 -0
  83. package/dist/rendering/gridSnapshot.d.ts +33 -0
  84. package/dist/rendering/gridSnapshot.js +32 -0
  85. package/dist/rendering/gridToSvg.d.ts +29 -0
  86. package/dist/rendering/gridToSvg.js +145 -0
  87. package/dist/rendering/terminalRenderer.d.ts +28 -0
  88. package/dist/rendering/terminalRenderer.js +161 -0
  89. package/dist/rendering/terminalScreen.d.ts +25 -0
  90. package/dist/rendering/terminalScreen.js +50 -0
  91. package/package.json +29 -0
@@ -0,0 +1,528 @@
1
+ import { DisplayLine } from "../common/displayLine.js";
2
+ import { BoxConstraints, Offset, Point, Rect, Size } from "../common/geometryPromitives.js";
3
+ import type { DamageList } from "../rendering/damage.js";
4
+ import type { CellPatch, ReadonlyCellData } from "../rendering/grid.js";
5
+ import { TerminalScreen } from "../rendering/terminalScreen.js";
6
+ import { type BorderStyle } from "./borderStyle.js";
7
+ import type { FocusManager } from "./events/focusManager.js";
8
+ import { TUIEventBase } from "./events/tuiEventBase.js";
9
+ import type { TUIFocusEvent } from "./events/tuiFocusEvent.js";
10
+ import { TUIKeyboardEvent } from "./events/tuiKeyboardEvent.js";
11
+ import type { TUIMouseEvent } from "./events/tuiMouseEvent.js";
12
+ import type { TUIPasteEvent } from "./events/tuiPasteEvent.js";
13
+ import type { OverlayLayer } from "./overlayLayer.js";
14
+ import type { AnyStyleToken } from "./styles/styleTokens.js";
15
+ import type { ResolvedTUIStyle, StyleColor, StyleResolutionContext, StyleState, TUIStyle } from "./styles/tuiStyle.js";
16
+ export declare class RenderContext {
17
+ readonly canvas: TerminalScreen;
18
+ readonly offset: Offset;
19
+ readonly clipRect: Rect;
20
+ constructor(canvas: TerminalScreen, offset?: Offset, clipRect?: Rect);
21
+ withOffset(extra: Offset): RenderContext;
22
+ withClip(rect: Rect): RenderContext;
23
+ setCell(x: number, y: number, cell: CellPatch): void;
24
+ /**
25
+ * Читает уже отрисованную ячейку в локальных координатах (тот же оффсет и
26
+ * клип, что у {@link setCell}). Возвращает null вне клипа или вне экрана.
27
+ * Нужен пост-обработке вроде оверлея выделения в списках: прочитать ячейку,
28
+ * решить по её текущим цветам и патчнуть через {@link setCell}.
29
+ */
30
+ getCell(x: number, y: number): ReadonlyCellData | null;
31
+ setCursorPosition(x: number, y: number): void;
32
+ /**
33
+ * Render a text string at (x, y), handling wide chars, tabs, combining marks and emoji.
34
+ * Each column within [startCol, startCol + maxWidth) is rendered.
35
+ *
36
+ * @param x Left screen column (local coordinates)
37
+ * @param y Screen row (local coordinates)
38
+ * @param text Raw text to render
39
+ * @param style Optional cell style (fg, bg, style flags) applied to every cell
40
+ * @param options tabSize (default 4) and maxWidth (default: no limit);
41
+ * displayLine — готовый DisplayLine, чтобы не сегментировать text заново
42
+ * на каждом кадре; вызывающий гарантирует, что он построен из тех же
43
+ * text/tabSize
44
+ * @returns Number of display columns written
45
+ */
46
+ drawText(x: number, y: number, text: string, style?: {
47
+ fg?: number;
48
+ bg?: number;
49
+ style?: number;
50
+ }, options?: {
51
+ tabSize?: number;
52
+ maxWidth?: number;
53
+ displayLine?: DisplayLine;
54
+ getStyle?: (offset: number) => {
55
+ fg?: number;
56
+ bg?: number;
57
+ style?: number;
58
+ } | undefined;
59
+ }): number;
60
+ /**
61
+ * Отрисовывает прямоугольную рамку box-drawing глифами — единый хелпер для
62
+ * всех бордер-виджетов (см. {@link BorderStyle}). Рисует углы, верхнюю/нижнюю
63
+ * горизонтали и боковые вертикали; строки из `separators` (offset от верха
64
+ * рамки, 1-based относительно `y`) рисуются как T-коннекторы `├───┤`.
65
+ *
66
+ * Координаты локальные (как у {@link drawText}). Клиппинг/оффсет применяются
67
+ * через {@link setCell}.
68
+ *
69
+ * @param x Левый столбец рамки
70
+ * @param y Верхняя строка рамки
71
+ * @param width Ширина рамки в столбцах (>= 2)
72
+ * @param height Высота рамки в строках (>= 2)
73
+ * @param options fg/bg, пресет `style` (по умолчанию {@link BORDER_ROUNDED} —
74
+ * канонический стиль оверлеев Vexx), `fill` (залить фон внутри
75
+ * рамки), `separators` (ряды-разделители)
76
+ */
77
+ drawBox(x: number, y: number, width: number, height: number, options?: {
78
+ fg?: number;
79
+ bg?: number;
80
+ style?: BorderStyle;
81
+ fill?: boolean;
82
+ separators?: readonly number[];
83
+ }): void;
84
+ }
85
+ export interface AddEventListenerOptions {
86
+ capture?: boolean;
87
+ }
88
+ export interface TUIElementEventMap {
89
+ keydown: TUIKeyboardEvent;
90
+ keyup: TUIKeyboardEvent;
91
+ keypress: TUIKeyboardEvent;
92
+ focus: TUIFocusEvent;
93
+ blur: TUIFocusEvent;
94
+ mousedown: TUIMouseEvent;
95
+ mouseup: TUIMouseEvent;
96
+ mousemove: TUIMouseEvent;
97
+ click: TUIMouseEvent;
98
+ dblclick: TUIMouseEvent;
99
+ mouseenter: TUIMouseEvent;
100
+ mouseleave: TUIMouseEvent;
101
+ wheel: TUIMouseEvent;
102
+ paste: TUIPasteEvent;
103
+ }
104
+ export declare class TUIElement {
105
+ private allocatedSize;
106
+ dirty: boolean;
107
+ layoutStyle: unknown;
108
+ layoutState: unknown;
109
+ id: string | undefined;
110
+ role: string | undefined;
111
+ focusable: boolean;
112
+ capturesPointer: boolean;
113
+ localPosition: Offset;
114
+ isLayoutDirty: boolean;
115
+ private isPaintDirty;
116
+ private hasPaintDirtyDescendant;
117
+ private lastPaintedRect;
118
+ private pendingDetachDamage;
119
+ protected _parent: TUIElement | null;
120
+ private isRootAnchor;
121
+ private requestRenderCallback;
122
+ focusManager: FocusManager | null;
123
+ private styleValue;
124
+ private resolvedStyleValue;
125
+ private isStyleDirty;
126
+ private subtreeStyleDirty;
127
+ private appliedFgValue;
128
+ private appliedBgValue;
129
+ private styleStatesSet;
130
+ private styleVarsValue;
131
+ private varScopeRef;
132
+ private childStyleContext;
133
+ get style(): Readonly<TUIStyle>;
134
+ set style(value: TUIStyle);
135
+ private _listeners;
136
+ /**
137
+ * Allocated visible area on screen, set by parent container via layout().
138
+ * Lazy fallback: if layout is dirty, triggers layout with loose constraints.
139
+ */
140
+ get layoutSize(): Size;
141
+ get isFocused(): boolean;
142
+ getParent(): TUIElement | null;
143
+ /**
144
+ * Абсолютная позиция элемента на экране — **производная** от цепочки
145
+ * родителей: `parent.globalPosition + localPosition`. Раньше это было поле,
146
+ * которое каждый контейнер обязан был выставлять руками параллельно с
147
+ * `localPosition` (LAYOUT.md честно писал «в корректном состоянии они
148
+ * равны») — забытая запись давала элемент, который рисуется, но не
149
+ * кликается. Теперь рассинхрон невозможен по построению.
150
+ *
151
+ * У отсоединённого элемента (parent=null) равна `localPosition` — так
152
+ * standalone-рендер в тестах может позиционировать элемент напрямую.
153
+ */
154
+ get globalPosition(): Point;
155
+ private childrenList;
156
+ getChildren(): readonly TUIElement[];
157
+ /**
158
+ * Видимость (аналог display:none): скрытый элемент ОСТАЁТСЯ в дереве —
159
+ * root и каскад стилей до него доходят, — но выпадает из hit-теста и
160
+ * Tab-обхода (базовые обходы), а контейнер не раскладывает и не рисует его.
161
+ * Это разводит «структуру» и «что сейчас видно», которые раньше смешивал
162
+ * getChildren(): контейнеры исключали скрытых детей из структуры и потом
163
+ * руками чинили пропагацию при показе (источник семейства багов #204).
164
+ */
165
+ get hidden(): boolean;
166
+ set hidden(value: boolean);
167
+ private hiddenValue;
168
+ /** Прикрепляет ребёнка в конец списка (снимая с прежнего родителя). */
169
+ protected appendChild(child: TUIElement): void;
170
+ /** Прикрепляет ребёнка на позицию index (снимая с прежнего родителя). */
171
+ protected insertChild(index: number, child: TUIElement): void;
172
+ /** Отцепляет ребёнка (no-op, если он не наш). */
173
+ protected removeChild(child: TUIElement): void;
174
+ /**
175
+ * Заменяет ребёнка, сохраняя позицию в списке — а значит z-порядок и место
176
+ * в Tab-обходе (важно слотовым контейнерам: content меняется, а overlay
177
+ * обязан остаться поверх).
178
+ */
179
+ protected replaceChild(oldChild: TUIElement, newChild: TUIElement): void;
180
+ /**
181
+ * Декларативно приводит список детей к заданному (слотовые контейнеры
182
+ * пересобирают канонический порядок одним вызовом). Лишние отцепляются,
183
+ * новые прикрепляются, порядок — как в next.
184
+ */
185
+ protected setChildren(next: readonly TUIElement[]): void;
186
+ /** Гасит фокус, если activeElement — этот элемент или его потомок. */
187
+ private releaseFocusIfInside;
188
+ /**
189
+ * Observable state for the inspector, self-described by the widget. The base
190
+ * returns `undefined` (no state to report); interactive widgets override to
191
+ * expose what a test would otherwise have to infer from rendered cells — an
192
+ * editor's cursor/selection/readonly, a panel's active tab, a quick-pick's
193
+ * items. Must be a plain JSON-serialisable snapshot, not live internals:
194
+ * it crosses the inspector wire and is a public contract (test it).
195
+ */
196
+ inspectState(): Record<string, unknown> | undefined;
197
+ /**
198
+ * Builds the path from root to this element (inclusive on both ends).
199
+ */
200
+ getAncestorPath(): TUIElement[];
201
+ /**
202
+ * Порядок Tab-обхода поддерева: фокусируемые (focusable) в глубину.
203
+ * Скрытые (hidden) поддеревья пропускаются целиком — Tab не должен уводить
204
+ * фокус в невидимый инпут (закрытый find-виджет, неактивная вкладка).
205
+ */
206
+ getDepthFirstFocusableOrder(): TUIElement[];
207
+ addEventListener<K extends keyof TUIElementEventMap>(type: K, handler: (event: TUIElementEventMap[K]) => void, options?: AddEventListenerOptions): void;
208
+ addEventListener(type: string, handler: (event: TUIEventBase) => void, options?: AddEventListenerOptions): void;
209
+ removeEventListener<K extends keyof TUIElementEventMap>(type: K, handler: (event: TUIElementEventMap[K]) => void, options?: AddEventListenerOptions): void;
210
+ removeEventListener(type: string, handler: (event: TUIEventBase) => void, options?: AddEventListenerOptions): void;
211
+ /**
212
+ * Dispatches event with capture → target → bubble phases (DOM-like).
213
+ * Returns true if preventDefault() was NOT called.
214
+ */
215
+ dispatchEvent(event: TUIEventBase): boolean;
216
+ /**
217
+ * Override in subclasses to define built-in element behavior (like opening a menu on click).
218
+ * Called after all capture/target/bubble listeners. Skipped if preventDefault() was called.
219
+ * Analogous to Web DOM default actions (e.g. <a> navigation, <input> text entry).
220
+ */
221
+ protected performDefaultAction(event: TUIEventBase): void;
222
+ /**
223
+ * Invoke listeners on an element for the given event.
224
+ * captureFilter: true = only capture, false = only bubble, null = both (target phase)
225
+ */
226
+ private _invokeListeners;
227
+ get resolvedStyle(): ResolvedTUIStyle;
228
+ /**
229
+ * Forces a style re-resolution of this element and its whole subtree (and
230
+ * schedules a render). Public so a container can refresh a subtree it just
231
+ * re-attached — e.g. a panel that was excluded from `getChildren()` while
232
+ * hidden and thus missed style propagation.
233
+ */
234
+ markStyleDirty(): void;
235
+ /**
236
+ * isStyleDirty вглубь по поддереву — БЕЗ подъёма вверх и без markDirty.
237
+ * Подъём достаточен один раз от вершины каскада (внутренним узлам
238
+ * subtreeStyleDirty не нужен — они и так isStyleDirty); прежняя рекурсия
239
+ * через markStyleDirty гоняла markDirty до корня из каждого потомка,
240
+ * O(N×глубина) на каскад.
241
+ */
242
+ private markStyleSubtree;
243
+ private markSubtreeStyleDirtyUp;
244
+ performStyleResolution(context: StyleResolutionContext): void;
245
+ /**
246
+ * Спуск стилевого прохода в детей. Переопределяется виртуализирующим
247
+ * контейнером, чтобы резолвить только видимое окно строк (зеркально
248
+ * hitTestChildren/getDepthFirstFocusableOrder); офскрин-строки остаются
249
+ * style-dirty и дорезолвливаются, когда въезжают в окно — см.
250
+ * {@link markSubtreeStyleDirty}.
251
+ */
252
+ protected performChildrenStyleResolution(context: StyleResolutionContext): void;
253
+ /**
254
+ * «У потомков могут быть неразрезолвленные стили»: subtreeStyleDirty здесь
255
+ * и вверх до корня, БЕЗ пометки самих детей и без markDirty. Для
256
+ * виртуализирующего контейнера, чей performLayout сместил окно: следующий
257
+ * стилевой проход обязан зайти внутрь и дорезолвить въехавшие строки
258
+ * (чистые отсеются ранним выходом performStyleResolution).
259
+ */
260
+ protected markSubtreeStyleDirty(): void;
261
+ private isStyleSelectorActive;
262
+ private buildChildStyleContext;
263
+ private describeForStyleError;
264
+ /**
265
+ * Кладёт таблицу токен→число, каскадирующую в поддерево ПОВЕРХ таблиц
266
+ * предков и дефолтов tuidom (STYLE_TOKEN_DEFAULTS). Обычное место — корень:
267
+ * хост транслирует сюда палитру темы одним вызовом (hot-swap = повторный
268
+ * вызов). Таблица заменяется целиком, null — снимает. Значения — только
269
+ * конкретные числа (packed RGB | DEFAULT_COLOR); сентинелы INHERITED_*
270
+ * нелегальны.
271
+ */
272
+ setStyleVars(vars: Readonly<Record<string, number>> | null): void;
273
+ /**
274
+ * Читает токен из ближайшего резолвленного var-scope — для painter-виджетов,
275
+ * рисующих несколько цветов в custom render. Валидно после резолва стилей
276
+ * (render всегда после него в кадре); до первого резолва видит дефолты
277
+ * tuidom. Незнакомый токен — throw; передан fallback (аналог второго
278
+ * аргумента CSS var()) — возвращается он. Fallback — для токенов, чьё
279
+ * отсутствие ЛЕГАЛЬНО и означает «взять из каскада» (editorGutter.background
280
+ * → фон редактора), а не страховка от опечаток.
281
+ */
282
+ styleVar(name: AnyStyleToken, fallback?: number): number;
283
+ /**
284
+ * Резолвит StyleColor (число | сентинел INHERITED_* | имя токена) в
285
+ * конкретный цвет в контексте ЭТОГО элемента (его resolvedStyle и
286
+ * var-scope). Для painter-виджетов, принимающих цвета данными
287
+ * (посимвольные стили TextLabel, iconColor строк дерева): данные могут
288
+ * ссылаться на токены и переживать смену темы без пере-пуша.
289
+ */
290
+ resolveColor(color: StyleColor): number;
291
+ /**
292
+ * Ставит/снимает состояние стиля. hover и focus ведёт ядро (диспатчер
293
+ * мыши и менеджер фокуса); произвольные строковые состояния ("selected",
294
+ * "checked", …) виджеты ставят сами. Смена состояния перерезолвит стиль
295
+ * элемента и поддерева: дети наследуют РЕЗУЛЬТАТ родителя с учётом его
296
+ * состояний, а `in:`-селекторы потомков видят состояния предков.
297
+ */
298
+ setStyleState(state: StyleState, active: boolean): void;
299
+ /**
300
+ * Как {@link setStyleState}, но для вызова из performLayout уже идущего
301
+ * кадра (виртуализирующие контейнеры синхронизируют selected/hover строк в
302
+ * layout). Стилевой проход идёт сразу после layout и потребит флаги, а
303
+ * markDirty здесь лишь оставлял бы корень layout-грязным ПОСЛЕ кадра — и
304
+ * следующее событие ввода рендерило бы пустой кадр (dirty-гейт
305
+ * TuiApplication).
306
+ */
307
+ setStyleStateDuringLayout(state: StyleState, active: boolean): void;
308
+ /** Мутация набора состояний; true — значение реально изменилось. */
309
+ private applyStyleState;
310
+ hasStyleState(state: StyleState): boolean;
311
+ /**
312
+ * Состояние активно на самом элементе ИЛИ на любом предке — рантайм-двойник
313
+ * `in:`-селектора для кода, который решает не цветом, а геометрией
314
+ * (инлайн-кнопка строки списка раскрывается, только когда строка активна).
315
+ * Селектор в `when` даёт ту же семантику декларативно и дешевле (готовый
316
+ * `ancestorStates` из контекста резолва); этот метод — для тех, кому
317
+ * состояние нужно ДО стилевого прохода, прямо в performLayout.
318
+ */
319
+ hasStyleStateWithin(state: StyleState): boolean;
320
+ /** Активные состояния (порядок вставки) — инспектор/тесты. */
321
+ get activeStyleStates(): readonly string[];
322
+ focus(): void;
323
+ blur(): void;
324
+ /**
325
+ * Marks this element and ancestors as dirty.
326
+ * Call this when layout-affecting properties change.
327
+ *
328
+ * When propagation reaches the root (no parent), fires the
329
+ * requestRenderCallback so TuiApplication can schedule a deferred render.
330
+ * Batching is handled by TuiApplication.scheduleRender().
331
+ */
332
+ markDirty(): void;
333
+ /**
334
+ * Пост-layout damage-обход (pre-order): собирает в sink повреждённые
335
+ * экранные области — rect'ы paint-dirty элементов и old∪new переехавших /
336
+ * изменивших размер / скрывшихся — и актуализирует lastPaintedRect.
337
+ * Спуск только по путям hasPaintDirtyDescendant или под переехавшим
338
+ * предком; устоявшееся поддерево стоит одну проверку флагов.
339
+ *
340
+ * Не заходит в скрытые и в не разложенные этим кадром поддеревья
341
+ * (isLayoutDirty после полного layout корня — виртуализация: контейнер их
342
+ * не раскладывал ⇒ не рисует ⇒ на экране их нет; чтение layoutSize там
343
+ * запустило бы lazy-layout с мусорными constraints).
344
+ */
345
+ collectDamage(sink: DamageList, parentOrigin: Point): void;
346
+ /** Обход детей damage-сбора — seam для контейнеров с нестандартной структурой. */
347
+ protected collectChildrenDamage(sink: DamageList, origin: Point): void;
348
+ /**
349
+ * true — поддерево рисуется как одно целое: любой paint-dirty потомок
350
+ * повреждает весь rect элемента, damage-обход внутрь не заходит. Для
351
+ * виртуализирующих контейнеров (ListViewElement: тысячи строк-детей с
352
+ * протухшими офскрин-позициями не итерируются) и контейнеров, рисующих
353
+ * собственный хром по состоянию ребёнка вне его rect'а (ScrollBarDecorator:
354
+ * бегунок в колонке за пределами ребёнка).
355
+ */
356
+ protected get paintsSubtreeAtomically(): boolean;
357
+ /** Рекурсивно забывает lastPaintedRect поддерева (отцепление/скрытие). */
358
+ private clearPaintedRects;
359
+ /**
360
+ * Только для TuiApplication: забрать rect'ы поддеревьев, отцеплённых от
361
+ * этого корня с прошлого кадра (закрытие оверлея, смена вкладки).
362
+ */
363
+ takePendingDetachDamage(): Rect[];
364
+ /**
365
+ * Внутренний сеттер обратной ссылки — вызывается ТОЛЬКО из
366
+ * appendChild/insertChild/removeChild/replaceChild/setChildren, поэтому
367
+ * список детей и parent меняются строго вместе. Отцепление (parent=null)
368
+ * гасит фокус, если он был внутри отцепляемого поддерева — иначе
369
+ * клавиатура продолжала бы уходить в элемент, которого больше нет на
370
+ * экране. После смены зовёт {@link onDidChangeParent} (хук для виджетов,
371
+ * вешающих слушатели на родителя, — MenuBarElement).
372
+ */
373
+ private setParent;
374
+ /**
375
+ * Хук смены родителя: вызывается после каждого перецепления. Базовая
376
+ * реализация пуста; переопределяется вместо запрещённого override
377
+ * setParent. Для доступа к КОРНЮ используйте {@link onDidConnect} — на
378
+ * момент этого хука поддерево может быть ещё не укоренено.
379
+ */
380
+ protected onDidChangeParent(_oldParent: TUIElement | null, _newParent: TUIElement | null): void;
381
+ /**
382
+ * Поддерево подключилось к укоренённому дереву: внутри хука
383
+ * `getRoot() === root`. Перенос между родителями (даже внутри одного
384
+ * дерева) — это всегда пара disconnect → connect: будьте идемпотентны.
385
+ * Скрытые (hidden) узлы получают хук наравне с видимыми — подключение
386
+ * не зависит от видимости. НЕ полагайтесь на `root.focusManager` — он
387
+ * появляется позже (TuiApplication.run). Хук не должен бросать; мутации
388
+ * разрешены только в собственном поддереве. Если хук удаляет узел из ещё
389
+ * не обойдённой части дерева, тот получит disconnect без предшествовавшего
390
+ * connect-уведомления (подключение — факт топологии, уведомления
391
+ * догоняют).
392
+ */
393
+ protected onDidConnect(_root: TUIElement): void;
394
+ /**
395
+ * Поддерево отключилось от укоренённого дерева: внутри хука
396
+ * `getRoot() === null`. Прежний root хук хранит сам, если нужен для
397
+ * отписки (DOM-прецедент: disconnectedCallback тоже без аргументов).
398
+ */
399
+ protected onDidDisconnect(): void;
400
+ /** Подключён ли элемент к укоренённому дереву. */
401
+ get isConnected(): boolean;
402
+ /**
403
+ * Pre-order обход поддерева (родитель раньше детей — DOM tree order).
404
+ * Снимок детей берётся ДО вызова хука узла, перед рекурсией проверяется
405
+ * актуальность связи: ребёнок, добавленный хуком, получит свой connect
406
+ * через собственный setParent; удалённый — уже получил disconnect и
407
+ * пропускается.
408
+ */
409
+ private fireDidConnect;
410
+ private fireDidDisconnect;
411
+ /**
412
+ * Корень дерева — **производный** от цепочки родителей: прогулка вверх до
413
+ * вершины; если вершина — якорь (setAsRoot), это и есть корень, иначе
414
+ * поддерево отсоединено и корня нет. Раньше root был кэшем, который
415
+ * пропагировался вниз через getChildren() при setParent — контейнеры,
416
+ * прячущие детей из getChildren() (неактивные вкладки), оставляли их с
417
+ * протухшим null-root навсегда (семейство багов #204: focus()/open()
418
+ * молча не работали). Живая цепочка родителей протухнуть не может.
419
+ */
420
+ getRoot(): TUIElement | null;
421
+ /**
422
+ * Ближайший overlay-слой вверх по дереву (попапы, контекстные меню,
423
+ * докнутые виджеты). Элементы-хосты слоёв (BodyElement, OverlayHostElement)
424
+ * переопределяют и возвращают свой слой.
425
+ */
426
+ getOverlayLayer(): OverlayLayer | null;
427
+ /**
428
+ * Sets this element as the root (used for testing and by BodyElement).
429
+ * Помечает элемент якорем — getRoot() признаёт корнем только вершину
430
+ * цепочки с этой меткой. Уже собранное поддерево получает
431
+ * {@link onDidConnect} (поздний setAsRoot легален); повторный вызов —
432
+ * no-op; якорь на узле с родителем запрещён (getRoot() не видит якорь в
433
+ * середине цепочки — сработал бы только после detach, миной).
434
+ */
435
+ setAsRoot(): void;
436
+ /**
437
+ * Sets a callback to be invoked when markDirty() reaches the root element.
438
+ * Used by TuiApplication to schedule async re-renders.
439
+ */
440
+ setRequestRenderCallback(callback: (() => void) | null): void;
441
+ getMinIntrinsicWidth(_height: number): number;
442
+ getMaxIntrinsicWidth(_height: number): number;
443
+ getMinIntrinsicHeight(_width: number): number;
444
+ getMaxIntrinsicHeight(_width: number): number;
445
+ /**
446
+ * Хелпер контейнера: позиционирует ребёнка (localPosition) и прогоняет его
447
+ * layout — одна строка вместо ритуала из двух-трёх записей. globalPosition
448
+ * не трогает: он производный.
449
+ */
450
+ protected layoutChild(child: TUIElement, x: number, y: number, constraints: BoxConstraints): Size;
451
+ /**
452
+ * Единственный публичный вход в layout — НЕ переопределять (переопределяется
453
+ * {@link performLayout}). Запоминает входные constraints (их читает
454
+ * геометрическая проверка validateTree) и следит за контрактом: размер после
455
+ * layout обязан удовлетворять constraints. «Контент не влез» — вопрос
456
+ * отрисовки (клип), а не геометрии; занимать меньше выделенного можно только
457
+ * под loose-constraints родителя. См. docs/LAYOUT.md, «Контракт performLayout».
458
+ */
459
+ layout(constraints: BoxConstraints): Size;
460
+ /** Constraints последнего layout() — null, если layout ещё не вызывался. */
461
+ get lastLayoutConstraints(): BoxConstraints | null;
462
+ private lastConstraintsValue;
463
+ /**
464
+ * Переопределяемая реализация layout: применяет constraints к выделенной
465
+ * области. Вызывается ТОЛЬКО из {@link layout} — снаружи зовите layout().
466
+ */
467
+ protected performLayout(constraints: BoxConstraints): Size;
468
+ /**
469
+ * Дефолт: залить собственный фон (если он задан собственным стилем) и
470
+ * отрисовать детей. Контейнер, которому нужно собственное полотно (рамка,
471
+ * заголовок), рисует его и зовёт {@link renderChildren}; полностью
472
+ * кастомный рендер (виртуализация, скролл-сдвиг) переопределяет метод
473
+ * целиком — и тогда сам зовёт {@link paintOwnBackground} первой строкой,
474
+ * если хочет фон от каскада.
475
+ */
476
+ render(context: RenderContext): void;
477
+ /**
478
+ * Заливает прямоугольник элемента resolvedStyle.bg, если bg задан
479
+ * СОБСТВЕННЫМ стилем — базой или сработавшим when-вариантом. Элементы без
480
+ * собственного bg прозрачны (как в CSS). Сентинелы INHERITED_* тоже
481
+ * считаются «задан»: это намеренная перезаливка цветом родителя (INHERITED_BG)
482
+ * или инверсия (INHERITED_FG). Выход за границы невозможен — контекст
483
+ * элемента уже клипован родителем.
484
+ */
485
+ protected paintOwnBackground(context: RenderContext): void;
486
+ /** true, если фон задан собственным стилем (см. {@link paintOwnBackground}). */
487
+ get hasOwnBackground(): boolean;
488
+ /**
489
+ * Сырые цвета после when-merge, до резолва токенов/сентинелов: что элемент
490
+ * «попросил сам» (undefined = наследует). Для инспектора и тестов.
491
+ */
492
+ get appliedStyle(): {
493
+ fg?: StyleColor;
494
+ bg?: StyleColor;
495
+ };
496
+ /**
497
+ * Каноничная отрисовка детей: каждый видимый ребёнок получает контекст со
498
+ * сдвигом на свою localPosition и клипом по своим границам (дети не рисуют
499
+ * за пределами выделенной области). Скрытые (hidden) пропускаются. Это тот
500
+ * самый цикл, который раньше был скопирован в десяток контейнеров.
501
+ */
502
+ protected renderChildren(context: RenderContext): void;
503
+ /**
504
+ * Финальный шаблон хит-теста — НЕ переопределять (кастомизация — через
505
+ * {@link hitTestChildren}/{@link hitTestSelf}): скрытое не кликается,
506
+ * вне собственных границ хита нет (инвариант вложенности Н2 гарантирует,
507
+ * что и у детей его там нет), дети опрашиваются в обратном порядке
508
+ * отрисовки, затем — сам элемент.
509
+ */
510
+ elementFromPoint(point: Point): TUIElement | null;
511
+ /**
512
+ * Хит-тест детей — зеркало {@link renderChildren}: тот же список, обратный
513
+ * порядок (верхний — первый), скрытых пропускает сам elementFromPoint
514
+ * ребёнка. Переопределение — для контейнеров с осознанно другой политикой:
515
+ * презентационные строки ListViewElement (`null` — мышь у контейнера),
516
+ * modal-хвост OverlayLayer.
517
+ */
518
+ protected hitTestChildren(point: Point): TUIElement | null;
519
+ /**
520
+ * Берёт ли элемент точку на себя, когда никто из детей её не взял.
521
+ * Дефолт — да (непрозрачный бокс). `false` — прозрачный для кликов
522
+ * контейнер: точка проваливается к элементам ПОД ним (OverlayLayer без
523
+ * попапа в этой точке).
524
+ */
525
+ protected hitTestSelf(_point: Point): boolean;
526
+ querySelector(selector: string): TUIElement | null;
527
+ querySelectorAll(selector: string): TUIElement[];
528
+ }