Skip to content
API 风格:

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是否顯示箭頭booleantrue
width彈出層寬度string | number—
popperClass彈出層自訂類別名string—
teleportTargetTeleport 目標(選擇器或元素)string | HTMLElement'body'
showAfter延遲顯示時間(ms)number—
hideAfter延遲隱藏時間(ms)number—
themeVars元件級主題變數,覆蓋 ConfigProvider 配置{ [x: string]: string; }—
bgPattern元件級背景花紋,覆蓋 ConfigProvider 配置string—
sxsx 快速版面配置屬性(作用於觸發器根元素)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'