Nested trees for pickers (PhoenixKitWeb.Components.TreePicker): built
from a flat, parent-linked list, and the pure operations a picker needs
over them — so no module shows nested records as an indented flat list.
A node is %{id, name, children} plus optional keys:
:type— an atom the picker filters on (pickable,path_skip):icon— a heroicon name for the row:hint— short text beside the name ("top level"):badge— a small badge after the name ("Archived")
Any other key a module puts on a node rides along untouched.
Ids are strings. "root" (root_id/0) is the conventional id of a row
standing for "no parent" — a picker posts it as "".
rows
|> Tree.from_flat(node: &%{name: &1.title, type: :page})
|> Tree.prune([record.uuid])
|> then(&[Tree.root(gettext("Top level"), &1)])
Summary
Functions
The ids of the rows above id, top first — what to open to show it.
The tree cut down to the rows whose name contains query (case and
accents ignored) and the rows above them, plus the ids to open so every
match shows. A matching row keeps its whole subtree, closed — finding a
parent still lets the admin open it and pick a child. A blank query
returns the tree untouched and opens nothing.
The row with id, or nil.
Builds the tree from a flat list of records linked by a parent id.
Every id in the tree whose row is one of types (:all for every row).
Applies fun to every node, children first.
Whether the tree offers id as a row of one of types — :all for any
row. A picker checks a picked id with this before taking it.
The names from the top down to id. skip leaves rows of those types
out of the path. [] when the tree lacks it.
For every row that is not one of types, the ids of the rows under it
that are — in one pass, for a picker whose branch boxes check a whole
branch.
The tree without the rows in ids and everything under them — a record
cannot move into its own subtree.
A row standing for "no parent", named name, holding children.
opts may set :hint and :icon (default "hero-home").
The id of the row that stands for "no parent".
Types
Functions
The ids of the rows above id, top first — what to open to show it.
The tree cut down to the rows whose name contains query (case and
accents ignored) and the rows above them, plus the ids to open so every
match shows. A matching row keeps its whole subtree, closed — finding a
parent still lets the admin open it and pick a child. A blank query
returns the tree untouched and opens nothing.
The row with id, or nil.
Builds the tree from a flat list of records linked by a parent id.
A record whose parent is not in the list is a root — so a list that leaves out trashed rows keeps their children, one level up. Building walks down from the roots, so a corrupt parent cycle can never loop: nothing in a cycle is reachable from a root. Sibling order is the list's order.
Options
:id— the record's id (default& &1.uuid):parent— its parent's id ornil(default& &1.parent_uuid):node— the node's own keys (default&%{name: &1.name});:idand:childrenare filled in
Every id in the tree whose row is one of types (:all for every row).
Applies fun to every node, children first.
Whether the tree offers id as a row of one of types — :all for any
row. A picker checks a picked id with this before taking it.
The names from the top down to id. skip leaves rows of those types
out of the path. [] when the tree lacks it.
For every row that is not one of types, the ids of the rows under it
that are — in one pass, for a picker whose branch boxes check a whole
branch.
The tree without the rows in ids and everything under them — a record
cannot move into its own subtree.
A row standing for "no parent", named name, holding children.
opts may set :hint and :icon (default "hero-home").
@spec root_id() :: String.t()
The id of the row that stands for "no parent".