Skip to content

Popover 气泡卡片 ​

点触发元素,在它旁边弹出一块可交互的浮层:筛选面板、简短表单、说明卡片。 只是看一眼的补充说明用 Tooltip;打断当前流程、必须处理的用 Modal。

基础用法

触发元素放进 trigger 插槽,点它切换;内容放默认插槽。点外面、按 Esc 或再点一次触发元素关闭。

<pt-popover aria-label="尺寸设置">
  <pt-button slot="trigger" variant="secondary">尺寸设置</pt-button>
  <div>
    <p>宽度与高度</p>
    <pt-input aria-label="宽度" value="100%"></pt-input>
  </div>
</pt-popover>
方位、对齐与锚点

placement 是首选方位,放不下时翻到对面;align 是交叉轴对齐。浮层要贴着另一块区域时把它放进 anchor 插槽。

<pt-popover placement="top" align="start" aria-label="上方">
  <pt-button slot="trigger" variant="secondary">top · start</pt-button>
  <div>在触发元素上方,行首对齐。</div>
</pt-popover>
<pt-popover align="start" aria-label="锚点">
  <pt-input slot="anchor" aria-label="关键词" placeholder="浮层贴着输入框"></pt-input>
  <pt-button slot="trigger" variant="secondary">选择</pt-button>
  <div>定位以 anchor 插槽里的输入框为准。</div>
</pt-popover>

触发与锚点 ​

触发元素放进 trigger 插槽,通常是 <button> 或 pt-button,点它切换展开。键盘由它自己负责: 原生按钮上的 Enter / Space 本来就会派发 click。

浮层默认贴着触发元素定位。要贴着另一块区域(例如整个输入框,而不只是旁边的按钮)时,把那块区域放进 anchor 插槽,点击仍由触发元素负责。

位置 ​

placement 是首选方位(默认 bottom),视口放不下时翻到对面,入场动画随之从锚点一侧滑入;反射到属性上的仍是首选值。 align 是交叉轴上的对齐(start / center / end,默认 center):placement 为 top / bottom 时 start 是行首一侧(RTL 下是右),为 left / right 时 start 是上。offset 是浮层与锚点的间距,默认 4px。

浮层经 popover API 进顶层:放在 overflow: hidden 的容器、表格单元格或带 transform 的祖先里都不会被裁切。 窄屏(≤640px)下切换为底部抽屉,带背景遮罩,点遮罩关闭。

宽度默认 288px,用 CSS 变量 --pt-popover-width 改。

关闭与拦截 ​

三种用户操作会关闭浮层,reason 分别是:

  • escape:按 Esc(输入法组合中的 Esc 不算);
  • outside:在浮层与触发元素之外按下指针;
  • trigger:再点一次触发元素。

关闭之前先发可取消的 ptRequestClose,event.preventDefault() 就保持打开、也不发 ptClose —— 例如浮层里的表单有未保存的改动。 事件约定与 Modal 一致:

  • ptOpen:浮层每次真正渲染出来之后发,不论是交互打开还是外部写 open 打开的。
  • ptClose:只在用户交互关闭时发,detail.reason 同上。外部把 open 写成 false 不发,也不经过 ptRequestClose。

与其它弹层共存。 打开的弹层(Modal、Popover,以及后续的 Menu、HoverCard)共用一个层栈,Esc 只关最上面那层: Modal 里打开的气泡卡片,第一下 Esc 只关气泡卡片,第二下才关 Modal。浮层里展开着的 pt-select、有内容的搜索框 会先把 Esc 用掉(收起下拉、清空搜索词),气泡卡片不跟着关。

焦点 ​

  • 打开后焦点移进浮层:落在内容里第一个可聚焦元素上,没有就落在浮层本身。auto-focus="false" 时焦点留在触发元素上。
  • 关闭时焦点若还在浮层里,回到触发元素;因为点了外面而关闭时不抢焦点 —— 用户已经点向别处了。
  • 默认非模态:Tab 可以离开浮层,页面其余部分照常可用。

模态(modal) ​

加 modal 后按 Radix 的模态 Popover 处理,浮层带 aria-modal="true",另外做四件事:

  • 焦点关在浮层里:Tab 在浮层里循环;焦点还在外面时(auto-focus="false")第一次 Tab 就被拉进来。
  • 点外面不穿透:浮层下面铺一层透明遮罩(同样进顶层),点外面只关浮层(reason 为 outside), 页面上的按钮、链接收不到这一下点击。
  • 页面其余部分对读屏隐藏:从气泡卡片往上走到 <body>,沿途的兄弟元素都设为 inert —— 读屏看不到、 也 Tab 不进去。选 inert 而不是 aria-hidden:后者只管读屏,被它包着的元素仍能被点到、被程序聚焦, axe 还会报「aria-hidden 里有可聚焦元素」。读屏播报区(带 aria-live 的元素,如轻提示)不隐藏; 使用方自己设的 inert 关闭后原样保留;浮层打开期间再弹出的 pt-modal / pt-sheet 若挂在被隐藏的区域里, 会在打开期间把自己放出来。
  • 锁住页面滚动,关闭后还原。

关闭后焦点还给触发元素 —— 包括点外面关闭的情况:页面是 inert 的,那一下点击没有把焦点交给任何东西。 打开期间改 modal 立即生效。

无障碍 ​

浮层是 role="dialog",用 aria-label 给它一个名称(包装层里属性名是 ariaLabelText)。 触发元素上由组件维护 aria-expanded、aria-haspopup="dialog" 与 aria-controls。

aria-controls 指向的是放进默认插槽的内容元素,而不是影子树里的浮层面板:IDREF 不跨影子边界, 指向面板等于没写。内容元素没有 id 时组件自动补一个。aria-expanded 写在触发元素的宿主上 —— 原生 <button> 直接生效;pt-button 的真实按钮在它自己的影子树里,追求读屏完整时优先用原生按钮(同 Collapsible)。

API ​

属性

属性Attribute类型默认值说明
alignalign"center" | "end" | "start"'center'交叉轴上的对齐:top / bottom 时 start 是行首一侧(RTL 下是右),left / right 时 start 是上
ariaLabelTextaria-labelstring | undefined—浮层(role="dialog")的无障碍名称:原生 HTML 里写 aria-label, React / Vue / Angular 包装层里属性名是 ariaLabelText。
autoFocusauto-focusbooleantrue打开后把焦点移进浮层:落在内容里第一个可聚焦元素上,没有就落在浮层本身。 设为 false 时焦点留在触发元素上。默认为真:原生 HTML 里关掉它写 `auto-focus="false"`。
modalmodalbooleanfalse模态:焦点关在浮层里(焦点在浮层外时第一次 Tab 就被拉进来);点外面只关浮层、不穿透到页面; 页面其余部分 inert(读屏看不到);锁住页面滚动。打开期间改它立即生效
offsetoffsetnumber4浮层与锚点之间的距离(px)
openopenbooleanfalse是否展开。用户交互时组件自己改写;外部直接赋值立即生效,不发 ptRequestClose / ptClose
placementplacement"bottom" | "left" | "right" | "top"'bottom'相对锚点的方位。视口放不下时翻到对面,反射的属性仍是这里的首选值

事件

事件detail 类型说明
ptClose{ reason: PtPopoverCloseReason; }用户交互关闭后触发,detail 说明是怎么关的。外部把 open 写成 false **不发** (与 pt-modal / pt-tooltip 一致:受控写法下再回告一次只会多绕一圈) **不冒泡**:弹层常互相嵌套(弹窗里的菜单、气泡卡片里的下拉),冒泡的话外层上的监听会收到内层的开关。
ptOpenvoid浮层渲染出来之后发(交互或属性打开都发,与 pt-modal 一致) **不冒泡**:弹层常互相嵌套(弹窗里的菜单、气泡卡片里的下拉),冒泡的话外层上的监听会收到内层的开关。
ptRequestClose{ reason: PtPopoverCloseReason; }用户要关闭浮层时(Esc / 点外面 / 再点触发元素)、真正关闭之前触发,可取消: `event.preventDefault()` 后浮层保持打开,也不会发 ptClose。直接把 open 设为 false 不经过它。 **不冒泡**:弹层常互相嵌套(弹窗里的菜单、气泡卡片里的下拉),冒泡的话外层上的监听会收到内层的开关。

插槽

名称说明
(默认)浮层内容
anchor定位锚点(可选),渲染在触发元素之前。不给时贴着触发元素
trigger触发元素,点击切换展开

可定制的内部元素(::part())

part说明
base浮层面板(与 panel 同一元素)
panel浮层面板

CSS 变量

变量说明
--pt-popover-width浮层宽度,默认 288px。窄屏抽屉形态下铺满宽度,不受它影响

Apache-2.0 协议开源