{
  "name": "Select",
  "category": "Forms",
  "subcategory": "Selection",
  "description": "Native-feeling styled dropdown with search and groups.",
  "thumbnail": "/preview-cards/select.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/Select/Select.razor",
    "UI/Select/SelectContent.razor",
    "UI/Select/SelectGroup.razor",
    "UI/Select/SelectItem.razor",
    "UI/Select/SelectLabel.razor",
    "UI/Select/SelectTrigger.razor"
  ],
  "dependencies": [
    "list",
    "spinner"
  ],
  "packageDependencies": [
    "Blazicons.Lucide"
  ],
  "cssVars": [
    "--color-accent",
    "--color-accent-foreground",
    "--color-background",
    "--color-border",
    "--color-destructive",
    "--color-foreground",
    "--color-input",
    "--color-muted",
    "--color-muted-foreground",
    "--color-popover",
    "--color-popover-foreground",
    "--color-ring",
    "--color-secondary",
    "--color-secondary-foreground",
    "--radius"
  ],
  "api": {
    "name": "Select",
    "category": "Forms",
    "subcategory": "Selection",
    "description": "Native-feeling styled dropdown with search and groups.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/Select/Select.razor",
      "UI/Select/SelectContent.razor",
      "UI/Select/SelectGroup.razor",
      "UI/Select/SelectItem.razor",
      "UI/Select/SelectLabel.razor",
      "UI/Select/SelectTrigger.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [],
    "parameters": [
      {
        "name": "FormField",
        "type": "FormField.FormFieldContext?",
        "default": null,
        "description": null,
        "isCascading": true,
        "captureUnmatched": false
      },
      {
        "name": "ChildContent",
        "type": "RenderFragment?",
        "default": null,
        "description": null,
        "isCascading": false,
        "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": "Open",
        "type": "bool",
        "default": null,
        "description": "Popover open state. Bind via @bind-Open when the Select lives inside a parent that re-renders heavily (e.g. an EditForm with active validation) — otherwise a parameter cascade can close the popover mid-click. In the bound (controlled) mode the parent owns state and the popover survives re-renders. When unbound, the component still functions but is vulnerable to the cascade-close race; binding is the recommended pattern.",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "OpenChanged",
        "type": "EventCallback<bool>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "IsOpen",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "IsOpenChanged",
        "type": "EventCallback<bool>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Searchable",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Multiple",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Values",
        "type": "List<string>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ValuesChanged",
        "type": "EventCallback<List<string>?>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Clearable",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Creatable",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "OnCreate",
        "type": "EventCallback<string>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "MaxDisplayTags",
        "type": "int",
        "default": "3",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Disabled",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "IsLoading",
        "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": "AdditionalAttributes",
        "type": "Dictionary<string, object>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": true
      },
      {
        "name": "Items",
        "type": "IEnumerable<object>?",
        "default": null,
        "description": "Optional data source. When set, SelectContent renders options from this list instead of ChildContent.",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ItemValue",
        "type": "Func<object, string>",
        "default": "it => it?.ToString() ?? \"\"",
        "description": "Extracts the string value for each item. Defaults to ToString().",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ItemText",
        "type": "Func<object, string>?",
        "default": null,
        "description": "Extracts the display label for each item. Defaults to ItemValue when null.",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ItemGroup",
        "type": "Func<object, string?>?",
        "default": null,
        "description": "Optional group key extractor. When set, options are grouped under sticky headers.",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ItemDisabled",
        "type": "Func<object, bool>?",
        "default": null,
        "description": "Optional per-item disabled predicate.",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ItemTemplate",
        "type": "RenderFragment<object>?",
        "default": null,
        "description": "Optional custom render fragment for an item's content. Receives the raw item object.",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Virtualize",
        "type": "bool",
        "default": null,
        "description": "When true and Items is set, uses Blazor's Virtualize component for the option list.",
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ItemSize",
        "type": "float",
        "default": "36f",
        "description": "Estimated pixel height of each option row used by the Virtualize component. Default 36.",
        "isCascading": false,
        "captureUnmatched": false
      }
    ],
    "events": [
      {
        "name": "ValueChanged",
        "type": "EventCallback<string?>",
        "description": null
      },
      {
        "name": "OpenChanged",
        "type": "EventCallback<bool>",
        "description": null
      },
      {
        "name": "IsOpenChanged",
        "type": "EventCallback<bool>",
        "description": null
      },
      {
        "name": "ValuesChanged",
        "type": "EventCallback<List<string>?>",
        "description": null
      },
      {
        "name": "OnCreate",
        "type": "EventCallback<string>",
        "description": null
      }
    ],
    "enums": [],
    "records": [
      {
        "name": "SelectContext",
        "signature": "SelectContext(\n        string? Value, bool IsOpen, bool Searchable, string SearchText,\n        bool Multiple, List<string>? Values, bool IsLoading, bool Disabled, bool Clearable, bool Creatable, int MaxDisplayTags,\n        EventCallback<string> OnSelect, EventCallback<bool> SetOpen, EventCallback<string> OnSearch,\n        EventCallback OnClear, EventCallback<string> OnCreateItem,\n        string WrapperId, string ContentId, int FocusedIndex, string? FocusedItemId, string? FocusedItemValue,\n        EventCallback<int> SetFocusedIndex,\n        Action<string, string> RegisterItem, Action<string> UnregisterItem,\n        bool Invalid, bool Required,\n        // Data-bound mode params (null when composition mode)\n        IEnumerable<object>? Items,\n        Func<object, string> ItemValue,\n        Func<object, string>? ItemText,\n        Func<object, string?>? ItemGroup,\n        Func<object, bool>? ItemDisabled,\n        RenderFragment<object>? ItemTemplate,\n        bool Virtualize,\n        float ItemSize)",
        "description": null
      }
    ],
    "cssVars": [
      "--color-accent",
      "--color-accent-foreground",
      "--color-background",
      "--color-border",
      "--color-destructive",
      "--color-foreground",
      "--color-input",
      "--color-muted",
      "--color-muted-foreground",
      "--color-popover",
      "--color-popover-foreground",
      "--color-ring",
      "--color-secondary",
      "--color-secondary-foreground",
      "--radius"
    ],
    "examples": [
      {
        "title": "Default",
        "code": "<Select @bind-Value=\"_selected\">\n    <SelectTrigger>\n        <span>@(_selected ?? \"Select a fruit\")</span>\n    </SelectTrigger>\n    <SelectContent>\n        <SelectItem Value=\"apple\">Apple</SelectItem>\n        <SelectItem Value=\"banana\">Banana</SelectItem>\n        <SelectItem Value=\"cherry\">Cherry</SelectItem>\n    </SelectContent>\n</Select>\n\n@code {\n    private string? _selected;\n}"
      },
      {
        "title": "With Binding",
        "code": "<Select @bind-Value=\"_selected\">\n    <SelectTrigger>\n        <span>@(_selected ?? \"Select a framework\")</span>\n    </SelectTrigger>\n    <SelectContent>\n        <SelectItem Value=\"blazor\">Blazor</SelectItem>\n        <SelectItem Value=\"react\">React</SelectItem>\n        <SelectItem Value=\"vue\">Vue</SelectItem>\n    </SelectContent>\n</Select>\n<Lumeo.Text Size=\"sm\" Color=\"muted\">Selected: @(_selected ?? \"none\")</Lumeo.Text>"
      },
      {
        "title": "With Groups",
        "code": "<Select @bind-Value=\"_selected\">\n    <SelectTrigger>\n        <span>@(_selected ?? \"Select a timezone\")</span>\n    </SelectTrigger>\n    <SelectContent>\n        <SelectGroup>\n            <SelectLabel>North America</SelectLabel>\n            <SelectItem Value=\"est\">Eastern (EST)</SelectItem>\n            <SelectItem Value=\"cst\">Central (CST)</SelectItem>\n        </SelectGroup>\n        <SelectGroup>\n            <SelectLabel>Europe</SelectLabel>\n            <SelectItem Value=\"gmt\">Greenwich (GMT)</SelectItem>\n            <SelectItem Value=\"cet\">Central European (CET)</SelectItem>\n        </SelectGroup>\n    </SelectContent>\n</Select>"
      },
      {
        "title": "Searchable",
        "code": "<Select Searchable=\"true\" @bind-Value=\"_selected\">\n    <SelectTrigger>\n        <span>@(_selected ?? \"Search countries...\")</span>\n    </SelectTrigger>\n    <SelectContent>\n        <SelectItem Value=\"us\">United States</SelectItem>\n        <SelectItem Value=\"uk\">United Kingdom</SelectItem>\n        <SelectItem Value=\"ca\">Canada</SelectItem>\n    </SelectContent>\n</Select>"
      }
    ],
    "subComponents": {
      "SelectContent": {
        "componentName": "SelectContent",
        "fileName": "SelectContent.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [
          "IAsyncDisposable"
        ],
        "parameters": [
          {
            "name": "Context",
            "type": "Select.SelectContext",
            "default": "default!",
            "description": null,
            "isCascading": true,
            "captureUnmatched": false
          },
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "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": [],
        "enums": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      },
      "SelectGroup": {
        "componentName": "SelectGroup",
        "fileName": "SelectGroup.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "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": [],
        "enums": [],
        "records": [
          {
            "name": "SelectGroupContext",
            "signature": "SelectGroupContext(string LabelId)",
            "description": null
          }
        ],
        "parseFailed": false,
        "parseError": null
      },
      "SelectItem": {
        "componentName": "SelectItem",
        "fileName": "SelectItem.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [
          "IDisposable"
        ],
        "parameters": [
          {
            "name": "Context",
            "type": "Select.SelectContext",
            "default": "default!",
            "description": null,
            "isCascading": true,
            "captureUnmatched": false
          },
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Value",
            "type": "string",
            "default": "\"\"",
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Disabled",
            "type": "bool",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Class",
            "type": "string?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "SearchValue",
            "type": "string?",
            "default": null,
            "description": "Optional text used to match against the search input when Searchable is enabled. Falls back to Value.",
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "AdditionalAttributes",
            "type": "Dictionary<string, object>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": true
          }
        ],
        "events": [],
        "enums": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      },
      "SelectLabel": {
        "componentName": "SelectLabel",
        "fileName": "SelectLabel.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "GroupContext",
            "type": "SelectGroup.SelectGroupContext?",
            "default": null,
            "description": null,
            "isCascading": true,
            "captureUnmatched": false
          },
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "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": [],
        "enums": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      },
      "SelectTrigger": {
        "componentName": "SelectTrigger",
        "fileName": "SelectTrigger.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "Context",
            "type": "Select.SelectContext",
            "default": "default!",
            "description": null,
            "isCascading": true,
            "captureUnmatched": false
          },
          {
            "name": "ChildContent",
            "type": "RenderFragment?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Placeholder",
            "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": [],
        "enums": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      }
    },
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "select",
  "docsUrl": "https://lumeo.nativ.sh/components/select",
  "sourceUrl": [
    {
      "file": "UI/Select/Select.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Select/Select.razor"
    },
    {
      "file": "UI/Select/SelectContent.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Select/SelectContent.razor"
    },
    {
      "file": "UI/Select/SelectGroup.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Select/SelectGroup.razor"
    },
    {
      "file": "UI/Select/SelectItem.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Select/SelectItem.razor"
    },
    {
      "file": "UI/Select/SelectLabel.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Select/SelectLabel.razor"
    },
    {
      "file": "UI/Select/SelectTrigger.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Select/SelectTrigger.razor"
    }
  ],
  "source": [
    {
      "path": "UI/Select/Select.razor",
      "content": "@namespace Lumeo\n\n@{\n    var focusedItemId = _focusedIndex >= 0 && _focusedIndex < _items.Count ? _items[_focusedIndex].Id : null;\n    var focusedItemValue = _focusedIndex >= 0 && _focusedIndex < _items.Count ? _items[_focusedIndex].Value : null;\n    var context = new SelectContext(Value, Open, Searchable, SearchText, Multiple, Values, IsLoading, Disabled, Clearable, Creatable, MaxDisplayTags,\n        EventCallback.Factory.Create<string>(this, OnValueSelected),\n        EventCallback.Factory.Create<bool>(this, SetOpen),\n        EventCallback.Factory.Create<string>(this, OnSearchChanged),\n        EventCallback.Factory.Create(this, OnClear),\n        EventCallback.Factory.Create<string>(this, OnCreateItem),\n        _wrapperId, _contentId, _focusedIndex, focusedItemId, focusedItemValue,\n        EventCallback.Factory.Create<int>(this, SetFocusedIndex),\n        RegisterItem, UnregisterItem, EffectiveInvalid, Required,\n        Items, ItemValue, ItemText, ItemGroup, ItemDisabled, ItemTemplate, Virtualize, ItemSize);\n}\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 ms-0.5\">*</span>\n        }\n    </label>\n}\n\n<CascadingValue Value=\"context\" IsFixed=\"false\">\n    <div id=\"@_wrapperId\" class=\"relative\" @attributes=\"AdditionalAttributes\">\n        @ChildContent\n    </div>\n</CascadingValue>\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@code {\n    [CascadingParameter] private FormField.FormFieldContext? FormField { get; set; }\n\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public string? Value { get; set; }\n    [Parameter] public EventCallback<string?> ValueChanged { get; set; }\n    /// <summary>\n    /// Popover open state. Bind via <c>@bind-Open</c> when the Select lives inside\n    /// a parent that re-renders heavily (e.g. an <c>EditForm</c> with active\n    /// validation) — otherwise a parameter cascade can close the popover mid-click.\n    /// In the bound (controlled) mode the parent owns state and the popover survives\n    /// re-renders. When unbound, the component still functions but is vulnerable to\n    /// the cascade-close race; binding is the recommended pattern.\n    /// </summary>\n    [Parameter] public bool Open { get; set; }\n    [Parameter] public EventCallback<bool> OpenChanged { get; set; }\n\n    [Obsolete(\"Use Open instead. IsOpen will be removed in a future release.\")]\n    [Parameter] public bool IsOpen { get => Open; set => Open = value; }\n\n    [Obsolete(\"Use OpenChanged instead. IsOpenChanged will be removed in a future release.\")]\n    [Parameter] public EventCallback<bool> IsOpenChanged { get => OpenChanged; set => OpenChanged = value; }\n\n    [Parameter] public bool Searchable { get; set; }\n    [Parameter] public bool Multiple { get; set; }\n    [Parameter] public List<string>? Values { get; set; }\n    [Parameter] public EventCallback<List<string>?> ValuesChanged { get; set; }\n    [Parameter] public bool Clearable { get; set; }\n    [Parameter] public bool Creatable { get; set; }\n    [Parameter] public EventCallback<string> OnCreate { get; set; }\n    [Parameter] public int MaxDisplayTags { get; set; } = 3;\n    [Parameter] public bool Disabled { get; set; }\n    [Parameter] public bool IsLoading { 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(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    // --- Data-bound mode (non-generic, purely additive) ---\n    /// <summary>Optional data source. When set, <see cref=\"SelectContent\"/> renders options from this list instead of ChildContent.</summary>\n    [Parameter] public IEnumerable<object>? Items { get; set; }\n    /// <summary>Extracts the string value for each item. Defaults to <c>ToString()</c>.</summary>\n    [Parameter] public Func<object, string> ItemValue { get; set; } = it => it?.ToString() ?? \"\";\n    /// <summary>Extracts the display label for each item. Defaults to <see cref=\"ItemValue\"/> when null.</summary>\n    [Parameter] public Func<object, string>? ItemText { get; set; }\n    /// <summary>Optional group key extractor. When set, options are grouped under sticky headers.</summary>\n    [Parameter] public Func<object, string?>? ItemGroup { get; set; }\n    /// <summary>Optional per-item disabled predicate.</summary>\n    [Parameter] public Func<object, bool>? ItemDisabled { get; set; }\n    /// <summary>Optional custom render fragment for an item's content. Receives the raw item object.</summary>\n    [Parameter] public RenderFragment<object>? ItemTemplate { get; set; }\n    /// <summary>When <c>true</c> and <see cref=\"Items\"/> is set, uses Blazor's Virtualize component for the option list.</summary>\n    [Parameter] public bool Virtualize { get; set; }\n    /// <summary>Estimated pixel height of each option row used by the Virtualize component. Default 36.</summary>\n    [Parameter] public float ItemSize { get; set; } = 36f;\n\n    private bool EffectiveInvalid => Invalid || FormField?.HasError == true;\n    private bool InsideFormField => FormField is not null;\n\n    private readonly string _wrapperId = $\"select-{Guid.NewGuid():N}\";\n    private readonly string _contentId = $\"select-content-{Guid.NewGuid():N}\";\n    private string SearchText { get; set; } = \"\";\n    private int _focusedIndex = -1;\n    private readonly List<(string Id, string Value)> _items = new();\n\n    private void RegisterItem(string id, string value)\n    {\n        if (!_items.Any(i => i.Id == id))\n            _items.Add((id, value));\n    }\n\n    private void UnregisterItem(string id)\n    {\n        _items.RemoveAll(i => i.Id == id);\n    }\n\n    private async Task SetFocusedIndex(int index)\n    {\n        var count = _items.Count;\n        if (count == 0) return;\n        if (index < 0) index = 0;\n        if (index >= count) index = count - 1;\n        _focusedIndex = index;\n        StateHasChanged();\n        await Task.CompletedTask;\n    }\n\n    private async Task OnValueSelected(string value)\n    {\n        if (Multiple)\n        {\n            var current = Values ?? new List<string>();\n            if (current.Contains(value))\n            {\n                current = current.Where(v => v != value).ToList();\n            }\n            else\n            {\n                current = new List<string>(current) { value };\n            }\n            Values = current.Count > 0 ? current : null;\n            await ValuesChanged.InvokeAsync(Values);\n        }\n        else\n        {\n            Value = value;\n            await ValueChanged.InvokeAsync(value);\n            await SetOpen(false);\n        }\n    }\n\n    private async Task SetOpen(bool value)\n    {\n        Open = value;\n        if (!value)\n        {\n            SearchText = \"\";\n            _focusedIndex = -1;\n            _items.Clear();\n        }\n        await OpenChanged.InvokeAsync(value);\n    }\n\n    private Task OnSearchChanged(string text)\n    {\n        SearchText = text;\n        _focusedIndex = -1;\n        _items.Clear();\n        return Task.CompletedTask;\n    }\n\n    private async Task OnClear()\n    {\n        if (Multiple)\n        {\n            Values = null;\n            await ValuesChanged.InvokeAsync(null);\n        }\n        else\n        {\n            Value = null;\n            await ValueChanged.InvokeAsync(null);\n        }\n    }\n\n    private async Task OnCreateItem(string value)\n    {\n        await OnCreate.InvokeAsync(value);\n        if (Multiple)\n        {\n            var current = Values ?? new List<string>();\n            current = new List<string>(current) { value };\n            Values = current;\n            await ValuesChanged.InvokeAsync(Values);\n        }\n        else\n        {\n            Value = value;\n            await ValueChanged.InvokeAsync(value);\n            await SetOpen(false);\n        }\n        SearchText = \"\";\n    }\n\n    public record SelectContext(\n        string? Value, bool IsOpen, bool Searchable, string SearchText,\n        bool Multiple, List<string>? Values, bool IsLoading, bool Disabled, bool Clearable, bool Creatable, int MaxDisplayTags,\n        EventCallback<string> OnSelect, EventCallback<bool> SetOpen, EventCallback<string> OnSearch,\n        EventCallback OnClear, EventCallback<string> OnCreateItem,\n        string WrapperId, string ContentId, int FocusedIndex, string? FocusedItemId, string? FocusedItemValue,\n        EventCallback<int> SetFocusedIndex,\n        Action<string, string> RegisterItem, Action<string> UnregisterItem,\n        bool Invalid, bool Required,\n        // Data-bound mode params (null when composition mode)\n        IEnumerable<object>? Items,\n        Func<object, string> ItemValue,\n        Func<object, string>? ItemText,\n        Func<object, string?>? ItemGroup,\n        Func<object, bool>? ItemDisabled,\n        RenderFragment<object>? ItemTemplate,\n        bool Virtualize,\n        float ItemSize);\n}\n"
    },
    {
      "path": "UI/Select/SelectContent.razor",
      "content": "@namespace Lumeo\n@implements IAsyncDisposable\n@using Microsoft.AspNetCore.Components.Web.Virtualization\n\n@if (Context.IsOpen)\n{\n    <div id=\"@Context.ContentId\" role=\"listbox\" class=\"@CssClass\"\n         @onkeydown=\"HandleKeyDown\" tabindex=\"-1\" @attributes=\"AdditionalAttributes\">\n        @if (Context.Searchable)\n        {\n            <div class=\"px-1 pb-1\">\n                <input type=\"text\" class=\"w-full rounded-sm border border-border bg-background px-2 py-1 text-sm outline-none focus:ring-1 focus:ring-ring placeholder:text-muted-foreground\"\n                    placeholder=\"@L[\"Combobox.SearchPlaceholder\"]\"\n                    value=\"@Context.SearchText\"\n                    @oninput=\"OnInput\"\n                    @onkeydown=\"HandleKeyDown\"\n                    @onclick:stopPropagation=\"true\" />\n            </div>\n        }\n\n        @if (Context.IsLoading)\n        {\n            <div class=\"flex items-center justify-center py-4\">\n                <Spinner Size=\"Spinner.SpinnerSize.Sm\" class=\"text-muted-foreground\" />\n            </div>\n        }\n        else if (Context.Items is not null)\n        {\n            @* Data-bound mode: render items from the Items collection *@\n            var filteredItems = FilteredItems;\n            @if (Context.Virtualize)\n            {\n                <Virtualize Items=\"@filteredItems\" Context=\"item\" ItemSize=\"@Context.ItemSize\">\n                    @RenderBoundItem(item)\n                </Virtualize>\n            }\n            else if (Context.ItemGroup is not null)\n            {\n                @* Grouped rendering *@\n                @foreach (var group in filteredItems.GroupBy(i => Context.ItemGroup(i) ?? \"\"))\n                {\n                    <div role=\"group\">\n                        @if (!string.IsNullOrEmpty(group.Key))\n                        {\n                            <div class=\"px-2 py-1.5 text-sm font-semibold\">@group.Key</div>\n                        }\n                        @foreach (var item in group)\n                        {\n                            @RenderBoundItem(item)\n                        }\n                    </div>\n                }\n            }\n            else\n            {\n                @foreach (var item in filteredItems)\n                {\n                    @RenderBoundItem(item)\n                }\n            }\n\n            @if (Context.Creatable && !string.IsNullOrWhiteSpace(Context.SearchText))\n            {\n                <button type=\"button\" class=\"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none hover:bg-accent hover:text-accent-foreground\"\n                        @onclick=\"HandleCreate\">\n                    <Blazicon Svg=\"Lucide.Plus\" class=\"h-3.5 w-3.5\" />\n                    <span>@string.Format(L[\"Combobox.Create\"], Context.SearchText)</span>\n                </button>\n            }\n        }\n        else\n        {\n            @ChildContent\n\n            @if (Context.Creatable && !string.IsNullOrWhiteSpace(Context.SearchText))\n            {\n                <button type=\"button\" class=\"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none hover:bg-accent hover:text-accent-foreground\"\n                        @onclick=\"HandleCreate\">\n                    <Blazicon Svg=\"Lucide.Plus\" class=\"h-3.5 w-3.5\" />\n                    <span>@string.Format(L[\"Combobox.Create\"], Context.SearchText)</span>\n                </button>\n            }\n        }\n    </div>\n}\n\n@inject ComponentInteropService Interop\n@inject Lumeo.Services.Localization.ILumeoLocalizer L\n\n@code {\n    [CascadingParameter] public Select.SelectContext Context { get; set; } = default!;\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private bool _registered;\n\n    /// <summary>Items filtered by the current search text (data-bound mode only).</summary>\n    private List<object> FilteredItems\n    {\n        get\n        {\n            if (Context.Items is null) return [];\n            if (!Context.Searchable || string.IsNullOrEmpty(Context.SearchText))\n                return Context.Items.ToList();\n\n            return Context.Items.Where(item =>\n            {\n                var text = Context.ItemText?.Invoke(item) ?? Context.ItemValue(item);\n                return text.Contains(Context.SearchText, StringComparison.OrdinalIgnoreCase);\n            }).ToList();\n        }\n    }\n\n    private RenderFragment RenderBoundItem(object item) => builder =>\n    {\n        var value = Context.ItemValue(item);\n        var isSelected = Context.Multiple\n            ? Context.Values != null && Context.Values.Contains(value)\n            : Context.Value == value;\n        var isDisabled = Context.ItemDisabled?.Invoke(item) ?? false;\n        var itemId = $\"select-item-bound-{value.GetHashCode():X}\";\n\n        // Register/unregister happens inline for bound items — we track them through a simple button render\n        // The keyboard nav uses the registered _items list on Select; bound items self-register by calling\n        // Context.RegisterItem from here just like SelectItem does, but without a persistent component.\n        // We render a plain button that matches SelectItem's markup so styling stays consistent.\n\n        var isFocused = Context.FocusedItemId == itemId;\n\n        builder.OpenElement(0, \"button\");\n        builder.AddAttribute(1, \"type\", \"button\");\n        builder.AddAttribute(2, \"id\", itemId);\n        builder.AddAttribute(3, \"role\", \"option\");\n        builder.AddAttribute(4, \"aria-selected\", isSelected.ToString().ToLower());\n        builder.AddAttribute(5, \"aria-disabled\", isDisabled.ToString().ToLower());\n        var cssClass = string.Join(\" \", new[]\n        {\n            \"relative flex w-full select-none items-center gap-2 rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none\",\n            isDisabled ? \"opacity-50 cursor-not-allowed pointer-events-none\" : \"cursor-pointer hover:bg-accent hover:text-accent-foreground\",\n            isFocused && !isDisabled ? \"bg-accent text-accent-foreground\" : null\n        }.Where(s => !string.IsNullOrWhiteSpace(s)));\n        builder.AddAttribute(6, \"class\", cssClass);\n        if (!isDisabled)\n        {\n            builder.AddAttribute(7, \"onclick\", EventCallback.Factory.Create(this, () => Context.OnSelect.InvokeAsync(value)));\n        }\n        builder.OpenElement(8, \"span\");\n        builder.AddAttribute(9, \"class\", \"flex h-3.5 w-3.5 shrink-0 items-center justify-center\");\n        if (isSelected)\n        {\n            builder.OpenComponent<Blazicons.Blazicon>(10);\n            builder.AddAttribute(11, \"Svg\", Blazicons.Lucide.Check);\n            builder.AddAttribute(12, \"class\", \"h-3.5 w-3.5\");\n            builder.CloseComponent();\n        }\n        builder.CloseElement(); // span\n\n        if (Context.ItemTemplate is not null)\n        {\n            builder.AddContent(13, Context.ItemTemplate(item));\n        }\n        else\n        {\n            var label = Context.ItemText?.Invoke(item) ?? value;\n            builder.AddContent(13, label);\n        }\n        builder.CloseElement(); // button\n    };\n\n    private string CssClass => $\"fixed z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg animate-fade-in {Class}\".Trim();\n\n    protected override async Task OnAfterRenderAsync(bool firstRender)\n    {\n        if (Context.IsOpen && !_registered)\n        {\n            await Interop.PositionFixed(Context.ContentId, Context.WrapperId, \"start\", true);\n            await Interop.RegisterClickOutside(Context.ContentId, null, async () =>\n            {\n                await Context.SetOpen.InvokeAsync(false);\n                await InvokeAsync(StateHasChanged);\n            });\n            _registered = true;\n        }\n        else if (!Context.IsOpen && _registered)\n        {\n            await Cleanup();\n        }\n    }\n\n    private async Task HandleKeyDown(KeyboardEventArgs e)\n    {\n        switch (e.Key)\n        {\n            case \"Escape\":\n                await Context.SetOpen.InvokeAsync(false);\n                break;\n            case \"ArrowDown\":\n                await Context.SetFocusedIndex.InvokeAsync(Context.FocusedIndex + 1);\n                break;\n            case \"ArrowUp\":\n                await Context.SetFocusedIndex.InvokeAsync(Context.FocusedIndex > 0 ? Context.FocusedIndex - 1 : 0);\n                break;\n            case \"Home\":\n                await Context.SetFocusedIndex.InvokeAsync(0);\n                break;\n            case \"End\":\n                await Context.SetFocusedIndex.InvokeAsync(int.MaxValue);\n                break;\n            case \"Enter\":\n                if (Context.FocusedItemValue is not null)\n                    await Context.OnSelect.InvokeAsync(Context.FocusedItemValue);\n                break;\n        }\n    }\n\n    private async Task OnInput(ChangeEventArgs e)\n    {\n        await Context.OnSearch.InvokeAsync(e.Value?.ToString() ?? \"\");\n    }\n\n    private async Task HandleCreate()\n    {\n        await Context.OnCreateItem.InvokeAsync(Context.SearchText);\n    }\n\n    private async Task Cleanup()\n    {\n        try { await Interop.UnpositionFixed(Context.ContentId); } catch (JSDisconnectedException) { }\n        try { await Interop.UnregisterClickOutside(Context.ContentId); } catch (JSDisconnectedException) { }\n        _registered = false;\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        if (_registered) await Cleanup();\n    }\n}\n"
    },
    {
      "path": "UI/Select/SelectGroup.razor",
      "content": "@namespace Lumeo\n\n<CascadingValue Value=\"_groupContext\" IsFixed=\"false\">\n    <div role=\"group\" aria-labelledby=\"@_groupContext.LabelId\" class=\"@CssClass\" @attributes=\"AdditionalAttributes\">\n        @ChildContent\n    </div>\n</CascadingValue>\n\n@code {\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private readonly SelectGroupContext _groupContext = new($\"select-label-{Guid.NewGuid():N}\");\n\n    private string CssClass => $\"p-1 {Class}\".Trim();\n\n    public record SelectGroupContext(string LabelId);\n}\n"
    },
    {
      "path": "UI/Select/SelectItem.razor",
      "content": "@namespace Lumeo\n@implements IDisposable\n\n@if (Visible)\n{\n    <button type=\"button\" id=\"@_itemId\" role=\"option\" aria-selected=\"@IsSelected.ToString().ToLower()\"\n            aria-disabled=\"@Disabled.ToString().ToLower()\"\n            class=\"@CssClass\" @onclick=\"HandleSelect\" @attributes=\"AdditionalAttributes\">\n        <span class=\"flex h-3.5 w-3.5 shrink-0 items-center justify-center\">\n            @if (IsSelected)\n            {\n                <Blazicon Svg=\"Lucide.Check\" class=\"h-3.5 w-3.5\" />\n            }\n        </span>\n        @ChildContent\n    </button>\n}\n\n@code {\n    [CascadingParameter] public Select.SelectContext Context { get; set; } = default!;\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter, EditorRequired] public string Value { get; set; } = \"\";\n    [Parameter] public bool Disabled { get; set; }\n    [Parameter] public string? Class { get; set; }\n    /// <summary>Optional text used to match against the search input when Searchable is enabled. Falls back to Value.</summary>\n    [Parameter] public string? SearchValue { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private readonly string _itemId = $\"select-item-{Guid.NewGuid():N}\";\n    private bool _registered;\n\n    private bool IsSelected => Context.Multiple\n        ? Context.Values != null && Context.Values.Contains(Value)\n        : Context.Value == Value;\n\n    private bool IsFocused => Context.FocusedItemId == _itemId;\n\n    private bool Visible\n    {\n        get\n        {\n            if (!Context.Searchable || string.IsNullOrEmpty(Context.SearchText)) return true;\n            var haystack = SearchValue ?? Value;\n            return haystack.Contains(Context.SearchText, StringComparison.OrdinalIgnoreCase);\n        }\n    }\n\n    private string CssClass => string.Join(\" \", new[] { \"relative flex w-full select-none items-center gap-2 rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none\", Disabled ? \"opacity-50 cursor-not-allowed pointer-events-none\" : \"cursor-pointer hover:bg-accent hover:text-accent-foreground\", IsFocused && !Disabled ? \"bg-accent text-accent-foreground\" : null, Class }.Where(s => !string.IsNullOrWhiteSpace(s)));\n\n    protected override void OnParametersSet()\n    {\n        if (Visible && !_registered)\n        {\n            Context.RegisterItem(_itemId, Value);\n            _registered = true;\n        }\n        else if (!Visible && _registered)\n        {\n            Context.UnregisterItem(_itemId);\n            _registered = false;\n        }\n    }\n\n    public void Dispose()\n    {\n        if (_registered) Context.UnregisterItem(_itemId);\n    }\n\n    private async Task HandleSelect()\n    {\n        if (Disabled) return;\n        await Context.OnSelect.InvokeAsync(Value);\n    }\n}\n"
    },
    {
      "path": "UI/Select/SelectLabel.razor",
      "content": "@namespace Lumeo\n\n<div id=\"@LabelId\" class=\"@CssClass\" @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</div>\n\n@code {\n    [CascadingParameter] public SelectGroup.SelectGroupContext? GroupContext { get; set; }\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private string LabelId => GroupContext?.LabelId ?? $\"select-label-{Guid.NewGuid():N}\";\n\n    private string CssClass => $\"px-2 py-1.5 text-sm font-semibold {Class}\".Trim();\n}\n"
    },
    {
      "path": "UI/Select/SelectTrigger.razor",
      "content": "@namespace Lumeo\n@inject Lumeo.Services.Localization.ILumeoLocalizer L\n\n<button type=\"button\" id=\"@_triggerId\"\n        role=\"combobox\"\n        aria-haspopup=\"listbox\"\n        aria-expanded=\"@Context.IsOpen.ToString().ToLower()\"\n        aria-controls=\"@Context.ContentId\"\n        aria-activedescendant=\"@(Context.IsOpen ? Context.FocusedItemId : null)\"\n        class=\"@CssClass\"\n        disabled=\"@Context.Disabled\"\n        @onclick=\"Toggle\"\n        @onkeydown=\"HandleKeyDown\"\n        @attributes=\"AdditionalAttributes\">\n    @if (Context.Multiple && Context.Values != null && Context.Values.Count > 0)\n    {\n        <div class=\"flex flex-wrap gap-1 items-center flex-1 min-w-0\">\n            @{\n                var displayed = Context.Values.Take(Context.MaxDisplayTags).ToList();\n                var remaining = Context.Values.Count - Context.MaxDisplayTags;\n            }\n            @foreach (var val in displayed)\n            {\n                <span class=\"inline-flex items-center gap-1 rounded-md border border-border/40 bg-secondary px-1.5 py-0.5 text-xs font-medium text-secondary-foreground\">\n                    @val\n                    <button type=\"button\" class=\"cursor-pointer rounded-full hover:bg-foreground/20\" aria-label=\"@($\"Remove {val}\")\" @onclick=\"() => RemoveValue(val)\" @onclick:stopPropagation=\"true\">\n                        <Blazicon Svg=\"Lucide.X\" class=\"h-3 w-3\" />\n                    </button>\n                </span>\n            }\n            @if (remaining > 0)\n            {\n                <span class=\"text-xs text-muted-foreground\">+@remaining more</span>\n            }\n        </div>\n    }\n    else if (HasValue)\n    {\n        @ChildContent\n    }\n    else\n    {\n        <span class=\"text-muted-foreground\">@(Placeholder ?? L[\"Select.Placeholder\"])</span>\n    }\n\n    <div class=\"flex items-center gap-1 ms-auto shrink-0\">\n        @if (Context.Clearable && HasValue)\n        {\n            <button type=\"button\" class=\"cursor-pointer hover:text-foreground\" aria-label=\"Clear selection\" @onclick=\"HandleClear\" @onclick:stopPropagation=\"true\">\n                <Blazicon Svg=\"Lucide.X\" class=\"h-3.5 w-3.5 opacity-50 hover:opacity-100\" />\n            </button>\n        }\n        <Blazicon Svg=\"Lucide.ChevronDown\" class=\"@($\"h-4 w-4 shrink-0 opacity-50 transition-transform duration-200 {(Context.IsOpen ? \"rotate-180\" : \"\")}\")\" />\n    </div>\n</button>\n\n@code {\n    [CascadingParameter] public Select.SelectContext Context { get; set; } = default!;\n    [Parameter] public RenderFragment? ChildContent { get; set; }\n    [Parameter] public string? Placeholder { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private readonly string _triggerId = $\"select-trigger-{Guid.NewGuid():N}\";\n\n    private bool HasValue => Context.Multiple\n        ? Context.Values != null && Context.Values.Count > 0\n        : !string.IsNullOrEmpty(Context.Value);\n\n    private string CssClass => $\"flex h-9 w-full cursor-pointer items-center justify-between whitespace-nowrap rounded-md border {(Context.Invalid ? \"border-destructive\" : \"border-input\")} bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 {(Context.Invalid ? \"focus-visible:ring-destructive/20\" : \"focus-visible:ring-ring\")} focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 {Class}\".Trim();\n\n    private async Task Toggle() => await Context.SetOpen.InvokeAsync(!Context.IsOpen);\n\n    private async Task HandleKeyDown(KeyboardEventArgs e)\n    {\n        switch (e.Key)\n        {\n            case \"ArrowDown\":\n                if (!Context.IsOpen)\n                    await Context.SetOpen.InvokeAsync(true);\n                else\n                    await Context.SetFocusedIndex.InvokeAsync(Context.FocusedIndex + 1);\n                break;\n            case \"ArrowUp\":\n                if (!Context.IsOpen)\n                    await Context.SetOpen.InvokeAsync(true);\n                else\n                    await Context.SetFocusedIndex.InvokeAsync(Context.FocusedIndex > 0 ? Context.FocusedIndex - 1 : 0);\n                break;\n            case \"Enter\":\n            case \" \":\n                if (!Context.IsOpen)\n                    await Context.SetOpen.InvokeAsync(true);\n                else if (Context.FocusedItemValue is not null)\n                    await Context.OnSelect.InvokeAsync(Context.FocusedItemValue);\n                break;\n            case \"Escape\":\n                await Context.SetOpen.InvokeAsync(false);\n                break;\n            case \"Home\":\n                if (Context.IsOpen)\n                    await Context.SetFocusedIndex.InvokeAsync(0);\n                break;\n            case \"End\":\n                if (Context.IsOpen)\n                    await Context.SetFocusedIndex.InvokeAsync(int.MaxValue);\n                break;\n        }\n    }\n\n    private async Task RemoveValue(string value)\n    {\n        await Context.OnSelect.InvokeAsync(value);\n    }\n\n    private async Task HandleClear()\n    {\n        await Context.OnClear.InvokeAsync();\n    }\n}\n"
    }
  ],
  "slots": [
    {
      "name": "ChildContent",
      "owner": "Select",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ItemTemplate",
      "owner": "Select",
      "type": "RenderFragment<object>?",
      "description": "Optional custom render fragment for an item's content. Receives the raw item object."
    },
    {
      "name": "ChildContent",
      "owner": "SelectContent",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "SelectGroup",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "SelectItem",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "SelectLabel",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "SelectTrigger",
      "type": "RenderFragment?",
      "description": null
    }
  ],
  "serviceDependencies": [
    {
      "service": "ComponentInteropService",
      "namespace": ""
    },
    {
      "service": "ILumeoLocalizer",
      "namespace": "Lumeo.Services.Localization"
    }
  ],
  "cascadingDependencies": [
    {
      "name": "FormField",
      "type": "FormField.FormFieldContext?",
      "required": false
    },
    {
      "name": "Context",
      "type": "Select.SelectContext",
      "required": true
    },
    {
      "name": "GroupContext",
      "type": "SelectGroup.SelectGroupContext?",
      "required": false
    }
  ],
  "relatedComponents": [
    {
      "name": "SelectContent",
      "reason": "sub-component"
    },
    {
      "name": "SelectGroup",
      "reason": "sub-component"
    },
    {
      "name": "SelectItem",
      "reason": "sub-component"
    },
    {
      "name": "SelectLabel",
      "reason": "sub-component"
    },
    {
      "name": "SelectTrigger",
      "reason": "sub-component"
    },
    {
      "name": "List",
      "reason": "used internally"
    },
    {
      "name": "Spinner",
      "reason": "used internally"
    }
  ],
  "keyboardInteractions": [],
  "tests": [
    "tests/Lumeo.Docs.Tests/Catalog/CatalogPageTests.cs",
    "tests/Lumeo.Docs.Tests/Services/RegistryServiceTests.cs",
    "tests/Lumeo.Tests/Components/Combobox/ComboboxDataBoundTests.cs",
    "tests/Lumeo.Tests/Components/DataGrid/DataGridCrossTalkTests.cs",
    "tests/Lumeo.Tests/Components/DataGrid/DataGridGroupPanelTests.cs",
    "tests/Lumeo.Tests/Components/DataGrid/DataGridToolbarTogglesTests.cs",
    "tests/Lumeo.Tests/Components/DataGrid/DataGridVirtualizedServerModeTests.cs",
    "tests/Lumeo.Tests/Components/DatePicker/DatePickerTests.cs",
    "tests/Lumeo.Tests/Components/DateTimePicker/DateTimePickerTests.cs",
    "tests/Lumeo.Tests/Components/FileUpload/FileUploadTests.cs",
    "tests/Lumeo.Tests/Components/OtpInput/OtpInputTests.cs",
    "tests/Lumeo.Tests/Components/QueryBuilder/QueryBuilderTests.cs",
    "tests/Lumeo.Tests/Components/Select/SelectDataBoundTests.cs",
    "tests/Lumeo.Tests/Components/Select/SelectTests.cs",
    "tests/Lumeo.Tests/Components/TextReveal/TextRevealTests.cs"
  ],
  "mdSummary": "# Select\n\nNative-feeling styled dropdown with search and groups.\n\n**Package:** `Lumeo` · **Category:** Forms/Selection\n**Docs:** https://lumeo.nativ.sh/components/select\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| Select | FormField | `FormField.FormFieldContext?` | `` |  |\n| Select | ChildContent | `RenderFragment?` | `` |  |\n| Select | Value | `string?` | `` |  |\n| Select | ValueChanged | `EventCallback<string?>` | `` |  |\n| Select | Open | `bool` | `` | Popover open state. Bind via @bind-Open when the Select lives inside a parent that re-renders heavily (e.g. an EditForm with active validation) — otherwise a parameter cascade can close the popover mid-click. In the bound (controlled) mode the parent owns state and the popover survives re-renders. When unbound, the component still functions but is vulnerable to the cascade-close race; binding is the recommended pattern. |\n| Select | OpenChanged | `EventCallback<bool>` | `` |  |\n| Select | IsOpen | `bool` | `` |  |\n| Select | IsOpenChanged | `EventCallback<bool>` | `` |  |\n| Select | Searchable | `bool` | `` |  |\n| Select | Multiple | `bool` | `` |  |\n| Select | Values | `List<string>?` | `` |  |\n| Select | ValuesChanged | `EventCallback<List<string>?>` | `` |  |\n| Select | Clearable | `bool` | `` |  |\n| Select | Creatable | `bool` | `` |  |\n| Select | OnCreate | `EventCallback<string>` | `` |  |\n| Select | MaxDisplayTags | `int` | `3` |  |\n| Select | Disabled | `bool` | `` |  |\n| Select | IsLoading | `bool` | `` |  |\n| Select | Required | `bool` | `` |  |\n| Select | Invalid | `bool` | `` |  |\n| Select | ErrorText | `string?` | `` |  |\n| Select | HelperText | `string?` | `` |  |\n| Select | Label | `string?` | `` |  |\n| Select | Name | `string?` | `` |  |\n| Select | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| Select | Items | `IEnumerable<object>?` | `` | Optional data source. When set, SelectContent renders options from this list instead of ChildContent. |\n| Select | ItemValue | `Func<object, string>` | `it => it?.ToString() ?? \"\"` | Extracts the string value for each item. Defaults to ToString(). |\n| Select | ItemText | `Func<object, string>?` | `` | Extracts the display label for each item. Defaults to ItemValue when null. |\n| Select | ItemGroup | `Func<object, string?>?` | `` | Optional group key extractor. When set, options are grouped under sticky headers. |\n| Select | ItemDisabled | `Func<object, bool>?` | `` | Optional per-item disabled predicate. |\n| Select | ItemTemplate | `RenderFragment<object>?` | `` | Optional custom render fragment for an item's content. Receives the raw item object. |\n| Select | Virtualize | `bool` | `` | When true and Items is set, uses Blazor's Virtualize component for the option list. |\n| Select | ItemSize | `float` | `36f` | Estimated pixel height of each option row used by the Virtualize component. Default 36. |\n| SelectContent | Context | `Select.SelectContext` | `default!` |  |\n| SelectContent | ChildContent | `RenderFragment?` | `` |  |\n| SelectContent | Class | `string?` | `` |  |\n| SelectContent | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| SelectGroup | ChildContent | `RenderFragment?` | `` |  |\n| SelectGroup | Class | `string?` | `` |  |\n| SelectGroup | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| SelectItem | Context | `Select.SelectContext` | `default!` |  |\n| SelectItem | ChildContent | `RenderFragment?` | `` |  |\n| SelectItem | Value | `string` | `\"\"` |  |\n| SelectItem | Disabled | `bool` | `` |  |\n| SelectItem | Class | `string?` | `` |  |\n| SelectItem | SearchValue | `string?` | `` | Optional text used to match against the search input when Searchable is enabled. Falls back to Value. |\n| SelectItem | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| SelectLabel | GroupContext | `SelectGroup.SelectGroupContext?` | `` |  |\n| SelectLabel | ChildContent | `RenderFragment?` | `` |  |\n| SelectLabel | Class | `string?` | `` |  |\n| SelectLabel | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| SelectTrigger | Context | `Select.SelectContext` | `default!` |  |\n| SelectTrigger | ChildContent | `RenderFragment?` | `` |  |\n| SelectTrigger | Placeholder | `string?` | `` |  |\n| SelectTrigger | Class | `string?` | `` |  |\n| SelectTrigger | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- `ChildContent` (Select) — RenderFragment slot\n- `ItemTemplate` (Select) — Optional custom render fragment for an item's content. Receives the raw item object.\n- `ChildContent` (SelectContent) — RenderFragment slot\n- `ChildContent` (SelectGroup) — RenderFragment slot\n- `ChildContent` (SelectItem) — RenderFragment slot\n- `ChildContent` (SelectLabel) — RenderFragment slot\n- `ChildContent` (SelectTrigger) — RenderFragment slot\n\n## Events\n- `ValueChanged` (`EventCallback<string?>`)\n- `OpenChanged` (`EventCallback<bool>`)\n- `IsOpenChanged` (`EventCallback<bool>`)\n- `ValuesChanged` (`EventCallback<List<string>?>`)\n- `OnCreate` (`EventCallback<string>`)\n\n## Sub-components\n- `SelectContent`, `SelectGroup`, `SelectItem`, `SelectLabel`, `SelectTrigger`\n\n## Cascading values it expects\n- `FormField` (`FormField.FormFieldContext?`)\n- `Context` (`Select.SelectContext`)\n- `GroupContext` (`SelectGroup.SelectGroupContext?`)\n\n## Services it injects\n- `ComponentInteropService` ()\n- `ILumeoLocalizer` (Lumeo.Services.Localization)\n\n## Keyboard\n—\n\n## Example\n```razor\n<Select />\n```\n\n## CSS variables\n`--color-accent`, `--color-accent-foreground`, `--color-background`, `--color-border`, `--color-destructive`, `--color-foreground`, `--color-input`, `--color-muted`, `--color-muted-foreground`, `--color-popover`, `--color-popover-foreground`, `--color-ring`, `--color-secondary`, `--color-secondary-foreground`, `--radius`\n\n## Files\n- `UI/Select/Select.razor`\n- `UI/Select/SelectContent.razor`\n- `UI/Select/SelectGroup.razor`\n- `UI/Select/SelectItem.razor`\n- `UI/Select/SelectLabel.razor`\n- `UI/Select/SelectTrigger.razor`\n"
}