A searchable, collapsible tree for picking one record — or several —
out of a nested set (a parent category, a folder, a page), instead of an
indented flat <select>. Build the tree with PhoenixKit.Utils.Tree.
Controlled. The parent owns what is picked: it passes value and
handles {PhoenixKitWeb.Components.TreePicker, id, value}, sent whenever
the admin picks (a single id, or the new list with multiple). The
component owns only the search text and which rows are open. The parent
builds the tree, so what to leave out — a record's own subtree, the
row being moved — is its call, and it must check a received id against
live data before acting on it: the tree may be minutes old.
<.live_component
module={PhoenixKitWeb.Components.TreePicker}
id="parent-picker"
tree={[Tree.root(gettext("Top level"), @parent_tree)]}
value={@parent_pick}
field
name="page[parent_uuid]"
/>
def handle_info({TreePicker, "parent-picker", id}, socket),
do: {:noreply, assign(socket, :parent_pick, id)}Attributes
id(required)tree(required) —PhoenixKit.Utils.Treenodesvalue— the picked id (nilfor none), or a list withmultiplepickable— the node:types that can be picked, or:all(default); other rows only open and closemultiple— check any number of rows; a row that cannot be picked then carries a box that checks or clears every pickable row under itcurrent— the id to badge "Current" (where the record is now)field—trueshows the picked row's path and a Change button, the tree only while changing (for forms);false(default) shows the tree at once (for dialogs). Single pickers onlypath_skip— node:types left out of the shown pathplaceholder— the path text while nothing is pickedsearch_placeholder— the search box's hint (default "Search...")disabled— field mode only: shows the path, offers no Change and posts nothing, like a disabled<select>name— renders hidden inputs so a surrounding form posts the value. Withmultiple, give it a list name ("item[tags][]"); an empty pick posts nothing, so read a missing key as[]post— what a hidden input posts for an id::id(default; the"root"row posts"", "no parent") or a function of the id
Inside a form. The search box has no name, and its hook
(TreePickerSearch) keeps its input/change events and Enter from
reaching the surrounding form — typing here must not run the form's
phx-change or submit it. Every other control is a type="button".