GrTextarea

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

Берут, когда текст длиннее строки.

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

Обзор

Когда брать

  • текст длиннее строки — комментарий, описание, письмо: перенос строк — часть значения;
  • высота подстраивается — autosize с maxLines растёт по содержимому и не съедает экран;
  • длина ограничена — счётчик символов и строк рядом с полем;
  • пользователь меняет размер сам — resize вместо фиксированной высоты.

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

НужноБерите
Значение в одну строкуGrInput
Нужно форматирование текстаrich-text редактора в экосистеме пока нет
Вводится числоGrNumberInput
Длинный текст запрашивают окномGrPromptDialog с multiline

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

state

  • default
  • success
  • warning
  • danger

Размеры

  • xs
  • sm
  • md
  • lg

Состояния

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

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

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

Сцена

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

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

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

  • Ответ, за который платят частями

    SMS уходит частями по 153 символа, и каждая часть оплачивается, поэтому слот count говорит частями и тем, сколько осталось в текущей, — show-count не нужен, слот сам и есть просьба. Счётчик остаётся связан с полем через aria-describedby; maxlength держит ответ в трёх частях, а clearable стирает черновик одним движением и сообщает об этом clear.

    Reply to Ana Ribeiro by SMSReplies go to +351 912 004 118
    1 part · 47 left in this part
  • Заметки к выпуску для панели в десять строк

    autosize растит поле вместе с заметками, чтобы черновик был виден целиком, а resize="none" убирает ручку, которая бы с ним спорила. show-line-count с max-lines считает логические строки — панель в приложении режет на десяти — и никогда не обрезает текст за автора. После десятой страница ставит state="warning": он добавляет иконку и скрытую подпись — один цвет сообщением не считается.

    Shown in the app after the update, ten lines at most

    5 / 10
  • Адрес доставки и записка курьеру

    autocomplete="street-address" даёт браузеру заполнить адрес целиком, а распознанный индекс делает рамку success, когда поле оставили. После того как курьер забрал посылку, адрес — readonly: его можно выделить, и он уходит с формой, — а записка, поменьше по size и с пределом show-count, disabled и в форму не попадает.

    Only the courier sees it

    48 / 140

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

Props

PropTypeпо умолчаниюОписание
disabledboolean | undefinedfalseВыключить поле: значение не редактируется и не уходит в форму. Складывается с `GrFormField`.
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Кнопка очистки значения. Настраивается через `GrConfigProvider`.
clearLabelstring | undefinedundefinedA11y-подпись кнопки очистки.
namestring | undefinedundefinedНативный `name` — имя поля при отправке формы.
idstring | undefinedundefined`id` поля. Не задан — берётся из `GrFormField`, чтобы подпись нашла поле.
rowsnumber | undefined4Высота в строках; с `autosize` — стартовая и минимальная. По умолчанию `4`.
state"default" | "success" | "warning" | "danger" | undefined"default"Подсветка рамки по решению разработчика. `success` и `warning` добавляют иконку и скрытую подпись; `invalid` сильнее. По умолчанию `default`.
autocompletestring | undefinedundefinedНативный `autocomplete`: чем браузеру заполнять поле — например, `street-address`.

Слоты

SlotTypeОписание
count
{ length: number; maxlength?: number | undefined; remaining?{ length: number; maxlength?: number | undefined; remaining?: number | undefined; }
Своя формулировка счётчика символов вместо `12 / 60` — «осталось 48», «почти предел» и что угодно ещё. Встроенных счётчиков два, но оба фиксированы по форме, и вторая готовая формулировка первой не заменяет.

События

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Снять фокус с поля.

Типы

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

Импорт
import type { GrTextareaProps, GrTextareaEmits, GrTextareaConfigurableProps } from '@feugene/granularity/components/GrTextarea'

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

GrTextareaPropsinterface
interface GrTextareaProps {
    /** Текст поля (`v-model`). */
    modelValue: string;
    /** Подсказка в пустом поле. Подпись не заменяет: имя даёт `GrFormField` или `ariaLabel`. */
    placeholder?: string;
    /** Нативный `autocomplete`: чем браузеру заполнять поле — например, `street-address`. */
    autocomplete?: string;
    /** Выключить поле: значение не редактируется и не уходит в форму. Складывается с `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?: GrTextareaState;
    /** Нативный `name` — имя поля при отправке формы. */
    name?: string;
    /** `id` поля. Не задан — берётся из `GrFormField`, чтобы подпись нашла поле. */
    id?: string;
    /** Высота в строках; с `autosize` — стартовая и минимальная. По умолчанию `4`. */
    rows?: number;
    /** Кегль и отступы по шкале контролов. По умолчанию `md`; берётся из `GrConfigProvider`. */
    size?: GrTextareaSize;
    /** Ограничение длины + основа для счётчика символов. */
    maxlength?: number;
    /** Показывать счётчик символов (`len` или `len/maxlength`). */
    showCount?: boolean;
    /**
     * Показывать счётчик строк. Считаются **логические** строки (переводы строки), а
     * не визуальные переносы: при `autosize` число не меняется от ширины поля.
     */
    showLineCount?: boolean;
    /**
     * Ориентир по числу строк для счётчика (`3 / 10`). Ввод не ограничивает: обрезать
     * набранный текст за пользователя компонент не вправе.
     */
    maxLines?: number;
    /** Подгонять высоту под содержимое (директива `v-autosize`). */
    autosize?: boolean;
    /** Кнопка очистки значения. Настраивается через `GrConfigProvider`. */
    clearable?: boolean;
    /** A11y-подпись кнопки очистки. */
    clearLabel?: string;
    /** Ручное изменение размера пользователем. */
    resize?: GrTextareaResize;
}
GrTextareaEmitsinterface
interface GrTextareaEmits {
    /** Текст изменился (`v-model`) — на каждый ввод и при очистке. */
    (e: 'update:modelValue', value: string): void;
    /** Значение зафиксировано нативным `change` — по `blur`. */
    (e: 'change', value: string): void;
    /** Значение стёрто кнопкой очистки. */
    (e: 'clear'): void;
    /** Фокус пришёл в поле. */
    (e: 'focus', event: FocusEvent): void;
    /** Фокус ушёл из поля. */
    (e: 'blur', event: FocusEvent): void;
}

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

grTextareaConfigconst
const grTextareaConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrTextarea">;

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

GrTextareaConfigurablePropsinterface

Пропы GrTextarea, настраиваемые глобально через componentDefaults. size типизируем через GrComponentSize, а не через GrTextareaSize: последний объявлен в grTextareaStyles.ts, который тянет контекст, — вышел бы цикл модулей.

interface GrTextareaConfigurableProps {
    size: GrComponentSize;
    /** Кнопка очистки значения. */
    clearable: boolean;
}

Файлdist/types/components/GrTextarea/grTextareaStyles.d.ts

GrTextareaStatetype
type GrTextareaState = GrControlState;
GrTextareaSizetype
type GrTextareaSize = GrComponentSize;

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

grTextareaSafelistconst
const grTextareaSafelist: string[];

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

GrTextareaInstancetype
type GrTextareaInstance = ComponentExposed<typeof GrTextareaComponent>;

Доступность

Паттерн APG

—

КлавишаДействие
—Tab из поля — на кнопку очистки при clearable; Enter/Space очищает и возвращает фокус в поле

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

События
СобытиеКогда
update:modelValueна каждый ввод
changeзначение зафиксировано: нативный change (по уходу фокуса) или кнопка очистки
clearзначение стёрто кнопкой очистки (clearable)
focus, blurс объектом FocusEvent

Набор совпадает с GrInput, чтобы обёртки над контролами писались одинаково. Нативные события переизлучаются компонентом: объявленный emit уходит из $attrs, и без этого @change у потребителя перестал бы работать.

clearable повторяет анатомию GrInput: крестик в правом верхнем углу при непустом значении, скрыт при disabled/readonly, настраивается через GrConfigProvider. Поле держит место под крестик справа, пока он может появиться, — первая строка не уходит под «×».

Обёртка вокруг textarea есть всегда, а кнопка, признак состояния и счётчики (showCount, showLineCount) — её соседи по условию. Раньше обёртка появлялась вместе с ними, и <textarea> пересоздавался: стоило state стать success на лету, фокус уходил на <body>, и следующие нажатия пропадали. class и style ложатся на обёртку, остальные атрибуты — на сам <textarea> (как у GrInput, см. form-controls.md). Поле внутри — блочное: высота с соседями и без них одна.

Паритет с GrInput

size, readonly, maxlength + showCount работают так же, как в поле ввода: textarea в одной форме рядом с GrInput не должна отличаться ни кеглем, ни набором возможностей.

Счётчик связан с полем через aria-describedby — иначе «12 / 60» видно глазами, но не слышно, при том что ограничение длины и есть его смысл.

state="success" и "warning" тем же приёмом получают иконку и скрытую подпись в aria-describedby: цвет рамки как единственный носитель смысла — это WCAG 1.4.1, а ARIA-эквивалента aria-invalid у этих двух состояний нет. danger иконки не получает — его смысл уже несут aria-invalid и текст ошибки от GrFormField, — и признак гаснет, как только поле объявлено невалидным.

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

Счётчик строк

showLineCount печатает вторую подпись в том же ряду: слева строки, справа символы. Строки считаются логическими — по переводам строки, — поэтому при autosize число не зависит от ширины поля: визуальный перенос строкой не считается.

Подпись локализована и склоняется (gr.textarea.lines), а maxLines меняет её на формат 3 / 10. Ограничения ввода у него нет намеренно: обрезать набранный текст за пользователя компонент не вправе — счётчик показывает перебор (12 / 10), а решение остаётся за формой.

Оба счётчика включаются независимо и оба связаны с полем через aria-describedby.

Своя формулировка

12 / 60 — не единственный способ сказать про длину, а счётчик строк вторую формулировку не заменяет: он просто второй готовый счётчик, такой же фиксированный по форме. Слот #count отдаёт текст потребителю:

vue
<GrTextarea v-model="text" :maxlength="280">
  <template #count="{ remaining }">
    {{ remaining < 0 ? `перебор на ${-remaining}` : `осталось ${remaining}` }}
  </template>
</GrTextarea>

Слот получает length, maxlength и remaining; без maxlength последние два приходят undefined — предел не выдумывается. remaining не зажат в ноль: maxlength держит ввод с клавиатуры, но значение, пришедшее в v-model из кода, ограничение перешагивает, и «-3» там честнее нуля.

Отдельный show-count при этом не нужен — заданный слот сам по себе просьба показать счётчик. Место в ряду, связка с полем через aria-describedby и оформление остаются те же: слот меняет текст, а не устройство.

Автоподгонка высоты
vue
<GrTextarea v-model="text" autosize :rows="2" />

autosize включает директиву v-autosize, которая уже была в пакете и просто не была подключена к компоненту. rows задаёт стартовую высоту.

resize (vertical по умолчанию, none, both) управляет ручным растягиванием: вместе с autosize его обычно выключают.

Тип пропов

GrTextareaProps — объявленный interface, а не typeof props. Прежний экспорт отдавал тип разрешённых пропов: после withDefaults все поля с дефолтами становились обязательными и readonly, и попытка написать const p: GrTextareaProps = { modelValue: '' } падала на ровном месте.

Отключённое состояние

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