Оверлеи
Поповер
Небольшой блок, всплывающий над интерфейсом по нажатию на кнопку и исчезающий
по клику мимо. Целиком на платформе: popover + popovertarget, без единой
строки скрипта.
Разметка
<button class="inst-btn" type="button" popovertarget="pop-actions">Действия</button>
<div class="inst-popover inst-popover--anchored" id="pop-actions" popover>
<div class="inst-menu" role="menu">
<span class="inst-menu-label">Прогон #4127</span>
<button class="inst-menu-item" type="button" role="menuitem">Перезапустить</button>
<button class="inst-menu-item" type="button" role="menuitem">Скопировать id</button>
</div>
</div>
Контракт
| Что | Обязательно | Почему |
|---|---|---|
popover на блоке |
да, у всплывающего | Без него это плавающая поверхность в потоке: ни верхнего слоя, ни закрытия по Escape |
id на блоке |
да | Кнопка ссылается на него по имени |
popovertarget="id" на кнопке |
да | Связка и неявный якорь одновременно |
type="button" на кнопке |
да | Иначе внутри формы она её отправит |
inst-popover--fill для плашки под полем |
да | Список уже или шире поля читается как чужой элемент рядом, а не как его продолжение |
Содержимое поповера своего класса не имеет: чаще всего это меню, но может быть любая разметка.
Вид отделён от механизма. .inst-popover — это поверхность; поведение
верхнего слоя добавляет атрибут popover. Поэтому тот же вид можно взять для
того, что плавает, но поповером не является: списка результатов комбобокса,
подсказки автодополнения. Без атрибута блок просто виден, показом управляет
приложение.
Доступность
| Клавиатура | Enter/Space на кнопке открывает, Escape закрывает, фокус возвращается на кнопку. Всё нативное, instrument.js ничего не перехватывает |
| Подложки нет | ::backdrop прозрачен: поповер не блокирует страницу и не притворяется модальным |
| Роль содержимого | Роли не выдумываются. Если внутри меню — role="menu" и role="menuitem" ставятся в разметке |
| Поверхность без атрибута | .inst-popover без popover не имеет ни верхнего слоя, ни закрытия по Escape: показ, скрытие и роль — на приложении |
| Уменьшенное движение | Переход схлопывается до 0.01ms, а не выключается: машины состояний, слушающие transitionend, продолжают работать |
| Печать | Поповер на листе не печатается: он по определению временный |
Варианты
Разметка
<button class="inst-btn" type="button" popovertarget="pop-anchored">Под кнопкой</button>
<div class="inst-popover inst-popover--anchored" id="pop-anchored" popover>
Встаёт под кнопкой и переворачивается, если снизу не помещается.
</div>
<button class="inst-btn" type="button" popovertarget="pop-centered">По центру</button>
<div class="inst-popover" id="pop-centered" popover>
Без модификатора поповер стоит по центру экрана.
</div>
| Модификатор | Где встаёт | Как меряется ширина |
|---|---|---|
| без модификатора | По центру экрана | max-content в пределах 11–22rem |
inst-popover--anchored |
Под вызвавшей кнопкой | То же |
inst-popover--fill |
Там, куда его поставили | Во всю ширину родителя |
Якорь у --anchored — неявный: кнопка с popovertarget уже является
якорем для своего поповера, поэтому имена якорей заводить не нужно.
Правило спрятано под @supports (position-area: block-end). Где
position-area не поддержан, поповер встаёт по центру экрана: деградирует, а
не ломается. Раскладка задана логическими значениями, поэтому в RTL
зеркалится сама. Если снизу или сбоку не помещается,
position-try-fallbacks переворачивает поповер по нужной оси, а не выпускает
его за край экрана.
inst-popover--fill нужен там, где плашка обязана совпасть по ширине с тем,
под чем стоит: выпадающий список под полем. Список у́же или ши́ре поля читается
как чужой элемент, случайно оказавшийся рядом.
Размеры
| Свойство | Значение |
|---|---|
| минимальная ширина | 11rem |
| максимальная ширина | min(22rem, 100vw - var(--space-8)) — на узком экране поповер не вылезет за край |
| ширина по содержимому | max-content в этих пределах |
с --fill |
100% родителя, без потолка |
Тень здесь при деле: --shadow-popover означает «плавает сверху и сейчас
исчезнет» — то есть описывает поповер целиком. Второй и последний носитель
тени в библиотеке — модалка.
Поведение
Поповер не имеет ни одной строки поведения в библиотеке, потому что всё это уже есть в браузере.
| Что | Кто делает |
|---|---|
Верхний слой — поповер не срежет ни overflow, ни z-index соседа |
Платформа |
Закрытие по Escape |
Платформа |
| Закрытие по клику мимо (лёгкое закрытие) | Платформа |
| Возврат фокуса на вызвавшую кнопку | Платформа |
| Связка кнопки и блока | popovertarget="id" |
| Появление и исчезновение | Библиотека: переход по opacity и translate |
Переход доигрывает и на закрытии, потому что в правиле объявлены
display и overlay с allow-discrete. Без них поповер исчезал бы мгновенно,
не дождавшись собственной анимации.
API
| Имя | Что делает |
|---|---|
| класс | |
inst-popover | Плавающая поверхность: рамка, радиус, фон, тень, внутренний отступ |
| модификатор | |
inst-popover--anchored | Привязка к вызвавшей кнопке. Под @supports |
inst-popover--fill | Ширина во всю ширину родителя вместо max-content. Для выпадающего списка под полем |
| Имя | Значение | Что делает |
|---|---|---|
| атрибут | ||
popover | auto · manual | Добавляет к поверхности верхний слой, закрытие по Escape и переход появления |
popovertarget | — | На кнопке. Связка и неявный якорь одновременно |
| Имя | Значение | Откуда |
|---|---|---|
| токен | ||
--surface-overlay | light-dark(var(--n-0), var(--n-11)) | база +1: тема |
--shadow-popover | 0 4px 12px -2px var(--shadow-color-far), 0 2px 4px -2px var(--shadow-color-near) | одно на все ячейки |
--border | light-dark(oklch(0 0 0 / 0.12), oklch(1 0 0 / 0.11)) | одно на все ячейки |
--hairline | 1px | база +1: экран |
--radius-md | 8px | база +4: масштаб |
--space-1 | 2px | одно на все ячейки |
--space-2 | 4px | одно на все ячейки |
--space-8 | 32px | одно на все ячейки |
--dur-2 | 140ms | одно на все ячейки |
--ease-out | cubic-bezier(0.22, 0.61, 0.36, 1) | одно на все ячейки |
Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument