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

@apigo.cc/datatable

v1.0.16

Published

`@apigo.cc/datatable` 是建立在 `@apigo.cc/base` 上的同步数据表格组件,重点不是“能显示表格”,而是“能直接被页面首屏加载并完成渲染、筛选、编辑和保存”。

Readme

@apigo.cc/datatable

@apigo.cc/datatable 是建立在 @apigo.cc/base 上的同步数据表格组件,重点不是“能显示表格”,而是“能直接被页面首屏加载并完成渲染、筛选、编辑和保存”。

目录

先看依赖

datatable 依赖 statebootstrapbase,加载顺序仍然必须是同步的。

推荐加载顺序

<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/[email protected]/dist/state.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/[email protected]/dist/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/[email protected]/dist/base.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/[email protected]/dist/datatable.min.js"></script>

全局对象

DataTable

DataTable 是本包挂到全局的对象。

<script>
  console.log(DataTable.getFieldTypes())
</script>

方法

| 方法 | 说明 | |---|---| | registerFieldType(config) | 注册字段类型 | | getFieldTypes() | 获取当前所有已注册字段类型 |

<DataTable>

<DataTable> 负责渲染、编辑、筛选、排序和虚拟滚动。内部会用到 <AutoForm>, <Modal>, <Resizer>,这些都来自 base

绑定方式

| 名称 | 写法示例 | 说明 | |---|---|---| | editable | <DataTable editable> | 开启编辑、删除、字段管理、保存按钮 | | $.state.fields | $.state.fields="window.fields" | 字段定义数组 | | $.state.list | $.state.list="window.list" | 数据行数组 |

事件

| 事件 | 触发时机 | detail | |---|---|---| | save | 点击底部保存按钮 | { list, fields } | | savefields | 字段新增 / 编辑 / 删除后 | fields | | remove | 删除行后 | { items } |

常用状态

| 名称 | 说明 | |---|---| | editable | 是否开启编辑模式 | | list | 当前生效的数据列表,排序和筛选后会更新这里 | | fields | 字段定义数组 | | _renderedList | 当前 DOM 实际渲染的片段 | | prevHeight | 虚拟滚动顶部占位高度 | | postHeight | 虚拟滚动底部占位高度 | | _listStartIndex | 当前渲染片段在原列表中的起始索引 | | selectedRowCount | 当前选中行数 | | _originalList | 原始数据快照,排序 / 筛选前的数据源 | | sortConfig | { fieldId, direction } | | filterConfig | { [fieldId]: { mode, value, value2, selectedValues } } | | activeFieldId | 当前打开列菜单的字段 id | | activeField | 当前打开列菜单的字段对象 | | activeModes | 当前列允许的筛选模式 | | _columnStats | 当前列高频值统计 | | _internalUpdate | 内部同步标记,避免列表回写时触发二次筛选 | | _appliedHash | 内部哈希缓存 | | _fieldsDirty | 字段变更标记 | | _masterCellNodes | 用于动态重建列模板的内部缓存 | | isDirty | 是否存在未保存修改 | | isBulkEdit | 是否处于批量编辑状态 |

方法

| 方法 | 写法示例 | 说明 | |---|---|---| | addRow() | table.addRow() | 新增一行空数据 | | deleteSelectedRow() | table.deleteSelectedRow() | 删除当前选区对应的行 | | saveChanges() | table.saveChanges() | 派发 save 事件 | | addField() | table.addField() | 新增字段 | | editField() | table.editField() | 编辑当前打开菜单的字段 | | deleteField() | table.deleteField() | 删除当前打开菜单的字段 | | showColumnMenu(field, event) | table.showColumnMenu(field, event) | 打开列菜单 | | hideColumnMenu() | table.hideColumnMenu() | 关闭列菜单 | | editCell(row, field, cellNode) | table.editCell(row, field, cellNode) | 打开单元格编辑器 | | onScroll() | table.onScroll() | 滚动时刷新虚拟列表 |

推荐示例

<script>
  window.fields = [
    { id: 'id', name: 'ID', type: 'string', settings: { width: 70, formType: 'text', pinned: 'left' } },
    { id: 'name', name: 'Name', type: 'string', settings: { width: 160, formType: 'text', pinned: 'left' } },
    { id: 'role', name: 'Role', type: 'string', settings: { width: 120, formType: 'select', options: [{ label: 'Admin', value: 'admin' }, { label: 'Editor', value: 'editor' }] } },
    { id: 'active', name: 'Active', type: 'boolean', settings: { width: 90, formType: 'switch', labelOn: 'YES', labelOff: 'NO' } },
    { id: 'score', name: 'Score', type: 'number', settings: { width: 120, formType: 'number', prefix: '$', decimals: 2, thousandSep: true } },
    { id: 'tags', name: 'Tags', type: 'object', settings: { width: 180, formType: 'TagsInput' } },
    { id: 'created', name: 'Created', type: 'date', settings: { width: 140, formType: 'date' } },
    { id: 'actions', name: 'Actions', type: 'string', settings: { width: 120, formType: 'text', pinned: 'right' }, formatter: () => 'Edit' }
  ]

  window.list = [
    { id: 1, name: 'Alice', role: 'admin', active: true, score: 12800.5, tags: ['Vue', 'AI'], created: '2026-06-01', actions: '' },
    { id: 2, name: 'Bob', role: 'editor', active: false, score: 980.25, tags: ['Docs'], created: '2026-06-02', actions: '' }
  ]

  DataTable.registerFieldType({
    value: 'TagsInput',
    label: '{#Tags#}',
    typeForDB: 'v4096',
    schema: [{ name: 'placeholder', label: 'Placeholder', type: 'text' }],
    formatter: val => Array.isArray(val) ? val.join(', ') : val
  })

</script>

<div style="height: 600px" class="border rounded overflow-hidden bg-body">
  <DataTable editable
    $.state.fields="window.fields"
    $.state.list="window.list"
    $onsave="UI.toast('save: ' + JSON.stringify(event.detail.list))"
    $onsavefields="UI.toast('fields changed')"
    $onremove="UI.toast('removed: ' + event.detail.items.length)">
  </DataTable>
</div>

字段定义

fields 是表格最重要的配置。每个字段都建议写完整,不要只写一个 id 就了事。

字段结构

| 字段 | 写法示例 | 说明 | |---|---|---| | id | id: 'name' | 列唯一标识,同时也是数据行中的字段名 | | name | name: 'Name' | 表头显示名 | | type | type: 'string' | 列的原始类型 | | memo | memo: 'User name' | 字段备注,字段编辑器可见 | | isIndex | isIndex: true | 是否索引字段 | | settings | settings: { width: 160, formType: 'text' } | 编辑与展示配置 | | formatter | formatter: v => String(v) | 自定义显示格式 |

settings 字段

| 字段 | 写法示例 | 说明 | |---|---|---| | width | width: 160 | 列宽,默认 150 | | pinned | pinned: 'left' | 固定列,leftright | | formType | formType: 'select' | 编辑器类型 | | options | options: [{ label: 'A', value: 'a' }] | 选择类选项 | | decimals | decimals: 2 | 数字小数位 | | prefix | prefix: '$' | 数字前缀 | | suffix | suffix: '%' | 数字后缀 | | thousandSep | thousandSep: true | 数字千分位 | | labelOn | labelOn: 'YES' | switch 为真时显示 | | labelOff | labelOff: 'NO' | switch 为假时显示 | | format | format: 'YYYY-MM-DD' | 日期时间格式说明 | | placeholder | placeholder: 'Enter value' | 文本占位符 |

字段示例

{
  id: 'score',
  name: 'Score',
  type: 'number',
  settings: {
    width: 120,
    formType: 'number',
    decimals: 2,
    prefix: '$',
    thousandSep: true
  },
  formatter: value => value == null ? '' : String(value)
}

内置编辑能力

单元格编辑支持的 formType

editCell() 会把 settings.formType || type || 'text' 交给 AutoForm,因此这些类型都能直接编辑:

  • text
  • number
  • select
  • checkbox
  • radio
  • switch
  • date
  • datetime
  • textarea
  • TagsInput
  • DatePicker
  • ColorPicker
  • IconPicker

字段编辑器默认可选类型

字段管理弹窗里,默认由 DataTable.getFieldTypes() 提供的类型是:

  • text
  • number
  • select
  • checkbox
  • switch
  • datetime
  • textarea

重要区别

  • dateradio 这些类型在单元格编辑里是可用的
  • 但默认字段编辑器列表里不一定出现
  • 如果你要让它们出现在字段管理弹窗里,直接用 registerFieldType() 补进去

默认字段类型的格式化规则

| 类型 | 显示规则 | |---|---| | text | 原样字符串化 | | number | 支持 decimalsprefixsuffixthousandSep | | select | 显示匹配项的 label | | checkbox | 把多选项 label 用逗号拼接 | | switch | true 显示 labelOnYesfalse 显示 labelOffNo | | datetime | 默认字符串化 | | textarea | 默认字符串化 |

筛选、排序、固定列

排序

  • 点击表头右侧下拉按钮
  • 选择 ASCDESC
  • 排序状态写在 state.sortConfig

筛选

列菜单支持三类筛选:

  • 文本类:contains, equals, starts, ends
  • 数字类:=, >, <, between
  • 值列表:直接勾选统计出来的高频值

支持的列筛选模式

| formType | 可用模式 | |---|---| | text | contains, equals, starts, ends | | textarea | contains, equals, starts, ends | | number | =, >, <, between | | date | =, >, <, between | | select | contains, equals | | TagsInput | contains, equals, starts, ends | | switch, checkbox, radio | 选项勾选,不走文本筛选模式 |

固定列

字段上设置:

settings: { pinned: 'left' }

或:

settings: { pinned: 'right' }

就会固定在对应侧边。

复制粘贴与批量编辑

选择

  • 鼠标拖选单元格区域
  • selectedRowCount 会同步更新
  • 选区会用于复制、粘贴和批量编辑

复制

  • Ctrl/Cmd + C 把选区复制成 TSV
  • 带引号、换行、制表符的值会自动转义

粘贴

  • Ctrl/Cmd + V 会把剪贴板 TSV 回填到选区
  • 单格选中时会把后续数据按行列铺开

批量编辑

  • 在已选中的区域内双击一个单元格
  • 如果选区跨多行,会进入批量编辑
  • 保存时会把新值写入范围内每一行

自定义字段类型

DataTable.registerFieldType(config)

DataTable.registerFieldType({
  value: 'MyTags',
  label: '{#Tags#}',
  typeForDB: 'v4096',
  schema: [
    { name: 'placeholder', label: 'Placeholder', type: 'text' }
  ],
  formatter: val => Array.isArray(val) ? val.join(' | ') : val
})

schema 说明

schema 的格式和 base 里的 AutoForm.schema 一样,所以这里也能用:

  • name
  • label
  • type
  • setting
  • options
  • if

适合注册的场景

  • 你要让字段编辑器认识一个新的 formType
  • 你要给某种值定义专用 formatter
  • 你要把扩展控件复用到表格内联编辑里

推荐示例

可编辑表格

<script>
  window.fields = [
    { id: 'id', name: 'ID', type: 'string', settings: { width: 70, formType: 'text', pinned: 'left' } },
    { id: 'name', name: 'Name', type: 'string', settings: { width: 160, formType: 'text', pinned: 'left' } },
    { id: 'role', name: 'Role', type: 'string', settings: { width: 120, formType: 'select', options: [{ label: 'Admin', value: 'admin' }, { label: 'Editor', value: 'editor' }] } },
    { id: 'active', name: 'Active', type: 'boolean', settings: { width: 90, formType: 'switch', labelOn: 'YES', labelOff: 'NO' } },
    { id: 'score', name: 'Score', type: 'number', settings: { width: 120, formType: 'number', prefix: '$', decimals: 2, thousandSep: true } },
    { id: 'created', name: 'Created', type: 'date', settings: { width: 140, formType: 'date' } }
  ]

  window.list = [
    { id: 1, name: 'Alice', role: 'admin', active: true, score: 12800.5, created: '2026-06-01' },
    { id: 2, name: 'Bob', role: 'editor', active: false, score: 980.25, created: '2026-06-02' }
  ]
</script>

<div style="height: 560px" class="border rounded overflow-hidden">
  <DataTable editable
    $.state.fields="window.fields"
    $.state.list="window.list"
    $onsave="UI.toast('save')"
    $onremove="UI.toast('remove: ' + event.detail.items.length)">
  </DataTable>
</div>

只读大表 + 虚拟滚动

<script>
  window.fields = [
    { id: 'id', name: 'ID', type: 'string', settings: { width: 70, formType: 'text', pinned: 'left' } },
    { id: 'name', name: 'Name', type: 'string', settings: { width: 160, formType: 'text', pinned: 'left' } },
    { id: 'role', name: 'Role', type: 'string', settings: { width: 120, formType: 'select', options: [{ label: 'Admin', value: 'admin' }, { label: 'Editor', value: 'editor' }] } },
    { id: 'active', name: 'Active', type: 'boolean', settings: { width: 90, formType: 'switch', labelOn: 'YES', labelOff: 'NO' } },
    { id: 'score', name: 'Score', type: 'number', settings: { width: 120, formType: 'number', prefix: '$', decimals: 2, thousandSep: true } },
    { id: 'created', name: 'Created', type: 'date', settings: { width: 140, formType: 'date' } }
  ]

  window.bigList = Array.from({ length: 2000 }, (_, i) => ({
    id: i + 1,
    name: 'User ' + (i + 1),
    role: i % 2 ? 'admin' : 'editor',
    active: i % 3 === 0,
    score: Math.round(Math.random() * 10000) / 100,
    created: '2026-06-01'
  }))
</script>

<div style="height: 560px" class="border rounded overflow-hidden">
  <DataTable
    $.state.fields="window.fields"
    $.state.list="window.bigList">
  </DataTable>
</div>

自定义字段类型

<script>
  DataTable.registerFieldType({
    value: 'TagsInput',
    label: '{#Tags#}',
    typeForDB: 'v4096',
    schema: [{ name: 'placeholder', label: 'Placeholder', type: 'text' }],
    formatter: val => Array.isArray(val) ? val.join(', ') : val
  })

  window.fields = [
    { id: 'tags', name: 'Tags', type: 'object', settings: { width: 180, formType: 'TagsInput' } }
  ]

  window.list = [
    { id: 1, tags: ['Vue', 'AI'] }
  ]
</script>

<DataTable $.state.fields="window.fields" $.state.list="window.list"></DataTable>

备注

  • 字段定义和数据列表建议放在 JS 里
  • 外层容器最好明确给高度
  • 需要保存时,监听 save;需要字段变更时,监听 savefields