@terrastack/ink
Version:
React for CLI. Temporary fork of https://github.com/vadimdemedes/ink
290 lines (226 loc) • 8.46 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _yogaLayoutPrebuilt = _interopRequireDefault(require("yoga-layout-prebuilt"));
var _widestLine = _interopRequireDefault(require("widest-line"));
var _cliBoxes = _interopRequireDefault(require("cli-boxes"));
var _chalk = _interopRequireDefault(require("chalk"));
var _applyStyles = _interopRequireDefault(require("./apply-styles"));
var _output = _interopRequireDefault(require("./output"));
var _dom = require("./dom");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const measureText = text => {
const width = (0, _widestLine.default)(text);
const height = text.split('\n').length;
return {
width,
height
};
}; // Traverse the node tree, create Yoga nodes and assign styles to each Yoga node
const buildLayout = (node, options) => {
const {
config,
terminalWidth,
skipStaticElements
} = options;
const yogaNode = _yogaLayoutPrebuilt.default.Node.create(config);
node.yogaNode = yogaNode;
const style = node.style || {}; // Root node of the tree
if (node.nodeName === 'ROOT') {
yogaNode.setWidth(terminalWidth);
if (node.childNodes.length > 0) {
const childNodes = node.childNodes.filter(childNode => {
return skipStaticElements ? !childNode.static : true;
});
for (const [index, childNode] of Object.entries(childNodes)) {
const childYogaNode = buildLayout(childNode, options).yogaNode;
yogaNode.insertChild(childYogaNode, index);
}
}
return node;
} // Apply margin, padding, flex, etc styles
(0, _applyStyles.default)(yogaNode, style); // Nodes with only text have a child Yoga node dedicated for that text
if (node.textContent) {
const {
width,
height
} = measureText(node.textContent);
yogaNode.setWidth(style.width || width);
yogaNode.setHeight(style.height || height);
return node;
} // Text node
if (node.nodeValue) {
const {
width,
height
} = measureText(node.nodeValue);
yogaNode.setWidth(width);
yogaNode.setHeight(height);
return node;
} // Nodes with other nodes as children
if (style.width) {
yogaNode.setWidth(style.width);
}
if (style.height) {
yogaNode.setHeight(style.height);
}
if (node.childNodes.length > 0) {
const childNodes = node.childNodes.filter(childNode => {
return skipStaticElements ? !childNode.static : true;
});
for (const [index, childNode] of Object.entries(childNodes)) {
const {
yogaNode: childYogaNode
} = buildLayout(childNode, options);
yogaNode.insertChild(childYogaNode, index);
}
}
return node;
}; // After nodes are laid out, render each to output object, which later gets rendered to terminal
const renderNodeToOutput = (node, output, offsetX = 0, offsetY = 0, {
transformers,
skipStaticElements
}) => {
if (node.static && skipStaticElements) {
return;
}
const {
yogaNode
} = node; // Left and top positions in Yoga are relative to their parent node
const x = offsetX + yogaNode.getComputedLeft();
const y = offsetY + yogaNode.getComputedTop();
const width = yogaNode.getComputedWidth();
const height = yogaNode.getComputedWidth();
const padding = yogaNode.getComputedPadding(0);
const margin = yogaNode.getComputedMargin(0);
const borderTop = yogaNode.getComputedBorder(_yogaLayoutPrebuilt.default.EDGE_TOP);
const borderRight = yogaNode.getComputedBorder(_yogaLayoutPrebuilt.default.EDGE_RIGHT);
const borderBottom = yogaNode.getComputedBorder(_yogaLayoutPrebuilt.default.EDGE_BOTTOM);
const borderLeft = yogaNode.getComputedBorder(_yogaLayoutPrebuilt.default.EDGE_LEFT);
const layout = yogaNode.getComputedLayout();
const styles = node.style || {};
const borderStyle = styles.borderStyle || 'round';
const borderColor = styles.borderColor ? _chalk.default[styles.borderColor] : a => a;
const box = _cliBoxes.default[borderStyle];
if (borderTop > 0) {
const horizontal = box.horizontal.repeat(width - 2);
output.write(x, y, borderColor(box.topLeft + horizontal + box.topRight), {
transformers
});
}
if (borderBottom > 0) {
const horizontal = box.horizontal.repeat(width - 2);
output.write(x, y + layout.height - 1, borderColor(box.bottomLeft + horizontal + box.bottomRight), {
transformers
});
}
if (borderLeft > 0) {
const vertical = box.vertical.repeat(layout.height - 2).split('').join('\n');
output.write(x, y + 1, borderColor(vertical), {
transformers
});
}
if (borderRight > 0) {
const vertical = box.vertical.repeat(layout.height - 2).split('').join('\n');
output.write(x + width - 1, y + 1, borderColor(vertical), {
transformers
});
} // Transformers are functions that transform final text output of each component
// See Output class for logic that applies transformers
let newTransformers = transformers;
if (node.unstable__transformChildren) {
newTransformers = [node.unstable__transformChildren, ...transformers];
} // Text nodes
const text = node.textContent || node.nodeValue;
if (text) {
output.write(x, y, text, {
transformers: newTransformers
});
return;
} // Nodes that have other nodes as children
for (const childNode of node.childNodes) {
renderNodeToOutput(childNode, output, x, y, {
transformers: newTransformers,
skipStaticElements
});
}
}; // Since <Static> components can be placed anywhere in the tree, this helper finds and returns them
const getStaticNodes = element => {
const staticNodes = [];
for (const childNode of element.childNodes) {
if (childNode.static) {
staticNodes.push(childNode);
}
if (Array.isArray(childNode.childNodes) && childNode.childNodes.length > 0) {
staticNodes.push(...getStaticNodes(childNode));
}
}
return staticNodes;
}; // Build layout, apply styles, build text output of all nodes and return it
var _default = ({
terminalWidth
}) => {
const config = _yogaLayoutPrebuilt.default.Config.create(); // Used to free up memory used by last Yoga node tree
let lastYogaNode;
let lastStaticYogaNode;
return node => {
if (lastYogaNode) {
lastYogaNode.freeRecursive();
}
if (lastStaticYogaNode) {
lastStaticYogaNode.freeRecursive();
}
const staticElements = getStaticNodes(node);
if (staticElements.length > 1) {
if (process.env.NODE_ENV !== 'production') {
console.error('Warning: There can only be one <Static> component');
}
} // <Static> component must be built and rendered separately, so that the layout of the other output is unaffected
let staticOutput;
if (staticElements.length === 1) {
const rootNode = (0, _dom.createNode)('root');
(0, _dom.appendChildNode)(rootNode, staticElements[0]);
const {
yogaNode: staticYogaNode
} = buildLayout(rootNode, {
config,
terminalWidth,
skipStaticElements: false
});
staticYogaNode.calculateLayout(_yogaLayoutPrebuilt.default.UNDEFINED, _yogaLayoutPrebuilt.default.UNDEFINED, _yogaLayoutPrebuilt.default.DIRECTION_LTR); // Save current Yoga node tree to free up memory later
lastStaticYogaNode = staticYogaNode;
staticOutput = new _output.default({
width: staticYogaNode.getComputedWidth(),
height: staticYogaNode.getComputedHeight()
});
renderNodeToOutput(rootNode, staticOutput, 0, 0, {
transformers: [],
skipStaticElements: false
});
}
const {
yogaNode
} = buildLayout(node, {
config,
terminalWidth,
skipStaticElements: true
});
yogaNode.calculateLayout(_yogaLayoutPrebuilt.default.UNDEFINED, _yogaLayoutPrebuilt.default.UNDEFINED, _yogaLayoutPrebuilt.default.DIRECTION_LTR); // Save current node tree to free up memory later
lastYogaNode = yogaNode;
const output = new _output.default({
width: yogaNode.getComputedWidth(),
height: yogaNode.getComputedHeight()
});
renderNodeToOutput(node, output, 0, 0, {
transformers: [],
skipStaticElements: true
});
return {
output: output.get(),
staticOutput: staticOutput ? `${staticOutput.get()}\n` : undefined
};
};
};
exports.default = _default;