GrInput

  • ядро
  • Формы
  • API · 32 props
  • ⌨ клавиши

Берут, когда вводится строка.

import { GrInput } from '@feugene/granularity/components/GrInput'

Обзор

Когда брать

  • вводится строка — имя, адрес, поисковый запрос, пароль: базовый контрол формы;
  • у поля есть аддоны — единица измерения, префикс протокола, иконка поиска через слоты;
  • длина ограничена — maxlength со счётчиком символов вместо молчаливого обрезания;
  • в поле идёт фоновая работа — индикатор занятости, не блокируя ввод.

Когда взять другое

НужноБерите
Текст в несколько строкGrTextarea
Вводится число со ступенямиGrNumberInput
Значений несколько, они чипамиGrInputTag
Выбор из готовых вариантовGrSelect
Поиск с подсказкамиGrAutocomplete
Дата или времяGrDatePicker

Варианты все тона и виды — настоящим компонентом

Варианты · GrInput
state \ shapeboxpill
default
success
warning
danger

Размеры

  • xs
  • sm
  • md
  • lg

Состояния

  • обычная
  • disabled
  • loading
  • readonly
  • invalid

Матрица живая: наводите и нажимайте. Состояний наведения, фокуса и нажатия здесь нет — они за браузером, а не за пропом.

Playground контролы выводятся из API, а не пишутся руками

Сцена

Загружается…

0 изменено· состояние — в адресе

Примеры 3 у каждого — живое демо и его код

  • Редактор товара с аддонами

    Аддоны держат то, что не является значением, — адрес магазина, валюту, единицу, — а describe-addons даёт скринридеру услышать их там, где они несут смысл. suffix-fixed с общим suffix-min-width делает EUR и kg одной ширины, и числа встают в колонку, а text-align="right" выравнивает их цифры.

    Linen shirt, sand
    shop.example.com/p/
    EUR
    EUR
    kg
  • Имя пользователя, проверенное по ходу ввода

    loading показывает проверку занятости внутри поля и ставит aria-busy; state красит ответ и добавляет его значок. maxlength с show-count держит лимит на виду, а password-toggle добавляет «глаз» с нормальным доступным именем.

    Create your account

    Letters, digits and hyphens.

    4 / 20

    12 characters or more

API порождается из пакета · разойтись не может

Props

PropTypeпо умолчаниюОписание
disabledboolean | undefinedfalseВыключить поле: очистка и показ пароля пропадают. Складывается с `GrFormField`.
type"number" | "search" | "text" | "email" | "password" | "tel" | "url" | undefined"text"Нативный `type`: меняет клавиатуру и браузерную проверку формата. По умолчанию `text`.
modelValuev-modelstring | undefined""Значение поля. Необязательное: без `v-model` поле рисуется пустым. Дефолт — пустая строка, а не `undefined`: длина значения читается напрямую (`showClear`), и `undefined` уронил бы рендер.
readonlyboolean | undefinedfalseТолько для чтения: значение видно и выделяемо, но не редактируется.
invalidboolean | undefinedfalseОшибка валидации: danger-рамка и `aria-invalid`. Складывается с ошибкой `GrFormField`.
requiredboolean | undefinedfalseОбязательное поле (`aria-required`). Складывается с `required` у `GrFormField`.
size"xs" | "sm" | "md" | "lg" | undefinedundefinedВысота и кегль по шкале контролов. По умолчанию `md`; берётся из `GrConfigProvider`.
placeholderstring | undefinedundefinedПодсказка в пустом поле. Подпись не заменяет: имя даёт `GrFormField` или `ariaLabel`.
ariaLabelstring | undefinedundefinedДоступное имя вне `GrFormField`.
clearableboolean | undefinedundefinedПоказывать кнопку очистки, когда есть значение (и не disabled/readonly).
loadingboolean | undefinedfalseФоновая работа по полю (проверка занятости логина, автосохранение): спиннер в trailing-области + `aria-busy`. Ввод не блокируется — для этого есть `disabled`/`readonly`.
clearLabelstring | undefinedundefinedi18n aria-label кнопки очистки.
namestring | undefinedundefinedНативный `name` — имя поля при отправке формы.
prefixMinWidthstring | undefinedundefinedМинимальная ширина префикса. По умолчанию — по ступени размера, у `inline`-аддона — `0`.

Слоты

SlotTypeОписание
prefixanyАддон слева от поля: иконка, код валюты, метка.
suffixanyАддон справа от поля: единица измерения, подсказка.

События

EventСигнатураОписание
update:modelValue(event: "update:modelValue", value: string): void—
change(event: "change", value: string): void—
clear(event: "clear"): void—
focus(event: "focus", event: FocusEvent): void—
blur(event: "blur", event: FocusEvent): void—

Методы

MethodsСигнатураОписание
focus() => voidПоставить фокус в поле.
blur() => voidСнять фокус с поля.
select() => voidВыделить содержимое поля — чтобы подставленное значение перезаписали вводом.

Типы

Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.

Импорт
import type { GrInputConfigurableProps, GrInputProps, GrInputEmits } from '@feugene/granularity/components/GrInput'

Файлdist/types/components/GrInput/config.d.ts

grInputConfigconst
const grInputConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrInput">;

Файлdist/types/components/GrInput/defaults.d.ts

GrInputConfigurablePropsinterface

Пропы GrInput, настраиваемые глобально через componentDefaults. Только оформление и поведение по умолчанию — см. GrButton/defaults.ts. size типизируем через GrComponentSize (та же шкала xs…lg), а не через GrInputSize: последний объявлен в GrInput.vue, который импортирует контекст, — получился бы цикл модулей.

interface GrInputConfigurableProps {
    size: GrComponentSize;
    clearable: boolean;
    /** Форма рамки — см. `components/shared/controlShape.ts`. */
    shape: GrControlShape;
}

Файлdist/types/components/GrInput/GrInput.vue.d.ts

GrInputPropsinterface
interface GrInputProps {
    /**
       * Значение поля. Необязательное: без `v-model` поле рисуется пустым.
       * Дефолт — пустая строка, а не `undefined`: длина значения читается
       * напрямую (`showClear`), и `undefined` уронил бы рендер.
       */
    modelValue?: string;
    /** Нативный `type`: меняет клавиатуру и браузерную проверку формата. По умолчанию `text`. */
    type?: 'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url';
    /** Подсказка в пустом поле. Подпись не заменяет: имя даёт `GrFormField` или `ariaLabel`. */
    placeholder?: string;
    /** Нативный `autocomplete`: чем браузеру заполнять поле — `email`, `one-time-code` и т. п. */
    autocomplete?: string;
    /** Нативный `inputmode`: какую экранную клавиатуру показать, не меняя `type`. */
    inputmode?: InputHTMLAttributes['inputmode'];
    /** Выключить поле: очистка и показ пароля пропадают. Складывается с `GrFormField`. */
    disabled?: boolean;
    /** Только для чтения: значение видно и выделяемо, но не редактируется. */
    readonly?: boolean;
    /** Ошибка валидации: danger-рамка и `aria-invalid`. Складывается с ошибкой `GrFormField`. */
    invalid?: boolean;
    /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */
    required?: boolean;
    /** Доступное имя вне `GrFormField`. */
    ariaLabel?: string;
    /**
     * Подсветка рамки по решению разработчика. `success` и `warning` добавляют иконку
     * и скрытую подпись; `invalid` сильнее. По умолчанию `default`.
     */
    state?: 'default' | 'success' | 'warning' | 'danger';
    /** Нативный `name` — имя поля при отправке формы. */
    name?: string;
    /** `id` поля. Не задан — берётся из `GrFormField`, чтобы подпись нашла поле. */
    id?: string;
    /** Высота и кегль по шкале контролов. По умолчанию `md`; берётся из `GrConfigProvider`. */
    size?: GrInputSize;
    /**
     * Форма рамки. `box` — скругление шкалы контролов, как было всегда; `pill` —
     * дорожка-пилюля, как у `GrSegmented`. Форма тянет за собой горизонтальный
     * отступ: в пилюле он не меньше половины высоты, иначе текст заезжает в дугу.
     */
    shape?: GrControlShape;
    /** Показывать кнопку очистки, когда есть значение (и не disabled/readonly). */
    clearable?: boolean;
    /** i18n aria-label кнопки очистки. */
    clearLabel?: string;
    /** Ограничение длины + основа для счётчика символов. */
    maxlength?: number;
    /** Показывать счётчик символов (`len` или `len/maxlength`). */
    showCount?: boolean;
    /**
     * Аддоны `prefix`/`suffix` несут смысл, а не украшают.
     *
     * По умолчанию они скрыты от скринридера: чаще всего там иконка поиска или
     * глиф, повторяющий подпись поля, и второе прочтение мешает. Но `https://`
     * или `₽` бывают единственным местом, где сказано, что вводить, — тогда
     * аддон уезжает в `aria-describedby` поля.
     *
     * Декоративную иконку внутри слота помечайте `aria-hidden` сами: проп снимает
     * скрытие со всей обёртки, а не разбирает её содержимое.
     */
    describeAddons?: boolean;
    /**
       * Фоновая работа по полю (проверка занятости логина, автосохранение):
       * спиннер в trailing-области + `aria-busy`. Ввод не блокируется —
       * для этого есть `disabled`/`readonly`.
       */
    loading?: boolean;
    /** Кнопка показать/скрыть пароль (только при `type="password"`). */
    passwordToggle?: boolean;
    /** i18n aria-label кнопки показать/скрыть пароль. */
    passwordShowLabel?: string;
    /** i18n aria-label кнопки, которая прячет показанный пароль. Не задан — из локали. */
    passwordHideLabel?: string;
    /** Выравнивание текста в поле. По умолчанию `left`. */
    textAlign?: GrInputTextAlign;
    /** Минимальная ширина префикса. По умолчанию — по ступени размера, у `inline`-аддона — `0`. */
    prefixMinWidth?: string;
    /** Максимальная ширина префикса: содержимое шире обрезается. */
    prefixMaxWidth?: string;
    /** Минимальная ширина суффикса. По умолчанию — по ступени размера, у `inline`-аддона — `0`. */
    suffixMinWidth?: string;
    /** Максимальная ширина суффикса: содержимое шире обрезается. */
    suffixMaxWidth?: string;
    /**
       * Фиксированная ширина у prefix/suffix: аддон получает жёсткую ширину
       * (из `*MaxWidth` → `*MinWidth` → дефолт), а контент обрезается по краю
       * (prefix — справа, suffix — слева). По умолчанию аддоны «растягиваются»
       * под контент (в пределах min/max), а излишек клипается оболочкой.
       */
    prefixFixed?: boolean;
    /** Жёсткая ширина суффикса — как у `prefixFixed`, только лишнее обрезается слева. */
    suffixFixed?: boolean;
    /**
       * Как выглядят аддоны `#prefix`/`#suffix`.
       *
       * `segment` (по умолчанию) — отдельный отсек, отрезанный рамкой и выровненный
       * по ступени размера: так поле с «₽» и поле с «USD» стоят в колонку.
       * `inline` — украшение внутри рамки: ни разделителя, ни своей ширины.
       *
       * Разница не косметическая. Поисковая строка с лупой в сегменте читается
       * составным элементом — полем с приклеенной кнопкой, — а не одним полем;
       * именно поэтому иконку внутри рамки нельзя было выразить аддоном, и
       * потребители отказывались от неё вовсе.
       */
    addon?: 'segment' | 'inline';
}
GrInputEmitsinterface
interface GrInputEmits {
    /** Новое значение на каждый ввод (`v-model`). Кнопка очистки шлёт пустую строку. */
    (e: 'update:modelValue', value: string): void;
    /** Значение зафиксировано нативным `change` — по `blur` или `Enter`. */
    (e: 'change', value: string): void;
    /** Поле получило фокус — нативный `focus` с `<input>`. */
    (e: 'focus', event: FocusEvent): void;
    /** Поле потеряло фокус — нативный `blur` с `<input>`. */
    (e: 'blur', event: FocusEvent): void;
    /**
     * Значение стёрто кнопкой очистки. Отдельное событие потому, что по
     * `update:modelValue` программную очистку от ручного стирания не отличить.
     */
    (e: 'clear'): void;
}
GrInputSizetype
type GrInputSize = GrComponentSize;
GrInputTextAligntype
type GrInputTextAlign = 'left' | 'center' | 'right';

Файлdist/types/components/GrInput/safelist.d.ts

grInputSafelistconst
const grInputSafelist: string[];

Файлdist/types/components/GrInput/index.d.ts

GrInputInstancetype
type GrInputInstance = ComponentExposed<typeof GrInputComponent>;

Доступность

Паттерн APG

—

КлавишаДействие
—Tab из поля — на кнопку очистки, затем на переключатель пароля (обе активируются Enter/Space и возвращают фокус в поле)

Использование из прозы библиотеки

Trailing-контролы достижимы с клавиатуры

Кнопки очистки и показа пароля стоят в обычном таб-порядке: Tab из поля ведёт на крестик, затем на глаз. Раньше на них висел tabindex="-1" — виджет был объявлен доступным (aria-label, aria-pressed), но воспользоваться им без мыши было нельзя.

Обе кнопки после нажатия возвращают фокус в поле, поэтому очистка не роняет фокус в никуда: кнопка исчезает вместе с опустевшим значением.

Аддоны: сегмент или украшение

#prefix и #suffix рисуются в двух видах, и это разные сущности, а не оформление одного.

segment (по умолчанию) — отдельный отсек, отрезанный рамкой и выровненный по ступени размера: поле с «₽» и поле с «USD» стоят в колонку, а не пляшут по ширине. Это то, чего ждут от денежного поля и поля с единицей измерения.

addon="inline" — украшение внутри рамки: ни разделителя, ни собственной ширины. Лупа поисковой строки, символ валюты, счётчик. В сегменте лупа читалась бы полем с приклеенной кнопкой, поэтому иконку внутри рамки нельзя было выразить аддоном вовсе — от неё просто отказывались.

vue
<GrInput v-model="query" addon="inline">
  <template #prefix>
    <GrIcon size="sm"><span class="i-lucide-search" /></GrIcon>
  </template>
</GrInput>

Отступ поля считается измеренной шириной аддона в обоих режимах: текст начинается сразу за украшением, а не в пустом отсеке. prefixFixed/suffixFixed и *MinWidth/*MaxWidth работают как раньше — они про сегмент.

Значащий аддон объявляется

Аддоны скрыты от скринридера по умолчанию: чаще всего там лупа поиска или глиф, повторяющий подпись поля, и второе прочтение мешает.

Но https:// в поле адреса или ₽ при подписи без валюты бывают единственным местом, где сказано, что вводить. Для них есть describeAddons.

Он не просто снимает скрытие: <div> рядом с полем в его доступное имя не входит, и диктор наткнулся бы на текст лишь при обходе страницы — не тогда, когда он нужен. Поэтому аддон уезжает в aria-describedby самого поля.

vue
<GrInput v-model="site" describe-addons>
  <template #prefix>
    https://
  </template>
</GrInput>

Декоративную иконку внутри слота помечайте aria-hidden сами: проп снимает скрытие со всей обёртки, а не разбирает её содержимое.

Форма рамки

shape="pill" даёт то же скругление, что у GrSegmented; дефолт — box, скругление шкалы контролов.

Форма тянет за собой горизонтальный отступ: у пилюли он не меньше половины высоты, иначе текст заезжает в дугу. Аддоны идут за ней же — отсек обрезается по внешнему контуру оболочкой (overflow-hidden), а его разделительная рамка остаётся прямой.

Ось и её границы — ../sizes.md; там же сказано, каким полям формы не досталось и почему.

Счётчик символов

showCount рисует 12 / 60 (или просто длину, если maxlength не задан) и связывает счётчик с полем через aria-describedby — при фокусе диктор читает остаток вместе с подписью.

Исчерпание лимита дополнительно объявляется живым регионом role="status" (gr.input.limitReached). Регион молчит, пока лимит не выбран: читать вслух каждый символ — гарантированный способ сделать поле неюзабельным для SR.

vue
<GrInput v-model="bio" :maxlength="60" show-count clearable />
События
СобытиеКогда
update:modelValueкаждый ввод
changeзначение зафиксировано: нативный change (по blur или Enter) или кнопка очистки
focus / blurфокус пришёл/ушёл, аргумент — FocusEvent
clearзначение стёрто кнопкой очистки

clear существует отдельно от update:modelValue потому, что по одному лишь значению программную очистку от ручного стирания не отличить — а реакция формы на них обычно разная.

Очистка кнопкой шлёт все три события подряд: update:modelValue, change, clear. Она такая же фиксация значения, как уход фокуса, — подписка ради «значение установилось» пропускала бы ровно её. Нативный аналог ведёт себя так же: крестик у <input type="search"> шлёт и input, и change.

Императивный API
vue
<GrInput ref="field" v-model="value" />

field.value отдаёт focus(), blur() и select(). Последний — спутник focus() для сценария «подставили значение, дайте перезаписать».

loading

Спиннер в trailing-области плюс aria-busy на поле: проверка занятости логина, автосохранение, догрузка справочника. Ввод при этом не блокируется — для запрета есть disabled и readonly. Спиннер резервирует место справа наравне с кнопками, поэтому текст под него не уезжает.

Состояния и токены

state (success / warning / danger) красит рамку и кольцо фокуса; invalid (свой проп или ошибка из GrFormField) всегда приводит к danger-виду.

success и warning вдобавок показывают иконку и скрытую подпись, которая уезжает в aria-describedby поля. Цвет рамки — единственный носитель смысла — это WCAG 1.4.1: иконка добавляет форму, подпись добавляет программный канал, которого у этих двух состояний нет (ARIA-эквивалента aria-invalid для них не существует). danger иконки не получает: его смысл уже несут aria-invalid и текст ошибки от GrFormField.

Признак гаснет, когда поле объявлено невалидным: галочка «корректно» рядом с aria-invalid="true" противоречила бы ему.

Заблокированное поле гасится фоном --gr-muted с текстом --gr-muted-fg, а не прозрачностью: opacity разбавляет выверенные на AA токены текста и роняет контраст.

Классы размеров, выравнивания и состояний живут в grInputStyles.ts и целиком объявлены в safelist — вместе с горизонтальным padding’ом, который тому же размеру нужен и числом (аддоны задают паддинги инлайн-стилем, а он перекрывает класс).

type

text, email, password, number, search, tel, url. Последние два меняют экранную клавиатуру на мобильных и включают браузерную валидацию формата.