Trees, by Pierre, for spaday
spaday-trees wraps Pierre's imperative FileTree in a <spaday-tree> custom element and exposes it
as the Python Tree component. The first release supports reactive path replacement, selected paths,
search, Git status, and path-based selection-change / search-change events.
Setting selected_paths reveals the selection — ancestor directories are expanded automatically and
the first selected path is scrolled into view. This reveal behaviour is a supported contract. A bare
string is coerced to a one-element list; other non-list values raise a TypeError.
The virtualized tree needs a height: give the element or an ancestor one (the quick example uses
.style(height="22rem")). Without one, the package stylesheet applies a min-height fallback of
200px, overridable through the --trees-min-height CSS variable. A tree that still measures zero
height with non-empty paths logs a one-time console warning.
The tree follows the ecosystem's page-mode convention: wa-dark on the root (e.g.
App.bind_root_class("wa-dark", …)) switches it to its dark colours and wa-light flips a nested
island back — no consumer CSS needed.
- Build a reactive project tree — guided first application.
- Synchronize paths, selection, and search — task-focused recipes.
- API reference — props, events, and package descriptor.
- Why Trees uses an imperative wrapper — integration design and tradeoffs.
This app filters the tree as you type. The button changes its selected path through spaday's reactive store; selecting and expanding nodes in the tree remains client-side.
from spaday import SetField, element
from spaday.backends.starlette import serve
from spaday_trees import Tree
paths = [
"README.md",
"pyproject.toml",
"src/app.py",
"src/components/tree.py",
"tests/test_app.py",
]
tree = (
Tree(paths=paths)
.bind("search", "query")
.bind("selected_paths", "selected")
.style(height="22rem")
)
page = (
element("main")
.style(max_width="42rem", margin="2rem auto", font_family="system-ui")
.child(element("h1").text("Project files"))
.child(
element("input", type="search", placeholder="Filter files…")
.bind("value", "query", mode="two-way")
.style(width="100%", padding="0.6rem", margin_bottom="0.75rem")
)
.child(element("button").text("Select README").on("click", SetField("selected", ["README.md"])))
.child(tree)
)
app = serve(page, packages=["trees"], store={"query": "", "selected": []})Save this as app.py, then run pip install spaday-trees starlette uvicorn and
uvicorn app:app. Open http://127.0.0.1:8000.
Installing this project registers the trees entry point with spaday. The equivalent explicit forms
are packages=[spaday_trees.package] and packages=["spaday_trees:package"].
The integration pins @pierre/trees 1.0.0-beta.5. Rename, drag-and-drop persistence, custom
composition renderers, and SSR/hydration are intentionally deferred while its beta API settles.
Rows render inside the shadow root of the engine's <file-tree-container> element, so innerText
or childElementCount on the <spaday-tree> host show nothing even when rendering works. Row
labels are fragmented for truncation, so text selectors fail. Use the stable hooks instead: each
rendered row carries [data-item-path] (its path, directories with a trailing /) and
[role="treeitem"].
python -m pip install -e ".[examples]"
python -m spaday_trees.exampleOpen http://127.0.0.1:8016 to inspect the complete project-explorer example: reactive
server path updates, selection, built-in search, Git status, responsive styling, and client events logged
by Python. It passes the local package descriptor directly, so it does not install or resolve the
integration from GitHub.
Note
This library was generated using copier from the Base Python Project Template repository.