Skip to content

更新日志 ​

本页由 Changesets 在发版时自动生成,不要手写。

所有公开包锁步发版、共用一个版本号,因此这里是全部包的统一变更记录。

0.1.1 ​

Patch Changes ​

  • 基础组件包的设计变量已包含在全局样式中,安装时不再额外拉取独立的 tokens 包。

0.1.0 ​

Minor Changes ​

  • 30f0008: 为后续迁入的组件准备图标与文案底座,现有组件的渲染结果不变。

    • 新增内部模块 utils/icons.tsx:16 个 lucide 图标(lucide-static@1.47.0,ISC)与渲染辅助 icon(name, { size }),统一 viewBox 24 / currentColor / 描边 2 / 圆头圆角 / aria-hidden。不从入口导出、不做 pt-icon 组件 —— 公开的图标仍由使用方经 slot 传入。现有组件的内联 SVG 都是 16 格自绘图形,与 lucide 不一致,本次不替换。致谢见根目录 NOTICE。
    • LocaleMessages 新增 24 个 key(combobox / command / pagination / breadcrumb / carousel / sidebar / scroller / promptPicker / toast / calendar / otp / resizable / attachment),zh-CN 与 en 齐全。经 registerLocale() 注册的自定义 locale 不受影响(接受 Partial,缺的 key 落回英文;显式传 undefined 的 key 现在也按缺失处理);但用 LocaleMessages 类型标注完整文案对象的代码会因缺这 24 个 key 编译失败,改成 Partial<LocaleMessages> 交给 registerLocale() 即可。
    • t() 新增可选第三个参数做 {name} 占位符插值,并导出类型 MessageParams。
  • 1f0f8a3: pt-button 补齐两处 API:

    • 新属性 download:透传给内部的 <a download>,只在有 href 且未禁用 / 未加载时生效。 空字符串同样透传(download="" 表示用默认文件名),不写才是正常导航。
    • 新 CSS 变量 --pt-button-padding-x:覆盖三档尺寸的默认横向内边距(sm 8px / md 12px / lg 16px), 在使用处兜底,设在祖先容器上即可作用到一排按钮。纯图标按钮与 link 变体恒为 0,不受它影响。

    不设这个变量时样式与之前逐条相同。

  • 4adc77e: 批 1 · C1.3:pt-badge 补齐可点形态,对齐源包 badge。

    • 新增 href / target / rel:给了 href 内部渲染成 <a class="base" part="base">;target="_blank" 且没给 rel 时自动补 noreferrer noopener(与 pt-button 一致)。
    • 新增 interactive(布尔,反射):内部渲染成 <button type="button">,点击监听宿主的原生 click。有 href 时视同打开。
    • 可点形态才挂状态层(default 用反转版,其余变体用常规版)、手型光标、焦点环与移动端触控外扩; 1px 透明边框按描边宿主处理(--pt-state-layer-border: 1px)。纯展示的 <span> 外观与行为不变。
    • 影子根开启 delegatesFocus,可点形态下 element.focus() 会落到内部的 a / button 上。
  • 793f852: pt-input 补齐搜索框与日期类(批 1 · C1.4):

    • type 新增 date / time / datetime-local / month / week,原样透传给原生 input,min / max / step 按原生语义生效。
    • type="search":前缀位置内置放大镜(prefix 插槽的后备内容,给了 prefix 就替换掉),内部 input 标 role="searchbox",去掉原生清除 ✕ 与装饰;有值时按 Esc 清空(先 ptClear 再 ptChange('')),并截住这次 Esc 不让它冒泡关掉外层弹窗,值为空时不拦。
    • 新属性 clear-label(clearLabel):清空按钮的读屏文案,优先于 locale 注册表的 input.clear。
    • 新 CSS 变量 --pt-input-border-width(默认 1px),供输入组拼接时去掉单个输入框的描边。
  • f2da3ab: pt-select 家族补齐(批 1 · C1.5,对应 design-components 的 SelectGroup / SelectLabel / SelectSeparator):

    • 新增子元素 pt-option-group:label 属性渲染一行不可选的组标题(14/20 半粗,左右 8px、上下 6px), 影子树里是 role="group" + aria-labelledby 指向组标题,构成 listbox > group > option。 part base / label。组内选项照常参与选中、方向键与首字母跳转,组标题不参与匹配; 组内增删选项时会通知所属 pt-select 重新同步。
    • pt-select 的列表里可以放 pt-separator(直接放或放在组里都行):上下 4px 间距、左右贯通弹层边缘 (窄屏抽屉下同样贯通),颜色默认换成 --pt-bg-secondary(源包的 bg-muted),在分隔线自身上设 --pt-separator-color 仍可覆盖。
    • pt-option 新增 keywords(空格分隔的搜索关键词):只存储、反射,不影响渲染,留给可搜索下拉匹配用。
    • pt-select 按宿主的书写方向定位列表:RTL 下 align: start 对齐到触发器右边缘(打开时读一次, 显式传给 openFloating 的 dir)。新增内部工具 utils/dom.ts 的 resolveDir()。
    • 全局样式表的 FOUC 段补上 pt-option-group 的 display。
  • a43f8a8: pt-modal 补齐 Dialog / AlertDialog 的能力:

    • 新属性 alert:确认框形态。面板 role="alertdialog"、遮罩用 --pt-bg-overlay-strong、标题与说明间距 8px、 默认不显示右上角关闭钮,点遮罩永远不关(close-on-overlay 对它无效),Esc 仍然关。 不叫 role:那是宿主元素的原生 ARIA 属性,同名 prop 会把宿主自己也变成一个 alertdialog。
    • hide-close 改为「不设置时跟随形态」:普通弹窗显示、确认框隐藏;显式 false 可以让确认框也显示 ✕。 普通弹窗的行为不变。
    • 新属性 close-label:关闭钮的 aria-label,优先于内置文案 t('modal.close')。
    • 新属性 auto-focus(默认为真):为 false 时打开不移动焦点,焦点陷阱仍然生效 —— 焦点在弹窗外时按 Tab 会被拉进弹窗(点过遮罩空白处之后同样适用,以前这时 Tab 会跑到页面上)。
    • 新事件 ptRequestClose(可取消,detail: { reason: 'close-button' | 'escape' | 'overlay' }): 用户关闭之前触发,preventDefault() 则不关、也不发 ptClose。hide() 与改 open 不经过它。
    • 导出类型 PtModalCloseReason / PtModalRequestCloseReason。
    • 遮罩打开时进顶层(showInTopLayer),不再受祖先 transform / overflow / 层叠上下文影响; 不支持 popover API 时退回原来的 position: fixed + --pt-z-overlay。
    • 面板在没有 aria-label 时以 title 插槽为名(aria-labelledby),并以 description 插槽为说明(aria-describedby)。
    • 输入法组合中按 Esc 不再关闭弹窗。
    • 没有关闭钮时(hide-close、确认框)标题不再给关闭钮预留右侧 32px。
  • 1ff40f9: pt-tooltip 补齐:

    • 新增 align(start / center / end,默认 center,反射)与 offset(px,默认 4)。
    • 默认间距由 6px 改为 4px,对齐源包 tooltip 的 sideOffset;气泡离触发元素近了 2px。
    • open 可受控(mutable、反射):外部写入立即开关,不走 delay;disabled 时写 true 回落为 false,显示期间被禁用立即收起。
    • 新增事件 ptOpen(气泡渲染出来后发,交互与属性打开都发)与 ptClose({ reason: 'pointer' | 'blur' | 'escape' },只在用户交互收起时发),约定与 pt-modal 一致。
    • 显示期间改 placement / align / offset 都会按新参数重新定位(原来只有 placement)。
    • 触发元素里输入法组合中的 Esc 不再收起提示。
  • 0c2731f: pt-empty-state 按源包 Empty 补齐:

    • 新增 title 插槽(有内容时替代 heading 文字)与 description 插槽(默认插槽继续作描述,两者可同时用)。
    • 新增 mediaVariant(attribute media-variant,default / icon,反射):icon 把媒体位放进 40px 圆角底色方块、图标 24px。
    • 新增 part media / description / actions。
    • 没有内容的插槽不再留空白(外壳隐藏,内容后加进来时自动显示)。
    • 视觉对齐源包:1px 虚线边框(--pt-border-default)、圆角 10px、内边距 24px(组件自身宽度 ≥ 768px 时 48px,容器查询)、 区块间距 24、标题 18/28 字重 500 text-strong、描述 14/20 text-standard(原来是 13/20 text-weak),描述里的链接带下划线。
    • 宿主改为 width: 100% 的尺寸容器:放进居中的 flex 容器时也占满宽度。
  • 7acc1ed: 新增 pt-alert 提示条(对应 design-components 的 Alert / AlertTitle / AlertDescription)。

    • variant:'default'(默认)| 'destructive',反射到宿主。destructive 下文字、图标、标题走 --pt-text-danger, 边框为 --pt-bg-danger-standard 叠 50% 透明度。
    • 插槽 icon / title / 默认(描述),都可选,空着的不占位。有图标时排成两列网格(16px 图标列 + 12px 间距), RTL 下图标自然落在右侧;插槽里的 svg 定成 16px。
    • 宿主默认 role="alert",宿主上已写了 role 时不覆盖 —— 静态说明性提示条可写 role="status" 避免加载时被播报。
    • part base / icon / title / description。
    • 全局样式表的 FOUC 段补上 pt-alert 的 display。
  • 61de759: 新增 pt-aspect-ratio 宽高比容器(对应 design-components 的 AspectRatio)。

    • ratio:宽 ÷ 高的数字,默认 1,反射到宿主。写在宿主内联的 aspect-ratio 上; undefined(包装层没传)、NaN、0、负数、Infinity 都按 1 处理,宿主高度不会塌成 0。
    • 宿主块级、宽度跟着容器;默认插槽的直接子元素拉满整个区域,默认 object-fit: cover, 子元素上自己写的 object-fit 优先。
    • 比例严格成立:宿主 min-height: 0,内容比区域高时溢出而不是把区域撑高。
    • 全局样式表的 FOUC 段补上 pt-aspect-ratio 的 display。
  • 11c0c40: 新增 pt-avatar 头像(迁自 design-components 的 Avatar):

    • 属性:src、alt、size(sm / md / lg = 24 / 32 / 40,默认 lg,反射)、delay(毫秒,fallback 延迟显示,避免快速加载时闪一下首字母)。
    • 默认插槽是 fallback 内容(首字母或图标),图片加载中 / 失败 / 未给 src 时显示;src 变化重置为加载中。
    • 事件:ptLoad / ptError,detail 为 { src }。
    • 直接渲染 <img>,SSR 输出里就有图片;hydrate 前已加载完(或已失败)的在组件加载时补发事件。
    • fallback 显示时宿主 role="img" + aria-label(取 alt)。
    • part image / fallback,CSS 变量 --pt-avatar-radius。
  • e00ce26: 新增 pt-button-group 按钮组(对应 design-components 的 ButtonGroup)。

    • orientation:'horizontal'(默认)| 'vertical',反射到宿主;拼错或包装层传 undefined 时兜底回 horizontal。
    • 宿主默认 role="group",使用方自己写了 role(如 toolbar)时不覆盖。
    • 直接子元素里的 pt-button / pt-input / pt-select 合并内侧圆角(逻辑属性,RTL 自动翻转), 两个控件直接相邻时后一个叠回 1px 让边框重合;悬停项 z-index: 1、聚焦项 z-index: 2,描边与焦点环不被邻居压住。 组里的 pt-input 吃掉多余宽度;组套组时子组之间留 8px;组里的 pt-separator 颜色换成 --pt-border-input。
    • 合并规则写在全局样式表 ptengine-ui.css 里(影子树内写不出 ::slotted()::part()),依赖三个控件已开放的 part="base"; FOUC 段同时补上 pt-button-group 的 display 与纵向的 flex-direction。
    • 源包的 ButtonGroupText / ButtonGroupSeparator 不单独做元素:分别用普通元素与纵向 pt-separator 代替。
    • Storybook 与文档站改为引入 ptengine-ui.css(此前只引了 tokens.css),示例里的合并效果才看得到。
  • 2eff80c: 新增 pt-card 卡片(对应 design-components 的 Card / CardHeader / CardTitle / CardDescription / CardContent / CardFooter)。

    • 插槽:title、description、actions(标题行尾,RTL 下在左)、默认(内容)、footer。
    • 空插槽不渲染也不留内边距:没有标题 / 说明 / 操作区时不渲染头部,没有页脚时不渲染页脚,只有内容时四周等宽留白。 首渲染前在 componentWillLoad 检测一次,之后跟随 slotchange 与 MutationObserver(文本原地改写、slot 属性改写)。
    • 外观在宿主上:display: block、1px --pt-border-default 边框、--pt-bg-box 背景、--pt-text-standard 正文色; 宿主不写 overflow。标题 16px / 600 / leading-none / tracking-tight / --pt-text-strong,说明 14/20。
    • CSS 变量 --pt-card-padding(默认 24px)、--pt-card-radius(默认 --pt-radius-12); part base / header / title / description / actions / content / footer。
    • 全局样式表的 FOUC 段补上 pt-card 的 display。
  • 18f1dff: 新增 pt-collapsible 折叠面板(对应 design-components 的 Collapsible)。

    • open(反射)+ ptChange<boolean> 受控值:ptChange 只在用户点击触发元素切换时发,外部改 open 不发。 登记进 openControls:Vue 支持 v-model,Angular 支持 [(ngModel)]。
    • @ptengine/ui-angular 新导出 OpenValueAccessor(openControls 这组第一次非空); standalone 组件要在 imports 里显式加上它,ngModel 才找得到 accessor。
    • disabled(反射):点击不再切换,外部改 open 仍然生效;不改触发元素的外观。
    • 插槽:trigger(触发元素,点击切换;键盘由触发元素自己的 click 覆盖)与默认插槽(内容)。
    • 触发元素上维护 aria-expanded 与 aria-controls(指向默认插槽里的 light DOM 内容元素,缺 id 时自动补), slotchange 时新触发元素补上、旧的清掉。
    • 内容区 part content:grid-template-rows 0fr ↔ 1fr 过渡,减少动态效果时关闭;收起时 inert + 过渡结束后 visibility: hidden,Tab 进不去。
    • 全局样式表的 FOUC 段补上 pt-collapsible,收起态在 upgrade 前先藏起内容。
  • 01668b0: 新增 pt-kbd 键盘按键与 pt-kbd-group 按键组合(对应 design-components 的 Kbd / KbdGroup)。

    • 两者都是光 DOM 元素(shadow: false),无属性:宿主本身就是按键框 / 组合容器,子节点原样保留, 样式在全局样式表 ptengine-ui.css,业务 className 可直接覆盖。
    • pt-kbd:行内 flex,高 20px、最小宽 20px、左右内边距 4px、圆角 --pt-radius-sm、底色 --pt-bg-secondary、 12/16 字号行高、medium 字重、--pt-text-weak;不响应指针、不可选中;内部 svg 渲染成 12px。不设 role。
    • pt-kbd-group:行内 flex,间距 4px。
  • 6f797d8: 新增 pt-label 标签文字(对应 design-components 的 Label)。

    • 14/20、中等字重、--pt-text-strong;宿主 display: inline-block,与原生 label 一样是行内级。
    • for(property 名 htmlFor,for 是 JS 保留字):点标签时在宿主所在的根(document 或外层影子树)里按 id 找目标。 勾选类 / 按钮类(pt-checkbox、pt-switch、pt-button、原生 checkbox / radio / button / file…)调用 click(), 其余(pt-input、pt-textarea、pt-select、原生文本框……)调用 focus()。点在标签内的链接 / 按钮上、 目标就包在标签里、disabled 时不转交。双击标签不选中文字。
    • required:尾部危险色星号,aria-hidden;必填语义仍由目标控件的 required 表达。
    • disabled:半透明 + not-allowed 光标,点击不再转交。
    • part base / required。
    • 它不会成为控件的可访问名(原生 label 关联跨不过影子边界、宿主上的 aria-labelledby 也到不了影子树里的控件), 文档写明由控件自己的 aria-label / 插槽文字给名字。
    • 全局样式表的 FOUC 段补上 pt-label 的 display。

    pt-checkbox / pt-switch:宿主自己被 click() 时也会切换并发 ptChange,与原生 input[type=checkbox].click() 一致 (pt-label 靠它转发)。影子树与插槽文字上的真实点击仍只切换一次。

  • 86a5f45: 新增 pt-progress 进度条(对应 design-components 的 Progress)。

    • value:0 到 max,缺省 / null / NaN 按 0,超出范围夹紧;max 默认 100,缺省、非数或 <= 0 时兜底 100。两者都反射到宿主。
    • 宿主 role="progressbar",aria-valuemin="0"、aria-valuemax / aria-valuenow 取兜底夹紧后的值; 支持 aria-label(包装层里属性名 ariaLabelText),写在宿主上。
    • 指示条用 inset-inline-start + 百分比宽度,RTL 下从右往左长;宽度过渡走 --pt-duration-fast,减少动态效果时关闭。
    • CSS 变量 --pt-progress-height(默认 8px);宿主占满宽度,也可直接给元素写 height。part base / indicator。
    • 全局样式表的 FOUC 段补上 pt-progress 的 display 与尺寸。
  • 6133454: 新增 pt-separator 分隔线(对应 design-components 的 Separator)。

    • orientation:'horizontal'(默认)| 'vertical',反射到宿主。横向占满容器宽度(扣除自身外边距); 纵向宽 1px,在 flex 行里撑满行高,在普通行内默认 1em 高。
    • decorative:默认 true,宿主 role="none";设为 false 时宿主 role="separator",纵向再带 aria-orientation="vertical"。
    • 默认插槽有内容时渲染成「线 文字 线」(只对横向生效)。
    • CSS 变量 --pt-separator-color(默认 --pt-border-default);part line / label。
    • 宿主不带外边距,间距交给容器(如 ::slotted(pt-separator))或使用方。
    • 全局样式表的 FOUC 段补上 pt-separator 的 display。
  • 3cee3ef: 新增 pt-skeleton 骨架屏(对应 design-components 的 Skeleton)。

    • 无属性:尺寸完全由使用方写在宿主上,占位直接画在宿主上(块级,默认圆角 --pt-radius-md,底色 --pt-bg-secondary)。
    • CSS 变量 --pt-skeleton-radius:圆形(头像)占位设为 9999px。
    • 宿主 aria-hidden="true";「正在加载」由使用方在加载容器上用 aria-busy 表达。
    • 透明度脉冲动画,prefers-reduced-motion: reduce 下停住。
    • 全局样式表的 FOUC 段补上 pt-skeleton 的 display。
  • 51613e1: 新增 pt-table 表格(对应 design-components 的 Table)。光 DOM 元素(shadow: false),里面直接写原生 <table>,单元格不用加 class;默认样式在全局样式表里,选择器全部包 :where(),业务 className 一写就能盖过。

    • stickyHeader(attribute sticky-header):表头吸顶,默认开,不反射。关掉时宿主写 data-sticky-header="false";原生 HTML 的 sticky-header="false" 样式表直接认,JS 到达前首帧就是对的。 宿主自己是滚动容器,给它一个高度上限吸顶才生效。
    • bordered:列间竖线,默认 false,反射。
    • 视觉对齐源包:外框 1px + 8px 圆角改为默认(原全局段只在 bordered 时有);字号 13 / 20(原为 14 / 20); 表头 40px 高、--pt-bg-default 不透明底 + inset 底线(原为 --pt-bg-secondary 底); tr[data-state="selected"] 选中底色;tfoot 半透明 weak 底 + 500;caption 在下方、13 / 20 弱化色、上间距 16; 含 pt-checkbox 的单元格尾部内边距为 0。
    • 表格改用 border-collapse: separate + 0 间距:边线属于单元格,吸顶时随表头一起吸住。
    • 宿主可聚焦时(使用方给 tabindex="0")有与控件一致的焦点环。
    • render 不带 <slot />(带了浏览器端会往宿主里插节点,React / Vue hydration 报 mismatch); 服务端渲染时撤掉 Stencil 给子元素打的 hidden,首屏 HTML 里表格可见,使用方自己写的 hidden 保留。
  • 52e025e: 新增 pt-popover 气泡卡片(批 2 · C2.1):

    • 属性 open(可写、反射)、placement(默认 bottom)、align(默认 center)、offset(默认 4)、modal(焦点关在浮层里)、 auto-focus(默认开:聚焦内容里第一个可聚焦元素,没有就聚焦浮层本身)、aria-label(ariaLabelText)。
    • 插槽 trigger(点击切换,组件维护 aria-expanded / aria-haspopup="dialog" / aria-controls)、anchor(可选的定位锚点)、默认插槽为内容。
    • 事件 ptOpen、ptRequestClose(可取消,{ reason: 'escape' | 'outside' | 'trigger' })、ptClose({ reason },外部改 open 不发)。
    • 浮层进顶层,不被 overflow: hidden 祖先裁切;窄屏(≤640px)切底部抽屉。新 CSS 变量 --pt-popover-width(默认 288px)。

    弹层驱散工具与层栈:

    • 新增 utils/popup.ts 的 dismissable():非模态弹层(popover,以及后续的 menu / hover-card)共用的 Esc 与外部点击处理, 与 pt-modal 共用层栈,只有最上层响应 Esc;已被 preventDefault 的 Esc 跳过。
    • pt-modal:已被 preventDefault 的 Esc 不再关闭弹窗 —— 弹窗里展开着的 pt-select、有内容的搜索框按 Esc 时只收起下拉 / 清空搜索词。
    • 弹层定位(utils/floating 的 computePosition)改按布局尺寸量弹层,不再受入场动画 transform 影响: 带缩放入场的弹层朝上翻转时不再压到锚点上,居中对齐不再偏移。
  • c766ef3: 新增标签页 pt-tabs + pt-tab + pt-tab-panel(批 2 · C2.2):

    • pt-tabs:value(受控,已登记 textControls,Vue v-model / Angular ngModel 可用)、variantsegmented(默认)/ line、size sm / md / lg、activation-mode automatic(默认)/ manual、aria-label。 ptChange 只在用户切换时发,且不冒泡(标签页会嵌套,理由同 pt-collapsible)。value 缺省时显示第一个未禁用的标签。
    • pt-tab:value、disabled;自己挂到标签栏插槽,宿主 role="tab",aria-selected / aria-controls / roving tabindex 由 pt-tabs 维护。pt-tab-panel:value,宿主 role="tabpanel"、aria-labelledby,未选中 hidden。
    • 键盘按 WAI-ARIA Tabs:←/→ 漫游(RTL 对调、首尾循环、跳过禁用)、Home / End;manual 模式 Enter / Space 选中。
    • 只认自己的直接子元素,嵌套的标签页互不串扰。
  • a756cb2: 新增 pt-radio-group 单选组与子元素 pt-radio(对应 design-components 的 RadioGroup / RadioGroupItem,批 2 · C2.3)。

    • pt-radio-group:value(反射,空串 = 未选)+ ptChange<string> 受控值,只在用户点选 / 方向键 / Space 时发, 外部改 value 不发。登记进 textControls:Vue 支持 v-model,Angular 支持 [(ngModel)]。 其余属性 name / orientation(vertical 默认 | horizontal,反射)/ disabled / required / invalid / aria-label。
    • 表单参与:form-associated,按 name 提交选中值,未选提交 null(字段不出现在 FormData 里),required 未选报 valueMissing,invalid 报 customError;表单重置回初始值,<fieldset disabled> 让整组禁用。
    • 无障碍:宿主 role="radiogroup" + aria-orientation / aria-required / aria-invalid / aria-disabled; 每项影子树里的圆点 role="radio" + aria-checked / aria-disabled,可访问名经 aria-labelledby 取插槽文字; role 与 Tab 停靠点不放在包着插槽的宿主上,标签文字里的链接、按钮对读屏与 Tab 仍然可用。
    • 键盘(WAI-ARIA Radio Group):roving tabindex,Tab 只停在选中项(无选中时第一个可用项);上下左右方向键移动即选中、 到头回绕、跳过禁用项,RTL 下左右对调;Space 选中当前项。
    • pt-radio:value(必填、非空,与 Radix RadioGroupItem 一致;缺了这一项不可选、方向键跳过,不回退到标签文字)/ disabled / aria-label(插槽里没有文字时给名称,如配合 pt-label 的 for);checked 由组维护。整行(圆点 + 文字)都是点击区, 标签文字里的链接 / 按钮点了不选中;宿主 click() 等同于点它。part:base / control / indicator / label。
    • pt-label 的 for 支持指向 pt-radio(点标签即选中)。
  • 261b5b0: 新增 pt-accordion 手风琴与子元素 pt-accordion-item(批 2 · C2.4,对应 design-components 的 Accordion)。

    • pt-accordion:type(single / multiple,默认 single)、value(single 为字符串,multiple 为字符串数组; HTML attribute 下 multiple 用逗号分隔)、collapsible(single 下再点已开项可收起)、disabled。 登记进 textControls:Vue 支持 v-model,Angular 用 TextValueAccessor 支持 [(ngModel)],数组值原样穿过。
    • ptChange 只在用户点击标题切换时发,detail 是切换后的 value;不冒泡(手风琴会嵌套,同 pt-collapsible)。
    • 展开态由 value 回写到直接子项的 open(反射);只认直接子项,嵌套的内外层互不串。
    • pt-accordion-item:value、disabled、heading-level(默认 3);插槽 trigger(标题)与默认插槽(内容); 影子树按 WAI-ARIA Accordion 渲染 role="heading" > <button aria-expanded aria-controls> + 旋转的 ChevronDown, 内容区 role="region"。part:heading / trigger / icon / content / body。
    • 键盘:每个标题都在 Tab 序列里,Enter / Space 切换;↑ ↓(回绕)/ Home / End 在各项标题间移动,禁用项跳过。
    • utils/roving.ts 新增两个选项:manageTabIndex: false(只移动焦点、不写 tabindex)与 focusTarget(项是宿主、真正聚焦影子树里的按钮)。
    • 展开 / 收起的内容区样式从 pt-collapsible 抽成 utils/disclosure.css 共用,collapsible 行为与外观不变。
    • 全局样式表的 FOUC 段补上 pt-accordion-item,upgrade 前先藏起内容。
    • 用户第一次在手风琴里切换之前,展开态变化(初始化、框架水合后写入的 value)不播过渡,直接切到位。
  • dd0b531: 新增下拉菜单 pt-menu 全家(批 2 · C2.5):

    • pt-menu:open(可写、反射)、placement(默认 bottom)、align(默认 center,源包未设、取 Radix 默认)、offset(默认 4)、 aria-label(ariaLabelText);插槽 trigger / 默认。触发元素上维护 aria-haspopup="menu" / aria-expanded (支持 ARIA 元素反射的浏览器里经 ariaControlsElements 指向面板)。点击打开聚焦面板;触发元素上 ↓ / Enter / Space 打开并聚焦首项、↑ 聚焦末项。 事件 ptOpen、ptClose{ reason: 'select' | 'escape' | 'outside' | 'trigger' | 'tab' }(外部改 open 不发)。 Tab 关闭菜单、焦点照常往后走。面板进顶层,窄屏不切抽屉。
    • pt-menu-item:type(default / checkbox / radio)、value、checked(可写、反射)、disabled、inset; 插槽 start(图标)/ 默认 / end(快捷键)。ptSelect(冒泡、可取消,preventDefault() 后菜单不关); 勾选项另发 ptChange<boolean>(不冒泡,避免与 pt-menu-group 的同名事件串)。
    • pt-menu-group:type(default / radio)、value(可写)、label;type="radio" 时管理组内单选项并发 ptChange<string> (不冒泡,组可经子菜单嵌套)。登记 textControls:Vue v-model、Angular [(ngModel)] 可用。
    • pt-menu-label:不可选的小标题,inset。
    • pt-menu-sub:子菜单。open(由菜单维护)、disabled、inset;插槽 trigger / 默认。悬停约 100ms、点击、→ / Enter / Space 打开, ← / Esc 只关子菜单并回到触发行(RTL 对调);离开后约 300ms 宽限再关(不做安全三角形)。
    • 新工具 utils/menu-controller.ts(菜单面板的开关、驱散、漫游 + typeahead、项激活、子菜单开关,给后续 pt-context-menu 复用)、 utils/menu-events.ts、utils/menu-panel.css。
  • 90b7a35: 新增 pt-hover-card 悬停卡片(批 2 · C2.6,对应 design-components 的 HoverCard)。

    • 属性 open(可写、反射)、open-delay(默认 700ms)、close-delay(默认 300ms)、placement(默认 bottom)、 align(默认 center)、offset(默认 4);插槽 trigger 与默认插槽(卡片内容);part base / panel。
    • 指针进入触发元素按 open-delay 打开,离开后按 close-delay 关闭,期间移进卡片就取消关闭;触屏的指针进出不理会。 键盘聚焦触发元素(:focus-visible,鼠标点击聚焦不算)同样打开,焦点离开触发元素与卡片即关闭;Esc 与外部按下走 utils/popup 的层栈。
    • 事件 ptOpen、ptClose({ reason: 'pointer' | 'blur' | 'escape' | 'outside' },外部改 open 不发)。
    • 与 Radix HoverCard 同一语义:只是视觉增强,不设 role="dialog"、触发元素不写 aria-expanded;文档写明只放补充信息。 窄屏不切抽屉。新 CSS 变量 --pt-hover-card-width(默认 256px),其余外观与 pt-popover 相同。
    • 卡片型浮层面板的外观与入场动画从 pt-popover 抽成 utils/popup-panel.css 共用,popover 外观不变。
  • f25e234: 新增 pt-toggle 切换按钮(批 2 · C2.7,对应 design-components 的 Toggle)。

    • 属性:pressed(受控、反射)、variant(default / outline)、size(sm / md / lg,默认 md)、disabled、 aria-label(包装层里是 ariaLabelText,纯图标时必须给)。
    • 影子树是原生 <button part="base" type="button" aria-pressed>,delegatesFocus;Space / Enter 都切换。
    • ptChange<boolean>:用户点击切换后发,detail 是切换后的 pressed;外部改写 pressed 不发。 作为 pt-toggle-group 的直接子元素时,点击不自行切换、也不发 ptChange(为 C2.8 预留,按父元素 tagName 判断)。
    • pressedControls 第一个成员:Vue 支持 v-model(绑 pressed);Angular 新增导出 PressedValueAccessor, standalone 组件 import 它后 [(ngModel)] 绑 pressed(只认宿主自己发出的 ptChange)。
    • 视觉对齐源包 toggleVariants:状态层、按下态 bg-selected + 最强文字色、outline 1px border-input; md 32 高 / min-w 36,sm 24 / 32,lg 40 / 40(图标 20px);高度与最小宽度写在宿主上(控件宿主约定)。 定制点 --pt-toggle-radius。
    • 全局样式表的 FOUC 段补上 pt-toggle(inline-flex)。
  • 495a65b: 新增 pt-toggle-group 切换按钮组(批 2 · C2.8,对应 design-components 的 ToggleGroup);pt-toggle 新增 value。

    • 属性:type(single / multiple,默认 single)、value(受控;single 为字符串、multiple 为字符串数组, HTML attribute 写法同 pt-accordion 用逗号分隔)、variant(default / outline)、size(sm / md / lg)、 disabled、orientation(horizontal / vertical,默认 horizontal)、loop(默认 true)、aria-label。
    • 按 value 把按下态回写到直接子元素 pt-toggle 的 pressed(写 attribute,子元素未升级也生效); 组上写了 variant / size 时回写到每一项(组级优先,项上之后再改也会被改回去;两者无默认值,组上不写时各项用自己的);组的 disabled 禁用全部项但不改写项自己的 disabled。
    • ptChange(冒泡)只在用户点击时发:single 下为字符串,再点已按下的项取消选择、detail 为 ''(同 Radix); multiple 下为新数组。登记 textControls:Vue v-model 与 Angular ngModel(TextValueAccessor)对字符串和数组都工作。
    • 键盘:roving tabindex(整组一个 Tab 停靠点,停在第一个按下的项、否则第一个可用项),方向键按 orientation 只移焦点不改按下态,RTL 下左右对调,Home / End,跳过禁用项,loop 控制回绕;Space / Enter 由项内原生按钮触发。
    • 无障碍:single / multiple 下组都是 role="group"、项内按钮都是切换按钮(aria-pressed)。single 不用 radiogroup / radio(Radix 用了):可取消选择、方向键只移焦点不选中,与 WAI-ARIA 单选组的交互约定不符。
    • 视觉对齐源包 toggle-group.tsx:flex、居中、gap 4;纵向时竖排、各项等宽。宿主 inline-flex(源包为块级 flex), FOUC 段补上 pt-toggle-group。
    • pt-toggle:新增 value(反射,只在组内有意义);直接父元素是 pt-toggle-group 时组的 disabled 也禁用它 (property 优先、组未升级时读 attribute);value / disabled / pressed 变化时通知组 refresh() —— 外部直接改组内项的 pressed 会被组按 value 覆盖回去(不发 ptChange),组始终是按下态的唯一来源。
  • 9382faa: 新增右键菜单 pt-context-menu(批 2 · C2.9),并让 ptSelect 可以在菜单上统一监听:

    • pt-context-menu:open(可写、反射;外部打开时从区域中心弹出)、disabled、aria-label(ariaLabelText); 插槽 trigger(右键区域)/ 默认(菜单内容,复用 pt-menu-item / group / label / sub 与 pt-separator)。 区域上右键(拦下浏览器菜单)在指针处打开,面板左上角贴着指针、右下展开,贴边翻转 / 收进视口;开着时在区域别处右键先关再在新位置重开。 触屏长按 700ms 打开(移动超过约 10px 取消,打开后吞掉随后的 click / contextmenu);区域里按 Shift+F10 / ContextMenu 键从区域中心打开并聚焦首项。 不改区域的 tabindex。事件 ptOpen、ptClose{ reason }(与 pt-menu 同一组)、ptSelect。宿主 display: contents。 视觉差异照源包 ContextMenu:分隔线 border-default、快捷键 text-weak(经内部变量传给 pt-menu-* 子元素,pt-menu 下不变)。
    • pt-menu / pt-context-menu 声明 ptSelect(cancelable,detail 同菜单项):组件自己不发,只为让 React / Vue / Angular 包装层 生成绑定 —— <PtMenu onPtSelect>、<PtMenu @pt-select> 现在收得到菜单项冒泡上来的选择,每次只收到一次, 在菜单上 preventDefault() 同样阻止关闭。以前写在各个菜单项上的用法照旧可用。
    • 菜单面板限高到视口里(pt-menu / pt-context-menu / pt-menu-sub 共用):面板高度按所选方位的可用空间限高 (utils/floating 新增 fitHeight 选项,写内联 max-height,关闭时还原),从视口中部打开的长菜单不再伸出视口、 首项 / 末项够不着,而是整块留在视口里内部滚动;可用空间不足两行时翻到空间大的一侧。
  • cd4ac31: 跨影子边界的标签关联(批 3 · C3.0):pt-label 的 for 现在会把标签文字交给目标控件,成为它的可访问名。

    • pt-input / pt-textarea / pt-select / pt-checkbox / pt-switch / pt-radio / pt-radio-group 接入:名字落在各自影子树里 真正获得焦点的元素上(radio-group 落在宿主的 ElementInternals 上),控件上不必再写与标签同文案的 aria-label。 外部标签优先于控件自己的 aria-label 与插槽文字;标签移除后退回原来的名字。
    • 标签晚于 / 早于控件出现、控件换 id、for 改指、标签移除、标签文字变化,关联都跟着更新(同一个根里的所有标签共用一个 MutationObserver)。
    • 机制:支持 ARIA 元素反射的浏览器(Chrome 135+、Firefox 136+、Safari 16.4+)写 ariaLabelledByElements / ariaDescribedByElements; 同时总把文字镜像进内部元素的 aria-label / aria-description,作为 Chrome 114–134、Firefox 125–135 的退路,也让 axe、 Playwright getByLabel 等只认 id 属性的工具看得到名字。
    • 新内部工具 utils/aria-link.ts(linkAria / AriaLinkController / watchIdRefs),支持 label / description / error 三类来源, 供后续 pt-field 使用;接入约定是控件宿主上的 Symbol.for 登记表,不增加任何公开属性或方法。
    • 行为变化:pt-select 在有外部标签时,触发器的名字是标签文字,不再是当前显示的值;勾选类有外部标签时不再用插槽文字取名。
  • da80c15: 新增 pt-sheet 侧边面板(批 3 · C3.1):

    • 属性 open(可写、反射)、side(top / right / bottom / left,默认 right)、close-on-overlay(默认开, 与源包 / Radix 一致;关掉写 close-on-overlay="false")、hide-close、close-label、auto-focus(默认开)、aria-label(ariaLabelText); 方法 show() / hide()。
    • 插槽 title、description、默认(正文)、footer(窄屏纵向、后写的在上,≥640px 横排靠行尾)。
    • 事件 ptOpen、ptRequestClose(可取消,{ reason: 'escape' | 'overlay' | 'close-button' })、ptClose({ reason },多一种 api; 外部改 open 不发),三者都不冒泡。
    • 视觉照源包 sheet.tsx:遮罩 --pt-bg-overlay-strong,左右两侧宽 75%、≥640px 最多 384px(--pt-sheet-size 可改),上下两侧宽度铺满; 打开 500ms / 关闭 300ms 从所在边滑入滑出、无缩放,减少动态效果时不播;右上角手绘关闭钮(16px 叉、opacity .7→1);标题 18/28。

    新增共享模态层 utils/modal-layer.ts:层栈、滚动锁、焦点陷阱与归还、document 上的 Esc(只栈顶、跳过输入法组合与已被 preventDefault 的)、遮罩进顶层、可取消的 ptRequestClose 流程,从 pt-modal 抽出,pt-modal / pt-sheet 共用(行为不变)。 共享 chunk 因此 +1.3KB(29.96 / 32KB)。

  • 340635a: 新增 pt-slider 滑块(批 3 · C3.2,对应 design-components 的 Slider)。

    • 属性:value(number[],单值也用数组,滑块数 = 长度;HTML attribute 写逗号分隔,同 pt-accordion / pt-toggle-group)、 min(0)max(100)step(1)、orientation(horizontal / vertical,竖向下端为最小值)、disabled、name、 min-steps-between-thumbs(0)、aria-label;只能 JS 赋值的 thumbLabels(多滑块各自的读屏名)与 formatValue(value, index)(写 aria-valuetext)。源包只有一档尺寸,不加 size。
    • 取值规整:缺省 / null / 空串 / 空数组按 [min](Angular ngModel 初值 null、Vue 初值 undefined 都能直接用); 外部值按 min / max 夹紧、按 step 对齐、排序后用于显示、读屏与表单,不回写 value,用户交互时才写回规整过的新数组。
    • 事件:ptChange<number[]> 在值每次变化时发(拖动中持续发,冒泡:滑块不自我嵌套);新增 ptCommit<number[]>, 一次操作结束且值变了时发(指针松开,或每一次改变了值的按键)。两者只在用户交互时触发。登记 textControls, Vue v-model 与 Angular ngModel 对数组生效。
    • 交互(Radix Slider):指针捕获拖动,按在滑块上不跳;点轨道把最近的滑块移过去并接着拖,重叠的滑块按第一次移动方向挑; touch-action: none。键盘 ← ↓ / → ↑ 一步(横向 RTL 左右对调)、PageUp / PageDown 与 Shift + 方向键十步、Home / End; 多个滑块不交叉,相邻至少隔 minStepsBetweenThumbs 步。
    • 无障碍:每个滑块是影子树里的 role="slider"(aria-valuenow/min/max、aria-orientation、tabindex=0); 单滑块时 aria-label 给滑块,多滑块时宿主 role="group" 带 aria-label,滑块默认念「最小值 / 最大值」 (三个及以上按序号),i18n 新增 slider.minimum / slider.maximum / slider.thumb。
    • 表单:formAssociated,按同一个 name 每个值提交一条;表单重置回到初始值,<fieldset disabled> 生效。 utils/form.ts 的 setFormValue / syncFormControl 接受 FormData 值,新增 multiFormValue(name, values)。
    • 视觉照源包 slider.tsx:轨道 6px 全圆角 bg-secondary、已填充段 bg-strong、滑块 16px 圆 1px border-neutral bg-default、 focus-visible 1px 焦点环;禁用只让已填充段 opacity .3 + not-allowed 指针,滑块不变(源包实测结论)。FOUC 段补上 pt-slider。
  • 3e88d1b: 新增原生下拉 pt-native-select(批 3 · C3.3):

    • 影子树里是真正的原生 <select>,选项沿用 pt-option(value / disabled / 文本)与 pt-option-group(label → <optgroup>), 由组件镜像成原生 <option> / <optgroup>;增删、改文本、改 value / disabled / label 经 MutationObserver 跟上。 pt-option / pt-option-group 在这里只是数据、不渲染自身(全局样式含 upgrade 前的 display: none)。
    • 属性 value(受控,已登记 textControls,Vue v-model / Angular ngModel 可用)、name、disabled、required、invalid、 size sm / md / lg、placeholder(渲染 value=""、disabled、hidden 的首项)、aria-label(ariaLabelText); 事件 ptChange(原生 change 时发,冒泡)。form-associated:提交原生控件实际显示的值,占位项时 required 报 valueMissing, 支持表单重置、fieldset 禁用与状态恢复。
    • 值不在选项里时与原生一致:有 placeholder 显示占位项,否则显示第一个可选项;value 属性本身不被改写。
    • 视觉照源包:高 32(sm 24 / lg 40)、圆角 8、1px border-input、内边距 12 / 32、13/20 strong、悬停 border-neutral、 focus-visible 1px 焦点环、invalid 红边与 30% 红色焦点环、禁用 .5;ChevronDown 16 weak 距末端 10。 CSS 变量 --pt-native-select-radius,part base / select / icon。

    @ptengine/tokens:tokens.css 在明暗主题作用域里新增 --pt-color-scheme(light / dark,跟随系统时在媒体查询里切换)。 它不是颜色、不进 tokens.json;组件用 color-scheme: var(--pt-color-scheme, normal) 让 UA 原生部件(原生下拉列表等)跟随主题明暗, 局部主题孤岛也按最近的祖先继承。

    utils/form.ts 的 syncFormControl 的 native 选项接受 HTMLSelectElement。

  • a917f88: 新增表单项 pt-field、字段组 pt-field-set、表单项分组 pt-field-group(批 3 · C3.4):

    • pt-field:插槽 label / 默认(控件)/ description / error;属性 orientation(vertical / horizontal / responsive,≥768px 转行)、invalid、required、disabled、errors: string[](只走 property;去空去重,一条纯文本、 多条列表,没有内容不渲染,role="alert";error 插槽有内容时以插槽为准)。 默认插槽里第一个 kit 表单控件(可包在容器里,不进嵌套的 pt-field)经 utils/aria-link 拿到标签作可访问名、 说明与错误作描述;invalid / required / disabled 以 attribute 下发给它,撤回时只移除自己加的。控件后插入、替换、移除, 标签改了 slot 或被移除时关联跟着更新并解除旧的。点标签聚焦控件,pt-checkbox / pt-switch 切换。 排列顺序跟随子元素的 DOM 先后,横向时控件写在标签前就排左侧。 视觉照源包:间距 8;标签 14 中等字重 strong、行高 snug;说明 14/20 standard;错误 14/20 danger; 横向时标签列 gap 6、垂直居中。part base / content / label / required / description / control / error。
    • pt-field-set:role="group" + aria-labelledby 指向 legend 插槽;legend-variant legend(16/24)/ label(14/20), 中等字重 standard、下边距 12;组内纵向间距 24。disabled 像原生 <fieldset disabled> 一样下发给组内的 pt-field、 kit 控件、pt-button 与原生表单控件 / 按钮(含禁用期间新加入的),撤回时只移除自己加的。part base / legend / content。
    • pt-field-group:纵向排列、间距 28,只管排版。FieldSeparator 不单做,用带文字的 pt-separator。
    • 新增 utils/field-control.ts:两者共用的控件清单与按来源计数的布尔 attribute 下发(Symbol.for 登记表,多来源并集)。

    体积预算:懒加载入口一次加三个标签后实测 2.85 KB,按「每批按实测再调、留约 3 个组件余量」由 2.8 调到 3; 共享 chunk 因 utils/field-control(pt-field 与 pt-field-set 共用,约 0.4 KB)实测 32.22 KB,由 32 调到 33。

  • 29fa641: 新增输入组 pt-input-group(批 3 · C3.5):

    • 插槽 start / 默认 / end:默认插槽放一个 pt-input(含 type="search" 搜索形态)或 pt-textarea(多行,宿主标 data-multiline、高度随内容、前后缀贴顶); 前后缀放文字 / 图标(14/20 弱色,svg 16px),末尾按钮用 pt-button size="sm" variant="ghost"(源包 InputGroupButton)。
    • 宿主画框(part="base"):高 24 / 32 / 40、圆角 8(sm 为 6)、1px border-input、内边距 12(sm 8 / lg 16)、间距 8、悬停 border-neutral、 焦点在输入框里时 1px 焦点环(data-focused)、出错红边与 30% 红色焦点环、禁用时外框与前后缀 .5 + not-allowed。 CSS 变量 --pt-input-group-radius。宿主默认 role="group"(已有 role 不覆盖)。
    • size 由输入组下发:写成子 pt-input 的 size attribute(未升级也生效)。invalid / disabled 以子控件为准(含 fieldset 禁用、pt-field 下发), 输入组经 MutationObserver 读取并反射成宿主 data-invalid / data-disabled。
    • 点在前后缀的文字、图标或外框留白上聚焦输入框(mousedown 拦默认行为:已聚焦时不会先失焦);按钮、链接等可交互元素不插手。 触屏(粗指针)下命中区上下扩到至少 44px,视觉高度不变。

    pt-input 新增 CSS 变量 --pt-input-padding-inline、--pt-input-focus-ring;pt-textarea 新增 --pt-textarea-border-width、 --pt-textarea-padding-inline、--pt-textarea-focus-ring。默认值与原来一致,输入组用它们去掉子控件自己的描边、内边距与焦点环,不穿透影子树。

    修正 pt-input:prefix / suffix 空着时不再占一个弹性项。原来空前缀也吃一份 8px 间距,文字离左边框是 20, 与源包的 12 不符;放进输入组后前缀文字与输入文字之间也会多出 8。

  • d027a3e: 新增可搜索下拉 pt-combobox(批 3 · C3.6):

    • 触发框本身就是输入框,打字即过滤 pt-option(可混放 pt-option-group / pt-separator):文案与 keywords(空格分隔) 任一包含查询即命中(trim、不分大小写),保持原顺序、禁用项照样可见;组内全被滤掉时整组藏起、有查询时分隔线藏起; 没有匹配项时显示 empty-text(不给时取 locale 的 combobox.empty)。收起时清空查询。
    • value 区分「未选」(undefined,null 视同)与「选了空串」('',合法的已选值):前者显示占位、表单不提交、required 报必填; 后者可以高亮、选中,提交空串。已登记 textControls:Vue v-model / Angular ngModel / React value + onPtChange 都保留这一区分。
    • 属性 placeholder empty-text max-height(列表最大高度,默认 320)name size(sm / md / lg)disabled invalid required openaria-label(ariaLabelText);方法 setFocus / show / hide / refresh;事件 ptChange(冒泡)、ptOpen / ptClose(不冒泡, reason 与 pt-select 同形)、ptFocus / ptBlur。form-associated,支持表单重置与 fieldset 禁用。接入 aria-link(pt-label for / pt-field)。
    • 源包「删了就坏」10 条逐条翻译成 kit 语义并各有一条 spec:Home / End 留给光标;收起时 Enter 放行并对所属表单 requestSubmit(隐式提交); '' 值可高亮可选;重复 value 按行区分;展开时再点输入框不收起;面板里只在选项上 mousedown preventDefault(滚动条可拖); 打开 / 关闭不抢焦点、不全选文字;展开中被禁用即收起并清查询;选中前再判禁用;输入法组合中不响应 Enter / 方向键。
    • 视觉照源包:触发框与 pt-select 同一套(高 32、圆角 8、border-input、13/20);已选文案在输入框下一层、打字时 opacity 0 占位不跳宽; 状态图标展开 Search / 收起 ChevronDown .5;面板 openFloating bottom start offset 4、按可用高度限高(fitHeight 80),min-width = 触发框宽(不超过视口减 16px)、max-width min(420px, 90vw)(只限按内容撑宽), padding 4,复用 popup-panel.css。触屏命中区 44px。CSS 变量 --pt-combobox-radius,part base / input / value / popup / listbox / empty。

    新增 utils/filter.ts:过滤规则(normalizeQuery / splitKeywords / matchesQuery)与「输入框 + aria-activedescendant 列表」的状态 (FilterList:过滤写回 hidden + data-filtered、只摘自己加的 hidden,高亮移动复用 listbox 的 moveActive),供之后的 pt-command 复用。

    pt-option-group 的选项增删同样通知所属的 pt-combobox;pt-label 把 pt-combobox 列入可交互目标。

    @ptengine/ui-angular:TextValueAccessor 对 pt-combobox 的 writeValue(null / undefined) 照原样写 undefined,不再转成空串 (由 stencil.config 的 nullableTextControls 生成)。

  • 437fe7a: 新增 pt-drawer 抽屉(批 3 · C3.7,对应源包 drawer.tsx / vaul):

    • 属性 open(可写、反射)、direction(bottom 默认 / top / left / right,物理方向,RTL 下不翻转,与 vaul 一致)、 dismissible(默认开;关掉后遮罩、Esc 不关,拖动只回弹)、handle(默认开,上下方向显示 100×8 把手)、 snapPoints(只走 property:0–1 比例或 '240px',缺省只有全开)、snap-index(可写、反射)、auto-focus、aria-label(ariaLabelText); 方法 show() / hide()。
    • 插槽 title、description、默认(正文,在面板里滚动)、footer(纵向排列);正文或页脚里带 data-drawer-close 的元素点击即关闭(对应 DrawerClose)。
    • 事件 ptOpen、ptRequestClose(可取消,{ reason: 'escape' | 'overlay' | 'drag' | 'close' },拖动被拦下时回弹)、 ptClose({ reason },多一种 api;外部改 open 不发)、ptSnap({ index, point },用户拖到新吸附点时),四者都不冒泡。
    • 拖动:pointer capture;只在把手 / 面板上不可滚动处起拖,正文里可滚动区域没滚到顶之前往下拉是滚动;文本框、下拉框、 data-drawer-no-drag 与 pointerdown 已被 preventDefault 的控件(如 pt-slider)上不起拖。松手按 vaul 的阈值判定 (0.4 px/ms、露出部分 25%、2 px/ms 快速甩动逐档 / 到底),遮罩随拖动淡出。判定逻辑是 utils/drawer-gesture.ts 的纯函数。
    • 模态层复用 utils/modal-layer(层栈、滚动锁、焦点陷阱与归还、遮罩进顶层、重插恢复)。
    • 视觉照源包:遮罩 --pt-bg-overlay-strong,面板 bg-default、1px 边、贴边一侧圆角 10、对边至少留 96px, header grid gap 6 p16,footer mt auto column gap 8 p16,标题 18/28 600 tracking -0.025em;开合 500ms cubic-bezier(0.32, 0.72, 0, 1), 减少动态效果时不播。shouldScaleBackground 不做。

    体积:pt-drawer 5.46KB(中档 8KB);拖动判定放在组件自己的入口,共享 chunk 不变(31.83 / 32KB)。 懒加载入口 45 个组件实测 2.83KB,超过 2.8,按「每批按实测、留约 3 个组件余量」上调到 3。

  • bc5f5ad: 新增命令面板 pt-command + pt-command-group + pt-command-item(批 3 · C3.8):

    • 顶上一个搜索框(Search 16 weak、高 40、底边),下面是可过滤的命令列表(max-height 300 内部滚动)。项写成 pt-command-item (value keywords disabled,slot start 图标 / 默认文案 / end 快捷键 13/20 weak 0.1em),可以用 pt-command-group (label 组标题,13/20 500)分组、夹 pt-separator(margin 0 -4)。
    • 与 pt-combobox 共用 utils/filter:文案(默认插槽的文字,不含 start / end)与 keywords 任一包含查询即命中, 保持原顺序、不按匹配度重排(有意偏离 cmdk 的打分排序,文档注明);禁用项照样可见;空组整组藏起、有查询时分隔线藏起; 没有匹配时显示 empty-text(缺省 locale 的 command.empty)或 empty 插槽。filter 属性可传自定义函数, should-filter="false" 关掉内置过滤(异步搜索)。项的增删与文案 / value / keywords / disabled 变化由 MutationObserver 自动重新过滤。
    • 焦点始终在输入框上(role="combobox" + aria-activedescendant → role="listbox"):↑↓ 移动高亮、跳过禁用项,loop(默认 false,同 cmdk); Home / End 到首尾(同 cmdk);Enter 选中;指针移过即高亮;输入法组合中不响应。Esc 不处理,留给外层弹窗。
    • ptSelect(cancelable,由项发出、冒泡):pt-command 声明了同名事件,包装层能在面板上绑定(同 pt-menu)。没被取消时面板随后清空查询、 高亮回到第一项(等同「重新打开」:放进弹窗时下次打开不停在上次的结果上)。
    • value 是高亮项(不是选中值),不登记受控清单;query 可读写。两者因用户操作改变时分别发 ptValueChange / ptQueryChange,外部写入不发。
    • label(输入框与列表的无障碍名称,缺省 locale 的新键 command.label),接入 aria-link(pt-label for)。
    • CommandDialog 不单做:文档给 pt-modal 包 pt-command 的写法,源包的尺寸差异经 CSS 变量覆盖: --pt-command-input-height(48)、--pt-command-item-padding-block(12)、--pt-command-icon-size(20),另有 --pt-command-radius、 --pt-command-list-max-height。

    utils/filter.ts 的 FilterList 新增 match 选项(自定义命中判断,拿到原样查询与规范化后的查询)与可按函数现读的 loop,补了 spec。 新增 utils/command-item.ts(项文案提取与内部选中事件名,pt-command / pt-command-item 共用)。

  • 9605123: 新增 pt-toaster 轻提示与 @ptengine/ui/toast 子路径(批 4 · C4.1,对应源包 sonner.tsx / sonner 2.0):

    • pt-toaster:属性 position(六个位置,默认 bottom-right,物理方向)、rich-colors、close-button、duration(4000)、 visible-toasts(3)、expand、gap(14)、offset(默认 24px)、label、close-label; 方法 show(options): id、update(id, options)、dismiss(id?)。每条的选项 type(default / success / error / warning / info / loading)、 title、description、action / cancel({ label, onClick },action 里 preventDefault 不关)、duration(0 / Infinity 常驻, 与 sonner 不同、沿用 AntD)、id(同 id 覆盖更新)、dismissible、closeButton、richColors、onDismiss、onAutoClose。
    • 行为照 sonner:最新在前、后面缩小叠压(最多露出 3 条);悬停 / 聚焦 / 按住 / 页面隐藏时暂停计时并展开; 按住往贴边或水平方位方向滑动关闭(pointer capture,45px 或 0.11 px/ms,反方向带阻尼);Alt+T 聚焦通知区、Esc 还焦点; 宿主 aria-live="polite"、每条 role="status"、error 那条 aria-live="assertive";减少动态效果时不播过渡。
    • 层级:通知区经 showInTopLayer 进顶层;之后打开的弹层压在它上面时,出新通知会重新提到顶层最上面 (utils/floating 新增 hasLayerAbove,只在确实被压住时才进出,避免打断堆叠过渡)。宿主带 aria-live, hideOthers 设 inert 时跳过它。移出 DOM 时出顶层、暂停计时,插回后恢复。
    • 视觉:bg-box、1px border-default、shadow-lg、圆角 8、宽 356、padding 16、标题 / 说明 13/20(源包未覆盖 sonner 的 13px, 计划里的「标题 14/20」以源包为准);语义色只染图标(danger / warning / success / information 的 standard 档); rich-colors 用语义 subtler 底 + 语义描边、文字不染;action 高强调黑、cancel 次要灰。
    • @ptengine/ui/toast:toast(message, opts) 与 .success / .error / .warning / .info / .loading / .message / .promise / .dismiss, 同步返回 id。找页面上的 <pt-toaster>(优先使用方自己挂的),没有就在 <body> 末尾建一个;元素没注册时按需 import('../components/pt-toaster.js'),注册前的调用排队、按顺序交付;Node(SSR)下调用是空操作。 Stencil 之外单独一步 tsc -p tsconfig.toast.json 产出 dist/toast/,package.json exports 加 ./toast (turbo 的 @ptengine/ui#build outputs 已含 dist/**,不另登记)。三个包装包不再导出一份。
    • i18n 新增 toast.label(通知区读屏名称)。

    体积:pt-toaster 进中档(8KB),实测见 MR;toast 子路径新增一条预算 3KB(实测 2.27)。

  • 9f05d0c: 新增 pt-breadcrumb 面包屑与子元素 pt-breadcrumb-item(对应 design-components 的 Breadcrumb*,批 4 · C4.2)。

    • pt-breadcrumb:label(导航地标的读屏名,缺省走新增的内置文案 breadcrumb.label:「面包屑」/ "Breadcrumb")、 separator(文字分隔符,缺省画 14px 右向箭头、RTL 下翻转;空串只留间距)。宿主 role="navigation", 影子树里只有一层 role="list" 的列表容器(part="list"):13/20、weak 色、flex 换行,间距 6px(视口 ≥640px 时 10px)。 CSS 变量 --pt-breadcrumb-gap、--pt-breadcrumb-separator-color。
    • pt-breadcrumb-item:光 DOM 元素(shadow: false),宿主 role="listitem";current(aria-current="page",strong 色 + 500)、 ellipsis(32×36 的「…」,读屏念 label,缺省 breadcrumb.more)。链接由使用方写在项里(<a> 或框架的 Link 组件), 样式在全局样式表、选择器包 :where(),业务写在项或 <a> 上的 className 直接盖过;触屏下链接本身撑到 44 × 44(命中区就是盒子,同行与换行后上下两行都不重叠)。
    • 分隔符与折叠图标都是伪元素(非首项的 ::before / 折叠项的 ::after),分隔符的替代文本为空、读屏不念; 光 DOM 里不渲染任何子节点,React / Vue 水合不会对不上。
    • 与计划的偏离:项不提供 href(光 DOM 里把使用方文字包进组件自建的 <a> 会让水合报 mismatch,客户端路由也需要框架自己的 Link); pt-breadcrumb 保留影子树,只为放列表语义(读屏念「第 N 项,共 M 项」需要项外面有一层列表,而宿主已是导航地标)。
    • SSR:pt-breadcrumb-item 加进 React output target 的 excludeServerSideRenderingFor(直接文本不被换成注释), @ptengine/ui-react/ssr 补导出 PtBreadcrumbItem。
    • 体积:全局样式表 4.29 → 4.89 KB(预算 5,未顶破;两段 mask 图标的 data URI 占了大头)。
  • a7efec2: 新增 pt-pagination 分页与子元素 pt-pagination-item(批 4 · C4.3,对应 design-components 的 Pagination)。

    • 两个都是光 DOM 元素(shadow: false):样式在全局样式表 ptengine-ui.css,选择器全部包 :where(), 业务在项上写 className、或用 .biz > a 写在里面的链接 / 按钮上都能直接盖过默认样式。
    • pt-pagination:role="navigation" 地标,label 缺省「分页」(新增内置文案 pagination.label); 声明 ptSelect(自己不发)以便三个框架包装层在分页上统一绑定项冒泡上来的事件。
    • pt-pagination-item:kind page / previous / next / ellipsis、page、href、active、disabled、label。 没有 href 渲染成按钮,点击发 ptSelect(detail { kind, page },冒泡);给了 href 渲染成链接、照常跳转。 内容全部由属性决定,不接收子节点。页码 32×32、上一页 / 下一页同高按文字撑宽,配色照 pt-button 的 ghost / secondary; 触屏下命中区 44px、项间距拉到 12px 使相邻命中区不重叠;RTL 下箭头翻转。
    • 不内置页码计算、不持有当前页(与源包一致),文档给出「当前页 ± 1 + 首末页」的写法。
    • 服务端渲染:两个元素加进 excludeServerSideRenderingFor,@ptengine/ui-react/ssr 导出客户端版本,首屏是空标签 (尺寸由全局样式表占住),链接在 upgrade 后渲染。@ptengine/ui-react 的 PtPaginationItem 在生成的包装外加一层 (opaque-light-dom.ts),给宿主固定传 dangerouslySetInnerHTML={{ __html: '' }}:项在自己的光 DOM 里渲染 <a> / <button>, 而 Stencil 先于 Next.js 的 hydration 渲染,不加这层 React 比对子节点时必报 #418。
    • 全局样式表预算 5 → 6KB:本段实测 +0.58(4.87),与并行的 breadcrumb 段合起来约 5.5,按批 1 预估的全局段上限定 6。
  • 6f2cfd2: 新增列表项 pt-item + pt-item-group + pt-item-media(批 4 · C4.4):

    • pt-item:源包 Item / ItemContent / ItemTitle / ItemDescription / ItemActions / ItemHeader / ItemFooter 合成一个元素, slot media title description 默认(说明下方的额外内容)actions header footer。variant default / outline / muted (muted 走 bg-secondary),size md(gap 16、p 16)/ sm(gap 10、上下 12 左右 16);14/20 正文色、radius-md、1px 边框; 标题 14/20 500 强调色、行内 gap 8;说明两行截断;header / footer 占满整行两端对齐。空插槽连同它两侧的 gap 一起收起 (照 pt-card 看宿主子节点 + slotchange + MutationObserver,坑 27)。href 时内部渲染 <a>(target / rel, _blank 自动补 noreferrer noopener),带状态层与焦点环、delegatesFocus。圆角经 --pt-item-radius 覆盖。
    • pt-item-group:flex-direction: column,宿主默认 role="list"(已有 role 不覆盖);直接子元素的 pt-item 在接入 DOM 时 自己补 role="listitem",挪出组时只撤自己补的。与 pt-kbd-group 一样是容器而不是子元素,不标 @childOf,与 pt-item 共用文档页。
    • pt-item-media(@childOf pt-item):variant default(svg 20 弱色)/ icon(32 radius-sm 描边 + bg-secondary,svg 16 正文色)/ image(40 radius-sm,::slotted(img) 铺满 cover)。
  • b466c25: 新增滚动区域 pt-scroll-area(批 4 · C4.5):

    • 滚动走浏览器原生(滚轮、触控板、触屏惯性、键盘、查找定位不打折扣),只把原生滚动条藏掉、换成自绘细滚动条: 竖条宽 10 / 横条高 10、padding 1、起始侧 1px 透明边,thumb 圆角满、--pt-border-default(经 --pt-scroll-area-thumb-color 覆盖)。
    • orientation vertical(默认)/ horizontal / both;type hover(默认)/ scroll / auto / always 与 scroll-hide-delay(600) 同 Radix 语义;hover 模式下视口经键盘聚焦时也显示。thumb 长度按可见 / 内容比例(最短 18),拖动用 pointer capture, 点轨道空白处朝那一侧翻一页(87.5%);ResizeObserver 盯视口与内容,容器尺寸变化、内容增减时跟随,不溢出时不显示。 RTL 下竖条换到左侧、横条从右端起步(逻辑属性 + scrollLeft 负数换算)。触屏下 thumb 命中区扩到 44px。
    • 宿主是纵向 flex 容器,给 height 或只给 max-height 都能滚动。
    • 可达性:溢出时视口 tabindex=0,有 label 时是 role="region" 地标,否则以新增的 i18n 文案 scrollArea.label (可滚动区域 / Scrollable region)命名的 role="group";自绘滚动条 aria-hidden。
    • 方法 scrollToPosition(options) 转发到视口的 scrollTo()(scrollTo 是元素原生方法,Stencil 保留名,不能同名)。
    • part viewport / scrollbar / thumb。
  • 4cacf8b: 新增标记 pt-marker(批 4 · C4.6):对话流 / 列表 / 时间线里标出一个节点的一行弱色说明(「今天」「以下为新消息」)。

    • 源包 Marker / MarkerIcon / MarkerContent 合成一个元素:slot icon(16px、aria-hidden)+ 默认插槽(文字)。 满宽横向 flex、最小高 16、gap 8、14/20 弱色、起始侧对齐。没放图标时图标位连同 gap 一起收起 (看宿主子节点 + slotchange + MutationObserver,坑 27)。
    • variant default / separator / border:separator 两侧各一条 1px --pt-border-default 细线(真实元素 part="line", 不用伪元素),线与文字之间的 4px 用逻辑方向(源包是物理 mr / ml),RTL 下照样对称;文字居中、不伸长, 过长时允许收缩折行(源包 flex-none 会连同两侧间距溢出容器)。border 底边 1px、pb 8。
    • href 时内部渲染 <a>(target / rel,_blank 自动补 noreferrer noopener):下划线 offset 4、悬停变 standard、 焦点环、delegatesFocus,触屏命中区经 .touch-target 外扩到 44px。默认插槽直接子元素的 <a> 经 ::slotted(a) 得到同样的下划线与悬停色、颜色随正文(嵌套更深的由使用方自己写)。
    • 开发站与文档站新开「对话 / AI」分组(源包归在 Chat / AI,批 5 的同类组件接着放)。
  • 1f2e8bb: 新增 pt-input-otp 验证码输入:定长格子、groups 分组、单字符 pattern(默认只收数字,移动端弹数字键盘), ptChange + 填满时的 ptComplete,参与表单(空值必填、不满位数报 tooShort),Enter 提交所属表单。 格子是视觉层,接收输入的是一个透明原生 input:短信验证码自动填充(one-time-code)、粘贴、输入法都走原生路径。 已登记 textControls(Vue v-model / Angular ngModel)与 pt-field 的控件清单。

    locale 注册表删去未使用的 otp.digit,新增 otp.incomplete(「请输入完整的 {length} 位验证码」)。 自定义 locale 里写了 otp.digit 的要改成新 key。

  • 39dca9b: 首个公开版本。

    设计变量(@ptengine/tokens):原语与语义两层,成对导出包装形态与通道形态; 明暗主题通过在根节点切换一组变量实现,支持任意嵌套的局部主题孤岛。

    基础组件(@ptengine/ui):Button、Input、Textarea、Select / Option、Checkbox、Switch、 Badge、Spinner、Modal、Tooltip、EmptyState。全部 Shadow DOM,表单类组件参与原生表单, 弹层类组件在窄屏下切换为底部抽屉,触控目标不小于 44px。

    业务组件(@ptengine/widgets):目前是占位,只有 pt-provider 依赖注入;业务组件以后再加。

    框架包装:React、Vue 3、Angular 19+ 三套包装层由构建自动生成,覆盖 SSR。

  • 5e1594e: Vue / Angular 包装层改为真正按需:只用 PtButton 时产物从约 120KB 降到约 25KB(gzip),不再打进全部组件。

    @ptengine/ui/dist/components/<tag>.js 改为无副作用(只导出类与 defineCustomElement,import 了不注册), 包装层从这里导入并自己注册;@ptengine/ui/components/<tag>.js 仍是 import 即注册,原生用法不变。 直接导入 dist/components/<tag>.js 并依赖它自动注册的代码,要改用 components/<tag>.js 或手动调 defineCustomElement()。 两个内核包新增 sideEffects 声明。

  • 9126cf4: 正确性修复(优化方案 P0)。

    业务组件:requestClient 不再在第二次通知时把自己退订(切换 client 只能切一次的 bug); pt-provider 挂载时广播 context-provider,断开时不再清空订阅者,同值不重复通知。

    基础组件:setLocale() 现在会让已渲染组件即时更新文案;checkbox 的英文硬编码改走 i18n; select / textarea / switch 补上表单校验(valueMissing / customError),switch 新增 required / invalid; select 的弹层定位改到 componentDidRender,click-outside 与定位抽到 utils/overlay.ts,抽屉样式抽到 utils/drawer.css; select 补 aria-controls / aria-activedescendant;tooltip 补 aria-describedby、触屏兜底与视口翻转; 组件级 CSS 变量改为使用处 fallback(使用方可覆盖);删掉永不匹配的 ::slotted 规则;清空按钮可 Tab 到达并满足 44px; modal 卸载时不再抢回焦点,关闭按钮 exportparts;去掉无用的 puppeteer 依赖。 API 对齐(0.x):select 的 ptClose detail 统一为 { reason };select / checkbox / switch 补 ptFocus / ptBlur; 每个组件都有 part="base";checkbox / switch 统一只在空格切换。

    tokens:新增语义 token --pt-bg-overlay(暗色下另有取值),z-overlay 提到 60;.pt-theme 不再强制亮色; [lang] 的字体规则从 tokens.css 挪到 ui 的全局样式;verify.mjs 支持比对值而不只比名字。

Patch Changes ​

  • d523000: utils/motion.css 按用途拆成 utils/motion/ 下的小文件(fade / zoom / slide-in / slide-out / spin / pulse / caret), 减少动态效果时的退化规则跟着对应文件走;motion.css 只保留为汇总与说明。

    Stencil 会把 @import 的 CSS 整份内联进每个引用它的组件:pt-modal / pt-select / pt-tooltip / pt-spinner 改为只引用自己用到的文件,各自不再携带用不到的 keyframes。编译产物里的 animation 声明与被引用的 keyframes(含 prefers-reduced-motion 下的重定义)与之前逐条相同,观感不变。

  • 8468bfd: 新增弹层共用的定位与顶层机制 utils/floating(批 0 · B1),pt-tooltip 先切过去作回归验证。

    • computePosition(anchor | {x, y}, floating, { placement, align, offset, padding, dir }) 返回视口坐标与翻转后的实际方位: 放不下翻到对侧,交叉轴 clamp 进视口,RTL 下 start / end 对调;纯函数版 computePositionFromRects 便于单测。
    • openFloating(anchor, floating, opts) → cleanup:弹层加 popover="manual" 并 showPopover() 进顶层, 不再被祖先的 overflow: hidden 裁切、不受 transform 祖先与层叠上下文影响;DOM 位置不动。 打开期间跟随滚动、窗口与尺寸变化(rAF 合并)。不支持 popover API 时退回同一套坐标的 position: fixed。
    • onOutsidePointerDown 新增;onOutsideClick 迁入同一文件(utils/overlay 仍转出,pt-select 不受影响)。
    • pt-tooltip:公开 API 与交互不变;气泡改为顶层 + 视口坐标定位,放在 overflow: hidden 容器里也能完整显示, 靠近视口左右边缘时不再被切掉一半(交叉轴 clamp)。
  • 6be7ef7: 批 0 · B3:新增 utils/listbox,pt-select 切到 utils/floating 顶层定位。公开 API 不变。

    • utils/listbox.ts:moveActive(current, move, { count, isDisabled, loop, pageSize }) 在选项间移动高亮项 (上下 / Home / End / PageUp / PageDown,跳过禁用项,是否循环可配),listboxMoveForKey 把按键翻译成移动方式; createTypeahead({ timeout, now }) / findTypeaheadMatch 按字母跳项(前缀累积、超时清空、从当前项之后找、 同一字母连按循环)。纯函数 / 小状态对象,后续 combobox / menu / command 共用。
    • utils/floating.ts 新增 showInTopLayer(element):只进顶层、不定位,给遮罩、贴底抽屉这类位置由 CSS 决定的层用; openFloating 内部也改走它。
    • pt-select:列表经 openFloating 进顶层(bottom、start 对齐、间距 4px、至少与触发器同宽),放在 overflow: hidden / transform 容器里不再被裁切;下方放不下翻到上方、交叉轴 clamp 进视口。 窄屏抽屉与遮罩也进顶层,始终贴视口底边、遮罩盖住整页,不受祖先 transform 影响。 字母跳项改为从当前项之后找、同一字母连按在以它开头的选项间循环(与原生 select 一致);其余键盘行为不变。
    • 删除 utils/overlay.ts(computePlacement 已无引用,onOutsideClick 一直在 utils/floating.ts)。
  • 3e676b6: 批 0 · B4 + B7:抽出共享动效与列表项样式,观感不变。

    • 新增 utils/motion.css:pt-fade-in/out、pt-zoom-in-95/out-95、pt-slide-in-from-* / pt-slide-out-to-* (距离取 --pt-motion-distance,默认 --pt-space-1)、pt-spin、pt-pulse、pt-caret-blink。 减少动态效果时位移、缩放类退化为纯淡入 / 淡出。只由用到的组件 @import,不进全局样式表。
    • pt-modal / pt-select / pt-tooltip / pt-spinner 的私有 keyframes 收编进来:pt-zoom-in → pt-fade-in, pt-zoom-in-95, pt-select-in → pt-fade-in, pt-slide-in-from-top,pt-tooltip-in → pt-fade-in;pt-spin / pt-pulse 同名迁入。 时长、缓动、终态与之前逐项相同。
    • 新增 utils/list-item.css(选项 / 菜单项 / 命令项共用的一行),pt-option 改为引用它。
  • 6da0023: 批 1 · C1.1:控件宿主 CSS 约定。

    • pt-button / pt-input / pt-select 的控件高度从内部元素挪到 :host(:host([size]) 换档),带边框的内部元素 width: 100%; height: 100% 撑满宿主;pt-button 纯图标(icon-only)的宽度同样挪到宿主,variant="link" 宿主不定高。 使用方写在元素上的 class="h-7 w-7" / style="width: 100%" 现在能改变控件的实际尺寸。默认尺寸与观感不变。
    • docs/component-api.md §7 写入该约定(pt-checkbox / pt-switch 例外及原因),并补充光 DOM 元素的适用范围。
    • 生成器:新增 --light 生成 shadow: false 的光 DOM 元素(样式段追加到全局样式表);模板不再在 :host 上声明组件级变量。
  • d23da85: 修复弹层互相嵌套时外层被连带关闭的两个问题(批 2 收口的弹层专项 e2e 抓出来的):

    • 弹层的开关事件改为不冒泡:pt-modal / pt-popover / pt-hover-card / pt-tooltip / pt-menu / pt-context-menu / pt-select 的 ptOpen、ptRequestClose、ptClose。以前弹窗里的菜单按 Esc 关闭时,它的 ptClose 冒泡到弹窗宿主, React / Vue / Angular 里弹窗上的 onPtClose / @pt-close / (ptClose)(受控写法通常是 setOpen(false))把弹窗也关了; 弹窗上 ptRequestClose 里的 preventDefault() 同样会误拦内层弹层的关闭。要在外层容器上统一监听,改用捕获阶段。
    • pt-select 点触发器展开时把焦点放到触发器上。Safari 点按钮不给焦点(原来聚焦着的还会落回 body), 列表的方向键 / Esc 都收不到;放在气泡卡片里时 Esc 越过下拉直接关掉了气泡卡片。
  • ec35c3e: 修复 pt-input / pt-textarea 的值在两次改动落在同一次重渲染之前时,内部原生控件不跟着更新的问题:

    • 搜索框(type="search")刚输入、还没来得及重渲染就按 Esc,宿主 value 已清空,框里的字却还在。
    • 受控用法里在 ptChange 回调中把 value 写回原值(拒绝这次输入),原生控件仍显示用户刚敲的内容。

    现在 value 一变就同步写进内部 input / textarea(只在不一致时写,正常输入不影响光标)。

  • a1efb75: 弹层打开期间跟随锚点位移(批 2 附加修复):openFloating 打开期间每帧比较一次锚点的 getBoundingClientRect(),变了才重算坐标。此前只在 scroll、window resize 与 ResizeObserver 时重算, 锚点尺寸不变但被推走(上方的 pt-accordion / pt-collapsible 展开、图片加载、框架晚一拍渲染)时 弹层停在原处。pt-select、pt-tooltip、pt-popover 同时受益;原先的 scroll 监听与锚点 ResizeObserver 被逐帧比较覆盖,一并去掉。API 不变。

  • aeb7c1c: pt-popover 被嵌进别的组件影子树时,关闭仍能把焦点还给触发元素:判断「焦点在不在浮层内容里」改为按宿主所在的树取 activeElement(新内部工具 utils/dom 的 activeElementAround),不再读只会给到最外层宿主的 document.activeElement。

  • 910922b: pt-popover 的 modal 补全为完整的模态(按 Radix 的模态 Popover):以前只把焦点关在浮层里,现在另外

    • 浮层下面铺一层透明遮罩(进顶层),点外面只关浮层(reason: 'outside'),不会点到页面上的元素;
    • 页面其余部分设为 inert(读屏看不到、Tab 不进去;aria-live 播报区不动,使用方自己设的 inert 保留);
    • 锁住页面滚动;
    • 关闭后焦点还给触发元素,包括点外面关闭的情况。

    打开期间改 modal 立即生效。浮层打开期间再弹出的 pt-modal / pt-sheet 若挂在被隐藏的区域里,会在打开期间把自己放出来。

  • e10e203: 批 3 收口修复:

    • pt-checkbox / pt-switch:没有标签文案时不再在方框(轨道)后面多留 8px。原来靠 .label:empty 隐藏,但标签容器里始终有 <slot>, 这条规则从没生效过。现在按默认插槽里有没有文案判断(纯空白与带具名 slot 的元素不算),文案中途加上或去掉会跟着切换。
    • pt-input:触屏(pointer: coarse)下内部原生 <input> 的命中区不小于 44px,宿主和描边框的视觉尺寸不变(同 pt-native-select 的做法)。 pt-textarea 最小 3 行,本来就高于 44px,不需要改。
    • pt-popover:打开着被移出 DOM 又插回来时(框架列表重排、换父节点),重新进顶层、定位,恢复 Esc / 点外面关闭,modal 时恢复 页面 inert、焦点陷阱与滚动锁;不重复发 ptOpen。pt-modal / pt-sheet / pt-drawer 已有这项处理。
    • pt-sheet:关闭时只等会结束的退场动画。使用方经 ::part() 挂的无限循环动画不会再让面板一直停在 closing、占着顶层(同 pt-drawer)。
    • pt-combobox / pt-select / pt-radio-group / pt-textarea / pt-checkbox / pt-switch:每轮渲染后对齐一次表单值与校验状态。 按需注册时组件刚升级那一刻被写进来的 required / invalid(如 pt-field 下发、Angular 在 <form> 里写值)不触发 @Watch, 以前会出现「界面显示必填、表单却不拦提交」(Angular 下 pt-field 包 pt-combobox 实测复现)。
  • d93a720: 批 3 遗留债:

    • pt-hover-card / pt-menu / pt-context-menu / pt-tooltip / pt-select:打开着被移出 DOM 又插回来时(框架列表重排、换父节点), 重新进顶层、定位,恢复 Esc / 点外面关闭,不重复发 ptOpen(同 pt-popover)。菜单与右键菜单把焦点放回面板(打开前的焦点仍在关闭时归还), 下拉选择器把焦点放回触发器,方向键照常可用;提示重新挂上 aria-describedby。
    • pt-modal:没有 description / footer 插槽内容时,两块连同间距一起收起,没有说明时面板也不再挂指向空内容的 aria-describedby。原来靠 :empty 隐藏,但容器里始终有 <slot>,这条规则从没生效过 —— 只有标题与正文的弹窗 会在标题下、正文下各多出一段间距。现在按宿主子元素判断,打开期间或关着时增删插槽内容都会跟着切换。
    • pt-select / pt-combobox / pt-menu / pt-context-menu / pt-menu-sub:触屏(粗指针)下相邻选项、菜单项外扩的 44px 命中区 上下重叠,点在上一项靠下的位置会选中下一项。现在按指针坐标改判为真正要点的那一项(行框包含该点的项,都不包含时取中心 最近的),判定抽成 utils/dom 的 elementAtPoint,与 pt-command 共用。共享 chunk 预算因此从 35KB 上调到 36KB(实测 35.29)。
  • c8a0ed7: pt-field / pt-field-set 改走独立的继承通道下发状态,不再改写控件自己的属性(AGENTS.md 坑 35)。

    • 以前表单项 / 字段组直接写控件的 required / invalid / disabled attribute:下发期间应用把同名状态设为 true, 撤回时会被一并清掉;受控绑定写回 false 又把下发的撤掉。现在 kit 控件新增内部继承 prop inherited-disabled / inherited-required / inherited-invalid(由父元素维护,不建议直接写),有效状态是 自身属性与继承的并集;读 el.disabled 等得到的始终是应用自己写的值。
    • 涉及:pt-input、pt-textarea、pt-select、pt-native-select、pt-combobox、pt-checkbox、pt-switch、pt-radio-group、 pt-slider(三个继承 prop,slider 只有 disabled);pt-button、pt-toggle、pt-toggle-group、pt-radio、pt-field、 pt-field-set(inherited-disabled,嵌套字段组的内层 aria-disabled 也跟上)。
    • 原生 <fieldset disabled> 的 formDisabledCallback 不再改写 disabled,改进内部状态:旧写法在 fieldset 解禁时 不会再收到回调,控件会一直停在禁用态。
    • 宿主上给样式用的有效状态改为 data-disabled / data-invalid(组件渲染),不再依赖 [disabled] / [invalid]。
    • 继承来的禁用与原生禁用一致:不随表单提交、不参与校验。
    • 组内原生 input / select / textarea / button 仍直接写原生 disabled(没有别的通道),限制见 field-set 文档。
  • 9cc8fe6: 修复 Next.js dev 模式下 pt-collapsible / pt-menu / pt-popover / pt-tooltip 引起的 hydration 告警 ("A tree hydrated but some attributes of the server rendered HTML didn't match")。

    这几个元素会往使用方写的触发元素上补 aria-expanded / aria-haspopup / aria-controls / aria-describedby (以及给没有 id 的内容元素补 id)。服务端渲染出来的元素在包装模块加载时当场升级、赶在 React hydration 之前就写了, React 比对时发现按钮上多出这几个 attribute。

    ui-react:这四个组件的包装(含 @ptengine/ui-react/ssr)给宿主带上内部标记 data-pt-hydrating, 在宿主的 ref 回调里(hydration 比对做完之后)摘掉并通知元素。

    ui:宿主带着这个标记时,元素先不往光 DOM 里写,放行后按当时的状态补写。没有标记的用法(原生 HTML、Vue、Angular) 行为不变。

    生产构建与 dev 下的读屏语义不变:水合完成后 attribute 与以前完全一致,交互后照常更新。公开 API 不变,使用方无需改动。

  • fa372ce: 包描述文件修正(优化方案 P1)。

    • 六个包装包的 exports 统一为 types / import / default 三个条件。
    • @ptengine/ui 与 @ptengine/widgets 的 exports 补上 CDN 入口(dist/<ns>/<ns>.esm.js), 文档里让用户加载的路径在打包器里也能解析。
  • b3a2007: 无障碍修正(优化方案 P2,由 axe 扫描发现):pt-checkbox 的方框、pt-switch 的轨道与 pt-select 的触发器 此前没有可访问名(role 元素本身无内容,<label> 包着的又不是原生 input),现在没传 aria-label 时用 aria-labelledby 指向插槽里的标签文字 / 当前值。pt-switch 的标签文字新增 part="label"。

  • a192b6a: 开发站(:3333 / :3334)从平铺单页改成一个组件一页。

    / 是总览,每个组件一张卡片(带预览片段,改完设计变量一眼看全部);点进去是 /button.html 这样的单组件页,只加载那一个组件。浏览器的地址栏就是路由,没有写任何 路由代码。侧栏、主题开关(记在 localStorage,翻页不丢)和组件清单来自 dev/_shell.js 与 dev/_nav.js,两个包的外壳文件逐字节相同,只有清单各写各的。

    顺带修掉一个一直存在、但因为不报错所以没人发现的问题:packages/*/dev/ 不在 Stencil 的 watch 范围里(它只递归监听 srcDir),此前改 dev 页必须重启 dev server 才生效。 现在登记在 watchExternalDirs 里。已经开着的 dev server 要重启一次才会生效。

    • pnpm component:new 多产出一个 dev 页,并往 dev/_nav.js 追加一条;新增 --cn 传中文名
    • pnpm components:check 新增五条检查:每个组件在开发站上看得见、有自己那页的必须进侧栏、 每页都引全 global CSS / esm 加载器 / _shell.js、没有指向已删组件的孤儿页。 其中 global CSS 那条此前只是 AGENTS.md 里的口头约定,现在由 CI 守着
    • 补上此前漏掉的 pt-textarea 开发页
  • 5ec427e: 组件 CSS 的行内方向改用逻辑属性(pt-select / pt-modal,以及 pt-option 所用的 utils/list-item.css),为 RTL 预留,LTR 下观感不变;全局样式表新增 pt-table 光 DOM 样式段(为后续的 pt-table 组件准备)。

  • 8e45d0a: tokens:新增三个变量,为 design-components 的 Sheet / AlertDialog / Drawer 等组件迁入做准备。

    • 语义色 --pt-bg-overlay-strong:black 80% 的强遮罩,明暗同值。
    • 层级 --pt-z-tooltip:默认 var(--pt-z-popup),与 popup 同层,只是让气泡可被单独抬高。
    • 阴影 --pt-shadow-xl:对齐 Tailwind 3 的 shadow-xl。

    ui:pt-tooltip 的气泡改用 --pt-z-tooltip,默认值不变,视觉无变化。

  • Updated dependencies [3e88d1b]

  • Updated dependencies [39dca9b]

  • Updated dependencies [9126cf4]

  • Updated dependencies [8e45d0a]

    • @ptengine/tokens@0.1.0

最后更新:

Apache-2.0 协议开源