切换主题
YModal
中心聚焦的任务容器。用于编辑、确认和选择;业务草稿、保存和权威回读仍由Feature拥有。
Usage
Import / Basic
vue
<script setup lang="ts">
import { ref } from 'vue'
import { YModal } from '@yoyoboot/ui-patterns/modal'
const open = ref(false), name = ref(''), saving = ref(false)
</script>
<template>
<UButton @click="open = true">编辑</UButton>
<YModal v-model:open="open" title="编辑" size="lg" expandable
:guard="{ dirty: () => !!name, busy: () => saving }">
<UInput v-model="name" aria-label="名称" />
<template #footer="{ close }"><UButton @click="close()">取消</UButton></template>
</YModal>
</template>Programmatic task
应用沿现有 createModalHelper/modalHelperKey 接线,在setup中connect(useOverlay())。useModalHelper().open(Component, params, options)获得ModalRef,其result直接沿Nuxt UI实例通道,不再创建结果Promise。任务组件接收modal并传给YModal;after:leave转发供上游释放。
modal.notifySaved(value, true)表示保存继续;完成、取消、失效由原ModalRef合同区分。保存成功不自动代表列表刷新成功。parent/replace保留原保护规则,不扩为通用OverlayHelper。
Size / fullscreen / stable
size为sm/md/lg,分别使用max-w-md/max-w-2xl/max-w-4xl;fullscreen、expandable控制全屏,stable使用稳定任务高度。body独立滚动,footer保留safe-area。组件属性优先于ModalRef options。
标题栏与拖动
标题与说明在左侧可收缩区域,fullscreen/restore 与 close 固定在右侧工具区;长标题和窄屏不会把按钮挤走,Feature 不需要用 margin 或私有 CSS 修正位置。
draggable 默认 true,只在桌面非全屏、fine pointer 且 viewport ≥768px 时生效:
- 从标题栏非交互区域开始拖动;关闭、全屏、链接、按钮和输入上的指针操作不启动拖动;
- 拖动只改当前实例的临时位置:位置限制在视口内,标题栏和关闭始终可达;窗口尺寸变化后重新限制;
- fullscreen 忽略临时偏移,restore 恢复进入全屏前的位置并按当前视口重新限制;fullscreen 中不启动拖动;
- 关闭或新建实例回标准位置,不写 localStorage/sessionStorage。
不需要拖动时显式 :draggable="false"。明确不做:边缘 resize、跨会话位置记忆、窗口管理器、记录上一条/下一条。
Task guard / dirty / busy
guard 可省略;只读详情不需要草稿或保存按钮。guard读取业务状态,不复制它:
ts
const guard = { dirty: () => draft.value !== baseline.value, busy: () => saving.value }关闭按钮、Escape、允许的outside dismiss和slot/exposed close()共享关闭策略:busy拒绝;dirty显示放弃确认;确认返回后再次检查busy。业务保存成功或身份/页面失效时,父组件直接令open=false是权威终止,不再询问放弃;普通取消必须调用安全close,而不是直接写open=false。
Responsive / long content
组件类型不会随viewport改变。通过ui.content选择宽度,body滚动,footer保留safe-area。页面自己的提交、验证和回读继续由Feature负责。
Nested / focus
子任务应渲染在父任务的body/content插槽内,使用现有YModal或选择组件。上游负责portal/focus trap;父任务guard可把子任务open纳入busy,防止操作绕过。关闭子任务后焦点回到父触发器。focusFirstField()定位首个可编辑输入,focusFirstError()定位aria-invalid字段并滚到可见位置;自定义输入控件需要提供正确的可聚焦节点与aria-invalid标记。
API
Props
| 属性 | 合同 |
|---|---|
| title | 必填标题 |
| description | 可选说明 |
| open | 声明式v-model,默认false;传modal时由ModalRef拥有 |
| modal | 可选ModalRef |
| guard | 可选ModalGuard,兼容TaskSurfaceGuard;声明式任务使用 |
| size | sm/md/lg,默认沿modal.options或md |
| fullscreen / expandable | 默认沿modal.options或false |
| closeOnOutside | 默认沿modal.options或false |
| draggable | 默认沿modal.options或true;桌面非全屏、fine pointer 且 viewport ≥768px 才生效 |
| stable | 默认false |
Models / Events
声明式update:open;after:leave;close(ModalResult)用于声明式结果。ModalRef结果status包括completed/saved/cancelled/invalidated。直接open=false/finish失效由Owner负责,普通取消使用safe close。
Slots / Exposed methods
default/body为任务内容;footer得到close、complete。暴露close、complete(value)、focusFirstField、focusFirstError。ModalGuard旧import路径继续有效;没有Drawer/SlideoverHelper。
Theme / Visual
安装 @yoyoboot/ui-patterns/visual/management.css 和 visual/motion.css。header/footer/body由management foundation持有,不要求Feature复制背景色。组件仅显式标记自己的Task Surface,不要求安装Starter外壳;ui可以覆盖具体布局。支持light/dark、既有density和reduced-motion,无Skin runtime。
Accessibility
提供有意义的title/description;无障碍关联、遮罩、焦点限制与恢复沿Nuxt UI。busy禁止正常取消但不替业务取消服务端写入;字段错误由Feature标记。Esc在dismissible=false时由上游阻止;close按钮和显式close()仍可在guard许可时取消。
何时不用 YModal
以下继续使用上游 UModal,它们不拥有业务草稿、关闭保护或结果合同,也不建立第二套 ModalHelper:
- YModal 自身的放弃修改确认,避免递归;
- 公共
TaskSurfaceConfirmation的内部确认; YTreePicker的响应式选择原语(桌面 Popover、移动 Modal);- Host 的路由离开未保存确认与身份返回阻塞。
普通新增、编辑、选择、导入导出任务用 YModal。仍直接用上游 UModal 的业务任务由 govern-yoyo-admin-umodal-adoption-v1 跟踪。
Underlying primitive
基于Nuxt UI UModal,保留原生焦点陷阱、portal与过渡机制。
Source / Verification
源码 packages/ui-patterns/src/modal/YModal.vue、contracts.ts、modal-helper.ts、drag.ts。拖动几何、fullscreen 位置与并发实例的不变量由 packages/ui-patterns/tests/modal-drag.spec.ts 覆盖。示例使用正式入口;可运行嵌套实例在 packages/ui-patterns/tests/fixtures/task-surfaces/App.vue。本页是该组件的 canonical 文档;是否进入 VitePress、公开 URL 与导航由 docs/site.manifest.json 决定。