Popover (всплывающее окно)
Всплывающий слой с богатым содержимым: вмещает больше информации, чем Tooltip (заголовок, основной текст, кнопки действий). Триггер оборачивается в слот по умолчанию, содержимое передаётся через слот content. При срабатывании по клику слой имеет role="dialog" и захватывает фокус; при срабатывании по наведению только отображает содержимое, не прерывая поток клавиатурной навигации.
Базовое использование
<script setup lang="ts">
</script>
<template>
<LhPopover title="Подсказка в стиле тушь" placement="top">
<LhButton>Кликните, чтобы открыть</LhButton>
<template #content>
<p>Это содержимое с богатой разметкой: здесь можно разместить любой markup.</p>
</template>
</LhPopover>
</template><script>
export default {}
</script>
<template>
<LhPopover title="Подсказка в стиле тушь" placement="top">
<LhButton>Кликните, чтобы открыть</LhButton>
<template #content>
<p>Это содержимое с богатой разметкой: здесь можно разместить любой markup.</p>
</template>
</LhPopover>
</template>Способ вызова
<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><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>Двенадцать сторон
<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><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>При нехватке места во viewport всплывающий слой автоматически меняет сторону (flip) и сдвигается по поперечной оси (shift); положение стрелки меняется вместе с ним.
Вложенное интерактивное содержимое
<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><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>Дополнительные свойства
<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>Скрытие происходит с задержкой 800 мс после ухода курсора.</p></template>
</LhPopover>
<!-- Пользовательский класс -->
<LhPopover title="Пользовательский класс" popper-class="demo-popper-accent" :show-after="0">
<LhButton>Пользовательский класс</LhButton>
<template #content><p>popperClass добавляется корневому элементу всплывающего слоя и пригоден для стилизации.</p></template>
</LhPopover>
<!-- Цель Teleport -->
<LhPopover title="Цель Teleport" teleport-target="#demo-popover-host" :show-after="0">
<LhButton>Контейнер Teleport</LhButton>
<template #content><p>Всплывающий слой teleport-ируется в контейнер ниже, а не в body.</p></template>
</LhPopover>
</template><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>Скрытие происходит с задержкой 800 мс после ухода курсора.</p></template>
</LhPopover>
<!-- Пользовательский класс -->
<LhPopover title="Пользовательский класс" popper-class="demo-popper-accent" :show-after="0">
<LhButton>Пользовательский класс</LhButton>
<template #content><p>popperClass добавляется корневому элементу всплывающего слоя и пригоден для стилизации.</p></template>
</LhPopover>
<!-- Цель Teleport -->
<LhPopover title="Цель Teleport" teleport-target="#demo-popover-host" :show-after="0">
<LhButton>Контейнер Teleport</LhButton>
<template #content><p>Всплывающий слой teleport-ируется в контейнер ниже, а не в body.</p></template>
</LhPopover>
</template>Контролируемая видимость
v-model:visible управляет видимостью всплывающего слоя вручную.
<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><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 | Задержка показа (мс) | number | — |
| hideAfter | Задержка скрытия (мс) | 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 от переполнения), без сторонних runtime-зависимостей
Триггер прокрутился за пределы viewport — слой скрывается: всплывающий слой позиционирован как
fixed, и после ухода триггера из viewport он больше не следует за ним, оставаясь висеть над содержимым страницы с высоким z-index. Поэтому при любом способе вызова (включая контролируемый режимmanual) полный уход триггера из viewport сворачивает панель и порождаетupdate:visibleдля синхронизации внешнего состояния.
Определения типов
Структуры TypeScript, экспортируемые этим компонентом (автоматически извлечены из packages/components/popover/src/types.ts и исходников @lh-ui/share/core/*; нажмите на каждый пункт, чтобы развернуть полное определение; описания свойств см. в таблице Props выше):
LhPopoverProps
/**
* Props компонента Popover
*/
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
/** Задержка показа (мс) */
showAfter?: number
/** Задержка скрытия (мс) */
hideAfter?: number
/** Переменные темы на уровне компонента, переопределяют конфигурацию ConfigProvider */
themeVars?: Record<string, string>
/** Узор фона на уровне компонента, переопределяет конфигурацию ConfigProvider */
bgPattern?: string
/** Свойства быстрой разметки sx (применяются к корневому элементу триггера) */
sx?: LhSxProps
}LhPopoverPlacement (из @lh-ui/share/core/shared/popper)
/**
* Движок позиционирования 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))
/** Способ вызова */
export type LhPopoverTrigger = 'click' | 'hover' | 'focus' | 'manual'