enhancement
major
minor
major
minor
major
#29597
TL Views: object-list als generischer Repeater (Elemente ohne Container, Raster-Layout, Index für gestaffelte Animation)
Kontext
In der React-Oberfläche (.view.xml-Ebene) gibt es kein Element, das eine beliebige Objektliste mit einer Vorlage pro Eintrag in ein Kartenraster rendert. <object-list> kommt dem am nächsten, ist aber an einen Container gebunden (input-Objekt plus items/`link`/`remove`-Funktionen) und legt die Einträge ausschließlich untereinander ab. Ein Foto-Kachelraster von Immobilien (Objekte aus einer Suche, Trefferliste eines Agenten) oder eine redaktionelle Aufteilung (ein Top-Treffer groß, weitere in zwei bzw. drei Spalten) ist damit nicht ausdrückbar.
Erweiterung
<object-list> wird zum generischen Repeater:
- Der Container-Kanal input entfällt. An seine Stelle tritt inputs, eine Liste von Kanalreferenzen wie bei <table rows>. items ist ein TL-Script-Ausdruck, der die Werte der inputs als führende positionale Argumente erhält und die anzuzeigenden Objekte liefert. link und remove erhalten ebenfalls die inputs-Werte, gefolgt vom Element; new-element bleibt der optionale Zusatz für den Container-Fall. Die bisherige Schreibweise input="ticket" items="ticket -> ..." link="ticket -> comment -> ..." wird zu inputs="ticket" mit unveränderten Funktionen.
- layout mit den Werten list (Standard, wie bisher) und grid. Im Raster gelten die Optionen von <grid> (min-column-width, max-columns, gap); gap gilt in beiden Anordnungen.
- Jeder Eintrag erhält eine stabile Klasse (tlObjectList__item) und den Index als CSS-Variable (--tl-item-index), sodass App-CSS gestaffelte Einblendungen definieren kann.
- empty-text und observed-types (Live-Aktualisierung) gelten unverändert.
Beispiel:
<object-list items="filter -> $filter.search()" inputs="filter" layout="grid" max-columns="3" min-column-width="18rem"> <item><view-ref view="estate-card.view.xml"/></item> </object-list>
Gestaffelte Einblendung
Eine gestaffelte (kaskadierende) Einblendung lässt die Karten eines Rasters nicht gleichzeitig, sondern nacheinander erscheinen: Karte 0 sofort, Karte 1 wenige Millisekunden später usw. CSS kann das nur ausdrücken, wenn jede Karte ihre Position kennt. Die Engine schreibt deshalb an den Wrapper jedes Eintrags die Klassen tlItem tlObjectList__item und die Position (0-basiert, nach jeder Aktualisierung neu vergeben) als CSS-Variable --tl-item-index. Die Animation selbst bleibt App-CSS, z. B. für die Einträge aller Raster-Anordnungen:
.tlGrid > .tlObjectList__item {
animation: fade-up 300ms both;
animation-delay: calc(var(--tl-item-index) * 60ms);
}
@media (prefers-reduced-motion: reduce) {
.tlGrid > .tlObjectList__item { animation: none; }
}
Einträge werden mit Schlüssel wiederverwendet: Ein Eintrag, der in der Liste bleibt, behält seinen DOM-Knoten und wird nicht erneut animiert; nur neu erscheinende Einträge animieren.
Lösung
- Gemeinsame inputs-Konfiguration. Die bisher pro Element kopierte Eigenschaft inputs (<table>, <tree>, <value-input>, <calendar>, <chart>, <flow-diagram>, berechnete Spalten, Skript-Filter, Kachelbeschriftung, <derived-channel>, Skript-Aktionen) ist in die gemeinsame Konfigurationsschnittstelle Inputs im Paket com.top_logic.layout.view.channel zusammengezogen; ChannelInputs löst die Referenzen auf und baut die Argumentlisten. Sie akzeptiert überall beide Schreibweisen: das Attribut inputs="a, b" und die strukturierte Form <inputs><input channel="a"/><input channel="b"/></inputs>.
- Gemeinsame Rasteroptionen. min-column-width, max-columns und gap bilden die Konfigurationsschnittstelle GridOptions, die <grid> und <object-list> verwenden. max-columns begrenzt die Spaltenzahl des responsiven Rasters nach oben; das Raster veröffentlicht seinen Abstand als CSS-Variable --tlGrid-gap, aus der die Spaltenvorlage die Obergrenze berechnet. (Phase 3 von #29596 liefert für <grid> danach nur noch max-width.)
- Eintrags-Wrapper als Container-Eigenschaft. TLStack und TLGrid teilen sich die Basisklasse ReactLayoutControl (Kinderliste, itemClass). Ist itemClass gesetzt, hüllt der Client jedes Kind in ein <div class="tlItem <itemClass>"> mit der CSS-Variablen --tl-item-index; die Klasse tlItem hält den Wrapper für das Layout durchlässig (Füll-Kontrakt). Es entsteht keine neue React-Komponente.
- Struktur der Objektliste. Das Control der Liste ist ein äußerer Stapel: zuerst die Anordnung (TLStack bei list, TLGrid bei grid), die ausschließlich die Einträge mit der Klasse tlObjectList__item enthält; dahinter folgen der leere Text bzw. der new-element-Inhalt als Geschwister der Anordnung, sodass sie weder eine Rasterzelle belegen noch als Eintrag animiert werden.
- Verhalten ohne Container. items wird mit den aktuellen Werten der inputs ausgewertet (ein gelöschtes Objekt wird als null übergeben). Der new-element-Inhalt wird nur angezeigt, wenn alle inputs einen Wert haben; das transiente neue Element erhält den Wert des ersten inputs-Kanals als Container, wenn dieser ein Objekt ist. <link-element> mit einer wertlosen Eingabe wird mit einer Fehlermeldung abgewiesen; Änderungen an jedem inputs-Kanal fragen den ungespeicherten Entwurf (Veto).
- Demo und Dokumentation. Die React-Demo erhält die Seite „Repeater“ (demo/repeater-demo.view.xml): ein Kartenraster über einer Suche (test.flowchart:FlowNode, höchstens drei Spalten) mit gestaffelter Einblendung aus dem App-Stylesheet style/tl-demo-react.css (registriert über ClientResources) sowie eine Liste ohne inputs; die Kommentarliste der Ticket-Demo ist auf inputs umgestellt, das Diagramm-Demo-Raster nutzt max-columns. Der FAQ-Artikel docs/faq/react-view-layer.md beschreibt den Repeater und die beiden inputs-Schreibweisen.
Migration
<object-list input="x"> wird zu <object-list inputs="x">; die Funktionen items, link und remove bleiben unverändert.