Element
Category
Parent
Children
The rules — the data-tree page
- Children are real rows.
children: { has, load }on the lister:has(item)says whether a row can open (a field name or a function),load(item)answers its child rows — return theg.fetchpromise of your list endpoint with the parent id indata; the rows pass throughmapandrenderlike the roots and get the checkbox, the row actions,refreshItem, the details box and the keyboard. - Outside pagination. Page size counts ROOTS; a root may reveal any number of descendants. The server paginates roots only and answers a parent id with the whole child set.
- The approved row (Andrei, Sep 2026). The selection checkbox comes FIRST and never indents — one vertical line down the list whatever the depth. The caret sits next to the code and indents with it; it opens the CHILDREN (
childrenToggle: 'start', the default). Only the caret and the first cell indent — the lib pads.lister-cols > :first-childby--lister-indent, the row keeps its own padding, so the name, the parent and every other column hold their place at every level (an indent on the row would stagger them down the page). The step is--lister-level-stepon the root: 1.5rem, halved below md so a four-level tree stays readable at 390px; override it inline. - THE STANDARD ROW (lister.md "The standard row"; the reference: the data list). No details toggle on the row:
rowClick: 'expand'— a click anywhere on the card outside controls opens the details (links, buttons, inputs, editable values and a text selection never trigger it) — plus a "Details" item FIRST in the row's dropdown, above a divider, markedlister-expand(the lib toggles the row on it, no handler) for the keyboard and assistive road. The open details are an INSET panel (lister-more-inseton the root: bordered, on the secondary surface) so an open row stands out instead of sinking into the list; they show the hidden columns first, thenrenderMore's own fields in therow g-3 smallgrid (label.text-body-secondaryover value). - FIT columns (lister.md "Fit columns"): every cell declares its share + readable minimum (
lister-col-w/lister-col-min), the hideable ones alister-col-priority+lister-col="Label"; the element cell (code + name) never hides. Open the filter panel or narrow the window: Parent, then Children, then Category step into the details — by the LIST's width, not the viewport's. On a phone the row actions go compact by themselves (a menu button + the details caret). - The shell is THE STANDARD SHELL (lister.md "Page markup"; the data list): the toolbar inside
.lister-main(the Filters toggle + the bulk Actions left, New right — no toolbar Select all), the panel as the Filters + Display cards (Display holds "Roots per page"), the list card with the header (its select-all checkbox first — it takes every visible element row, children included, never a group row), and the count + the pager on the.lister-footerline. - Toggle positions are an ALTERNATIVE, not the standard. The standard row above has only the tree caret at the start. A page that needs a visible details toggle may still place it: the tree toggle (
[lister-children]) and the details toggle ([lister-expand]) each sit at the row START (the lib-injected bare chevron — right closed, down open) or at the row END (the page authors the round.btn-contrastcaret — down closed, up open); one may take the start (lister.md "Toggle positions"). The second lister below shows that alternative: details at the start, children at the end. - Codes are never clipped. A
.codevalue wraps between its segments only (g.format.code()plants the breaks) — shrink the window to 390px and open three levels. - Every level groups on its own. The category rows are synthetic rows the page's
mapinserts (rowClassgives themlister-group,selectablekeeps the checkbox off them); children go through the samemap, so a node's children group among themselves. An element appears exactly once: under its parent, inside its own category group. - Memory. Open nodes and open details survive
reload()byidKey(nested nodes re-open too); page/search/filter changes start closed.refreshChildren(id)re-fetches ONE node's children after a create/move under it. - Flat on search. A search may match a row whose ancestors do not: the endpoint answers a flat list from every level and
hassays no while a search is active. - Inline cell editing, per field.
editable: { code: { type: 'text', save }, category: { type: 'selekt', options }, locked: { type: 'boolean', save } }— the PROJECT opts each field in with the input type it needs; inrenderthe value carrieslister-edit="code". Click the value → an input in the card with a check + a cross (Enter saves, Escape cancels) →save(item, value)answers the fresh row (ag.fetchenvelope) → the row repaints IN PLACE, never moving; a validation answer (data.validated: false+data.fields) lands under the input the lister way. Here: the code (tryx, and a locked element), the name, the category (a select), and Locked in the details box (a switch). - Keyboard. → opens a node (then steps into its first child), ← closes it (then climbs to the parent); ↑/↓ walk the visible rows in order, children included.
Alternative: the reverse assignment — details at the start, children at the end, card click = details (not the standard row)
Element
Category
Children