{
  "name": "NumberInput",
  "category": "Forms",
  "subcategory": "Inputs",
  "description": "Numeric input with stepper buttons and locale formatting.",
  "thumbnail": "/preview-cards/number-input.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/NumberInput/NumberInput.razor"
  ],
  "dependencies": [],
  "packageDependencies": [
    "Blazicons.Lucide"
  ],
  "cssVars": [
    "--color-background",
    "--color-border",
    "--color-destructive",
    "--color-foreground",
    "--color-muted",
    "--color-muted-foreground",
    "--radius"
  ],
  "api": {
    "name": "NumberInput",
    "category": "Forms",
    "subcategory": "Inputs",
    "description": "Numeric input with stepper buttons and locale formatting.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/NumberInput/NumberInput.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [],
    "parameters": [
      {
        "name": "FormField",
        "type": "FormField.FormFieldContext?",
        "default": null,
        "description": null,
        "isCascading": true,
        "captureUnmatched": false
      },
      {
        "name": "Value",
        "type": "double?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ValueChanged",
        "type": "EventCallback<double?>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Min",
        "type": "double?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Max",
        "type": "double?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Step",
        "type": "double",
        "default": "1",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Precision",
        "type": "int?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Disabled",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "UseMouseWheel",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Prefix",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Suffix",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Culture",
        "type": "System.Globalization.CultureInfo?",
        "default": null,
        "description": "Culture used to parse typed input. Display of the value inside the native &lt;input type=\"number\"&gt; element uses InvariantCulture because HTML5 requires period-as-decimal — browsers will not accept a comma decimal in this input type. Leave null to honour ASP.NET request localization for parsing (lets a de-DE user paste 1,5 and have it round-trip to 1.5).",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Required",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Invalid",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ErrorText",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "HelperText",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Label",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Name",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Class",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "AdditionalAttributes",
        "type": "Dictionary<string, object>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": true
      }
    ],
    "events": [
      {
        "name": "ValueChanged",
        "type": "EventCallback<double?>",
        "description": null
      }
    ],
    "enums": [],
    "records": [],
    "cssVars": [
      "--color-background",
      "--color-border",
      "--color-destructive",
      "--color-foreground",
      "--color-muted",
      "--color-muted-foreground",
      "--radius"
    ],
    "examples": [
      {
        "title": "Basic",
        "code": "<NumberInput @bind-Value=\"_value\" />"
      },
      {
        "title": "Min / Max / Step",
        "code": "<NumberInput @bind-Value=\"_value\" Min=\"0\" Max=\"100\" Step=\"5\" />"
      },
      {
        "title": "Precision",
        "code": "<NumberInput @bind-Value=\"_value\" Step=\"0.01\" Precision=\"2\" />"
      },
      {
        "title": "Disabled",
        "code": "<NumberInput Value=\"42\" Disabled=\"true\" />"
      }
    ],
    "subComponents": {},
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "number-input",
  "docsUrl": "https://lumeo.nativ.sh/components/number-input",
  "sourceUrl": [
    {
      "file": "UI/NumberInput/NumberInput.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/NumberInput/NumberInput.razor"
    }
  ],
  "source": [
    {
      "path": "UI/NumberInput/NumberInput.razor",
      "content": "@namespace Lumeo\n\n@if (Label is not null && !InsideFormField)\n{\n    <label class=\"block text-sm font-medium mb-1.5\">\n        @Label\n        @if (Required)\n        {\n            <span class=\"text-destructive ml-0.5\">*</span>\n        }\n    </label>\n}\n\n<div class=\"@CssClass\" @attributes=\"AdditionalAttributes\">\n    <button type=\"button\"\n            class=\"@($\"inline-flex h-9 w-9 items-center justify-center rounded-l-md border border-r-0 {(EffectiveInvalid ? \"border-destructive\" : \"border-border/60\")} bg-muted text-muted-foreground transition-colors hover:bg-muted/80 disabled:pointer-events-none disabled:opacity-50\")\"\n            disabled=\"@(Disabled || IsAtMin)\"\n            @onclick=\"Decrement\"\n            aria-label=\"@L[\"NumberInput.Decrease\"]\">\n        <Blazicon Svg=\"Lucide.Minus\" class=\"h-4 w-4\" />\n    </button>\n    <div class=\"@($\"relative inline-flex items-center h-9 border {(EffectiveInvalid ? \"border-destructive\" : \"border-border/60\")} bg-background\")\">\n        @if (!string.IsNullOrEmpty(Prefix))\n        {\n            <span class=\"pl-2 text-sm text-muted-foreground select-none\">@Prefix</span>\n        }\n        <input type=\"number\"\n               name=\"@Name\"\n               aria-required=\"@Required.ToString().ToLowerInvariant()\"\n               aria-invalid=\"@EffectiveInvalid.ToString().ToLowerInvariant()\"\n               class=\"h-9 w-20 bg-transparent @(string.IsNullOrEmpty(Prefix) && string.IsNullOrEmpty(Suffix) ? \"px-3\" : \"px-1\") text-center text-sm text-foreground outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none\"\n               value=\"@DisplayValue\"\n               disabled=\"@Disabled\"\n               min=\"@Min\"\n               max=\"@Max\"\n               step=\"@Step\"\n               @onchange=\"OnInputChange\"\n               @onkeydown=\"HandleKeyDown\"\n               @onwheel=\"HandleWheel\"\n               @onwheel:preventDefault=\"@UseMouseWheel\" />\n        @if (!string.IsNullOrEmpty(Suffix))\n        {\n            <span class=\"pr-2 text-sm text-muted-foreground select-none\">@Suffix</span>\n        }\n    </div>\n    <button type=\"button\"\n            class=\"@($\"inline-flex h-9 w-9 items-center justify-center rounded-r-md border border-l-0 {(EffectiveInvalid ? \"border-destructive\" : \"border-border/60\")} bg-muted text-muted-foreground transition-colors hover:bg-muted/80 disabled:pointer-events-none disabled:opacity-50\")\"\n            disabled=\"@(Disabled || IsAtMax)\"\n            @onclick=\"Increment\"\n            aria-label=\"@L[\"NumberInput.Increase\"]\">\n        <Blazicon Svg=\"Lucide.Plus\" class=\"h-4 w-4\" />\n    </button>\n</div>\n\n@if (!InsideFormField)\n{\n    @if (EffectiveInvalid && !string.IsNullOrEmpty(ErrorText))\n    {\n        <p class=\"mt-1 text-xs text-destructive\">@ErrorText</p>\n    }\n    else if (!string.IsNullOrEmpty(HelperText))\n    {\n        <p class=\"mt-1 text-xs text-muted-foreground\">@HelperText</p>\n    }\n}\n\n@inject Lumeo.Services.Localization.ILumeoLocalizer L\n\n@code {\n    [CascadingParameter] private FormField.FormFieldContext? FormField { get; set; }\n\n    [Parameter] public double? Value { get; set; }\n    [Parameter] public EventCallback<double?> ValueChanged { get; set; }\n    [Parameter] public double? Min { get; set; }\n    [Parameter] public double? Max { get; set; }\n    [Parameter] public double Step { get; set; } = 1;\n    [Parameter] public int? Precision { get; set; }\n    [Parameter] public bool Disabled { get; set; }\n    [Parameter] public bool UseMouseWheel { get; set; }\n    [Parameter] public string? Prefix { get; set; }\n    [Parameter] public string? Suffix { get; set; }\n\n    /// <summary>\n    /// Culture used to parse typed input. Display of the value inside the native\n    /// <c>&lt;input type=\"number\"&gt;</c> element uses <see cref=\"System.Globalization.CultureInfo.InvariantCulture\"/>\n    /// because HTML5 requires period-as-decimal — browsers will not accept a comma decimal\n    /// in this input type. Leave null to honour ASP.NET request localization for parsing\n    /// (lets a <c>de-DE</c> user paste <c>1,5</c> and have it round-trip to <c>1.5</c>).\n    /// </summary>\n    [Parameter] public System.Globalization.CultureInfo? Culture { get; set; }\n\n    private System.Globalization.CultureInfo EffectiveCulture => Culture ?? System.Globalization.CultureInfo.CurrentCulture;\n    [Parameter] public bool Required { get; set; }\n    [Parameter] public bool Invalid { get; set; }\n    [Parameter] public string? ErrorText { get; set; }\n    [Parameter] public string? HelperText { get; set; }\n    [Parameter] public string? Label { get; set; }\n    [Parameter] public string? Name { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private bool EffectiveInvalid => Invalid || FormField?.HasError == true;\n    private bool InsideFormField => FormField is not null;\n\n    private string CssClass => $\"inline-flex items-center {Class}\".Trim();\n\n    private bool IsAtMin => Min.HasValue && Value.HasValue && Value.Value <= Min.Value;\n    private bool IsAtMax => Max.HasValue && Value.HasValue && Value.Value >= Max.Value;\n\n    private string DisplayValue\n    {\n        get\n        {\n            if (Value is null) return \"\";\n            if (Precision.HasValue)\n                return Value.Value.ToString($\"F{Precision.Value}\", System.Globalization.CultureInfo.InvariantCulture);\n            return Value.Value.ToString(System.Globalization.CultureInfo.InvariantCulture);\n        }\n    }\n\n    private async Task Increment()\n    {\n        var newValue = (Value ?? 0) + Step;\n        await SetValue(newValue);\n    }\n\n    private async Task Decrement()\n    {\n        var newValue = (Value ?? 0) - Step;\n        await SetValue(newValue);\n    }\n\n    private async Task HandleKeyDown(KeyboardEventArgs e)\n    {\n        if (Disabled) return;\n        switch (e.Key)\n        {\n            case \"ArrowUp\":\n                await Increment();\n                break;\n            case \"ArrowDown\":\n                await Decrement();\n                break;\n        }\n    }\n\n    private async Task HandleWheel(WheelEventArgs e)\n    {\n        if (!UseMouseWheel || Disabled) return;\n        if (e.DeltaY < 0)\n            await Increment();\n        else if (e.DeltaY > 0)\n            await Decrement();\n    }\n\n    private async Task OnInputChange(ChangeEventArgs e)\n    {\n        var raw = e.Value?.ToString();\n        // Try invariant first (what the native <input type=\"number\"> reports), then the\n        // effective culture as a fallback for consumers that render via FormatType=Text\n        // or feed values programmatically.\n        if (double.TryParse(raw, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var parsed)\n            || double.TryParse(raw, System.Globalization.NumberStyles.Any, EffectiveCulture, out parsed))\n        {\n            await SetValue(parsed);\n        }\n    }\n\n    private async Task SetValue(double newValue)\n    {\n        if (Min.HasValue && newValue < Min.Value) newValue = Min.Value;\n        if (Max.HasValue && newValue > Max.Value) newValue = Max.Value;\n\n        if (Precision.HasValue)\n        {\n            newValue = Math.Round(newValue, Precision.Value);\n        }\n\n        Value = newValue;\n        await ValueChanged.InvokeAsync(Value);\n    }\n}\n"
    }
  ],
  "slots": [],
  "serviceDependencies": [
    {
      "service": "ILumeoLocalizer",
      "namespace": "Lumeo.Services.Localization"
    }
  ],
  "cascadingDependencies": [
    {
      "name": "FormField",
      "type": "FormField.FormFieldContext?",
      "required": false
    }
  ],
  "relatedComponents": [],
  "keyboardInteractions": [],
  "tests": [
    "tests/Lumeo.Tests/Components/NumberInput/NumberInputTests.cs"
  ],
  "mdSummary": "# NumberInput\n\nNumeric input with stepper buttons and locale formatting.\n\n**Package:** `Lumeo` · **Category:** Forms/Inputs\n**Docs:** https://lumeo.nativ.sh/components/number-input\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| NumberInput | FormField | `FormField.FormFieldContext?` | `` |  |\n| NumberInput | Value | `double?` | `` |  |\n| NumberInput | ValueChanged | `EventCallback<double?>` | `` |  |\n| NumberInput | Min | `double?` | `` |  |\n| NumberInput | Max | `double?` | `` |  |\n| NumberInput | Step | `double` | `1` |  |\n| NumberInput | Precision | `int?` | `` |  |\n| NumberInput | Disabled | `bool` | `` |  |\n| NumberInput | UseMouseWheel | `bool` | `` |  |\n| NumberInput | Prefix | `string?` | `` |  |\n| NumberInput | Suffix | `string?` | `` |  |\n| NumberInput | Culture | `System.Globalization.CultureInfo?` | `` | Culture used to parse typed input. Display of the value inside the native &lt;input type=\"number\"&gt; element uses InvariantCulture because HTML5 requires period-as-decimal — browsers will not accept a comma decimal in this input type. Leave null to honour ASP.NET request localization for parsing (lets a de-DE user paste 1,5 and have it round-trip to 1.5). |\n| NumberInput | Required | `bool` | `` |  |\n| NumberInput | Invalid | `bool` | `` |  |\n| NumberInput | ErrorText | `string?` | `` |  |\n| NumberInput | HelperText | `string?` | `` |  |\n| NumberInput | Label | `string?` | `` |  |\n| NumberInput | Name | `string?` | `` |  |\n| NumberInput | Class | `string?` | `` |  |\n| NumberInput | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- (none)\n\n## Events\n- `ValueChanged` (`EventCallback<double?>`)\n\n## Sub-components\n- (none)\n\n## Cascading values it expects\n- `FormField` (`FormField.FormFieldContext?`)\n\n## Services it injects\n- `ILumeoLocalizer` (Lumeo.Services.Localization)\n\n## Keyboard\n—\n\n## Example\n```razor\n<NumberInput />\n```\n\n## CSS variables\n`--color-background`, `--color-border`, `--color-destructive`, `--color-foreground`, `--color-muted`, `--color-muted-foreground`, `--radius`\n\n## Files\n- `UI/NumberInput/NumberInput.razor`\n"
}