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

@trustbaseai/shop-ui

v0.2.0

Published

TrustBase 店铺界面组件(框架无关 Web Components):商品列表 / 商品详情与规格选择 / 下单表单 / 我的账户;样式只引用 design tokens,零运行时依赖,不持私钥不签名

Readme

@trustbaseai/shop-ui

TrustBase 店铺界面组件 —— 框架无关的 Web Components,四个最小元素: 商品列表 / 商品详情(规格选择)/ 下单表单 / 我的账户。

  • 零运行时依赖:自己写 Custom Elements,不引任何第三方 UI 框架/库(连 @trustbase/shop 都不 import)。
  • 样式只引用 design tokens(src/tokens.ts 是唯一允许出现视觉字面量的文件)。
  • 不持私钥、不签名:组件只渲染 + 抛事件;下单只产出"意愿 + memo"。
  • 移动优先:默认 375 宽可用(demo/index.html 就是按 375 做的)。
  • 状态:已实现并测试通过(122 个用例);未验证/未做的清单见 §7。

1. 组件契约(元素名/属性/事件都是冻结的)

<tb-goods-list> — 商品列表

| 入参 data-* | 说明 | |---|---| | data-goods | JSON 数组:[{ goods_id, seller?, title, summary?, image?, status?, skus: [{ sku_id, title, price_per_unit, stock, spec? }] }] | | data-state | loading / error / empty / ready(可选,不写按数据推断) | | data-error | 错误文案(data-state="error" 时显示) |

| 出参事件 | detail | |---|---| | tb:goods-select | { goods } —— 被点的商品原样(宿主据此打开详情) |

<tb-goods-detail> — 详情 + 规格选择

| 入参 data-* | 说明 | |---|---| | data-goods | JSON 对象(单个商品,结构同上) | | data-sku-id | 预选规格(可选;缺省 = 有货且最便宜的) | | data-quantity | 初始数量(可选,默认 1,上限 = 库存) | | data-state / data-error | 同上 |

| 出参事件 | detail | |---|---| | tb:sku-change | { goods_id, sku_id, sku, quantity } | | tb:goods-buy | { goods_id, seller, sku_id, sku, quantity, price_per_unit, amount_micro_tct } —— 下单意愿的输入 |

单规格商品不渲染规格选择器(规范 §3.5「默认即最优」);缺货/下架时主按钮禁用。

<tb-order-form> — 下单表单

| 入参 data-* | 说明 | |---|---| | data-lines | JSON 数组:[{ goods_id?, sku_id, quantity, price_per_unit, title? }](title 只用于显示) | | data-buyer | 买家 tct1 地址(预填可改) | | data-seller | 卖家 tct1 地址(只读显示) | | data-order-ref | 平台侧订单引用(必填:订单号由平台侧给,UI 不自己编) | | data-session-ref | 会话引用(可选) | | data-timestamp-ms | 固定毫秒时间戳(可选;重试请传同一个值 → 意愿 hash 不变 → 幂等) | | data-state / data-error | 同上 |

| 出参事件 | detail | |---|---| | tb:order-submit | { buyer, seller, order_ref, timestamp_ms, session_ref, lines: [{ goods_id, sku_id, quantity, price_per_unit }], total_micro_tct, contact: { name, phone, address }, intent } | | tb:order-error | { code, message } —— 表单不合法或意愿构造失败(此时不发 tb:order-submit) |

intent 由宿主注入的 intentBuilder 产出(属性,不是属性值):

import { buildOrderIntentHash } from '@trustbase/shop';
const form = document.querySelector('tb-order-form');
form.intentBuilder = (draft) =>
  buildOrderIntentHash(
    draft.lines.map((l) => ({ sku_id: l.sku_id, quantity: l.quantity, price_per_unit: l.price_per_unit })),
    { buyer: draft.buyer, seller: draft.seller, order_ref: draft.order_ref, timestamp_ms: draft.timestamp_ms, session_ref: draft.session_ref }
  );
// → detail.intent = { intent, canonical, hash, memo };memo 形如 "tb-auth:<64 位小写 hex>"

为什么是注入而不是 import:意愿口径只能有一个实现(@trustbase/shop), 而本包零依赖 —— 与其在 UI 里抄第二份、再指望两份不漂移,不如让调用方把真实现塞进来。 test/format.test.ts 与 test/order-form.test.ts 就是拿 @trustbase/shop 的真实现做的交叉验证。

<tb-account-panel> — 我的账户

| 入参 data-* | 说明 | |---|---| | data-account | JSON:{ address, balance_micro_tct?, balance_unit?, pending_orders? } | | data-orders | JSON 数组(占位):[{ order_id, status, title?, total_micro_tct?, created_at? }] | | data-state / data-error | 同上 |

| 出参事件 | detail | |---|---| | tb:account-action | { action, address? },action ∈ topup \| addresses \| orders \| copy-address |

2. 注册与使用

import { registerShopUi, TOKENS_CSS } from '@trustbaseai/shop-ui';
registerShopUi();                                    // 幂等;Node/SSR 里是安全空操作
document.head.append(Object.assign(document.createElement('style'), { textContent: TOKENS_CSS })); // 可选:壳层也用同一套变量
<tb-goods-list data-goods='[{"goods_id":"g1","title":"茶","skus":[{"sku_id":"s1","title":"250g","price_per_unit":"19900000","stock":5}]}]'></tb-goods-list>

事件是 bubbles: true + composed: true,所以在 document 上挂监听也能收到(有测试)。

3. Design tokens(src/tokens.ts,唯一来源)

《TrustBase-设计风格规范-2026-09-22》§1 的数字逐条落成代码 + 断言:

| 类别 | 取值 | |---|---| | 灰阶(恰好 5 级) | #1D1D1F / #6E6E73 / #8E8E93 / #C7C7CC / #E5E5EA | | 主色 / 强调色 | 文字 #1D1D1F、底 #FFFFFF、分区底 #F5F5F7;强调色 #C4633C(只用于主操作与选中态) | | 语义色(只用于状态) | 成功 #1D8A5B / 警告 #B26A00 / 错误 #C0392B(规范未指定色值,v0 取值写在这里) | | 字号(≤5 级) | 34 / 22 / 17 / 15 / 13 | | 字重 · 行高 | 400 · 600;正文 1.5、标题 1.2 | | 圆角(3 种) | 16 / 10 / 6 | | 间距(7 档) | 4 / 8 / 12 / 16 / 24 / 32 / 48 | | 描边或阴影(二选一) | 组件统一用描边(--tb-border-width: 1px,规范未给宽度);--tb-shadow-card 留给将来的浮层 | | 动效 | 120ms / 200ms,ease-out,只用于状态变化 |

tokens 由 tokensCss(':host') 注入每个组件的 shadow root —— 组件自带主题,不依赖宿主先引全局 CSS。

4. 证据(两条"防退化"的机器检查)

npm test          # 122 个用例,8 个套件
  • 无魔法值(test/styles.test.ts):扫描四个组件真渲染出的 <style>(去掉 tokens 注入块), 禁止 #hex / rgb() / hsl() / 13px / 1.5rem / 200ms / 内联 style="; 并断言用到的每个 var(--tb-*) 都已在 tokens 里定义(写错变量名浏览器不报错,只有这条能发现); 另断言"描边与阴影不在同一条规则里叠加"。src/ 其余文件也扫(tokens.ts 是唯一例外)。
  • tokens 与规范一致(test/tokens.test.ts):逐条断言上表的数字,并做一条通用兜底 —— 所有 token 里出现的 px 取值必须落在允许集合内(防"顺手加个 20px 圆角")。

5. 演示页(375 宽,纯静态,引用构建产物)

npm run build && npm run serve:demo     # http://127.0.0.1:4173/demo/

demo/index.html + demo/app.js 演示完整流程:商品列表 → 详情(选规格/改数量)→ 下单表单 → 意愿回执(展示要交给商户守护进程的 payload 与 tb-auth:<hash> memo)→ 我的账户(地址/余额/订单占位)。

  • 演示页直接 import ESM 构建产物(../dist/esm/index.js),不需要打包器; 唯一的裸依赖 @noble/hashes(shop 的 sha256)用浏览器原生 import map 映射(见 index.html 注释)。
  • 截图(Edge + puppeteer-core,375×812@2x)在 demo/shots/。

6. 公开 API

| 出口 | 内容 | |---|---| | 组件 | TbGoodsList TbGoodsDetail TbOrderForm TbAccountPanel TbElement | | 注册 | registerShopUi() SHOP_UI_ELEMENTS shopUiElementNames() | | tokens | TOKENS TOKENS_CSS tokensCss() HOST_STYLE AMOUNT_STYLE + 分组常量(gray color fontSize …) | | 事件名 | SHOP_UI_EVENTS(GOODS_SELECT SKU_CHANGE GOODS_BUY ORDER_SUBMIT ORDER_ERROR ACCOUNT_ACTION) | | 视图格式化 | formatAmount formatAmountParts multiplyAmounts sumAmounts shortAddress orderStatusLabel goodsMetaLine | | 类型 | GoodsView GoodsDetailView OrderLineView OrderSubmitDetail IntentBuilder AccountView AccountOrderView UIState |

7. 未验证 / 未做(诚实清单)

  • 未验证:手机真机上的表现(只有 Edge 375 宽截图为证);PWA 离线/安装(属 @trustbase/pwa-kit)。
  • 未验证:组件与真实索引器数据的对接(现在只吃过演示数据与测试夹具,映射属 WP5)。
  • 未做:购物车界面(@trustbase/shop 有购物车模型,但本期 UI 只有"立即购买"一条路 —— 一屏一个主操作);商品图上传/裁剪;库存实时刷新;分页/无限滚动;搜索。
  • 未做:收货地址簿(tb-account-panel 的"收货地址"只抛事件); 订单详情页;评价与售后入口;优惠券、多仓。
  • 未做:a11y 全面审计(已做基础项:role/tabindex/键盘 Enter·空格/aria-pressed/aria-label,未过读屏实测)。
  • 未做:i18n(文案现在直接是中文)。
  • 未做:shipping_address_hash 的加密与提交 —— 由商户守护进程做(界面只把明文交给它)。

8. 开发

npm run build      # CJS → dist/ ;ESM → dist/esm/(浏览器可直接 import)
npm test           # jest + jsdom(122 用例)
npm run typecheck  # tsc --noEmit
npm run serve:demo # 演示页(先 build)
  • src/ 的相对导入写 ESM 形态(带 .js 后缀)→ dist/esm/ 可被浏览器直接加载; jest 侧用 moduleNameMapper 摘后缀。
  • 测试用 jsdom;test/setup.cjs 补 TextEncoder/TextDecoder(jsdom 没有、浏览器有), 这样"组件 + @trustbase/shop 真意愿实现"能在同一个测试里跑通。
  • package.json 的 sideEffects 只留 register(customElements.define 是副作用入口),其余模块可 tree-shake。

9. 约束(会被测试拦下的写法)

  • 组件样式出现字面量 → test/styles.test.ts 失败;
  • src/ 里 import 任何外部包(含 @trustbase/*)→ test/isomorphic.test.ts 失败;
  • 源码出现 privateKey / mnemonic / signTransaction / keystore 等标识符 → 同款测试失败;
  • 新增第 6 种圆角/字号/间距 → test/tokens.test.ts 失败。