Отображение данных
Код и копируемое значение
Блок кода и однострочное значение, которое хочется скопировать: путь, хеш, идентификатор.
Страница про оба: первый показывают, второй забирают.
Разметка
<div class="inst-code">go -C tools run ./cmd/contrast
<button class="inst-copy" type="button" aria-label="Скопировать команду">
<svg class="inst-icon" aria-hidden="true"><use href="#i-copy"/></svg>
</button>
</div>
<span class="inst-copyable">a4f7c2e91b0d5537
<button class="inst-copy" type="button" aria-label="Скопировать a4f7c2e91b0d5537">
<svg class="inst-icon" aria-hidden="true"><use href="#i-copy"/></svg>
</button>
</span>
Контракт
| Что | Обязательно | Почему |
|---|---|---|
tabindex="0" и role="region" с именем |
да, если внутри нет фокусируемого | Блок прокручивается по горизонтали, и до прокрутки надо добраться с клавиатуры |
aria-label со значением у кнопки копирования |
да | Ряд кнопок «Скопировать» на слух неразличим: «Скопировать a4f7c2e» |
| Ничего для озвучивания копирования | — | instrument.js заводит общую живую область [data-inst-live] сам и говорит в неё словом. Приложению здесь делать нечего |
Переноса строк в блоке кода нет: перенос в команде меняет её смысл.
Отсюда горизонтальная прокрутка, а scrollbar-gutter зарезервирован, чтобы
появление полосы не сдвигало код на пиксель при каждом обновлении.
Доступность
| Прокрутка | Блок прокручивается по горизонтали, поэтому обязан быть достижим с клавиатуры: tabindex="0" и role="region" с именем, если внутри нет фокусируемого |
scrollbar-gutter |
Зарезервирован, чтобы появление полосы не сдвигало код на пиксель при каждом обновлении |
| Имя кнопки копирования | aria-label со значением: «Скопировать a4f7c2e». Ряд кнопок «Скопировать» на слух неразличим |
| Результат копирования | instrument.js меняет вид кнопки и говорит словом: смена глифа для скринридера — событие без содержания. Живая область у кита одна на документ, заводится при первом сообщении и переиспользуется |
| Перенос строк | В блоке кода его нет: перенос в команде меняет её смысл. Отсюда горизонтальная прокрутка |
| Кегль | --text-xs — 12,5px. Ниже --text-2xs кегль не опускается |
Варианты
Копируемое значение
Однострочное значение с кнопкой — хеш, идентификатор, путь.
Разметка
<span class="inst-copyable">a4f7c2e
<button class="inst-copy" type="button" aria-label="Скопировать a4f7c2e">
<svg class="inst-icon" aria-hidden="true"><use href="#i-copy"/></svg>
</button>
</span>
Кнопка стоит в потоке, с просветом, и видна всегда. Абсолют в углу с проявлением по наведению — приём блока кода: там угол свободен и текста много. У однострочного значения угла нет, и значок ложился на последние символы, а на касании его не было вовсе, потому что наведения на касании не бывает.
Область нажатия у кнопки растёт до нормы невидимым ::before — тем же приёмом,
что у флажка: растёт область, а не значок, и строка остаётся прежней высоты.
Копируемое значение в строке
Самое частое его место и меньше всего похожее на блок кода: колонка
идентификаторов в таблице, машинное значение в списке свойств. Разметка — тот
же inst-copyable, таблица в ней ничего не меняет.
| Прогон | Идентификатор | Состояние |
|---|---|---|
| nightly-4127 | 2f8a1c04-9b7e-4d31-a5f0-c6e2
|
идёт |
| audio-pass | 7c31b9de-2a44-4e08-8f1b-30d7
|
упало |
Разметка
<table class="inst-table">
<thead><tr><th>Прогон</th><th>Идентификатор</th><th>Состояние</th></tr></thead>
<tbody>
<tr><td>nightly-4127</td>
<td><span class="inst-copyable"><code>2f8a1c04-9b7e-4d31-a5f0-c6e2</code>
<button class="inst-copy" type="button" aria-label="Скопировать 2f8a1c04-9b7e-4d31-a5f0-c6e2">
<svg class="inst-icon" aria-hidden="true"><use href="#i-copy"/></svg>
</button></span></td>
<td><span class="inst-badge" data-tone="running"><span class="inst-dot"></span>идёт</span></td></tr>
<tr><td>audio-pass</td>
<td><span class="inst-copyable"><code>7c31b9de-2a44-4e08-8f1b-30d7</code>
<button class="inst-copy" type="button" aria-label="Скопировать 7c31b9de-2a44-4e08-8f1b-30d7">
<svg class="inst-icon" aria-hidden="true"><use href="#i-copy"/></svg>
</button></span></td>
<td><span class="inst-badge" data-tone="error"><span class="inst-dot"></span>упало</span></td></tr>
</tbody>
</table>
Три вещи про строку, в которых легко ошибиться, — и в двух из них справочник ошибался сам, пока этот раздел не был написан:
| Что | Почему |
|---|---|
| Кнопка видна всегда, а не по наведению на строку | У колонки идентификаторов на тачскрине наведения нет, и кнопки, которая им вызывается, там просто не существует. Справочник прятал её так в собственных таблицах API |
Цвет — --text-muted, а не --text-faint |
Faint рисуется по порогу 3:1 и предназначен линиям и разделителям. Контрол читают, значит ему причитаются 4.5:1 у muted |
В aria-label стоит значение |
Колонка из десяти кнопок «Скопировать» на слух — одно слово десять раз. «Скопировать 2f8a1c04» говорит, какая это строка |
Размер значка здесь не написан и написан быть не должен: .inst-copy — это
--size-icon-sm везде, где он стоит, потому что значок рядом со значением
обслуживает значение. Места, которые выбирали сами, выбирали по-разному —
именно так в одном ките оказались кнопка копирования на 18 пикселей и на 10 на
одной странице.
Листинг с номерами строк
Разметка
<div class="inst-code inst-code--numbered">
<div class="inst-code-line"><span class="inst-code-num">41</span><span class="inst-code-src">const size = 256;</span></div>
<div class="inst-code-line"><span class="inst-code-num">42</span><span class="inst-code-src">let seed = Date.now();</span></div>
<div class="inst-code-line"><span class="inst-code-num">43</span><span class="inst-code-src">return generate(size, seed);</span></div>
</div>
Номер не выделяется мышью — user-select: none: копируют код, а не страницу.
Колонка номеров ровно 5ch, и это не запас на глаз: пятизначный номер строки
встречается в настоящих файлах, а колонка, растущая от содержимого, сдвигала
бы код при каждой подгрузке.
inst-code |
inst-copyable |
|
|---|---|---|
| Сколько строк | Сколько угодно | Одна |
| Прокрутка | По горизонтали | Нет |
| Кнопка | Нет | Есть |
| Пример | Команда, конфиг | Хеш, путь, идентификатор |
JS
Подключите модуль один раз на страницу — инициализировать компоненты по
отдельности не нужно, instrument.js работает делегированием и видит узлы, пришедшие
позже.
<script type="module" src="instrument.js"></script>
Что делает instrument.js
Примеры на этой странице живые: наведите на блок кода и нажмите кнопку копирования — значок сменится на галку.
Копирование целиком: текст в буфер, цветной ответ на кнопке и объявление результата вслух. Ответ обязателен — буфер обмена ничего не показывает, и без него человек жмёт кнопку второй раз.
Источник — текст ближайшего .inst-code или .inst-copyable без самой
кнопки. Читается textContent, а не innerText: второй у свёрнутого блока
возвращает пустую строку, и копирование ушло бы в ничто беззвучно.
Опции
| Атрибут | Что делает |
|---|---|
data-copy |
Копировать это значение, а не текст блока. Нужно, когда видно сокращение, а копировать надо полное |
data-copied-label |
Своя фраза для скринридера вместо «Copied» |
data-failed-label |
То же для неудачи. По умолчанию «Could not copy» |
<span class="inst-copyable">a4f7c2e
<button class="inst-copy" type="button" data-copy="a4f7c2e91b0d5537"
aria-label="Скопировать полный хеш"></button>
</span>
События
inst:copy всплывает с { text } и отменяем: preventDefault() означает
«копирую сам», и instrument.js не трогает буфер.
document.addEventListener('inst:copy', (e) => {
analytics.track('copy', { length: e.detail.text.length });
});
Чего instrument.js не делает
Подсветку синтаксиса. Она требует разбора языка, и это работа приложения.
API
| Имя | Что делает |
|---|---|
| класс | |
inst-code | Блок кода. Прокручивается по горизонтали |
inst-code-line | Строка листинга. Только внутри --numbered |
inst-code-num | Номер. --text-muted, потому что его читают. Не выделяется мышью |
inst-code-src | Сам код. Единственное место, где живёт overflow-wrap: anywhere |
inst-copyable | Однострочное значение с кнопкой |
inst-copy | Кнопка копирования. Работу выполняет [instrument.js](../../foundations/behavior.md). Внутри .inst-code встаёт в дальний верхний угол сама |
| модификатор | |
inst-code--numbered | Листинг с колонкой номеров строк |
| Имя | Значение | Что делает |
|---|---|---|
| атрибут | ||
data-copy | — | Копировать это значение, а не текст блока |
data-copied-label | — | Своя фраза для скринридера вместо «Copied» |
data-failed-label | — | То же для неудачи. По умолчанию «Could not copy» |
data-copied | true · false | Ответ кнопки. Ставит и снимает instrument.js |
| Имя | Значение | Откуда |
|---|---|---|
| токен | ||
--surface-sunken | light-dark(var(--n-2), var(--n-14)) | база +1: тема |
--border-subtle | light-dark(oklch(0 0 0 / 0.07), oklch(1 0 0 / 0.06)) | одно на все ячейки |
--hairline | 1px | база +1: экран |
--radius-md | 8px | база +4: масштаб |
--space-4 | 8px | одно на все ячейки |
--pad-panel | var(--space-5) | база +14: плотность · масштаб |
--text-xs | 0.78125rem | база +4: масштаб |
--font-mono | ui-monospace, " , " , " , Consolas, " , monospace | одно на все ячейки |
Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument