{ "schemaVersion": 1, "sourceKind": "authored-spec", "provenance": "authored-in-repo", "specFile": "spec/移动端规格.md", "specSection": "41 · 弹出气泡 Popover", "confidence": "high", "slug": "mobile-popover", "name": "弹出气泡 Popover", "semanticTypeCandidates": [ "popover", "tooltip", "hint-bubble" ], "variantDimensions": [ { "name": "placement", "values": [ "top", "bottom", "left", "right" ] }, { "name": "arrow", "values": [ "false", "true" ] } ], "representativeVariants": [ { "placement": "top", "arrow": "true", "label": "上方气泡(带箭头)" }, { "placement": "bottom", "arrow": "true", "label": "下方气泡(最常用)" }, { "placement": "left", "arrow": "true", "label": "左侧气泡" }, { "placement": "right", "arrow": "false", "label": "右侧不带箭头" } ], "anatomy": { "popover": "根元素,position: relative 的包一层,气泡在其中绝对定位(因此永远贴着自己的触发器)", "trigger": "触发器(原生 button),热区 ≥44px,写 aria-expanded 与 aria-haspopup", "panel": "气泡面板,按 placement 贴着触发器的某一侧;role=\"dialog\" 并带 aria-label", "title / text": "标题与正文,可选", "箭头": "arrow=true 时由纯 CSS 三角(border 拼出,无图片、无 hex)指向触发器" }, "structurePatterns": { "placement": "top / bottom / left / right(气泡相对触发器出现在哪一侧)", "arrow": "false 不带三角 / true 三角指向触发器", "状态类": "is-open 展开(触发器同步 aria-expanded=true)" }, "usageHints": [ "在某个元素的旁边弹出一小块说明或轻量操作(运费规则、字段解释、更多操作),说完就收", "触屏没有悬停:桌面端移开鼠标就收起,触屏必须显式给一条关闭路径(点击气泡之外)", "气泡是相对某个触发元素定位的,不是铺满视口的浮层 —— 这是它与轻提示 / 遮罩层的根本区别", "一次轻点触发器展开 / 收起,同一个按钮负责开与关", "边缘空间不足时的翻转(flip)由宿主决定:把 placement 换成对侧即可,组件不做自动测量" ], "doNotInvent": [ "基于可用空间的自动翻转与自动方位选择(placement 由宿主决定)", "悬停触发(触屏没有悬停;桌面端若需要,由宿主包装)", "气泡内的表单校验与提交流程", "气泡之间的互斥开关(哪个开着由宿主管理)" ], "unknowns": [ "是否需要在气泡贴近视口边缘时自动夹在边界内(当前只做静态定位)", "role=\"dialog\" 对纯文字说明类气泡是否过重(当前统一用 dialog + aria-label)", "点击外部关闭是否需要区分「点了另一个气泡」的情况" ], "interaction": [ "一次轻点触发器展开 / 收起,同一个按钮负责开与关", "点击外部关闭:落点不在「气泡或触发器」之内时收起;气泡没有遮罩可依赖,靠文档级 pointerdown 判定", "uni-app 端没有 document,改用铺满视口的透明捕获层(层级低于面板)", "不响应长按、双击与拖动;气泡自身不消费纵向滚动", "气泡与触发器之间留 --kole-m-popover-gap(默认 8px)的间隙,避免气泡盖住触发器的按下反馈", "开合动效 120ms(--kole-duration-fast),减少动态偏好下瞬时切换" ], "accessibility": [ "触发器是原生 button,带 aria-expanded(读屏能播报「已展开 / 已折叠」)与 aria-haspopup", "面板 role=\"dialog\" + aria-label:名称取 title,为空时退回触发器文字,保证读屏不会读到一个匿名对话框", "弹出时不移动焦点(气泡是补充说明而非中断式浮层)", "气泡不因展开而隐藏任何内容:关闭后触发器仍在原地可再次打开" ], "api": { "source": "implementation", "note": "props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs", "requiredNote": "「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。", "props": [ { "name": "open", "type": "boolean", "default": "false", "desc": "状态 open:气泡展开可见(规格 §41.4)", "required": false }, { "name": "placement", "type": "'top' | 'bottom' | 'left' | 'right'", "default": "'bottom'", "desc": "变体 placement:气泡相对触发器出现在哪一侧(规格 §41.3)", "required": false }, { "name": "triggerText", "type": "string", "default": "''", "desc": "触发器文字,同时作为面板 aria-label 的兜底名称(规格 §41.2 trigger)", "required": false }, { "name": "title", "type": "string", "default": "''", "desc": "标题,可选;同时作为面板的 aria-label(规格 §41.2 title)", "required": false }, { "name": "text", "type": "string", "default": "''", "desc": "正文文字,可选(规格 §41.2 text)", "required": false }, { "name": "arrow", "type": "boolean", "default": "true", "desc": "变体 arrow:是否渲染指向触发器的小三角(规格 §41.3)", "required": false }, { "name": "closeOnOutside", "type": "boolean", "default": "true", "desc": "落点在气泡与触发器之外时是否关闭(规格 §41.5)", "required": false } ], "events": [ { "name": "toggle", "params": "—", "desc": "点击触发器时触发;是否展开由宿主决定(受控)(规格 §41.5)" }, { "name": "close", "params": "—", "desc": "落点在气泡与触发器之外、且 closeOnOutside=true 时触发(规格 §41.5)" } ], "slots": [ { "name": "default", "desc": "气泡内容(放在 title / text 之后,用于自定义内容)" } ] }, "variantClasses": { "placement": { "top": [ ".kole-m-popover__panel--top" ], "bottom": [ ".kole-m-popover__panel--bottom" ], "left": [ ".kole-m-popover__panel--left" ], "right": [ ".kole-m-popover__panel--right" ] }, "arrow": { "false": [], "true": [ ".kole-m-popover__panel--arrow" ] } }, "demos": [ { "id": "placements", "group": "01 组件类型", "title": "四个方向", "desc": "上方 / 下方 / 左侧 / 右侧:气泡各自相对自己的触发器定位,所以四个可以同页并列而互不影响。", "variant": "placement=top|bottom|left|right" }, { "id": "arrow", "group": "01 组件类型", "title": "无箭头", "desc": "arrow=false:省掉三角,仅靠位置表达归属,适合小屏上空间紧张的场景。", "variant": "arrow=false" }, { "id": "title", "group": "02 组件状态", "title": "带标题", "desc": "title 给出这条气泡在说什么,正文再补一句;标题同时作为面板的 aria-label 供读屏播报。", "variant": "title 非空" }, { "id": "outside", "group": "02 组件状态", "title": "点击外部关闭", "desc": "气泡没有遮罩可依赖,落点在气泡与触发器之外时收起;触屏上这是唯一的隐式关闭路径。", "variant": "closeOnOutside=true" } ], "related": [ { "slug": "mobile-popup", "why": "需要铺满视口、带方向位移与标题栏的浮层时用弹出层;气泡只贴着触发器,不覆盖整屏" }, { "slug": "mobile-toast", "why": "只是短暂告知结果、不需要贴着某个元素时用轻提示;气泡会一直停在触发器旁边等用户处理" }, { "slug": "mobile-dialog", "why": "需要用户确认或输入的中断式浮层用对话框,气泡不该承载需要决策的内容" } ] }