npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-cascading-input

v0.1.0

Published

A headless React component for cascading tree-style input with canvas connection lines

Readme

react-cascading-input

一个 Headless 的 React 级联输入组件,通过 Canvas 在父子层级间绘制关系线(贝塞尔曲线 / 折线),支持任意嵌套层级。

特性

  • 🎨 Canvas 关系线 — 自动在父子层级间绘制连接线,支持贝塞尔曲线和折线两种样式,可自定义颜色与粗细
  • 🧩 Headless 架构 — 每列通过 render prop 完全自定义渲染,自由集成任意 UI 库
  • 📦 零依赖 — 核心无第三方依赖,体积轻量
  • 无闪烁重绘 — 使用 useLayoutEffect + ResizeObserver,数据变更时关系线无感知更新
  • 🔧 TypeScript — 完整类型支持
  • 🎯 React 18+ — 兼容 React 18 / 19

安装

npm install react-cascading-input
# 或
pnpm add react-cascading-input

基础用法

renderColumnConfig 的必填字段,由你决定每列渲染什么控件:

import { useState } from 'react';
import { CascadingInput } from 'react-cascading-input';
import 'react-cascading-input/styles';
import type { ColumnConfig } from 'react-cascading-input';

const columns: ColumnConfig[] = [
    {
        title: '训练任务',
        dataIndex: 'product',
        width: 120,
        hasAdd: true,
        render: ({ value, onChange, title }) => (
            <input
                placeholder={`请输入${title}`}
                value={value}
                onChange={(e) => onChange(e.target.value)}
            />
        ),
    },
    {
        title: '训练集群',
        dataIndex: 'region',
        width: 120,
        hasAdd: true,
        render: ({ value, onChange, title }) => (
            <input
                placeholder={`请输入${title}`}
                value={value}
                onChange={(e) => onChange(e.target.value)}
            />
        ),
    },
    {
        title: '框架版本',
        dataIndex: 'spec',
        width: 180,
        hasAdd: false,
        render: ({ value, onChange, title }) => (
            <input
                placeholder={`请输入${title}`}
                value={value}
                onChange={(e) => onChange(e.target.value)}
            />
        ),
    },
];

function App() {
    const [value, setValue] = useState([]);
    return <CascadingInput columns={columns} value={value} onChange={setValue} />;
}

自定义渲染

通过 render 可以渲染任意控件,比如 select,亦或者第三方 UI 库中的组件。

import type { ColumnConfig } from 'react-cascading-input';

const columns: ColumnConfig[] = [
    {
        title: '训练任务',
        dataIndex: 'product',
        width: 160,
        hasAdd: true,
        render: ({ value, onChange, title }) => (
            <input
                value={value}
                onChange={(e) => onChange(e.target.value)}
                placeholder={`请输入${title}`}
            />
        ),
    },
    {
        title: '训练集群',
        dataIndex: 'region',
        width: 140,
        hasAdd: true,
        render: ({ value, onChange, title }) => (
            <select
                value={value || ''}
                onChange={(e) => onChange(e.target.value)}
                style={{ width: '100%' }}
            >
                <option value="">请选择{title}</option>
                <option value="beijing">北京</option>
                <option value="shanghai">上海</option>
                <option value="guangzhou">广州</option>
            </select>
        ),
    },
];

连线样式

所有连线相关配置通过 line prop 统一管理:

{/* 折线风格 */}
<CascadingInput columns={columns} value={value} onChange={setValue} line={{ style: 'straight' }} />

{/* 自定义连线颜色和粗细 */}
<CascadingInput
    columns={columns}
    value={value}
    onChange={setValue}
    line={{ color: '#1890ff', width: 2 }}
/>

{/* 开启溯源动画 */}
<CascadingInput
    columns={columns}
    value={value}
    onChange={setValue}
    line={{ showSource: true }}
/>

API

CascadingInput Props

| Prop | Type | Default | Description | | ----------- | ----------------------------- | ----------- | ---------------- | | columns | ColumnConfig[] | — | 列配置(必填) | | value | TreeNode[] | [] | 树数据(受控) | | onChange | (value: TreeNode[]) => void | — | 数据变更回调 | | line | LineConfig | {} | 连线配置 |

LineConfig

| Property | Type | Default | Description | | ------------ | --------------------------------------- | ----------- | ------------------------------------ | | style | 'straight' \| 'curve' | 'curve' | 连线风格 | | color | string | '#d9d9d9' | 连线颜色 | | width | number | 1.5 | 连线粗细 | | showSource | boolean \| SourceAnimationOptions | false | 溯源动画,粒子从子节点流向父节点 |

SourceAnimationOptions

line.showSource 设为对象时可自定义粒子动画:

| Property | Type | Default | Description | | ----------------- | -------- | --------------- | ---------------- | | color | string | 跟随 line.color | 粒子颜色 | | minRadius | number | 2.5 | 粒子最小半径 | | maxRadius | number | 4 | 粒子最大半径 | | breatheCycle | number | 400 | 呼吸周期(ms) | | breatheAmplitude| number | 1 | 呼吸幅度 0~1 | | speed | number | 0.004 | 动画速度 |

ColumnConfig

| Property | Type | Required | Description | | ----------- | --------------------------------------- | -------- | ------------------------------------ | | title | string | ✓ | 列标题 | | dataIndex | string | ✓ | 数据字段名 | | width | number | ✓ | 列宽度(px) | | hasAdd | boolean | ✓ | 是否显示"添加"按钮 | | render | (props: CellRenderProps) => ReactNode | ✓ | 自定义单元格渲染 | | addRender | (props: ActionRenderProps) => ReactNode | | 自定义"添加"按钮渲染,位置固定在单元格下方 | | deleteRender | (props: ActionRenderProps) => ReactNode | | 自定义"删除"按钮渲染(仅叶子层级),位置固定在行末尾 |

CellRenderProps

| Property | Type | Description | | ----------- | --------------------------- | ---------------------------- | | value | string | 当前单元格值 | | onChange | (val: string) => void | 值变更回调 | | node | TreeNode | 当前树节点,可访问 node.children 等 | | level | number | 当前层级索引(0 开始) | | dataIndex | string | 数据字段名 | | title | string | 列标题 | | onAdd | () => void | 添加同级节点回调 | | onDelete | () => void | 删除节点回调(仅叶子层级) | | isLeaf | boolean | 是否为叶子层级 | | width | number | 列宽度(px) |

TreeNode

| Property | Type | Description | | --------------- | ------------ | ---------------------------------------- | | id | string | 唯一标识 | | children | TreeNode[] | 子节点列表 | | [key: string] | any | 由 ColumnConfig.dataIndex 决定的动态字段 |

LineStyle

type LineStyle = 'straight' | 'curve';

开发

# 安装依赖
pnpm install

# 启动文档开发服务器(含在线演示)
pnpm dev

# 运行测试
pnpm test

# 构建
pnpm build

License

MIT