{
  "name": "NumberInput",
  "source": "src/components/NumberInput.tsx",
  "propsType": "NumberInputProps",
  "description": "NumberInput — a quantity, with formatting, bounds and steps.\n\nNOT `<input type=\"number\">`, and the list of reasons is the component:\n\n  It scrolls. A focused `type=\"number\"` changes value under the mouse wheel,\n  so a user scrolling the page past a price field edits the price. The wheel\n  is OFF here by default (`isWheelDisabled`), and a caller has to ask for it.\n\n  It cannot format. \"1,234.50\", \"€12\", \"15%\" are text to the browser's number\n  input, which rejects them. `formatOptions` takes `Intl.NumberFormatOptions`,\n  so currency, percent and units display and parse in the user's locale —\n  including \"1.234,50\" for a German user.\n\n  It is not a number to the browser's validation either. `type=\"text\"` with\n  the right `inputMode` gets the numeric keyboard on a phone without\n  Firefox letting letters in and Chrome silently reporting an empty value.\n\nEMPTY IS `null`, NOT `NaN`. React Aria reports an empty field as `NaN`, and\n`NaN === NaN` is false — so every \"is it empty\" check a caller writes is\nwrong. This converts at the boundary, both ways.\n\n− AND + SIT SIDE BY SIDE, NOT STACKED. Stacked chevrons split the field's\nheight in two, which puts a Small field's targets at 16px — under the 24px\nWCAG 2.5.8 asks. Side by side, every size clears it. They are out of the tab\norder, as React Aria sets them: the arrow keys do the same job from the field,\nand two extra tab stops per number on a form is a tax on every keyboard user.\n\nThe box is Input's, class for class, so every state — hover, focus, invalid,\nread-only, disabled — is Input's and cannot drift from it.",
  "import": "import { NumberInput } from 'ionbase-ui';",
  "status": "stable",
  "summary": "A quantity field: formats and parses in the user's locale, clamps to bounds, steps with − and + or the arrow keys, and never changes under the scroll wheel unless asked.",
  "useWhen": [
    "the value is a quantity someone might add to or compare — a price, a count, a percentage, a duration",
    "the value has bounds or a step the field should enforce",
    "the value needs formatting — currency, percent, units — in the user's locale"
  ],
  "useInstead": [
    {
      "when": "the value is made of digits but is not a quantity — a phone number, postcode, card number, account or order ID",
      "use": "Input",
      "why": "a number field strips leading zeros, adds grouping separators and offers + and −, all of which corrupt an identifier. Nobody increments a postcode"
    },
    {
      "when": "the value is a phone number",
      "use": "PhoneInput"
    },
    {
      "when": "a rough value within a range matters more than an exact one — volume, a zoom level",
      "use": "Slider",
      "why": "a stepper that takes forty presses to cross the range is the wrong control; a track shows where the value sits"
    }
  ],
  "variants": {
    "size": {
      "sm": {
        "use": "dense forms and table cells"
      },
      "md": {
        "use": "the default"
      },
      "lg": {
        "use": "a form where this value is the main input"
      }
    }
  },
  "a11y": {
    "requires": [
      "`label` or `aria-label` — the accessible name. A unit shown by `formatOptions` is not a name"
    ],
    "guarantees": [
      "Arrow Up and Down step the value from the field; Page Up and Down step by ten; Home and End go to the bounds",
      "the − and + buttons are named and out of the tab order, because the arrow keys already do their job from the field",
      "every size's step buttons are at least 24px square — WCAG 2.5.8",
      "the field is `type=\"text\"` with the matching `inputMode`, so phones show a numeric keyboard without the native number input's parsing quirks"
    ],
    "notes": [
      "the scroll wheel does not change the value unless `isWheelDisabled={false}` — a focused number input edited by scrolling past it is the classic bug",
      "`onChange` fires on commit — blur, Enter, a step — not per keystroke. `null` means empty; `NaN` never reaches the caller"
    ]
  },
  "antiPatterns": [
    {
      "dont": "<NumberInput label=\"Postcode\" />",
      "do": "<Input label=\"Postcode\" inputMode=\"numeric\" />",
      "why": "\"02134\" becomes 2,134 — leading zero gone, separator added, and a + button nobody should press"
    },
    {
      "dont": "<Input type=\"number\" />",
      "do": "<NumberInput />",
      "why": "the native number input changes under the scroll wheel, cannot show currency or grouping, and reports an empty string for input it cannot parse"
    },
    {
      "dont": "a unit typed into the label and the value — label=\"Price ($)\" with a \"$\" typed in",
      "do": "formatOptions={{ style: 'currency', currency: 'USD' }}",
      "why": "formatOptions displays and parses the unit in the user's locale; a hand-typed symbol is text the parser has to reject"
    },
    {
      "dont": "checking `Number.isNaN(value)` for empty",
      "do": "`value === null`",
      "why": "this component converts the hook's NaN to null at the boundary, so NaN never arrives"
    }
  ],
  "stylesheet": "src/styles/number-input.css",
  "tokens": [
    "--icon-default",
    "--icon-disabled",
    "--icon-secondary",
    "--ion-duration-base",
    "--ion-ease-out",
    "--ion-input-height",
    "--ion-input-icon-size",
    "--radius-sm",
    "--spacing-2",
    "--spacing-4",
    "--spacing-8",
    "--surface-default",
    "--surface-hover",
    "--surface-pressed"
  ],
  "props": {
    "value": {
      "type": "number | null | undefined",
      "required": false,
      "origin": "own",
      "description": "Controlled value. `null` is empty — never `NaN`, which is what the\nunderlying hook uses and which fails every `===` check a caller writes."
    },
    "defaultValue": {
      "type": "number | null | undefined",
      "required": false,
      "origin": "own",
      "description": "Uncontrolled starting value. `null` for empty."
    },
    "onChange": {
      "type": "((value: number | null) => void) | undefined",
      "required": false,
      "origin": "own",
      "description": "Fires when the value is committed — on blur, Enter, a step button or an\narrow key — not on every keystroke. `null` when the field was cleared."
    },
    "size": {
      "type": "NumberInputSize | undefined",
      "required": false,
      "origin": "own",
      "description": "Matches Input's `size`: Small, Medium, Large.",
      "values": [
        "sm",
        "md",
        "lg"
      ],
      "default": "md"
    },
    "showStepper": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own",
      "description": "The − and + buttons. On by default; turn off for a dense table cell where\nthe arrow keys are enough.",
      "default": true
    },
    "className": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Class names for the control box (`.ion-input`)."
    },
    "wrapperClassName": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Class names for the `.ion-field` wrapper when a label or helper is shown."
    },
    "autoFocus": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "aria",
      "description": "Whether the element should receive focus on render."
    },
    "id": {
      "type": "string | undefined",
      "required": false,
      "origin": "aria",
      "description": "The element's unique identifier. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id)."
    },
    "aria-describedby": {
      "type": "string | undefined",
      "required": false,
      "origin": "aria",
      "description": "Identifies the element (or elements) that describes the object."
    },
    "aria-details": {
      "type": "string | undefined",
      "required": false,
      "origin": "aria",
      "description": "Identifies the element (or elements) that provide a detailed, extended description for the\nobject."
    },
    "aria-label": {
      "type": "string | undefined",
      "required": false,
      "origin": "aria",
      "description": "Defines a string value that labels the current element."
    },
    "aria-labelledby": {
      "type": "string | undefined",
      "required": false,
      "origin": "aria",
      "description": "Identifies the element (or elements) that labels the current element."
    },
    "onCopy": {
      "type": "ClipboardEventHandler<HTMLInputElement> | undefined",
      "required": false,
      "origin": "aria",
      "description": "Handler that is called when the user copies text. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/oncopy)."
    },
    "onCut": {
      "type": "ClipboardEventHandler<HTMLInputElement> | undefined",
      "required": false,
      "origin": "aria",
      "description": "Handler that is called when the user cuts text. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/oncut)."
    },
    "onPaste": {
      "type": "ClipboardEventHandler<HTMLInputElement> | undefined",
      "required": false,
      "origin": "aria",
      "description": "Handler that is called when the user pastes text. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/onpaste)."
    },
    "onCompositionEnd": {
      "type": "CompositionEventHandler<HTMLInputElement> | undefined",
      "required": false,
      "origin": "aria",
      "description": "Handler that is called when a text composition system completes or cancels the current text\ncomposition session. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionend_event)."
    },
    "onCompositionStart": {
      "type": "CompositionEventHandler<HTMLInputElement> | undefined",
      "required": false,
      "origin": "aria",
      "description": "Handler that is called when a text composition system starts a new text composition session.\nSee [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionstart_event)."
    },
    "onCompositionUpdate": {
      "type": "CompositionEventHandler<HTMLInputElement> | undefined",
      "required": false,
      "origin": "aria",
      "description": "Handler that is called when a new character is received in the current text composition\nsession. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionupdate_event)."
    },
    "onFocus": {
      "type": "((e: FocusEvent<Element, Element>) => void) | undefined",
      "required": false,
      "origin": "aria",
      "description": "Handler that is called when the element receives focus."
    },
    "onBlur": {
      "type": "((e: FocusEvent<Element, Element>) => void) | undefined",
      "required": false,
      "origin": "aria",
      "description": "Handler that is called when the element loses focus."
    },
    "onBeforeInput": {
      "type": "FormEventHandler<HTMLInputElement> | undefined",
      "required": false,
      "origin": "aria",
      "description": "Handler that is called when the input value is about to be modified. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/beforeinput_event)."
    },
    "onInput": {
      "type": "FormEventHandler<HTMLInputElement> | undefined",
      "required": false,
      "origin": "aria",
      "description": "Handler that is called when the input value is modified. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event)."
    },
    "onKeyDown": {
      "type": "((e: KeyboardEvent) => void) | undefined",
      "required": false,
      "origin": "aria",
      "description": "Handler that is called when a key is pressed."
    },
    "onKeyUp": {
      "type": "((e: KeyboardEvent) => void) | undefined",
      "required": false,
      "origin": "aria",
      "description": "Handler that is called when a key is released."
    },
    "onSelect": {
      "type": "ReactEventHandler<HTMLInputElement> | undefined",
      "required": false,
      "origin": "aria",
      "description": "Handler that is called when text in the input is selected. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/select_event)."
    },
    "step": {
      "type": "number | undefined",
      "required": false,
      "origin": "aria",
      "description": "The amount that the input value changes with each increment or decrement \"tick\"."
    },
    "placeholder": {
      "type": "string | undefined",
      "required": false,
      "origin": "aria",
      "description": "Temporary text that occupies the text input when it is empty."
    },
    "isDisabled": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "aria",
      "description": "Whether the input is disabled."
    },
    "isReadOnly": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "aria",
      "description": "Whether the input can be selected but not changed by the user."
    },
    "isRequired": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "aria",
      "description": "Whether user input is required on the input before form submission."
    },
    "isInvalid": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "aria",
      "description": "Whether the input value is invalid."
    },
    "validationState": {
      "type": "ValidationState | undefined",
      "required": false,
      "origin": "aria",
      "values": [
        "valid",
        "invalid"
      ],
      "tags": {
        "deprecated": "Use `isInvalid` instead."
      }
    },
    "validationBehavior": {
      "type": "\"aria\" | \"native\" | undefined",
      "required": false,
      "origin": "aria",
      "description": "Whether to use native HTML form validation to prevent form submission\nwhen the value is missing or invalid, or mark the field as required\nor invalid via ARIA.",
      "values": [
        "aria",
        "native"
      ],
      "tags": {
        "default": "'aria'"
      }
    },
    "validate": {
      "type": "((value: number) => ValidationError | true | null | undefined) | undefined",
      "required": false,
      "origin": "aria",
      "description": "A function that returns an error message if a given value is invalid.\nValidation errors are displayed to the user when the form is submitted\nif `validationBehavior=\"native\"`. For realtime validation, use the `isInvalid`\nprop instead."
    },
    "description": {
      "type": "ReactNode",
      "required": false,
      "origin": "aria",
      "description": "A description for the field. Provides a hint such as specific requirements for what to choose."
    },
    "errorMessage": {
      "type": "ReactNode | ((v: ValidationResult) => ReactNode)",
      "required": false,
      "origin": "aria",
      "description": "An error message for the field."
    },
    "onFocusChange": {
      "type": "((isFocused: boolean) => void) | undefined",
      "required": false,
      "origin": "aria",
      "description": "Handler that is called when the element's focus status changes."
    },
    "label": {
      "type": "ReactNode",
      "required": false,
      "origin": "aria",
      "description": "The content to display as the label."
    },
    "decrementAriaLabel": {
      "type": "string | undefined",
      "required": false,
      "origin": "aria",
      "description": "A custom aria-label for the decrement button. If not provided, the localized string \"Decrement\"\nis used."
    },
    "incrementAriaLabel": {
      "type": "string | undefined",
      "required": false,
      "origin": "aria",
      "description": "A custom aria-label for the increment button. If not provided, the localized string \"Increment\"\nis used."
    },
    "isWheelDisabled": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "aria",
      "description": "Enables or disables changing the value with scroll.",
      "default": true
    },
    "formatOptions": {
      "type": "Intl.NumberFormatOptions | undefined",
      "required": false,
      "origin": "other",
      "description": "Formatting options for the value displayed in the number field.\nThis also affects what characters are allowed to be typed by the user."
    },
    "commitBehavior": {
      "type": "\"validate\" | \"snap\" | undefined",
      "required": false,
      "origin": "other",
      "description": "Controls the behavior of the number field when the user blurs the field after editing. 'snap'\nwill clamp the value to the min/max values, and snap to the nearest step value. 'validate' will\nnot clamp the value, and will validate that the value is within the min/max range and on a\nvalid step.",
      "values": [
        "validate",
        "snap"
      ],
      "tags": {
        "default": "'snap'"
      }
    },
    "minValue": {
      "type": "number | undefined",
      "required": false,
      "origin": "aria",
      "description": "The smallest value allowed for the input."
    },
    "maxValue": {
      "type": "number | undefined",
      "required": false,
      "origin": "aria",
      "description": "The largest value allowed for the input."
    }
  },
  "propCounts": {
    "own": 7,
    "aria": 37,
    "dom": 278,
    "other": 2
  }
}
