{
  "name": "ColorPicker",
  "category": "Forms",
  "subcategory": "Specialized",
  "description": "Hue + saturation/value picker with hex input.",
  "thumbnail": "/preview-cards/color-picker.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/ColorPicker/ColorPicker.razor"
  ],
  "dependencies": [],
  "packageDependencies": [
    "Blazicons.Lucide"
  ],
  "cssVars": [
    "--color-accent",
    "--color-accent-foreground",
    "--color-background",
    "--color-border",
    "--color-destructive",
    "--color-foreground",
    "--color-muted",
    "--color-muted-foreground",
    "--color-popover",
    "--color-popover-foreground",
    "--color-ring",
    "--radius"
  ],
  "api": {
    "name": "ColorPicker",
    "category": "Forms",
    "subcategory": "Specialized",
    "description": "Hue + saturation/value picker with hex input.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/ColorPicker/ColorPicker.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [
      "IAsyncDisposable"
    ],
    "parameters": [
      {
        "name": "FormField",
        "type": "FormField.FormFieldContext?",
        "default": null,
        "description": null,
        "isCascading": true,
        "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": "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": "ShowAlpha",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Presets",
        "type": "List<string>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Disabled",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Open",
        "type": "bool",
        "default": null,
        "description": null,
        "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": "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
      },
      {
        "name": "OpenChanged",
        "type": "EventCallback<bool>",
        "description": null
      },
      {
        "name": "IsOpenChanged",
        "type": "EventCallback<bool>",
        "description": null
      }
    ],
    "enums": [],
    "records": [
      {
        "name": "ColorPickerContext",
        "signature": "ColorPickerContext(string Value, bool IsOpen, EventCallback<string> OnColorChanged, EventCallback<bool> SetOpen, string WrapperId)",
        "description": null
      }
    ],
    "cssVars": [
      "--color-accent",
      "--color-accent-foreground",
      "--color-background",
      "--color-border",
      "--color-destructive",
      "--color-foreground",
      "--color-muted",
      "--color-muted-foreground",
      "--color-popover",
      "--color-popover-foreground",
      "--color-ring",
      "--radius"
    ],
    "examples": [
      {
        "title": "Basic",
        "code": "<ColorPicker @bind-Value=\"_color\" />"
      },
      {
        "title": "With Presets",
        "code": "<ColorPicker @bind-Value=\"_color\" Presets=\"_presets\" />"
      },
      {
        "title": "With Alpha",
        "code": "<ColorPicker @bind-Value=\"_color\" ShowAlpha=\"true\" />"
      },
      {
        "title": "Disabled",
        "code": "<ColorPicker Value=\"#6366f1\" Disabled=\"true\" />"
      }
    ],
    "subComponents": {},
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "color-picker",
  "docsUrl": "https://lumeo.nativ.sh/components/color-picker",
  "sourceUrl": [
    {
      "file": "UI/ColorPicker/ColorPicker.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/ColorPicker/ColorPicker.razor"
    }
  ],
  "source": [
    {
      "path": "UI/ColorPicker/ColorPicker.razor",
      "content": "@namespace Lumeo\n@implements IAsyncDisposable\n\n@{\n    var context = new ColorPickerContext(Value ?? \"#000000\", Open,\n        EventCallback.Factory.Create<string>(this, OnColorChanged),\n        EventCallback.Factory.Create<bool>(this, SetOpen),\n        _wrapperId);\n}\n\n@if (Label is not null && !InsideFormField)\n{\n    <label class=\"text-sm font-medium leading-none\">\n        @Label\n        @if (Required)\n        {\n            <span class=\"text-destructive ml-0.5\">*</span>\n        }\n    </label>\n}\n\n<CascadingValue Value=\"context\" IsFixed=\"false\">\n    <div id=\"@_wrapperId\" class=\"@ContainerClass\" @attributes=\"AdditionalAttributes\">\n        <button type=\"button\"\n                name=\"@Name\"\n                aria-required=\"@Required.ToString().ToLower()\"\n                aria-invalid=\"@EffectiveInvalid.ToString().ToLower()\"\n                class=\"@TriggerClass\"\n                @onclick=\"ToggleOpen\"\n                disabled=\"@Disabled\"\n                aria-label=\"@L[\"ColorPicker.PickColor\"]\">\n            <span class=\"lumeo-cp-chip h-5 w-5 rounded-sm border border-border/40 shrink-0\" style=\"@ChipStyle\"></span>\n            <span class=\"text-sm text-foreground font-mono\">@DisplayHex</span>\n            <Blazicon Svg=\"Lucide.ChevronDown\" class=\"@($\"h-4 w-4 text-muted-foreground ml-auto transition-transform duration-200 {(Open ? \"rotate-180\" : \"\")}\")\" />\n        </button>\n\n        @if (Open)\n        {\n            <div id=\"@_contentId\" class=\"@ContentClass\" role=\"dialog\" aria-label=\"@L[\"ColorPicker.PickColor\"]\"\n                 @onkeydown=\"HandleKeyDown\" tabindex=\"-1\">\n\n                @* Saturation-Value 2D picker *@\n                <div id=\"@_svId\"\n                     class=\"lumeo-cp-sv relative w-full rounded-md overflow-hidden cursor-crosshair touch-none select-none\"\n                     style=\"@SvBackgroundStyle\">\n                    @* Bug A — NOT a theme bug: rgba(255,255,255) white→transparent and rgba(0,0,0) black→transparent\n                       are HSV color-space primitives (saturation and value axes). They are mathematically defined\n                       constants for the SV canvas and must not use CSS theme variables. *@\n                    <div class=\"absolute inset-0\" style=\"background: linear-gradient(to right, rgba(255,255,255,1), rgba(255,255,255,0))\"></div>\n                    <div class=\"absolute inset-0\" style=\"background: linear-gradient(to top, rgba(0,0,0,1), rgba(0,0,0,0))\"></div>\n                    @* Bug C — border-white is intentional (always-white circle contrasts against the colored SV canvas).\n                       The outer shadow uses lumeo-cp-sv-indicator (CSS class) so it adapts to the page theme\n                       via color-mix instead of being hardcoded rgba(0,0,0,0.35). *@\n                    <div class=\"lumeo-cp-sv-indicator absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white pointer-events-none\"\n                         style=\"@SvIndicatorStyle\"></div>\n                </div>\n\n                @* Hue slider *@\n                <div class=\"mt-3\">\n                    <input type=\"range\" min=\"0\" max=\"360\" value=\"@_h\"\n                           class=\"lumeo-cp-slider lumeo-cp-hue w-full cursor-pointer\"\n                           aria-label=\"@L[\"ColorPicker.Hue\"]\" aria-valuemin=\"0\" aria-valuemax=\"360\" aria-valuenow=\"@_h\"\n                           @oninput=\"OnHueChanged\" />\n                </div>\n\n                @if (ShowAlpha)\n                {\n                    <div class=\"mt-2\">\n                        <input type=\"range\" min=\"0\" max=\"100\" value=\"@_alpha\"\n                               class=\"lumeo-cp-slider lumeo-cp-slider-alpha w-full cursor-pointer\"\n                               style=\"--lumeo-cp-color: @SolidColorHex\"\n                               aria-label=\"@L[\"ColorPicker.Opacity\"]\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"@_alpha\"\n                               @oninput=\"OnAlphaChanged\" />\n                    </div>\n                }\n\n                @* Preview + Hex *@\n                <div class=\"mt-3 flex items-center gap-2\">\n                    <div class=\"lumeo-cp-chip h-9 w-9 rounded-md border border-border/40 shrink-0\" style=\"@ChipStyle\"></div>\n                    <input type=\"text\" value=\"@DisplayHex\"\n                           class=\"flex h-9 w-full min-w-0 rounded-md border border-border/60 bg-transparent px-2 text-xs font-mono uppercase focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring\"\n                           @onchange=\"OnHexInput\" maxlength=\"9\"\n                           aria-label=\"@L[\"ColorPicker.HexValue\"]\" />\n                </div>\n\n                @* RGB inputs *@\n                <div class=\"mt-2 grid grid-cols-3 gap-1.5\">\n                    <div>\n                        <input type=\"number\" min=\"0\" max=\"255\" value=\"@_r\"\n                               class=\"flex h-8 w-full min-w-0 rounded-md border border-border/60 bg-transparent px-1.5 text-xs font-mono tabular-nums text-center focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring\"\n                               @onchange=\"OnRChanged\" aria-label=\"@L[\"ColorPicker.Red\"]\" />\n                        <div class=\"mt-0.5 text-[10px] text-muted-foreground text-center\">R</div>\n                    </div>\n                    <div>\n                        <input type=\"number\" min=\"0\" max=\"255\" value=\"@_g\"\n                               class=\"flex h-8 w-full min-w-0 rounded-md border border-border/60 bg-transparent px-1.5 text-xs font-mono tabular-nums text-center focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring\"\n                               @onchange=\"OnGChanged\" aria-label=\"@L[\"ColorPicker.Green\"]\" />\n                        <div class=\"mt-0.5 text-[10px] text-muted-foreground text-center\">G</div>\n                    </div>\n                    <div>\n                        <input type=\"number\" min=\"0\" max=\"255\" value=\"@_b\"\n                               class=\"flex h-8 w-full min-w-0 rounded-md border border-border/60 bg-transparent px-1.5 text-xs font-mono tabular-nums text-center focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring\"\n                               @onchange=\"OnBChanged\" aria-label=\"@L[\"ColorPicker.Blue\"]\" />\n                        <div class=\"mt-0.5 text-[10px] text-muted-foreground text-center\">B</div>\n                    </div>\n                </div>\n\n                @if (Presets is { Count: > 0 })\n                {\n                    <div class=\"mt-3 pt-3 border-t border-border/40\">\n                        <div class=\"flex flex-wrap gap-1.5\">\n                            @foreach (var preset in Presets)\n                            {\n                                <button type=\"button\"\n                                        class=\"@GetSwatchClass()\"\n                                        style=\"background-color: @preset\"\n                                        @onclick=\"() => SelectColor(preset)\"\n                                        title=\"@preset\"\n                                        aria-label=\"@preset\">\n                                    @if (string.Equals(Value, preset, StringComparison.OrdinalIgnoreCase))\n                                    {\n                                        <Blazicon Svg=\"Lucide.Check\" class=\"@CheckIconClass(preset)\" />\n                                    }\n                                </button>\n                            }\n                        </div>\n                    </div>\n                }\n            </div>\n        }\n    </div>\n</CascadingValue>\n\n@if (!InsideFormField)\n{\n    @if (EffectiveInvalid && ErrorText is not null)\n    {\n        <p class=\"text-xs text-destructive\">@ErrorText</p>\n    }\n    else if (HelperText is not null)\n    {\n        <p class=\"text-xs text-muted-foreground\">@HelperText</p>\n    }\n}\n\n@inject ComponentInteropService Interop\n@inject Lumeo.Services.Localization.ILumeoLocalizer L\n\n@code {\n    [CascadingParameter] private FormField.FormFieldContext? FormField { get; set; }\n\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\n    private bool EffectiveInvalid => Invalid || FormField?.HasError == true;\n    private bool InsideFormField => FormField is not null;\n\n    [Parameter] public string? Value { get; set; }\n    [Parameter] public EventCallback<string> ValueChanged { get; set; }\n    [Parameter] public bool ShowAlpha { get; set; }\n    [Parameter] public List<string>? Presets { get; set; }\n    [Parameter] public bool Disabled { get; set; }\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 string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private readonly string _wrapperId = $\"colorpicker-{Guid.NewGuid():N}\";\n    private readonly string _contentId = $\"colorpicker-content-{Guid.NewGuid():N}\";\n    private readonly string _svId = $\"colorpicker-sv-{Guid.NewGuid():N}\";\n    private bool _registered;\n\n    private int _h;\n    private int _s = 100;\n    private int _v = 100;\n    private int _alpha = 100;\n    private int _r, _g, _b;\n    private string? _lastSyncedValue;\n\n    private string ContainerClass => $\"relative inline-block {Class}\".Trim();\n\n    private string TriggerClass => $\"flex items-center gap-2 h-9 px-3 rounded-md border {(EffectiveInvalid ? \"border-destructive ring-1 ring-destructive/20\" : \"border-border/60\")} bg-background text-sm hover:bg-accent hover:text-accent-foreground transition-colors disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-[1.5px] {(EffectiveInvalid ? \"focus-visible:ring-destructive\" : \"focus-visible:ring-ring\")}\";\n\n    private const string ContentClass = \"fixed z-50 w-72 rounded-md border border-border/40 bg-popover p-3 text-popover-foreground shadow-lg animate-fade-in\";\n\n    private string ChipStyle => $\"background-color: {DisplayColor}\";\n    private string DisplayColor => ShowAlpha && _alpha < 100\n        ? $\"rgba({_r}, {_g}, {_b}, {(_alpha / 100.0).ToString(\"0.##\", System.Globalization.CultureInfo.InvariantCulture)})\"\n        : SolidColorHex;\n    private string SolidColorHex => $\"#{_r:X2}{_g:X2}{_b:X2}\";\n    private string DisplayHex => Value ?? SolidColorHex;\n\n    private string SvBackgroundStyle => $\"background-color: hsl({_h}, 100%, 50%)\";\n    private string SvIndicatorStyle => $\"left: {_s.ToString(System.Globalization.CultureInfo.InvariantCulture)}%; top: {(100 - _v).ToString(System.Globalization.CultureInfo.InvariantCulture)}%\";\n\n    private static string GetSwatchClass() =>\n        \"h-6 w-6 rounded-sm border border-border/40 cursor-pointer hover:scale-110 transition-transform flex items-center justify-center focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring\";\n\n    // Bug B — NOT a theme bug: text-black / text-white here is contrast-against-the-swatch-color, not against the\n    // page background. The luminance check picks whichever extreme (black or white) is most readable on top of the\n    // specific preset swatch. This is correct regardless of the page's light/dark theme.\n    private static string CheckIconClass(string color) =>\n        $\"h-3 w-3 {(IsLight(color) ? \"text-black\" : \"text-white\")} drop-shadow-[0_1px_1px_rgba(0,0,0,0.35)]\";\n\n    protected override void OnParametersSet()\n    {\n        var current = Value ?? \"#000000\";\n        if (!string.Equals(current, _lastSyncedValue, StringComparison.OrdinalIgnoreCase))\n        {\n            SyncStateFromHex(current);\n            _lastSyncedValue = current;\n        }\n    }\n\n    private async Task ToggleOpen() { if (!Disabled) await SetOpen(!Open); }\n\n    private async Task SetOpen(bool value)\n    {\n        Open = value;\n        await OpenChanged.InvokeAsync(value);\n    }\n\n    private async Task SelectColor(string color) => await ApplyColor(color);\n\n    private async Task OnColorChanged(string color) => await ApplyColor(color);\n\n    private async Task ApplyColor(string color)\n    {\n        Value = color;\n        SyncStateFromHex(color);\n        _lastSyncedValue = color;\n        await ValueChanged.InvokeAsync(color);\n    }\n\n    private async Task OnHueChanged(ChangeEventArgs e)\n    {\n        if (int.TryParse(e.Value?.ToString(), out var h))\n        {\n            _h = Math.Clamp(h, 0, 360);\n            await ApplyHsv();\n        }\n    }\n\n    private void OnAlphaChanged(ChangeEventArgs e)\n    {\n        if (int.TryParse(e.Value?.ToString(), out var a)) _alpha = Math.Clamp(a, 0, 100);\n    }\n\n    private async Task OnRChanged(ChangeEventArgs e) { if (int.TryParse(e.Value?.ToString(), out var n)) { _r = Math.Clamp(n, 0, 255); await ApplyRgb(); } }\n    private async Task OnGChanged(ChangeEventArgs e) { if (int.TryParse(e.Value?.ToString(), out var n)) { _g = Math.Clamp(n, 0, 255); await ApplyRgb(); } }\n    private async Task OnBChanged(ChangeEventArgs e) { if (int.TryParse(e.Value?.ToString(), out var n)) { _b = Math.Clamp(n, 0, 255); await ApplyRgb(); } }\n\n    private async Task ApplyHsv()\n    {\n        (_r, _g, _b) = HsvToRgb(_h, _s, _v);\n        Value = $\"#{_r:X2}{_g:X2}{_b:X2}\";\n        _lastSyncedValue = Value;\n        await ValueChanged.InvokeAsync(Value);\n    }\n\n    private async Task ApplyRgb()\n    {\n        Value = $\"#{_r:X2}{_g:X2}{_b:X2}\";\n        (_h, _s, _v) = RgbToHsv(_r, _g, _b);\n        _lastSyncedValue = Value;\n        await ValueChanged.InvokeAsync(Value);\n    }\n\n    private async Task OnHexInput(ChangeEventArgs e)\n    {\n        var hex = e.Value?.ToString()?.Trim() ?? \"\";\n        if (!hex.StartsWith('#')) hex = \"#\" + hex;\n        if (hex.Length is 4 or 7 or 9 && IsValidHex(hex))\n        {\n            await ApplyColor(hex);\n        }\n    }\n\n    private async Task HandleKeyDown(KeyboardEventArgs e)\n    {\n        if (e.Key == \"Escape\") await SetOpen(false);\n    }\n\n    protected override async Task OnAfterRenderAsync(bool firstRender)\n    {\n        if (Open && !_registered)\n        {\n            await Interop.PositionFixed(_contentId, _wrapperId, \"start\", false, \"bottom\");\n            await Interop.RegisterClickOutside(_contentId, _wrapperId, async () =>\n            {\n                await SetOpen(false);\n                await InvokeAsync(StateHasChanged);\n            });\n            await Interop.RegisterSvDrag(_svId, async (s, v) =>\n            {\n                _s = (int)Math.Round(Math.Clamp(s, 0, 100));\n                _v = (int)Math.Round(Math.Clamp(v, 0, 100));\n                await ApplyHsv();\n                await InvokeAsync(StateHasChanged);\n            });\n            _registered = true;\n        }\n        else if (!Open && _registered)\n        {\n            await Cleanup();\n        }\n    }\n\n    private async Task Cleanup()\n    {\n        try { await Interop.UnpositionFixed(_contentId); } catch (JSDisconnectedException) { }\n        try { await Interop.UnregisterClickOutside(_contentId); } catch (JSDisconnectedException) { }\n        try { await Interop.UnregisterSvDrag(_svId); } catch (JSDisconnectedException) { }\n        _registered = false;\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        if (_registered) await Cleanup();\n    }\n\n    private void SyncStateFromHex(string hex)\n    {\n        if (!TryParseHex(hex, out var r, out var g, out var b)) return;\n        _r = r; _g = g; _b = b;\n        (_h, _s, _v) = RgbToHsv(r, g, b);\n    }\n\n    private static bool IsValidHex(string hex)\n    {\n        if (string.IsNullOrEmpty(hex) || hex[0] != '#') return false;\n        if (hex.Length is not (4 or 7 or 9)) return false;\n        for (int i = 1; i < hex.Length; i++)\n            if (!Uri.IsHexDigit(hex[i])) return false;\n        return true;\n    }\n\n    private static bool TryParseHex(string hex, out int r, out int g, out int b)\n    {\n        r = g = b = 0;\n        if (!IsValidHex(hex)) return false;\n        if (hex.Length == 4)\n        {\n            r = int.Parse($\"{hex[1]}{hex[1]}\", System.Globalization.NumberStyles.HexNumber);\n            g = int.Parse($\"{hex[2]}{hex[2]}\", System.Globalization.NumberStyles.HexNumber);\n            b = int.Parse($\"{hex[3]}{hex[3]}\", System.Globalization.NumberStyles.HexNumber);\n        }\n        else\n        {\n            r = int.Parse(hex.Substring(1, 2), System.Globalization.NumberStyles.HexNumber);\n            g = int.Parse(hex.Substring(3, 2), System.Globalization.NumberStyles.HexNumber);\n            b = int.Parse(hex.Substring(5, 2), System.Globalization.NumberStyles.HexNumber);\n        }\n        return true;\n    }\n\n    private static bool IsLight(string hex)\n    {\n        if (!TryParseHex(hex, out var r, out var g, out var b)) return false;\n        var luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255.0;\n        return luminance > 0.6;\n    }\n\n    private static (int r, int g, int b) HsvToRgb(int h, int s, int v)\n    {\n        double hd = h / 360.0;\n        double sd = s / 100.0;\n        double vd = v / 100.0;\n\n        if (sd < 0.0001)\n        {\n            var n = (int)Math.Round(vd * 255);\n            return (n, n, n);\n        }\n\n        double sector = hd * 6;\n        int i = (int)Math.Floor(sector);\n        double f = sector - i;\n        double p = vd * (1 - sd);\n        double q = vd * (1 - sd * f);\n        double t = vd * (1 - sd * (1 - f));\n\n        double r, g, b;\n        switch (i % 6)\n        {\n            case 0: r = vd; g = t;  b = p;  break;\n            case 1: r = q;  g = vd; b = p;  break;\n            case 2: r = p;  g = vd; b = t;  break;\n            case 3: r = p;  g = q;  b = vd; break;\n            case 4: r = t;  g = p;  b = vd; break;\n            default: r = vd; g = p; b = q;  break;\n        }\n        return ((int)Math.Round(r * 255), (int)Math.Round(g * 255), (int)Math.Round(b * 255));\n    }\n\n    private static (int h, int s, int v) RgbToHsv(int r, int g, int b)\n    {\n        double rd = r / 255.0, gd = g / 255.0, bd = b / 255.0;\n        double max = Math.Max(rd, Math.Max(gd, bd));\n        double min = Math.Min(rd, Math.Min(gd, bd));\n        double d = max - min;\n\n        double h = 0;\n        if (d > 0.0001)\n        {\n            if (max == rd)      h = 60 * (((gd - bd) / d) % 6);\n            else if (max == gd) h = 60 * ((bd - rd) / d + 2);\n            else                h = 60 * ((rd - gd) / d + 4);\n            if (h < 0) h += 360;\n        }\n        double s = max < 0.0001 ? 0 : d / max;\n        double v = max;\n        return ((int)Math.Round(h), (int)Math.Round(s * 100), (int)Math.Round(v * 100));\n    }\n\n    public record ColorPickerContext(string Value, bool IsOpen, EventCallback<string> OnColorChanged, EventCallback<bool> SetOpen, string WrapperId);\n}\n"
    }
  ],
  "slots": [],
  "serviceDependencies": [
    {
      "service": "ComponentInteropService",
      "namespace": ""
    },
    {
      "service": "ILumeoLocalizer",
      "namespace": "Lumeo.Services.Localization"
    }
  ],
  "cascadingDependencies": [
    {
      "name": "FormField",
      "type": "FormField.FormFieldContext?",
      "required": false
    }
  ],
  "relatedComponents": [],
  "keyboardInteractions": [
    {
      "key": "Escape",
      "action": "HandleKeyDown handles Escape in ColorPicker.razor"
    }
  ],
  "tests": [
    "tests/Lumeo.Docs.Tests/Helpers/DocsTestContext.cs",
    "tests/Lumeo.Tests/Components/ColorPicker/ColorPickerTests.cs"
  ],
  "mdSummary": "# ColorPicker\n\nHue + saturation/value picker with hex input.\n\n**Package:** `Lumeo` · **Category:** Forms/Specialized\n**Docs:** https://lumeo.nativ.sh/components/color-picker\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| ColorPicker | FormField | `FormField.FormFieldContext?` | `` |  |\n| ColorPicker | Required | `bool` | `` |  |\n| ColorPicker | Invalid | `bool` | `` |  |\n| ColorPicker | ErrorText | `string?` | `` |  |\n| ColorPicker | HelperText | `string?` | `` |  |\n| ColorPicker | Label | `string?` | `` |  |\n| ColorPicker | Name | `string?` | `` |  |\n| ColorPicker | Value | `string?` | `` |  |\n| ColorPicker | ValueChanged | `EventCallback<string>` | `` |  |\n| ColorPicker | ShowAlpha | `bool` | `` |  |\n| ColorPicker | Presets | `List<string>?` | `` |  |\n| ColorPicker | Disabled | `bool` | `` |  |\n| ColorPicker | Open | `bool` | `` |  |\n| ColorPicker | OpenChanged | `EventCallback<bool>` | `` |  |\n| ColorPicker | IsOpen | `bool` | `` |  |\n| ColorPicker | IsOpenChanged | `EventCallback<bool>` | `` |  |\n| ColorPicker | Class | `string?` | `` |  |\n| ColorPicker | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- (none)\n\n## Events\n- `ValueChanged` (`EventCallback<string>`)\n- `OpenChanged` (`EventCallback<bool>`)\n- `IsOpenChanged` (`EventCallback<bool>`)\n\n## Sub-components\n- (none)\n\n## Cascading values it expects\n- `FormField` (`FormField.FormFieldContext?`)\n\n## Services it injects\n- `ComponentInteropService` ()\n- `ILumeoLocalizer` (Lumeo.Services.Localization)\n\n## Keyboard\n- `Escape` — HandleKeyDown handles Escape in ColorPicker.razor\n\n## Example\n```razor\n<ColorPicker />\n```\n\n## CSS variables\n`--color-accent`, `--color-accent-foreground`, `--color-background`, `--color-border`, `--color-destructive`, `--color-foreground`, `--color-muted`, `--color-muted-foreground`, `--color-popover`, `--color-popover-foreground`, `--color-ring`, `--radius`\n\n## Files\n- `UI/ColorPicker/ColorPicker.razor`\n"
}