blog.hangerthem.com/lib/react-node-text.ts

58 lines
2.1 KiB
TypeScript

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
}