@zhang_jifan/fanui 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ACCESSIBILITY.md +61 -0
- package/CHANGELOG.md +112 -0
- package/COMMERCIAL-LICENSE.md +71 -0
- package/DESIGN-SYSTEM.md +294 -0
- package/LICENSE +661 -0
- package/README.md +348 -0
- package/USAGE.md +1926 -0
- package/_index.scss +20 -0
- package/dist/fanui.cjs +15 -0
- package/dist/fanui.css +28209 -0
- package/dist/fanui.d.ts +353 -0
- package/dist/fanui.esm.js +71 -0
- package/dist/fanui.js +4782 -0
- package/dist/fanui.min.css +1 -0
- package/dist/fanui.mjs +71 -0
- package/dist/modules/fanui-advanced.css +830 -0
- package/dist/modules/fanui-advanced.min.css +129 -0
- package/dist/modules/fanui-alert.css +575 -0
- package/dist/modules/fanui-alert.min.css +122 -0
- package/dist/modules/fanui-badge.css +426 -0
- package/dist/modules/fanui-badge.min.css +77 -0
- package/dist/modules/fanui-base.css +1394 -0
- package/dist/modules/fanui-base.min.css +236 -0
- package/dist/modules/fanui-blog.css +762 -0
- package/dist/modules/fanui-blog.min.css +108 -0
- package/dist/modules/fanui-button.css +526 -0
- package/dist/modules/fanui-button.min.css +56 -0
- package/dist/modules/fanui-card.css +291 -0
- package/dist/modules/fanui-card.min.css +52 -0
- package/dist/modules/fanui-choice.css +539 -0
- package/dist/modules/fanui-choice.min.css +83 -0
- package/dist/modules/fanui-dashboard.css +1057 -0
- package/dist/modules/fanui-dashboard.min.css +160 -0
- package/dist/modules/fanui-data.css +687 -0
- package/dist/modules/fanui-data.min.css +125 -0
- package/dist/modules/fanui-feedback.css +432 -0
- package/dist/modules/fanui-feedback.min.css +79 -0
- package/dist/modules/fanui-form.css +779 -0
- package/dist/modules/fanui-form.min.css +123 -0
- package/dist/modules/fanui-glass.css +497 -0
- package/dist/modules/fanui-glass.min.css +46 -0
- package/dist/modules/fanui-input-plus.css +799 -0
- package/dist/modules/fanui-input-plus.min.css +124 -0
- package/dist/modules/fanui-interactive.css +960 -0
- package/dist/modules/fanui-interactive.min.css +138 -0
- package/dist/modules/fanui-layout.css +4399 -0
- package/dist/modules/fanui-layout.min.css +1172 -0
- package/dist/modules/fanui-marketing.css +1125 -0
- package/dist/modules/fanui-marketing.min.css +144 -0
- package/dist/modules/fanui-nav.css +458 -0
- package/dist/modules/fanui-nav.min.css +76 -0
- package/dist/modules/fanui-overlay.css +547 -0
- package/dist/modules/fanui-overlay.min.css +91 -0
- package/dist/modules/fanui-table.css +290 -0
- package/dist/modules/fanui-table.min.css +62 -0
- package/dist/modules/fanui-tabs.css +333 -0
- package/dist/modules/fanui-tabs.min.css +58 -0
- package/dist/modules/fanui-themes.css +2728 -0
- package/dist/modules/fanui-themes.min.css +40 -0
- package/dist/modules/fanui-tokens.css +587 -0
- package/dist/modules/fanui-tokens.min.css +3 -0
- package/dist/modules/fanui-utilities.css +6557 -0
- package/dist/modules/fanui-utilities.min.css +1762 -0
- package/dist/modules/sizes.json +103 -0
- package/docs/MIGRATION-v2.md +179 -0
- package/examples/01-script-tag.html +149 -0
- package/examples/02-react/README.md +39 -0
- package/examples/02-react/index.html +12 -0
- package/examples/02-react/package.json +20 -0
- package/examples/02-react/src/App.jsx +108 -0
- package/examples/02-react/src/main.jsx +13 -0
- package/examples/02-react/src/useFanUI.js +87 -0
- package/examples/02-react/vite.config.js +6 -0
- package/examples/03-vue3/README.md +67 -0
- package/examples/03-vue3/index.html +12 -0
- package/examples/03-vue3/package.json +19 -0
- package/examples/03-vue3/src/App.vue +93 -0
- package/examples/03-vue3/src/main.js +8 -0
- package/examples/03-vue3/src/useFanUI.js +85 -0
- package/examples/03-vue3/vite.config.js +6 -0
- package/examples/04-vue2-cdn.html +93 -0
- package/examples/README.md +34 -0
- package/package.json +127 -0
- package/src/_functions.scss +319 -0
- package/src/_mixins.scss +1202 -0
- package/src/_tokens.scss +180 -0
- package/src/_variables.scss +491 -0
- package/src/base/_animations.scss +210 -0
- package/src/base/_reset.scss +359 -0
- package/src/base/_rtl.scss +133 -0
- package/src/base/_typography.scss +353 -0
- package/src/components/_advanced.scss +763 -0
- package/src/components/_alert.scss +322 -0
- package/src/components/_badge.scss +279 -0
- package/src/components/_blog.scss +693 -0
- package/src/components/_button.scss +351 -0
- package/src/components/_capabilities.scss +463 -0
- package/src/components/_card.scss +281 -0
- package/src/components/_choice.scss +444 -0
- package/src/components/_dashboard.scss +950 -0
- package/src/components/_data.scss +618 -0
- package/src/components/_feedback.scss +403 -0
- package/src/components/_form.scss +408 -0
- package/src/components/_glass.scss +374 -0
- package/src/components/_input-plus.scss +730 -0
- package/src/components/_interactive.scss +885 -0
- package/src/components/_marketing.scss +982 -0
- package/src/components/_nav.scss +480 -0
- package/src/components/_overlay.scss +517 -0
- package/src/components/_table.scss +276 -0
- package/src/components/_tabs.scss +338 -0
- package/src/fanui.d.ts +353 -0
- package/src/fanui.js +4782 -0
- package/src/fanui.scss +79 -0
- package/src/glass/_switches.scss +36 -0
- package/src/glass/_tokens.scss +259 -0
- package/src/layout/_container.scss +111 -0
- package/src/layout/_flex.scss +164 -0
- package/src/layout/_grid.scss +180 -0
- package/src/themes/_accents.scss +201 -0
- package/src/themes/_palettes.scss +180 -0
- package/src/utilities/_borders.scss +104 -0
- package/src/utilities/_display.scss +145 -0
- package/src/utilities/_effects.scss +210 -0
- package/src/utilities/_sizing.scss +123 -0
- package/src/utilities/_spacing.scss +118 -0
- package/src/utilities/_texture.scss +323 -0
package/ACCESSIBILITY.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# fanUI 无障碍声明(Accessibility Statement)
|
|
2
|
+
|
|
3
|
+
> 适用版本:fanUI v2.1+ · 更新日期:2026-09-18
|
|
4
|
+
|
|
5
|
+
## 承诺等级
|
|
6
|
+
|
|
7
|
+
fanUI 以 **WCAG 2.2 AA** 为设计与验收基线:
|
|
8
|
+
|
|
9
|
+
- **对比度**:12 套主题色 × 明暗模式共 48 组合,实心 / hover / active / soft·ink 文字对比度全部 ≥ 4.5:1(最近一次矩阵审计最低值 4.60:1),由 `fanui-pick-tone` 在生成主题时自动保证;
|
|
10
|
+
- **颜色无关**:状态不单靠颜色表达(成功 / 警告 / 危险均带图标与文案);
|
|
11
|
+
- **键盘可达**:命令面板、下拉菜单、日期 / 时间 / 颜色 / 级联 / 树 / 穿梭框均支持键盘操作与 `Esc` 关闭;焦点环令牌 `--fanui-focus-ring-*` 全局统一;
|
|
12
|
+
- **跳转链接**:提供 `.fanui-skip-link`(Tab 首个可达,悬浮于左上角直达主内容);
|
|
13
|
+
- **系统偏好**:尊重 `prefers-color-scheme`(auto 模式)、`prefers-reduced-motion`(关闭流光 / 涟漪 / 大幅动效)、`prefers-reduced-transparency`(液态玻璃自动退化为实体表面)、`forced-colors`(描边与文字走系统色);
|
|
14
|
+
- **语义**:交互组件带 `role` / `aria-*`(映射表见 `ACCESSIBILITY.md` 第 2 节),图标按钮要求 `aria-label`。
|
|
15
|
+
|
|
16
|
+
### 2. 机器验证(axe-core,`npm run audit:a11y`)
|
|
17
|
+
|
|
18
|
+
演示站 3 个页面 × **明/暗两种模式** 共 6 组,`serious` / `critical` 违规数 **0**。
|
|
19
|
+
该门禁会拦截:对比度不足、ARIA 属性/角色误用、表单控件缺可访问名、可滚动区域键盘不可达等。
|
|
20
|
+
|
|
21
|
+
### 3. 语义令牌的 AA 保证(v2.1 修正)
|
|
22
|
+
|
|
23
|
+
次级文字是历史上最容易跌破 AA 的位置,本版对令牌做了系统性校正:
|
|
24
|
+
|
|
25
|
+
| 令牌 | 旧值 | 新值 | 最差常见底色上的对比度 |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| `--fanui-color-text-subtle`(浅) | neutral 400 `#94a3b8` | `#5b6b80` | `--color-bg #f1f5f9` 上 **4.97**(旧 2.34) |
|
|
28
|
+
| `--fanui-color-text-subtle`(暗) | neutral 500 `#64748b` | `#8592a6` | `--color-surface #0f172a` 上 **5.66**(旧 3.75) |
|
|
29
|
+
| `--fanui-color-disabled-text`(浅/暗) | neutral 400 / 600 | `#5b6b80` / `#8592a6` | `--color-disabled-bg` 上 **4.97 / 5.01** |
|
|
30
|
+
|
|
31
|
+
语义色(success / warning / danger / info / primary)在暗色下的文字一律读取运行时令牌
|
|
32
|
+
(`--{name}-ink` 最亮档用于徽标与提示标题、`--{name}` 400 阶用于正文、`--{name}-fg` 用于实心底前景),
|
|
33
|
+
**不再于构建期从浅色基准推导中间色** —— 那是暗色模式对比度不足的根因。
|
|
34
|
+
|
|
35
|
+
### 4. 组件级 ARIA 映射
|
|
36
|
+
|
|
37
|
+
| 组件 | 角色 / 属性 |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| 开关 switch | `<input type="checkbox" role="switch">`(`aria-checked` 由原生状态驱动);**不要**把 `aria-checked` 写在外层 `<label>` 上 |
|
|
40
|
+
| 选项卡 tabs | 容器 `role="tablist"`,页签 `role="tab"` + `aria-selected` + `aria-controls`,面板 `role="tabpanel"` + `aria-labelledby`;禁用页签仍需 `role="tab"` + `disabled` |
|
|
41
|
+
| 树 tree | 容器 `role="tree"`,节点 `li[role="treeitem"]` + `aria-level` + `aria-expanded`,子级 `ul[role="group"]` |
|
|
42
|
+
| 可滚动区域 | 横/纵向溢出且内部无可聚焦内容时,由 `FanUI.a11y.focusableScrollables()` 自动补 `tabindex="0"` + `role="region"` + `aria-label`(init / load / resize 各补一次,幂等) |
|
|
43
|
+
| 徽标 / 提示 | 暗色下文字读 `--{name}-ink`;`--solid` 实心底的前景读 `--{name}-fg`(自动切换深/浅墨) |
|
|
44
|
+
|
|
45
|
+
### 5. 设计约定(给业务方的建议)
|
|
46
|
+
|
|
47
|
+
1. **400 阶色值只用于深色底**;浅色底上的正文/标签请用 700 阶(如 `.fanui-text-primary-700`)。
|
|
48
|
+
2. **不要用整体 `opacity` 表达禁用**——那会把文字一起压暗;请用 `--fanui-color-disabled-bg/-text`。
|
|
49
|
+
3. 图标按钮、纯图形开关必须提供 `aria-label`(组件初始化时会从包裹标签文本自动推导一次,但显式声明更稳)。
|
|
50
|
+
|
|
51
|
+
## 已知限制
|
|
52
|
+
|
|
53
|
+
1. `backdrop-filter` 不支持的浏览器自动降级为实体表面(`@supports` 兜底),无障碍不受影响,但视觉效果降级;
|
|
54
|
+
2. 玻璃材质上的文字已通过「可读性地板」保证对比度;若业务自行在玻璃元素上叠加自定义低对比文字色,需自行负责;
|
|
55
|
+
3. `date-picker` 目前键盘方向键导航为渐进增强(Esc / Enter 已支持),完整网格导航在路线图中;
|
|
56
|
+
4. 200% 缩放与 RTL 布局已做基础适配,RTL 有独立覆盖层(`src/base/_rtl.scss`),尚未全量机器验证。
|
|
57
|
+
|
|
58
|
+
## 反馈渠道
|
|
59
|
+
|
|
60
|
+
发现无障碍问题请通过官网「定价」页的联系渠道(微信 / QQ)反馈,
|
|
61
|
+
标注「fanUI a11y」,我们按 P0 缺陷处理。
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# 更新日志(Changelog)
|
|
2
|
+
|
|
3
|
+
本项目的全部重要变更都记录在本文件中。格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
|
4
|
+
版本遵循 [SemVer 2.0.0](https://semver.org/lang/zh-CN/)。
|
|
5
|
+
|
|
6
|
+
## [2.1.0] - 2026-09-18
|
|
7
|
+
|
|
8
|
+
### 新增(对应缺口清单 A / B / C / D / E / F / G 全量落地)
|
|
9
|
+
- **组件**:`date-picker`(单日/区间/快捷项)、`time-picker`(步长)、`color-picker`(色相/明度/色板/设为主题色)、`cascader`(多列联动/面包屑/搜索)、`tree`(复选/半选/展开)、`transfer`(双列表/搜索/禁用)、`loading-bar`(`FanUI.loadingBar.start()/set()/done()` + 不确定态)、`cluster` 布局、`logo-cloud` 客户 Logo 墙(A1–A9)
|
|
10
|
+
- **无障碍**:axe-core 自动审计脚本(C1,**每页 × 明/暗双模式**)、组件 ARIA 规范表(C2)、统一焦点环令牌 `--fanui-focus-ring-*`(C3)、`skip-link` 统一实现(C4)、无障碍声明 `ACCESSIBILITY.md`(C5)
|
|
11
|
+
- **无障碍增强 API**:`FanUI.a11y.focusableScrollables()` —— 给确实可滚动但无可聚焦内容的容器自动补 `tabindex` + `role="region"` + `aria-label`(init / load / resize 自动执行,幂等)
|
|
12
|
+
- **国际化**:`FanUI.i18n` 词表与 `setLocale()/register()`(D2)、`FanUI.format` Intl 格式化层(date/time/number/currency,D3)、RTL 逻辑属性覆盖层 `src/base/_rtl.scss`(D1)
|
|
13
|
+
- **工程化**:`LICENSE`(**注意**:v2.1.0 初期为 FanUI EULA 1.0,**发布前已废弃并改为 AGPL-3.0-or-later + 商业许可双许可**,见下方「发布就绪」段)、`CHANGELOG.md`、TypeScript 声明 `dist/fanui.d.ts`、GitHub Actions CI 门禁与自动发版流水线、`CONTRIBUTING.md`、`.editorconfig` / `.stylelintrc.json` / `.prettierrc`、`.gitignore` / `.npmignore`
|
|
14
|
+
- **分发**:分模块构建 `dist/modules/fanui-<name>.css`(24 个模块,可独立引入)+ `sizes.json` 体积清单、体积预算门禁 `scripts/check-size.cjs`、轻量压缩器(去注释/压缩空白)、CDN+SRI 示例与实测哈希
|
|
15
|
+
- **质量**:JS 纯逻辑单元测试 `scripts/test-unit.cjs`(20 项)、视觉回归 `scripts/verify-visual.cjs`(5 场景基线 diff)、跨浏览器矩阵 `scripts/verify-browsers.cjs`(Chrome/Edge,自动探测 Firefox)、边界用例 `scripts/verify-edge.cjs`(200% 缩放/超长文本/空态/forced-colors)、一键全量门禁 `npm run gates`(13 项)
|
|
16
|
+
- **文档**:组件 API 总表(`USAGE.md` §10 补齐 15 个组件——`date-picker`/`time-picker`/`color-picker`/`cascader`/`tree`/`transfer`/`loading-bar`/`form-item`/`form-label`/`textarea`/`stack`/`cluster`/`code`/`anchor`/`logo-cloud`,以及 17 个此前未文档化的 `data-fanui-*` 属性;§11 新增 7 项能力补齐的完整 API / 类名 / 属性;§1.7 改写为「服务端直出(推荐)」并给出 `FanUI.ssr.document()` / `themeBootstrap()` / `render()` 示例;`README.md` §7 组件清单新增「能力补齐(v2.1)」行,补上 7 项能力的类名、状态类、`FanUI.*` 命名空间与零 JS 自动初始化属性——此前 7 项能力只存在于 `USAGE.md`,README 首页完全检索不到)、迁移指南 v1→v2(`docs/MIGRATION-v2.md`)、按需引入实测指南、critical CSS 策略、ARIA 映射表
|
|
17
|
+
- **v2.1 能力补齐(7 项)**:`virtual-list` 虚拟滚动、`lightbox` 图片灯箱(缩放/平移/键盘导航)、`watermark` 防篡改水印、`tour` 引导(分步高亮 + 完成事件)、`splitter` 分割面板、`affix` 固钉、`backtop` 回到顶部。全部走逻辑属性、支持 `prefers-reduced-motion` 降级与键盘操作(源码 `src/fanui.js` 第 14 节,样式 `src/components/_capabilities.scss`)
|
|
18
|
+
- **服务端渲染(SSR)**:`dist/fanui.cjs` / `dist/fanui.esm.js` 在 Node 下**不再导出 `null`**,改为导出「服务端门面」——`FanUI.isServer === true`,`FanUI.ssr.render()/renderList()/document()/htmlAttrs()/themeBootstrap()/escape()` 可在服务端**直出组件标记**(Next / Nuxt / Astro 可直接使用),浏览器专属命名空间退化为调用不抛错的空实现。修复前服务端框架既拿不到类型也拿不到任何可渲染能力
|
|
19
|
+
- **工程化(本次加固)**:新增 `scripts/lib/runtime.cjs` 统一解析 playwright-core / sass 依赖(移除全部硬编码本机路径,改为「环境变量 → 项目 node_modules → NODE_PATH → 托管工作区 → 裸 require」);新增零依赖 `scripts/lint.cjs`(镜像 stylelint/prettier 规则)并置于 `npm run gates` 首位;覆盖度审计新增「反向一致性」(已实现但文档 0 提及)与「硬编码路径」检查,并修复其为纯报告、永不失败的问题(现以退出码表达结论);同时修复审计解析器两处盲区——(1) 会把 SSR 服务端门面误判为命名空间映射,导致 33 条误报「API 不存在」;(2) 无法识别 IIFE 单例(如 `FanUI.loadingBar`),且因全局存在 7 处同名 `var api = {` 而取错对象,产生 5 条误报。现改为按花括号深度提取对象顶层键、并在 IIFE 体内解析返回标识符;`EXPECT_COMPONENTS` 新增「能力补齐」组,使 7 项能力补齐的文档缺失也会被门禁拦截
|
|
20
|
+
- **门禁真实性(本次加固)**:`verify-examples.cjs` 从 1 个用例扩到 3 个真实浏览器断言(新增 Vue 2 CDN 示例),打包器示例(React/Vue3)在依赖缺失时**显式列出 SKIP 与启用命令**而非静默略过,并新增「断言链自检」——故意注入坏结果,若未被捕获即判失败,防止门禁被写成假通过;`probe-entries.cjs` 的 CJS 断言从「Node 下应导出 null」改为**校验服务端门面**(非 null / `isServer` / 版本一致 / `ssr.render` 可直出 / 空实现不抛错);`check-size.cjs` 在原始大小之外**新增 gzip 预算**(用户实际下载量);**新增 `verify-ssr.cjs` SSR 门禁**(14 项断言)——此前 SSR 无任何门禁覆盖,因此才漏掉了下面两个缺陷
|
|
21
|
+
- **修复**:
|
|
22
|
+
- **虚拟列表:服务端直出的行全部不可见**(高):`src/components/_capabilities.scss` 把原始数据源写成无条件 `display: none !important`,而 SSR 直出的标记恰恰只在 `__source` 里 —— 结果 `FanUI.ssr.render("virtualList", …)` 产出的行一个都看不见,与组件「渐进增强」的设计意图(无 JS / SSR 直出时正常流式展示)完全相反。现改为:`__source` 默认可见,JS 接管(容器加上 `.is-virtualized`)后才隐藏,并由 `__inner` 承担渲染;同时在未虚拟化时解除 `contain: strict` 与 320px 固定高度,真正流式展示
|
|
23
|
+
- **`dist/fanui.cjs` 在 Node 下导出 `null`**(高):见上文「服务端渲染(SSR)」条目
|
|
24
|
+
- **体积预算上调(显式记录,按 `check-size.cjs` 的调整规则)**:`fanui.js` 原始预算 `150KB → 220KB`,原因是新增 SSR 服务端门面与 7 项能力补齐(实测 208KB / gzip 53.6KB)。同时新增 gzip 预算:`fanui.js` 60KB、`fanui.min.css` 80KB
|
|
25
|
+
|
|
26
|
+
### 交付可用性(本轮加固 · 以「真的装一遍」为准)
|
|
27
|
+
|
|
28
|
+
> 之前所有验证都在仓库内跑 —— `dist/` 就在旁边、`package.json` 也没被当包读取,
|
|
29
|
+
> 所以 exports / files / 入口后缀这类问题**永远不会暴露**。本轮改为「`npm pack` → 全新项目 `npm install` → 逐项断言」,并固化为门禁 `npm run verify:install`。
|
|
30
|
+
|
|
31
|
+
- **修复:Node 原生 ESM 入口在 Node < 22.7 直接不可用**(高)。`exports["."].import` 指向 `dist/fanui.esm.js`,而本包 `package.json` 没有 `"type":"module"`,`.js` 后缀在 Node 眼里是 CommonJS —— Node 22.7+ 靠「模块语法探测」兜住了,但 `engines` 声明的是 `>=20`,Node 20(或关闭探测的 Node 22)下 `import FanUI, { theme } from "@zhang_jifan/fanui"` 直接报 `SyntaxError: Named export 'ssr' not found. The requested module '@zhang_jifan/fanui' is a CommonJS module`。现新增 `dist/fanui.mjs`(内容与 `.esm.js` 一致,后缀无歧义),并把 `exports.import` / `exports.default` 指向它;`.esm.js` 保留给浏览器 / 打包器与既有文档。
|
|
32
|
+
- **修复:文档宣传的 SCSS 接入方式实际编译不过**(高)。`USAGE.md` §1.4 写的是 `@use "@zhang_jifan/fanui/scss"`,但 **Sass 的 `loadPaths` 根本不读 npm 的 `exports` 字段**,实测报 `Can't find stylesheet to import`;示例给的 CLI 也没有 `--load-path=node_modules`,照抄必然失败。另外示例里的 `$fanui-font-size-base` **并不存在**(真实变量是 `$fanui-font-size-root`),会报 `not declared with !default`。现 §1.4 改为两套实测可编译的写法:`@use "pkg:@zhang_jifan/fanui"`(dart-sass ≥1.79 内置包导入)与 `@use "@zhang_jifan/fanui"` + `--load-path=node_modules`,并列出三个易踩的坑。
|
|
33
|
+
- **新增:包根 `_index.scss`**(转发到 `src/fanui`),使最符合直觉的 `@use "@zhang_jifan/fanui"` + `--load-path=node_modules` 能够解析(此前只能写到 `src/fanui` 这一层)。
|
|
34
|
+
- **新增:`exports["."]` 的 `sass` 条件**(`./src/fanui.scss`),使 `@use "pkg:@zhang_jifan/fanui"` 能正确落到 SCSS 源而不是 `.esm.js`(此前报 `resolved to ...fanui.esm.js, which is not a '.scss', '.sass', or '.css' file`)。
|
|
35
|
+
- **修复:下载到包的人拿不到任何可运行示例**(中)。`files` 未含 `examples`,而 `USAGE.md` / `examples/README.md` 都在用相对链接指向 `examples/01-script-tag.html` —— 对 npm 使用者是死链。现将 `examples` 纳入发布内容(该示例引用 `../dist/fanui.css`,包内有 `dist`,可直接打开)。
|
|
36
|
+
- **新增门禁 `scripts/verify-install.cjs`(`npm run verify:install`,已进 `npm run gates` 与 `check`)**:tarball 内容清单 → exports 子路径解析(8 项)→ CJS require + SSR 直出 → **原生 ESM import(用 `--no-experimental-detect-module` 显式模拟 Node<22.7)** → SCSS 五种写法编译 → 真实 Chrome 里从 `node_modules` 直接引用 IIFE/ESM。本机 Chrome 起不来时降级为显式 SKIP(不静默略过)。
|
|
37
|
+
- **文档:新增「`file://` 下 ES Module 不可用」的实测结论**。实测 `<script type="module">` 在 `file://` 下被 CORS 拦截(`Access to script ... blocked by CORS policy`),表现为「样式正常但 JS 完全没生效」;经典 `<script src>` 不受影响。已写入 `examples/README.md`,避免使用者把模块示例也理解成「双击即可运行」。
|
|
38
|
+
- **文案:不再以「零依赖 / 零构建」作为卖点**,改为陈述真实交付能力(可直接引入编译产物 / 也可 npm + ESM + SCSS)。涉及 `package.json` description、`demo/index.html` meta 与能力卡、`README` 标语与章节名、`DESIGN-SYSTEM.md` 小节名、示例页与 `USAGE.md` 的脚注。
|
|
39
|
+
- **验证报告**:完整的过程、证据与复现方式见 `reports/DELIVERY-VERIFICATION.md`。
|
|
40
|
+
### 命名与引用链路(2026-09-21 · 仍未发布,随 2.1.0 首发)
|
|
41
|
+
|
|
42
|
+
- **包名改为 `@zhang_jifan/fanui`(scoped)**。原定包名 `fanui` 会被 npm 的 New Package Moniker 规则拦下 —— 新包名去标点并小写后与已存在的 `fan-ui` 相同,服务端在**最终 PUT** 时才返回 `Package name too similar to existing package`,而 `npm view` 返回 404、`npm publish --dry-run` 也会通过,本地自测永远发现不了。scoped 包不受该规则约束;其默认私有,故 `publishConfig.access` 已显式设为 `public`。**关键:CSS 类名前缀、`data-fanui-*` 属性与源码里的 `PREFIX` / `$prefix` 常量永远是 `fanui`,与包名无关** —— 改名时把它们一起改掉,运行时拼出的选择器会变成以 @ 开头、含斜杠的非法形式(形如 `.@scope/name-tabs`),一个常量出错即可让 15 项门禁炸掉 13 项(本次真实踩坑,已加哨兵防复发)。
|
|
43
|
+
- **修复:`sideEffects` 白名单漏了 `dist/fanui.mjs`**(高)。该文件正是 `exports["."].import` / `.default` 的落点,漏掉它会让打包器把 `import "@zhang_jifan/fanui";` 这种**纯副作用引入**(只为触发 `data-fanui-*` 自动初始化)整块 tree-shake 掉,表现为「装了包但交互全部消失」。同时 `exports` 补齐 `"./style"` / `"./style.min"` 别名(此前只提供带 `.css` 后缀的写法)。
|
|
44
|
+
- **文档:`USAGE.md` §1.2「CDN 引入」加显著告警**。该节给出的 jsdelivr / unpkg 链接在包发布前必然 404,而 `<link>` 加载失败是**静默**的 —— 页面只表现为「样式全没、控制台无报错」,是「照抄文档后不生效」的头号原因。现补充「未发布期间改用本地 / 内网分发」的正确写法与自检命令(`npm view @zhang_jifan/fanui version`)。
|
|
45
|
+
- **门禁加固:消除两处环境性假红**
|
|
46
|
+
- `verify:demo` 的轮播断言由「绝对索引必须等于 1」改为「调用 `next()` 后**相对前进一格**(取模比较)」,并先 `stop()` 停掉自动播放再复位到第 1 张。原断言在 `data-autoplay="6000"` 的页面上天生不稳定:断言时刻若已停在末页,`next()` 会被 `go(index()+1)` clamp 成原地不动。
|
|
47
|
+
- `verify:install` 的安装步骤改为「`npm install` 超时 120s,未成功则**退化为直接解包 tarball**」。实测本机 npm 装这个 593KB 的**零依赖** tarball 需要 24~28s —— 打开 `--loglevel=http` 只看到 `npm http cache … (cache hit)`,**没有任何网络请求**;同一个 tarball 用系统 bsdtar 解包只要 1.1s、纯 `copyFileSync` 8ms。也就是说耗时全部来自本机 npm 自身,且偶发 >180s 卡死,属于环境抖动而非包的问题。退化等价的**前提是包零运行时依赖**,该前提由 `checkContents` 显式断言(有依赖时报错),因此退化不会掩盖真实缺陷;需要强制走真实安装时设 `FANUI_VERIFY_INSTALL_STRICT=1`。两条路径均已做坏值自检:注入超时 → 退化后仍全绿且 exit 0;严格模式 → 必须 exit 1(防止门禁被写成假通过)。
|
|
48
|
+
- `run-gates` 的摘要行过滤新增「退化 / WARN / 警告」,使环境性降级在门禁输出里直接可见。
|
|
49
|
+
- **门禁加固:`audit:a11y` 改为在「静止的最终状态」采样**。demo 首页是渐进增强的,采样瞬间实测有 **400+ 个动画**在跑,元素 `opacity<1` 会被 axe 折算进对比度计算 —— 同一份代码时红时绿(实测:动画在跑时报 `color-contrast × 24` 与 `link-in-text-block × 1`;而「冻结动效」与「等 3s 自然静止」两种独立方法都稳定报 0)。现在采样前注入「动画/过渡时长清零」并 `stop()` 掉轮播自动播放;审计报告同时**打印违规节点的选择器**(此前只报「× N 个节点」,排查时无从定位)。已做双向自检:注入一个浅灰压白底的反例页必须被抓住,demo 三页连跑 3 次必须稳定全绿。
|
|
50
|
+
- **修复:demo 首页正文里的内联链接只靠颜色区分**(serious,`link-in-text-block`)。该处用的是裸 `<a>` + 内联 `style="color:var(--fanui-primary)"`,而 reset 已统一去掉链接下划线 → 没有任何非颜色区分,且其与周围正文的颜色差恰好卡在判定阈值附近,导致审计在阈值上下抖动(这正是上面那条 flake 的具体来源)。现改用库自带的 `.fanui-link`(以 `border-bottom` 作为非颜色区分),既符合 WCAG 1.4.1,也彻底消除了这个临界抖动。
|
|
51
|
+
- **提示**:库对**裸 `<a>` 默认不加下划线**(仅 hover 时),因此**正文里的内联链接建议统一使用 `.fanui-link`**,否则在 prose 场景下会退化为「只靠颜色区分」。本次只修了 demo 内容,未改动设计系统的默认链接样式。
|
|
52
|
+
- **新增门禁(`scripts/lint.cjs`)**:第 4 节「包入口自洽性」—— `main`/`module`/`browser`/`types`/`style`/`sass`/`exports` 指向的文件必须存在,且每个 JS 入口都必须被 `sideEffects` 覆盖(含极简 glob 匹配);第 5 节「包名与类名前缀不许混淆」哨兵 —— 任何文件出现「包名后**紧跟连字符**」的形式即报错(合法子路径一律写成 `@zhang_jifan/fanui/style.css`,用斜杠分隔,因此永不出现连字符),并强制 `src/fanui.js` 保留 `var PREFIX = "fanui";`。两节都做过坏值自检。
|
|
53
|
+
- **修复:`demo/index.html` 在 320px 宽度下横向溢出 75px**(中)。安装命令行因包名变长而被撑破 —— flex 子项默认 `min-width: auto` 不收缩;已给 `.demo-install__cmd` 及其 `code` 加 `min-width: 0`。
|
|
54
|
+
|
|
55
|
+
### 修复
|
|
56
|
+
- **暗色模式语义色文字对比度不足**(serious):徽标 / 提示 / 表头着色 / 选中行次级文字此前在构建期用 `tint(浅色基准, 30%)` 推导暗色文字,得到中间色(4.08–4.39:1);现改读运行时令牌 `--{name}-ink` / `--{name}` / `--{name}-fg`,全部 ≥ 4.5:1
|
|
57
|
+
- **次级与禁用文字令牌不达标**(serious):`--fanui-color-text-subtle` 浅色 `#94a3b8`→`#5b6b80`(最差常见底 2.34→4.97)、暗色 `#64748b`→`#8592a6`(3.75→5.66);`--fanui-color-disabled-text` 同步校正
|
|
58
|
+
- **logo 墙 / 穿梭框禁用项整体降透明**(serious):`opacity: 0.45–0.62` 把文字一起压到 1.5–3:1;淡化改为只作用于图片,禁用态改用 `--fanui-color-disabled-*` 令牌
|
|
59
|
+
- **开关把 `aria-checked` 写在 `<label>` 上**(critical):label 隐式角色不支持该属性;改为 checkbox `role="switch"` 并自动补可访问名
|
|
60
|
+
- **树形控件结构与语义错误**(serious):根 `<ul>` 直接嵌 `<ul>`;且初始 `aria-expanded="false"` 与实际展开状态相反。重写渲染为 `role="tree"` / `treeitem` / `group`,展开态同步 ARIA
|
|
61
|
+
- **选项卡 `aria-required-children`**(critical):tablist 内混入无 `role="tab"` 的子元素
|
|
62
|
+
- **可滚动代码块键盘不可达**(serious):新增 `FanUI.a11y.focusableScrollables()` 统一补齐
|
|
63
|
+
- **`createAccent()` 非法输入静默产出 NaN 主题**(高):`hex2rgb()` 现校验 `#rgb/#rrggbb` 并抛错,不再把 NaN 令牌写入 `<head>`;取色器逐字符解析场景改用宽容版 `tryHex2rgb()`
|
|
64
|
+
- **`snapshot()`/`restore()` 不对称**(中):`restore(snapshot?)` 现支持传入快照往返还原;`readAccentColors()` 返回值新增 `available` 标志并在样式表未加载时给出一次性告警
|
|
65
|
+
- **命令面板对比度不足**(高):玻璃浮层统一加「可读性地板」(overlay 渐变抬升有效不透明度至 ~0.85),次级文字 subtle→muted;实测 desc 对比度 1.9:1 → 7.58:1
|
|
66
|
+
- **导航巨幕菜单溢出视口**(高):新增 `--mega-shift` 视口夹持,任何分辨率的 5 个菜单项均完整可见(12px 安全边距)
|
|
67
|
+
- **SCSS 配置入口失效**(高):文档宣传的 `@use "@zhang_jifan/fanui" with ($fanui-*: ...)` 实际报错;入口新增 `@forward "variables"` 后实测可用
|
|
68
|
+
- **Sass `if()` 弃用告警**:焦点环 mixin 改用 `@if/@else` 控制指令,构建零警告
|
|
69
|
+
- 级联/日期等弹层:面板重绘导致被点元素脱离 DOM、doc 层误关面板(事件级 `__fanuiPanelHit` 豁免);数据源误读相邻组件 JSON(改为优先元素自身)
|
|
70
|
+
- 杂项:list-item 选中态 `primary-bordery` 令牌名笔误;演示站头部硬编码的旧版本号改为运行时读取;验证脚本中的版本断言改为从 `package.json` 读取
|
|
71
|
+
- **演示站页脚仍写着 `MIT License`**(与 `LICENSE` 口径完全相反):`demo/index.html` 页脚继承自早期版本,此前多次口径修订均遗漏该处。现改为 `AGPL-3.0(双许可)`
|
|
72
|
+
- **`audit:a11y` 在 `npm run gates` 里时红时绿(门禁健壮性问题,不是产品缺陷)**:demo 首页的 reveal 入场动画使「采样瞬间每个元素的 opacity」取决于加载进度与机器负载 —— 单独跑审计全绿,在门禁里(前面已连跑十余项)`demo/index.html [light]` 稳定报 `color-contrast × 24`,而**违规节点数恰好等于视口内 reveal 元素的个数**。探针实测:把 reveal 元素强制到终态后违规归零,且此时**视口外的元素也一并被审计**(比只看当前视口更严格,不是放宽)。修法:CSS 钉死属性 + JS 补 `.is-visible`(双保险)+ **生效自检**(注入后统计仍非终态的 reveal 元素数并告警)。仅冻结 `transition-duration` 这类启发式做法不够 —— `waitForTimeout(800)` 不是确定性条件
|
|
73
|
+
|
|
74
|
+
### 发布就绪(npm 发布准备)
|
|
75
|
+
- **发布前自检脚本** `scripts/preflight-publish.cjs`(`npm run preflight`):一条命令检查「发布这件事本身」的资格与元数据——字段合法性、包名规则、semver 格式、8 个入口字段与 `exports` 全部目标文件是否真实存在、dist 关键产物是否齐全、实际打包清单(该有的都在 / 不该有的都不在)、许可证与仓库元数据、登录态与包名占用。与 `verify:install` 分工不同:后者验「装完能不能用」,本脚本验「有没有资格发」。网络不通时降级为 SKIP 而非误判为缺陷
|
|
76
|
+
- **同形名冲突探测**(阻断级,专治「最后一步才失败」):npm 的 New Package Moniker rules 会把包名去掉标点、统一小写后与已有包名比较,**服务端在最终 PUT 请求时才校验** —— `npm view <名>` 返回 404、`npm publish --dry-run` 也照样通过,只有真正发布时才 403。自检脚本在发布前主动比对含标点变体,提前暴露冲突(本包已命中:`fanui` 与已存在的 `fan-ui` 去标点后完全相同)
|
|
77
|
+
- **`publishConfig`**:显式声明 `access: public`、`registry: https://registry.npmjs.org/`、`tag: latest`。防止全局 registry 被设为淘宝镜像(只读复制站,不接受发布)时发布报错或发到错误位置
|
|
78
|
+
- **`files` 新增 `!examples/shots`**:示例截图是 `verify-examples.cjs` 的测试产物,对使用者无价值。tarball 795KB → 591KB(126 文件 / 解包 3.8MB)
|
|
79
|
+
- **`prepublishOnly` 增加 `verify:install`**:发布前强制过一遍交付门禁(build + lint + 体积预算 + 装得上且能用),防止发出使用者装不上的包
|
|
80
|
+
- **发布流程教程** `PUBLISHING.md`(新增):覆盖发布前准备(字段 / 入口 / 白名单 / 版本 / 依赖 / 许可证 / 仓库)、账号注册与 2FA、发布步骤、常见错误排查表(认证 / 冲突 / 权限 / 网络 / 内容五类)、版本更新、撤销与弃用(含官方 unpublish 政策与 72 小时窗口)、CI 自动化。该文件刻意**不写进 `files`** —— 属维护者文档,不随包发布
|
|
81
|
+
- **修复**:`.github/workflows/release.yml` 的 GitHub Release 附件清单漏了 `dist/fanui.mjs`(v2.1 新增的 ESM 入口,也是 `exports.import` 的目标)与 `dist/fanui.d.ts`,已补齐
|
|
82
|
+
- **许可证改为双许可(2026-09-21,公开发布的前置条件)**:原 `LICENSE` 是自定义 EULA(个人免费、禁再分发 / 二次开发 / 商用),而 npm 公开包条款规定「**发布即授权 npm 复制、公开、分发给所有用户**」,两者直接冲突 —— EULA 里「禁止再分发」在公开 registry 上**没有技术执行力**。现改为 **`AGPL-3.0-or-later` + 商业许可的双许可模式**:`package.json` 的 `license` 换成标准 SPDX 标识 `AGPL-3.0-or-later`,`LICENSE` 全文替换为 GNU AGPL-3.0 官方文本(34.5KB,逐字节一致并已校验首尾特征),新增随包发布的 `COMMERCIAL-LICENSE.md`(逐场景选择指引 + 商业授权获取方式 + 贡献者许可条款)。**闭源商用 / 闭源 SaaS 仍走商业授权** —— 变现路径不变,只是从「EULA 限制」换成「AGPL 的 copyleft 义务 + 商业许可豁免」。同时删除 npm 不识别的无效字段 `package.json#licenseFile`
|
|
83
|
+
- **`preflight` 新增两项检查**:①「scope 归属」—— scoped 包名的 `@scope` 必须等于登录账号名(大小写不敏感),不等即阻断。原检查只在「包已被占用」分支比对维护者列表,而**新包走 404 分支永远走不到那里**,偏偏 scope 名写错是新包最容易踩的 403;②「公开包 × 限制性许可证」—— `publishConfig.access="public"` 配 `SEE LICENSE IN` / `UNLICENSED` / `PolyForm-*` 时告警,直接点名条款矛盾。两条均做了坏值自检(注入伪登录名,验证阻断分支真的会失败,而非永远绿色的摆设)
|
|
84
|
+
|
|
85
|
+
### 演示页修复:液态玻璃「看得见」(2026-09-21)
|
|
86
|
+
|
|
87
|
+
用户反馈「液态开关不生效、没有液态效果」。排查结论:**库的玻璃链路(JS 状态机 / CSS 令牌 / `backdrop-filter`)完全正常**(computed style 在开关切换时确实在 `blur(22px) saturate(1.7)` ↔ `none` 之间变化),真正的缺陷有两个,都在演示页:
|
|
88
|
+
|
|
89
|
+
- **导航错配**:官网巨幕菜单里的「液态玻璃材质」链接指向 `data-nav-scene="system"`,而玻璃材质展示区实际在 `widgets` 场景 —— 顺着导航找玻璃永远找不到。已改为指向 widgets(含 `href="#widgets"`)
|
|
90
|
+
- **舞台底板无结构**:磨砂感来自「把有结构的内容糊掉」,而玻璃舞台底板只有两层极淡的 radial 渐变 —— 模糊前后几乎无像素差,玻璃开/关**肉眼不可辨**(探针 A/B 截图证实)。现改为「多色斑(展示折射色彩)+ 细网格(展示磨砂)」的结构化底板,开/关对比一眼可辨;并补操作引导文案
|
|
91
|
+
|
|
92
|
+
设计约束(记录给后续维护者):**给玻璃元素垫背景时必须有高对比结构(线条/色块)**;在 demo 覆盖 `--fanui-glass-*` 令牌必须限定在 `[data-fanui-glass="on"]` 下 —— 库的关闭规则权重 (0,2,0),同权重且后加载的覆盖会把它翻盘,导致「关了玻璃还是半透明」。
|
|
93
|
+
|
|
94
|
+
## [2.0.1] - 2026-09-18
|
|
95
|
+
|
|
96
|
+
### 修复(玻璃引擎关键缺陷)
|
|
97
|
+
- **玻璃关闭态失效**(高):`data-fanui-glass="off"` 与主题块同权重但输出在前,被后声明覆盖;新增独立开关层 `src/glass/_switches.scss` 并置于产物最末
|
|
98
|
+
- **深色玻璃更厚未生效**(中):预设类写死 `blur-base` 压过根级模式值;新增 `--fanui-glass-blur-mode-k`(浅 1 / 深 1.09)
|
|
99
|
+
- **关闭态兜底选择器不全**(中):11 个手工列举改为 `[class*="--glass"]` 子串匹配
|
|
100
|
+
- **预设写死底色**(低):预设只保留模式无关参数,底色交给模式令牌
|
|
101
|
+
- 玻璃令牌重构:全部强度公式在消费端内联展开,元素级 `--fanui-glass-intensity` 覆盖可实时重算
|
|
102
|
+
|
|
103
|
+
## [2.0.0] - 2026-09-16
|
|
104
|
+
|
|
105
|
+
### 新增
|
|
106
|
+
- 12 套运行时可切换主题色 × 明暗(light/dark/auto)× 液态玻璃开关,三条正交轴全部由根属性驱动
|
|
107
|
+
- 每套主题色自动派生 50–950 全色阶 + 14 个角色令牌,实心色自动满足 WCAG AA
|
|
108
|
+
- 液态玻璃材质:强度 0–2 连续可调、5 档预设(thin/regular/thick/ultra/lens)、5 档降级(off/reduced-transparency/reduced-motion/@supports/forced-colors)
|
|
109
|
+
- 场景化组件:营销页 / 博客 / 控制台 / 交互创新(命令面板、Dock、聚光灯卡片、主题工作台等)
|
|
110
|
+
- 零依赖构建:SCSS → CSS + IIFE/ESM/CJS 三入口;演示站 5 场景 + React/Vue 示例
|
|
111
|
+
|
|
112
|
+
> v1 → v2 的破坏性变更清单见 `docs/MIGRATION-v2.md`。
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# fanUI 授权说明(双许可)
|
|
2
|
+
|
|
3
|
+
fanUI 采用 **双许可(dual licensing)** 模式 —— 同一个软件,两种授权路径,你按自己的使用场景选一种:
|
|
4
|
+
|
|
5
|
+
| | 开源许可 | 商业许可 |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| 许可证 | **GNU AGPL-3.0-or-later** | 商业授权协议(单独签约) |
|
|
8
|
+
| 全文 | [LICENSE](./LICENSE) | 向作者索取 |
|
|
9
|
+
| 费用 | 免费 | 按授权类型付费 |
|
|
10
|
+
| 源码义务 | **必须开源**(含对外提供网络服务的场景) | 无需开源,可闭源使用 |
|
|
11
|
+
|
|
12
|
+
`package.json` 中对应声明为 `"license": "AGPL-3.0-or-later"`。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. 什么情况用 AGPL-3.0 就够了(免费)
|
|
17
|
+
|
|
18
|
+
AGPL-3.0 是 OSI 认可的自由软件许可证。你可以**免费**使用、修改、分发 fanUI,只需遵守其条款:
|
|
19
|
+
|
|
20
|
+
- **个人学习、课程作业、技术调研** —— 无附加条件;
|
|
21
|
+
- **开源项目**(以 AGPL-3.0 兼容的许可证发布)—— 直接使用;
|
|
22
|
+
- **公司内部系统**,且不向公司以外的人提供网络服务;
|
|
23
|
+
- **分发时保留版权声明与许可证文本**,并公开你对 fanUI 本身所做的修改。
|
|
24
|
+
|
|
25
|
+
> **AGPL 与 GPL 的关键差异在「第 13 条 远程网络交互」**:即使你**不分发**软件,
|
|
26
|
+
> 只要把它作为**网络服务**提供给用户(例如部署成一个对外可访问的网站 / SaaS),
|
|
27
|
+
> 你也必须向这些用户提供**完整的对应源码**(包含你对 fanUI 的修改)。
|
|
28
|
+
> 这一条正是「闭源商业使用需要购买商业许可」的根源。
|
|
29
|
+
|
|
30
|
+
## 2. 什么情况需要商业许可(付费)
|
|
31
|
+
|
|
32
|
+
以下场景**无法**仅靠 AGPL-3.0 满足,需要购买商业许可:
|
|
33
|
+
|
|
34
|
+
- **闭源商业产品**:把 fanUI 用在不开源的商业前端 / 客户端产品中;
|
|
35
|
+
- **闭源 SaaS / 网络服务**:对外提供服务,但不希望按 AGPL 第 13 条公开源码;
|
|
36
|
+
- **OEM / 白标**:源码级修改后嵌入自有产品再分发;
|
|
37
|
+
- **企业采购 / 招投标 / 法务合规**:需要一份可归档的**书面授权凭证**;
|
|
38
|
+
- 任何**无法满足 AGPL 开源义务**的商业场景。
|
|
39
|
+
|
|
40
|
+
购买商业许可后,在该许可范围内的 **AGPL 开源义务被商业许可条款替代**(不再要求你开源)。
|
|
41
|
+
|
|
42
|
+
## 3. 如何获取商业许可
|
|
43
|
+
|
|
44
|
+
授权类型与报价见官网「定价」分区;获取授权请通过该分区的联系渠道(微信 / QQ)。
|
|
45
|
+
|
|
46
|
+
| 授权类型 | 覆盖场景 |
|
|
47
|
+
|---|---|
|
|
48
|
+
| **Business** | 团队协作(≤ 20 人)、商业产品使用 |
|
|
49
|
+
| **Enterprise** | 多产品线、私有化部署、不限人数 |
|
|
50
|
+
| **定制 / OEM** | 源码级修改、再授权嵌入自有产品,单独签约 |
|
|
51
|
+
|
|
52
|
+
商业授权以**授权证书 / 授权码**为凭。企业采购可开具发票。
|
|
53
|
+
|
|
54
|
+
> ⚠️ **待维护者补充**:此处建议填入一个可长期使用的公开商务联系方式
|
|
55
|
+
> (例如商务邮箱或官网定价页 URL),否则使用者无从询价。
|
|
56
|
+
> 当前表述沿用官网「定价」分区的联系入口(微信 / QQ),与 `README.md`、`ACCESSIBILITY.md` 口径一致。
|
|
57
|
+
|
|
58
|
+
## 4. 贡献(Contributions)
|
|
59
|
+
|
|
60
|
+
向本仓库提交代码,即表示你同意:
|
|
61
|
+
|
|
62
|
+
1. 你的贡献以 **AGPL-3.0-or-later** 授权;
|
|
63
|
+
2. 作者有权将你的贡献**一并纳入商业许可**对外授权。
|
|
64
|
+
|
|
65
|
+
第 2 条是双许可模式能够成立的前提 —— 若缺少它,作者将无法把包含外部贡献的版本对外提供商业授权。
|
|
66
|
+
若你不能接受这两条,请不要向本仓库提交代码。详见 [CONTRIBUTING.md](./CONTRIBUTING.md)。
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
> 本文件是对授权模式的**通俗说明**,不构成法律意见,也不替代 [LICENSE](./LICENSE) 中的条款;
|
|
71
|
+
> 两者若有冲突,以 `LICENSE` 全文为准。
|
package/DESIGN-SYSTEM.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# fanUI 设计系统 v2.1
|
|
2
|
+
|
|
3
|
+
> 一套把「**主题色 · 深浅模式 · 液态玻璃**」抽象成**三条正交运行时轴**的 UI 设计系统。
|
|
4
|
+
> 不重新编译 CSS,只改根属性,全站视觉即刻重塑。
|
|
5
|
+
|
|
6
|
+
- 设计原则:**统一规范 · 可复用 · 易扩展 · 自动可达(Automatic Attainability)**
|
|
7
|
+
- 对应演示:`demo/index.html`(场景化展示)、`demo/components.html`(全组件百科)
|
|
8
|
+
- 对比度审计:`scripts/audit-theme.cjs` → `theme-audit.md`(60/60 通过)
|
|
9
|
+
- 浏览器回归:`scripts/verify-demo.cjs`(零 console error / 48 组合遍历 / 交互回归 / 截图)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. 三条正交运行时轴
|
|
14
|
+
|
|
15
|
+
| 轴 | 根属性 | 取值 | 默认 |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| 主题色 | `data-fanui-accent` | `cyan sky blue indigo violet purple pink rose red orange emerald teal`(或 `createAccent` 生成的自定义名) | `cyan` |
|
|
18
|
+
| 深浅模式 | `data-fanui-theme` | `light · dark · auto`(auto 跟随系统) | `auto` |
|
|
19
|
+
| 液态玻璃 | `data-fanui-glass` | `on · off` | `on` |
|
|
20
|
+
|
|
21
|
+
三条轴相互独立、可任意组合(12 色 × 3 模式 × 玻璃开关 = 72 种全局形态),全部在运行时通过根属性切换,**零重编译**。
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<html data-fanui-theme="dark" data-fanui-accent="violet" data-fanui-glass="on">
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 1.1 局部主题(子树级)
|
|
28
|
+
|
|
29
|
+
除了全局根属性,还可在任意子树上叠加:
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<section data-fanui-accent="rose">…</section> <!-- 属性选择器 -->
|
|
33
|
+
<section class="fanui-accent-teal">…</section> <!-- 等价类名 -->
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 2. 主题色系统
|
|
39
|
+
|
|
40
|
+
### 2.1 十二套内置主题
|
|
41
|
+
|
|
42
|
+
每套主题 = **11 阶色板(50–950)+ 13 个角色令牌 + 副色(accent)+ 品牌渐变**。
|
|
43
|
+
|
|
44
|
+
| 名称 | 色相 | 气质 | 副色搭配 |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| 青 Cyan | 190 | 科技·清爽 | violet |
|
|
47
|
+
| 天蓝 Sky | 200 | 轻盈·开放 | blue |
|
|
48
|
+
| 经典蓝 Blue | 220 | 稳健·通用 | indigo |
|
|
49
|
+
| 靛蓝 Indigo | 245 | 专业·前沿 | blue |
|
|
50
|
+
| 堇罗兰 Violet | 258 | 创意·智能 | purple |
|
|
51
|
+
| 紫紫 Purple | 280 | 艺术·张弛 | pink |
|
|
52
|
+
| 品粉 Pink | 330 | 年轻·潮流 | rose |
|
|
53
|
+
| 玫红 Rose | 348 | 热情·灵韵 | red |
|
|
54
|
+
| 朱红 Red | 0 | 强烈·行动 | orange |
|
|
55
|
+
| 暖橙 Orange | 25 | 活力·温暖 | amber |
|
|
56
|
+
| 翡翠 Emerald | 160 | 成长·健康 | teal |
|
|
57
|
+
| 青碧 Teal | 175 | 沉静·高效 | cyan |
|
|
58
|
+
|
|
59
|
+
### 2.2 角色令牌(每个主题色输出 13 个角色)
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
--fanui-primary 实心主色(自动选中对比度达标的那一阶)
|
|
63
|
+
--fanui-primary-fg 实心主色上的前景文字(白或深墨,≥4.5:1)
|
|
64
|
+
--fanui-primary-hover / -active
|
|
65
|
+
--fanui-primary-vivid / -vivid-2 高饱和装饰色(渐变端点)
|
|
66
|
+
--fanui-primary-soft / -soft-2 半透明底色(信息条 / 选中态)
|
|
67
|
+
--fanui-primary-border 半透明描边
|
|
68
|
+
--fanui-primary-ink soft 底色上的文字色
|
|
69
|
+
--fanui-primary-ring 焦点环
|
|
70
|
+
--fanui-primary-glow 品牌光晕(阴影)
|
|
71
|
+
--fanui-primary-grad-a / -grad-b 品牌渐变端点
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
另有 `--fanui-accent-*`(副色同构 13 角色)与合成令牌 `--fanui-brand-grad`、`--fanui-brand-grad-soft`、`--fanui-brand-aura-a/b`、`--fanui-brand-name`。
|
|
75
|
+
|
|
76
|
+
**关键约定**:所有组件**只消费角色令牌**,从不写死色值 —— 这就是「换主题色全组件一致生效」的实现基础。
|
|
77
|
+
|
|
78
|
+
### 2.3 自动可达性(Automatic Attainability)
|
|
79
|
+
|
|
80
|
+
主题色的实心色**不是手工指定**,而是由 `fanui-pick-tone()` 从 600/700/800/900/500 候选阶里**自动挑选**第一个与白字对比度 ≥ 4.5:1 的那一阶(深色模式在 500/400/600/300 中挑选);前景色由 `fanui-best-fg()` 在白与深墨间择优。因此:
|
|
81
|
+
|
|
82
|
+
- 任意内置主题 × 任意模式 ⇒ 实心色/文字 60/60 通过 WCAG AA;
|
|
83
|
+
- **任意品牌色**经 `FanUI.theme.createAccent(hex)` 生成 11 阶色板后同样自动合规(运行时从 HSL 计算,镜像 SCSS 逻辑)。
|
|
84
|
+
|
|
85
|
+
### 2.4 语义色(success / warning / danger / info)
|
|
86
|
+
|
|
87
|
+
语义色同样随模式自动调整(浅色模式取 700 阶实心、深色模式取 400 阶),并通过 `fanui-emit-color-roles()` 输出与主题色同构的角色令牌,保证 `.fanui-btn--success`、`.fanui-alert--danger` 在深浅两种模式下都达标。
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 3. 液态玻璃(Liquid Glass)
|
|
92
|
+
|
|
93
|
+
### 3.1 材质分层
|
|
94
|
+
|
|
95
|
+
玻璃不是一层 `backdrop-filter`,而是**四层叠加**,每层都由强度旋钮驱动:
|
|
96
|
+
|
|
97
|
+
| 层 | 实现 | 作用 |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| 折射 | `backdrop-filter: blur() saturate() brightness()` | 透镜感、色彩饱和 |
|
|
100
|
+
| 底色 | `--fanui-glass-bg`(半透明 tint) | 可读性基底 |
|
|
101
|
+
| 高光 | `background-image` 径向高光 + 流光(位于内容之下) | 玻璃光泽 |
|
|
102
|
+
| 厚度 | `::after` + `mask-composite: exclude` 边缘环 | 玻璃切面描边 |
|
|
103
|
+
|
|
104
|
+
### 3.2 单一强度旋钮
|
|
105
|
+
|
|
106
|
+
`--fanui-glass-intensity`(0–2,默认 1)统一驱动:模糊半径、底色透明度、饱和度增益、边缘浓度、高光强度、投影深度。一个数字,整套材质浓淡联动。
|
|
107
|
+
|
|
108
|
+
### 3.3 预设
|
|
109
|
+
|
|
110
|
+
| 预设 | intensity | 模糊 | 场景 |
|
|
111
|
+
|---|---|---|---|
|
|
112
|
+
| `thin` | 0.5 | 10px | 信息条、工具条 |
|
|
113
|
+
| `regular` | 1 | 22px | 卡片、导航(默认) |
|
|
114
|
+
| `thick` | 1.45 | 30px | 面板、强强调 |
|
|
115
|
+
| `ultra` | 1.9 | 44px | 全屏遮罩 |
|
|
116
|
+
| `lens` | 2 | 36px | 透镜特化(高折射低 tint) |
|
|
117
|
+
|
|
118
|
+
### 3.4 使用方式
|
|
119
|
+
|
|
120
|
+
```html
|
|
121
|
+
<div class="fanui-glass">…</div> <!-- 基础玻璃 -->
|
|
122
|
+
<div class="fanui-glass fanui-glass--thick">…</div> <!-- 预设 -->
|
|
123
|
+
<div class="fanui-card fanui-card--glass">…</div> <!-- 已有组件的玻璃形态 -->
|
|
124
|
+
<div class="fanui-navbar fanui-navbar--glass">…</div>
|
|
125
|
+
<div class="fanui-modal fanui-modal--glass">…</div>
|
|
126
|
+
<div class="fanui-glass fanui-glass--flow">…</div> <!-- 流光动画版 -->
|
|
127
|
+
<div data-fanui-glass-track>…</div> <!-- 指针追踪高光 -->
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
已内置玻璃形态的组件:`card · navbar · modal · drawer · dropdown · toast · alert · btn · table`。
|
|
131
|
+
|
|
132
|
+
### 3.5 开关与降级
|
|
133
|
+
|
|
134
|
+
- **全局开关**:`<html data-fanui-glass="off">` —— 所有玻璃(含组件玻璃形态)自动退回实体表面。
|
|
135
|
+
- **能力回退**:`@supports not (backdrop-filter…)` 自动改用实色。
|
|
136
|
+
- **无障碍**:`prefers-reduced-transparency` / `prefers-reduced-motion` 下自动降级为实色 / 关闭流光。
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 4. 深浅模式
|
|
141
|
+
|
|
142
|
+
- `data-fanui-theme="light|dark|auto"`,`auto` 跟随系统并监听系统变化。
|
|
143
|
+
- 中性色、语义色、玻璃 tint、阴影强度均为**模式感知**:深色下阴影减淡、soft 层改用低透明度亮色、玻璃 tint 换向。
|
|
144
|
+
- JS 持久化到 `localStorage`,刷新后由 `FanUI.theme.restore()` 恢复。
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 5. JS 运行时(`dist/fanui.js`,可选)
|
|
149
|
+
|
|
150
|
+
### 5.1 FanUI.theme —— 三轴控制中枢
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
FanUI.theme.setAccent("violet"); // 12 选 1
|
|
154
|
+
FanUI.theme.nextAccent(); // 循环切换
|
|
155
|
+
FanUI.theme.getAccent(); // → "violet"
|
|
156
|
+
FanUI.theme.accents; // [{key,label,en,hue,vibe}, …12]
|
|
157
|
+
FanUI.theme.readAccentColors(); // 当前主题全部令牌的实时值
|
|
158
|
+
FanUI.theme.createAccent("#ff6b35"); // 任意品牌色 → 合规 11 阶主题,返回新主题名
|
|
159
|
+
|
|
160
|
+
FanUI.theme.setMode("dark"); // light / dark / auto
|
|
161
|
+
FanUI.theme.cycleMode(); // 循环切换
|
|
162
|
+
FanUI.theme.getEffectiveMode(); // auto 解析后的实际模式
|
|
163
|
+
|
|
164
|
+
FanUI.theme.setGlass(false); // 总开关
|
|
165
|
+
FanUI.theme.toggleGlass();
|
|
166
|
+
FanUI.theme.setGlassIntensity(1.4); // 0–2 连续旋钮
|
|
167
|
+
FanUI.theme.setGlassPreset("lens"); // thin/regular/thick/ultra/lens
|
|
168
|
+
|
|
169
|
+
FanUI.theme.snapshot(); // 当前全部偏好 → JSON
|
|
170
|
+
FanUI.theme.reset(); // 恢复出厂
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
事件:`fanui:accentchange` · `fanui:themechange` · `fanui:glasschange`(均派发到 `document`)。
|
|
174
|
+
v1 兼容:`FanUI.theme.get/set/toggle` 仍指向模式操作。
|
|
175
|
+
|
|
176
|
+
### 5.2 FanUI.studio —— 主题工作台(可视化面板)
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
FanUI.studio.mount({ defaultOpen: false }); // 页面挂载一次即可
|
|
180
|
+
FanUI.studio.api().open(); // 12 色卡 + 深浅分段 + 玻璃开关/强度/预设 + 快照展示
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
内置快捷键:`Alt+T` 开合面板 · `Alt+D` 切换深浅。适合交付给非前端同学做「所见即所得」换肤。
|
|
184
|
+
|
|
185
|
+
### 5.3 组件交互模块
|
|
186
|
+
|
|
187
|
+
| 模块 | 用法 | 说明 |
|
|
188
|
+
|---|---|---|
|
|
189
|
+
| Command | `FanUI.command("#sel")` / `⌘K` | 命令面板:模糊搜索、键盘导航、`setItems()` 动态数据源、`fanui:command` 事件 |
|
|
190
|
+
| Carousel | `el.__fanuiApi.next()/index()` | scroll-snap 轮播:拖拽、自动播放、分页点、进度条 |
|
|
191
|
+
| Modal/Overlay | `FanUI.overlay("#sel")` | 模态/抽屉/底部面板统一实例 API |
|
|
192
|
+
| Toast | `FanUI.toast.success/info/warning/error` | 轻提示区域自动创建 |
|
|
193
|
+
| Tabs / Dropdown / Collapse / Switch / Form | 声明式初始化 | `data-fanui-*` 属性驱动 |
|
|
194
|
+
| Rate / Tags / Stepper / Pin / Swatch / Upload / Combobox | 声明式初始化 | 进阶表单控件(评分、标签输入、步进器、验证码、色板、上传、联想下拉) |
|
|
195
|
+
| Kanban | 声明式 | HTML5 拖拽看板 |
|
|
196
|
+
| Notify / FilterBar | 声明式 | 通知中心 / 筛选栏 |
|
|
197
|
+
| Scroll | 自动 | 阅读进度、导航加深、回顶、scrollspy |
|
|
198
|
+
| reveal / Counter | `data-fanui-reveal` `data-fanui-count-to` | 入场动画 + 数字滚动(嵌套在 reveal 容器内的计数器同样触发) |
|
|
199
|
+
| Tracker | 自动 | 指针追踪玻璃高光 / 聚光灯 / 光标光晕 |
|
|
200
|
+
| ripple / Copy | `data-fanui-ripple` / `data-fanui-copy` | 涟漪 / 复制 |
|
|
201
|
+
|
|
202
|
+
> 自动初始化:默认 `DOMContentLoaded` 时全量扫描;在 `<html>` 上加 `data-fanui-auto="false"` 可关闭,改手动 `FanUI.init(scope)`。
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 6. 组件层总览(v2 新增 9 个模块)
|
|
207
|
+
|
|
208
|
+
**v1 基础 11 模块**:button / form / choice / card / table / badge / alert / nav / tabs / overlay / feedback / data
|
|
209
|
+
|
|
210
|
+
**v2 新增**:
|
|
211
|
+
|
|
212
|
+
| 模块 | 内容 |
|
|
213
|
+
|---|---|
|
|
214
|
+
| `_glass.scss` | 玻璃材质本体 + 5 预设 + 9 组件玻璃形态 + 降级链 |
|
|
215
|
+
| `_input-plus.scss` | 滑块(含双滑块/气泡)、评分、上传、标签输入、步进器、验证码、色板、联想下拉 |
|
|
216
|
+
| `_interactive.scss` | ⌘K 命令面板、Dock 放大(`:has()` 邻域感知)、轮播、FAB、阅读进度、聚光灯、光标光晕、主题工作台、底部安全面板 |
|
|
217
|
+
| `_marketing.scss` | 公告条、Hero(极光+网格底纹)、特性卡、Bento 网格、流程、跑马灯、数据带、定价(月/年切换)、证言、CTA、FAQ、页脚 |
|
|
218
|
+
| `_blog.scss` | 阅读元信息、文章卡(横/素/叠图)、文章版式(+目录 scrollspy)、作者卡、分享栏、评论、订阅、相关阅读、标签云 |
|
|
219
|
+
| `_dashboard.scss` | KPI 卡(迷你走势图+计数)、柱状图(纯 CSS)、环形图(conic-gradient)、图例、用量条、看板、动态流、通知、筛选栏 |
|
|
220
|
+
| `glass/tokens.scss` | 玻璃令牌族(按模式输出) |
|
|
221
|
+
| `themes/palettes.scss` | 12 主题定义(数据与逻辑分离) |
|
|
222
|
+
| `themes/accents.scss` | 主题发射器(24 组合全量输出) |
|
|
223
|
+
| `utilities/texture.scss` | 极光/网格/噪点/渐变描边/渐变文字/流光/遮罩/涟漪 |
|
|
224
|
+
|
|
225
|
+
**纯 CSS 交互创新点**(无 JS 参与):Dock 邻域放大、聚光灯、命令面板骨架、scroll-snap 轮播、conic 环形图、Bento 网格、玻璃流光。
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 7. 令牌总表
|
|
230
|
+
|
|
231
|
+
| 令牌族 | 前缀 | 说明 |
|
|
232
|
+
|---|---|---|
|
|
233
|
+
| 主题色角色 | `--fanui-primary-*`(13 个) | 随 accent 轴切换 |
|
|
234
|
+
| 副色角色 | `--fanui-accent-*`(13 个) | 品牌渐变第二端 |
|
|
235
|
+
| 语义色角色 | `--fanui-{success\|warning\|danger\|info}-*` | 随模式自适应 |
|
|
236
|
+
| 原始色阶 | `--fanui-{name}-{50…950}` | 全色系 11 阶 |
|
|
237
|
+
| 表面语义 | `--fanui-color-{bg\|surface\|text\|text-muted\|border…}` | 随模式切换 |
|
|
238
|
+
| 玻璃 | `--fanui-glass-{intensity\|blur-base\|filter\|bg\|layer\|border\|edge-*\|specular-*\|shadow\|divider}` | 随开关/预设/强度联动 |
|
|
239
|
+
| 其余 | 字号/行高/字重/间距/圆角/阴影/层级/动效/控件/断点 | 与 v1 一致 |
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 8. 扩展指南
|
|
244
|
+
|
|
245
|
+
### 8.1 新增一套内置主题色
|
|
246
|
+
|
|
247
|
+
在 `src/themes/_palettes.scss` 的 map 中追加一项(label / en / hue / vibe / secondary / palette 11 阶),发射器自动输出全部组合,无需改动其他文件。
|
|
248
|
+
|
|
249
|
+
### 8.2 运行时接入企业品牌色
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
const name = FanUI.theme.createAccent("#0aa2c0"); // 生成合规色板
|
|
253
|
+
FanUI.theme.setAccent(name); // 全组件即时生效
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### 8.3 新组件接入主题系统
|
|
257
|
+
|
|
258
|
+
只允许消费角色令牌:
|
|
259
|
+
|
|
260
|
+
```scss
|
|
261
|
+
.my-widget {
|
|
262
|
+
background: fn.fanui-var("primary-soft");
|
|
263
|
+
border: 1px solid fn.fanui-var("primary-border");
|
|
264
|
+
color: fn.fanui-var("primary-ink");
|
|
265
|
+
&:focus-visible { outline: 2px solid fn.fanui-var("primary-ring"); }
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### 8.4 新组件接入玻璃
|
|
270
|
+
|
|
271
|
+
```scss
|
|
272
|
+
.my-panel { @include mx.fanui-liquid-glass($radius: 16px); }
|
|
273
|
+
// 总开关降级自动生效(fanui-glass-off 已处理 data-fanui-glass="off")
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 9. 质量守护
|
|
279
|
+
|
|
280
|
+
| 脚本 | 作用 |
|
|
281
|
+
|---|---|
|
|
282
|
+
| `scripts/build.cjs` | 编译 expanded + compressed,复制 JS,自动定位 sass |
|
|
283
|
+
| `scripts/audit-theme.cjs` | 解析 dist CSS,校验 12 主题 × 明暗 实心色/前景 对比度 → `theme-audit.md`(60/60) |
|
|
284
|
+
| `scripts/verify-demo.cjs` | 真实浏览器回归:结构健康、浏览器内对比度(含半透明 alpha 合成)、48 组合遍历、交互回归、14 张截图 |
|
|
285
|
+
|
|
286
|
+
浏览器要求:本机安装 Chrome(脚本以 `channel: "chrome"` 启动);playwright-core 来自托管 node 工作区。
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## 10. 兼容性基线
|
|
291
|
+
|
|
292
|
+
- 现代常青浏览器(Chrome / Edge / Firefox / Safari 最近两个版本)。
|
|
293
|
+
- 玻璃效果需 `backdrop-filter`;不支持时经 `@supports` 自动实色,功能不受影响。
|
|
294
|
+
- `:has()`(Dock 放大)不支持时布局退化为普通等距排列。
|