Skip to content

Modal 弹窗 ​

宽度三档:sm = 480 / md = 640 / lg = 960。

基础结构

标题、说明、正文、底部操作各占一个插槽。弹窗初始是关闭的:把 open 设为 true 或调用 show() 才会出现,这里只展示结构。

弹窗默认关闭,页面上看不到它;下面的代码就是它的全部结构。
<pt-modal>
  <span slot="title">确认删除</span>
  <span slot="description">删除后不可恢复。</span>
  <p>正文内容。</p>
  <pt-button slot="footer" variant="secondary">取消</pt-button>
  <pt-button slot="footer" variant="destructive">删除</pt-button>
</pt-modal>

打开与关闭都走 open 属性(show() / hide() 只是它的糖)。关闭后会发 ptClose, detail.reason 说明是怎么关的:close-button / escape / overlay 是用户发起的, api 是调用了 hide()。直接把 open 设为 false 不发 ptClose——那是你自己关的, 受控写法下不需要再被告知一次。同时打开多个弹窗时,Esc 与 Tab 只由最上层那个处理。

点遮罩默认不关闭 ​

弹窗里多半是用户填到一半的东西,手滑点到旁边就整份丢掉,代价和收益完全不成比例。 关闭只留三条明路:右上角 ✕、底部按钮、Esc。

确实需要「点外面就关」的轻量场景,显式加 close-on-overlay。 必须做出选择的场景(如二次确认)可以用 no-escape 连 Esc 也禁掉。

确认框 ​

删除确认这类「必须明确答复」的场景加 alert:面板的角色换成 alertdialog(读屏会连同说明一起念出来)、 遮罩加深、默认不显示右上角 ✕,并且点遮罩永远不关(close-on-overlay 对它无效)。 Esc 仍然可用,等同「取消」;要连它也禁掉再加 no-escape。

确认框

alert 形态:role="alertdialog"、遮罩加深、默认没有右上角关闭按钮,点遮罩不关。同样初始关闭,这里只展示结构。

确认框默认关闭,页面上看不到它;下面的代码就是它的全部结构。
<pt-modal alert>
  <span slot="title">删除这份报表?</span>
  <span slot="description">删除后不可恢复。</span>
  <pt-button slot="footer" variant="secondary">取消</pt-button>
  <pt-button slot="footer" variant="destructive">删除</pt-button>
</pt-modal>

确认框确实需要 ✕ 时显式写 hide-close="false"(框架里 hideClose={false})—— hide-close 不设置时跟随形态,设了就以它为准。

拦截关闭 ​

用户点 ✕、按 Esc、点遮罩(开了 close-on-overlay 时)的那一刻,弹窗先发一个可取消的 ptRequestClose,detail.reason 同上。监听方调用 event.preventDefault(),弹窗就保持打开,也不会发 ptClose —— 典型用法是表单有未保存的改动时先拦下来,换成一个二次确认:

html
<pt-modal id="edit">…</pt-modal>
<script>
  document.querySelector('#edit').addEventListener('ptRequestClose', event => {
    if (form.dirty) event.preventDefault();
  });
</script>
tsx
<PtModal
  open={open}
  onPtRequestClose={event => {
    if (dirty) event.preventDefault();
  }}
  onPtClose={() => setOpen(false)}
>
  …
</PtModal>
vue
<PtModal :open="open" @pt-request-close="e => dirty && e.preventDefault()" @pt-close="open = false">
  …
</PtModal>

hide() 与直接把 open 设为 false 是使用方自己的决定,不经过这个事件,也拦不住。

焦点 ​

打开时焦点落在弹窗容器本身,不给第一个按钮 —— 否则按钮带着焦点环出场, 看上去像「已经默认选中了」。读屏照常宣布弹窗,Tab 从头遍历且不会跑出弹窗, 关闭后焦点还给打开它的那个元素。

需要把焦点交给某个特定元素(比如正文里的输入框)时,设 auto-focus="false"(框架里 autoFocus={false}), 在 ptOpen 里自己聚焦。这时弹窗不移动焦点,但焦点陷阱照样生效:焦点还在弹窗外面时按 Tab, 会被拉到弹窗里的第一个可聚焦元素(Shift+Tab 到最后一个)。

右上角 ✕ 的读屏名称默认是内置文案「关闭」(随 locale 切换),这一处要说别的话时用 close-label。

层级 ​

遮罩打开时进浏览器的顶层(popover API):不会被祖先的 overflow: hidden 裁掉,也不受 transform 祖先和层叠上下文影响,不用再调 z-index。弹窗里再打开的下拉、提示同样进顶层,而且比弹窗后进, 所以始终盖在弹窗之上。不支持 popover API 的环境退回 position: fixed + --pt-z-overlay。

移动端 ​

窄屏(≤640px)下自动切换为底部抽屉:贴着底边、铺满宽度、上方两角圆角。

API ​

属性

属性Attribute类型默认值说明
alertalertbooleanfalse确认框形态:面板 `role="alertdialog"`、遮罩加深、默认不显示关闭按钮、点遮罩永远不关。 用于删除确认这类必须明确答复的场景。
ariaLabelTextaria-labelstring | undefined—无障碍名称:原生 HTML 里写 aria-label,React / Vue / Angular 包装层里属性名是 ariaLabelText。 没有用 title 插槽时必须给。
autoFocusauto-focusbooleantrue打开时把焦点移进弹窗(落在弹窗容器本身)。设为 false 时焦点原地不动 —— 适合在 ptOpen 里自己聚焦某个输入框;焦点陷阱照样生效,第一次 Tab 就会被拉进弹窗。 默认为真:原生 HTML 里关掉它写 `auto-focus="false"`。
closeLabelclose-labelstring | undefined—右上角关闭按钮的无障碍名称。不设置时用内置文案(`t('modal.close')`,随 locale 切换)。
closeOnOverlayclose-on-overlaybooleanfalse允许点遮罩关闭(默认关闭,理由见组件说明)。`alert` 形态下无效
hideClosehide-closeboolean | undefined—隐藏右上角关闭按钮。隐藏后必须自己在 footer 里提供关闭入口。 不设置时跟随形态:普通弹窗显示,`alert` 确认框隐藏;显式设为 false 可以让确认框也显示 ✕ (原生 HTML 里写 `hide-close="false"`,或用 JS 赋值)。
noEscapeno-escapebooleanfalse按 Esc 不关闭。用于「必须做出选择」的场景
openopenbooleanfalse是否打开
sizesize"lg" | "md" | "sm"'sm'宽度档位

事件

事件detail 类型说明
ptClose{ reason: PtModalCloseReason; }关闭后触发,detail 说明是怎么关的:`close-button` / `escape` / `overlay` 是用户发起的 (之前的 ptRequestClose 没被取消),`api` 是调用了 hide()。 直接把 open 设为 false **不发**:那是使用方自己关的,受控写法下再回告一次只会让 onPtClose → setOpen(false) 多绕一圈(与 pt-tooltip 的约定一致) **不冒泡**:弹层常互相嵌套(弹窗里的菜单、气泡卡片里的下拉),冒泡的话外层上的监听会收到内层的开关。
ptOpenvoid打开后触发 **不冒泡**:弹层常互相嵌套(弹窗里的菜单、气泡卡片里的下拉),冒泡的话外层上的监听会收到内层的开关。
ptRequestClose{ reason: PtModalRequestCloseReason; }用户要关闭弹窗时(点 ✕ / 按 Esc / 点遮罩)、真正关闭之前触发,可取消: `event.preventDefault()` 后弹窗保持打开,也不会发 ptClose。 `hide()` 与直接把 open 设为 false 不经过它。 **不冒泡**:弹层常互相嵌套(弹窗里的菜单、气泡卡片里的下拉),冒泡的话外层上的监听会收到内层的开关。

方法

方法说明
hide() => Promise<void>关闭弹窗
show() => Promise<void>打开弹窗

插槽

名称说明
(默认)正文
description标题下的说明
footer底部操作区
title标题

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

part说明
base最外层容器(与 overlay 同一元素)
body正文
close关闭按钮(pt-button 宿主)
close-button关闭按钮内部的 button 元素(pt-button 的 base part 转发)
footer底部
header头部
overlay遮罩
panel弹窗本体

CSS 变量

变量说明
--pt-modal-width弹窗最大宽度,默认按 size 档位(sm 480px / md 640px / lg 960px)。窄屏抽屉形态下铺满宽度,不受它影响

Apache-2.0 协议开源