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/base

v1.0.23

Published

`@apigo.cc/base` 是基于 `@apigo.cc/state` 和 Bootstrap 5.3 的同步组件层。页面只需要按顺序引入脚本,就能在首屏完成 DOM 处理和组件渲染。

Readme

@apigo.cc/base

@apigo.cc/base 是基于 @apigo.cc/state 和 Bootstrap 5.3 的同步组件层。页面只需要按顺序引入脚本,就能在首屏完成 DOM 处理和组件渲染。

目录

依赖加载

按顺序同步加载:

@apigo.cc/state -> @apigo.cc/bootstrap -> @apigo.cc/base

base 会直接使用来自 @apigo.cc/state 的全局对象 StateHashLocalStorage

推荐加载顺序

<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>

全局变量

| 变量 | 说明 | |---|---| | HTTP | 请求工具 | | UI | 提示、确认、对话框工具 | | AutoForm | 自动表单容器和扩展注册器 | | VirtualScroll | 虚拟滚动工具 | | MouseMover | 鼠标拖拽位移工具 |

HTTP

HTTP 只处理请求,不处理 UI。

方法

| 方法 | 写法示例 | 说明 | |---|---|---| | HTTP.request(options) | await HTTP.request({ url: '/api/user', method: 'POST', data: { id: 1 } }) | 通用请求入口 | | HTTP.get(...) | await HTTP.get({ url: '/api/user/1' }) | GET | | HTTP.post(...) | await HTTP.post({ url: '/api/user', data: form }) | POST | | HTTP.put(...) | await HTTP.put({ url: '/api/user/1', data: patch }) | PUT | | HTTP.delete(...) | await HTTP.delete({ url: '/api/user/1' }) | DELETE | | HTTP.head(...) | await HTTP.head({ url: '/api/user/1' }) | HEAD |

HTTP.request 参数

| 参数 | 类型 | 写法示例 | 说明 | |---|---|---|---| | url | string | url: '/api/user' | 必填 | | method | string | method: 'POST' | 默认 POST,内部会转大写 | | data | any | data: { name: 'Alice' } | 请求体 | | headers | object | headers: { Authorization: 'Bearer xxx' } | 请求头 | | responseType | string | responseType: 'json' | 可手动指定 | | timeout | number | timeout: 5000 | 默认 10000 ms |

HTTP 对象属性

| 属性 | 写法示例 | 说明 | |---|---|---| | keepHeaders | HTTP.keepHeaders = ['Session-Id'] | 需要跨请求保留的响应头名 |

Content-Type 规则

  • data 是普通对象时,默认按 JSON 发送,并自动补 Content-Type: application/json
  • dataFormData 时,不手动设置 Content-Type,让浏览器生成带 boundary 的 multipart 请求头
  • 如果你在 headers 里手动传了 Content-Type,普通对象请求会按你的设置发送;FormData 仍然以浏览器生成的请求头为准

返回值

{
  ok,
  status,
  headers,
  responseType,
  result,
  error
}

推荐示例

<script>
  window.userRequest = {
    url: '/api/users/1',
    method: 'GET'
  }
  window.apiResponse = null
  window.apiResult = null
  window.user = null
</script>

<API id="userApi"
  auto
  $.request="window.userRequest"
  $onresponse="window.user = event.detail.result"
  $onerror="UI.toast(event.detail.message, { type: 'danger' })">
</API>

<button class="btn btn-primary" onclick="userApi.do({ noui: true })">Reload</button>

API

<API> 是把请求封装成页面节点的方式。

属性和状态

| 名称 | 写法示例 | 说明 | |---|---|---| | auto | <API auto> | 监听 request 变化后自动请求 | | request | $.request="window.userRequest" | 请求配置对象 | | response | $.response="window.apiResponse" | 最近一次请求状态 | | result | $.result="window.apiResult" | 响应结果缓存 |

request 默认结构

{
  url: '',
  method: 'GET',
  headers: {},
  data: null,
  timeout: 10000,
  responseType: ''
}

response 默认结构

{
  loading: false,
  ok: null,
  status: null,
  error: null,
  headers: {},
  responseType: '',
  result: null
}

方法

| 方法 | 写法示例 | 说明 | |---|---|---| | do(opt = {}) | await userApi.do() | 手动请求 | | opt.noui | await userApi.do({ noui: true }) | 失败时不弹 toast |

事件

| 事件 | 触发时机 | detail | |---|---|---| | response | 请求成功 | 完整响应对象 | | error | 请求失败或返回错误 | Error 对象 |

备注

  • auto 只在 request.url 存在时生效
  • request 深度变化会在微任务里触发一次请求,避免同一轮重复请求

AutoForm

<AutoForm> 通过 schema 生成表单,适合和 APIwindow 上的页面数据直接联动。

属性

| 名称 | 写法示例 | 说明 | |---|---|---| | vertical | <AutoForm vertical> | 垂直布局 | | horizontal | <AutoForm horizontal> | 强制左右两列布局 | | inline | <AutoForm inline> | 行内紧凑布局 | | nobutton | <AutoForm nobutton> | 隐藏底部提交按钮 | | api | $.api="saveApi" | 绑定 <API> 实例或其引用 | | submitlabel | submitlabel="Search" | 提交按钮文案,默认 {#Submit#} |

状态和方法

| 名称 | 写法示例 | 说明 | |---|---|---| | data | $.state.data="window.profile" | 表单数据,建议用 Object.assign 更新 | | schema | $.state.schema="window.profileSchema" | 表单定义 | | request | $.request="{ method: 'POST' }" | 默认提交配置 | | response | $.response="window.formResponse" | 最近一次提交结果 | | result | $.result="window.formResult" | 最近一次提交的 response.result | | submit(opt = {}) | form.submit() | 主动提交 |

schema 字段

| 字段 | 写法示例 | 说明 | |---|---|---| | name | name: 'role' | 必填,数据键名 | | label | label: 'Role' | 表单项标题 | | type | type: 'select' | 控件类型 | | setting | setting: { required: true } | 透传给控件的属性 | | options | options: ['A', 'B'] | select / checkbox / radio 选项 | | placeholder | placeholder: 'Please choose' | select 的空值占位文案 | | if | if: 'this.data.enable' | 条件渲染表达式 |

if 说明

  • if 不是字符串模板拼接,而是表达式
  • 表达式执行时可直接访问 thisStateHashLocalStorageitemdata
  • 这类条件是表单级动态渲染,适合做联动显隐

支持的 type

| type | 用途 | |---|---| | text | 单行文本 | | password | 密码 | | email | 邮箱 | | number | 数字 | | date | 日期 | | datetime | 日期时间 | | file | 文件 | | select | 下拉单选 | | checkbox | 多选 | | radio | 单选按钮组 | | switch | 开关 | | textarea | 多行文本 | | DatePicker | 扩展日期控件 | | ColorPicker | 扩展颜色控件 | | IconPicker | 扩展图标控件 | | TagsInput | 扩展标签控件 |

options 写法

['Admin', 'Editor']
[{ label: 'Admin', value: 'admin' }]

事件

| 事件 | 触发时机 | detail | |---|---|---| | submit | 表单校验通过、提交前 | 当前表单数据 | | response | 提交成功 | 完整响应对象 | | error | 提交失败 | Error 对象 |

插槽

| 插槽 | 位置 | 用途 | |---|---|---| | actions | 默认提交按钮左侧 | 自定义动作按钮 |

推荐示例

<script>
  window.profile = { name: 'Alice' }
  window.profileSchema = [
    { name: 'name', label: 'Name', type: 'text', setting: { required: true, placeholder: 'Enter name' } },
    { name: 'role', label: 'Role', type: 'select', options: [{ label: 'Admin', value: 'admin' }, { label: 'Editor', value: 'editor' }] },
    { name: 'active', label: 'Active', type: 'switch' },
    { name: 'birthday', label: 'Birthday', type: 'DatePicker', setting: { rangeEnd: 'birthdayEnd' } },
    { name: 'birthdayEnd', label: 'Birthday End', type: 'date' },
    { name: 'theme', label: 'Theme', type: 'ColorPicker' },
    { name: 'icon', label: 'Icon', type: 'IconPicker' },
    { name: 'tags', label: 'Tags', type: 'TagsInput' },
    { name: 'remark', label: 'Remark', type: 'textarea', if: 'this.data.active' }
  ]
</script>

<AutoForm vertical
  $.state.schema="window.profileSchema"
  $.state.data="window.profile"
  $onsubmit="UI.toast('submit: ' + JSON.stringify(event.detail))">
  <template slot="actions">
    <button type="button" class="btn btn-outline-secondary" onclick="Object.assign(window.profile, { name: 'Alice' })">Reset</button>
  </template>
</AutoForm>

编程注意

  • 不要直接整体覆盖 form.dataform.state.data
  • 需要更新时,优先用 Object.assign(form.data, nextData)

AutoForm.register(name, typeName = '')

注册可被 schema.type 识别的自定义控件。

| 参数 | 写法示例 | 说明 | |---|---|---| | name | AutoForm.register('MyPicker') | 组件标签名 | | typeName | AutoForm.register('MyPicker', 'picker') | 默认 ''。只有当标签名和 schema.type 不同,或需要复用同一个标签名匹配另一个 type 时才传 |

什么时候传 typeName

  • 你想让 <MyPicker> 处理 schema.type === 'picker'
  • 你想把一个标签名映射成不同的 type
  • 如果 schema.type 和标签名一致,直接省略第二个参数

表单扩展控件

这些控件既可以在 AutoForm 里作为 schema.type 使用,也可以单独放到页面上。单独使用时,直接绑定 value,再监听 change 即可。

<DatePicker>

| 项目 | 说明 | |---|---| | state.start | 起始日期 | | state.end | 结束日期 | | value | 映射到 state.start | | isRange | 是否启用范围模式 | | setting.rangeEnd | 范围结束字段名 | | rangeEnd | 组件属性形式的范围结束字段名 | | change | 开始日期变化时触发,detail 为开始日期字符串 |

<DatePicker> 示例

<script>
  window.rangeForm = {}
  window.rangeSchema = [
    { name: 'rangeStart', label: 'Range', type: 'DatePicker', setting: { rangeEnd: 'rangeEnd' } },
    { name: 'rangeEnd', label: 'Range End', type: 'date' }
  ]
</script>

<AutoForm $.state.schema="window.rangeSchema" $.state.data="window.rangeForm"></AutoForm>

<DatePicker> 单独使用

<script>
  window.birthDate = '2026-06-01'
</script>

<DatePicker $bind="window.birthDate"></DatePicker>

<ColorPicker>

| 项目 | 说明 | |---|---| | state.value | 颜色值,默认 #000000 | | value | 可读写,映射到 state.value | | change | 颜色变化时触发,detail 为颜色字符串 |

<ColorPicker> 示例

<script>
  window.colorForm = {}
  window.colorSchema = [
    { name: 'theme', label: 'Theme', type: 'ColorPicker' }
  ]
</script>

<AutoForm $.state.schema="window.colorSchema" $.state.data="window.colorForm"></AutoForm>

<IconPicker>

| 项目 | 说明 | |---|---| | state.value | 当前图标名 | | state.search | 搜索词 | | state.open | 下拉是否展开 | | filteredIcons | 过滤后的图标列表 | | value | 可读写,映射到 state.value | | change | 选择图标时触发,detail 为图标名 |

<IconPicker> 示例

<script>
  window.iconForm = {}
  window.iconSchema = [
    { name: 'icon', label: 'Icon', type: 'IconPicker' }
  ]
</script>

<AutoForm $.state.schema="window.iconSchema" $.state.data="window.iconForm"></AutoForm>

<TagsInput>

| 项目 | 说明 | |---|---| | state.tags | 标签数组 | | change | 标签变更时触发,detail 为最新数组 |

<TagsInput> 示例

<script>
  window.tagForm = {}
  window.tagsSchema = [
    { name: 'tags', label: 'Tags', type: 'TagsInput' }
  ]
</script>

<AutoForm $.state.schema="window.tagsSchema" $.state.data="window.tagForm"></AutoForm>

<TagsInput> 单独使用

<script>
  window.tags = ['Vue', 'AI']
</script>

<TagsInput $bind="window.tags"></TagsInput>

List

<List> 支持普通、分组、树形和虚拟滚动。它的重点不是“能渲染”,而是“能直接拿来绑定状态”。

属性

| 名称 | 写法示例 | 说明 | |---|---|---| | mode | <List mode="group"> | normal / group / tree,默认 normal | | fast | <List fast> | 开启虚拟滚动。任意模式都能用 | | collapsible | <List collapsible> | 树形模式下允许折叠 | | auto-select | <List auto-select> | 点击项后把当前项 id 写回 selectedItem | | auto-select-group | <List auto-select-group> | 点击分组后把当前分组 id 写回 selectedGroup |

状态

| 名称 | 说明 | |---|---| | list | 原始列表 | | groups | 分组列表,group 模式使用 | | filter | 字符串或对象筛选条件 | | order | 排序规则,例如 name-namename desc | | selectedItem | 当前选中的项 id | | selectedGroup | 当前选中的分组 id | | renderedList | 当前实际渲染的列表片段 | | flatList | 逻辑处理后的扁平列表 | | collapsed | 树形折叠状态 |

实例属性

| 名称 | 写法示例 | 说明 | |---|---|---| | filterFunc | list.filterFunc = item => item.active | 自定义过滤函数 | | orderFunc | list.orderFunc = (a, b) => a.name.localeCompare(b.name) | 自定义排序函数 |

事件

| 事件 | 触发时机 | detail | |---|---|---| | change | 选中项变化 | selectedItem | | itemclick | 点击普通项 | { item, index } | | groupclick | 点击分组项 | { item, index } |

绑定习惯

  • 推荐配合 $bind 保存当前选中 id,比如 $bind="window.currentUserId"
  • 如果你只关心点击事件,也可以不绑,直接监听 itemclick / groupclick

插槽

| 插槽 | 说明 | |---|---| | item | 单项自定义内容 | | item-actions | 单项右侧动作区 | | group-actions | 分组右侧动作区 |

字段默认映射

不写映射属性时,默认字段名就是这些:

| 字段 | 默认值 | |---|---| | idfield | id | | labelfield | label | | summaryfield | summary | | groupidfield | id | | grouplabelfield | label | | groupsummaryfield | summary | | groupfield | group | | parentfield | parent | | groupicon | folder | | itemicon | file |

推荐示例

普通列表 + $bind

<script>
  window.currentUserId = null
  window.userList = [
    { id: '1', label: 'Alice', summary: 'Admin' },
    { id: '2', label: 'Bob', summary: 'Editor' }
  ]
</script>

<List class="h-100 overflow-auto" auto-select
  $.state.list="window.userList"
  $bind="window.currentUserId"
  $onitemclick="UI.toast('item: ' + event.detail.item.label)">
  <template slot="item">
    <span $text="item.label"></span>
    <span class="ms-auto text-muted small" $text="item.summary"></span>
  </template>
</List>

分组列表

<script>
  window.groups = [
    { id: 'team-a', label: 'Team A', summary: '2 people' },
    { id: 'team-b', label: 'Team B', summary: '1 person' }
  ]

  window.members = [
    { id: '1', label: 'Alice', summary: 'Admin', group: 'team-a' },
    { id: '2', label: 'Bob', summary: 'Editor', group: 'team-b' }
  ]
</script>

<List mode="group" class="h-100 overflow-auto" auto-select-group
  $.state.list="window.members"
  $.state.groups="window.groups"
  $ongroupclick="UI.toast('group: ' + event.detail.item.label)">
  <template slot="item">
    <span $text="item.label"></span>
    <span class="ms-auto text-muted small" $text="item.summary"></span>
  </template>
</List>

树形列表

<script>
  window.treeList = [
    { id: '1', label: 'Root', parent: '' },
    { id: '2', label: 'Child A', parent: '1' },
    { id: '3', label: 'Child B', parent: '1' }
  ]
</script>

<List mode="tree" collapsible class="h-100 overflow-auto"
  $.state.list="window.treeList">
</List>

虚拟滚动

<script>
  window.bigList = Array.from({ length: 2000 }, (_, i) => ({
    id: String(i + 1),
    label: 'Row ' + (i + 1),
    summary: 'virtual row'
  }))
</script>

<List fast class="h-100 overflow-auto"
  $.state.list="window.bigList">
</List>

行为说明

  • filter 是字符串时,同时匹配 labelsummary
  • filter 是对象时,按字段模糊匹配
  • tree 模式会自动保留命中的祖先链
  • fast 模式要求外层容器有 overflow-auto
  • 数据项可加 _itemHeight 优化虚拟滚动测量

Nav

<Nav> 默认是水平导航。需要侧边栏时才加 vertical

属性

| 名称 | 写法示例 | 说明 | |---|---|---| | vertical | <Nav vertical> | 垂直侧边栏 |

状态

| 名称 | 说明 | |---|---| | brand | { image, icon, label } | | list | 导航项数组 | | value | 当前选中项 | | openName | 当前展开的下拉项 |

导航项结构

| 字段 | 说明 | |---|---| | type | button / dropdown / label / fill | | name | 导航项标识 | | label | 文本 | | icon | Bootstrap Icons 名称 | | class | 额外 class | | noselect | 选中时不写回 value | | width | 仅 dropdown 在水平模式下控制下拉宽度 | | list | dropdown 的子项 |

子项结构

dropdown.list 内的子项支持:

  • type: 'button'
  • type: 'label'
  • type: 'switch'

switch 子项需要:

  • bind: 一个状态对象
  • name: 写回的字段名

事件

| 事件 | 触发时机 | detail | |---|---|---| | change | 选中项变化 | 当前 name | | nav | 点击或展开时 | { item }{ item, open } |

推荐示例

<script>
  window.currentNav = 'users'
  window.brand = { icon: 'cpu', label: 'Admin' }
  window.navSettings = { darkMode: false }
  window.navList = [
    { type: 'button', name: 'users', label: 'Users', icon: 'people' },
    { type: 'dropdown', name: 'settings', label: 'Settings', icon: 'gear', list: [
      { type: 'button', name: 'profile', label: 'Profile', icon: 'person' },
      { type: 'switch', name: 'darkMode', label: 'Dark Mode', icon: 'moon', bind: window.navSettings }
    ]}
  ]
</script>

<Nav class="border-bottom"
  $.state.brand="window.brand"
  $.state.list="window.navList"
  $bind="window.currentNav"
  $onnav="UI.toast(event.detail.item.label)">
</Nav>

备注

  • 水平模式是默认推荐形态
  • 需要左侧栏时再加 vertical
  • list 推荐在 JS 里单独定义,不要硬塞成一长串 HTML 字面量
  • 如果需要同步当前路由或菜单名,可以再加 $bind

Modal / Dialog

<Modal>

| 名称 | 写法示例 | 说明 | |---|---|---| | show() | editModal.show() | 打开弹窗 | | hide() | editModal.hide() | 关闭弹窗 | | change | @change | 关闭时派发 false |

<Modal> 的插槽

| 插槽 | 说明 | |---|---| | header | 弹窗头部 | | body | 弹窗主体 | | footer | 弹窗底部 |

<Modal> 示例

<Modal id="editModal">
  <div slot="header">Edit User</div>
  <div slot="body">
    <AutoForm nobutton inline $.state.schema="window.profileSchema" $.state.data="window.profile"></AutoForm>
  </div>
  <div slot="footer">
    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
  </div>
</Modal>

<button class="btn btn-primary" onclick="editModal.show()">Open</button>

<Dialog>

<Dialog> 是基于 Modal 的消息确认弹窗,平时直接用 UI.showDialog 更快。

UI.showDialog

| 参数 | 写法示例 | 说明 | |---|---|---| | title | title: 'Delete' | 标题 | | message | message: 'Are you sure?' | 内容 | | buttons | buttons: ['Cancel', 'Confirm'] | 按钮文本数组 | | type | type: 'danger' | 样式类型 |

UI.showDialog 示例

const ok = await UI.confirm('Delete it?', { type: 'danger' })
if (ok) {
  UI.toast('deleted', { type: 'success' })
}

Resizer

<Resizer> 默认调整前一个元素的宽度。需要别的目标时再传 target

属性

| 名称 | 写法示例 | 说明 | |---|---|---| | vertical | <Resizer vertical> | 调整高度,不是宽度 | | min | min="180" | 最小像素,默认 10 | | max | max="420" | 最大像素,默认 1000 | | target | target="sidebar" | 要调整的元素;不写时默认前一个兄弟节点 |

事件

| 事件 | detail | 说明 | |---|---|---| | resizing | { oldSize, newSize } | 拖动中持续触发 | | resize | { oldSize, newSize } | 拖动结束触发 | | change | newSize | 拖动结束触发,适合写回状态 |

推荐示例

默认用法

<div id="sidebar" style="width: 280px"></div>
<Resizer min="180" max="420"></Resizer>

指定 target

<div id="sidebar" style="width: 280px"></div>
<Resizer target="sidebar" min="180" max="420"></Resizer>

UI

UI.toast(message, options)

| 参数 | 写法示例 | 说明 | |---|---|---| | message | UI.toast('Saved') | 提示文案 | | type | UI.toast('Saved', { type: 'success' }) | 主题类型 | | delay | UI.toast('Saved', { delay: 2000 }) | 自动关闭延迟 | | buttons | UI.toast('Confirm?', { buttons: ['Cancel', 'Confirm'] }) | 底部按钮 | | container | UI.toast('...', { container: 'right-top' }) | 自定义容器 |

UI.toastConfirm(message, options)

返回 Promise<boolean>

UI.alert(message, options)

await UI.alert('Saved')

UI.confirm(message, options)

const ok = await UI.confirm('Delete it?')

UI.showDialog(options)

await UI.showDialog({
  title: 'Confirm',
  message: 'Do you want to continue?',
  buttons: ['Cancel', 'Confirm'],
  type: 'primary'
})

VirtualScroll / MouseMover

VirtualScroll(options = {})

参数:

| 参数 | 写法示例 | 说明 | |---|---|---| | itemHeight | VirtualScroll({ itemHeight: 40 }) | 默认行高预估值 |

返回值:

| 方法 | 说明 | |---|---| | reset(list, container) | 重置缓存并返回初始可见列表 | | init(list, refreshCallback) | 初始化高度缓存 | | update(index, node) | 记录节点实际高度 | | calc(container, list) | 计算当前需要渲染的片段 |

MouseMover.start(event, handlers)

MouseMover.start(event, {
  onmousemove: ({ w, h }) => {},
  onmouseup: ({ w, h }) => {}
})

说明

  • VirtualScroll 主要给 ListDataTable
  • MouseMover 主要给 Resizer