@jrichman/ink 6.4.11 → 6.4.12

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 (111) hide show
  1. package/build/components/App.d.ts +6 -0
  2. package/build/components/App.js +5 -0
  3. package/build/components/App.js.map +1 -1
  4. package/build/components/AppContext.d.ts +36 -1
  5. package/build/components/AppContext.js +7 -2
  6. package/build/components/AppContext.js.map +1 -1
  7. package/build/components/Box.d.ts +26 -2
  8. package/build/components/Box.js +3 -2
  9. package/build/components/Box.js.map +1 -1
  10. package/build/components/StaticRender.d.ts +8 -0
  11. package/build/components/StaticRender.js +11 -0
  12. package/build/components/StaticRender.js.map +1 -0
  13. package/build/debug-log.d.ts +2 -0
  14. package/build/debug-log.js +44 -0
  15. package/build/debug-log.js.map +1 -0
  16. package/build/dom.d.ts +37 -3
  17. package/build/dom.js +19 -3
  18. package/build/dom.js.map +1 -1
  19. package/build/index.d.ts +4 -1
  20. package/build/index.js +3 -1
  21. package/build/index.js.map +1 -1
  22. package/build/ink.d.ts +29 -2
  23. package/build/ink.js +215 -102
  24. package/build/ink.js.map +1 -1
  25. package/build/measure-element.d.ts +20 -1
  26. package/build/measure-element.js +201 -51
  27. package/build/measure-element.js.map +1 -1
  28. package/build/output.d.ts +195 -10
  29. package/build/output.js +494 -160
  30. package/build/output.js.map +1 -1
  31. package/build/reconciler.js +19 -3
  32. package/build/reconciler.js.map +1 -1
  33. package/build/render-background.js +1 -1
  34. package/build/render-background.js.map +1 -1
  35. package/build/render-cached.d.ts +17 -0
  36. package/build/render-cached.js +62 -0
  37. package/build/render-cached.js.map +1 -0
  38. package/build/render-container.d.ts +24 -0
  39. package/build/render-container.js +169 -0
  40. package/build/render-container.js.map +1 -0
  41. package/build/render-node-to-output.d.ts +15 -7
  42. package/build/render-node-to-output.js +123 -485
  43. package/build/render-node-to-output.js.map +1 -1
  44. package/build/render-screen-reader.d.ts +5 -0
  45. package/build/render-screen-reader.js +54 -0
  46. package/build/render-screen-reader.js.map +1 -0
  47. package/build/render-scrollbar.d.ts +22 -0
  48. package/build/render-scrollbar.js +77 -0
  49. package/build/render-scrollbar.js.map +1 -0
  50. package/build/render-sticky.d.ts +56 -0
  51. package/build/render-sticky.js +314 -0
  52. package/build/render-sticky.js.map +1 -0
  53. package/build/render-text-node.d.ts +24 -0
  54. package/build/render-text-node.js +133 -0
  55. package/build/render-text-node.js.map +1 -0
  56. package/build/render.d.ts +39 -0
  57. package/build/render.js +5 -0
  58. package/build/render.js.map +1 -1
  59. package/build/renderer.d.ts +10 -2
  60. package/build/renderer.js +103 -7
  61. package/build/renderer.js.map +1 -1
  62. package/build/replay.d.ts +60 -0
  63. package/build/replay.js +138 -0
  64. package/build/replay.js.map +1 -0
  65. package/build/scroll.js +20 -1
  66. package/build/scroll.js.map +1 -1
  67. package/build/selection.d.ts +9 -0
  68. package/build/selection.js +47 -0
  69. package/build/selection.js.map +1 -1
  70. package/build/serialization.d.ts +28 -0
  71. package/build/serialization.js +267 -0
  72. package/build/serialization.js.map +1 -0
  73. package/build/styles.d.ts +18 -0
  74. package/build/styles.js.map +1 -1
  75. package/build/terminal-buffer.d.ts +53 -0
  76. package/build/terminal-buffer.js +441 -0
  77. package/build/terminal-buffer.js.map +1 -0
  78. package/build/worker/animation-controller.d.ts +72 -0
  79. package/build/worker/animation-controller.js +128 -0
  80. package/build/worker/animation-controller.js.map +1 -0
  81. package/build/worker/ansi-utils.d.ts +16 -0
  82. package/build/worker/ansi-utils.js +40 -0
  83. package/build/worker/ansi-utils.js.map +1 -0
  84. package/build/worker/canvas.d.ts +47 -0
  85. package/build/worker/canvas.js +94 -0
  86. package/build/worker/canvas.js.map +1 -0
  87. package/build/worker/compositor.d.ts +33 -0
  88. package/build/worker/compositor.js +314 -0
  89. package/build/worker/compositor.js.map +1 -0
  90. package/build/worker/platform.d.ts +15 -0
  91. package/build/worker/platform.js +19 -0
  92. package/build/worker/platform.js.map +1 -0
  93. package/build/worker/render-worker.d.ts +112 -0
  94. package/build/worker/render-worker.js +936 -0
  95. package/build/worker/render-worker.js.map +1 -0
  96. package/build/worker/scene-manager.d.ts +26 -0
  97. package/build/worker/scene-manager.js +99 -0
  98. package/build/worker/scene-manager.js.map +1 -0
  99. package/build/worker/scroll-optimizer.d.ts +32 -0
  100. package/build/worker/scroll-optimizer.js +110 -0
  101. package/build/worker/scroll-optimizer.js.map +1 -0
  102. package/build/worker/terminal-writer.d.ts +116 -0
  103. package/build/worker/terminal-writer.js +722 -0
  104. package/build/worker/terminal-writer.js.map +1 -0
  105. package/build/worker/worker-entry.d.ts +6 -0
  106. package/build/worker/worker-entry.js +130 -0
  107. package/build/worker/worker-entry.js.map +1 -0
  108. package/build/wrap-text.d.ts +6 -0
  109. package/build/wrap-text.js +120 -0
  110. package/build/wrap-text.js.map +1 -0
  111. package/package.json +3 -1
@@ -1,91 +1,88 @@
1
1
  import Yoga from 'yoga-layout';
2
- import { wrapOrTruncateStyledChars } from './text-wrap.js';
3
- import getMaxWidth from './get-max-width.js';
4
- import squashTextNodes from './squash-text-nodes.js';
5
- import renderBorder from './render-border.js';
6
- import renderBackground from './render-background.js';
7
- import colorize from './colorize.js';
8
- import { getVerticalScrollbarBoundingBox, getHorizontalScrollbarBoundingBox, } from './measure-element.js';
9
- import { measureStyledChars, splitStyledCharsByNewline, toStyledCharacters, } from './measure-text.js';
10
- // If parent container is `<Box>`, text nodes will be treated as separate nodes in
11
- // the tree and will have their own coordinates in the layout.
12
- // To ensure text nodes are aligned correctly, take X and Y of the first text node
13
- // and use it as offset for the rest of the nodes
14
- // Only first node is taken into account, because other text nodes can't have margin or padding,
15
- // so their coordinates will be relative to the first node anyway
16
- const applyPaddingToStyledChars = (node, lines) => {
17
- const yogaNode = node.childNodes[0]?.yogaNode;
18
- if (yogaNode) {
19
- const offsetX = yogaNode.getComputedLeft();
20
- const offsetY = yogaNode.getComputedTop();
21
- const space = {
22
- type: 'char',
23
- value: ' ',
24
- fullWidth: false,
25
- styles: [],
2
+ import { setCachedRender, } from './dom.js';
3
+ import Output, { isRectIntersectingClip, extractSelectableText, } from './output.js';
4
+ import { handleTextNode } from './render-text-node.js';
5
+ import { renderStickyNode, getStickyDescendants } from './render-sticky.js';
6
+ import { handleContainerNode } from './render-container.js';
7
+ import { handleCachedRenderNode } from './render-cached.js';
8
+ import { getRelativeLeft, getRelativeTop } from './measure-element.js';
9
+ export const renderToStatic = (node, options = {}) => {
10
+ if (options.calculateLayout && node.yogaNode) {
11
+ node.yogaNode.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
12
+ }
13
+ const width = node.yogaNode?.getComputedWidth() ?? 0;
14
+ const height = node.yogaNode?.getComputedHeight() ?? 0;
15
+ const stickyNodes = getStickyDescendants(node);
16
+ const cachedStickyHeaders = [];
17
+ for (const { node: stickyNode } of stickyNodes) {
18
+ const { naturalLines, stuckLines, naturalHeight, maxHeaderHeight } = renderStickyNode(stickyNode, {
19
+ skipStaticElements: options.skipStaticElements ?? false,
20
+ selectionMap: options.selectionMap,
21
+ selectionStyle: options.selectionStyle,
22
+ });
23
+ const parent = stickyNode.parentNode;
24
+ const parentYogaNode = parent?.yogaNode;
25
+ const currentBorderTop = node.yogaNode?.getComputedBorder(Yoga.EDGE_TOP) ?? 0;
26
+ const naturalRow = getRelativeTop(stickyNode, node) ?? 0 - currentBorderTop;
27
+ const stickyType = stickyNode.internalSticky === 'bottom' ? 'bottom' : 'top';
28
+ const headerObj = {
29
+ nodeId: stickyNode.internalId,
30
+ lines: naturalLines,
31
+ stuckLines,
32
+ styledOutput: stuckLines ?? naturalLines,
33
+ x: getRelativeLeft(stickyNode, node) ??
34
+ 0 - (node.yogaNode?.getComputedBorder(Yoga.EDGE_LEFT) ?? 0),
35
+ y: getRelativeTop(stickyNode, node) ?? 0 - currentBorderTop,
36
+ naturalRow,
37
+ startRow: naturalRow,
38
+ endRow: naturalRow + naturalHeight,
39
+ scrollContainerId: -1,
40
+ isStuckOnly: true,
41
+ relativeX: getRelativeLeft(stickyNode, node) ??
42
+ 0 - (node.yogaNode?.getComputedBorder(Yoga.EDGE_LEFT) ?? 0),
43
+ relativeY: getRelativeTop(stickyNode, node) ?? 0 - currentBorderTop,
44
+ height: maxHeaderHeight,
45
+ type: stickyType,
46
+ parentRelativeTop: parent
47
+ ? (getRelativeTop(parent, node) ?? 0 - currentBorderTop)
48
+ : 0,
49
+ parentHeight: parentYogaNode
50
+ ? parentYogaNode.getComputedHeight()
51
+ : Number.MAX_SAFE_INTEGER,
52
+ parentBorderTop: parentYogaNode
53
+ ? parentYogaNode.getComputedBorder(Yoga.EDGE_TOP)
54
+ : 0,
55
+ parentBorderBottom: parentYogaNode
56
+ ? parentYogaNode.getComputedBorder(Yoga.EDGE_BOTTOM)
57
+ : 0,
58
+ node: stickyNode,
26
59
  };
27
- const paddingLeft = Array.from({ length: offsetX }).map(() => space);
28
- lines = lines.map(line => [...paddingLeft, ...line]);
29
- const paddingTop = Array.from({ length: offsetY }).map(() => []);
30
- lines.unshift(...paddingTop);
31
- }
32
- return lines;
33
- };
34
- export const renderNodeToScreenReaderOutput = (node, options = {}) => {
35
- if (options.skipStaticElements && node.internal_static) {
36
- return '';
37
- }
38
- if (node.internalStickyAlternate) {
39
- return '';
40
- }
41
- if (node.yogaNode?.getDisplay() === Yoga.DISPLAY_NONE) {
42
- return '';
43
- }
44
- let output = '';
45
- if (node.nodeName === 'ink-text') {
46
- output = squashTextNodes(node);
47
- }
48
- else if (node.nodeName === 'ink-box' || node.nodeName === 'ink-root') {
49
- const separator = node.style.flexDirection === 'row' ||
50
- node.style.flexDirection === 'row-reverse'
51
- ? ' '
52
- : '\n';
53
- const childNodes = node.style.flexDirection === 'row-reverse' ||
54
- node.style.flexDirection === 'column-reverse'
55
- ? [...node.childNodes].reverse()
56
- : [...node.childNodes];
57
- output = childNodes
58
- .map(childNode => {
59
- const screenReaderOutput = renderNodeToScreenReaderOutput(childNode, {
60
- parentRole: node.internal_accessibility?.role,
61
- skipStaticElements: options.skipStaticElements,
62
- });
63
- return screenReaderOutput;
64
- })
65
- .filter(Boolean)
66
- .join(separator);
67
- }
68
- if (node.internal_accessibility) {
69
- const { role, state } = node.internal_accessibility;
70
- if (state) {
71
- const stateKeys = Object.keys(state);
72
- const stateDescription = stateKeys.filter(key => state[key]).join(', ');
73
- if (stateDescription) {
74
- output = `(${stateDescription}) ${output}`;
75
- }
76
- }
77
- if (role && role !== options.parentRole) {
78
- output = `${role}: ${output}`;
79
- }
80
- }
81
- return output;
60
+ cachedStickyHeaders.push(headerObj);
61
+ }
62
+ const staticOutput = new Output({
63
+ width,
64
+ height,
65
+ id: node.internalId,
66
+ });
67
+ for (const childNode of node.childNodes) {
68
+ renderNodeToOutput(childNode, staticOutput, {
69
+ offsetX: 0,
70
+ offsetY: 0,
71
+ transformers: undefined,
72
+ skipStaticElements: options.skipStaticElements ?? false,
73
+ isStickyRender: options.isStickyRender,
74
+ selectionMap: options.selectionMap,
75
+ selectionStyle: options.selectionStyle,
76
+ });
77
+ }
78
+ const rootRegion = staticOutput.get();
79
+ rootRegion.cachedStickyHeaders = cachedStickyHeaders;
80
+ rootRegion.selectableText = extractSelectableText(rootRegion.selectableSpans);
81
+ setCachedRender(node, rootRegion);
82
82
  };
83
83
  // After nodes are laid out, render each to output object, which later gets rendered to terminal
84
- const renderNodeToOutput = (node, output, options) => {
85
- if (options.nodeToSkip === node) {
86
- return;
87
- }
88
- const { offsetX = 0, offsetY = 0, transformers = [], skipStaticElements, isStickyRender = false, selectionMap, selectionStyle, } = options;
84
+ function renderNodeToOutput(node, output, options) {
85
+ const { offsetX = 0, offsetY = 0, absoluteOffsetX = 0, absoluteOffsetY = 0, transformers = [], skipStaticElements, isStickyRender = false, skipStickyHeaders = false, selectionMap, selectionStyle, } = options;
89
86
  if (skipStaticElements && node.internal_static) {
90
87
  return;
91
88
  }
@@ -100,22 +97,23 @@ const renderNodeToOutput = (node, output, options) => {
100
97
  // Left and top positions in Yoga are relative to their parent node
101
98
  const x = offsetX + yogaNode.getComputedLeft();
102
99
  const y = offsetY + yogaNode.getComputedTop();
100
+ // Absolute screen coordinates (for clipping/visibility check)
101
+ const absX = absoluteOffsetX + yogaNode.getComputedLeft();
102
+ const absY = absoluteOffsetY + yogaNode.getComputedTop();
103
103
  const width = yogaNode.getComputedWidth();
104
104
  const height = yogaNode.getComputedHeight();
105
105
  const clip = output.getCurrentClip();
106
106
  if (clip) {
107
- const nodeLeft = x;
108
- const nodeRight = x + width;
109
- const nodeTop = y;
110
- const nodeBottom = y + height;
111
- const clipLeft = clip.x1 ?? -Infinity;
112
- const clipRight = clip.x2 ?? Infinity;
113
- const clipTop = clip.y1 ?? -Infinity;
114
- const clipBottom = clip.y2 ?? Infinity;
115
- const isVisible = nodeRight > clipLeft &&
116
- nodeLeft < clipRight &&
117
- nodeBottom > clipTop &&
118
- nodeTop < clipBottom;
107
+ const absoluteNodeLeft = absX;
108
+ const absoluteNodeRight = absoluteNodeLeft + width;
109
+ const absoluteNodeTop = absY;
110
+ const absoluteNodeBottom = absoluteNodeTop + height;
111
+ const isVisible = isRectIntersectingClip({
112
+ x1: absoluteNodeLeft,
113
+ y1: absoluteNodeTop,
114
+ x2: absoluteNodeRight,
115
+ y2: absoluteNodeBottom,
116
+ }, clip);
119
117
  if (!isVisible) {
120
118
  return;
121
119
  }
@@ -126,401 +124,41 @@ const renderNodeToOutput = (node, output, options) => {
126
124
  if (typeof node.internal_transform === 'function') {
127
125
  newTransformers = [node.internal_transform, ...transformers];
128
126
  }
129
- if (node.nodeName === 'ink-text') {
130
- const text = squashTextNodes(node);
131
- let styledChars = toStyledCharacters(text);
132
- let selectionState;
133
- const selectionRange = selectionMap?.get(node);
134
- if (selectionRange) {
135
- selectionState = {
136
- range: selectionRange,
137
- currentOffset: 0,
138
- };
139
- }
140
- if (selectionState) {
141
- styledChars = applySelectionToStyledChars(styledChars, selectionState, selectionStyle);
142
- }
143
- if (styledChars.length > 0 || node.internal_terminalCursorFocus) {
144
- let lines = [];
145
- let cursorLineIndex = 0;
146
- let relativeCursorPosition = node.internal_terminalCursorPosition ?? 0;
147
- if (styledChars.length > 0) {
148
- const { width: currentWidth } = measureStyledChars(styledChars);
149
- const maxWidth = getMaxWidth(yogaNode);
150
- lines =
151
- currentWidth > maxWidth
152
- ? wrapOrTruncateStyledChars(styledChars, maxWidth, node.style.textWrap ?? 'wrap')
153
- : splitStyledCharsByNewline(styledChars);
154
- lines = applyPaddingToStyledChars(node, lines);
155
- // Calculate cursor line index for terminal cursor positioning
156
- cursorLineIndex = lines.length - 1;
157
- if (node.internal_terminalCursorFocus &&
158
- node.internal_terminalCursorPosition !== undefined) {
159
- ({ cursorLineIndex, relativeCursorPosition } =
160
- calculateWrappedCursorPosition(lines, styledChars, node.internal_terminalCursorPosition));
161
- }
162
- }
163
- else {
164
- // Empty text with cursor focus - use single empty line for IME support
165
- lines = [[]];
166
- }
167
- for (const [index, line] of lines.entries()) {
168
- output.write(x, y + index, line, {
169
- transformers: newTransformers,
170
- lineIndex: index,
171
- isTerminalCursorFocused: node.internal_terminalCursorFocus && index === cursorLineIndex,
172
- terminalCursorPosition: relativeCursorPosition,
173
- });
174
- }
175
- }
127
+ if (node.nodeName === 'ink-static-render' && !node.cachedRender) {
176
128
  return;
177
129
  }
178
- let clipped = false;
179
- let childrenOffsetY = y;
180
- let childrenOffsetX = x;
181
- let verticallyScrollable = false;
182
- let horizontallyScrollable = false;
183
- let activeStickyNode;
184
- let nextStickyNode;
185
- if (node.nodeName === 'ink-box') {
186
- renderBackground(x, y, node, output);
187
- renderBorder(x, y, node, output);
188
- const overflow = node.style.overflow ?? 'visible';
189
- const overflowX = node.style.overflowX ?? overflow;
190
- const overflowY = node.style.overflowY ?? overflow;
191
- verticallyScrollable = overflowY === 'scroll';
192
- horizontallyScrollable = overflowX === 'scroll';
193
- if (verticallyScrollable) {
194
- childrenOffsetY -= node.internal_scrollState?.scrollTop ?? 0;
195
- const stickyNodes = getStickyDescendants(node);
196
- if (stickyNodes.length > 0) {
197
- const scrollTop = (node.internal_scrollState?.scrollTop ?? 0) +
198
- yogaNode.getComputedBorder(Yoga.EDGE_TOP);
199
- let activeStickyNodeIndex = -1;
200
- for (const [index, stickyNode] of stickyNodes.entries()) {
201
- if (stickyNode.yogaNode) {
202
- const stickyNodeTop = getRelativeTop(stickyNode, node);
203
- if (stickyNodeTop < scrollTop) {
204
- const parent = stickyNode.parentNode;
205
- if (parent?.yogaNode) {
206
- const parentTop = getRelativeTop(parent, node);
207
- const parentHeight = parent.yogaNode.getComputedHeight();
208
- if (parentTop + parentHeight > scrollTop) {
209
- activeStickyNode = stickyNode;
210
- activeStickyNodeIndex = index;
211
- }
212
- }
213
- }
214
- }
215
- }
216
- if (activeStickyNodeIndex !== -1 &&
217
- activeStickyNodeIndex + 1 < stickyNodes.length) {
218
- nextStickyNode = stickyNodes[activeStickyNodeIndex + 1];
219
- }
220
- }
221
- }
222
- if (horizontallyScrollable) {
223
- childrenOffsetX -= node.internal_scrollState?.scrollLeft ?? 0;
224
- }
225
- const clipHorizontally = overflowX === 'hidden' || overflowX === 'scroll';
226
- const clipVertically = overflowY === 'hidden' || overflowY === 'scroll';
227
- if (clipHorizontally || clipVertically) {
228
- const x1 = clipHorizontally
229
- ? x + yogaNode.getComputedBorder(Yoga.EDGE_LEFT)
230
- : undefined;
231
- const x2 = clipHorizontally
232
- ? x +
233
- yogaNode.getComputedWidth() -
234
- yogaNode.getComputedBorder(Yoga.EDGE_RIGHT)
235
- : undefined;
236
- const y1 = clipVertically
237
- ? y + yogaNode.getComputedBorder(Yoga.EDGE_TOP)
238
- : undefined;
239
- const y2 = clipVertically
240
- ? y +
241
- yogaNode.getComputedHeight() -
242
- yogaNode.getComputedBorder(Yoga.EDGE_BOTTOM)
243
- : undefined;
244
- output.clip({ x1, x2, y1, y2 });
245
- clipped = true;
246
- }
247
- }
248
- if (node.nodeName === 'ink-root' || node.nodeName === 'ink-box') {
249
- for (const childNode of node.childNodes) {
250
- renderNodeToOutput(childNode, output, {
251
- offsetX: childrenOffsetX,
252
- offsetY: childrenOffsetY,
253
- transformers: newTransformers,
254
- skipStaticElements,
255
- nodeToSkip: activeStickyNode,
256
- isStickyRender,
257
- selectionMap,
258
- selectionStyle,
259
- });
260
- }
261
- if (activeStickyNode?.yogaNode) {
262
- const alternateStickyNode = activeStickyNode.childNodes.find(childNode => childNode.internalStickyAlternate);
263
- const nodeToRender = alternateStickyNode ?? activeStickyNode;
264
- const nodeToRenderYogaNode = nodeToRender.yogaNode;
265
- if (!nodeToRenderYogaNode) {
266
- return;
267
- }
268
- const stickyYogaNode = activeStickyNode.yogaNode;
269
- const borderTop = yogaNode.getComputedBorder(Yoga.EDGE_TOP);
270
- const scrollTop = node.internal_scrollState?.scrollTop ?? 0;
271
- const parent = activeStickyNode.parentNode;
272
- const parentYogaNode = parent.yogaNode;
273
- const parentTop = getRelativeTop(parent, node);
274
- const parentHeight = parentYogaNode.getComputedHeight();
275
- const parentBottom = parentTop + parentHeight;
276
- const stickyNodeHeight = nodeToRenderYogaNode.getComputedHeight();
277
- const maxStickyTop = y - scrollTop + parentBottom - stickyNodeHeight;
278
- const naturalStickyY = y - scrollTop + getRelativeTop(activeStickyNode, node);
279
- const stuckStickyY = y + borderTop;
280
- let finalStickyY = Math.min(Math.max(stuckStickyY, naturalStickyY), maxStickyTop);
281
- if (nextStickyNode?.yogaNode) {
282
- const nextStickyNodeTop = getRelativeTop(nextStickyNode, node);
283
- const nextStickyNodeTopInViewport = y - scrollTop + nextStickyNodeTop;
284
- if (nextStickyNodeTopInViewport < finalStickyY + stickyNodeHeight) {
285
- finalStickyY = nextStickyNodeTopInViewport - stickyNodeHeight;
286
- }
287
- }
288
- let offsetX;
289
- let offsetY;
290
- if (nodeToRender === alternateStickyNode) {
291
- const parentAbsoluteX = x + getRelativeLeft(parent, node);
292
- const stickyNodeAbsoluteX = parentAbsoluteX + stickyYogaNode.getComputedLeft();
293
- offsetX = stickyNodeAbsoluteX;
294
- offsetY = finalStickyY;
295
- }
296
- else {
297
- const parentAbsoluteX = x + getRelativeLeft(parent, node);
298
- offsetX = parentAbsoluteX;
299
- offsetY = finalStickyY - stickyYogaNode.getComputedTop();
300
- }
301
- renderNodeToOutput(nodeToRender, output, {
302
- offsetX,
303
- offsetY,
304
- transformers: newTransformers,
305
- skipStaticElements,
306
- isStickyRender: true,
307
- selectionMap,
308
- selectionStyle,
309
- });
310
- }
311
- if (clipped) {
312
- output.unclip();
313
- }
314
- if (node.nodeName === 'ink-box') {
315
- if (verticallyScrollable) {
316
- renderVerticalScrollbar(node, x, y, output);
317
- }
318
- if (horizontallyScrollable) {
319
- renderHorizontalScrollbar(node, x, y, output);
320
- }
321
- }
322
- }
323
- }
324
- };
325
- const calculateWrappedCursorPosition = (lines, styledChars, targetOffset) => {
326
- const styledCharToOffset = new Map();
327
- let offset = 0;
328
- for (const char of styledChars) {
329
- styledCharToOffset.set(char, offset);
330
- offset += char.value.length;
331
- }
332
- let cursorLineIndex = lines.length - 1;
333
- let relativeCursorPosition = targetOffset;
334
- // -1 represents "before document start" so first character (offset 0) is handled correctly
335
- let previousLineEndOffset = -1;
336
- for (const [i, line] of lines.entries()) {
337
- if (line.length > 0) {
338
- const firstChar = line.find(char => styledCharToOffset.has(char));
339
- const lastChar = line.findLast(char => styledCharToOffset.has(char));
340
- if (!firstChar || !lastChar) {
341
- // Padding-only line (originally empty), treat as empty line
342
- if (targetOffset > previousLineEndOffset) {
343
- cursorLineIndex = i;
344
- relativeCursorPosition = targetOffset - previousLineEndOffset - 1;
345
- previousLineEndOffset++;
346
- }
347
- continue;
348
- }
349
- const lineStartOffset = styledCharToOffset.get(firstChar);
350
- const lineEndOffset = styledCharToOffset.get(lastChar) + lastChar.value.length;
351
- // Set as candidate if targetOffset is at or after line start
352
- if (targetOffset >= lineStartOffset) {
353
- cursorLineIndex = i;
354
- relativeCursorPosition = Math.max(0, targetOffset - lineStartOffset);
355
- }
356
- // Finalize and exit if targetOffset is within or before this line's range.
357
- // If targetOffset is in a gap (between previousLineEndOffset and lineStartOffset),
358
- // the cursor stays at the previous line's end (already set in previous iteration).
359
- if (targetOffset <= lineEndOffset) {
360
- break;
361
- }
362
- previousLineEndOffset = lineEndOffset;
363
- }
364
- else if (i === 0 && targetOffset === 0) {
365
- // Edge case: First line is empty and cursor is at position 0
366
- cursorLineIndex = 0;
367
- relativeCursorPosition = 0;
368
- break;
369
- }
370
- else if (i > 0 && targetOffset > previousLineEndOffset) {
371
- // Handle empty lines (usually caused by \n)
372
- cursorLineIndex = i;
373
- relativeCursorPosition = targetOffset - previousLineEndOffset - 1;
374
- // Advance past the \n character
375
- previousLineEndOffset++;
376
- }
377
- }
378
- return { cursorLineIndex, relativeCursorPosition };
379
- };
380
- function getStickyDescendants(node) {
381
- const stickyDescendants = [];
382
- for (const child of node.childNodes) {
383
- if (child.nodeName === '#text') {
384
- continue;
385
- }
386
- const domChild = child;
387
- if (domChild.internalStickyAlternate) {
388
- continue;
389
- }
390
- if (domChild.internalSticky) {
391
- stickyDescendants.push(domChild);
392
- }
393
- else {
394
- const overflow = domChild.style.overflow ?? 'visible';
395
- const overflowX = domChild.style.overflowX ?? overflow;
396
- const overflowY = domChild.style.overflowY ?? overflow;
397
- const isScrollable = overflowX === 'scroll' || overflowY === 'scroll';
398
- if (!isScrollable && domChild.childNodes) {
399
- stickyDescendants.push(...getStickyDescendants(domChild));
400
- }
401
- }
402
- }
403
- return stickyDescendants;
404
- }
405
- function getRelativeTop(node, ancestor) {
406
- if (!node.yogaNode) {
407
- return 0;
408
- }
409
- let top = node.yogaNode.getComputedTop();
410
- let parent = node.parentNode;
411
- while (parent && parent !== ancestor) {
412
- if (parent.yogaNode) {
413
- top += parent.yogaNode.getComputedTop();
414
- if (parent.nodeName === 'ink-box') {
415
- const overflow = parent.style.overflow ?? 'visible';
416
- const overflowY = parent.style.overflowY ?? overflow;
417
- if (overflowY === 'scroll') {
418
- top -= parent.internal_scrollState?.scrollTop ?? 0;
419
- }
420
- }
421
- }
422
- parent = parent.parentNode;
423
- }
424
- return top;
425
- }
426
- function getRelativeLeft(node, ancestor) {
427
- if (!node.yogaNode) {
428
- return 0;
429
- }
430
- let left = node.yogaNode.getComputedLeft();
431
- let parent = node.parentNode;
432
- while (parent && parent !== ancestor) {
433
- if (parent.yogaNode) {
434
- left += parent.yogaNode.getComputedLeft();
435
- if (parent.nodeName === 'ink-box') {
436
- const overflow = parent.style.overflow ?? 'visible';
437
- const overflowX = parent.style.overflowX ?? overflow;
438
- if (overflowX === 'scroll') {
439
- left -= parent.internal_scrollState?.scrollLeft ?? 0;
440
- }
441
- }
130
+ if (node.cachedRender) {
131
+ handleCachedRenderNode(node, output, {
132
+ x,
133
+ y,
134
+ selectionMap,
135
+ selectionStyle,
136
+ });
137
+ return;
442
138
  }
443
- parent = parent.parentNode;
444
- }
445
- return left;
446
- }
447
- function renderScrollbar(node, output, layout, axis) {
448
- const { thumb } = layout;
449
- const thumbColor = node.style.scrollbarThumbColor;
450
- for (let index = thumb.start; index < thumb.end; index++) {
451
- const cellStartHalf = index * 2;
452
- const cellEndHalf = (index + 1) * 2;
453
- const start = Math.max(cellStartHalf, thumb.startHalf);
454
- const end = Math.min(cellEndHalf, thumb.endHalf);
455
- const fill = end - start;
456
- if (fill > 0) {
457
- const char = axis === 'vertical'
458
- ? fill === 2
459
- ? '█'
460
- : // Fill === 1
461
- start % 2 === 0
462
- ? '▀' // Top half of the cell is filled
463
- : '▄' // Bottom half of the cell is filled
464
- : fill === 2
465
- ? '█'
466
- : // Fill === 1
467
- start % 2 === 0
468
- ? '▌' // Left half of the cell is filled
469
- : '▐'; // Right half of the cell is filled
470
- const outputX = axis === 'vertical' ? layout.x : layout.x + index;
471
- const outputY = axis === 'vertical' ? layout.y + index : layout.y;
472
- output.write(outputX, outputY, colorize(char, thumbColor, 'foreground'), {
473
- transformers: [],
474
- preserveBackgroundColor: true,
139
+ if (node.nodeName === 'ink-text') {
140
+ handleTextNode(node, output, {
141
+ x,
142
+ y,
143
+ newTransformers,
144
+ selectionMap,
145
+ selectionStyle,
475
146
  });
147
+ return;
476
148
  }
149
+ handleContainerNode(node, output, {
150
+ x,
151
+ y,
152
+ width,
153
+ height,
154
+ newTransformers,
155
+ skipStaticElements,
156
+ isStickyRender,
157
+ skipStickyHeaders,
158
+ selectionMap,
159
+ selectionStyle,
160
+ });
477
161
  }
478
162
  }
479
- function renderVerticalScrollbar(node, x, y, output) {
480
- const layout = getVerticalScrollbarBoundingBox(node, { x, y });
481
- if (layout) {
482
- renderScrollbar(node, output, layout, 'vertical');
483
- }
484
- }
485
- function renderHorizontalScrollbar(node, x, y, output) {
486
- const layout = getHorizontalScrollbarBoundingBox(node, { x, y });
487
- if (layout) {
488
- renderScrollbar(node, output, layout, 'horizontal');
489
- }
490
- }
491
- const applySelectionToStyledChars = (styledChars, selectionState, selectionStyle) => {
492
- const { range, currentOffset } = selectionState;
493
- const { start, end } = range;
494
- let charCodeUnitOffset = 0;
495
- const newStyledChars = [];
496
- for (const char of styledChars) {
497
- const charLength = char.value.length;
498
- const globalOffset = currentOffset + charCodeUnitOffset;
499
- if (globalOffset >= start && globalOffset < end) {
500
- if (selectionStyle) {
501
- newStyledChars.push(selectionStyle(char));
502
- }
503
- else {
504
- // 7 is the ANSI code for inverse (reverse video)
505
- const newChar = {
506
- ...char,
507
- styles: [...char.styles],
508
- };
509
- newChar.styles.push({
510
- type: 'ansi',
511
- code: '\u001B[7m',
512
- endCode: '\u001B[27m',
513
- });
514
- newStyledChars.push(newChar);
515
- }
516
- }
517
- else {
518
- newStyledChars.push(char);
519
- }
520
- charCodeUnitOffset += charLength;
521
- }
522
- selectionState.currentOffset += charCodeUnitOffset;
523
- return newStyledChars;
524
- };
525
163
  export default renderNodeToOutput;
526
164
  //# sourceMappingURL=render-node-to-output.js.map