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>12 種類の方位
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'