Popover (експеримент)
ЕКСПЕРИМЕНТ
Постачається як підшлях orio-ui/experiments, а не як компонент з автоімпортом. Назви, пропси й поведінка можуть змінитися в будь-якому релізі, навіть у патчі. Використовуйте <orio-popover>, коли потрібна стабільність.
Поповер на основі нативного HTML Popover API — без телепорту, без вимірювання позицій; закриття кліком поза ним і Esc обробляє браузер.
Живе демо
Два екземпляри, щоб перевірити, що кожна панель прив'язується до свого тригера:
Вміст нативного поповера.
Клікніть поза ним або натисніть Esc, щоб закрити.
Прив'язаний до власного тригера.
Без слотів, щоб показати значення за замовчуванням — тригер <orio-button>, панель із поверхнею, поява з прозорістю та зсувом:
flip="initial" зберігає бік, на якому панель відкрилася, навіть коли прокручування виштовхує її з поля зору:
Використання
Експерименти ніколи не імпортуються автоматично. Імпортуйте з підшляху:
<script setup lang="ts">
import { Popover } from "orio-ui/experiments";
</script>
<template>
<Popover>
<template #trigger="triggerProps">
<orio-button v-bind="triggerProps">Open</orio-button>
</template>
<template #body="bodyProps">
<div v-bind="bodyProps">
<p>Your content here</p>
</div>
</template>
</Popover>
</template>Обидва набори слотів треба розгорнути на справжній елемент — саме пара popovertarget / id пов'язує тригер із панеллю.
Демо на цій сторінці використовують <orio-x-popover> — глобальний псевдонім лише для документації того самого компонента.
Пропси
| Проп | Тип | За замовчуванням | Опис |
|---|---|---|---|
id | string | useId() | Id, спільний для тригера й панелі; має бути унікальним |
position | PopoverPosition | 'bottom' | Розміщення як CSS-значення position-area |
gap | number | 0.25 | Відстань між тригером і панеллю, у rem |
flip | PopoverFlip | 'auto' | Наскільки наполегливо панель тримається в межах в'юпорту |
Значення позиції
Одне ключове слово охоплює поперечну вісь, тож bottom центрує панель під тригером:
- По центру:
'top','bottom','left','right' - Вирівняні за краєм:
'bottom span-right'(лівий край панелі збігається з краєм тригера),'bottom span-left','top span-right','top span-left','left span-top','left span-bottom','right span-top','right span-bottom' - Кутові клітинки:
'top left','top right','bottom left','bottom right'
Об'єднання навмисно закрите, а не Property.PositionArea з csstype — той тип приймає будь-який рядок, а це значення підставляється в тег <style>.
Слоти
| Слот | Пропси | Опис |
|---|---|---|
trigger | { popovertarget } | Елемент-тригер |
body | { id, class, popover } | Панель поповера |
Значення за замовчуванням
Без слотів компонент уже рендерить щось придатне до використання:
- Тригер —
<orio-button>, тож одразу відповідає дизайн-системі. - Поверхня — фон панелі, рамка, заокруглення, відступи й тінь з токенів теми.
- Рух — поява з прозорістю й зсувом від тригера через
@starting-styleіtransition-behavior: allow-discrete. Враховуєprefers-reduced-motion.
Усе це генерується всередині :where(), що має нульову специфічність, — один ваш клас перекриває будь-що з цього без !important:
<template #body="bodyProps">
<div v-bind="bodyProps" class="my-panel">…</div>
</template>
<style scoped>
.my-panel {
background: var(--color-accent-soft);
padding: 1.5rem;
--popover-enter-distance: 0; /* kill the slide, keep the fade */
}
</style>Перевертання біля країв
Якщо flip не дорівнює 'off', панель генерує position-try-fallbacks, тож браузер пробує інше розміщення, коли панель вийшла б за в'юпорт: спершу по блоковій осі для top/bottom, по рядковій — для left/right, потім по обох.
Значення flip
| Значення | Перевертається під час відкриття | Перевертається, поки відкрита |
|---|---|---|
'auto' (за замовчуванням) | так | так — перевизначається після зупинки прокручування та зміни розміру |
'initial' | так | ні — бік фіксується, щойно панель відкрилася |
'off' | ні | ні — завжди рендериться з запитаного боку |
'initial' — це поведінка чистого CSS: браузер генерує position-try-fallbacks і застосовує їх під час розкладки. 'auto' додає непомітне перевідкриття, описане нижче.
Навіщо 'auto' потрібен трюк
Браузер обирає запасний варіант лише під час розкладки панелі — тобто коли вона відкривається. Поки панель відкрита, вона стежить за тригером, але обраний бік «липкий»: панель, що відкрилася під тригером, а потім її прокрутили до краю в'юпорту, лишається знизу й виходить за межі. За вимірюваннями в Chrome 151 примусова інвалідація стилів не допомагає; перезапис position-area і перемикання position-try-fallbacks не змінюють обраного боку. Перевизначає його лише нова розкладка в момент показу.
Тож flip="auto" закриває й знову відкриває панель, щоб викликати нову розкладку. Підміна непомітна:
- приховування й показ відбуваються в одній задачі, тож жодного кадру із закритою панеллю не малюється — виміряно нуль прихованих кадрів;
- переходи на час підміни вимикаються, тож анімація відкриття не програється вдруге;
- нативна пара
beforetoggle/toggleпоглинається, тож слухачі на панелі не бачать хибного закриття/відкриття; - фокус і власні зсуви прокручування панелі відновлюються, бо приховування поповера повертає фокус ініціатору, а зміна display скинула б
scrollTop.
Це відбувається після зупинки прокручування (через 100 мс після останньої події scroll) і під час зміни розміру, і лише коли панель справді виходить за в'юпорт — одна перерозкладка на жест, а не на кожен кадр. Перейдіть на flip="initial", якщо хочете, щоб панель зберігала бік, на якому відкрилася.
Порівняння з <orio-popover>
<orio-popover> | <orio-x-popover> | |
|---|---|---|
| Розміщення | вимірюється в JS, автоперевертання | CSS anchor positioning, position-try-fallbacks |
| Переміщення під час прокручування | слухачі у фазі перехоплення | компоновник стежить за якорем, без JS |
| Повторне перевертання під час прокручування | так, перевимірювання на кожну подію | так, через непомітне перевідкриття після зупинки прокручування |
| Накладання | телепорт + z-index: 999999 | нативний top layer |
| Закриття кліком поза | власна реалізація click-outside | браузер |
| Закриття через Esc | ні | браузер |
| Стан відкриття | внутрішній ref + слот-проп toggle | DOM (popovertarget) |
Не реалізовано
position-visibility— панель лишається на місці, коли її тригер прокручують за межі видимості, замість того щоб ховатися разом із ним- Керування фокусом (
popover="auto"— не діалог, тож фокус не утримується) aria-expanded/aria-haspopupу наборі тригера- Перевірка підтримки CSS anchor positioning і
@starting-styleбраузером — де їх немає, панель повертається до нативної позиції по центру й з'являється без анімації