{
  "name": "InputMask",
  "category": "Forms",
  "subcategory": "Inputs",
  "description": "Masked input for phone numbers, dates, and custom patterns.",
  "thumbnail": "/preview-cards/input-mask.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/InputMask/InputMask.razor"
  ],
  "dependencies": [],
  "packageDependencies": [],
  "cssVars": [
    "--color-destructive",
    "--color-foreground",
    "--color-input",
    "--color-muted",
    "--color-muted-foreground",
    "--color-ring",
    "--radius"
  ],
  "api": {
    "name": "InputMask",
    "category": "Forms",
    "subcategory": "Inputs",
    "description": "Masked input for phone numbers, dates, and custom patterns.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/InputMask/InputMask.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [],
    "parameters": [
      {
        "name": "FormField",
        "type": "FormField.FormFieldContext?",
        "default": null,
        "description": null,
        "isCascading": true,
        "captureUnmatched": false
      },
      {
        "name": "Value",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ValueChanged",
        "type": "EventCallback<string?>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Mask",
        "type": "string",
        "default": "\"\"",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Placeholder",
        "type": "string?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "PromptChar",
        "type": "char",
        "default": "'_'",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Disabled",
        "type": "bool",
        "default": null,
        "description": null,
        "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<string?>",
        "description": null
      }
    ],
    "enums": [],
    "records": [],
    "cssVars": [
      "--color-destructive",
      "--color-foreground",
      "--color-input",
      "--color-muted",
      "--color-muted-foreground",
      "--color-ring",
      "--radius"
    ],
    "examples": [
      {
        "title": "Phone Number",
        "code": "<InputMask @bind-Value=\"_phone\" Mask=\"(###) ###-####\" />"
      },
      {
        "title": "Date Mask",
        "code": "<InputMask @bind-Value=\"_date\" Mask=\"##/##/####\" Placeholder=\"MM/DD/YYYY\" />"
      },
      {
        "title": "Credit Card",
        "code": "<InputMask @bind-Value=\"_card\" Mask=\"#### #### #### ####\" />"
      },
      {
        "title": "IP Address",
        "code": "<InputMask @bind-Value=\"_ip\" Mask=\"###.###.###.###\" />"
      }
    ],
    "subComponents": {},
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "input-mask",
  "docsUrl": "https://lumeo.nativ.sh/components/input-mask",
  "sourceUrl": [
    {
      "file": "UI/InputMask/InputMask.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/InputMask/InputMask.razor"
    }
  ],
  "source": [
    {
      "path": "UI/InputMask/InputMask.razor",
      "content": "@namespace Lumeo\n\n@if (Label is not null && !InsideFormField)\n{\n    <label class=\"block text-sm font-medium text-foreground mb-1.5\">\n        @Label\n        @if (Required)\n        {\n            <span class=\"text-destructive ml-0.5\">*</span>\n        }\n    </label>\n}\n\n<input class=\"@CssClass\"\n       value=\"@_displayValue\"\n       placeholder=\"@EffectivePlaceholder\"\n       disabled=\"@Disabled\"\n       name=\"@(Name ?? FormField?.Name)\"\n       aria-required=\"@(Required ? \"true\" : \"false\")\"\n       aria-invalid=\"@(EffectiveInvalid ? \"true\" : \"false\")\"\n       @oninput=\"HandleInput\"\n       @onkeydown=\"HandleKeyDown\"\n       @attributes=\"AdditionalAttributes\" />\n\n@if (!InsideFormField)\n{\n    @if (EffectiveInvalid && ErrorText is not null)\n    {\n        <p class=\"text-xs text-destructive mt-1.5\">@ErrorText</p>\n    }\n    else if (HelperText is not null)\n    {\n        <p class=\"text-xs text-muted-foreground mt-1.5\">@HelperText</p>\n    }\n}\n\n@code {\n    [CascadingParameter] private FormField.FormFieldContext? FormField { get; set; }\n\n    [Parameter] public string? Value { get; set; }\n    [Parameter] public EventCallback<string?> ValueChanged { get; set; }\n    [Parameter] public string Mask { get; set; } = \"\";\n    [Parameter] public string? Placeholder { get; set; }\n    [Parameter] public char PromptChar { get; set; } = '_';\n    [Parameter] public bool Disabled { get; set; }\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 _displayValue = \"\";\n    private string _rawValue = \"\";\n\n    private string CssClass => $\"flex h-9 w-full rounded-md border {(EffectiveInvalid ? \"border-destructive focus-visible:ring-destructive/20\" : \"border-input focus-visible:ring-ring\")} bg-transparent px-3 py-1 text-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[1.5px] disabled:cursor-not-allowed disabled:opacity-50 {Class}\".Trim();\n\n    private string EffectivePlaceholder => Placeholder ?? BuildMaskPlaceholder();\n\n    protected override void OnParametersSet()\n    {\n        if (Value != _rawValue)\n        {\n            _rawValue = Value ?? \"\";\n            _displayValue = ApplyMask(_rawValue);\n        }\n    }\n\n    private string BuildMaskPlaceholder()\n    {\n        if (string.IsNullOrEmpty(Mask)) return \"\";\n        return new string(Mask.Select(c => IsMaskChar(c) ? PromptChar : c).ToArray());\n    }\n\n    private bool IsMaskChar(char c) => c == '#' || c == 'A' || c == '*';\n\n    private bool MatchesMaskChar(char maskChar, char inputChar) => maskChar switch\n    {\n        '#' => char.IsDigit(inputChar),\n        'A' => char.IsLetter(inputChar),\n        '*' => true,\n        _ => false\n    };\n\n    private string ApplyMask(string raw)\n    {\n        if (string.IsNullOrEmpty(Mask)) return raw;\n\n        var result = new System.Text.StringBuilder();\n        int rawIndex = 0;\n\n        foreach (char maskChar in Mask)\n        {\n            if (rawIndex >= raw.Length) break;\n\n            if (IsMaskChar(maskChar))\n            {\n                // Find next matching raw character\n                while (rawIndex < raw.Length && !MatchesMaskChar(maskChar, raw[rawIndex]))\n                    rawIndex++;\n\n                if (rawIndex < raw.Length)\n                {\n                    result.Append(raw[rawIndex]);\n                    rawIndex++;\n                }\n                else\n                {\n                    break;\n                }\n            }\n            else\n            {\n                result.Append(maskChar);\n            }\n        }\n\n        return result.ToString();\n    }\n\n    private string ExtractRaw(string display)\n    {\n        if (string.IsNullOrEmpty(Mask)) return display;\n\n        var raw = new System.Text.StringBuilder();\n        int maskIndex = 0;\n\n        for (int i = 0; i < display.Length && maskIndex < Mask.Length; i++)\n        {\n            // Advance mask past literal chars\n            while (maskIndex < Mask.Length && !IsMaskChar(Mask[maskIndex]))\n                maskIndex++;\n\n            if (maskIndex >= Mask.Length) break;\n\n            if (MatchesMaskChar(Mask[maskIndex], display[i]))\n            {\n                raw.Append(display[i]);\n                maskIndex++;\n            }\n        }\n\n        return raw.ToString();\n    }\n\n    private async Task HandleInput(ChangeEventArgs e)\n    {\n        var inputVal = e.Value?.ToString() ?? \"\";\n        _rawValue = ExtractRaw(inputVal);\n        _displayValue = ApplyMask(_rawValue);\n        await ValueChanged.InvokeAsync(_rawValue);\n    }\n\n    private async Task HandleKeyDown(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs e)\n    {\n        if (e.Key == \"Backspace\" && _rawValue.Length > 0)\n        {\n            _rawValue = _rawValue[..^1];\n            _displayValue = ApplyMask(_rawValue);\n            await ValueChanged.InvokeAsync(_rawValue);\n        }\n    }\n}\n"
    }
  ],
  "slots": [],
  "serviceDependencies": [],
  "cascadingDependencies": [
    {
      "name": "FormField",
      "type": "FormField.FormFieldContext?",
      "required": false
    }
  ],
  "relatedComponents": [],
  "keyboardInteractions": [
    {
      "key": "Backspace",
      "action": "HandleKeyDown handles Backspace in InputMask.razor"
    }
  ],
  "tests": [
    "tests/Lumeo.Tests/Components/InputMask/InputMaskTests.cs"
  ],
  "mdSummary": "# InputMask\n\nMasked input for phone numbers, dates, and custom patterns.\n\n**Package:** `Lumeo` · **Category:** Forms/Inputs\n**Docs:** https://lumeo.nativ.sh/components/input-mask\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| InputMask | FormField | `FormField.FormFieldContext?` | `` |  |\n| InputMask | Value | `string?` | `` |  |\n| InputMask | ValueChanged | `EventCallback<string?>` | `` |  |\n| InputMask | Mask | `string` | `\"\"` |  |\n| InputMask | Placeholder | `string?` | `` |  |\n| InputMask | PromptChar | `char` | `'_'` |  |\n| InputMask | Disabled | `bool` | `` |  |\n| InputMask | Required | `bool` | `` |  |\n| InputMask | Invalid | `bool` | `` |  |\n| InputMask | ErrorText | `string?` | `` |  |\n| InputMask | HelperText | `string?` | `` |  |\n| InputMask | Label | `string?` | `` |  |\n| InputMask | Name | `string?` | `` |  |\n| InputMask | Class | `string?` | `` |  |\n| InputMask | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- (none)\n\n## Events\n- `ValueChanged` (`EventCallback<string?>`)\n\n## Sub-components\n- (none)\n\n## Cascading values it expects\n- `FormField` (`FormField.FormFieldContext?`)\n\n## Services it injects\n- (none)\n\n## Keyboard\n- `Backspace` — HandleKeyDown handles Backspace in InputMask.razor\n\n## Example\n```razor\n<InputMask />\n```\n\n## CSS variables\n`--color-destructive`, `--color-foreground`, `--color-input`, `--color-muted`, `--color-muted-foreground`, `--color-ring`, `--radius`\n\n## Files\n- `UI/InputMask/InputMask.razor`\n"
}