Перейти к содержимому
EN
Масштаб
Плотность
Тема
Акцент

Ввод

Числовое поле инспектора

JS Перетаскивание подписи оси — делает `instrument.js`

Число с буквой оси слева — примитив инспектора: три таких поля составляют вектор, и в панели свойств их десятки. Компактнее текстового поля, потому что подпись оси занимает место степпера, а не отдельную колонку.

Компонент
X Y Z
Разметка
<span class="inst-num-field">
  <span class="inst-num-axis">X</span>
  <input type="number" value="128" aria-label="Позиция X">
</span>
<span class="inst-num-field">
  <span class="inst-num-axis">Y</span>
  <input type="number" value="0" aria-label="Позиция Y">
</span>
<span class="inst-num-field">
  <span class="inst-num-axis">Z</span>
  <input type="number" value="-64" aria-label="Позиция Z">
</span>

Контракт

Здесь легко получить поле, которое выглядит подписанным и не является им.

Что Обязательно Почему
aria-label на <input>, включающий ось да Скринридер иначе прочитает «поле, 128» и не скажет, чего именно. Ось — часть имени: «Позиция X», а не «X»
inst-num-axis не <label> да Это ручка перетаскивания и визуальный маркер, а не подпись. <label for> сделал бы буквой «X» полное доступное имя поля — вместо имени свойства получилось бы одно слово
type="number" да Клавиатурные стрелки, шаг и числовая клавиатура на сенсорных — от платформы
<input> прямым ребёнком да Правила написаны через & > input
title на inst-prop-label да, в строке свойства Обрезанное имя свойства иначе не прочитать

Отсюда следствие, которое проще нарушить, чем заметить: буква оси не должна быть единственным местом, где написано, какое это поле. Имя свойства живёт в inst-prop-label рядом и повторяется в aria-label каждого поля — три одинаковых квадратика с буквами X, Y, Z вне контекста строки не значат ничего.

Перетаскивание делает instrument.js. Курсор ew-resize на подписи оси означает «отсюда тянут», и модуль это обещание выполняет: Shift ускоряет в десять раз, Alt замедляет в десять.

Без модуля поле остаётся обычным числовым вводом, и он полностью работает: цифры, стрелки, колесо, вставка.

aria-label обязан включать ось и свойство: «X» именем не является. Сама inst-num-axis подписью не размечена — иначе она перебила бы aria-label и стала бы полным именем поля.

Клавиатура нативная: стрелки меняют значение на step, PageUp и PageDown — на десять шагов. Степперы сняты оформлением, поведение не тронуто. Кольцо фокуса висит на обёртке через :focus-within, поэтому виден весь контрол, а не только цифры.

Рамка берёт --border-control и держит 3:1: здесь граница и есть контрол. В forced-colors обёртка получает рамку CanvasText — без неё поле схлопывается с поверхностью панели. Высота --control-h-sm даёт 26px, а в плотном режиме 22px, то есть ниже 24px по WCAG 2.5.8: это осознанная цена панели инспектора, где на экране десятки полей.

Состояния

Пример
X Y Z
Разметка
<span class="inst-num-field">
  <span class="inst-num-axis">X</span>
  <input type="number" value="128" aria-label="Позиция X">
</span>
<span class="inst-num-field">
  <span class="inst-num-axis">Y</span>
  <input type="number" value="0" aria-label="Позиция Y" readonly>
</span>
<span class="inst-num-field">
  <span class="inst-num-axis">Z</span>
  <input type="number" value="-64" aria-label="Позиция Z" disabled>
</span>
Состояние Как ставится Что происходит
наведение :hover на обёртке Рамка темнеет до --text-muted
фокус :focus-within на обёртке Рамка акцентом. Кольцо рисует обёртка, а не поле: у поля рамки нет вовсе
только чтение readonly на <input> Значение читается и копируется. Не disabled — разницу см. в текстовом поле
недоступно disabled на <input> Нативная недоступность

Нативные стрелки-степперы сняты (appearance: textfield): в плотном ряду инспектора они съедали треть ширины поля и попадали под курсор чаще, чем сам ввод. Клавиатурные стрелки при этом продолжают работать — снято оформление, а не поведение.

JS

Подключите модуль один раз на страницу — инициализировать компоненты по отдельности не нужно, instrument.js работает делегированием и видит узлы, пришедшие позже.

<script type="module" src="instrument.js"></script>

Что делает instrument.js

Пример в шапке живой: потяните букву оси влево-вправо, с Shift — в десять раз быстрее.

Перетаскивание буквы оси меняет значение. Курсор ew-resize на ней — обещание, данное картинкой, и instrument.js его выполняет.

Модификатор Множитель шага
— ×1
Shift ×10
Alt ×0.1

Границы берутся из min и max, шаг — из step. Поле с disabled или readonly не тянется.

События

Своих событий нет: instrument.js меняет <input> и шлёт нативные input во время перетаскивания и change по отпусканию. Фреймворк видит их без единой строки клея.

input.addEventListener('change', (e) => scene.setX(+e.target.value));

Опции

Атрибут Что делает
step Шаг перетаскивания. По умолчанию 1
min, max Границы. Без них значение не ограничено
<span class="inst-num-field">
  <span class="inst-num-axis">X</span>
  <input type="number" value="0.5" step="0.01" min="0" max="1"
         aria-label="Прозрачность X">
</span>

Композиции

Вектор

Пример
X Y Z
Разметка
<span class="inst-vec">
  <span class="inst-num-field"><span class="inst-num-axis">X</span><input type="number" value="128" aria-label="Позиция X"></span>
  <span class="inst-num-field"><span class="inst-num-axis">Y</span><input type="number" value="0" aria-label="Позиция Y"></span>
  <span class="inst-num-field"><span class="inst-num-axis">Z</span><input type="number" value="-64" aria-label="Позиция Z"></span>
</span>

inst-vec раздаёт трём полям равные доли ширины, поэтому вектор не перекашивается, когда в одну компоненту вводят шестизначное число.

Одиночное значение с осью-именем

Пример
R
Разметка
<span class="inst-num-field">
  <span class="inst-num-axis">R</span>
  <input type="number" value="45" step="5" aria-label="Радиус влияния">
</span>

Сценарии

Строка свойства инспектора

В контексте
Позиция X Y Z
Разметка
<div class="inst-prop">
  <span class="inst-prop-label" title="Позиция">Позиция</span>
  <span class="inst-prop-control inst-vec">
    <span class="inst-num-field"><span class="inst-num-axis">X</span><input type="number" value="128" aria-label="Позиция X"></span>
    <span class="inst-num-field"><span class="inst-num-axis">Y</span><input type="number" value="0" aria-label="Позиция Y"></span>
    <span class="inst-num-field"><span class="inst-num-axis">Z</span><input type="number" value="-64" aria-label="Позиция Z"></span>
  </span>
</div>

API

Классы
ИмяЧто делает
класс
inst-num-fieldОбёртка: рамка, врез, фокус. Высота — --control-h-sm
inst-num-axisБуква оси. Ручка перетаскивания, не подпись
inst-vecРяд из нескольких полей равной ширины
inst-propСтрока свойства инспектора
inst-prop-labelИмя свойства. Обрезается, поэтому обязателен title
inst-prop-controlКонтрольная часть строки
Токены
ИмяЗначениеОткуда
токен
--control-h-sm24pxбаза +14: плотность · масштаб
--radius-sm6pxбаза +4: масштаб
--surface-fieldvar(--surface-raised)одно на все ячейки
--border-controllight-dark(oklch(0 0 0 / 0.46), oklch(1 0 0 / 0.38))одно на все ячейки
--accent-borderlight-dark(var(--a-4), var(--a-3))одно на все ячейки
--text-2xs0.6875remбаза +4: масштаб
--text-xs0.78125remбаза +4: масштаб
--space-24pxодно на все ячейки
--space-36pxодно на все ячейки

Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument