## 41 · 弹出气泡 Popover ### 41.1 用途 在**某个元素的旁边**弹出一小块说明或轻量操作(运费规则、字段解释、更多操作),说完就收。移动端与桌面端的关键差别是**触发方式与关闭路径**:桌面端靠 hover 弹出、移开即收;触屏没有悬停,只能点击展开,而展开后「怎么收起来」变成了真问题 —— 桌面上移开鼠标就收了,触屏必须显式给一条关闭路径(点击气泡之外)。这也是它与轻提示 / 遮罩层的根本区别:气泡是**相对某个触发元素**定位的,不是铺满视口的浮层。 ### 41.2 结构(anatomy) - `popover`:根元素,`position: relative` 的包一层,气泡在其中绝对定位(因此永远贴着自己的触发器) - `trigger`:触发器(原生 `button`),热区 ≥44px,写 `aria-expanded` 与 `aria-haspopup` - `panel`:气泡面板,按 `placement` 贴着触发器的某一侧;`role="dialog"` 并带 `aria-label` - `title` / `text`:标题与正文,可选 - 箭头:`arrow=true` 时由纯 CSS 三角(border 拼出,无图片、无 hex)指向触发器 ### 41.3 变体维度 - `placement`:`top` / `bottom` / `left` / `right`(气泡相对触发器出现在哪一侧) - `arrow`:`false`(不带三角,小屏空间紧张时用)/ `true`(三角指向触发器) (`closeOnOutside` 是行为开关而不是形态维度,见 §41.5:它只改变关闭路径,不改变任何视觉。) ### 41.4 状态 - closed:收起(`opacity: 0` + `visibility: hidden` + `pointer-events: none`,不可见也不可点) - open:展开(`is-open`;触发器同步 `aria-expanded="true"` 并转为品牌色描边) ### 41.5 交互与触控 - 一次轻点触发器展开 / 收起,**同一个按钮负责开与关**(触屏没有「移开鼠标」这条隐式关闭路径) - 点击外部关闭:落点不在「气泡或触发器」之内时收起。气泡**没有遮罩可依赖**(有遮罩就成了弹出层),判定靠监听文档级 `pointerdown`;uni-app 端没有 `document`,改用铺满视口的透明捕获层(层级低于面板) - 不响应长按、双击与拖动;气泡自身不消费纵向滚动 —— 内容超长应改用弹出层,不在气泡里做内部滚动 - 气泡与触发器之间留 `--kole-m-popover-gap`(默认 8px)的间隙,避免气泡盖住触发器的按下反馈 - 开合动效 120ms(`--kole-duration-fast`),减少动态偏好下瞬时切换 - 边缘空间不足时的翻转(flip)由宿主决定:把 `placement` 换成对侧即可,组件不做自动测量 ### 41.6 无障碍 - 触发器是原生 `button`,带 `aria-expanded`(读屏能播报「已展开 / 已折叠」)与 `aria-haspopup` - 面板 `role="dialog"` + `aria-label`:名称取 `title`,为空时退回触发器文字,保证读屏不会读到一个匿名对话框 - 弹出时**不移动焦点**(气泡是补充说明而非中断式浮层,抢焦点会让用户丢失当前输入位置);键盘用户用 Tab 进入气泡内容,用 Esc 或再点一次触发器收起 - 气泡不因展开而隐藏任何内容:关闭后触发器仍在原地可再次打开 ### 41.7 doNotInvent - 基于可用空间的自动翻转与自动方位选择(`placement` 由宿主决定) - 悬停触发(触屏没有悬停;桌面端若需要,由宿主包装) - 气泡内的表单校验与提交流程 - 气泡之间的互斥开关(哪个开着由宿主管理) ### 41.8 unknowns - 是否需要在气泡贴近视口边缘时自动夹在边界内(当前只做静态定位) - `role="dialog"` 对纯文字说明类气泡是否过重(当前统一用 dialog + aria-label) - 点击外部关闭是否需要区分「点了另一个气泡」的情况(当前点另一个气泡会同时收起前一个) ---