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