oscript-ui
oscript-ui

Движение

Движение в ките — кривые и длительности Material Design 3, а поведение — как у MUI. Почти всё делает CSS. Скрипт кита добавляет четыре вещи, без которых страница всё равно работает целиком.

Кривые#

Кривая задаёт характер движения. Стандартная — для мелких переходов внутри экрана, выразительная — для того, что входит на экран или покидает его.

  • --md-sys-motion-easing-standard
  • --md-sys-motion-easing-standard-decelerate
  • --md-sys-motion-easing-standard-accelerate
  • --md-sys-motion-easing-emphasized
  • --md-sys-motion-easing-emphasized-decelerate
  • --md-sys-motion-easing-emphasized-accelerate
  • --md-sys-motion-easing-legacy
  • --md-sys-motion-easing-linear

Наведите указатель на строку или встаньте на неё клавишей Tab — точка проедет дорожку с этой кривой за одинаковое время.

Токен Значение Где в ките
standard cubic-bezier(.2, 0, 0, 1) Слой состояния, подпись поля, цвет шапки, рябь
standard-decelerate cubic-bezier(0, 0, 0, 1) —
standard-accelerate cubic-bezier(.3, 0, 1, 1) —
emphasized cubic-bezier(.2, 0, 0, 1) Раскрывашка, индикатор вкладок, поворот шеврона
emphasized-decelerate cubic-bezier(.05, .7, .1, 1) Появление: меню, шторка, лист, диалог, тост, ползунок тумблера
emphasized-accelerate cubic-bezier(.3, 0, .8, .15) —
legacy cubic-bezier(.4, 0, .2, 1) —
linear cubic-bezier(0, 0, 1, 1) —

Токен кривой — --md-sys-motion-easing-<кривая>. Выразительная кривая Material составная, одной cubic-bezier её не записать, поэтому emphasized в CSS совпадает со стандартной — так же её записывает и сама Material.

Длительности#

Шкала длительностей Material 3 — четыре ступени по четыре шага.

Ступень 1 2 3 4
short 50 100 150 200
medium 250 300 350 400
long 450 500 550 600
extra-long 700 800 900 1000

Токен длительности — --md-sys-motion-duration-<ступень><шаг>, значения в миллисекундах. Чем больше путь и площадь элемента, тем длиннее его движение: слой наведения — 150, шторка — 300.

Прежние имена остались псевдонимами:

Прежнее имя Значение Для чего
--motion-fast short3, 150 Наведение, фокус, переключатель
--motion-medium medium1, 250 Раскрытие секции, поворот шеврона
--motion-slow medium2, 300 Смена раздела настроек
--motion-ease standard Кривая по умолчанию
--motion-pulse 1600 Оборот индикатора занятой кнопки

Что делает CSS#

Всё движение, которому хватает состояния разметки, лист кита делает сам — без единой строки скрипта.

Что Как
Слой состояния Фон за short3, кнопка на наведении приподнимается тенью за short4
Подпись поля Уменьшается до 4/5 (12 пикселей) и поднимается к верхнему краю за short4; путь короче прежнего — поле 44 пикселя, а не 56; у поля со СкрытаяМетка метки на экране нет — нечему и плыть, пример виден всегда
Рамка поля в фокусе Из outline (в покое, не меньше 3:1 к заливке поля и карточке) становится 2 пикселя цветом primary за short3; содержимое поля не сдвигается — второй пиксель рисует кольцо внутрь
Тумблер Ползунок едет за medium1; поповер справки «?» рядом с ним проявляется, как у поля
Выпадающее меню Растёт из угла у кнопки, как Grow у MUI: проявление за short4, размер за medium1; кольцо-контур растёт вместе с ним
Шторка единого меню Выезжает из-за левого края за medium2; заголовки групп и рубрики в ней — одним стилем, title-small обычным регистром
Лист меню на телефоне Выезжает снизу за medium2
Окно на телефоне Базовое окно проявляется по центру, как на широком экране; листом во весь экран встаёт только окно ВоВесьЭкран и окно с шагами — без своего движения, тем же проявлением
Диалог Проявляется и чуть вырастает, скрим набирает плотность вместе с ним — medium2; растёт из центра экрана, в каком бы контейнере его ни напечатали
Раскрывашка Высота содержимого едет от нуля до своей за medium2, как аккордеон MUI
Тост Въезжает снизу на 8 пикселей и проявляется за medium2
Шапка Меняет цвет и даёт тень за medium1, когда страница прокручена; линия outline-variant под ней стоит всегда и не анимируется
Сегменты Заливка выбранного за short3, галка въезжает за short4; набор полей отмеченного сегмента показывает :has(:checked); на телефоне группа — во всю ширину равными колонками, подпись переносится, а не обрезается; источник значения (Источник) стоит справа на строке подписи группы и не движется
Подсказка Поповер проявляется за short4; открывает и закрывает его движок (popover)
Сетка Не движется: число колонок меняют точки перелома листа, ряд перестраивается сразу, без анимации и без скрипта; у сеток в 3 и 4 колонки число — потолок; у сетки ПолныеРяды на смене точки перелома хвост неполного ряда сразу пропадает или возвращается — лист, без скрипта
Шапка карточки объекта Не движется: имя сжимается последним и не уже девяти знаков, в узкой карточке плашки сразу встают строкой под имя — по ширине самой карточки (@container), а не окна
Блок команды Не движется: длинная строка переносится у края блока, кнопка «Копировать» держит ширину; на телефоне она сразу встаёт под код, если рядом не помещается
Окно без скрипта Окно, напечатанное открытым (?окно=…, отбитая форма), без скрипта не модальное, но стоит, как модальное: закреплено по центру экрана выше шапки и секций, подложка гасит страницу (тень со спредом во весь экран) и ловит щелчок мимо окна, страница под ним прокручивается; появление то же — проявление с небольшим ростом, и доходит до конца без :modal
Секция со списком карточек Не движется: на телефоне (до 600 px) карточка или секция с сеткой карточек объектов становится плоской — без кольца, заливки и боковых полей, — а карточки объектов в ней остаются карточками во всю ширину экрана без полей страницы (на 390 — 358 px), без черты между ними; без :has() — прежний вид; сетка простых карточек (Кит.Карточка в ячейках сетки — обзор групп) — так же (дев-сборка 8)
Панель сохранения Тень уровня 2 появляется за medium1, только когда панель прилипла к низу экрана и под ней прокручивается форма (признак data-stuck ставит скрипт); в конце короткой формы панель без тени; без скрипта признака нет, и панель всегда обведена кольцом outline-variant — прилипшая отделена от полей под ней краем (дев-сборка 10); слой панели выше плавающих подписей полей; несохранённые правки меняют её заливку на secondary-container
Заголовок-ссылка карточки Не движется: у карточки и секции с Адресом и Иконкой значок и имя внутри ссылки стоят с зазором 8 px, как у заголовка без ссылки
Шеврон панели Раскрывашка-панель несёт шеврон справа: вниз у свёрнутой, вверх у открытой — поворот за short4; у записи реестра его нет
Сегменты в окне Не движутся: в окне на любой ширине — как на телефоне (во всю ширину, колонка не уже слова, подпись переносится между словами); пять сегментов в окне, а на телефоне четыре, встают столбиком — сегмент на строку
Длинное слово в окне Не движется: адрес, ключ или хеш без пробелов в заголовке, лиде и теле окна и в подписи эха подтверждения переносится по знакам — окно не шире экрана, слово не обрезано его краем; подпись эха стоит над полем обычной строкой
Подсветка кода README Не движется: в светлой теме содержимое блока кода, раскрашенного shiki в цвета github-light, притемнено фильтром до 0.7 — самый бледный токен на белом 6.3:1 вместо 3.5:1, оттенки те же; тёмная тема не меняется
Подвал страницы Не движется: тело страницы — столбик во всю высоту окна, основное забирает остаток, и на короткой странице подвал стоит у низа окна, а не посреди него; длинная страница прокручивается, как прежде
Вложенная поверхность Не движется: карточка, карточка объекта, плитка и свёртка внутри карточки или секции — с одним кольцом, без своей тени (в светлой теме тени больше не лежат одна в другой); на наведении — тень уровня 1, на ступень ниже, чем у карточки на странице
Карточка в карточке Не движется: на телефоне (до 600 px) вложенная карточка, секция и таблица сразу становятся частью родителя — без тени, кольца и боковых полей, с чертой между соседями; поля страницы и карточки — 16 px
Ссылка в тексте Не движется: подчёркнута всегда, а не только под указателем; ссылка навигации, кнопки, ячейки таблицы и имя карточки подчёркиваются только под указателем, как прежде
Строка поиска со значком Не движется: значок «поиск» стоит внутри рамки поля справа на любой ширине, на телефоне ничего не переносится; на нажатии — рябь и слой состояния кнопки-значка. Поиск дерева галок (дев-сборка 8) — той же строкой
Кнопка без отправки Не движется: кнопка БезОтправки в форме — type="button", на нажатии та же рябь и слой состояния, что у любой кнопки, а форма не уходит
Плашка с крестиком формой Не движется: плашка со словом Снять печатается внутри своей тихой формы (tag-form) — строчным блоком, и в ряду плашек форма встаёт, как сама плашка; крестик — кнопка отправки, ответ ложится на страницу тихой отправкой
Тёмная тема помягче Не движется: поверхности тёмной темы на ~4 тона светлее базовой схемы Material — фон L* 10 (#1d1a21 при базовом акценте) вместо 6, контейнеры 6/13/16/20/24, кольцо карточек поднято вслед за фоном; текст на любой поверхности — не меньше 4.5:1, карточка на 6 тонов светлее фона; светлая тема прежняя
Значок бренда Не движется: логотип бренда (бренд:<ключ>, Simple Icons) — цветом бренда; тёмный логотип (GitHub, Keycloak, Mattermost) в тёмной теме — цветом текста: и инлайн-значок (лист), и файл для <img> (его стиль по color-scheme страницы)
Справка у флажка и тумблера Не движется: значок «?» флажка и тумблера стоит в строке подписи и остаётся позиционированным, поэтому его зона касания 48 px — вокруг значка, а не во всю строку: щелчок по флажку, дорожке и подписи переключает, по «?» — открывает справку (было с дев-сборки 5: зона накрывала всю строку, и тумблер со справкой не переключался)
Рубрика шторки Не движется: рубрика внутри группы разделов в шторке — ступенью ниже имени группы: label-medium приглушённо, левым краем — с текстом пунктов; имя группы — прежний title-small (дев-сборка 9)
Пустое состояние с двумя путями Не движется: второй путь из пустоты (Действие дважды или Действия(…)) — рядом с первым одной группой по центру, второй без своего вида — вторичной кнопкой; на телефоне (до 600 px) — столбиком во всю ширину; у корня пустота держит отбивку снизу --space-4, как карточка, — карточка под ней не прилипает (дев-сборка 9)
Переключатель темы пилюлей Бегунок пилюли темы едет за short4 (200 мс) стандартной кривой, когда страница меняет тему: положение и значок (солнце в светлой, луна в тёмной) лист берёт у html[data-theme], поэтому со скриптом бегунок едет вместе с перекраской, а aria-checked скрипт правит сразу; без скрипта — POST и страница возвращается с бегунком на новом месте; при «уменьшить движение» — без перехода (дев-сборка 9)
Пресеты темы Не движутся: пресет (classic — кит 0.8, material — по умолчанию, oscript — сайт oscript.io) меняет затравку, нейтраль и тоны поверхностей обеих тем; рамка образца со словом Пресет(Ключ, Поле) перекрашивается на месте при выборе пресета (зеркало признаком), её переключатель «Светлая / Тёмная» показывает пресет во второй теме (дев-сборка 9)
Выбор значка Не движется: сетка значков (глифы и бренды) — радиокнопки, выбранная ячейка — заливка secondary-container и кольцо primary, наведение — слой состояния за short3; со скриптом строка поиска отбирает ячейки на месте (пустой раздел прячется, счёт найденного — в живую область), Enter выбирает первый найденный; зеркало поля пишет в образец сам значок (дев-сборка 9)
Страница входа Не движется: узкая страница — колонка 360 px по центру (на телефоне во всю ширину с полями), шапка колонки — знак 48 px, заголовок headline-small и подпись по центру; каркас вокруг неё печатает только знак и пилюлю темы; разделитель «или» — линии outline-variant по бокам текста; рамочная строка — текст по центру в рамке; ссылка «Забыли пароль?» — над полем справа (дев-сборка 9)
Шапка узкой страницы Не движется: у страницы с узкой колонкой (вход, ворота) шапка на телефоне — одной строкой, знак слева, пилюля темы справа (прежняя шапка без единого меню на узком встаёт колонкой); длинное имя знака сжимается многоточием (дев-сборка 10)
Сноска Не движется: примечание вида «сноска» — body-small цветом пояснения, без рамки и своих маргинов, ссылка того же кегля; в узкой колонке по центру (дев-сборка 10)
Служебный значок ключа Не движется: key каталога провайдеров — глиф Material, а не бренд: в перечне Иконка.ИменаБрендов() и в разделе «Бренды» выбора значка его нет; файл key.svg и имя бренд:key печатаются, как прежде (дев-сборка 10)
Служебные глифы Не движутся: в выборе значка стрелок, меню, крестика, солнца и луны нет — раздел «служебные» печатается, только если назван; слово Исключить убирает значки, которых домен не принимает; сохранённое значение остаётся ячейкой (дев-сборка 10)
Окно сброса слота картинки Слово АдресЗакрытия у слота — как у окна и подтверждения: крестик и «Отмена» окна «Вернуть стандартную?» — ссылки на страницу без окна, без скрипта переход, со скриптом закрытие на месте той же анимацией (дев-сборка 10)
Лид ворот Не движется: подпись шапки узкой страницы — строка или узел; абзац длиннее строки переносится по словам по центру, строки выровнены по длине, не шире колонки; длинное слово (адрес, логин) переносится внутри себя (дев-сборка 10)
Отказ у сегментов Не движется: слово Ошибка у сегментов — строка field__error под пилюлей, рамка и черты пилюли цветом ошибки, группа aria-invalid; со скриптом после тихой отправки фокус встаёт на отмеченный сегмент отбитой группы (дев-сборка 10)
Значок у сегмента Не движется: значок сегмента (слово Иконка, глиф или бренд) стоит перед подписью 18 px (у малых — 16), не сжимается; галка выбранного въезжает перед ним; на телефоне подпись переносится, значок остаётся (дев-сборка 10)
Пустая ячейка со своим значком Не движется: Пусто(Подпись, Иконка) у выбора значка — приглушённый значок вместо перечёркнутого круга (выбранный — в полную силу) и видимая подпись справа от ячейки; поиск её не прячет (дев-сборка 10)
Проза Не движется: Кит.Проза(Html) — чужой документ (README), санитизированный китом при печати, в оформлении вида «проза» (блоки кода, таблицы, картинки) (дев-сборка 10)
Три режима ветки Не движутся: режим ветки дерева галок — массовая отметка; «все» и «никакие» отмечают или снимают детей и запирают их на месте, «выбранные» отпирают — галки меняются сразу, без перехода; сервер печатает детей «все» и «никакие» запертыми их состоянием (дев-сборка 10)
Страница ветки Не движется: страница ветки с режимом (Ветка(Ключ, Страница, Всего, Режим)) печатает строку ветки с сегментами режима, запертых детей «все»/«никакие», ряд отмеченного и правок — без скрипта она выглядит и правится как ветка дерева; скрипт берёт из неё только детей (дев-сборка 10)
Сегменты режима строкой Не движутся: сегменты режима у строки дерева галок — малой пилюлей своей ширины одной строкой на любой ширине, и в окне (правила окна и телефона — столбиком и во всю ширину — для выбора в форме, не для строки дерева); подписи не переносятся, строка ветки переносит пилюлю под подпись целиком (дев-сборка 10)
Скрипт-модуль Не движется: Кит.Скрипт().Модуль(Текст) — встроенный <script type="module"> в теле страницы (подсветка кода README); модули отложены движком сами, порядок — порядок узлов (дев-сборка 10)
Закрытие окна переходом Окно со словом Переходом() не закрывается на месте: крестик, «Закрыть», «Отмена» — переход на адрес закрытия, Esc со скриптом — тоже (data-close-to); анимации закрытия нет — страница сменяется (дев-сборка 10)
Предел ряда отмеченного Не движется: ряд отмеченного вне показанного со словом ПределОтмеченных печатает первые плашки и хвост «ещё N» — текстом или ссылкой на продолжение (дев-сборка 10)
Ворота вошедшего Узкая страница вошедшему печатает его учётку справа от пилюли темы; меню открывается тем же движением, что единое меню (на широком — выпадающим, на телефоне — шторкой), и в нём нет дорог (дев-сборка 10)
Цели касания Не движутся: на касании (pointer: coarse) область нажатия кнопки-значка, крестика плашки, кружка акцента и «?» — 48 × 48 при прежнем видимом размере; слой состояния и рябь — в видимых границах
Шапка врезки Маркер раскрытия впереди — шеврон, как у дерева галок и свёрнутой секции (дев-сборка 8; был треугольник), поворачивается вниз у открытой врезки и подробности (короткая стандартная кривая); у простой раскрывашки — тот же шеврон вместо системного треугольника браузера; дети шапки — значок, подпись, плашки — стоят в строку с зазором 8 px и переносятся вместе
Панель сегмента Не движется: панель вне набора (ПанельСегмента) появляется сразу, как только в форме отмечен её сегмент, — лист по месту группы и номеру сегмента, без скрипта; без :has() видны все панели
Подпись сегмента на телефоне Не движется: подпись переносится только между словами; колонка сегмента не уже самого длинного слова своей подписи, остаток ширины — поровну, и однословная подпись делает свою колонку шире соседей, а не рвётся посреди слова
Колонка чисел Не движется: число и заголовок над ним — по правому краю, цифры одной ширины; в таблице карточками на телефоне число сразу встаёт слева под подписью — лист, без скрипта
Прочерк пустой ячейки Не движется: печатает сервер — прочерк таблицы, у колонки свой поверх него (Колонка(…).Пустое(…)), у колонки действий его можно снять

Меню, шторка и диалог открываются движком браузера по атрибутам popover и <dialog>. Их выход тоже анимирован: display и верхний слой снимаются дискретно, после движения. Движок, который так не умеет, открывает и закрывает их мгновенно — поведение то же, только без анимации.

Раскрывашка анимируется там, где движок знает interpolate-size и ::details-content. Остальные открывают её сразу, как раньше.

Подпись поля плавает, рамка в фокусе становится primary

HTML = Набор.Поле().Отрисовать(Новый Структура("Имя, Метка, Подсказка", "название", "Название проекта", "Видно всем участникам."));

Ползунок тумблера

HTML = Набор.Тумблер().Отрисовать( Новый Структура("Имя, Метка, Включено, ПодписьВкл, ПодписьВыкл", "видимость", "Видимость проекта", Ложь, "публичный", "приватный"));

Раскрывашка — высота едет до своей

Подробности сборки

Собрано за 42 секунды, предупреждений нет.

HTML = Кит.Раскрывашка(Кит.Примечание("Собрано за 42 секунды, предупреждений нет.")) .Шапка("Подробности сборки").Рендер();

Меню растёт из угла у кнопки

Пункты = Новый Массив(); Пункты.Добавить(Новый Структура("Подпись, Адрес, Иконка", "Настройки", "/настройки", "шестерёнка")); Пункты.Добавить(Новый Структура("Подпись, Адрес, Иконка", "История", "/история", "история")); HTML = Набор.Меню().Отрисовать(Новый Структура("Подпись, Пункты", "Действия проекта", Пункты));

Тост въезжает снизу

Проект сохранён.
HTML = Набор.Тост().Отрисовать(Новый Структура("Текст, Вид", "Проект сохранён.", "ok"));

Что делает скрипт#

Скрипт кита oscript-ui.js делает то, чего одним CSS не сделать. Каркас страницы подключает его сам, перед скриптом инсталляции. Глобальных имён скрипт не заводит и разметку не печатает — только атрибуты, рябь и текст отказа под полем (тот же field__error, что печатает сервер); новую плашку списка он клонирует из шаблона, который напечатал сервер.

  1. Рябь. Круг расходится от точки нажатия и гаснет, как в MUI. С клавиатуры, на Enter и пробеле, рябь идёт из середины. Даже от короткого касания она успевает показаться — живёт не меньше 225 миллисекунд. Рябь есть у кнопки, чипа, вкладки, пункта дерева, страницы пагинации, хода в шапке, плашки-ссылки, плитки-ссылки и кнопок копирования.
  2. Пустота поля. Атрибут data-empty у пустого текстового поля опускает подпись на место значения. Скрипт следит за вводом, сбросом формы и автозаполнением браузера. У выбора, даты, цвета и файла подпись всегда наверху: они всегда что-то показывают.
  3. Шапка при прокрутке. Атрибут data-scrolled у шапки, когда страница сдвинута вниз. По нему шапка берёт цвет контейнера и тень второго уровня.
  4. Индикатор вкладок. При переходе по вкладке того же ряда скрипт даёт активной вкладке имя перехода (data-vt), и движок везёт индикатор со старой страницы на новую (View Transitions между документами). Сама страница меняется сразу — двигается только индикатор, как у Tabs в MUI.
  5. Закрываемый тост. Крестик data-close гасит тост и убирает его со страницы.
  6. Тихая отправка. Форма с БезПерезагрузки() уходит в фоне, и ответ сервера ложится на страницу на место, без перезагрузки. Подробно — в разделе Формы деревом.
  7. Тема сразу. Переключатель темы (форма с data-theme-toggle) перекрашивает страницу, не дожидаясь сервера: обе палитры уже лежат в голове страницы, и смена темы — один атрибут. Где движок умеет View Transitions, старая тема растворяется в новой. Сервер узнаёт о смене тем же запросом, что и без скрипта, только в фоне, и его ответ страницу не трогает: куку ставит ответ, а из него скрипт берёт только сам переключатель — значок и подсказку. Куда бы сервер ни перенаправил ответ, хоть на главную, человек остаётся на своей странице, с прокруткой и введённым; сбой сети тоже ничего не меняет.
  8. Отправка по изменению и несохранённое. Тумблер с СохранятьСразу отправляет форму сам; запасную кнопку отправки его формы, где других полей нет, лист при работающем скрипте прячет, а у формы без своей кнопки её печатает сам кит (toggle__save, словарь toggle.save) и при скрипте тоже прячет (@media (scripting: enabled)) — в любом месте страницы, а не только в записи реестра. Панель сохранения data-savebar подсвечивается на первом же вводе в её форму, и подсветку снимает ответ сервера после сохранения. Форму, в которой правили, пока шёл запрос, скрипт помечает data-edited, и ответ её введённое не сбрасывает. Панель сохранения, прилипшую к низу экрана, скрипт отмечает признаком data-stuck (наблюдатель пересечений; лист держит панель на пиксель ниже края) — по нему лист даёт ей тень.
  9. Окна. Команды кнопки show-modal и close (command и commandfor) исполняет движок; движку, который их ещё не знает, их доучивает скрипт. Ссылка-кнопка с data-opens (кнопка Открывает(Окно), когда у окна или у кнопки есть Адрес) не уводит со страницы: скрипт открывает окно с этим идентификатором на месте, без запроса; без скрипта она ведёт на страницу, где сервер печатает окно открытым. Окна и подтверждения из записей реестра сервер печатает после списка, а не в теле записи: скрипт открывает их на виду, даже когда запись свёрнута. Окно, которое сервер напечатал открытым, скрипт поднимает модальным. Форма в окне после удачной тихой отправки закрывает окно и ставит фокус на элемент с data-focus (слово Фокус()), после отказа (4xx) остаётся в окне с отказами у полей; ответ 2xx, который сам печатает другое окно открытым, закрывает окно формы и поднимает окно ответа модальным, а открытое окно, которого в ответе нет, закрывает — раздел «В окне» страницы Формы деревом. Открывая окно, скрипт (а с командой show-modal — движок по его признаку autofocus) ставит фокус на первое поле тела — поле с отказом раньше, — а окну без полей на заголовок (tabindex="-1", без кольца), а не на крестик в шапке (дев-сборка 8). Ссылка с data-closes (крестик и «Отмена» окна со словом АдресЗакрытия) закрывает окно на месте, без перехода и перезагрузки; без скрипта — ведёт на страницу без окна.
  10. Окно по адресу. Кнопка с data-window-link (Кит.ОкноПоАдресу) не уводит на страницу с окном: скрипт берёт её запросом в фоне, кладёт окно в одну на страницу оболочку data-window-shell и открывает. Не вышло — показывает тост data-window-error, напечатанный сервером рядом с оболочкой. Подробно — Окно.
  11. Подтверждение. Форма с data-confirm (СПодтверждением) не уходит сразу: скрипт открывает окно подтверждения, а уходит форма его кнопкой. Значение признака — идентификатор окна, и обслуживает его скрипт кита: свой window.confirm на data-confirm приложение не вешает. Эхо-поле держит эту кнопку недоступной (disabled), пока набранное не совпало с образцом. Форма из набора записи реестра печатается дважды — пунктом меню ⚙ и кнопкой внизу, — но id несёт только пункт меню: копия внизу без id тоже открывает подтверждение своим data-confirm, а кнопка окна шлёт форму меню.
  12. Шаги. У шагов видна одна панель, над ней полоса номеров, под ней «Назад / Дальше». «Дальше» и Enter в поле пускают на следующий шаг, только когда поля текущего годны: первому негодному браузер показывает свой отказ (reportValidity). Признаки: data-steps (корень, номер шага, с которого начать), data-steps-bar, data-steps-go, data-steps-panel, data-steps-back, data-steps-next, data-steps-submit печатает сервер; data-steps-live у корня и data-steps-done у пройденного шага ставит скрипт. Без скрипта шаги — панели подряд, и форма отправляется целиком. Единственная видимая панель шагов со скриптом — без рамки и своей заливки: содержимое шага стоит прямо под полосой номеров, и в окне нет поверхности в поверхности (дев-сборка 8).
  13. Сегменты и подсказка (0.12). Своего раздела в скрипте у них нет. Сегменты со словом СохранятьСразу печатают у каждой радиокнопки data-autosubmit — тот же признак, что у тумблера, его исполняет пункт 8. Признак data-segment (значение сегмента) у панели набора полей печатает сервер для своего CSS и скрипта страницы; наборы переключает лист. Панель вне набора (ПанельСегмента) и её группа несут data-segments-slot (место группы в форме), панель — ещё data-segments-number; её тоже переключает лист. Подсказка открывается движком по popovertarget.
  14. Поле-список. У списка скрипт показывает плашки, напечатанные сервером, отдаёт имя поля скрытому полю data-chips-value и пишет в него строку через запятую при каждой правке. Enter, запятая и уход из строки делают из набранного плашку — её скрипт клонирует из шаблона data-chips-chip, напечатанного сервером; крестик data-chips-remove и Backspace в пустой строке плашку снимают. Признаки: data-chips, data-chips-value, data-chips-chip, data-chips-remove печатает сервер; data-chips-live у корня ставит скрипт. Без скрипта это обычное текстовое поле со строкой.
  15. Поле цвета. У поля цвета скрипт показывает ряд образцов data-swatches-row и держит его в согласии с полем кода: кружок пишет свой код, набранный код отмечает свой кружок, палитра data-swatches-own — «свой»; имя выбранного пишет в data-swatches-name. Корень — data-swatches. Палитра кита (ОбразцыТемы) — те же кружки, своего поведения у неё нет. Без скрипта ряд спрятан, и код набирают руками.
  16. Проверка до отправки. Поле с текстами отказов (data-error-required, -type, -number, -pattern, -minlength, -maxlength, -min, -max — их печатает поле из слов Длина, Шаблон, ПроверкаДоОтправки) не показывает пузырь браузера: на попытке отправить и на уходе из поля скрипт пишет текст отказа (уход в кнопку отправки своей формы отказ не печатает — его напечатает проверка отправки на том же щелчке, и строка отказа не сдвигает кнопку из-под отпускаемой мыши) в его field__error, ставит aria-invalid и ссылку из aria-describedby, фокус — на первое негодное поле; исправленное поле снимает отказ на вводе. Поле с data-check (ПроверкаНаСервере) спрашивает адрес, свободно ли значение: 200 — свободно, 409 с текстом — занято.
  17. Тост из ответа. Новые тосты из ответа тихой отправки скрипт поднимает вниз экрана и показывает по одному, каждый 5 секунд (data-toast-live: wait, show, gone); пока на тосте мышь или фокус, время стоит. Тост с ролью alert остаётся на своём месте — подробно на странице Тост.
  18. Слот картинки. На файловом поле слота картинки признак data-autosubmit: выбор файла отправляет форму слота сам, тихой отправкой (multipart), а выбранная картинка сразу встаёт в предпросмотр из памяти браузера. Отказ (4xx) ложится на страницу, как любой ответ тихой отправки: под слотом встаёт напечатанный сервером отказ (Ошибка), в рамке — прежняя картинка. Меню ⚙ «Вернуть стандартную» отправляет форму сброса с data-confirm — через подтверждение. Закрытое окно сброса возвращает фокус на кнопку ⚙.
  19. Зеркало и образец. Узел с data-mirror="имя" (слово Зеркало) показывает текущее значение поля с этим именем при каждом вводе; поле цвета пишет свойство CSS --mirror-<имя>; узел с data-mirrors="имя …" (слово ЗеркалоПризнаком) получает значение поля в признак data-mirror-<имя>, вид по нему задаёт лист приложения; свойство --mirror-accent в образце лист сразу превращает в акцент узла — основная кнопка, плашка и ссылка внутри перекрашиваются без своего движения. Кнопки data-preview-theme образца меняют data-theme его рамки (aria-pressed), а палитру для рамки скрипт собирает из блока токенов страницы и подключает листом без разметки (adoptedStyleSheets).
  20. Таблица карточками и свойства (0.13). Своего поведения у них нет. Запись-карточку раскладывает лист: заглавная ячейка cell-title — первой строкой, ячейка меню cell-menu — в правом верхнем углу. Секрет свойства открывает лист по галке «Показать». Кнопку копирования свойства с data-copy — тот же признак, что у блока команды и поля из конфига, — оживляет скрипт инсталляции. Скрипт кита даёт этой кнопке и кнопке «Показать» только рябь.
  21. Страницы и отбор без перезагрузки. Ссылка с data-quiet — шаг пагинации и ссылка сортировки колонки со словом БезПерезагрузки — не уводит со страницы: скрипт берёт её адрес запросом в фоне и кладёт ответ на место тем же морфингом, что ответ тихой формы, прокрутка и фокус остаются, адрес уходит в историю вкладки (pushState). Пока переход в пути, навигация или таблица помечены aria-busy. Тихая GET-форма — строка поиска и форма отбора — ещё и уходит сама по изменению поля: через 0,4 с после последней буквы, выбор — почти сразу; тот же отбор дважды не уходит, и форма после ответа не сбрасывается. «Назад» и «Вперёд» по записям истории, которые пометил скрипт, тоже берутся тихо и возвращают прокрутку и значения полей отбора. Щелчок с Ctrl/Cmd, Shift или средней кнопкой — по-прежнему браузеру. Без скрипта это обычные ссылки и обычная GET-форма.
  22. Дерево галок (0.13). Щелчок по галке ветки (data-checktree-box) дерева галок раздаёт её состояние детям, а ветки выше пересчитывают «частично» по детям (indeterminate; напечатанное сервером data-checktree-state скрипт не переписывает). «Показать ещё» (data-checktree-more) и раскрытие пустой ветки берут страницу детей запросом в фоне и дописывают её в список data-checktree-page с тем же ключом. «Найти» в поиске data-checktree-search берёт ответ data-checktree-found с адреса поиска и кладёт его на место веток. У дерева data-checktree-edits-only правки галок и режимов (data-checktree-modes) копятся плашками в data-checktree-edits — плашку скрипт клонирует из шаблона data-checktree-chip с текстами data-checktree-text-on, -off, -mode, крестик data-checktree-undo снимает правку, — и форма отдаёт только их: правки=1, включить, снять, режим. Обёртка — data-checktree-set. Режим ветки скрипт узнаёт по значению радиокнопки (дев-сборка 10 — три режима с массовыми галками): «все» отмечает всех детей и запирает их (disabled с признаком data-checktree-locked, список — inert), «никакие» снимает и запирает, «выбранные» отпирает и начинает с того, что было видно — после «все» все отмечены, после «никакие» все сняты (массовая отметка — основа: правки детей считаются против неё, с формой уходит основа=<ключ>:все|никакие); дети, приехавшие страницей позже, встают по режиму, отмеченному сейчас; «кроме» (дев-сборка 8) отпирает детей отмеченными. Скрытое поле показанного data-checktree-shown (слово ПравкиБезСкрипта) при отправке без правок скрипт пересчитывает по всем галкам обёртки. Крестик плашки отмеченного вне показанного (ряд data-checktree-picked, плашка data-checktree-pick) у дерева с правками становится правкой того же узла в общем ряду, и ряд правок держит плашку зачёркнутой, пока правка стоит; без скрипта он — галка формы, и зачёркивает плашку лист. Строку сводки правок data-checktree-changes (слово СводкаПравок) скрипт пишет по плашкам ряда: «Изменений: 3 (+2 −1)», тексты — data-checktree-text-changes и -split; без правок она спрятана. Без скрипта ветка показывает первые 50, ссылка и кнопка поиска ведут на свои адреса, и форма отдаёт галки.
  23. Подсказка обрезанного имени (0.13). Имя карточки объекта, которое лист обрезал многоточием, на наведении получает title с именем целиком; у целого имени подсказки нет, свой title от сервера скрипт не трогает. Без скрипта имя так же обрезано, а целиком его читает скринридер и видно по ссылке.

Без скрипта страница работает и выглядит законченной: подпись поля просто стоит наверху, ряби нет, шапка без тени, индикатор появляется на новом месте, а формы и переключатель темы отправляются обычной навигацией с перезагрузкой. Окно открывает движок, а где он команд не знает — сервер печатает его открытым по адресу с ?окно=…; подтверждение спрашивает сервер страницей. Негодное поле не пускает браузер своим пузырём, а то, что знает только сервер, он отбивает отказом под полем; тост стоит на странице, где его напечатал сервер. Слот картинки показывает системное файловое поле и кнопку отправки, образец — в теме страницы, а зеркала — то, что напечатал сервер. Секрет свойства открывается и без скрипта, а значение для копирования выделяют мышью.

Рябь — нажмите на кнопку

HTML = Набор.Кнопка().Отрисовать(Новый Структура("Подпись, Иконка", "Сохранить", "сохранить")) + Набор.Кнопка().Отрисовать(Новый Структура("Подпись, Вариант", "Отмена", "вторичная")) + Набор.Кнопка().Отрисовать(Новый Структура("Подпись, Вариант, Иконка, ТолькоИконка", "Настройки", "ссылка", "шестерёнка", Истина));

Меньше движения#

Человек может попросить систему убрать движение — настройка prefers-reduced-motion. Кит её слушает:

  • лист сводит длительность всех анимаций и переходов почти к нулю, повторы — к одному, плавную прокрутку отключает;
  • скрипт перестаёт рождать рябь.

Состояния при этом меняются как прежде — пропадает только путь между ними.

Гайдлайны#

ДелайтеДлительность и кривая — токенами шкалы
Код
HTML = Набор.Кнопка().Отрисовать(Новый Структура("Подпись, Вариант", "Наведите", "вторичная"));
Не делайтеСвоя секунда по прямой: наведение тянется, интерфейс кажется вязким
Код
HTML = "<button class=""button button--ghost"" type=""button"" "
	+ "style=""transition:background-color 1200ms linear"">Наведите</button>";
ДелайтеЗанятость — индикатором внутри кнопки
Код
HTML = Набор.Кнопка().Отрисовать(Новый Структура("Подпись, Занята", "Сохраняется", Истина));
Не делайтеЭлемент прыгает ради внимания: бесконечное движение отвлекает и укачивает
Код
HTML = "<button class=""button"" type=""button"" "
	+ "style=""animation:ui-toast 600ms ease-in-out infinite alternate"">Сохраняется</button>";

Доступность#

  • Настройка «меньше движения» гасит анимации кита целиком. Своё движение в листе инсталляции тоже оборачивайте в @media (prefers-reduced-motion: reduce).
  • Движение ничего не сообщает в одиночку: занятая кнопка несёт aria-busy, тост — роль status или alert.
  • Рябь — декорация: её контейнер скрыт от скринридера (aria-hidden) и не ловит указатель.