( node: DOMElement, stylePool: StylePool, )
| 30 | export type Renderer = (options: RenderOptions) => Frame |
| 31 | |
| 32 | export default function createRenderer( |
| 33 | node: DOMElement, |
| 34 | stylePool: StylePool, |
| 35 | ): Renderer { |
| 36 | // Reuse Output across frames so charCache (tokenize + grapheme clustering) |
| 37 | // persists — most lines don't change between renders. |
| 38 | let output: Output | undefined |
| 39 | return options => { |
| 40 | const { frontFrame, backFrame, isTTY, terminalWidth, terminalRows } = |
| 41 | options |
| 42 | const prevScreen = frontFrame.screen |
| 43 | const backScreen = backFrame.screen |
| 44 | // Read pools from the back buffer's screen — pools may be replaced |
| 45 | // between frames (generational reset), so we can't capture them in the closure |
| 46 | const charPool = backScreen.charPool |
| 47 | const hyperlinkPool = backScreen.hyperlinkPool |
| 48 | |
| 49 | // Return empty frame if yoga node doesn't exist or layout hasn't been computed yet. |
| 50 | // getComputedHeight() returns NaN before calculateLayout() is called. |
| 51 | // Also check for invalid dimensions (negative, Infinity) that would cause RangeError |
| 52 | // when creating arrays. |
| 53 | const computedHeight = node.yogaNode?.getComputedHeight() |
| 54 | const computedWidth = node.yogaNode?.getComputedWidth() |
| 55 | const hasInvalidHeight = |
| 56 | computedHeight === undefined || |
| 57 | !Number.isFinite(computedHeight) || |
| 58 | computedHeight < 0 |
| 59 | const hasInvalidWidth = |
| 60 | computedWidth === undefined || |
| 61 | !Number.isFinite(computedWidth) || |
| 62 | computedWidth < 0 |
| 63 | |
| 64 | if (!node.yogaNode || hasInvalidHeight || hasInvalidWidth) { |
| 65 | // Log to help diagnose root cause (visible with --debug flag) |
| 66 | if (node.yogaNode && (hasInvalidHeight || hasInvalidWidth)) { |
| 67 | logForDebugging( |
| 68 | `Invalid yoga dimensions: width=${computedWidth}, height=${computedHeight}, ` + |
| 69 | `childNodes=${node.childNodes.length}, terminalWidth=${terminalWidth}, terminalRows=${terminalRows}`, |
| 70 | ) |
| 71 | } |
| 72 | return { |
| 73 | screen: createScreen( |
| 74 | terminalWidth, |
| 75 | 0, |
| 76 | stylePool, |
| 77 | charPool, |
| 78 | hyperlinkPool, |
| 79 | ), |
| 80 | viewport: { width: terminalWidth, height: terminalRows }, |
| 81 | cursor: { x: 0, y: 0, visible: true }, |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | const width = Math.floor(node.yogaNode.getComputedWidth()) |
| 86 | const yogaHeight = Math.floor(node.yogaNode.getComputedHeight()) |
| 87 | // Alt-screen: the screen buffer IS the alt buffer — always exactly |
| 88 | // terminalRows tall. <AlternateScreen> wraps children in <Box |
| 89 | // height={rows} flexShrink={0}>, so yogaHeight should equal |
no test coverage detected