版本与兼容性政策
本页回答一个问题:升级到下一个 1.x 小版本时,已依赖的公开面是否会变化。
XiHan.UI 的公开面横跨五种介质:替换自带皮肤、手写 Light DOM 结构都是 皮肤与样式分层 与 解剖与部件契约 公开说明的用法:
| 介质 | 使用者写在哪里 | 例子 |
|---|---|---|
| JS / TS 导出 | 自己的源码 | import { XhSelectRoot } from '@xihan-ui/vue' |
| 解剖属性 | CSS 选择器 | [data-scope='select'][data-part='trigger'] |
data-* 状态属性 | CSS 选择器 | [data-state='open']、[data-tone='danger'] |
CSS 自定义属性与 @layer 名 | 自己的样式表 | --xh-bg-brand、@layer xihan.overrides |
| 自定义元素标签与 attribute | 自己的 HTML | <xh-dialog modal="true">、data-xh-part="content" |
破坏其中任何一种都不会产生编译错误、运行时异常或降级,只会表现为样式丢失或控件不响应。因此本页对五种介质逐类给出结论。
三个档位
| 档位 | 含义 |
|---|---|
| 受约束 | 改名、删除、收窄语义 = major。新增同类成员 = minor。 |
| 只增不减 | 只保证已有成员不消失、不改名。新增字段与条目不算破坏。 |
| 排除 | 明确不在承诺内,任何版本都可能改。使用者不应依赖。 |
版本号采用语义化版本 major.minor.patch:受约束的成员变更走 major,新增走 minor,只修正行为不改名字走 patch。
18 个包必须同版本安装
18 个包锁步发布:任何一个包发新版本,全部 18 个包一起发同一个号。
@xihan-ui/core @xihan-ui/position @xihan-ui/tokens
@xihan-ui/styles @xihan-ui/headless @xihan-ui/icons
@xihan-ui/vue @xihan-ui/web-components @xihan-ui/chat-stream
@xihan-ui/markdown @xihan-ui/code-highlight @xihan-ui/backgrounds
@xihan-ui/sound @xihan-ui/motion @xihan-ui/animations
@xihan-ui/pointer @xihan-ui/viz两条后果:
- 某个包的 major 可能不含任何针对它自身的变更。
@xihan-ui/markdown升到2.0.0可能只是因为@xihan-ui/vue修改了一个 prop。判断某次 major 是否影响当前项目,以 更新日志 的分类条目为准,不以版本号跨度为准。 - 不混装版本。
@xihan-ui/vue与@xihan-ui/headless版本不一致时类型无法对齐;@xihan-ui/web-components更严格:同一个xh-标签被两个版本注册会直接抛错。适配器与其兄弟包目前是普通dependencies,包管理器不会阻止这种组合,需要使用方自行保证(见尚无门禁的条款)。
一、JS / TS 导出面
18 个包中 18 个出 JS 或类型入口,共 36 个带类型的入口。
受约束
| 类别 | 数量 | 说明 |
|---|---|---|
| 包名 | 18 | 把代码从一个包移到另一个包 = major |
exports 子路径 | 36 个 JS 入口 | 如 @xihan-ui/vue/backgrounds、@xihan-ui/web-components/define、@xihan-ui/core/metadata。没有 ./* 通配,深路径引用(.../dist/xxx.js)会被 Node 与打包器拒绝,这些路径不是 API |
Vue 组件导出 Xh* | 1056(136 个家族) | XhButton、XhSelectRoot、XhSelectItemIndicator |
Vue 组合式函数 use<家族> | 104 | useSelect、useCombobox。不使用库内部件、自行编写标记时的唯一入口 |
| Vue 指令 | 2 | vBackground(@xihan-ui/vue/backgrounds)、vSound(@xihan-ui/vue/sound),两个子入口各依赖一个可选 peer |
无头内核 connect* | 136 | connectAccordion 及其参数顺序、返回的 getter 名 |
无头内核 *Machine | 80 | 机器 schema 的形状 |
类型 *Props / *Api / *ChangeDetails / *Schema | 145 / 120 / 103 / 80 | 删字段、改字段名、把可选改必填都是 major |
| Vue prop 名与未传入语义 | 373 个不同名字 / 1335 处声明 | 见下方说明 |
| Vue emit 名集合 | 67 个不同名字 / 243 处 | 名字受约束(列在 emits 中的事件不经 $attrs 透传,删除名字会静默改变行为) |
| Vue 插槽名 | 12 | default、item、label、trigger、panel、content、empty、indicator、mark、cell、split、tooltip |
Vue prop 的未传入语义也是契约
disabled 声明为裸 Boolean(未传入恒为 false)还是 { type: Boolean, default: undefined }(未传入交由底层状态机决定),在类型上几乎没有差别,但决定了不传该 prop 时组件的表现。仓库中 202 处裸 Boolean、180 处三态、12 处 default: true、1 处显式 default: false。把某个 prop 从一种改为另一种是 major,即使名字和类型都未变。
只增不减
| 类别 | 数量 | 说明 |
|---|---|---|
Vue 上下文类型 *Context / *Callbacks | 108 | 用于给透传的 api 标注类型。只保证可读,不保证可构造:新增可选字段不算破坏,因此不要写 const c: SelectContext = { … } 这类字面量赋值 |
custom-elements.json(CEM) | 1 份 / 138 个元素 | 已经进过清单的 tagName / attribute / event 条目不会消失;cssProperties(皮肤覆盖槽)与 events 的 type(detail 类型)由 scripts/enrich-cem.mjs 从皮肤与元素源码生成,gate:cem 校验同步。字段结构细节仍不承诺,补充算 minor |
排除
| 类别 | 数量 | 排除原因 |
|---|---|---|
@xihan-ui/headless 的内部算子与常量 | 625 | clampRating、buildMonthGrid、colorHexToRgba、CAROUSEL_AUTOPLAY_INTERVAL 等。它们是内核实现的一部分,实现变化时签名随之变化。所需默认值应从组件 props 的文档默认值读取,不应 import 常量再自行比对 |
@xihan-ui/headless 的内部伴生类型 | 564 | *Refs(机器持有的 DOM 引用袋,46 个)、ColorHsva、CascaderLevel 等,是上述函数的参数与返回类型 |
xxxAnatomy / xxxMeta / xxxKeyboard 三组导出对象 | 各 136 | 它们描述的 part 名单受约束(见第二节),但这三个对象本身的组织方式不受约束。部件名单以组件文档页的解剖表为准,不应 import 这些对象 |
Vue 的 provide* / use*Context 函数 | 115 | Vue 适配器 已写明父子组件之间的 provide / inject 是内部实现,不对外开放。需要下探时使用 use<家族>() |
Vue 的 useTimelineItem | 1 | 名字形似组合式函数,实际是 inject 管道,与上一行同类 |
| 适配器运行时底座 | Vue 3 个、WC 8 个 | createVueRuntime / createVueIdGenerator / vueNormalize;createLitRuntime / createSpreader / defineElement / discoverParts / wcNormalize / MachineController 等。这些是适配器与内核之间的接缝,签名依赖的类型未从同一个包导出,无法构造调用 |
WC 的元素类导出 Xh*Element | 110 | 仅用于 instanceof 与手动 customElements.define,不支持 extends(基类不导出、wire() 是 protected abstract)。获取元素使用 document.querySelector |
WC 元素上的 static partContract | 136 | 部件校验的输入数据,实现细节 |
dist/ 内部文件名 | — | element-Bx4xCiT2.js 这类打包 chunk 每次构建都可能变化,不得 deep import |
.d.ts 的文件布局 | — | 类型从包入口获取,不引用具体 .d.ts 路径 |
ui/tooling/、ui/apps/playground-* | — | 不发布 |
二、解剖属性(data-scope / data-part)
这是更换皮肤时最核心的一组名字,也是 Web Components 使用者手写 HTML 时书写的字符串。
| 类别 | 数量 | 档位 |
|---|---|---|
data-scope 取值(组件身份) | 136 | 受约束(新增第 137 个组件是 minor) |
data-part 取值(部件名) | 290 个不同名字 / 1103 条「组件 × 部件」配对 | 受约束 |
data-xh-part(WC 作者书写的角色声明) | 属性名 1 个,取值即上面 290 个 | 受约束 |
meta.requiredParts(必备部件) | 300 条 | 受约束(加条目 = major),方向见下 |
data-scope 的取值与三处完全同名,不做任何转换:headless 目录名、自定义元素标签 xh-<scope>、皮肤文件 <scope>.css。改动一处即四处同时破坏。
data-xh-part、data-xh-scroll、墨色域与液态下层的声明是 data-xh- 前缀里仅有的例外
其余 data-xh-* 属性(data-xh-scrollbar、data-xh-focus-guard、data-xh-inert-exempt 等 53 个)是库自用标记,排除在承诺之外。例外都由作者书写:data-xh-part 是 Web Components 适配器唯一的作者输入 API;data-xh-scroll 只承诺「作者容器取得 reset 层的原生细条」(见皮肤与样式分层),不进任何组件契约,删除或改名同样按公开面破坏处理;data-xh-ink 与 data-xh-ink-margin 写在作者自己的彩色区块上,声明底色极性与余量(见色彩 · 彩色面与墨色域),取值与语义按公开面承诺;data-xh-backdrop 与 data-xh-backdrop-busy 写在作者自己的图片、视频、画布区域上,声明液态面下层的明暗与杂乱(见设计令牌与主题 · 液态材质),同样按公开面承诺。作者书写 data-xh-part="trigger" 是声明,元素接线后在同一节点写入 data-scope + data-part 是事实。皮肤匹配后者,后者不应手写。
requiredParts 的方向是反的
向 requiredParts 新增一条是收紧:原本可运行的 Light DOM 开始在 诊断通道 报 wc.missing-part。规则:
- 删条目:任何版本都可以。
- 加条目:属于破坏性变更,只出现在 major,并且落地即按
error报告。缺少必需角色节点恒发error,没有先以warn过渡一个版本的档位。
三、data-* 状态属性
connect 一共产出 237 个不同的 data-* 属性名、1662 条「组件 × 属性」配对。分两类。
受约束
行为契约。这些不是样式钩子,删除会使功能静默失效:
| 属性 | 作用 |
|---|---|
data-value | 集合型组件的条目值。行为层按它反查节点执行方向键导航,Web Components 使用者需手写在 item 节点上。删除后所有集合型组件的键盘导航静默失效 |
data-name | 表单字段名(form) |
data-index | 条目序号(0 基) |
样式钩子。自带皮肤消费了 183 个属性名 / 793 条「皮肤 × 属性」配对(不含解剖的 data-scope / data-part),第三方皮肤参照的就是这一组:
| 属性 | 选中它的皮肤份数 |
|---|---|
data-size | 79 |
data-state | 64 |
data-disabled | 58 |
data-variant | 32 |
data-invalid | 27 |
data-orientation / data-readonly | 各 26 |
data-placement | 22 |
data-highlighted | 20 |
data-hidden / data-motion | 各 18 |
data-dragging | 13 |
data-tone | 10 |
data-current / data-placeholder / data-empty / data-clearable … | 其余 |
data-cols-sm / -md / -lg / -xl | 仅 grid,断点分档名受约束 |
份数低不等于使用少:data-tone 的规则集中在 tone.css 一份文件中,浮层的 data-placement 同样集中在定位块中,逐份皮肤只在需要额外微调时才单独选中它们。属性名与取值的约束不按份数区分。
取值同样受约束。属性名变更会破坏皮肤,取值变更同样破坏且更隐蔽:[data-state='open'] 在取值改为 expanded 之后仍是合法 CSS,只是永远不匹配。自带皮肤当前用到的 34 个 data-state 取值全部受约束:
open closed checked unchecked indeterminate on
active current incomplete visible hidden ready empty invalid error
completed preparing finishing dismissing picking copying取值的真源是 tooling/scripts/state-vocabulary.json,按族登记了 7 个族共 38 个取值:同族互斥,一个部件同一时刻只取其中一个。上面 21 个是皮肤当前选中的取值,其余只在 DOM 上出现,尚无对应的皮肤规则。check-state-vocabulary 双向校验:connect 发出词汇表外的值报错,皮肤选中词汇表中不存在的值同样报错。
三条视觉轴的取值同理。data-tone 的合法值是 6 个,唯一真源是 tone.css:
brand neutral success warning danger info排除
其余 45 个 data-*(data-lane、data-one-way、data-checked-count、data-sticking、data-file-size、data-modules 等):自带皮肤未选中它们,组件文档页也未记载。check-state-vocabulary 每次都会列出这批发射但零引用的属性名(连同上面 data-value / data-index 两个数据载体,共 47 个)。它们在 DOM 上存在,但不承诺稳定,随时可能改名或消失。需要依赖它们编写样式时,先提交 issue。
四、CSS 自定义属性与 @layer
受约束
| 类别 | 数量 | 说明 |
|---|---|---|
@layer 名与声明顺序 | 5 | xihan.reset → xihan.tokens → xihan.motion → xihan.components → xihan.overrides。改名、调序、增删中间层均为 major。xihan.overrides 刻意留空,专供使用方覆盖,不会作为未使用的层被清理 |
| 全局令牌 · 原语层 | 115 | --xh-color-brand-500、--xh-space-4、--xh-radius-md。皮肤中不应直接使用它们,但接入品牌轴必须写 --xh-color-brand-*,因此它们是公开的 |
| 全局令牌 · 语义层 | 302 | --xh-bg-brand、--xh-fg-on-brand、--xh-control-h-md、--xh-shape-control。主题定制的正门,见 设计令牌与主题 |
| 组件覆盖槽 | 4088(覆盖 135 个组件) | --xh-button-bg、--xh-button-h、--xh-dialog-max-w。全部写成 var(--xh-x-y, 默认值) 形态,在 :root 中设置即可修改该组件 |
| 语气轴槽 | 12 | --xh-_tone、--xh-_tone-on、--xh-_tone-hover、--xh-_tone-subtle、--xh-_tone-border 等。这是自定义语气的唯一机制:写入 [data-tone='premium'] { --xh-_tone: gold; --xh-_tone-on: #000 },读取这批槽的 58 份皮肤随之生效。虽然带下划线前缀,但按受约束处理 |
| 关键帧名 | 40 | xh-pop-in、xh-fade-out、xh-spin。共享关键帧住在 family/motion.css(子入口 @xihan-ui/styles/motion.css),皮肤 @import 它;组件专属关键帧仍在各皮肤。在 xihan.overrides 层中重定义同名关键帧即可替换该段动画(关键帧名因此是公开面),所以改名与删名同样是 major |
| 跨包内联属性 | 4 | --xh-_truncate-lines、--xh-_float-button-offset、--xh-_tour-spotlight-radius、--xh-_carousel-autoplay-duration。由 headless 写入内联 style,皮肤必须读取。整套更换皮肤时若不读取这些值,文本截断、浮动按钮贴边、引导目标圆角或轮播进度会失效,且不报任何错误 |
@xihan-ui/styles 的 CSS 子路径 | 160 | .、./index.css、./index.unlayered.css,家族文件 ./action-control.css、./chart.css、./collection-item.css、./field-chrome.css、./swatch.css、./material.css、./motion.css,与 148 条 .css:136 份组件皮肤加 ./layers.css、./tone.css、./reset.css、./overlay-arrow.css、./visually-hidden.css、./undefined.css、./focus.css、./label.css、./description.css、./pointer.css、./forced-colors.css |
@xihan-ui/tokens 的 CSS 子路径 | 2 | ./tokens.css、./tokens.json |
排除
| 类别 | 数量 | 排除原因 |
|---|---|---|
其余 --xh-_ 私有槽 | 465 | 皮肤内部的回退中转(--xh-_bg、--xh-_bg-hover、--xh-_mention-py 等),变体只改槽位、不重写规则依赖它。不应在外部设置它们 |
| 令牌的取值 | — | --xh-color-brand-500 这个名字受约束,其对应的 oklch() 值不受约束。调色板随视觉迭代变化,这正是令牌存在的意义 |
index.css 的内部结构 | — | 它是生成的扁平文件:家族配方只内联一次、排在皮肤之前,皮肤段的顺序即源序。段标记注释(/* styles/xxx.css */)只作阅读定位,不是承诺 |
index.unlayered.css 的内部结构 | — | 它是同一源序的扁平镜像,不带 @layer。使用该入口时没有 xihan.overrides 覆盖槽位,层名承诺不适用 |
命名前缀不能反推归属
--xh-field-py 形似 field 组件的覆盖槽,实际是全局语义令牌,field.css 本身并不使用它。同理 --xh-text-*(13 个全局文本令牌)与 text-field 的 48 条组件槽同前缀,--xh-color-*(43 个原语调色板令牌)与 color-picker 的 70 条组件槽同前缀。判断一条属性属于哪一档,看它在不在上表列的那 660 个全局令牌里,不按前缀推断。
五、自定义元素与 attribute
| 类别 | 数量 | 档位 |
|---|---|---|
自定义元素标签 xh-* | 139(defineXhElements() 注册 138 + xh-background) | 受约束 |
| 注册函数 | 2(defineXhElements、defineXhBackground) | 受约束 |
| observed attribute | 1357 条声明 / 393 个不同名字 | 受约束(具体元素上的具体属性名) |
| attribute 名词汇表本身 | 393 | 只增不减(新组件复用 size / tone / dir 不算破坏) |
CustomEvent 名 | 96 个名字 / 210 条「元素 × 事件」 | 受约束 |
| 事件传播语义 | bubbles: true, composed: true(195 处中 193 处) | 受约束。取消冒泡会使祖先节点上的事件委托静默失效。例外是名为 submit 的事件(xh-prompt-input / xh-question-flow):与原生表单提交同名,一律不冒泡,避免被祖先 <form> 视为自身的提交 |
事件 detail 形状 | 188 个 *Details 类型 | 受约束,等同于 headless 的同名类型 |
attribute: false 的 JS 字段 | 223 条(涉及 76 个字段名) | 受约束。collection、translations、validate、filter 等只能通过 JS 赋值,HTML 中无法表达:不是每个 property 都有对应 attribute |
| 命令式方法 | 97(分布在 50 个元素) | 受约束,含参数与返回类型 |
命令式方法全清单:
| 元素 | 方法 |
|---|---|
xh-form | setFieldValue setFieldError clearErrors submit reset getFieldId getFieldValue getFieldError |
xh-notification | create updateItem dismiss dismissAll getItemsByPlacement |
xh-file-upload | openFilePicker setFiles addFiles deleteFile clear |
xh-timer | start pause resume reset |
xh-virtualizer | scrollToIndex measureElement measure |
xh-signature-pad | clear toSvg |
xh-log | scrollToBottom |
xh-tour | remeasure |
布尔 attribute 是三态的
modal="false" 表示关闭,不写表示交由组件决定,这与原生 HTML 布尔属性(写出即为真)不同。把三态转换器换回原生语义是不改名字的语义收窄,按 major 处理。
以下与包结构有关的事实同样在承诺内:
- 元素在
@xihan-ui/web-components/define,不在包主入口。defineXhElements()是全量注册:调用即注册全部 138 个元素,没有逐个的defineXhButton()。(补充细粒度 define 是 minor;补充后defineXhElements的全量语义即成为承诺。) xh-background单独在@xihan-ui/web-components/backgrounds,因为它依赖可选 peer。把它移入./define会强制所有使用方安装 WebGL 引擎,属于破坏性变更。- 元素全部是 Light DOM,没有 shadow root,
::part()不生效。CEM 中的cssParts条目在本包读作data-xh-part,不是 shadow part。
六、什么算破坏性变更
正例(major)与反例(minor / patch)对照,按介质各举数条。
JS 导出
| 算破坏 | 不算破坏 |
|---|---|
删除或改名任一 Xh* 组件(含 XhTreeSelectBranchIndicator 等低频部件) | 新增组件、新增部件 |
connectAccordion 的返回值把 getRootProps 改成 getProps | 给 connectAccordion 加一个新的 getter |
SelectProps 新增一个必填字段,或把可选字段改为必填 | SelectProps 新增可选字段 |
SelectApi 删除一个 getter | SelectContext 新增一个可选字段 |
把 useSelect() 的返回从 ComputedRef<Api> 换成其他容器 | 新增 useXxx |
把某个 prop 从裸 Boolean 改成 default: undefined | 给某个 prop 补文档 |
从某个组件的 emits 数组中删除一个名字 | 向 emits 新增一个名字 |
解剖与 data-*
| 算破坏 | 不算破坏 |
|---|---|
data-part 从 indicator 改成 marker | 给某个组件新增一个可选部件 |
把 item-text 改成 itemText(分词/大小写变化) | 皮肤内部改用其他选择器组合实现同样的视觉 |
某个组件不再输出 data-size / data-tone | 给某个组件补上原本没有的 data-tone |
data-state 的取值从 open 改成 expanded | 新增一个 data-state 取值 |
把 data-disabled 从「存在即禁用」改成 data-disabled="true" | — |
删除 data-value | 删除 data-lane(在排除清单中) |
CSS
| 算破坏 | 不算破坏 |
|---|---|
--xh-button-h 改名成 --xh-button-block-size | 新增 --xh-button-letter-spacing |
删除 --xh-radius-xl(即使库内部未使用) | 调整 --xh-color-brand-500 的 oklch() 值 |
把 var(--xh-alert-px, …) 改为直接写语义令牌(等于取消该覆盖点) | 给某条规则补充 @supports 兜底 |
调整 @layer 声明顺序 | 在 xihan.components 里加规则 |
删除 ./tone.css 子路径 | 新增 ./<新组件>.css 子路径 |
自定义元素
| 算破坏 | 不算破坏 |
|---|---|
xh-empty-state 改名成 xh-empty | 新增 xh-xxx 元素 |
某元素的 observedAttributes 减少一条 | 给某元素新增 attribute |
把 read-only 改写成 readonly | 把某个 attribute: false 的字段反向暴露成 attribute |
事件名 value-change 改成 change | 新增事件名 |
把某个事件的 bubbles 改成 false | 补 HTMLElementEventMap 类型增强 |
xh-notification.create() 从返回 string 改成返回对象 | 给 xh-form 新增一个方法 |
把某个可选 part 提升为 requiredParts | 新增一个可选 part |
支持面
| 算破坏 | 不算破坏 |
|---|---|
| 抬高 Node 下限 | 降低 Node 下限 |
| 抬高浏览器硬底线(使用一个无兜底的新 CSS 特性) | 新增一个带退化路径的增强特性 |
收窄 vue peer 区间(加上限、抬下限) | 放宽 peer 区间 |
| 撤销 ESM 导出条件 | 增加 CJS 导出条件 |
七、移除流程
本库不设废弃期,也不保留兼容层。改名直接改,删除直接删:不为旧写法保留别名、不把旧名放入兜底链、不在源码中标注 @deprecated 延长存续。在一个 major 中读到的名字就是当时唯一存在的名字。
移除动作集中在 major,一律出现在更新日志顶部、单列一节,逐条写明旧名与替换写法,这是唯一的迁移材料。
四种介质需要单独说明:data-*、CSS 自定义属性、@layer 名、元素 attribute 没有 IDE 提示,改名之后对应选择器只会静默失配,不报错也不降级。它们唯一的告知渠道是更新日志,因此每次移除都在 changeset 中逐条列出旧名与替换写法,使用方可据此在自己的代码库中全文搜索。
八、支持面
收窄支持面按破坏性变更处理,放宽随时可以。
运行时
| 项 | 值 | 变更规则 |
|---|---|---|
| Node(安装并运行本库) | ≥ 18 | 抬高 = major。产物实际用到的最高特性是 Object.hasOwn(Node 16.9),>=18 留了余量 |
| Node(开发本仓库) | ≥ 24,pnpm ≥ 11 | 与上一行无关,随时可变 |
| 模块格式 | ESM only | 无 CJS、无 node10 解析条件。包中 main / module 字段存在但指向 ESM 文件,是给旧式 bundler 的别名,不代表可以 require |
| 包管理器 | 无要求 | — |
浏览器
硬底线由三个无兜底的 CSS 特性决定:@layer(125/125 个样式文件使用,不识别它的解析器会丢弃整块)、oklch()(tokens.css 中 63 处,是唯一颜色来源)、color-mix()。
| 浏览器 | 硬底线 | 完整保真 |
|---|---|---|
| Chrome / Edge | 111 | 111 |
| Firefox | 113 | 121(:has()) |
| Safari | 16.2 | 16.4 |
测试承诺:当前正式版,外加 Chromium 与 Safari 各向前两个大版本、Firefox ESR。硬底线只在 major 中抬高。
低于硬底线不会降级,而是无样式:皮肤中刻意不写兜底值,令牌缺席是缺陷而不是降级。
高于硬底线又无从兜底的特性,皮肤一律不用,由 check-css-floor 的拒绝名单拦截(@scope、@starting-style、嵌套选择器等)。其中容易误写的一条:
| 特性 | 最早完整支持 | 底线内的引擎不认时 | 皮肤的写法 |
|---|---|---|---|
媒体查询区间写法 (width < 768px) | Chrome 104 / Firefox 63 / Safari 16.4 | Safari 16.2 / 16.3 把整条查询判作不成立,块内规则静默丢失 | 断点只写 (min-width: …),窄档专属规则写它的补集 not all and (min-width: …) |
可选增强层(不支持时按下表退化;新增增强特性不算破坏,前提是同时提供退化路径):
| 特性 | Chrome | Firefox | Safari | 不支持时 |
|---|---|---|---|---|
:has() | 105 | 121 | 15.4 | 少量间距/gutter 微调失效 |
scrollbar-gutter | 94 | 97 | 18.2 | 滚动条出现时轻微位移 |
dvh / svh / lvh | 108 | 101 | 15.4 | 回落到 vh |
text-wrap: balance | 114 | 121 | 17.5 | 标题按默认换行 |
light-dark() | 123 | 120 | 17.5 | 代码块语法色退化成单色 |
field-sizing | 123 | 152 | 26.2 | prompt-input 输入框退化成固定行数 |
Web Components 侧不构成额外约束:全部 Light DOM,不使用 shadow DOM、ElementInternals、adoptedStyleSheets,平台要求只到 Custom Elements v1。
宿主框架
| 包 | peer | 规则 |
|---|---|---|
@xihan-ui/vue | vue: ^3.5.0 | 下限由 useId 决定(Vue 3.5 引入)。收窄为 major,放宽为 minor |
@xihan-ui/vue、@xihan-ui/web-components | @xihan-ui/backgrounds(optional) | 不使用背景效果时无需安装 |
@xihan-ui/vue | @xihan-ui/sound(optional) | 不使用音效时无需安装 |
@xihan-ui/web-components | 无框架 peer | 原生自定义元素,不要求任何框架 |
| 其余 15 个包 | 无 | — |
九、包的稳定性分级
锁步发版意味着版本号不反映稳定性,因此单独列表。
下面两张表覆盖全部 18 个包,每个包必在其中一张中;门禁比对两张表的包名与 packages/ 下的公开包,新增包未定级即构建失败。
稳定
破坏性变更只出现在 major,移除按上文流程执行。
| 包 | 说明 |
|---|---|
@xihan-ui/vue | 1056 个组件、104 个组合式函数 |
@xihan-ui/react | 组件与 hooks(铺开中,公开面随批次增长) |
@xihan-ui/web-components | 139 个自定义元素 |
@xihan-ui/headless | connect* / *Machine / 各类公开类型;内部算子在排除清单里 |
@xihan-ui/styles | 136 份组件皮肤、5 个层名 |
@xihan-ui/tokens | 660 个令牌名,外加 ./runtime 的主题控制器与种子色 API |
@xihan-ui/icons | 图标集 |
@xihan-ui/core | 只有被适配器与 headless 公开消费的那部分(createAnatomy、createNormalizer、归一化规则、状态机公开面),含 data-value 这条集合导航契约;另有 ./date 子入口的全部导出(PlainDate / PlainTime / PlainDateTime、时区换算、周规则、边界函数与格式化器) |
@xihan-ui/position | createPositionEngine 与它的选项;其余 9 个导出是内部算子 |
@xihan-ui/motion | 缓动名、时长常量、animate、补间与弹簧算子。三值与令牌层同源(check-motion-source 比对),core / headless 与各适配器均建立在它之上,改名会先破坏库自身 |
@xihan-ui/pointer | createPointerSession / createMultiPointerSession 与四层几何纯函数。headless 与各适配器的拖拽、缩放、划动全部经由它,同上 |
实验
这七个包的破坏性变更可以出现在 minor 中,不适合用于不易升级的生产代码。
| 包 | 仍在变化的原因 |
|---|---|
@xihan-ui/viz | 图表组件按期接入,比例尺、形状、场景与过渡的签名随组件落地仍会调整 |
@xihan-ui/code-highlight | 承诺面是 HighlighterPort 端口,自研分词器(tokenizeCode、langSpecOf、LangSpec)随时可能整体替换 |
@xihan-ui/markdown | 公开面是 createStreamRenderer 与它的三个类型,外加 blockKind / fenceLang / isFenceClosed / LIVE_BLOCK_KEY 四个切块算子;解析、切块、缓存的中间件随时可能变化 |
@xihan-ui/chat-stream | AI 会话协议类型(UIMessage、TextPart 等)跟随上游生态演进 |
@xihan-ui/backgrounds | 效果参数与点云 API 仍在调整;通用短名(bool / num / str / rgb)不是公开 API |
@xihan-ui/animations | 公开面是 MotionSpec 的字段与 BUILTIN_MOTION_NAMES 里的预设名。预设表随组件接入继续调整,预设改名没有门禁拦截:它不是导出名,删除不会被报告 |
@xihan-ui/sound | 同上:SoundSpec 的层与包络字段、三套主题、BUILTIN_SOUND_NAMES 中的预设名仍在调整 |
门禁覆盖范围
已由门禁保证
六种介质的改名即 major 已有门禁保证。pnpm gate:surface 运行的 check-public-surface 以入库基线(ui/tooling/public-surface.json,16366 个名字)比对当前状态: 基线中有而当前没有,即为删除或改名,构建失败。新增一律放行,因为新增是 minor。
覆盖:包名与 198 条子入口、8513 个导出名、136 个 data-scope 与 1103 条部件配对、 136 个组件的 1763 个 prop 名、239 种 data-*、34 个 data-state 取值、660 个令牌、 5 个 @layer 名、4088 个组件覆盖槽、138 个自定义元素及其 attribute 与事件。
prop 名一维是后补的:在它加入之前,修改一个 prop 名(实测 transfer 的 items 改 collection、splitter 的 size 改 sizes)其余门禁全程沉默。它的事实源是无头内核的 <组件>Schema['props'],各适配器的 props 均按它展开。
它的必要性可以复现:把 switch 的 thumb 改名 knob,其余门禁全部通过, 只有这一道拦截;把 switch 的 checked 改名 isChecked,同样只有它报出 「组件 prop switch: checked」。删除一个语义令牌、修改一个组件覆盖槽名同理。
确需破坏性变更时,运行 pnpm surface:update 更新基线并在 changeset 中说明: 门禁拦截的是无意删除,不是有意的 major。
基线锁定的名字比本页承诺的范围宽
基线是所有可写出名字的机械快照,不区分档位:本页归入排除的成员同样在内,包括 headless 的 625 个内部算子与常量、564 个伴生类型、*Anatomy / *Meta / *Keyboard 三组导出对象, 以及六个实验包的全部导出。政策允许随时删改它们,但删除仍会触发门禁。 行使这份删除权时先运行 pnpm surface:update 更新基线,这不是破坏性变更, changeset 按 patch / minor 记录即可:更新基线这一动作本身不代表 major。
另有三道门禁把原本依赖自觉的条款转为机器检查。check-css-floor 保证浏览器硬底线: .browserslistrc 记录底线,拒绝名单拦截 @scope、媒体查询区间写法等无兜底的抬底线特性, light-dark() / dvh / lh 必须带级联兜底。check-version-lock 保证 18 包锁步:任何一个 package.json 的 version 与其余不同,门禁直接失败。check-wiring 保证检查系统自身:新增的 check 脚本未接入 pnpm gate 等同于未编写,死引用同样被拦截。
三条视觉轴已收敛为联合类型,tone / size / variant 不再是裸 string, 写错取值在编译期报错。Vue 事件载荷已有类型,101 个组件的 emits 全是对象式, 产物 .d.ts 中不再出现 (...args: any[]) => any。
尚无门禁的条款
以下条款目前没有门禁。
| 条款 | 现状 | 计划补充的机制 |
|---|---|---|
| Vue 作用域插槽载荷 | 带载荷的插槽已声明 slots: 选项,由 check-slot-types 门禁的四条判据保证(缺声明 / 键非可选 / 值非函数 / 声明未用)。仅渲染无载荷插槽的部件仍不声明,消费方写错 slot 名不会报错 | 无载荷插槽也补充声明,或明确只有带载荷的插槽进入契约 |
移除提示(CSS / data-* / attribute / 层名) | 这四种介质没有 IDE 提示:名字移除之后,消费方的声明只是静默失配,既不报错也不降级。唯一的告知渠道是更新日志,因此每次移除都在 changeset 中逐条列出旧名与替换写法,供使用方在自己的代码库中全文搜索 | 无 |
| 浏览器硬底线 | 已落地:.browserslistrc 记录硬底线,check-css-floor 门禁拒绝抬底线的无兜底特性(@scope、媒体查询区间写法等),并校验 light-dark() / dvh / lh 的级联兜底 | 拒绝名单改动时联动本页支持面表格的提醒 |
| 「18 个包必须同版本」 | check-version-lock 门禁保证 18 个 package.json 同版本。运行期一侧只在 dev 有提示:三个适配器启动时调用 checkLockstepVersion,自身版本与 core 不一致时经诊断通道发出一条 warn;生产构建中跳过,且只比对适配器与 core 两个版本,不是全部 18 个 | 提升为 peer,或把运行期比对扩展到全部包 |
本页内容与实际行为不符时按缺陷处理,请提交 issue。
