Accordion 手风琴
一组纵向排列、各自可展开的区块。适合 FAQ、设置分组这类「标题一眼扫完、需要时再展开细节」的内容。 只有一块内容要折叠时用 Collapsible 折叠面板。
每一项的标题放进 trigger 插槽、内容放默认插槽。默认 single:同一时间最多展开一项;collapsible 让已展开的那项能再点收起。
<pt-accordion value="refund" collapsible>
<pt-accordion-item value="refund">
<span slot="trigger">能退款吗?</span>
购买 7 天内可无理由退款,原路退回。
</pt-accordion-item>
<pt-accordion-item value="payment">
<span slot="trigger">支持哪些付款方式?</span>
信用卡、支付宝与银行转账。
</pt-accordion-item>
<pt-accordion-item value="invoice">
<span slot="trigger">怎么开发票?</span>
在「账单」页选择对应订单,填写抬头后即时开具。
</pt-accordion-item>
</pt-accordion>type="multiple" 时各项独立展开,value 是字符串数组(HTML 里写成逗号分隔)。禁用的项点不开,方向键也跳过它。
<pt-accordion type="multiple" value="general,privacy">
<pt-accordion-item value="general">
<span slot="trigger">通用</span>
语言、时区与默认首页。
</pt-accordion-item>
<pt-accordion-item value="privacy">
<span slot="trigger">隐私</span>
数据保留期限与访客 IP 匿名化。
</pt-accordion-item>
<pt-accordion-item value="billing" disabled>
<span slot="trigger">计费(仅组织所有者可改)</span>
套餐与付款方式。
</pt-accordion-item>
</pt-accordion>结构
pt-accordion 里直接放若干 pt-accordion-item;每一项的标题放进 trigger 插槽,内容放默认插槽。 项必须是 pt-accordion 的直接子元素 —— 手风琴只认自己的直接子项,所以可以放心嵌套,内外层互不串。
每一项都写上 value,展开态靠它对应。没写的项也能点开,但它的键是内部分配的,没法事先写进 value 受控。
受控值
受控属性是 value,事件是 ptChange:
type | value 的形状 | 全部收起时 |
|---|---|---|
single(默认) | 字符串:展开那一项的 value | '' |
multiple | 字符串数组:展开的各项的 value | [] |
- Vue:
<PtAccordion v-model="open"> - Angular:
<pt-accordion [(ngModel)]="open">,standalone 组件要在imports里加上TextValueAccessor - React:
<PtAccordion value={open} onPtChange={e => setOpen(e.detail)}>
数组只能用 JS / 框架绑定赋值;在 HTML 里写 multiple 的初始值时用逗号分隔:value="general,privacy" (各段去掉首尾空白)。因此 multiple 下项的 value 本身不要含逗号。
ptChange 只在用户点击标题切换时发;外部直接改 value 只改状态,不发事件。 multiple 下组件写回的总是一个新数组,Vue / React 的浅比较能感知变化。
ptChange 不冒泡
手风琴会嵌套,而三个框架的双向绑定都挂在宿主上 —— 冒泡的话展开内层的项会连带改写外层绑定。 要在外层容器上统一监听,用捕获阶段:el.addEventListener('ptChange', fn, true)。
可收起
single 下默认总有一项开着:再点已展开的那项不会收起它。加 collapsible 后可以,收起后 value 为空串。 multiple 下各项本来就能各自收起,collapsible 无意义。
禁用
项上的 disabled 只禁用那一项;pt-accordion 上的 disabled 禁用全部项。被禁用的标题不可点、不可聚焦, 方向键跳过它。禁用只拦用户操作 —— 外部改 value 照样能展开 / 收起。
键盘与无障碍
按 WAI-ARIA Accordion 模式:
- 每一项的标题都是一个原生按钮,全部在 Tab 序列里;Enter / Space 切换。
- 焦点在某个标题上时,↑ ↓ 在各项标题之间移动(到头回绕),Home / End 跳到第一项 / 最后一项。 焦点在内容里(比如输入框)时方向键不归手风琴管。
- 标题外面包着
role="heading",层级用项上的heading-level设(默认 3),按它在页面标题结构里的实际位置给。 - 按钮带
aria-expanded、aria-controls;内容区是role="region",以标题为名。
收起时内容区 inert 且不可见:Tab 不会走进看不见的内容,读屏也不会读到它。 展开 / 收起的高度过渡与 Collapsible 相同,系统开了「减少动态效果」时直接切换。
API
属性
| 属性 | Attribute | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
collapsible | collapsible | boolean | false | `single` 下再点已展开的那一项能否把它收起(收起后 `value` 为空串)。默认不能 —— 总有一项开着。`multiple` 下无意义 |
disabled | disabled | boolean | false | 禁用整个手风琴:全部标题不可点、不可聚焦。不影响通过 `value` 控制展开态 |
type | type | "multiple" | "single" | 'single' | 展开方式: - `single`:同一时间最多展开一项,点开另一项时原来那项收起; - `multiple`:各项独立展开。 |
value | value | string | string[] | '' | 展开着的项的 `value`。 - `single`:字符串,空串表示全部收起; - `multiple`:字符串数组。数组只能用 JS / 框架绑定赋值;写成 HTML attribute 时用**逗号分隔** (`value="a,c"`,各段去掉首尾空白),因此 multiple 下项的 `value` 本身不要含逗号。 用户点击标题时组件自己改写它(multiple 下总是写回一个新数组)并发 `ptChange`;外部直接赋值不发事件。 |
事件
| 事件 | detail 类型 | 说明 |
|---|---|---|
ptChange | string | string[] | 用户点击标题切换展开态后触发,detail 是切换后的 `value`(`single` 为字符串、`multiple` 为字符串数组)。 **不冒泡**(与其它组件的默认相反):手风琴会嵌套,而三个框架的双向绑定都挂在宿主上 —— Vue 的 v-model 只按 tagName 过滤冒泡事件,内外层同是 PT-ACCORDION 拦不住。冒泡的话展开内层的项 会连带改写外层绑定。要在外层容器上统一监听,用捕获阶段(`addEventListener('ptChange', fn, true)`)。 |
方法
| 方法 | 说明 |
|---|---|
refresh() => Promise<void> | 重新按 `value` 同步各项的展开态。子项增删、改 value 时组件自己会调,一般不需要手动调用 |
插槽
| 名称 | 说明 |
|---|---|
(默认) | 若干 `pt-accordion-item` |
pt-accordion-item
属性
| 属性 | Attribute | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
disabled | disabled | boolean | false | 禁用这一项:标题按钮不可点、不可聚焦,方向键跳过它。父 `pt-accordion` 的 `disabled` 也会禁用全部项 |
headingLevel | heading-level | number | 3 | 标题的层级(`aria-level`),按它在页面标题结构里的位置给,1–6。默认 3 |
open | open | boolean | false | 是否展开。由父 `pt-accordion` 按它的 `value` 回写,只读;样式可用 `pt-accordion-item[open]` |
value | value | string | undefined | — | 这一项的值,父元素 `value` 里出现它就展开。同一个手风琴里要唯一。 不给时父元素会给它分配一个内部键(照样能点开),但那个键没法事先写进 `value`,也就没法受控 —— 建议总是写上 |
插槽
| 名称 | 说明 |
|---|---|
(默认) | 展开后显示的内容 |
trigger | 标题内容(文字,或文字加图标)。整行都是按钮,不要在里面放别的可交互元素 |
可定制的内部元素(::part())
| part | 说明 |
|---|---|
body | 内容的内边距层 |
content | 内容区(做高度过渡的那一层,role="region") |
heading | 标题行(role="heading") |
icon | 标题尾部的展开指示箭头 |
trigger | 标题按钮 |