import { cloneElement, isValidElement, type ReactNode } from 'react' /** * Recursively traverses a ReactNode tree and extracts the text content. * @param node - The ReactNode to process. * @returns A string containing the concatenated text content of the ReactNode tree. */ export function getTextContent(node: ReactNode): string { if (node === null || typeof node === 'boolean') return '' if (typeof node === 'string' || typeof node === 'number') return String(node) if (Array.isArray(node)) return node.map(getTextContent).join('') if (isValidElement(node)) { const props = node.props as { children?: ReactNode } return getTextContent(props.children) } return '' } /** * Recursively traverses a ReactNode tree and removes leading whitespace and a specified pattern from the beginning of text nodes. * @param node - The ReactNode to process. * @param pattern - The regular expression pattern to match and remove from the beginning of text nodes. * @returns A new ReactNode with the leading whitespace and specified pattern removed from text nodes. */ export function stripLeadingMatch(node: ReactNode, pattern: RegExp): ReactNode { if (typeof node === 'string') { const trimmed = node.replace(/^\s+/, '') if (pattern.test(trimmed)) { return trimmed.replace(pattern, '').replace(/^\s+/, '') } return node } if (Array.isArray(node)) { let stripped = false return node.map((child) => { if (stripped) return child if (typeof child === 'string' && child.trim() === '') return child // skip whitespace nodes if (typeof child === 'string') { const trimmed = child.replace(/^\s+/, '') if (pattern.test(trimmed)) { stripped = true return trimmed.replace(pattern, '').replace(/^\s+/, '') } return child } if (isValidElement(child)) { stripped = true return stripLeadingMatch(child, pattern) } return child }) } if (isValidElement(node)) { const props = node.props as { children?: ReactNode } return cloneElement(node, {}, stripLeadingMatch(props.children, pattern)) } return node }