{
  "name": "TreeView",
  "category": "Data Display",
  "subcategory": "Lists & Trees",
  "description": "Hierarchical tree with expand/collapse and selection.",
  "thumbnail": "/preview-cards/tree-view.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/TreeView/TreeView.razor",
    "UI/TreeView/TreeViewNode.razor"
  ],
  "dependencies": [
    "checkbox",
    "list"
  ],
  "packageDependencies": [
    "Blazicons.Lucide"
  ],
  "cssVars": [
    "--color-accent",
    "--color-accent-foreground",
    "--color-input",
    "--color-muted",
    "--color-muted-foreground",
    "--color-ring",
    "--radius"
  ],
  "api": {
    "name": "TreeView",
    "category": "Data Display",
    "subcategory": "Lists & Trees",
    "description": "Hierarchical tree with expand/collapse and selection.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/TreeView/TreeView.razor",
      "UI/TreeView/TreeViewNode.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [],
    "parameters": [
      {
        "name": "Items",
        "type": "List<TreeViewItem<T>>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ShowCheckboxes",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "Expandable",
        "type": "bool",
        "default": "true",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "OnItemClick",
        "type": "EventCallback<TreeViewItem<T>>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "OnItemCheck",
        "type": "EventCallback<TreeViewItem<T>>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ShowSearch",
        "type": "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
      },
      {
        "name": "LoadChildren",
        "type": "Func<TreeViewItem<T>, Task<List<TreeViewItem<T>>>>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "CascadeCheck",
        "type": "bool",
        "default": "true",
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "CheckedValuesChanged",
        "type": "EventCallback<List<T?>>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "NodeTemplate",
        "type": "RenderFragment<TreeViewItem<T>>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "ExpandAll",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "MultiSelect",
        "type": "bool",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "SelectedValues",
        "type": "List<T>?",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      },
      {
        "name": "SelectedValuesChanged",
        "type": "EventCallback<List<T>>",
        "default": null,
        "description": null,
        "isCascading": false,
        "captureUnmatched": false
      }
    ],
    "events": [
      {
        "name": "OnItemClick",
        "type": "EventCallback<TreeViewItem<T>>",
        "description": null
      },
      {
        "name": "OnItemCheck",
        "type": "EventCallback<TreeViewItem<T>>",
        "description": null
      },
      {
        "name": "CheckedValuesChanged",
        "type": "EventCallback<List<T?>>",
        "description": null
      },
      {
        "name": "SelectedValuesChanged",
        "type": "EventCallback<List<T>>",
        "description": null
      }
    ],
    "enums": [],
    "records": [
      {
        "name": "TreeViewItem",
        "signature": "TreeViewItem",
        "description": null
      }
    ],
    "cssVars": [
      "--color-accent",
      "--color-accent-foreground",
      "--color-input",
      "--color-muted",
      "--color-muted-foreground",
      "--color-ring",
      "--radius"
    ],
    "examples": [
      {
        "title": "Basic Tree",
        "code": "<TreeView TItem=\"string\" Items=\"_items\" />"
      },
      {
        "title": "With Checkboxes (Tri-state)",
        "code": "<TreeView TItem=\"string\" Items=\"_items\" ShowCheckboxes=\"true\" CascadeCheck=\"true\" />"
      },
      {
        "title": "With Search",
        "code": "<TreeView TItem=\"string\" Items=\"_items\" ShowSearch=\"true\" />"
      },
      {
        "title": "Multi-select",
        "code": "<TreeView TItem=\"string\" Items=\"_items\" MultiSelect=\"true\" @bind-SelectedValues=\"_selectedValues\" />"
      }
    ],
    "subComponents": {
      "TreeViewNode": {
        "componentName": "TreeViewNode",
        "fileName": "TreeViewNode.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [],
        "parameters": [
          {
            "name": "Item",
            "type": "TreeView<T>.TreeViewItem<T>",
            "default": "default!",
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Level",
            "type": "int",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "ShowCheckboxes",
            "type": "bool",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Expandable",
            "type": "bool",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "OnItemClick",
            "type": "EventCallback<TreeView<T>.TreeViewItem<T>>",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "OnItemCheck",
            "type": "EventCallback<TreeView<T>.TreeViewItem<T>>",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "CascadeCheck",
            "type": "bool",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "MultiSelect",
            "type": "bool",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "SelectedValues",
            "type": "HashSet<T>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "NodeTemplate",
            "type": "RenderFragment<TreeView<T>.TreeViewItem<T>>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "LoadChildren",
            "type": "Func<TreeView<T>.TreeViewItem<T>, Task<List<TreeView<T>.TreeViewItem<T>>>>?",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "OnExpandChanged",
            "type": "EventCallback<TreeView<T>.TreeViewItem<T>>",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "OnSelectionChanged",
            "type": "EventCallback<TreeView<T>.TreeViewItem<T>>",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          }
        ],
        "events": [
          {
            "name": "OnItemClick",
            "type": "EventCallback<TreeView<T>.TreeViewItem<T>>",
            "description": null
          },
          {
            "name": "OnItemCheck",
            "type": "EventCallback<TreeView<T>.TreeViewItem<T>>",
            "description": null
          },
          {
            "name": "OnExpandChanged",
            "type": "EventCallback<TreeView<T>.TreeViewItem<T>>",
            "description": null
          },
          {
            "name": "OnSelectionChanged",
            "type": "EventCallback<TreeView<T>.TreeViewItem<T>>",
            "description": null
          }
        ],
        "enums": [],
        "records": [],
        "parseFailed": false,
        "parseError": null
      }
    },
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "tree-view",
  "docsUrl": "https://lumeo.nativ.sh/components/tree-view",
  "sourceUrl": [
    {
      "file": "UI/TreeView/TreeView.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/TreeView/TreeView.razor"
    },
    {
      "file": "UI/TreeView/TreeViewNode.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/TreeView/TreeViewNode.razor"
    }
  ],
  "source": [
    {
      "path": "UI/TreeView/TreeView.razor",
      "content": "@namespace Lumeo\n@typeparam T\n\n<div class=\"@CssClass\" role=\"tree\" aria-multiselectable=\"@MultiSelect.ToString().ToLowerInvariant()\" @attributes=\"AdditionalAttributes\">\n    @if (ShowSearch)\n    {\n        <div class=\"mb-2\">\n            <input type=\"text\" placeholder=\"Search...\"\n                   class=\"flex h-8 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring\"\n                   value=\"@_searchText\"\n                   @oninput=\"OnSearchInput\" />\n        </div>\n    }\n\n    @if (Items is { Count: > 0 })\n    {\n        @foreach (var item in _displayItems)\n        {\n            <TreeViewNode T=\"T\" @key=\"item\" Item=\"item\" Level=\"0\"\n                          ShowCheckboxes=\"ShowCheckboxes\"\n                          Expandable=\"Expandable\"\n                          CascadeCheck=\"CascadeCheck\"\n                          MultiSelect=\"MultiSelect\"\n                          SelectedValues=\"_selectedValues\"\n                          NodeTemplate=\"NodeTemplate\"\n                          LoadChildren=\"LoadChildren\"\n                          OnItemClick=\"HandleItemClick\"\n                          OnItemCheck=\"HandleItemCheck\"\n                          OnExpandChanged=\"HandleExpandChanged\"\n                          OnSelectionChanged=\"HandleSelectionChanged\" />\n        }\n    }\n</div>\n\n@code {\n    [Parameter] public List<TreeViewItem<T>>? Items { get; set; }\n    [Parameter] public bool ShowCheckboxes { get; set; }\n    [Parameter] public bool Expandable { get; set; } = true;\n    [Parameter] public EventCallback<TreeViewItem<T>> OnItemClick { get; set; }\n    [Parameter] public EventCallback<TreeViewItem<T>> OnItemCheck { get; set; }\n    [Parameter] public bool ShowSearch { get; set; }\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    [Parameter] public Func<TreeViewItem<T>, Task<List<TreeViewItem<T>>>>? LoadChildren { get; set; }\n    [Parameter] public bool CascadeCheck { get; set; } = true;\n    [Parameter] public EventCallback<List<T?>> CheckedValuesChanged { get; set; }\n    [Parameter] public RenderFragment<TreeViewItem<T>>? NodeTemplate { get; set; }\n    [Parameter] public bool ExpandAll { get; set; }\n    [Parameter] public bool MultiSelect { get; set; }\n    [Parameter] public List<T>? SelectedValues { get; set; }\n    [Parameter] public EventCallback<List<T>> SelectedValuesChanged { get; set; }\n\n    private string _searchText = \"\";\n    private List<TreeViewItem<T>>? _previousItems;\n    private bool _previousExpandAll;\n    private List<TreeViewItem<T>> _filteredCache = [];\n    private List<TreeViewItem<T>> _displayItems = [];\n    private HashSet<T> _selectedValues = [];\n\n    private string CssClass => $\"text-sm {Class}\".Trim();\n\n    protected override void OnParametersSet()\n    {\n        if (SelectedValues is not null)\n            _selectedValues = [..SelectedValues];\n\n        bool itemsChanged = !ReferenceEquals(Items, _previousItems);\n        bool expandAllChanged = ExpandAll != _previousExpandAll;\n\n        if (itemsChanged)\n        {\n            _previousItems = Items;\n            if (ExpandAll)\n                ApplyExpandAll(Items ?? [], true);\n            RebuildDisplay();\n        }\n        else if (expandAllChanged)\n        {\n            _previousExpandAll = ExpandAll;\n            ApplyExpandAll(Items ?? [], ExpandAll);\n            RebuildDisplay();\n        }\n    }\n\n    public void ExpandAllNodes()\n    {\n        ApplyExpandAll(Items ?? [], true);\n        StateHasChanged();\n    }\n\n    public void CollapseAllNodes()\n    {\n        ApplyExpandAll(Items ?? [], false);\n        StateHasChanged();\n    }\n\n    private async Task HandleItemClick(TreeViewItem<T> item)\n    {\n        await OnItemClick.InvokeAsync(item);\n    }\n\n    private async Task HandleItemCheck(TreeViewItem<T> item)\n    {\n        if (CascadeCheck && ShowCheckboxes)\n        {\n            SetChildrenChecked(item.Children, item.IsChecked);\n            UpdateAncestorStates(Items ?? []);\n        }\n        await OnItemCheck.InvokeAsync(item);\n\n        if (CheckedValuesChanged.HasDelegate)\n        {\n            var allChecked = CollectChecked(Items ?? []);\n            await CheckedValuesChanged.InvokeAsync(allChecked);\n        }\n\n        StateHasChanged();\n    }\n\n    private Task HandleExpandChanged(TreeViewItem<T> _)\n    {\n        StateHasChanged();\n        return Task.CompletedTask;\n    }\n\n    private async Task HandleSelectionChanged(TreeViewItem<T> item)\n    {\n        if (SelectedValuesChanged.HasDelegate)\n            await SelectedValuesChanged.InvokeAsync([.._selectedValues]);\n        StateHasChanged();\n    }\n\n    private void OnSearchInput(ChangeEventArgs e)\n    {\n        _searchText = e.Value?.ToString() ?? \"\";\n        RebuildDisplay();\n    }\n\n    private void RebuildDisplay()\n    {\n        if (string.IsNullOrWhiteSpace(_searchText))\n        {\n            _filteredCache = [];\n            _displayItems = Items ?? [];\n        }\n        else\n        {\n            _filteredCache = FilterItems(Items ?? [], _searchText);\n            _displayItems = _filteredCache;\n        }\n    }\n\n    private static List<TreeViewItem<T>> FilterItems(List<TreeViewItem<T>> items, string filter)\n    {\n        var result = new List<TreeViewItem<T>>();\n        foreach (var item in items)\n        {\n            var childMatches = item.Children is { Count: > 0 }\n                ? FilterItems(item.Children, filter)\n                : [];\n\n            if (item.Text.Contains(filter, StringComparison.OrdinalIgnoreCase) || childMatches.Count > 0)\n            {\n                result.Add(item with\n                {\n                    Children = childMatches.Count > 0 ? childMatches : item.Children,\n                    IsExpanded = childMatches.Count > 0 || item.IsExpanded\n                });\n            }\n        }\n        return result;\n    }\n\n    private static void SetChildrenChecked(List<TreeViewItem<T>>? children, bool value)\n    {\n        if (children is null) return;\n        foreach (var child in children)\n        {\n            child.IsChecked = value;\n            child.IsIndeterminate = false;\n            SetChildrenChecked(child.Children, value);\n        }\n    }\n\n    private static (bool allChecked, bool anyChecked) UpdateAncestorStates(List<TreeViewItem<T>> items)\n    {\n        bool allChecked = true;\n        bool anyChecked = false;\n\n        foreach (var item in items)\n        {\n            if (item.Children is { Count: > 0 })\n            {\n                var (childAll, childAny) = UpdateAncestorStates(item.Children);\n                if (childAll)\n                {\n                    item.IsChecked = true;\n                    item.IsIndeterminate = false;\n                }\n                else if (childAny || item.IsChecked)\n                {\n                    item.IsIndeterminate = true;\n                    item.IsChecked = false;\n                }\n                else\n                {\n                    item.IsChecked = false;\n                    item.IsIndeterminate = false;\n                }\n            }\n\n            if (item.IsChecked) anyChecked = true;\n            else allChecked = false;\n            if (item.IsIndeterminate) anyChecked = true;\n        }\n\n        return (allChecked, anyChecked);\n    }\n\n    private static List<T?> CollectChecked(List<TreeViewItem<T>> items)\n    {\n        var result = new List<T?>();\n        foreach (var item in items)\n        {\n            if (item.IsChecked) result.Add(item.Value);\n            if (item.Children is { Count: > 0 })\n                result.AddRange(CollectChecked(item.Children));\n        }\n        return result;\n    }\n\n    private static void ApplyExpandAll(List<TreeViewItem<T>> items, bool expanded)\n    {\n        foreach (var item in items)\n        {\n            item.IsExpanded = expanded;\n            if (item.Children is { Count: > 0 })\n                ApplyExpandAll(item.Children, expanded);\n        }\n    }\n\n    public record TreeViewItem<TValue>\n    {\n        public string Text { get; init; } = \"\";\n        public TValue? Value { get; init; }\n        public List<TreeViewItem<TValue>>? Children { get; set; }\n        public bool IsExpanded { get; set; }\n        public bool IsChecked { get; set; }\n        public SvgIcon? Icon { get; init; }\n        public bool IsLeaf { get; init; }\n        public bool IsLoading { get; set; }\n        public bool IsIndeterminate { get; set; }\n        public bool IsDisabled { get; init; }\n        public bool ChildrenLoaded { get; set; }\n    }\n}\n"
    },
    {
      "path": "UI/TreeView/TreeViewNode.razor",
      "content": "@namespace Lumeo\n@typeparam T\n\n<div class=\"group\" role=\"treeitem\"\n     aria-expanded=\"@AriaExpanded\"\n     aria-selected=\"@IsSelected.ToString().ToLowerInvariant()\"\n     aria-disabled=\"@Item.IsDisabled.ToString().ToLowerInvariant()\"\n     tabindex=\"@TabIndex\"\n     @onfocus=\"HandleFocus\"\n     @onblur=\"HandleBlur\"\n     @onkeydown=\"HandleKeyDown\"\n     @onkeydown:stopPropagation=\"true\">\n\n    <div class=\"@NodeClass\" style=\"padding-left: @(Level * 16)px\" @onclick=\"HandleClick\">\n\n        @* Expand chevron / loading spinner / leaf spacer *@\n        @if (Item.IsLoading)\n        {\n            <span class=\"shrink-0 p-0.5 w-6 flex items-center justify-center\">\n                <Blazicon Svg=\"Lucide.Loader\" class=\"h-4 w-4 text-muted-foreground animate-spin\" />\n            </span>\n        }\n        else if (ShowExpandChevron && Expandable)\n        {\n            <button type=\"button\"\n                    class=\"shrink-0 p-0.5 rounded hover:bg-accent transition-colors\"\n                    @onclick=\"ToggleExpand\"\n                    @onclick:stopPropagation=\"true\"\n                    tabindex=\"-1\"\n                    aria-label=\"@ExpandLabel\">\n                <Blazicon Svg=\"Lucide.ChevronRight\" class=\"@ChevronClass\" />\n            </button>\n        }\n        else\n        {\n            <span class=\"w-5\"></span>\n        }\n\n        @* Checkbox *@\n        @if (ShowCheckboxes)\n        {\n            <span @onclick:stopPropagation=\"true\">\n                <Checkbox Checked=\"@Item.IsChecked\"\n                          IsIndeterminate=\"@Item.IsIndeterminate\"\n                          CheckedChanged=\"HandleCheck\"\n                          Disabled=\"@Item.IsDisabled\"\n                          Class=\"mr-1\" />\n            </span>\n        }\n\n        @* Label area: custom template OR default icon + text *@\n        @if (NodeTemplate is not null)\n        {\n            @NodeTemplate(Item)\n        }\n        else\n        {\n            @if (Item.Icon is { } icon)\n            {\n                <Blazicon Svg=\"icon\" class=\"@IconClass\" />\n            }\n            <span class=\"truncate\">@Item.Text</span>\n        }\n    </div>\n\n    @* Children *@\n    @if (HasLoadedChildren && Item.IsExpanded)\n    {\n        <div role=\"group\">\n            @foreach (var child in Item.Children!)\n            {\n                <TreeViewNode T=\"T\" @key=\"child\" Item=\"child\" Level=\"@(Level + 1)\"\n                              ShowCheckboxes=\"ShowCheckboxes\"\n                              Expandable=\"Expandable\"\n                              CascadeCheck=\"CascadeCheck\"\n                              MultiSelect=\"MultiSelect\"\n                              SelectedValues=\"SelectedValues\"\n                              NodeTemplate=\"NodeTemplate\"\n                              LoadChildren=\"LoadChildren\"\n                              OnItemClick=\"OnItemClick\"\n                              OnItemCheck=\"OnItemCheck\"\n                              OnExpandChanged=\"OnExpandChanged\"\n                              OnSelectionChanged=\"OnSelectionChanged\" />\n            }\n        </div>\n    }\n</div>\n\n@code {\n    [Parameter] public TreeView<T>.TreeViewItem<T> Item { get; set; } = default!;\n    [Parameter] public int Level { get; set; }\n    [Parameter] public bool ShowCheckboxes { get; set; }\n    [Parameter] public bool Expandable { get; set; }\n    [Parameter] public EventCallback<TreeView<T>.TreeViewItem<T>> OnItemClick { get; set; }\n    [Parameter] public EventCallback<TreeView<T>.TreeViewItem<T>> OnItemCheck { get; set; }\n\n    [Parameter] public bool CascadeCheck { get; set; }\n    [Parameter] public bool MultiSelect { get; set; }\n    [Parameter] public HashSet<T>? SelectedValues { get; set; }\n    [Parameter] public RenderFragment<TreeView<T>.TreeViewItem<T>>? NodeTemplate { get; set; }\n    [Parameter] public Func<TreeView<T>.TreeViewItem<T>, Task<List<TreeView<T>.TreeViewItem<T>>>>? LoadChildren { get; set; }\n    [Parameter] public EventCallback<TreeView<T>.TreeViewItem<T>> OnExpandChanged { get; set; }\n    [Parameter] public EventCallback<TreeView<T>.TreeViewItem<T>> OnSelectionChanged { get; set; }\n\n    // Roving tabindex: any node that currently holds DOM focus gets tabindex=0 so\n    // the browser can return focus to it. Level-0 nodes are also in the tab sequence\n    // initially (before any node has been focused) so the tree is reachable via Tab.\n    // Child nodes that have never been focused stay at -1 — they are only reachable\n    // via keyboard arrow-key navigation, matching the WAI-ARIA tree pattern.\n    private bool _isFocused;\n    private string TabIndex => (_isFocused || Level == 0) ? \"0\" : \"-1\";\n    private string ExpandLabel => Item.IsExpanded ? \"Collapse\" : \"Expand\";\n\n    private bool HasLoadedChildren => Item.Children is { Count: > 0 };\n\n    private bool ShowExpandChevron =>\n        HasLoadedChildren ||\n        (LoadChildren is not null && !Item.IsLeaf && !Item.ChildrenLoaded);\n\n    private bool IsSelected => SelectedValues?.Contains(Item.Value!) == true;\n\n    private string? AriaExpanded => ShowExpandChevron\n        ? Item.IsExpanded.ToString().ToLowerInvariant()\n        : null;\n\n    private string NodeClass\n    {\n        get\n        {\n            var parts = new List<string>\n            {\n                \"flex items-center gap-1.5 py-1 px-2 rounded-md transition-colors text-sm\",\n                Item.IsDisabled ? \"opacity-50 cursor-not-allowed text-muted-foreground\" : \"cursor-pointer hover:bg-accent/50\"\n            };\n            if (IsSelected && !Item.IsDisabled)\n                parts.Add(\"bg-accent text-accent-foreground\");\n            return string.Join(\" \", parts);\n        }\n    }\n\n    private string ChevronClass =>\n        string.Join(\" \", new[]\n        {\n            \"h-4 w-4 text-muted-foreground transition-transform\",\n            Item.IsExpanded ? \"rotate-90\" : null\n        }.Where(s => !string.IsNullOrWhiteSpace(s)));\n\n    private string IconClass =>\n        Item.IsDisabled\n            ? \"h-4 w-4 text-muted-foreground opacity-50 shrink-0\"\n            : \"h-4 w-4 text-muted-foreground shrink-0\";\n\n    private void HandleFocus()\n    {\n        _isFocused = true;\n    }\n\n    private void HandleBlur()\n    {\n        _isFocused = false;\n    }\n\n    private async Task ToggleExpand()\n    {\n        if (Item.IsExpanded)\n        {\n            Item.IsExpanded = false;\n            await OnExpandChanged.InvokeAsync(Item);\n            return;\n        }\n\n        if (LoadChildren is not null && !Item.ChildrenLoaded && !Item.IsLeaf && !HasLoadedChildren)\n        {\n            Item.IsLoading = true;\n            StateHasChanged();\n\n            try\n            {\n                var loaded = await LoadChildren(Item);\n                Item.Children = loaded;\n                Item.ChildrenLoaded = true;\n\n                // rc.42: if the parent was fully checked before lazy-load, cascade\n                // that state to the freshly-loaded children. Without this, a\n                // checked parent would expand to reveal unchecked children — a\n                // visibly broken tri-state. Indeterminate parents leave loaded\n                // children at their default (unchecked) state, then we\n                // recompute the parent to reflect actual loaded counts.\n                if (Item.IsChecked && !Item.IsIndeterminate)\n                {\n                    CascadeChecked(Item.Children, true);\n                }\n                else if (Item.IsIndeterminate)\n                {\n                    // All loaded children default to unchecked → parent is no\n                    // longer indeterminate if nothing is checked. Recompute.\n                    var anyChecked = Item.Children.Any(c => c.IsChecked || c.IsIndeterminate);\n                    if (!anyChecked)\n                    {\n                        Item.IsIndeterminate = false;\n                        Item.IsChecked = false;\n                    }\n                }\n            }\n            finally\n            {\n                Item.IsLoading = false;\n            }\n        }\n\n        Item.IsExpanded = true;\n        await OnExpandChanged.InvokeAsync(Item);\n    }\n\n    private async Task HandleClick()\n    {\n        if (Item.IsDisabled) return;\n        await OnItemClick.InvokeAsync(Item);\n    }\n\n    private async Task HandleCheck(bool value)\n    {\n        if (Item.IsDisabled) return;\n        Item.IsChecked = value;\n        if (Item.IsIndeterminate && !value)\n            Item.IsIndeterminate = false;\n        await OnItemCheck.InvokeAsync(Item);\n    }\n\n    private async Task HandleKeyDown(KeyboardEventArgs e)\n    {\n        switch (e.Key)\n        {\n            case \"ArrowRight\":\n                if (ShowExpandChevron && Expandable && !Item.IsExpanded)\n                    await ToggleExpand();\n                break;\n\n            case \"ArrowLeft\":\n                if (Item.IsExpanded)\n                {\n                    Item.IsExpanded = false;\n                    await OnExpandChanged.InvokeAsync(Item);\n                }\n                break;\n\n            case \"Enter\":\n            case \" \":\n                if (!Item.IsDisabled)\n                {\n                    if (ShowCheckboxes)\n                    {\n                        if (Item.IsIndeterminate)\n                        {\n                            Item.IsIndeterminate = false;\n                            Item.IsChecked = true;\n                        }\n                        else\n                        {\n                            Item.IsChecked = !Item.IsChecked;\n                        }\n                        await OnItemCheck.InvokeAsync(Item);\n                    }\n                    else\n                    {\n                        await OnItemClick.InvokeAsync(Item);\n                    }\n                }\n                break;\n        }\n    }\n\n    /// <summary>Cascades a check state to a freshly-loaded subtree (rc.42).\n    /// Used after <see cref=\"LoadChildren\"/> resolves so children inherit a\n    /// fully-checked parent's state instead of staying unchecked.</summary>\n    private static void CascadeChecked(List<TreeView<T>.TreeViewItem<T>> items, bool value)\n    {\n        foreach (var item in items)\n        {\n            item.IsChecked = value;\n            item.IsIndeterminate = false;\n            if (item.Children is { Count: > 0 })\n            {\n                CascadeChecked(item.Children, value);\n            }\n        }\n    }\n}\n"
    }
  ],
  "slots": [
    {
      "name": "NodeTemplate",
      "owner": "TreeView",
      "type": "RenderFragment<TreeViewItem<T>>?",
      "description": null
    },
    {
      "name": "NodeTemplate",
      "owner": "TreeViewNode",
      "type": "RenderFragment<TreeView<T>.TreeViewItem<T>>?",
      "description": null
    }
  ],
  "serviceDependencies": [],
  "cascadingDependencies": [],
  "relatedComponents": [
    {
      "name": "TreeViewNode",
      "reason": "sub-component"
    },
    {
      "name": "List",
      "reason": "used internally"
    },
    {
      "name": "Checkbox",
      "reason": "used internally"
    }
  ],
  "keyboardInteractions": [],
  "tests": [
    "tests/Lumeo.Tests/Components/TreeView/TreeViewTests.cs",
    "tests/Lumeo.Tests/Contract/ComponentContractTests.cs"
  ],
  "mdSummary": "# TreeView\n\nHierarchical tree with expand/collapse and selection.\n\n**Package:** `Lumeo` · **Category:** Data Display/Lists & Trees\n**Docs:** https://lumeo.nativ.sh/components/tree-view\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| TreeView | Items | `List<TreeViewItem<T>>?` | `` |  |\n| TreeView | ShowCheckboxes | `bool` | `` |  |\n| TreeView | Expandable | `bool` | `true` |  |\n| TreeView | OnItemClick | `EventCallback<TreeViewItem<T>>` | `` |  |\n| TreeView | OnItemCheck | `EventCallback<TreeViewItem<T>>` | `` |  |\n| TreeView | ShowSearch | `bool` | `` |  |\n| TreeView | Class | `string?` | `` |  |\n| TreeView | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| TreeView | LoadChildren | `Func<TreeViewItem<T>, Task<List<TreeViewItem<T>>>>?` | `` |  |\n| TreeView | CascadeCheck | `bool` | `true` |  |\n| TreeView | CheckedValuesChanged | `EventCallback<List<T?>>` | `` |  |\n| TreeView | NodeTemplate | `RenderFragment<TreeViewItem<T>>?` | `` |  |\n| TreeView | ExpandAll | `bool` | `` |  |\n| TreeView | MultiSelect | `bool` | `` |  |\n| TreeView | SelectedValues | `List<T>?` | `` |  |\n| TreeView | SelectedValuesChanged | `EventCallback<List<T>>` | `` |  |\n| TreeViewNode | Item | `TreeView<T>.TreeViewItem<T>` | `default!` |  |\n| TreeViewNode | Level | `int` | `` |  |\n| TreeViewNode | ShowCheckboxes | `bool` | `` |  |\n| TreeViewNode | Expandable | `bool` | `` |  |\n| TreeViewNode | OnItemClick | `EventCallback<TreeView<T>.TreeViewItem<T>>` | `` |  |\n| TreeViewNode | OnItemCheck | `EventCallback<TreeView<T>.TreeViewItem<T>>` | `` |  |\n| TreeViewNode | CascadeCheck | `bool` | `` |  |\n| TreeViewNode | MultiSelect | `bool` | `` |  |\n| TreeViewNode | SelectedValues | `HashSet<T>?` | `` |  |\n| TreeViewNode | NodeTemplate | `RenderFragment<TreeView<T>.TreeViewItem<T>>?` | `` |  |\n| TreeViewNode | LoadChildren | `Func<TreeView<T>.TreeViewItem<T>, Task<List<TreeView<T>.TreeViewItem<T>>>>?` | `` |  |\n| TreeViewNode | OnExpandChanged | `EventCallback<TreeView<T>.TreeViewItem<T>>` | `` |  |\n| TreeViewNode | OnSelectionChanged | `EventCallback<TreeView<T>.TreeViewItem<T>>` | `` |  |\n\n## Slots\n- `NodeTemplate` (TreeView) — RenderFragment slot\n- `NodeTemplate` (TreeViewNode) — RenderFragment slot\n\n## Events\n- `OnItemClick` (`EventCallback<TreeViewItem<T>>`)\n- `OnItemCheck` (`EventCallback<TreeViewItem<T>>`)\n- `CheckedValuesChanged` (`EventCallback<List<T?>>`)\n- `SelectedValuesChanged` (`EventCallback<List<T>>`)\n\n## Sub-components\n- `TreeViewNode`\n\n## Cascading values it expects\n- (none)\n\n## Services it injects\n- (none)\n\n## Keyboard\n—\n\n## Example\n```razor\n<TreeView />\n```\n\n## CSS variables\n`--color-accent`, `--color-accent-foreground`, `--color-input`, `--color-muted`, `--color-muted-foreground`, `--color-ring`, `--radius`\n\n## Files\n- `UI/TreeView/TreeView.razor`\n- `UI/TreeView/TreeViewNode.razor`\n"
}