GrNumberInput

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

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

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

Обзор

Когда брать

  • значение числовое — количество, цена, процент: v-model даёт number, а не строку;
  • есть шаг и границы — step, min, max с кнопками ± и удержанием;
  • важна точность — precision вместе с разделителем и группировкой разрядов по локали;
  • пустое значение допустимо — null означает «не заполнено», а не ноль.

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

НужноБерите
Значение задают перетаскиваниемGrSlider
Значение текстовоеGrInput
Число — оценка звёздамиGrRating
Выбор из фиксированного набора чиселGrSelect
Показать число, а не ввестиGrStatistic

Разница с GrInput type="number" не косметическая: нативное числовое поле отдаёт строку, теряет значение на неверном вводе и по-разному ведёт себя в браузерах. Здесь модель — number | null, и null отличается от нуля.

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

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

Размеры

  • xs
  • sm
  • md
  • lg

Состояния

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

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

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

Сцена

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

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

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

  • Количество в корзине

    На сенсорном экране кнопки стоят по бокам — controls-direction="horizontal". max — остаток на складе, поэтому «+» гаснет на нём, а не молча бездействует, а набранное руками число зажимается при коммите; суммы строк и итог идут за моделью.

    Your bag
    Linen shirt, sand, M€64.00 · 4 in stock
    €64.00
    Canvas tote€18.00 · 12 in stock
    €36.00
    Wool cardigan, navy, L€129.00 · last one
    €129.00
    Subtotal€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

PropTypeпо умолчаниюОписание
disabledboolean | undefinedfalseВыключить поле: ввод, кнопки ± и очистка недоступны. Складывается с `GrFormField`.
readonlyboolean | undefinedfalseТолько для чтения: значение видно и уходит в форму, но не редактируется.
invalidboolean | undefinedfalseБыстрый флаг невалидности; эквивалент `state='danger'` + `aria-invalid`.
requiredboolean | undefinedfalseОбязательное поле (`aria-required`). Складывается с `required` у `GrFormField`.
size"xs" | "sm" | "md" | "lg" | undefinedundefinedВысота и кегль по шкале контролов. По умолчанию `md`; берётся из `GrConfigProvider`.
placeholderstring | undefinedundefinedПодсказка в пустом поле. Подпись не заменяет: имя даёт `GrFormField` или `ariaLabel`.
ariaLabelstring | undefinedundefinedДоступное имя вне `GrFormField`.
clearableboolean | undefinedundefinedКнопка очистки значения.
clearLabelstring | undefinedundefinedA11y-подпись кнопки очистки.
namestring | undefinedundefinedНативный `name` — имя поля при отправке формы.
prefixMinWidthstring | undefinedundefinedМинимальная ширина префикса. По умолчанию — по ступени размера.
prefixMaxWidthstring | undefinedundefinedМаксимальная ширина префикса: содержимое шире обрезается.
suffixMinWidthstring | undefinedundefinedМинимальная ширина суффикса. По умолчанию — по ступени размера.
suffixMaxWidthstring | undefinedundefinedМаксимальная ширина суффикса: содержимое шире обрезается.

Слоты

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

События

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

grNumberInputConfigconst
const grNumberInputConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrNumberInput">;

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

GrNumberInputConfigurablePropsinterface

Пропы 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

grNumberInputSafelistconst
const grNumberInputSafelist: string[];

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

GrNumberInputPropsinterface
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;
}
GrNumberInputEmitsinterface
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

GrNumberInputStatetype
type GrNumberInputState = GrControlState;
GrNumberInputSizetype
type GrNumberInputSize = GrComponentSize;
GrNumberInputControlsDirectiontype
type GrNumberInputControlsDirection = 'vertical' | 'horizontal';
GrNumberInputTextAligntype
type GrNumberInputTextAlign = 'left' | 'center' | 'right';
NumberInputSizetype
type NumberInputSize = GrNumberInputSize;
NumberInputControlsDirectiontype
type NumberInputControlsDirection = GrNumberInputControlsDirection;

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

GrNumberInputInstancetype
type GrNumberInputInstance = ComponentExposed<typeof GrNumberInputComponent>;

Доступность

Паттерн APG

spinbutton

Клавиши

↑ / ↓PageUp / PageDownHome / End

КлавишаДействие
↑ / ↓шаг
PageUp / PageDownкрупный шаг (десять шагов либо десятая часть диапазона, что крупнее — правило GrSlider)
Home / Endк min/max; при readonly клавиши отдаются нативной каретке, значение не меняется

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

Значение — число, ввод — черновик

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.

Кнопки ±
vue
<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 отдаются нативной каретке, значение не меняется ни клавишами, ни кнопками «±» (они в этом состоянии недоступны), ни автоповтором.