Popover (cuadro emergente)
Capa emergente de contenido enriquecido: alberga más información que un Tooltip (título, contenido, botones de acción). El disparador se envuelve en el slot por defecto y el contenido emergente se pasa a través del slot content. Con disparo por clic, la capa emergente es role="dialog" y atrapa el foco; con disparo por suspensión, solo muestra el contenido sin interrumpir el flujo del teclado.
Uso básico
<script setup lang="ts">
</script>
<template>
<LhPopover title="Aviso de tinta" placement="top">
<LhButton>Pulsa para abrir</LhButton>
<template #content>
<p>Este es un contenido enriquecido; puedes incluir cualquier markup.</p>
</template>
</LhPopover>
</template><script>
export default {}
</script>
<template>
<LhPopover title="Aviso de tinta" placement="top">
<LhButton>Pulsa para abrir</LhButton>
<template #content>
<p>Este es un contenido enriquecido; puedes incluir cualquier markup.</p>
</template>
</LhPopover>
</template>Forma de disparo
<template>
<!-- Disparo por suspensión -->
<LhPopover trigger="hover" title="Disparo por suspensión" :show-after="0">
<LhButton>Pasa el cursor sobre mí</LhButton>
<template #content><p>Mover el puntero hacia la capa emergente no la cierra.</p></template>
</LhPopover>
<!-- Disparo por foco -->
<LhPopover trigger="focus" title="Disparo por foco" :show-after="0">
<LhInput placeholder="Campo de enfoque" />
<template #content><p>Se muestra cuando el campo recibe el foco.</p></template>
</LhPopover>
<!-- Control manual -->
<LhPopover v-model:visible="manualVisible" trigger="manual" title="Control manual">
<LhButton @click="manualVisible = !manualVisible">Conmutador externo</LhButton>
<template #content><p>La visibilidad la controla por completo el v-model externo.</p></template>
</LhPopover>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const manualVisible = ref(false)
</script><template>
<!-- Disparo por suspensión -->
<LhPopover trigger="hover" title="Disparo por suspensión" :show-after="0">
<LhButton>Pasa el cursor sobre mí</LhButton>
<template #content><p>Mover el puntero hacia la capa emergente no la cierra.</p></template>
</LhPopover>
<!-- Disparo por foco -->
<LhPopover trigger="focus" title="Disparo por foco" :show-after="0">
<LhInput placeholder="Campo de enfoque" />
<template #content><p>Se muestra cuando el campo recibe el foco.</p></template>
</LhPopover>
<!-- Control manual -->
<LhPopover v-model:visible="manualVisible" trigger="manual" title="Control manual">
<LhButton @click="manualVisible = !manualVisible">Conmutador externo</LhButton>
<template #content><p>La visibilidad la controla por completo el v-model externo.</p></template>
</LhPopover>
</template>
<script>
export default {
data() {
return { manualVisible: false }
},
}
</script>Doce posiciones
<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>Posición: {{ 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>Posición: {{ 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>Cuando no hay espacio suficiente en la ventana, la capa emergente voltea automáticamente de dirección (flip) y se sujeta en el eje transversal (shift); la posición de la flecha cambia en consecuencia.
Contenido interactivo anidado
<template>
<LhPopover placement="right" title="Nota rápida">
<LhButton>Añadir nota</LhButton>
<template #content>
<div class="demo-popover-form">
<LhInput v-model="note" placeholder="Escribe la nota" />
<LhButton type="primary" @click="saveNote">Guardar</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="Nota rápida">
<LhButton>Añadir nota</LhButton>
<template #content>
<div class="demo-popover-form">
<LhInput v-model="note" placeholder="Escribe la nota" />
<LhButton type="primary" @click="saveNote">Guardar</LhButton>
</div>
</template>
</LhPopover>
</template>
<script>
export default {
data() {
return { note: '', saved: '' }
},
methods: {
saveNote() {
this.saved = this.note
this.note = ''
},
},
}
</script>Más propiedades
<script setup lang="ts">
</script>
<template>
<!-- Deshabilitado -->
<LhPopover disabled title="Deshabilitado">
<LhButton disabled>Disparador deshabilitado</LhButton>
<template #content><p>Con disabled, ni el disparador ni la capa emergente responden.</p></template>
</LhPopover>
<!-- Sin flecha -->
<LhPopover title="Sin flecha" :arrow="false" :show-after="0">
<LhButton>Sin flecha</LhButton>
<template #content><p>Con arrow a false no se renderiza la flecha indicadora.</p></template>
</LhPopover>
<!-- Ancho fijo -->
<LhPopover title="Ancho fijo" width="240" :show-after="0">
<LhButton>Ancho fijo</LhButton>
<template #content><p>Aquí se ha especificado el ancho de la capa emergente con width: 240px.</p></template>
</LhPopover>
<!-- Ocultar con retardo -->
<LhPopover title="Ocultar con retardo" :hide-after="800" :show-after="0">
<LhButton>Ocultar con retardo</LhButton>
<template #content><p>Tras salir del disparador, tarda 800 ms en cerrarse.</p></template>
</LhPopover>
<!-- Clase personalizada -->
<LhPopover title="Clase personalizada" popper-class="demo-popper-accent" :show-after="0">
<LhButton>Clase personalizada</LhButton>
<template #content><p>popperClass se añade al elemento raíz de la capa emergente y sirve para personalizar el estilo.</p></template>
</LhPopover>
<!-- Objetivo de teletransporte -->
<LhPopover title="Objetivo de teletransporte" teleport-target="#demo-popover-host" :show-after="0">
<LhButton>Contenedor Teleport</LhButton>
<template #content><p>La capa emergente se teletransporta al contenedor de abajo en lugar de a body.</p></template>
</LhPopover>
</template><script>
export default {}
</script>
<template>
<!-- Deshabilitado -->
<LhPopover disabled title="Deshabilitado">
<LhButton disabled>Disparador deshabilitado</LhButton>
<template #content><p>Con disabled, ni el disparador ni la capa emergente responden.</p></template>
</LhPopover>
<!-- Sin flecha -->
<LhPopover title="Sin flecha" :arrow="false" :show-after="0">
<LhButton>Sin flecha</LhButton>
<template #content><p>Con arrow a false no se renderiza la flecha indicadora.</p></template>
</LhPopover>
<!-- Ancho fijo -->
<LhPopover title="Ancho fijo" width="240" :show-after="0">
<LhButton>Ancho fijo</LhButton>
<template #content><p>Aquí se ha especificado el ancho de la capa emergente con width: 240px.</p></template>
</LhPopover>
<!-- Ocultar con retardo -->
<LhPopover title="Ocultar con retardo" :hide-after="800" :show-after="0">
<LhButton>Ocultar con retardo</LhButton>
<template #content><p>Tras salir del disparador, tarda 800 ms en cerrarse.</p></template>
</LhPopover>
<!-- Clase personalizada -->
<LhPopover title="Clase personalizada" popper-class="demo-popper-accent" :show-after="0">
<LhButton>Clase personalizada</LhButton>
<template #content><p>popperClass se añade al elemento raíz de la capa emergente y sirve para personalizar el estilo.</p></template>
</LhPopover>
<!-- Objetivo de teletransporte -->
<LhPopover title="Objetivo de teletransporte" teleport-target="#demo-popover-host" :show-after="0">
<LhButton>Contenedor Teleport</LhButton>
<template #content><p>La capa emergente se teletransporta al contenedor de abajo en lugar de a body.</p></template>
</LhPopover>
</template>Visibilidad controlada
v-model:visible controla manualmente la visibilidad de la capa emergente.
<script setup lang="ts">
import { ref } from 'vue'
const popVisible = ref(false)
</script>
<template>
<LhPopover v-model:visible="popVisible" title="Capa controlada" trigger="manual">
<LhButton>{{ popVisible ? 'Cerrar' : 'Abrir' }}</LhButton>
</LhPopover>
</template><script>
export default {
data() {
return { popVisible: false }
},
}
</script>
<template>
<LhPopover v-model:visible="popVisible" title="Capa controlada" trigger="manual">
<LhButton>{{ popVisible ? 'Cerrar' : 'Abrir' }}</LhButton>
</LhPopover>
</template>Popover API
Props
| Prop | Descripción | Tipo | Predeterminado |
|---|---|---|---|
| placement | Posición de visualización | "top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end" | 'top' |
| trigger | Forma de disparo | "click" | "hover" | "focus" | "manual" | 'click' |
| title | Título (también se puede usar el slot title) | string | — |
| visible | Estado de visualización controlado manualmente (v-model:visible) | boolean | — |
| disabled | Si se deshabilita | boolean | — |
| arrow | Si se muestra la flecha | boolean | true |
| width | Ancho de la capa emergente | string | number | — |
| popperClass | Clase personalizada de la capa emergente | string | — |
| teleportTarget | Objetivo del Teleport (selector o elemento) | string | HTMLElement | 'body' |
| showAfter | Retraso antes de mostrar (ms) | number | — |
| hideAfter | Retraso antes de ocultar (ms) | number | — |
| themeVars | Variables de tema a nivel de componente, prevalecen sobre la configuración de ConfigProvider | { [x: string]: string; } | — |
| bgPattern | Patrón de fondo a nivel de componente, prevalece sobre la configuración de ConfigProvider | string | — |
| sx | Props de maquetación rápida sx (se aplican al elemento raíz del disparador) | LhSxProps | — |
Eventos
| Evento | Descripción | Parámetro |
|---|---|---|
| update:visible | Cambio de visibilidad | (value: boolean) |
| show | Se dispara tras mostrar | — |
| hide | Se dispara tras ocultar | — |
Slots
| Slot | Descripción |
|---|---|
| default | Contenido del disparador |
| title | Título personalizado |
| content | Contenido emergente |
Métodos
| Método | Descripción |
|---|---|
| show | Mostrar por programación |
| hide | Ocultar por programación |
| toggle | Alternar por programación |
Accesibilidad
- Con disparo por clic, la capa emergente es
role="dialog"+aria-modal="true"; el disparador llevaaria-haspopup/aria-expanded - Con disparo por clic, el foco se mueve al primer elemento enfocable de la capa emergente; Tab circula dentro de ella y, al cerrar, el foco vuelve al disparador
- Con disparo por suspensión o por foco no se mueve el foco, para no interrumpir la operación con teclado
- Escape cierra y la pulsación fuera de la capa emergente también; si hay título, se relaciona mediante
aria-labelledby - El motor de posicionamiento es una función pura propia (flip/shift anti-desbordamiento), sin dependencias de terceros en tiempo de ejecución
El disparador que sale de la ventana cierra la capa: la capa emergente tiene posición
fixed; cuando el disparador sale de la ventana, la capa deja de seguirlo y queda flotando sobre el contenido de la página con un z-index alto. Por eso, sea cual sea la forma de disparo (incluido el modo controladomanual), al salir el disparador por completo de la ventana el panel se cierra y emiteupdate:visiblepara sincronizar el estado externo.
Definiciones de tipos
Las estructuras de tipos TypeScript que exporta este componente son las siguientes (extraídas automáticamente de packages/components/popover/src/types.ts y del código fuente de @lh-ui/share/core/*; despliega cada entrada para ver la definición completa; la descripción de los atributos está en la tabla de Props de arriba):
LhPopoverProps
/**
* Props del componente Popover
*/
export interface LhPopoverProps {
/** Posición de visualización */
placement?: LhPlacement
/** Forma de disparo */
trigger?: LhPopoverTrigger
/** Título */
title?: string
/** Estado de visualización controlado manualmente (v-model:visible) */
visible?: boolean
/** Si se deshabilita */
disabled?: boolean
/** Si se muestra la flecha */
arrow?: boolean
/** Ancho de la capa emergente */
width?: string | number
/** Clase personalizada de la capa emergente */
popperClass?: string
/** Objetivo del Teleport (selector o elemento, body por defecto) */
teleportTarget?: string | HTMLElement
/** Retraso antes de mostrar (ms) */
showAfter?: number
/** Retraso antes de ocultar (ms) */
hideAfter?: number
/** Variables de tema a nivel de componente, prevalecen sobre la configuración de ConfigProvider */
themeVars?: Record<string, string>
/** Patrón de fondo a nivel de componente, prevalece sobre la configuración de ConfigProvider */
bgPattern?: string
/** Props de maquetación rápida sx (se aplican al elemento raíz del disparador) */
sx?: LhSxProps
}LhPopoverPlacement (de @lh-ui/share/core/shared/popper)
/**
* Motor de posicionamiento Popper (función pura, sin dependencias de framework)
*
* Cálculos geométricos compartidos por las capas emergentes
* (Tooltip / Popover / Popconfirm / Dropdown, etc.):
* colocación de 12 posiciones, volteo al desbordar en el eje principal (flip)
* y sujeción en el eje transversal (shift).
* Todos los parámetros de entrada son literales rect, sin tocar el DOM;
* se puede probar directamente con pruebas unitarias.
*/
/** Posición de la capa emergente (dirección + alineación) */
export type LhPlacement =
| 'top'
| 'top-start'
| 'top-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end'
| 'right'
| 'right-start'
| 'right-end'LhPopoverTrigger (de @lh-ui/share/core/popover (logic.ts))
/** Forma de disparo */
export type LhPopoverTrigger = 'click' | 'hover' | 'focus' | 'manual'