Skip to content

Commit 52cea54

Browse files
committed
feat: add getTreeLinePrefix utility for tree connector lines
Add a utility function that generates tree-line prefix strings (├, └, │) for nodes, similar to the Unix `tree` command output. This enables users to render visual tree connectors in their custom node renderers without implementing the parent-traversal logic themselves. Features: - Generates correct connector lines by traversing ancestor nodes - Customizable characters via TreeLineChars option - Works with any tree depth - Exported from package root for easy access Styling note: The prefix uses Box Drawing characters which require a monospace font for correct alignment. Wrap the prefix in a <span> with fontFamily: "monospace" and use a consistent fontSize (e.g. 14-16px). Inherited line-height or font-size from parent elements can cause misalignment. Usage: ```tsx import { getTreeLinePrefix } from "react-arborist"; function MyNode({ node, style }) { const icon = node.isLeaf ? "📄" : node.isOpen ? "📂" : "📁"; return ( <div style={style}> <span style={{ fontFamily: "monospace", fontSize: 16 }}> {getTreeLinePrefix(node)} </span> {icon} {node.data.name} </div> ); } ```
1 parent c851c6f commit 52cea54

2 files changed

Lines changed: 83 additions & 0 deletions

File tree

modules/react-arborist/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ export * from "./interfaces/node-api";
77
export * from "./interfaces/tree-api";
88
export * from "./data/simple-tree";
99
export * from "./hooks/use-simple-tree";
10+
export { getTreeLinePrefix } from "./utils";
11+
export type { TreeLineChars } from "./utils";

modules/react-arborist/src/utils.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,87 @@ export function getInsertIndex(tree: TreeApi<any>) {
173173
return 0;
174174
}
175175

176+
export type TreeLineChars = {
177+
last: string;
178+
middle: string;
179+
pipe: string;
180+
blank: string;
181+
};
182+
183+
const defaultTreeLineChars: TreeLineChars = {
184+
last: "└ ",
185+
middle: "├ ",
186+
pipe: "│ ",
187+
blank: "\u3000 ",
188+
};
189+
190+
/**
191+
* Generate a tree-line prefix string for a node.
192+
*
193+
* Returns characters like `├ `, `└ `, `│` that visually connect
194+
* parent and child nodes, similar to the Unix `tree` command.
195+
*
196+
* **Styling note:** The prefix uses Box Drawing characters (`│`, `├`, `└`)
197+
* which require a monospace font for correct alignment. Wrap the prefix
198+
* in a `<span>` with `fontFamily: "monospace"` and use a consistent
199+
* `fontSize` (e.g. 14–16px). Inherited `line-height` or `font-size`
200+
* from parent elements can cause misalignment.
201+
*
202+
* @example Basic usage
203+
* ```tsx
204+
* function MyNode({ node, style }: NodeRendererProps<MyData>) {
205+
* return (
206+
* <div style={style}>
207+
* <span style={{ fontFamily: "monospace", fontSize: 14 }}>
208+
* {getTreeLinePrefix(node)}
209+
* </span>
210+
* {node.data.name}
211+
* </div>
212+
* );
213+
* }
214+
* ```
215+
*
216+
* @example With folder/file icons
217+
* ```tsx
218+
* function MyNode({ node, style }: NodeRendererProps<MyData>) {
219+
* const icon = node.isLeaf ? "📄" : node.isOpen ? "📂" : "📁";
220+
* return (
221+
* <div style={style}>
222+
* <span style={{ fontFamily: "monospace", fontSize: 16 }}>
223+
* {getTreeLinePrefix(node)}
224+
* </span>
225+
* {icon} {node.data.name}
226+
* </div>
227+
* );
228+
* }
229+
* ```
230+
*
231+
* @example Custom characters
232+
* ```tsx
233+
* // ASCII-only style
234+
* getTreeLinePrefix(node, { last: "`- ", middle: "|- ", pipe: "|", blank: " " })
235+
* ```
236+
*/
237+
export function getTreeLinePrefix(
238+
node: NodeApi<any>,
239+
chars: Partial<TreeLineChars> = {}
240+
): string {
241+
const c = { ...defaultTreeLineChars, ...chars };
242+
if (node.level === 0) return "";
243+
244+
const isLast = node.nextSibling === null;
245+
let prefix = isLast ? c.last : c.middle;
246+
247+
let ancestor = node.parent;
248+
while (ancestor && ancestor.level > 0) {
249+
const isAncestorLast = ancestor.nextSibling === null;
250+
prefix = (isAncestorLast ? c.blank : c.pipe) + prefix;
251+
ancestor = ancestor.parent;
252+
}
253+
254+
return prefix;
255+
}
256+
176257
export function getInsertParentId(tree: TreeApi<any>) {
177258
const focus = tree.focusedNode;
178259
if (!focus) return null;

0 commit comments

Comments
 (0)