Popover 彈出框
富內容彈出層:比 Tooltip 承載更多資訊(標題、正文、操作按鈕)。觸發器包裹在預設插槽裡,彈出內容透過 content 插槽傳入。點選觸發時彈出層是 role="dialog" 並圈住焦點,懸停觸發時只展示內容、不打斷鍵盤流。
基礎用法
點選觸發
vue
<script setup lang="ts">
</script>
<template>
<LhPopover title="水墨提示" placement="top">
<LhButton>点击弹出</LhButton>
<template #content>
<p>这是一段富内容,可以放置任意 markup。</p>
</template>
</LhPopover>
</template>vue
<script>
export default {}
</script>
<template>
<LhPopover title="水墨提示" placement="top">
<LhButton>点击弹出</LhButton>
<template #content>
<p>这是一段富内容,可以放置任意 markup。</p>
</template>
</LhPopover>
</template>觸發方式
hover / focus / manual
vue
<template>
<!-- 懸停觸發 -->
<LhPopover trigger="hover" title="悬停触发" :show-after="0">
<LhButton>悬停我</LhButton>
<template #content><p>指针移进弹出层不会触发关闭。</p></template>
</LhPopover>
<!-- 聚焦觸發 -->
<LhPopover trigger="focus" title="聚焦触发" :show-after="0">
<LhInput placeholder="聚焦输入框" />
<template #content><p>输入框获得焦点时展示。</p></template>
</LhPopover>
<!-- 手動控制 -->
<LhPopover v-model:visible="manualVisible" trigger="manual" title="手动控制">
<LhButton @click="manualVisible = !manualVisible">外部开关</LhButton>
<template #content><p>显隐完全由外部 v-model 控制。</p></template>
</LhPopover>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const manualVisible = ref(false)
</script>vue
<template>
<!-- 懸停觸發 -->
<LhPopover trigger="hover" title="悬停触发" :show-after="0">
<LhButton>悬停我</LhButton>
<template #content><p>指针移进弹出层不会触发关闭。</p></template>
</LhPopover>
<!-- 聚焦觸發 -->
<LhPopover trigger="focus" title="聚焦触发" :show-after="0">
<LhInput placeholder="聚焦输入框" />
<template #content><p>输入框获得焦点时展示。</p></template>
</LhPopover>
<!-- 手動控制 -->
<LhPopover v-model:visible="manualVisible" trigger="manual" title="手动控制">
<LhButton @click="manualVisible = !manualVisible">外部开关</LhButton>
<template #content><p>显隐完全由外部 v-model 控制。</p></template>
</LhPopover>
</template>
<script>
export default {
data() {
return { manualVisible: false }
},
}
</script>十二種方位
placement
vue
<template>
<LhPopover
v-for="p in placements"
:key="p"
:placement="p"
:title="p"
trigger="hover"
:show-after="0"
>
<LhButton size="small">{{ p }}</LhButton>
<template #content><p>方位:{{ p }}</p></template>
</LhPopover>
</template>
<script setup lang="ts">
const placements = [
'top', 'top-start', 'top-end',
'bottom', 'bottom-start', 'bottom-end',
'left', 'left-start', 'left-end',
'right', 'right-start', 'right-end',
] as const
</script>vue
<template>
<LhPopover
v-for="p in placements"
:key="p"
:placement="p"
:title="p"
trigger="hover"
:show-after="0"
>
<LhButton size="small">{{ p }}</LhButton>
<template #content><p>方位:{{ p }}</p></template>
</LhPopover>
</template>
<script>
export default {
data() {
return {
placements: [
'top', 'top-start', 'top-end',
'bottom', 'bottom-start', 'bottom-end',
'left', 'left-start', 'left-end',
'right', 'right-start', 'right-end',
],
}
},
}
</script>視區空間不足時彈出層會自動翻轉方向(flip)並夾回交叉軸(shift),箭頭位置隨之切換。
巢狀互動內容
表單示例
vue
<template>
<LhPopover placement="right" title="快速备注">
<LhButton>添加备注</LhButton>
<template #content>
<div class="demo-popover-form">
<LhInput v-model="note" placeholder="填写备注内容" />
<LhButton type="primary" @click="saveNote">保存</LhButton>
</div>
</template>
</LhPopover>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const note = ref('')
const saved = ref('')
function saveNote() {
saved.value = note.value
note.value = ''
}
</script>vue
<template>
<LhPopover placement="right" title="快速备注">
<LhButton>添加备注</LhButton>
<template #content>
<div class="demo-popover-form">
<LhInput v-model="note" placeholder="填写备注内容" />
<LhButton type="primary" @click="saveNote">保存</LhButton>
</div>
</template>
</LhPopover>
</template>
<script>
export default {
data() {
return { note: '', saved: '' }
},
methods: {
saveNote() {
this.saved = this.note
this.note = ''
},
},
}
</script>更多屬性
disabled / arrow / width / hideAfter / popperClass / teleportTarget
Teleport 容器(#demo-popover-host)
vue
<script setup lang="ts">
</script>
<template>
<!-- 已停用 -->
<LhPopover disabled title="已禁用">
<LhButton disabled>禁用触发器</LhButton>
<template #content><p>disabled 时触发器与弹出层均不响应。</p></template>
</LhPopover>
<!-- 無箭頭 -->
<LhPopover title="无箭头" :arrow="false" :show-after="0">
<LhButton>无箭头</LhButton>
<template #content><p>arrow 为 false 时不渲染指向箭头。</p></template>
</LhPopover>
<!-- 固定寬度 -->
<LhPopover title="固定宽度" width="240" :show-after="0">
<LhButton>固定宽度</LhButton>
<template #content><p>通过 width 指定弹出层宽度,这里为 240px。</p></template>
</LhPopover>
<!-- 延遲隱藏 -->
<LhPopover title="延迟隐藏" :hide-after="800" :show-after="0">
<LhButton>延迟隐藏</LhButton>
<template #content><p>移出触发器后延迟 800ms 才收起。</p></template>
</LhPopover>
<!-- 自訂類別名 -->
<LhPopover title="自定义类名" popper-class="demo-popper-accent" :show-after="0">
<LhButton>自定义类名</LhButton>
<template #content><p>popperClass 会加在弹出层根元素上,可用于定制样式。</p></template>
</LhPopover>
<!-- 指定傳送目標 -->
<LhPopover title="指定传送目标" teleport-target="#demo-popover-host" :show-after="0">
<LhButton>Teleport 容器</LhButton>
<template #content><p>弹出层被传送到下方容器而非 body。</p></template>
</LhPopover>
</template>vue
<script>
export default {}
</script>
<template>
<!-- 已停用 -->
<LhPopover disabled title="已禁用">
<LhButton disabled>禁用触发器</LhButton>
<template #content><p>disabled 时触发器与弹出层均不响应。</p></template>
</LhPopover>
<!-- 無箭頭 -->
<LhPopover title="无箭头" :arrow="false" :show-after="0">
<LhButton>无箭头</LhButton>
<template #content><p>arrow 为 false 时不渲染指向箭头。</p></template>
</LhPopover>
<!-- 固定寬度 -->
<LhPopover title="固定宽度" width="240" :show-after="0">
<LhButton>固定宽度</LhButton>
<template #content><p>通过 width 指定弹出层宽度,这里为 240px。</p></template>
</LhPopover>
<!-- 延遲隱藏 -->
<LhPopover title="延迟隐藏" :hide-after="800" :show-after="0">
<LhButton>延迟隐藏</LhButton>
<template #content><p>移出触发器后延迟 800ms 才收起。</p></template>
</LhPopover>
<!-- 自訂類別名 -->
<LhPopover title="自定义类名" popper-class="demo-popper-accent" :show-after="0">
<LhButton>自定义类名</LhButton>
<template #content><p>popperClass 会加在弹出层根元素上,可用于定制样式。</p></template>
</LhPopover>
<!-- 指定傳送目標 -->
<LhPopover title="指定传送目标" teleport-target="#demo-popover-host" :show-after="0">
<LhButton>Teleport 容器</LhButton>
<template #content><p>弹出层被传送到下方容器而非 body。</p></template>
</LhPopover>
</template>受控顯隱
v-model:visible 手動控制彈出層顯隱。
受控顯隱
vue
<script setup lang="ts">
import { ref } from 'vue'
const popVisible = ref(false)
</script>
<template>
<LhPopover v-model:visible="popVisible" title="受控弹出层" trigger="manual">
<LhButton>{{ popVisible ? '收起' : '展开' }}</LhButton>
</LhPopover>
</template>vue
<script>
export default {
data() {
return { popVisible: false }
},
}
</script>
<template>
<LhPopover v-model:visible="popVisible" title="受控弹出层" trigger="manual">
<LhButton>{{ popVisible ? '收起' : '展开' }}</LhButton>
</LhPopover>
</template>Popover API
Props
| 屬性名 | 說明 | 類型 | 預設值 |
|---|---|---|---|
| placement | 展示位置 | "top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end" | 'top' |
| trigger | 觸發方式 | "click" | "hover" | "focus" | "manual" | 'click' |
| title | 標題(也可用 title 插槽) | string | — |
| visible | 手動控制的顯示狀態(v-model:visible) | boolean | — |
| disabled | 是否停用 | boolean | — |
| arrow | 是否顯示箭頭 | boolean | true |
| width | 彈出層寬度 | string | number | — |
| popperClass | 彈出層自訂類別名 | string | — |
| teleportTarget | Teleport 目標(選擇器或元素) | string | HTMLElement | 'body' |
| showAfter | 延遲顯示時間(ms) | number | — |
| hideAfter | 延遲隱藏時間(ms) | number | — |
| themeVars | 元件級主題變數,覆蓋 ConfigProvider 配置 | { [x: string]: string; } | — |
| bgPattern | 元件級背景花紋,覆蓋 ConfigProvider 配置 | string | — |
| sx | sx 快速版面配置屬性(作用於觸發器根元素) | LhSxProps | — |
Events
| 事件名 | 說明 | 回呼參數 |
|---|---|---|
| update:visible | 顯隱變化 | (value: boolean) |
| show | 顯示後觸發 | — |
| hide | 隱藏後觸發 | — |
Slots
| 插槽名 | 說明 |
|---|---|
| default | 觸發器內容 |
| title | 自訂標題 |
| content | 彈出內容 |
Methods
| 方法 | 說明 |
|---|---|
| show | 程式化顯示 |
| hide | 程式化隱藏 |
| toggle | 程式化切換 |
無障礙
- 點選觸發時彈出層為
role="dialog"+aria-modal="true",觸發器帶aria-haspopup/aria-expanded - 點選觸發會把焦點移入彈出層首個可聚焦元素,Tab 在彈出層內循環,關閉後歸還觸發器焦點
- 懸停/聚焦觸發時不移動焦點,避免打斷鍵盤操作
- Escape 關閉、點選彈出層外部關閉;有標題時透過
aria-labelledby關聯 - 定位引擎為自研純函式(flip/shift 防溢出),零第三方執行時依賴
觸發器滾出視區即收起:彈出層為
fixed定位,觸發器離開視區後它不會再隨觸發器移動, 會以高 z-index 懸浮在頁面內容上。因此無論何種觸發方式(含manual受控模式), 觸發器整體滾出視區時面板都會收起,並拋出update:visible同步外部狀態。
類型定義
本元件對外匯出的 TypeScript 類型結構如下(自動提取自 packages/components/popover/src/types.ts 與 @lh-ui/share/core/* 原始碼,點選各條目展開檢視完整定義;屬性說明見上方 Props 表):
LhPopoverProps
ts
/**
* Popover 元件 Props
*/
export interface LhPopoverProps {
/** 展示位置 */
placement?: LhPlacement
/** 觸發方式 */
trigger?: LhPopoverTrigger
/** 標題 */
title?: string
/** 手動控制的顯示狀態(v-model:visible) */
visible?: boolean
/** 是否停用 */
disabled?: boolean
/** 是否顯示箭頭 */
arrow?: boolean
/** 彈出層寬度 */
width?: string | number
/** 彈出層自訂類別名 */
popperClass?: string
/** Teleport 目標(選擇器或元素,預設 body) */
teleportTarget?: string | HTMLElement
/** 延遲顯示時間(ms) */
showAfter?: number
/** 延遲隱藏時間(ms) */
hideAfter?: number
/** 元件級主題變數,覆蓋 ConfigProvider 配置 */
themeVars?: Record<string, string>
/** 元件級背景花紋,覆蓋 ConfigProvider 配置 */
bgPattern?: string
/** sx 快速版面配置屬性(作用於觸發器根元素) */
sx?: LhSxProps
}LhPopoverPlacement (來自 @lh-ui/share/core/shared/popper)
ts
/**
* Popper 定位引擎(純函式,無框架依賴)
*
* 彈出層(Tooltip / Popover / Popconfirm / Dropdown 等)共用的幾何計算:
* 12 種 placement 擺放、主軸溢出翻轉(flip)、交叉軸夾回(shift)。
* 入參全部是 rect 字面值,不觸碰 DOM,可直接單測。
*/
/** 彈出層方位(方向 + 對齊) */
export type LhPlacement =
| 'top'
| 'top-start'
| 'top-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end'
| 'right'
| 'right-start'
| 'right-end'LhPopoverTrigger (來自 @lh-ui/share/core/popover (logic.ts))
ts
/** 觸發方式 */
export type LhPopoverTrigger = 'click' | 'hover' | 'focus' | 'manual'