GrNumberInput
Берут, когда значение числовое.
Обзор
Когда брать
- значение числовое — количество, цена, процент:
v-modelдаётnumber, а не строку; - есть шаг и границы —
step,min,maxс кнопками ± и удержанием; - важна точность —
precisionвместе с разделителем и группировкой разрядов по локали; - пустое значение допустимо —
nullозначает «не заполнено», а не ноль.
Когда взять другое
| Нужно | Берите |
|---|---|
| Значение задают перетаскиванием | GrSlider |
| Значение текстовое | GrInput |
| Число — оценка звёздами | GrRating |
| Выбор из фиксированного набора чисел | GrSelect |
| Показать число, а не ввести | GrStatistic |
Разница с GrInput type="number" не косметическая: нативное числовое поле
отдаёт строку, теряет значение на неверном вводе и по-разному ведёт себя в
браузерах. Здесь модель — number | null, и null отличается от нуля.
Варианты все тона и виды — настоящим компонентом
state \ shape | box | pill |
|---|---|---|
default | ||
success | ||
warning | ||
danger |
Размеры
xssmmdlg
Состояния
обычнаяdisabledreadonlyinvalid
Матрица живая: наводите и нажимайте. Состояний наведения, фокуса и нажатия здесь нет — они за браузером, а не за пропом.
Playground контролы выводятся из API, а не пишутся руками
Загружается…
0 изменено· состояние — в адресе
<GrNumberInput
v-model="value"
controls
:min="1"
:max="50"
/>Примеры 3 у каждого — живое демо и его код
Количество в корзине
На сенсорном экране кнопки стоят по бокам —
controls-direction="horizontal".max— остаток на складе, поэтому «+» гаснет на нём, а не молча бездействует, а набранное руками число зажимается при коммите; суммы строк и итог идут за моделью.Your bag Linen shirt, sand, M€64.00 · 4 in stock€64.00Canvas tote€18.00 · 12 in stock€36.00Wool cardigan, navy, L€129.00 · last one€129.00Subtotal€229.00Предложение в немецком формате
locale="de-DE"сdecimal-separator=","показывает 1.284,50 и принимает «1284,5», а модель хранит 1284.5.use-groupingдобавляет разряды, только когда поле не правят, аprecisionокругляет при коммите, а не под курсором.Angebot AN-2026-118 · Brauhaus Weber GmbHShown and typed in German format; the model holds plain numbers Gesamt netto4.658,09 €Границы автомасштабирования, опирающиеся друг на друга
maxминимума — текущий максимум, и наоборот, поэтому кнопки гаснут ровно там, где пара пересеклась бы. Цель по CPU шагает по 5, а PageUp/PageDown — на десять шагов или десятую часть диапазона.Autoscaling · api-productioneu-west-1 · 4 instances running Between 2 and 8 instances; a new one starts when CPU stays above 65% for 3 minutes.
API порождается из пакета · разойтись не может
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
disabled | boolean | undefined | false | Выключить поле: ввод, кнопки ± и очистка недоступны. Складывается с `GrFormField`. |
readonly | boolean | undefined | false | Только для чтения: значение видно и уходит в форму, но не редактируется. |
invalid | boolean | undefined | false | Быстрый флаг невалидности; эквивалент `state='danger'` + `aria-invalid`. |
required | boolean | undefined | false | Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | Высота и кегль по шкале контролов. По умолчанию `md`; берётся из `GrConfigProvider`. |
placeholder | string | undefined | undefined | Подсказка в пустом поле. Подпись не заменяет: имя даёт `GrFormField` или `ariaLabel`. |
ariaLabel | string | undefined | undefined | Доступное имя вне `GrFormField`. |
clearable | boolean | undefined | undefined | Кнопка очистки значения. |
clearLabel | string | undefined | undefined | A11y-подпись кнопки очистки. |
name | string | undefined | undefined | Нативный `name` — имя поля при отправке формы. |
prefixMinWidth | string | undefined | undefined | Минимальная ширина префикса. По умолчанию — по ступени размера. |
prefixMaxWidth | string | undefined | undefined | Максимальная ширина префикса: содержимое шире обрезается. |
suffixMinWidth | string | undefined | undefined | Минимальная ширина суффикса. По умолчанию — по ступени размера. |
suffixMaxWidth | string | undefined | undefined | Максимальная ширина суффикса: содержимое шире обрезается. |
prefixFixed | boolean | undefined | false | Фиксированная ширина у prefix/suffix: жёсткая ширина (из `*MaxWidth` → `*MinWidth` → дефолт) + обрезка контента по краю (prefix — справа, suffix — слева). По умолчанию аддоны растягиваются под контент. |
suffixFixed | boolean | undefined | false | Жёсткая ширина суффикса — как у `prefixFixed`, только лишнее обрезается слева. |
shape | "box" | "pill" | undefined | undefined | Форма рамки. `box` — скругление шкалы контролов; `pill` — пилюля. Отступ поля и вылет степперов идут за формой: `overflow-hidden` обрезает их по внешней дуге, но встать вплотную к ней они не должны. |
max | number | undefined | undefined | Верхняя граница: применяется при коммите ввода, а не на каждом нажатии. `End` ставит её. |
id | string | undefined | undefined | `id` поля. Не задан — берётся из `GrFormField`, чтобы подпись нашла поле. |
locale | string | undefined | undefined | BCP-47 локаль для отображения значения (группировка разрядов и разделители через `Intl.NumberFormat`). Работает вместе с `useGrouping`. |
precision | number | undefined | undefined | Сколько знаков после разделителя оставлять при коммите. Не задано — без округления. |
state | "default" | "success" | "warning" | "danger" | undefined | "default" | Подсветка рамки по решению разработчика. `success` и `warning` добавляют иконку и скрытую подпись; `invalid` сильнее. По умолчанию `default`. |
autocomplete | string | undefined | undefined | Нативный `autocomplete`: чем браузеру заполнять поле. |
inputmode |
| "decimal" | Нативный `inputmode`: какую экранную клавиатуру показать. По умолчанию `decimal`. |
textAlign | GrNumberInputTextAlign | undefined | "left" | Выравнивание числа в поле. По умолчанию `left`. |
decimalSeparator | string | undefined | "." | Знак дробной части на экране. При вводе принимаются и `.`, и `,`, а в модели всегда число. По умолчанию `.`. |
step | number | undefined | 1 | Шаг кнопок ± и стрелок `↑`/`↓`; `PageUp`/`PageDown` шагают крупнее. По умолчанию `1`. |
min | number | undefined | undefined | Нижняя граница: применяется при коммите ввода, а не на каждом нажатии. `Home` ставит её. |
useGrouping | boolean | undefined | false | Группировать разряды при отображении (когда поле не в фокусе). При фокусе показывается «сырое» значение для редактирования. По умолчанию выключено. |
controls | boolean | undefined | false | Показывать кнопки +/-. |
controlsDirection | GrNumberInputControlsDirection | undefined | "vertical" | Раскладка кнопок ±: `vertical` — столбиком справа, `horizontal` — по бокам поля. По умолчанию `vertical`. |
increaseLabel | string | undefined | undefined | i18n-friendly aria-label для кнопки "увеличить". |
decreaseLabel | string | undefined | undefined | i18n-friendly aria-label для кнопки "уменьшить". |
modelValueобязательныйv-model | number | null | — | Значение поля. `null` — пусто. Незавершённый ввод («-», «1,») числом не является и в модель не попадает: пока он набирается, поле держит его во внутреннем черновике, а модель честно говорит «числа пока нет». |
Слоты
| Slot | Type | Описание |
|---|---|---|
prefix | any | Аддон слева от поля: знак валюты, иконка. |
suffix | any | Аддон справа от поля: единица измерения. |
События
| Event | Сигнатура | Описание |
|---|---|---|
update:modelValue | (event: "update:modelValue", value: number | null): void | — |
change | (event: "change", value: number | null): void | — |
clear | (event: "clear"): void | — |
focus | (event: "focus", event: FocusEvent): void | — |
blur | (event: "blur", event: FocusEvent): void | — |
Методы
| Methods | Сигнатура | Описание |
|---|---|---|
focus | () => void | Поставить фокус в поле. |
blur | () => void | Снять фокус с поля. |
Типы
Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.
import type { GrNumberInputConfigurableProps, GrNumberInputProps, GrNumberInputEmits } from '@feugene/granularity/components/GrNumberInput'Файлdist/types/components/GrNumberInput/config.d.ts
const grNumberInputConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrNumberInput">;Файлdist/types/components/GrNumberInput/defaults.d.ts
Пропы GrNumberInput, настраиваемые глобально через
<GrConfigProvider :component-defaults="{ GrNumberInput: { … } }">.
Только оформление — см. GrButton/defaults.ts.
interface GrNumberInputConfigurableProps {
size: GrNumberInputSize;
clearable: boolean;
/** Форма рамки — см. `components/shared/controlShape.ts`. */
shape: GrControlShape;
}Файлdist/types/components/GrNumberInput/safelist.d.ts
const grNumberInputSafelist: string[];Файлdist/types/components/GrNumberInput/GrNumberInput.vue.d.ts
interface GrNumberInputProps {
/**
* Значение поля. `null` — пусто.
*
* Незавершённый ввод («-», «1,») числом не является и в модель не попадает:
* пока он набирается, поле держит его во внутреннем черновике, а модель
* честно говорит «числа пока нет».
*/
modelValue: number | null;
/** Подсказка в пустом поле. Подпись не заменяет: имя даёт `GrFormField` или `ariaLabel`. */
placeholder?: string;
/** Нативный `autocomplete`: чем браузеру заполнять поле. */
autocomplete?: string;
/** Нативный `inputmode`: какую экранную клавиатуру показать. По умолчанию `decimal`. */
inputmode?: InputHTMLAttributes['inputmode'];
/** Выключить поле: ввод, кнопки ± и очистка недоступны. Складывается с `GrFormField`. */
disabled?: boolean;
/** Быстрый флаг невалидности; эквивалент `state='danger'` + `aria-invalid`. */
invalid?: boolean;
/** Только для чтения: значение видно и уходит в форму, но не редактируется. */
readonly?: boolean;
/** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */
required?: boolean;
/** Доступное имя вне `GrFormField`. */
ariaLabel?: string;
/**
* Подсветка рамки по решению разработчика. `success` и `warning` добавляют иконку
* и скрытую подпись; `invalid` сильнее. По умолчанию `default`.
*/
state?: GrNumberInputState;
/** Нативный `name` — имя поля при отправке формы. */
name?: string;
/** `id` поля. Не задан — берётся из `GrFormField`, чтобы подпись нашла поле. */
id?: string;
/** Высота и кегль по шкале контролов. По умолчанию `md`; берётся из `GrConfigProvider`. */
size?: GrNumberInputSize;
/**
* Форма рамки. `box` — скругление шкалы контролов; `pill` — пилюля.
* Отступ поля и вылет степперов идут за формой: `overflow-hidden` обрезает их
* по внешней дуге, но встать вплотную к ней они не должны.
*/
shape?: GrControlShape;
/** Выравнивание числа в поле. По умолчанию `left`. */
textAlign?: GrNumberInputTextAlign;
/**
* Знак дробной части на экране. При вводе принимаются и `.`, и `,`, а в модели
* всегда число. По умолчанию `.`.
*/
decimalSeparator?: string;
/** Шаг кнопок ± и стрелок `↑`/`↓`; `PageUp`/`PageDown` шагают крупнее. По умолчанию `1`. */
step?: number;
/** Нижняя граница: применяется при коммите ввода, а не на каждом нажатии. `Home` ставит её. */
min?: number;
/** Верхняя граница: применяется при коммите ввода, а не на каждом нажатии. `End` ставит её. */
max?: number;
/** Сколько знаков после разделителя оставлять при коммите. Не задано — без округления. */
precision?: number;
/** Кнопка очистки значения. */
clearable?: boolean;
/** A11y-подпись кнопки очистки. */
clearLabel?: string;
/**
* BCP-47 локаль для отображения значения (группировка разрядов и разделители
* через `Intl.NumberFormat`). Работает вместе с `useGrouping`.
*/
locale?: string;
/**
* Группировать разряды при отображении (когда поле не в фокусе). При фокусе
* показывается «сырое» значение для редактирования. По умолчанию выключено.
*/
useGrouping?: boolean;
/** Показывать кнопки +/-. */
controls?: boolean;
/**
* Раскладка кнопок ±: `vertical` — столбиком справа, `horizontal` — по бокам поля.
* По умолчанию `vertical`.
*/
controlsDirection?: GrNumberInputControlsDirection;
/** Минимальная ширина префикса. По умолчанию — по ступени размера. */
prefixMinWidth?: string;
/** Максимальная ширина префикса: содержимое шире обрезается. */
prefixMaxWidth?: string;
/** Минимальная ширина суффикса. По умолчанию — по ступени размера. */
suffixMinWidth?: string;
/** Максимальная ширина суффикса: содержимое шире обрезается. */
suffixMaxWidth?: string;
/**
* Фиксированная ширина у prefix/suffix: жёсткая ширина (из `*MaxWidth` →
* `*MinWidth` → дефолт) + обрезка контента по краю (prefix — справа,
* suffix — слева). По умолчанию аддоны растягиваются под контент.
*/
prefixFixed?: boolean;
/** Жёсткая ширина суффикса — как у `prefixFixed`, только лишнее обрезается слева. */
suffixFixed?: boolean;
/** i18n-friendly aria-label для кнопки "увеличить". */
increaseLabel?: string;
/** i18n-friendly aria-label для кнопки "уменьшить". */
decreaseLabel?: string;
}interface GrNumberInputEmits {
/** Новое значение (`v-model`); `null`, пока набранное не разбирается в число. */
(e: 'update:modelValue', value: number | null): void;
/**
* Значение зафиксировано: нативный `change`, шаг кнопкой или клавишей, очистка.
* Границы и точность к этому моменту уже применены.
*/
(e: 'change', value: number | null): void;
/** Поле получило фокус — нативный `focus` с `<input>`. */
(e: 'focus', event: FocusEvent): void;
/** Поле потеряло фокус — нативный `blur` с `<input>`. */
(e: 'blur', event: FocusEvent): void;
/** Значение стёрто кнопкой очистки. */
(e: 'clear'): void;
}Файлdist/types/components/GrNumberInput/grNumberInputStyles.d.ts
type GrNumberInputState = GrControlState;type GrNumberInputSize = GrComponentSize;type GrNumberInputControlsDirection = 'vertical' | 'horizontal';type GrNumberInputTextAlign = 'left' | 'center' | 'right';type NumberInputSize = GrNumberInputSize;type NumberInputControlsDirection = GrNumberInputControlsDirection;Файлdist/types/components/GrNumberInput/index.d.ts
type GrNumberInputInstance = ComponentExposed<typeof GrNumberInputComponent>;Доступность
Паттерн APG
spinbutton
Клавиши
↑ / ↓PageUp / PageDownHome / End
| Клавиша | Действие |
|---|---|
| ↑ / ↓ | шаг |
| PageUp / PageDown | крупный шаг (десять шагов либо десятая часть диапазона, что крупнее — правило GrSlider) |
| Home / End | к min/max; при readonly клавиши отдаются нативной каретке, значение не меняется |
Полный контракт пакета: docs/keyboard.md ↗
Использование из прозы библиотеки
Значение — число, ввод — черновик
v-model — number | null, где null означает «пусто». Так же, как у
GrSlider: числовой контрол отдаёт число, а не строку, которую
потребителю приходится разбирать самому.
Возражение против числовой модели было такое: незавершённый ввод («-», «1,»)
числом не является, а превращать его в NaN на каждом нажатии нельзя. Оно
снимается разделением ролей — незавершённый набор просто не обязан быть моделью:
- черновик — строка ровно в том виде, в каком её набирают. Живёт внутри поля, пока набор не завершён, и именно он показывается на экране;
- модель — число. Пока черновик не разбирается в число, модель говорит
null, а не хранит полуфабрикат.
Коммит (change, потеря фокуса, шаг кнопкой или клавишей) снимает черновик:
применяются min/max и precision, и показ снова считается от модели. Именно
поэтому precision="2" не превращает «7» в «7.00» прямо под курсором — только
по завершении ввода.
decimalSeparator перестаёт быть частью значения и остаётся тем, чем и был, —
способом показа и ввода дробной части. В модели 1,25 при
decimal-separator="," лежит как 1.25.
Кнопки ±
<GrNumberInput v-model="qty" controls :min="1" :max="10" />controls показывает кнопки, controlsDirection ставит их столбиком справа или
по бокам поля.
Кнопка гаснет на своей границе — на максимуме «+» недоступна, а не молча
бездействует. В readonly гаснут обе: кнопка, которая заведомо получит отказ,
вводит в заблуждение. Удержание кнопки шагает повторно (пауза 400 мс, затем каждые
60 мс) и останавливается на границе, при отпускании и при уходе курсора.
Финальный клик такого удержания шага не даёт — это хвост жеста, а не новое
намерение. Клавиатурная активация (Enter/Space) под это правило не подпадает
никогда: она шагает всегда, даже если жест до неё оборвался без клика.
Фокус кнопка себе не забирает: нажав Enter на «+», клавиатурный пользователь
остаётся на ней и может нажать снова. Поле фокусируется только тогда, когда шаг
пришёл от него самого — стрелками ↑/↓.
Шаг
step задаёт величину шага, precision — сколько знаков оставлять. Дробный шаг
не копит двоичную погрешность и без precision: результат округляется до
разрядности большего из операндов, поэтому три нажатия ↑ при step="0.1" дают
0.3, а не 0.30000000000000004.
PageUp/PageDown шагают крупно — по тому же правилу, что у GrSlider: десять
шагов либо десятая часть диапазона, что крупнее. Без заданных min/max
диапазона не существует, и остаются десять шагов.
Группировка и локаль
useGrouping показывает значение с разделителями разрядов, пока поле не в
фокусе: при правке группировка мешала бы. Локаль берётся из пропа locale, а
если он не задан — из i18n-адаптера пакета, так что в мультиязычном приложении
её не нужно передавать каждому полю.
При группировке отформатированное значение уходит в aria-valuetext — иначе
диктор читал бы сырое число. Без группировки атрибут не выставляется: aria-valuenow
уже несёт то же самое.
Очистка и события
clearable добавляет крестик; он не показывается при пустом значении, в
readonly и disabled. Очистка эмитит clear и возвращает фокус в поле.
Набор событий совпадает с GrInput: update:modelValue,
change, focus, blur, clear.
Состояния
state (default | success | warning | danger) задаёт оттенок рамки, invalid
форсирует красную.
success и warning вдобавок показывают иконку и скрытую подпись, которая
уезжает в aria-describedby поля: цвет рамки как единственный носитель смысла
— это WCAG 1.4.1, а ARIA-эквивалента aria-invalid у этих двух состояний нет.
danger иконки не получает — его смысл уже несут aria-invalid и текст ошибки
от GrFormField, — и признак гаснет, как только поле объявлено невалидным.
disabled гасит поле токенами --gr-disabled-*, а не
прозрачностью: opacity разбавляет выверенные на AA цвета текста.
Readonly
readonly-поле ведёт себя как текст: стрелки, PageUp/PageDown и Home/End
отдаются нативной каретке, значение не меняется ни клавишами, ни кнопками «±»
(они в этом состоянии недоступны), ни автоповтором.
Пропы этого примера