[Feature Request] composable for searchable trees
@johnleider is already working on this.
Since Jul 2, 2026.
- Dominant language
- TypeScript
- Stars
- 735
- Forks
- 10
- Avg merge
- 10h 47m
- Merged PRs (30d)
- 72
Description
Origin
- vuetify#22983 - draft for improvements for VTreeview
High level: tree structures (treeview, datatables with group-by, lists) might benefit from headless composable driving search «» opened interaction while also supporting open-all.
Filtered reveal for nested trees (useNestedReveal + Treeview filter prop)
A searchable tree needs its matches to become visible — which means opening every collapsed ancestor of each match. Doing this naively (recompute openedIds from the match set on every change) breaks real UX in ways that are non-obvious until you hit them. This was discovered while patching Vuetify's VTreeview. PR attempts to introduce open.ts that should eventually be replaced by v0-native utility.
createNested already ships the mechanics — reveal(ids) (opens ancestors only), open/close, openedIds, getAncestors, isLeaf, and a registration-driven openAll. What is missing is the filter-reveal session policy: the rules for how open state should evolve as a query comes and goes, without fighting the user.
createFilter already produces the match set. Nothing wires the two together.
The policy (behavior spec)
Given a reactive set of matched ids (from createFilter) and a NestedContext:
- reveal, don't dump.
- for each match, open nodes path (
reveal) - open the matched node itself.
- additive, not authoritative.
- merge reveal targets into
openedIds. A branch the user collapses mid-search stays collapsed (until a new query makes it match again).
- remember only what the search opened.
- track the ids this session actually opened (those not already open when revealed)
- on clear, undo only the temporarily revealed nodes — except what user opened deliberately mid-search
- accept live tree changes. — as matches appear/disappear (async load, edits), reveal nodes and leafs that match
Proposed API
export function useNestedReveal (
nested: NestedContext,
matchedIds: MaybeRefOrGetter<Iterable<ID>>, // e.g. from createFilter → ids
options?: { active?: MaybeRefOrGetter<boolean> }, // default: derive from matchedIds emptiness
): void
- No return value; it drives
nested.openedIdsas a side effect whileactive. activefalse (query cleared) triggers the rule-4 cleanup exactly once.- Pure policy over
reveal/open/close/openedIds/getAncestors/getPath/
isLeaf. Ships withcreateNestedconceptually; keep it a separate export so
non-tree consumers ofcreateNesteddon't pay for it.
Component surface
Add a filter/query prop to Treeview.Root (mirrors openAll/reveal which already live there). Internally: createFilter → matched ids → useNestedReveal(nested, ids). Visibility of non-matches is a separate render concern (a matched(id) predicate on the
context for the item to consult), independent of open state — keep the two decoupled.
Test checklist (from Vuetify's VTreeview tests)
- reveal a match nested under collapsed ancestors (both eager + lazy registration)
- collapse a revealed ancestor mid-search → stays collapsed when search/query is cleared
- clear removes only session-opened nodes
- clear keeps a branch the user opened mid-search and its adopted ancestors
- match added tree modes while active is revealed
openAll+ collapsed nodes do not re-open accidentally
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.