Builtin Library
README.md
dataView
Filterable, multi-selectable data list bound to a named
modules.api.editor.selection scope. props.mode ("list" | "table" |
"grid", default "list") selects the rendering strategy:
- list — one focusable canvas row per visible item (icon + label +
badge) inside a virtualized
scrollArea. - table — the same focusable-row shape extended to multiple columns, with a sortable header.
- grid — a wrapping
Z.gridof selectable thumbnail cells, for asset browsers.
Exports
dataView(props: Props?) -> any— build a DataView widget. The module returns this function directly.
Types:
Props = { id: string?, items: { any }?, key: ((any) -> string)?, row: ((any) -> RowSpec)?, selection: Scope?, onActivate: string?, filter: string?, rowHeight: number?, rowWidth: number?, maxHeight: number?, mode: string?, app: any?, columns: { ColumnSpec }?, gridColumns: number?, commands: { string }? }RowSpec = { label: string, icon: string?, badge: string?, columns: { [string]: string }?, thumb: string? }ColumnSpec = { id: string, label: string, width: number?, align: ("left" | "right" | "center")?, sort: boolean? }Scope = { name: string }— matcheseditorSelection.Scope.
id, key, row, and selection are required at runtime (a missing
one raises an error); columns is additionally required for table
mode. Every Props field is typed optional so props or {} at the
registration boundary stays well-typed.
Usage
local dataView = require("@builtin::modules.zui.widget.dataView")
local Selection = require("@builtin::modules.api.editor.selection")
local scope = Selection.scope("asset")
-- list mode
dataView{
id = "assets", items = assets, selection = scope,
key = function(a) return a.guid end,
row = function(a) return { label = a.name, icon = a.icon } end,
onActivate = "assets:open",
filter = state.filterText,
}
-- table mode
dataView{
id = "assetsTable", mode = "table", items = assets, selection = scope,
key = function(a) return a.guid end,
row = function(a) return { columns = { name = a.name, type = a.assetType, size = tostring(a.size) } } end,
columns = {
{ id = "name", label = "Name", width = 160, sort = true },
{ id = "type", label = "Type", width = 100, sort = true },
{ id = "size", label = "Size", width = 80, align = "right", sort = true },
},
}
-- grid mode
dataView{
id = "assetsGrid", mode = "grid", items = assets, selection = scope,
key = function(a) return a.guid end,
row = function(a) return { label = a.name, thumb = a.path .. "/preview.png" } end,
gridColumns = 4,
}
-- right-click command menu (list/table mode)
local Commands = require("@builtin::modules.api.editor.commands")
Commands.declare({ id = "asset.rename", title = "Rename", category = "Assets", run = function(ctx) ... end })
dataView{
id = "assets", items = assets, selection = scope,
key = function(a) return a.guid end,
row = function(a) return { label = a.name } end,
commands = { "asset.rename", "-", "asset.delete" },
}
Notes
- Selection is read from and written to
props.selectionin every mode — the widget holds no selected-ids of its own, so every other view sharing the scope always agrees with what's drawn. Only the click anchor, keyboard focus position, and (table mode) sort key/direction live inui.widgetState, because none of them has a home in the selection model. - Row click resolves through
selectionModel.resolveClick(plain / ctrl / shift semantics) using positions in the CURRENT filtered/ sorted view, not indices intoprops.items— a shift-range always spans what's visually between the anchor and the click. - The canvas
onClickpayload carries only the cursor position and button, never modifier keys, so ctrl/shift state is read frominput.isDown("ControlLeft" | "ControlRight" | "ShiftLeft" | "ShiftRight")at the moment of the click — the same substratemodules.zinput.rebindpolls for modifier-aware interactions. - Keyboard nav (list/table row canvas
onKey):ArrowDown/ArrowUpmove focus and single-select the new row,Home/Endjump to the first/last visible row,Enterdispatchesprops.onActivatefor the focused row. Double-clicking a row also dispatchesprops.onActivate. Grid cells are panels (genericonClickonly) — they select but don't carry keyboard nav or double-click. - Table mode's column header cells dispatch a
<id>:sort:<colId>click for any column withsort = true, cycling none/other-column -> ascending -> descending -> none. Sort comparesrow(item).columns[col.id](the same text shown in the cell), so a numeric-looking column sorts lexicographically unless the cell text is itself a plain unpadded number. - Every scrollable body (
virtual = truelist/table rows, the grid's wrappingZ.grid) is wrapped with BOTHmaxHeightandminHeightset to the same value. WithoutminHeight, nesting the scrollArea under a header (table mode) or wrappingZ.griddirectly (grid mode) makes its column an indefinite-height ancestor, and the viewport collapses to a sliver well under the intended bound. - Grid cells set
width/height(notminWidth/minHeight) on the panel —Z.grid's content-driven column sizing reads each child'swidthstyle to pick a cell size, and aminWidth-only cell measures as 0 there. - An empty filtered view renders a single muted "No items" label instead of an empty scroll area (table mode keeps the header above it).
- Handlers (click, double-click, keyboard nav, column sort) are
registered once per widget id (deduped via
app._dataViewHandlers); item list, filter, column and callback swaps land without re-registering, mirroringselectableList's handler dedup. props.commands(list/table mode only — see below) adds a right- click command menu, driven bymodules.api.editor.commands. Each entry is a command id (Commands.get(id).titlelabels it,Commands.isEnabled(id, ctx)gates it); a"-"entry is a separator. Right-clicking a row selects it alone first if it wasn't already part of the selection, then opens the menu anchored to that row.ctxpassed toenabledWhen/runisSelection.context()(this DataView's selection scope, since selecting the row focuses it) plusview(this widget's id) anditem(the row's underlying item). Clicking an entry runs the command and closes the menu; clicking elsewhere or Escape also closes it (Z.popupauto-dismiss).- Grid mode does not support
props.commands— its cells areZ.panels (genericonClickonly), andonPointerDown(right-click sensing) is decoded by the Canvas widget type alone.