(
node: DOMElement,
output: Output,
{
offsetX = 0,
offsetY = 0,
prevScreen,
skipSelfBlit = false,
inheritedBackgroundColor,
}: {
offsetX?: number
offsetY?: number
prevScreen: Screen | undefined
// Force this node to descend instead of blitting its own rect, while
// still passing prevScreen to children. Used for non-opaque absolute
// overlays over a dirty clipped region: the overlay's full rect has
// transparent gaps (stale underlying content in prevScreen), but its
// opaque descendants' narrower rects are safe to blit.
skipSelfBlit?: boolean
inheritedBackgroundColor?: Color
},
)
| 385 | |
| 386 | // After nodes are laid out, render each to output object, which later gets rendered to terminal |
| 387 | function renderNodeToOutput( |
| 388 | node: DOMElement, |
| 389 | output: Output, |
| 390 | { |
| 391 | offsetX = 0, |
| 392 | offsetY = 0, |
| 393 | prevScreen, |
| 394 | skipSelfBlit = false, |
| 395 | inheritedBackgroundColor, |
| 396 | }: { |
| 397 | offsetX?: number |
| 398 | offsetY?: number |
| 399 | prevScreen: Screen | undefined |
| 400 | // Force this node to descend instead of blitting its own rect, while |
| 401 | // still passing prevScreen to children. Used for non-opaque absolute |
| 402 | // overlays over a dirty clipped region: the overlay's full rect has |
| 403 | // transparent gaps (stale underlying content in prevScreen), but its |
| 404 | // opaque descendants' narrower rects are safe to blit. |
| 405 | skipSelfBlit?: boolean |
| 406 | inheritedBackgroundColor?: Color |
| 407 | }, |
| 408 | ): void { |
| 409 | const { yogaNode } = node |
| 410 | |
| 411 | if (yogaNode) { |
| 412 | if (yogaNode.getDisplay() === LayoutDisplay.None) { |
| 413 | // Clear old position if node was visible before becoming hidden |
| 414 | if (node.dirty) { |
| 415 | const cached = nodeCache.get(node) |
| 416 | if (cached) { |
| 417 | output.clear({ |
| 418 | x: Math.floor(cached.x), |
| 419 | y: Math.floor(cached.y), |
| 420 | width: Math.floor(cached.width), |
| 421 | height: Math.floor(cached.height), |
| 422 | }) |
| 423 | // Drop descendants' cache too — hideInstance's markDirty walks UP |
| 424 | // only, so descendants' .dirty stays false. Their nodeCache entries |
| 425 | // survive with pre-hide rects. On unhide, if position didn't shift, |
| 426 | // the blit check at line ~432 passes and copies EMPTY cells from |
| 427 | // prevScreen (cleared here) → content vanishes. |
| 428 | dropSubtreeCache(node) |
| 429 | layoutShifted = true |
| 430 | } |
| 431 | } |
| 432 | return |
| 433 | } |
| 434 | |
| 435 | // Left and top positions in Yoga are relative to their parent node |
| 436 | const x = offsetX + yogaNode.getComputedLeft() |
| 437 | const yogaTop = yogaNode.getComputedTop() |
| 438 | let y = offsetY + yogaTop |
| 439 | const width = yogaNode.getComputedWidth() |
| 440 | const height = yogaNode.getComputedHeight() |
| 441 | |
| 442 | // Absolute-positioned overlays (e.g. autocomplete menus with bottom='100%') |
| 443 | // can compute negative screen y when they extend above the viewport. Without |
| 444 | // clamping, setCellAt drops cells at y<0, clipping the TOP of the content |
no test coverage detected