import type { DOMElement } from './dom.js' import type { TextStyles } from './styles.js' /** * A segment of text with its associated styles. * Used for structured rendering without ANSI string transforms. */ export type StyledSegment = { text: string styles: TextStyles hyperlink?: string } /** * Squash text nodes into styled segments, propagating styles down through the tree. * This allows structured styling without relying on ANSI string transforms. */ export function squashTextNodesToSegments( node: DOMElement, inheritedStyles: TextStyles = {}, inheritedHyperlink?: string, out: StyledSegment[] = [], ): StyledSegment[] { const mergedStyles = node.textStyles ? { ...inheritedStyles, ...node.textStyles } : inheritedStyles for (const childNode of node.childNodes) { if (childNode === undefined) { continue } if (childNode.nodeName === '#text') { if (childNode.nodeValue.length > 0) { out.push({ text: childNode.nodeValue, styles: mergedStyles, hyperlink: inheritedHyperlink, }) } } else if ( childNode.nodeName === 'ink-text' || childNode.nodeName === 'ink-virtual-text' ) { squashTextNodesToSegments( childNode, mergedStyles, inheritedHyperlink, out, ) } else if (childNode.nodeName === 'ink-link') { const href = childNode.attributes['href'] as string | undefined squashTextNodesToSegments( childNode, mergedStyles, href || inheritedHyperlink, out, ) } } return out } /** * Squash text nodes into a plain string (without styles). * Used for text measurement in layout calculations. */ function squashTextNodes(node: DOMElement): string { let text = '' for (const childNode of node.childNodes) { if (childNode === undefined) { continue } if (childNode.nodeName === '#text') { text += childNode.nodeValue } else if ( childNode.nodeName === 'ink-text' || childNode.nodeName === 'ink-virtual-text' ) { text += squashTextNodes(childNode) } else if (childNode.nodeName === 'ink-link') { text += squashTextNodes(childNode) } } return text } export default squashTextNodes