UNPKG

ink-gradient

Version:
133 lines (131 loc) 5.04 kB
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime"; import { Children, isValidElement, cloneElement, } from 'react'; import { Box, Transform, Text } from 'ink'; import gradientString from 'gradient-string'; import stripAnsi from 'strip-ansi'; /** @example ``` import React from 'react'; import {render} from 'ink'; import Gradient from 'ink-gradient'; import BigText from 'ink-big-text'; render( <Gradient name="rainbow"> <BigText text="unicorns"/> </Gradient> ); ``` */ const Gradient = props => { if (props.name && props.colors) { throw new Error('The `name` and `colors` props are mutually exclusive'); } let gradient; if (props.name) { gradient = gradientString[props.name]; } else if (props.colors) { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument gradient = gradientString(props.colors); // Note: `gradient-string` types are too loose to express this safely. } else { throw new Error('Either `name` or `colors` prop must be provided'); } const applyGradient = (text) => gradient.multiline(stripAnsi(text)); const containsBoxDescendant = (nodeChildren) => { let hasBox = false; const search = (value) => { Children.forEach(value, child => { if (hasBox) { return; } if (!isValidElement(child)) { return; } if (child.type === Box) { hasBox = true; return; } const childProps = child.props; if (Object.hasOwn(childProps, 'children')) { search(childProps['children']); } }); }; search(nodeChildren); return hasBox; }; const hasChildrenProp = (props) => Object.hasOwn(props, 'children'); const isPlainTextNode = (node) => typeof node === 'string' || typeof node === 'number'; const isNonRenderableChild = (node) => node === null || node === undefined || typeof node === 'boolean'; const childrenCount = Children.count(props.children); // Check if children is just a string/number (simple case) if (isPlainTextNode(props.children)) { return _jsx(Transform, { transform: applyGradient, children: props.children }); } if (childrenCount === 1 && !containsBoxDescendant(props.children)) { return _jsx(Transform, { transform: applyGradient, children: props.children }); } // For complex children (components), apply gradient to text nodes directly const applyGradientToChildren = (children) => { const nodes = []; let bufferedText = ''; let nodeIndex = 0; const createKey = () => `gradient-node-${nodeIndex++}`; const pushTransformed = (node, key) => { nodes.push(_jsx(Transform, { transform: applyGradient, children: node }, key)); }; const flushText = () => { if (bufferedText === '') { return; } const text = bufferedText; bufferedText = ''; pushTransformed(_jsx(Text, { children: text }), createKey()); }; Children.forEach(children, child => { if (isNonRenderableChild(child)) { return; } if (isPlainTextNode(child)) { bufferedText += String(child); return; } flushText(); if (isValidElement(child)) { const childKey = child.key ?? createKey(); const childProps = child.props; if (child.type === Text) { pushTransformed(child, childKey); return; } if (child.type === Box) { if (hasChildrenProp(childProps)) { const childChildren = childProps['children']; nodes.push(cloneElement(child, { key: childKey }, applyGradientToChildren(childChildren))); return; } nodes.push(cloneElement(child, { key: childKey })); return; } if (hasChildrenProp(childProps)) { const childChildren = childProps['children']; if (!containsBoxDescendant(childChildren)) { pushTransformed(child, childKey); return; } nodes.push(cloneElement(child, { key: childKey }, applyGradientToChildren(childChildren))); return; } pushTransformed(child, childKey); return; } nodes.push(child); }); flushText(); return nodes; }; return _jsx(_Fragment, { children: applyGradientToChildren(props.children) }); }; export default Gradient;