{
  "name": "Splitter",
  "category": "Navigation",
  "subcategory": null,
  "description": "Resizable split pane for horizontal/vertical layouts.",
  "thumbnail": "/preview-cards/splitter.png",
  "hasDocsPage": true,
  "nugetPackage": "Lumeo",
  "files": [
    "UI/Splitter/Splitter.razor",
    "UI/Splitter/SplitterDivider.razor",
    "UI/Splitter/SplitterPane.razor"
  ],
  "dependencies": [],
  "packageDependencies": [],
  "cssVars": [
    "--color-border",
    "--color-primary",
    "--color-ring"
  ],
  "api": {
    "name": "Splitter",
    "category": "Navigation",
    "subcategory": null,
    "description": "Resizable split pane for horizontal/vertical layouts.",
    "nugetPackage": "Lumeo",
    "files": [
      "UI/Splitter/Splitter.razor",
      "UI/Splitter/SplitterDivider.razor",
      "UI/Splitter/SplitterPane.razor"
    ],
    "namespace": "Lumeo",
    "inheritsFrom": null,
    "implements": [],
    "parameters": [
      {
        "name": "Orientation",
        "type": "SplitterOrientation",
        "default": "SplitterOrientation.Horizontal",
        "description": null,
        "isCascading": false,
        "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": [
      {
        "name": "SplitterOrientation",
        "values": [
          "Horizontal",
          "Vertical"
        ],
        "description": null
      }
    ],
    "records": [
      {
        "name": "SplitterContext",
        "signature": "SplitterContext(\n        string GroupId,\n        SplitterOrientation Orientation,\n        List<object> Children,\n        Action<object> RegisterChild,\n        Action<object> UnregisterChild,\n        Func<object, double, Task> ResizeNeighbors\n    )",
        "description": null
      }
    ],
    "cssVars": [
      "--color-border",
      "--color-primary",
      "--color-ring"
    ],
    "examples": [
      {
        "title": "Two Panes — Horizontal",
        "code": "<Splitter Orientation=\"Splitter.SplitterOrientation.Horizontal\"\n          Class=\"min-h-[200px] rounded-lg border\">\n    <SplitterPane Size=\"50\">Left</SplitterPane>\n    <SplitterDivider />\n    <SplitterPane Size=\"50\">Right</SplitterPane>\n</Splitter>"
      },
      {
        "title": "Three Panes — Sidebar + Main + Aside",
        "code": "<Splitter Orientation=\"Splitter.SplitterOrientation.Horizontal\"\n          Class=\"min-h-[260px] rounded-lg border\">\n    <SplitterPane Size=\"20\" MinSize=\"10\">Sidebar</SplitterPane>\n    <SplitterDivider />\n    <SplitterPane Size=\"55\">Main</SplitterPane>\n    <SplitterDivider />\n    <SplitterPane Size=\"25\" MinSize=\"10\">Aside</SplitterPane>\n</Splitter>"
      },
      {
        "title": "Vertical Stack",
        "code": "<Splitter Orientation=\"Splitter.SplitterOrientation.Vertical\"\n          Class=\"h-[320px] rounded-lg border\">\n    <SplitterPane Size=\"60\">Editor</SplitterPane>\n    <SplitterDivider />\n    <SplitterPane Size=\"40\">Terminal</SplitterPane>\n</Splitter>"
      },
      {
        "title": "Nested — Horizontal containing Vertical",
        "code": "<Splitter Orientation=\"Splitter.SplitterOrientation.Horizontal\"\n          Class=\"h-[360px] rounded-lg border\">\n    <SplitterPane Size=\"25\">Files</SplitterPane>\n    <SplitterDivider />\n    <SplitterPane Size=\"75\">\n        <Splitter Orientation=\"Splitter.SplitterOrientation.Vertical\" Class=\"h-full\">\n            <SplitterPane Size=\"65\">Code</SplitterPane>\n            <SplitterDivider />\n            <SplitterPane Size=\"35\">Output</SplitterPane>\n        </Splitter>\n    </SplitterPane>\n</Splitter>"
      }
    ],
    "subComponents": {
      "SplitterDivider": {
        "componentName": "SplitterDivider",
        "fileName": "SplitterDivider.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [
          "IDisposable"
        ],
        "parameters": [
          {
            "name": "Context",
            "type": "Splitter.SplitterContext",
            "default": "default!",
            "description": null,
            "isCascading": true,
            "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
      },
      "SplitterPane": {
        "componentName": "SplitterPane",
        "fileName": "SplitterPane.razor",
        "namespace": "Lumeo",
        "inheritsFrom": null,
        "implements": [
          "IDisposable"
        ],
        "parameters": [
          {
            "name": "Context",
            "type": "Splitter.SplitterContext",
            "default": "default!",
            "description": null,
            "isCascading": true,
            "captureUnmatched": false
          },
          {
            "name": "Size",
            "type": "double",
            "default": null,
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "MinSize",
            "type": "double",
            "default": "10",
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "MaxSize",
            "type": "double",
            "default": "90",
            "description": null,
            "isCascading": false,
            "captureUnmatched": false
          },
          {
            "name": "Collapsible",
            "type": "bool",
            "default": null,
            "description": null,
            "isCascading": false,
            "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
      }
    },
    "parseFailed": false,
    "parseError": null
  },
  "$schema": "https://lumeo.nativ.sh/registry-component-schema.json",
  "key": "splitter",
  "docsUrl": "https://lumeo.nativ.sh/components/splitter",
  "sourceUrl": [
    {
      "file": "UI/Splitter/Splitter.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Splitter/Splitter.razor"
    },
    {
      "file": "UI/Splitter/SplitterDivider.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Splitter/SplitterDivider.razor"
    },
    {
      "file": "UI/Splitter/SplitterPane.razor",
      "url": "https://github.com/Brain2k-0005/Lumeo/tree/master/src/Lumeo/UI/Splitter/SplitterPane.razor"
    }
  ],
  "source": [
    {
      "path": "UI/Splitter/Splitter.razor",
      "content": "@namespace Lumeo\n\n@{\n    var context = new SplitterContext(\n        _groupId,\n        Orientation,\n        _children,\n        RegisterChild,\n        UnregisterChild,\n        ResizeNeighbors\n    );\n}\n\n<div id=\"@_groupId\"\n     class=\"@CssClass\"\n     style=\"min-width:0;min-height:0;\"\n     data-splitter-orientation=\"@OrientationString\"\n     @attributes=\"AdditionalAttributes\">\n    <CascadingValue Value=\"context\" IsFixed=\"false\">\n        @ChildContent\n    </CascadingValue>\n</div>\n\n@inject ComponentInteropService Interop\n\n@code {\n    [Parameter] public SplitterOrientation Orientation { get; set; } = SplitterOrientation.Horizontal;\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 string _groupId = $\"splitter-{Guid.NewGuid():N}\";\n    private readonly List<object> _children = new();\n    private bool _distributed;\n\n    private string OrientationString => Orientation == SplitterOrientation.Horizontal ? \"horizontal\" : \"vertical\";\n\n    private string CssClass =>\n        Orientation == SplitterOrientation.Horizontal\n            ? $\"flex flex-row h-full w-full {Class}\".Trim()\n            : $\"flex flex-col h-full w-full {Class}\".Trim();\n\n    private void RegisterChild(object child)\n    {\n        if (!_children.Contains(child))\n            _children.Add(child);\n    }\n\n    private void UnregisterChild(object child)\n    {\n        _children.Remove(child);\n    }\n\n    private IEnumerable<SplitterPaneHandle> Panes =>\n        _children.OfType<SplitterPaneHandle>();\n\n    protected override void OnAfterRender(bool firstRender)\n    {\n        if (firstRender || !_distributed)\n        {\n            DistributeInitialSizes();\n        }\n    }\n\n    private void DistributeInitialSizes()\n    {\n        var panes = Panes.ToList();\n        if (panes.Count == 0) return;\n\n        var unsetPanes = panes.Where(p => p.Size <= 0).ToList();\n        if (unsetPanes.Count == 0)\n        {\n            _distributed = true;\n            return;\n        }\n\n        var usedSpace = panes.Where(p => p.Size > 0).Sum(p => p.Size);\n        var remaining = Math.Max(0, 100 - usedSpace);\n        var share = remaining / unsetPanes.Count;\n\n        foreach (var pane in unsetPanes)\n        {\n            pane.Size = share;\n        }\n\n        _distributed = true;\n        StateHasChanged();\n    }\n\n    internal async Task ResizeNeighbors(object dividerToken, double deltaPixels)\n    {\n        var idx = _children.IndexOf(dividerToken);\n        if (idx < 0) return;\n\n        SplitterPaneHandle? before = null;\n        for (var i = idx - 1; i >= 0; i--)\n        {\n            if (_children[i] is SplitterPaneHandle p) { before = p; break; }\n        }\n\n        SplitterPaneHandle? after = null;\n        for (var i = idx + 1; i < _children.Count; i++)\n        {\n            if (_children[i] is SplitterPaneHandle p) { after = p; break; }\n        }\n\n        if (before is null || after is null) return;\n\n        var dimension = Orientation == SplitterOrientation.Horizontal ? \"width\" : \"height\";\n        var totalSize = await Interop.GetElementDimension(_groupId, dimension);\n        if (totalSize <= 0) return;\n\n        var deltaPercent = (deltaPixels / totalSize) * 100.0;\n\n        var newBefore = before.Size + deltaPercent;\n        var newAfter = after.Size - deltaPercent;\n\n        if (newBefore < before.MinSize || newBefore > before.MaxSize) return;\n        if (newAfter < after.MinSize || newAfter > after.MaxSize) return;\n\n        before.Size = newBefore;\n        after.Size = newAfter;\n\n        StateHasChanged();\n    }\n\n    public enum SplitterOrientation { Horizontal, Vertical }\n\n    public class SplitterPaneHandle\n    {\n        public double Size { get; set; }\n        public double MinSize { get; set; } = 10;\n        public double MaxSize { get; set; } = 90;\n    }\n\n    public class SplitterDividerToken { }\n\n    public record SplitterContext(\n        string GroupId,\n        SplitterOrientation Orientation,\n        List<object> Children,\n        Action<object> RegisterChild,\n        Action<object> UnregisterChild,\n        Func<object, double, Task> ResizeNeighbors\n    );\n}\n"
    },
    {
      "path": "UI/Splitter/SplitterDivider.razor",
      "content": "@namespace Lumeo\n@implements IDisposable\n\n<div id=\"@_dividerId\"\n     role=\"separator\"\n     tabindex=\"0\"\n     class=\"@CssClass\"\n     @onpointerdown=\"OnPointerDown\"\n     @onpointerdown:preventDefault=\"true\"\n     @onpointermove=\"OnPointerMove\"\n     @onpointerup=\"OnPointerUp\"\n     @onpointercancel=\"OnPointerUp\"\n     @onkeydown=\"OnKeyDown\"\n     @onkeydown:preventDefault=\"true\"\n     @attributes=\"AdditionalAttributes\">\n</div>\n\n@inject ComponentInteropService Interop\n@inject ThemeService Theme\n\n@code {\n    [CascadingParameter] public Splitter.SplitterContext Context { get; set; } = default!;\n    [Parameter] public string? Class { get; set; }\n    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }\n\n    private readonly string _dividerId = $\"splitter-divider-{Guid.NewGuid():N}\";\n    private readonly Splitter.SplitterDividerToken _token = new();\n    private bool _dragging;\n    private long _pointerId;\n    private double _lastX;\n    private double _lastY;\n\n    private bool IsHorizontal => Context?.Orientation == Splitter.SplitterOrientation.Horizontal;\n\n    private string CssClass =>\n        IsHorizontal\n            ? $\"shrink-0 w-1 self-stretch bg-border hover:bg-primary/50 transition-colors cursor-col-resize touch-none select-none focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring {Class}\".Trim()\n            : $\"shrink-0 h-1 w-full bg-border hover:bg-primary/50 transition-colors cursor-row-resize touch-none select-none focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring {Class}\".Trim();\n\n    protected override void OnInitialized()\n    {\n        Context?.RegisterChild(_token);\n    }\n\n    private async Task OnPointerDown(Microsoft.AspNetCore.Components.Web.PointerEventArgs e)\n    {\n        _dragging = true;\n        _pointerId = e.PointerId;\n        _lastX = e.ClientX;\n        _lastY = e.ClientY;\n        await Interop.SetPointerCaptureOnElement(_dividerId, _pointerId);\n    }\n\n    private async Task OnPointerMove(Microsoft.AspNetCore.Components.Web.PointerEventArgs e)\n    {\n        if (!_dragging) return;\n        if (Context is null) return;\n\n        var delta = IsHorizontal ? e.ClientX - _lastX : e.ClientY - _lastY;\n        _lastX = e.ClientX;\n        _lastY = e.ClientY;\n\n        if (Math.Abs(delta) < 0.001) return;\n\n        await Context.ResizeNeighbors(_token, delta);\n    }\n\n    private async Task OnPointerUp(Microsoft.AspNetCore.Components.Web.PointerEventArgs e)\n    {\n        if (!_dragging) return;\n        _dragging = false;\n        await Interop.ReleasePointerCaptureOnElement(_dividerId, _pointerId);\n    }\n\n    // Keyboard resize: arrow keys move the divider by a fixed 10px step,\n    // matching the same Context.ResizeNeighbors path used by pointer drag.\n    // @onkeydown:preventDefault=\"true\" on the element stops page scroll.\n    // In RTL, ArrowLeft/Right are swapped for horizontal splitters so the key\n    // that points toward the visual \"start\" always shrinks the leading panel.\n    private async Task OnKeyDown(KeyboardEventArgs e)\n    {\n        if (Context is null) return;\n        const double step = 10.0;\n\n        var isRtl = IsHorizontal && Theme.CurrentDirection == LayoutDirection.Rtl;\n        var delta = (IsHorizontal, e.Key) switch\n        {\n            (true,  \"ArrowLeft\")  => isRtl ?  step : -step,\n            (true,  \"ArrowRight\") => isRtl ? -step :  step,\n            (false, \"ArrowUp\")    => -step,\n            (false, \"ArrowDown\")  =>  step,\n            _ => 0.0\n        };\n\n        if (delta != 0.0)\n        {\n            await Context.ResizeNeighbors(_token, delta);\n        }\n    }\n\n    public void Dispose()\n    {\n        Context?.UnregisterChild(_token);\n    }\n}\n"
    },
    {
      "path": "UI/Splitter/SplitterPane.razor",
      "content": "@namespace Lumeo\n@implements IDisposable\n\n<div class=\"@CssClass\"\n     style=\"@InlineStyle\"\n     @attributes=\"AdditionalAttributes\">\n    @ChildContent\n</div>\n\n@code {\n    [CascadingParameter] public Splitter.SplitterContext Context { get; set; } = default!;\n    [Parameter] public double Size { get; set; }\n    [Parameter] public double MinSize { get; set; } = 10;\n    [Parameter] public double MaxSize { get; set; } = 90;\n    [Parameter] public bool Collapsible { 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    internal Splitter.SplitterPaneHandle Handle { get; } = new();\n\n    private string CssClass => $\"overflow-hidden {Class}\".Trim();\n\n    private string InlineStyle\n    {\n        get\n        {\n            // Use flex-grow ratios instead of flex-basis% so vertical splitters work without requiring\n            // an explicit parent height (% basis resolves to auto when parent height is min-height only).\n            var size = Handle.Size.ToString(\"F4\", System.Globalization.CultureInfo.InvariantCulture);\n            return $\"flex: {size} 1 0; min-width: 0; min-height: 0;\";\n        }\n    }\n\n    protected override void OnInitialized()\n    {\n        Handle.Size = Size;\n        Handle.MinSize = MinSize;\n        Handle.MaxSize = MaxSize;\n        Context?.RegisterChild(Handle);\n    }\n\n    protected override void OnParametersSet()\n    {\n        // Track min/max updates but don't overwrite runtime-computed size\n        Handle.MinSize = MinSize;\n        Handle.MaxSize = MaxSize;\n    }\n\n    public void Dispose()\n    {\n        Context?.UnregisterChild(Handle);\n    }\n}\n"
    }
  ],
  "slots": [
    {
      "name": "ChildContent",
      "owner": "Splitter",
      "type": "RenderFragment?",
      "description": null
    },
    {
      "name": "ChildContent",
      "owner": "SplitterPane",
      "type": "RenderFragment?",
      "description": null
    }
  ],
  "serviceDependencies": [
    {
      "service": "ComponentInteropService",
      "namespace": ""
    },
    {
      "service": "ThemeService",
      "namespace": ""
    }
  ],
  "cascadingDependencies": [
    {
      "name": "Context",
      "type": "Splitter.SplitterContext",
      "required": true
    }
  ],
  "relatedComponents": [
    {
      "name": "SplitterDivider",
      "reason": "sub-component"
    },
    {
      "name": "SplitterPane",
      "reason": "sub-component"
    }
  ],
  "keyboardInteractions": [],
  "tests": [
    "tests/Lumeo.Tests/Components/Splitter/SplitterPaneTests.cs",
    "tests/Lumeo.Tests/Components/Splitter/SplitterTests.cs"
  ],
  "mdSummary": "# Splitter\n\nResizable split pane for horizontal/vertical layouts.\n\n**Package:** `Lumeo` · **Category:** Navigation\n**Docs:** https://lumeo.nativ.sh/components/splitter\n\n## Parameters\n| Owner | Name | Type | Default | Description |\n|---|---|---|---|---|\n| Splitter | Orientation | `SplitterOrientation` | `SplitterOrientation.Horizontal` |  |\n| Splitter | ChildContent | `RenderFragment?` | `` |  |\n| Splitter | Class | `string?` | `` |  |\n| Splitter | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| SplitterDivider | Context | `Splitter.SplitterContext` | `default!` |  |\n| SplitterDivider | Class | `string?` | `` |  |\n| SplitterDivider | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n| SplitterPane | Context | `Splitter.SplitterContext` | `default!` |  |\n| SplitterPane | Size | `double` | `` |  |\n| SplitterPane | MinSize | `double` | `10` |  |\n| SplitterPane | MaxSize | `double` | `90` |  |\n| SplitterPane | Collapsible | `bool` | `` |  |\n| SplitterPane | ChildContent | `RenderFragment?` | `` |  |\n| SplitterPane | Class | `string?` | `` |  |\n| SplitterPane | AdditionalAttributes | `Dictionary<string, object>?` | `` |  |\n\n## Slots\n- `ChildContent` (Splitter) — RenderFragment slot\n- `ChildContent` (SplitterPane) — RenderFragment slot\n\n## Events\n- (none)\n\n## Sub-components\n- `SplitterDivider`, `SplitterPane`\n\n## Cascading values it expects\n- `Context` (`Splitter.SplitterContext`)\n\n## Services it injects\n- `ComponentInteropService` ()\n- `ThemeService` ()\n\n## Keyboard\n—\n\n## Example\n```razor\n<Splitter />\n```\n\n## CSS variables\n`--color-border`, `--color-primary`, `--color-ring`\n\n## Files\n- `UI/Splitter/Splitter.razor`\n- `UI/Splitter/SplitterDivider.razor`\n- `UI/Splitter/SplitterPane.razor`\n"
}