input-number —— Числовое поле ввода
Числовое поле ввода добавляет к обычному полю три вещи: шаг (прибавление/вычитание по step), кламп (не выходит за min/max) и точность (знаки после запятой). core/input-number выделяет чистые вычисления для этих трёх вещей, а UI-слой лишь подключает события кнопок увеличения и уменьшения.
Когда использовать
Заголовок раздела «Когда использовать»- В каждой платформе реализован
LhInputNumber: выбор количества, ввод суммы, размер страницы. - Не использовать для: свободного числового текста (используйте
core/input— пользователь может захотеть ввести «примерно 10 штук», то есть не строгое число); слайдерный выбор (нагляднееcore/slider).
import { incrementValue, decrementValue, shouldDisableIncrement, shouldDisableDecrement, getDisplayValue, getInputNumberAria,} from '@lh-ui/share/core/input-number'import type { LhInputNumberState } from '@lh-ui/share/core/input-number'Замысел дизайна
Заголовок раздела «Замысел дизайна»Три линии защиты в incrementValue / decrementValue:
- Начальное значение null: при
value === nullсложение идёт отstep, а вычитание от-step, а не от 0 — если пользователь ещё не ввёл значение и нажал «плюс», должно получиться «один шаг», а не «min». - Очистка точности:
precisionпропускается черезsafePrecisionизshared/sanitizeс приведением к целому в[0, 100], чтобы избежать RangeError отtoFixed(101). Эта линия защиты общая сcore/statistic. - Откат для не конечного результата: внутренний
clampSafeпри результате NaN откатывается кclamp(0, min, max)— грязные props (step: NaN) не приведут к тому, что значение NaN распространится в представление.
Проверка в shouldDisableIncrement выглядит как value !== null && value >= max: при null кнопка не отключается (нажатие даст step), а отключается только при достижении верхней границы. Этот !== null опускать нельзя, иначе кнопка «плюс» в пустом поле будет серой.
Таблица функций
Заголовок раздела «Таблица функций»| Функция | Сигнатура | Возвращаемое значение | Назначение |
|---|---|---|---|
incrementValue |
(value: number | null, step: number, min: number, max: number, precision?: number) => number |
новое значение | прибавление шага |
decrementValue |
(value: number | null, step: number, min: number, max: number, precision?: number) => number |
новое значение | вычитание шага |
shouldDisableIncrement |
(state: LhInputNumberState) => boolean |
логическое значение | disabled или достигнута верхняя граница |
shouldDisableDecrement |
(state: LhInputNumberState) => boolean |
логическое значение | disabled или достигнута нижняя граница |
getDisplayValue |
(value: number | null, _placeholder: string) => string |
строка отображения | при наличии значения приводится к String, при null — пустая строка |
clampValue |
то же, что shared/sanitize.clamp |
значение после клампа | совместимый псевдоним реэкспортируется |
Обратите внимание: второй параметр getDisplayValue _placeholder имеет префикс в виде подчёркивания и не используется в теле функции — рендеринг плейсхолдера является обязанностью UI-слоя (плейсхолдер — это «подсказка при отсутствии значения», а «отображение значения» — другое дело).
Определения типов
Заголовок раздела «Определения типов»export interface LhInputNumberState { value: number | null min: number max: number step: number disabled: boolean size: LhComponentSize controls: boolean // показывать ли кнопки увеличения/уменьшения precision: number | undefined placeholder: string}Доступность
Заголовок раздела «Доступность»getInputNumberAria выводит семантику spinbutton из APG:
getInputNumberAria({ value: 5, min: 0, max: 10, step: 1 })// { role: 'spinbutton', 'aria-valuemin': 0, 'aria-valuemax': 10, 'aria-valuenow': 5 }
getInputNumberAria({ value: null, min: 0, max: 10, step: 1 })// { role: 'spinbutton', 'aria-valuemin': 0, 'aria-valuemax': 10 } —— при null valuenow не выводитсяaria-valuenowвыводится только приvalue != null: наличиеaria-valuenow="NaN"приведёт к сбою при зачитывании программой чтения экрана.- При
disabledвыводитсяaria-disabled(а не полагаться на нативный disabled, так как часть платформ использует кастомные элементы управления). ariaValueTextвыводится какaria-valuetext— когда самого по себе числа недостаточно для выражения смысла (например, «3 дня», а не «3»), и он зачитывается вместо valuenow.
Примеры использования
Заголовок раздела «Примеры использования»import { incrementValue, shouldDisableIncrement } from '@lh-ui/share/core/input-number'
incrementValue(4, 1, 0, 10) // 5incrementValue(null, 2, 0, 10) // 2 (при null отсчёт идёт от step)incrementValue(9, 1, 0, 10) // 10 (кламп к max)incrementValue(9, 1, 0, 10, 2) // 10, причём после toFixed(2) приводится к NumberdecrementValue(null, 2, 0, 10) // -2 (до клампа -2, затем clamp к 0)
shouldDisableIncrement({ value: 10, max: 10, min: 0, step: 1, disabled: false, ... }) // trueshouldDisableIncrement({ value: null, max: 10, ... }) // false<!-- Vue3 --><div :class="['lh-input-number', `lh-input-number--${size}`]"> <button v-if="controls" class="lh-input-number__decrease" :disabled="shouldDisableDecrement(state)" aria-label="Уменьшить" @click="emit('update:modelValue', decrementValue(modelValue, step, min, max, precision))" >−</button> <input class="lh-input-number__input" v-bind="getInputNumberAria({ value: modelValue, min, max, step, disabled })" /> <button v-if="controls" class="lh-input-number__increase" :disabled="shouldDisableIncrement(state)">+</button></div>// Reactconst next = incrementValue(value, step, min, max, precision)return <button onClick={() => onChange(next)} disabled={shouldDisableIncrement(state)}>+</button>// uniapp: кнопки увеличения/уменьшения через view + @tap; после изменения шагового значения// управляемо записывается обратно, следите за гонкой между вводом с клавиатуры и кнопкамиСтили и токены
Заголовок раздела «Стили и токены»| Имя класса | Семантика |
|---|---|
.lh-input-number |
базовый класс |
.lh-input-number--{small|large} |
размер (medium по умолчанию, без модификатора) |
.lh-input-number--disabled |
отключённое состояние |
.lh-input-number__input |
средняя область ввода |
.lh-input-number__increase / __decrease |
кнопки увеличения и уменьшения |
Высота 32px (вместе с 37px у input и 32px у select образует неоднородный список из «специальной задачи по токенам высоты»).
Замечания
Заголовок раздела «Замечания»stepравен 0 или отрицателен:incrementValueне проверяет допустимость step, иstep: 0оставит значение неизменным; UI-слой должен гарантировать положительный step.- Соотношение точности и step:
precisionзаботится только о количестве отображаемых разрядов и не выравнивается по step автоматически. Приstep: 0.5, ноprecision: 0значениеincrementValue(0, 0.5, ...)отобразится как1(сначала вычисление, затем округление). - Семантика пустого значения:
value === nullиvalue === 0— разные вещи, первое означает «не заполнено».getDisplayValue(null)возвращает пустую строку, а не'0'. - Накопление погрешности float: классическая проблема
0.1 + 0.2реально существует при пошаговом изменении; путь сtoFixedчерезprecision— единственная линия защиты: при заданном precision всё стабильно, без него возможно накапливание погрешности.
@lh-ui/share/core/input-number → incrementValue( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue