List
A container that scrolls, and tells you when the user reaches the end of what it holds. It is for the dialog surface: a deck tile is a fixed box, and a list in one would hide content behind a gesture the deck itself uses.
ui.list
Example
Section titled “Example”new UiList{ Key = "results", Fill = true, Gap = UiSize.FromBasis(0.015), Events = [UiEventHandler.On(UiComponentEvents.Reveal, OnRevealed)], Children = [ new UiRepeat<CatalogItem> { Key = "rows", Items = UiValue.From<IReadOnlyList<CatalogItem>>(() => items.Value.Take(window.Value).ToList()), KeySelector = item => item.Id, Template = (item, _) => Row(item), }, ],}
A windowed result list, adapted from the built-in music picker: it starts with a few rows and grows as the user scrolls.
Loading more items
Section titled “Loading more items”void OnRevealed(UiEventData data){ if (!data.TryGetDouble(out var index)) return;
var wanted = (int)index + 25; if (wanted > window.Value) window.Value = wanted;}reveal carries the index of the furthest child the user has brought into view. Append children, or do
not; there are no page sizes and no “has more” flag. When you run out, append nothing - the reader asks
again only once the user goes further than before.
Replacing the rows, as a new search does, starts that over: once the list holds fewer children than before, or a different child sits where the furthest one reported was, the reader reports again from what is in view, even an index at or below one it sent earlier. Grow your window only when the new index asks for more, as above, so a lower index never takes rows away. If you shrink your window but the list still shows the same children, the reader cannot tell, and asks again only beyond the furthest index it already reported.
Keeping rows stable
Section titled “Keeping rows stable”KeySelector = item => item.Id,Key rows by the item’s own id, never its position. Rows are patched in place as the list grows or a filter narrows it, and a positional key would re-key everything below the change.
Showing an empty or loading state
Section titled “Showing an empty or loading state”new UiWhen{ Key = "emptyWhen", Condition = () => items.Value.Count == 0, Content = () => new UiTextRun { Key = "empty", Text = "Nothing found", Role = UiComponentTextRoles.Secondary },},“Loading” and “empty” are your own state, so express them as ordinary children of the list rather than properties.
Scrolling horizontally
Section titled “Scrolling horizontally”new UiList{ Key = "covers", Direction = UiComponentDirections.Horizontal, RequiredComponentVersion = 2, Fallback = new UiStack { Key = "coversFallback", Children = [firstCover] }, Children = covers,}Horizontal needs component version 2. A version 1 reader ignores direction and scrolls vertically,
so ask for version 2 and carry a ui.stack fallback.
Following the end
Section titled “Following the end”new UiList{ Key = "chat", Fill = true, Anchor = UiComponentListAnchors.End, RequiredComponentVersion = 3, Fallback = new UiList { Key = "chatNewestFirst", Fill = true, Children = [new UiRepeat<ChatMessage> { Key = "rows", Items = newestFirst, KeySelector = m => m.Id, Template = Row }], }, Children = [new UiRepeat<ChatMessage> { Key = "rows", Items = oldestFirst, KeySelector = m => m.Id, Template = Row }],}A chat or a log appends at the bottom. With Anchor = End the reader keeps the view at the end while the
user is there, so each new row scrolls into view. Once the user scrolls up to read, new rows and rows removed
above the view, such as the oldest messages dropping off a capped history, leave the view where it is, and
the reader offers a way back to the newest row. You send nothing and receive nothing for any of this: where
the view is belongs to the reader.
Only a vertical list follows its end. End needs component version 3; an older reader ignores anchor and
leaves the newest row below the fold, so ask for version 3 and carry a fallback that shows the newest row
first, such as the same rows in reverse order.
Properties
Section titled “Properties”| Property | Values | Default | Meaning |
|---|---|---|---|
Direction (direction) |
UiComponentDirections.Vertical, .Horizontal (vertical, horizontal) |
vertical |
The scroll axis; horizontal needs component version 2. |
Anchor (anchor) |
UiComponentListAnchors.Start, .End (start, end) |
start |
Which end the view holds on to; end follows new rows at the end of a vertical list and needs component version 3. |
Gap (gap) |
length | No gap | The gap between children. |
Padding (padding) |
length | No padding | Inner padding on every edge. |
Background (background) |
#rrggbb |
Paints nothing behind its children | The list’s own fill, a literal colour like every container fill - see Colours and text. |
MainSize (mainSize), Fill (fill), Answer (answer) |
- | - | Shared with every container - see Stack and layer. |
Events
Section titled “Events”Declared only where Events names it - see Events.
| Event | When | Payload |
|---|---|---|
UiComponentEvents.Reveal (reveal) |
The user has reached the end of what the list currently holds | The index of the furthest child brought into view, as a bare number |
Children
Section titled “Children”Any element, any number, one after another along the scroll axis.
Layout
Section titled “Layout”The main axis - y for a vertical list, x for a horizontal one - is unbounded: children take their
natural extent along it, and their mainSize/fill are ignored on that axis. Across it, each child takes
the list’s inner extent. On its own parent’s main axis a list follows the ordinary container rule:
mainSize or fill if declared, otherwise its content extent. See Sizing.
Reader behaviour
Section titled “Reader behaviour”- Send
revealonly if the node declares it. - Send at most two
revealevents a second, and only for an index beyond the furthest one already sent for the same list’s current content. - Start that over when the list holds fewer children than at its previous paint, or when the child at the furthest index sent is gone or has a different id: the content was replaced.
- When the throttle holds back an index the user has reached, send it as soon as the throttle allows: a user already at the end of the list may never scroll again.
- The payload is a child index, not a page; never assume a page size.
- A version 1 reader ignores
directionand scrolls vertically - negotiation catches unknown types, not unknown values, so producers pairhorizontalwith version 2 and a fallback. - Ignore children’s
mainSizeandfillon the scroll axis. anchorabsent,startor a value you do not know: leave the view where the user put it, as always.endon a vertical list:- While the view is at the end of the list, keep it there: after a paint that adds or removes children, and while children grow after one, scroll to the end.
- Once the user has scrolled away from the end, never move the view for a content change. When children above the view are removed, keep the first visible child that is still there at the same offset from the top edge.
- Deciding whether the view is at the end is up to the user’s own scrolling: a paint, a resized box or children that grew are not the user leaving the end.
- When a child the list did not hold before arrives last while the user is away, offer a way back to the end, such as a control that scrolls there. Removing the last child is not new content. The control must not count as a press on any node, and it goes away once the view is back at the end. A reader that cannot keep it in view, such as an engine without sticky positioning, may leave it out.
- A horizontal list ignores
anchor. - None of this is reported to the producer.
- Advertise
ui.listversion 3 only once you follow the end; a version 2 reader ignoresanchor, so producers pairendwith version 3 and a fallback.
See also
Section titled “See also”- Stack and layer
- Text field - the other dialog-only element
- Events
- Sizing