render-border.ts
ink/render-border.ts
232
Lines
6642
Bytes
3
Exports
7
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with ui-flow. It contains 232 lines, 7 detected imports, and 3 detected exports.
Important relationships
Detected exports
BorderTextOptionsCUSTOM_BORDER_STYLESBorderStyle
Keywords
nodestylepositiontextoutputborderlengthbordertextalignbeforeafter
Detected imports
chalkcli-boxes./colorize.js./dom.js./output.js./stringWidth.js./styles.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
import chalk from 'chalk'
import cliBoxes, { type Boxes, type BoxStyle } from 'cli-boxes'
import { applyColor } from './colorize.js'
import type { DOMNode } from './dom.js'
import type Output from './output.js'
import { stringWidth } from './stringWidth.js'
import type { Color } from './styles.js'
export type BorderTextOptions = {
content: string // Pre-rendered string with ANSI color codes
position: 'top' | 'bottom'
align: 'start' | 'end' | 'center'
offset?: number // Only used with 'start' or 'end' alignment. Number of characters from the edge.
}
export const CUSTOM_BORDER_STYLES = {
dashed: {
top: '╌',
left: '╎',
right: '╎',
bottom: '╌',
// there aren't any line-drawing characters for dashes unfortunately
topLeft: ' ',
topRight: ' ',
bottomLeft: ' ',
bottomRight: ' ',
},
} as const
export type BorderStyle =
| keyof Boxes
| keyof typeof CUSTOM_BORDER_STYLES
| BoxStyle
function embedTextInBorder(
borderLine: string,
text: string,
align: 'start' | 'end' | 'center',
offset: number = 0,
borderChar: string,
): [before: string, text: string, after: string] {
const textLength = stringWidth(text)
const borderLength = borderLine.length
if (textLength >= borderLength - 2) {
return ['', text.substring(0, borderLength), '']
}
let position: number
if (align === 'center') {
position = Math.floor((borderLength - textLength) / 2)
} else if (align === 'start') {
position = offset + 1 // +1 to account for corner character
} else {
// align === 'end'
position = borderLength - textLength - offset - 1 // -1 for corner character
}
// Ensure position is valid
position = Math.max(1, Math.min(position, borderLength - textLength - 1))
const before = borderLine.substring(0, 1) + borderChar.repeat(position - 1)
const after =
borderChar.repeat(borderLength - position - textLength - 1) +
borderLine.substring(borderLength - 1)
return [before, text, after]
}
function styleBorderLine(
line: string,
color: Color | undefined,
dim: boolean | undefined,
): string {
let styled = applyColor(line, color)
if (dim) {
styled = chalk.dim(styled)
}
return styled
}
const renderBorder = (
x: number,
y: number,
node: DOMNode,
output: Output,
): void => {
if (node.style.borderStyle) {
const width = Math.floor(node.yogaNode!.getComputedWidth())
const height = Math.floor(node.yogaNode!.getComputedHeight())
const box =
typeof node.style.borderStyle === 'string'
? (CUSTOM_BORDER_STYLES[
node.style.borderStyle as keyof typeof CUSTOM_BORDER_STYLES
] ?? cliBoxes[node.style.borderStyle as keyof Boxes])
: node.style.borderStyle
const topBorderColor = node.style.borderTopColor ?? node.style.borderColor
const bottomBorderColor =
node.style.borderBottomColor ?? node.style.borderColor
const leftBorderColor = node.style.borderLeftColor ?? node.style.borderColor
const rightBorderColor =
node.style.borderRightColor ?? node.style.borderColor
const dimTopBorderColor =
node.style.borderTopDimColor ?? node.style.borderDimColor
const dimBottomBorderColor =
node.style.borderBottomDimColor ?? node.style.borderDimColor
const dimLeftBorderColor =
node.style.borderLeftDimColor ?? node.style.borderDimColor
const dimRightBorderColor =
node.style.borderRightDimColor ?? node.style.borderDimColor
const showTopBorder = node.style.borderTop !== false
const showBottomBorder = node.style.borderBottom !== false
const showLeftBorder = node.style.borderLeft !== false
const showRightBorder = node.style.borderRight !== false
const contentWidth = Math.max(
0,
width - (showLeftBorder ? 1 : 0) - (showRightBorder ? 1 : 0),
)
const topBorderLine = showTopBorder
? (showLeftBorder ? box.topLeft : '') +
box.top.repeat(contentWidth) +
(showRightBorder ? box.topRight : '')
: ''
// Handle text in top border
let topBorder: string | undefined
if (showTopBorder && node.style.borderText?.position === 'top') {
const [before, text, after] = embedTextInBorder(
topBorderLine,
node.style.borderText.content,
node.style.borderText.align,
node.style.borderText.offset,
box.top,
)
topBorder =
styleBorderLine(before, topBorderColor, dimTopBorderColor) +
text +
styleBorderLine(after, topBorderColor, dimTopBorderColor)
} else if (showTopBorder) {
topBorder = styleBorderLine(
topBorderLine,
topBorderColor,
dimTopBorderColor,
)
}
let verticalBorderHeight = height
if (showTopBorder) {
verticalBorderHeight -= 1
}
if (showBottomBorder) {
verticalBorderHeight -= 1
}
verticalBorderHeight = Math.max(0, verticalBorderHeight)
let leftBorder = (applyColor(box.left, leftBorderColor) + '\n').repeat(
verticalBorderHeight,
)
if (dimLeftBorderColor) {
leftBorder = chalk.dim(leftBorder)
}
let rightBorder = (applyColor(box.right, rightBorderColor) + '\n').repeat(
verticalBorderHeight,
)
if (dimRightBorderColor) {
rightBorder = chalk.dim(rightBorder)
}
const bottomBorderLine = showBottomBorder
? (showLeftBorder ? box.bottomLeft : '') +
box.bottom.repeat(contentWidth) +
(showRightBorder ? box.bottomRight : '')
: ''
// Handle text in bottom border
let bottomBorder: string | undefined
if (showBottomBorder && node.style.borderText?.position === 'bottom') {
const [before, text, after] = embedTextInBorder(
bottomBorderLine,
node.style.borderText.content,
node.style.borderText.align,
node.style.borderText.offset,
box.bottom,
)
bottomBorder =
styleBorderLine(before, bottomBorderColor, dimBottomBorderColor) +
text +
styleBorderLine(after, bottomBorderColor, dimBottomBorderColor)
} else if (showBottomBorder) {
bottomBorder = styleBorderLine(
bottomBorderLine,
bottomBorderColor,
dimBottomBorderColor,
)
}
const offsetY = showTopBorder ? 1 : 0
if (topBorder) {
output.write(x, y, topBorder)
}
if (showLeftBorder) {
output.write(x, y + offsetY, leftBorder)
}
if (showRightBorder) {
output.write(x + width - 1, y + offsetY, rightBorder)
}
if (bottomBorder) {
output.write(x, y + height - 1, bottomBorder)
}
}
}
export default renderBorder