---
url: https://nsh.dev/ru/elements/number-input
description: "Компонент NumberInput: число, которое вводится в поле или изменяется с помощью кнопок, клавиш со стрелками и программ чтения с экрана, с соблюдением количества десятичных знаков шага, а также значений min и max, с полем, кнопками и колонкой элементов управления, стиль которых задаёт приложение."
demo: https://nsh.dev/play/index.html#el-number-input
---

# NumberInput

Числовой ввод хранит число, которое пользователь набирает или меняет шагами: количество, цену, размер. Поведение он берёт на себя: его поле принимает число или текст на пути к числу (`-`, `1.`) и отклоняет всё остальное, включая цифры и знаки других клавиатур. Его кнопки, клавиши Up и Down, а также увеличение и уменьшение у скринридера меняют число на `step`, сохраняя его десятичные знаки, и останавливаются на `min` и `max`. При уходе из поля набранное число приводится к этим границам. Внешний вид задаёт приложение: у `NumberInput` нет собственных цветов, размеров, границ, значков или строк. Он состоит из кнопки уменьшения, поля между `prefix` и `suffix` и кнопки увеличения (или поля между ними и колонки с обеими кнопками, `controlsRight`). Приложение стилизует каждую часть и наполняет кнопки.

```rust
use nsh_base::{number_input, theme};
use nsh_core::prelude::*;

fn tickets(count: Signal<Option<f64>>) -> impl IntoElement {
    number_input()
        .value(move || count.get())
        .min(1.0)
        .max(10.0)
        .accessibility_label("Tickets")
        .on_change(move |next: &Option<f64>| count.set(*next))
        .h(px(32.0))
        .flex_row()
        .rounded(px(8.0))
        .border_1()
        .border_color(move || theme().input)
        .input_style(|s| s.flex_1().text_center().text_color(move || theme().foreground))
        .decrement_button_style(|s| s.w(px(32.0)).items_center().justify_center())
        .increment_button_style(|s| s.w(px(32.0)).items_center().justify_center())
        .decrement_button(text("−"))
        .increment_button(text("+"))
}
```

```tsx
import { mount, signal, Text } from "nsh";
import { NumberInput } from "nsh/base";

function App() {
  const tickets = signal<number | null>(2);
  return (
    <NumberInput
      value={tickets()}
      onChange={({ value }) => tickets.set(value)}
      min={1}
      max={10}
      accessibilityLabel="Tickets"
      class="h-8 flex-row overflow-hidden rounded-lg border border-input bg-field focus-within:outline-2 focus-within:outline-ring"
      inputClass="flex-1 text-center text-foreground"
      decrementButtonClass="w-8 items-center justify-center enabled:hover:bg-accent active:bg-muted disabled:opacity-40"
      incrementButtonClass="w-8 items-center justify-center enabled:hover:bg-accent active:bg-muted disabled:opacity-40"
      decrementButton={<Text>−</Text>}
      incrementButton={<Text>+</Text>}
    />
  );
}

mount(App);
```

`NumberInput` происходит из [`nsh/base`](https://nsh.dev/translations/ru/modules/nsh-base.md), крейта `nsh-base` в Rust. В Rust `number_input()` создаёт его с сеттером на каждое свойство (`.value(..)`, `.input_style(..)`), методом-слушателем на каждое событие (`.on_change(..)`), типизированными методами стиля для самого ввода и `.prefix(..)` / `.suffix(..)` для того, что находится вокруг поля. Его значение имеет тип `Option<f64>`.

<a id="value"></a>

## Значение

Значение — это число или `null` для пустого поля. Приложение владеет им через `value` и устанавливает то значение, о котором сообщает `onChange`, как и для любого элемента управления `nsh/base`. Либо оно оставляет значение самому вводу с помощью `defaultValue` и всё равно узнаёт об изменениях.

Текст поля и значение разделены там, где это необходимо, чтобы пользователь мог печатать свободно:

- `onChange` сообщает число, которое читается из текста, всякий раз, когда это число меняется: `null`, пока текст не читается как число (пусто, `-`, `.`). Набор `1.` после `1` или `1.50` после `1.5` ничего не сообщает.
- Текст остаётся таким, как его набрал пользователь, пока он читается как значение, которое хранит ввод. Поэтому управляемый ввод, который возвращает каждое сообщённое значение, сохраняет `1.` и `2.50`. Значение, которое задало приложение и о котором ввод не сообщал, показывается в поле как самый короткий текст, читающийся как это значение (`0.3`, `12`).
- Во время набора число может выходить за `min` и `max` (1 на пути к 15 при `min={10}`) и сообщается как набрано. Когда поле покидают или нажимают Return / Enter, число приводится к диапазону, и об этом сообщается. Текст, который не читается как число, остаётся как есть.
- Шаг (кнопка, клавиша, действие скринридера) запрашивает следующее значение через `onChange`. При управляемом значении поле меняется, когда приложение возвращает его, сохраняя десятичные знаки шага (`99.5` вверх до `max={100}` показывает `100.0`). Поэтому приложение, отклонившее шаг, оставляет поле на месте. Отклонённая приложением правка остаётся в поле, пока оно редактируется, и исчезает, когда поле покидают.

```tsx
const price = signal<number | null>(19.95);

<NumberInput
  value={price()}
  onChange={({ value }) => price.set(value)}
  onSubmit={({ value }) => save(value)}
  min={0}
  step={0.05}
  placeholder="Price"
  accessibilityLabel="Price"
/>;
```

Поле принимает:

- Цифры, одну десятичную точку и знак в начале (`+` или `-`). Буква, вторая точка или второй знак, а также знак в другом месте отклоняются, и поле возвращается к прежнему тексту.
- Полноширинные цифры, знаки и точку, знак минус (U+2212) и идеографическую точку (`。`), которые читаются как их ASCII-формы, как их набирают китайские и японские методы ввода.
- Запятую как десятичную точку, пока в тексте нет точки (`3,25` — это 3.25), как её набирает десятичная клавиша клавиатур многих языков.

<a id="props"></a>

## Свойства

| Свойство | Rust | Тип | По умолчанию | Описание |
|---|---|---|---|---|
| `value` | `.value(..)` | `number \| null` | нет | Значение (управляемое): число или `null` для пустого поля. Приложение устанавливает то значение, о котором сообщает `onChange`. |
| `defaultValue` | `.default_value(..)` | `number \| null` | нет | Начальное значение, пока `value` не задано; тогда ввод хранит собственное значение. |
| `min` | `.min(..)` | `number` | нет | Наименьшее значение: шаги останавливаются на нём, а набранное значение ниже него приводится к нему при уходе из поля. |
| `max` | `.max(..)` | `number` | нет | Наибольшее значение, как `min` сверху; значение ниже `min` считается `min`. |
| `step` | `.step(..)` | `number` | `1` | На сколько меняет значение шаг (Page Up / Page Down: десять шагов); значение, не являющееся положительным числом, считается `1`. |
| `disabled` | `.disabled(..)` | `boolean` | `false` | Без набора, шагов и фокуса; классы `disabled:` применяются к вводу и его частям. |
| `readonly` | `.readonly(..)` | `boolean` | `false` | Текст можно выделять и копировать, но нельзя менять ни набором, ни шагами; кнопки отключены. |
| `placeholder` | `.placeholder(..)` | `string` | нет | Показывается, пока поле пусто. |
| `accessibilityLabel` | `.accessibility_label(..)` | `string` | нет | Что скринридеры произносят для поля, когда его не называет видимая метка. |
| `controlsRight` | `.controls_right(..)` | `boolean` | `false` | Обе кнопки в колонке справа от поля, увеличение над уменьшением. Читается при создании ввода. |
| `inputClass`, `inputStyle` | `.input_style(\|s\| ..)` | `string`, `StyleProp<InputStyle>` | нет | Поле: его рамка и текст (`color`, `fontSize`, `textAlign`, `placeholderColor`, `caretColor`). |
| `decrementButtonClass`, `decrementButtonStyle` | `.decrement_button_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Кнопка уменьшения. |
| `incrementButtonClass`, `incrementButtonStyle` | `.increment_button_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Кнопка увеличения. |
| `decrementButton`, `incrementButton` | `.decrement_button(..)`, `.increment_button(..)` | `JSX.Element` | нет | Содержимое кнопок: знак, значок. |
| `controlsClass`, `controlsStyle` | `.controls_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Колонка, в которой лежат обе кнопки при `controlsRight`. |
| `prefix` | `.prefix(..)` | `JSX.Element` | нет | Что стоит перед полем, после левой кнопки: знак валюты, значок. |
| `suffix` | `.suffix(..)` | `JSX.Element` | нет | Что следует за полем, перед правыми кнопками: единица измерения, валюта. |
| `class`, `style` | методы стиля | `string`, `StyleProp<ViewStyle>` | нет | Сам ввод: строка вокруг поля и кнопок. |

<a id="events"></a>

## События

| Событие | Rust | Данные | Когда |
|---|---|---|---|
| `onChange` | `.on_change(\|value: &Option<f64>\| ..)` | `{ value: number \| null }` | Число, которое читается из поля, изменилось (набор, шаг, увеличение или уменьшение скринридера, уход из поля со значением вне диапазона). |
| `onSubmit` | `.on_submit(\|value: &Option<f64>\| ..)` | `{ value: number \| null }` | Return / Enter в поле: значение, приведённое к `min` и `max`. |
| `onFocus` | `.on_focus(..)` | нет | Поле получило фокус клавиатуры. |
| `onBlur` | `.on_blur(..)` | нет | Поле потеряло фокус клавиатуры (сначала его значение приводится к `min` и `max`). |

<a id="parts"></a>

## Части

| Часть | Где находится | Стилизуется через |
|---|---|---|
| Ввод | | `class`, `style` |
| Кнопка уменьшения | внутри ввода, первая (при `controlsRight`: в колонке кнопок, вторая) | `decrementButtonClass`, `decrementButtonStyle`; её содержимое `decrementButton` |
| Поле | внутри ввода | `inputClass`, `inputStyle` |
| Префикс | внутри ввода, перед полем | собственные элементы (`prefix`) |
| Суффикс | внутри ввода, после поля | собственные элементы (`suffix`) |
| Кнопка увеличения | внутри ввода, последняя (при `controlsRight`: в колонке кнопок, первая) | `incrementButtonClass`, `incrementButtonStyle`; её содержимое `incrementButton` |
| Колонка кнопок | внутри ввода, последняя, при `controlsRight` | `controlsClass`, `controlsStyle` |

Ввод — это строка, которую раскладывает приложение: `flex-row` на `class` и `flex-1` на поле, чтобы оно заняло место, оставшееся от кнопок. Состояния, которые показывают части:

- Ввод: `focus-within:`, пока его поле редактируется (кольцо вокруг всего ввода), `disabled:`.
- Поле: `focus:`, пока оно редактируется, `disabled:`.
- Кнопка: `hover:` под указателем, `active:` при нажатии, `disabled:`, пока ввод отключён или доступен только для чтения, и там, где шаг ничего не изменит (уменьшение на `min`, увеличение на `max`). Помещайте наведение за `enabled:` (`enabled:hover:bg-accent`), чтобы отключённая кнопка не подсвечивалась.

Анимацию состояний выбирает приложение (`transition-colors`), а `motion-reduce:` оставляет её неподвижной для тех, кто просит меньше движения.

<a id="keyboard"></a>

## Клавиатура

Пока поле редактируется, ввод обрабатывает клавиши spin button из WAI-ARIA, которые повторяются при удержании:

| Клавиша | Действие |
|---|---|
| Up | Один шаг вверх. |
| Down | Один шаг вниз. |
| Page Up, Page Down | Десять шагов вверх или вниз. |

Home и End остаются за полем: они двигают курсор в тексте. Кнопки не являются остановками фокуса клавиатуры: Tab переходит от поля к тому, что следует за вводом, а нажатие на кнопку оставляет фокус в поле (на десктопах нажатие переносит фокус в поле, если он был в другом месте; на сенсорных экранах фокус остаётся где был, поэтому клавиатура не появляется).

<a id="accessibility"></a>

## Доступность

- Поле является spin button (`"spinbutton"`) со своей меткой, значением (число, которое читается из текста, между `min` и `max`) и текстом.
- Действия увеличения и уменьшения у скринридера (смахивание вверх и вниз в VoiceOver) меняют значение так же, как Up и Down.
- Кнопки скрыты от скринридеров, как и у числового поля браузера: меняют значение собственные действия увеличения и уменьшения поля.
- Пока `disabled`, скринридеры сообщают, что поле неактивно.

О метках, ролях и действиях в целом см. [Доступность](https://nsh.dev/translations/ru/docs/accessibility.md).

<a id="platform-notes"></a>

## Заметки по платформам

- Поле — это текстовое поле платформы ([Input](https://nsh.dev/translations/ru/elements/input.md)): клавиатура, курсор, выделение, копирование и вставка, методы ввода — системные. Телефоны показывают десятичную клавиатуру со знаком, когда `min` меньше 0 или не задан.
- Каждая платформа рисует то, что стилизует приложение. Клавиши Up / Down и Page Up / Page Down доходят до ввода с аппаратной клавиатуры на любой платформе.
