Metadata-Version: 2.4
Name: anycomplextree
Version: 0.1.1
Summary: An anywidget wrapping react-complex-tree
Requires-Python: >=3.12
Requires-Dist: anywidget>=0.9
Requires-Dist: traitlets
Description-Content-Type: text/markdown

# anycomplextree

An [anywidget](https://anywidget.dev) that wraps [react-complex-tree](https://rct.lukasbach.com/), giving you an interactive tree view inside Jupyter / JupyterLab / VS Code / Marimo notebooks with two-way state sync to Python.

## Features

- Hierarchical tree rendering powered by `react-complex-tree`
- Two-way sync for selection, expansion, and focus
- Python-side validation of tree structure with clear error messages
- Single bundled ESM asset — no CDN fetches at runtime

## Installation

```bash
uv add anycomplextree
```

## Quick start

```python
from anycomplextree import ComplexTree

tree = ComplexTree(
    items={
        "root": {
            "isFolder": True,
            "children": ["fruits", "meals"],
            "data": "Root",
        },
        "fruits": {
            "isFolder": True,
            "children": ["apple", "orange", "banana"],
            "data": "Fruits",
        },
        "apple":  {"data": "Apple"},
        "orange": {"data": "Orange"},
        "banana": {"data": "Banana"},
        "meals": {
            "isFolder": True,
            "children": ["pasta", "pizza"],
            "data": "Meals",
        },
        "pasta": {"data": "Pasta"},
        "pizza": {"data": "Pizza"},
    },
    root_item="root",
)
tree
```

## The items model

Each entry in `items` represents one node. The dict **key** is the node's identifier — react-complex-tree calls it `index`. The key, the optional `index` field on the item, and the entries inside parent `children` arrays must all be the same string.

| field      | type       | description                                                |
| ---------- | ---------- | ---------------------------------------------------------- |
| `index`    | `str`      | Unique identifier. Auto-filled from the dict key if omitted. |
| `data`     | `Any`      | Per-item payload. By default used as the display title.   |
| `isFolder` | `bool`     | Whether the item can have children.                        |
| `children` | `list[str]` | IDs of child items (must exist as keys in `items`).       |

### Using UUIDs

Since the dict key *is* the identifier, just use UUID strings as keys:

```python
import uuid
apple_id = str(uuid.uuid4())
fruits_id = str(uuid.uuid4())

tree = ComplexTree(
    items={
        fruits_id: {"isFolder": True, "children": [apple_id], "data": "Fruits"},
        apple_id:  {"data": "Apple"},
        "root":    {"isFolder": True, "children": [fruits_id], "data": "Root"},
    },
)

tree.selected_items  # -> [apple_id] after you click Apple
```

## Traits

All traits are two-way synced between Python and the browser.

| trait            | type              | description                                         |
| ---------------- | ----------------- | --------------------------------------------------- |
| `items`          | `dict`            | The tree contents (see above).                      |
| `root_item`      | `str`             | Key of the root node. Defaults to `"root"`.         |
| `selected_items` | `list[str]`       | Currently selected item IDs.                        |
| `expanded_items` | `list[str]`       | Currently expanded folder IDs.                      |
| `focused_item`   | `str \| None`     | Currently focused item ID.                          |

Read, write, or observe them like any traitlet:

```python
tree.expanded_items = ["root", "fruits"]
tree.selected_items = ["apple"]
tree.focused_item   = "apple"

tree.observe(lambda change: print("selection:", change["new"]), names="selected_items")
```

> **Note:** traitlets only fires change events when the trait is reassigned, not when mutated in place. Use `tree.selected_items = [...]`, not `tree.selected_items.append(...)`.

## Validation

`items` and `root_item` are validated on assignment. You'll get a `TraitError` with a clear message for:

- A dict key that doesn't match an explicit `index` field
- A `children` entry that doesn't exist in `items`
- A `root_item` that isn't a key in `items`

## Development

The widget bundle lives in `js/` and builds to `anycomplextree/static/`.

```bash
# one-time install
cd js && npm install

# rebuild after JS changes
npm run build

# watch mode during development
npm run dev
```

On the Python side:

```bash
uv sync
uv run jupyter lab
```

## License

MIT
