forked from vadimdemedes/ink
/
render-node-to-output.ts
144 lines (116 loc) Β· 3.88 KB
/
render-node-to-output.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import widestLine from 'widest-line';
import indentString from 'indent-string';
// eslint-disable-next-line n/file-extension-in-import
import Yoga from 'yoga-wasm-web/auto';
import wrapText from './wrap-text.js';
import getMaxWidth from './get-max-width.js';
import squashTextNodes from './squash-text-nodes.js';
import renderBorder from './render-border.js';
import {type DOMElement} from './dom.js';
import type Output from './output.js';
// If parent container is `<Box>`, text nodes will be treated as separate nodes in
// the tree and will have their own coordinates in the layout.
// To ensure text nodes are aligned correctly, take X and Y of the first text node
// and use it as offset for the rest of the nodes
// Only first node is taken into account, because other text nodes can't have margin or padding,
// so their coordinates will be relative to the first node anyway
const applyPaddingToText = (node: DOMElement, text: string): string => {
const yogaNode = node.childNodes[0]?.yogaNode;
if (yogaNode) {
const offsetX = yogaNode.getComputedLeft();
const offsetY = yogaNode.getComputedTop();
text = '\n'.repeat(offsetY) + indentString(text, offsetX);
}
return text;
};
export type OutputTransformer = (s: string) => string;
// After nodes are laid out, render each to output object, which later gets rendered to terminal
const renderNodeToOutput = (
node: DOMElement,
output: Output,
options: {
offsetX?: number;
offsetY?: number;
transformers?: OutputTransformer[];
skipStaticElements: boolean;
}
) => {
const {
offsetX = 0,
offsetY = 0,
transformers = [],
skipStaticElements
} = options;
if (skipStaticElements && node.internal_static) {
return;
}
const {yogaNode} = node;
if (yogaNode) {
if (yogaNode.getDisplay() === Yoga.DISPLAY_NONE) {
return;
}
// Left and top positions in Yoga are relative to their parent node
const x = offsetX + yogaNode.getComputedLeft();
const y = offsetY + yogaNode.getComputedTop();
// Transformers are functions that transform final text output of each component
// See Output class for logic that applies transformers
let newTransformers = transformers;
if (typeof node.internal_transform === 'function') {
newTransformers = [node.internal_transform, ...transformers];
}
if (node.nodeName === 'ink-text') {
let text = squashTextNodes(node);
if (text.length > 0) {
const currentWidth = widestLine(text);
const maxWidth = getMaxWidth(yogaNode);
if (currentWidth > maxWidth) {
const textWrap = node.style.textWrap ?? 'wrap';
text = wrapText(text, maxWidth, textWrap);
}
text = applyPaddingToText(node, text);
output.write(x, y, text, {transformers: newTransformers});
}
return;
}
let clipped = false;
if (node.nodeName === 'ink-box') {
renderBorder(x, y, node, output);
const clipHorizontally = node.style.overflowX === 'hidden';
const clipVertically = node.style.overflowY === 'hidden';
if (clipHorizontally || clipVertically) {
const x1 = clipHorizontally
? x + yogaNode.getComputedBorder(Yoga.EDGE_LEFT)
: undefined;
const x2 = clipHorizontally
? x +
yogaNode.getComputedWidth() -
yogaNode.getComputedBorder(Yoga.EDGE_RIGHT)
: undefined;
const y1 = clipVertically
? y + yogaNode.getComputedBorder(Yoga.EDGE_TOP)
: undefined;
const y2 = clipVertically
? y +
yogaNode.getComputedHeight() -
yogaNode.getComputedBorder(Yoga.EDGE_BOTTOM)
: undefined;
output.clip({x1, x2, y1, y2});
clipped = true;
}
}
if (node.nodeName === 'ink-root' || node.nodeName === 'ink-box') {
for (const childNode of node.childNodes) {
renderNodeToOutput(childNode as DOMElement, output, {
offsetX: x,
offsetY: y,
transformers: newTransformers,
skipStaticElements
});
}
if (clipped) {
output.unclip();
}
}
}
};
export default renderNodeToOutput;