Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commitdd348ac6c3

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.grid of 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 } — matches editorSelection.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.selection in 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 in ui.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 into props.items — a shift-range always spans what's visually between the anchor and the click.
  • The canvas onClick payload carries only the cursor position and button, never modifier keys, so ctrl/shift state is read from input.isDown("ControlLeft" | "ControlRight" | "ShiftLeft" | "ShiftRight") at the moment of the click — the same substrate modules.zinput.rebind polls for modifier-aware interactions.
  • Keyboard nav (list/table row canvas onKey): ArrowDown / ArrowUp move focus and single-select the new row, Home / End jump to the first/last visible row, Enter dispatches props.onActivate for the focused row. Double-clicking a row also dispatches props.onActivate. Grid cells are panels (generic onClick only) — 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 with sort = true, cycling none/other-column -> ascending -> descending -> none. Sort compares row(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 = true list/table rows, the grid's wrapping Z.grid) is wrapped with BOTH maxHeight and minHeight set to the same value. Without minHeight, nesting the scrollArea under a header (table mode) or wrapping Z.grid directly (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 (not minWidth / minHeight) on the panel — Z.grid's content-driven column sizing reads each child's width style to pick a cell size, and a minWidth-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, mirroring selectableList's handler dedup.
  • props.commands (list/table mode only — see below) adds a right- click command menu, driven by modules.api.editor.commands. Each entry is a command id (Commands.get(id).title labels 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. ctx passed to enabledWhen/run is Selection.context() (this DataView's selection scope, since selecting the row focuses it) plus view (this widget's id) and item (the row's underlying item). Clicking an entry runs the command and closes the menu; clicking elsewhere or Escape also closes it (Z.popup auto-dismiss).
  • Grid mode does not support props.commands — its cells are Z.panels (generic onClick only), and onPointerDown (right-click sensing) is decoded by the Canvas widget type alone.