Conditional formatting¶
The table-format module provides dialog and APIs for rule‑based conditional formatting for cells/rows.
- Build a
TableCustomizationfrom rules viarememberCustomization(rules, matches = ...). Row‑wide rules havecolumns = emptyList(); cell‑specific rules list field keys incolumns. - Use
FormatDialog(...)to create/edit rules (Design / Condition / Fields tabs).
Simple example:
// 1) Rules
val rules = remember {
listOf(
TableFormatRule.new<PersonField, Person>(id = 1, filter = Person("", 0))
)
}
// 2) Matching logic
val customization = rememberCustomization<Person, PersonField, Person>(
rules = rules,
matches = { item, filter -> item.age >= 65 },
)
// 3) Pass customization to the table
Table(
itemsCount = items.size,
itemAt = { index -> items.getOrNull(index) },
state = state,
columns = columns,
customization = customization,
)
// 4) Optional: rules editor dialog
FormatDialog(
showDialog = show,
rules = rules,
onRulesChange = { /* persist */ },
getNewRule = { id -> TableFormatRule.new<PersonField, Person>(id, Person("", 0)) },
getTitle = { field -> field.name },
filters = { rule, onApply -> /* return list of FormatFilterData for fields */ emptyList() },
entries = PersonField.entries.toImmutableList(),
key = Unit,
strings = DefaultStrings,
onDismissRequest = { /* ... */ },
)
Hosting the editor in your own container¶
FormatDialog renders the editor inside a Material3 AlertDialog. To host the same UI in your own container — a
custom dialog wrapper, a side panel, a split pane — use FormatDialogContent, which renders the editor without a
dialog. The container owns visibility, scrim, background and elevation; the content applies only its title/text
colors. Give it a bounded‑height parent (the rule list scrolls inside), and pass onDismissRequest = null to hide
the close button for embedded, non‑modal placements.
MyAdaptiveDialogHost(visible = show, onDismissRequest = { show = false }) {
FormatDialogContent(
rules = rules,
onRulesChange = { /* persist */ },
getNewRule = { id -> TableFormatRule.new<PersonField, Person>(id, Person("", 0)) },
getTitle = { it.name },
filters = { rule, onApply -> emptyList() },
entries = PersonField.entries.toImmutableList(),
key = Unit,
strings = DefaultStrings,
onDismissRequest = { show = false },
)
}
Colors for both FormatDialog and FormatDialogContent come from FormatDialogColors, defaulting to
FormatDialogDefaults.colors() (the Material3 AlertDialog defaults). FormatDialogContent applies the title and
text content colors itself and leaves the container color and elevation to its parent.
rememberCustomization merges base styles with matching rules into a resulting TableCustomization (background,
content color, text style, alignment, etc.).
Minimal example:
data class Person(val name: String, val age: Int, val rating: Int)
enum class PersonField { Name, Age, Rating }
// Rules
val rules = remember {
val ratingFilter: Map<PersonField, TableFilterState<*>> =
mapOf(
PersonField.Rating to TableFilterState(
constraint = FilterConstraint.GTE,
values = listOf(4),
),
)
val ratingRule =
TableFormatRule<PersonField, Map<PersonField, TableFilterState<*>>>(
id = 1L,
enabled = true,
base = false,
columns = listOf(PersonField.Rating),
cellStyle = TableCellStyleConfig(
contentColor = 0xFFFFD700.toInt(), // Gold
),
filter = ratingFilter,
)
listOf(ratingRule)
}
// Matching logic (app‑specific)
val customization = rememberCustomization<Person, PersonField, Person>(
rules = rules,
matches = { person, ruleFilters ->
for ((column, stateAny) in ruleFilters) {
when (column) {
PersonField.Rating -> {
val value = person.rating
val st = stateAny as TableFilterState<Int>
val constraint = st.constraint ?: continue
when (constraint) {
FilterConstraint.GT -> value > (st.values?.getOrNull(0) ?: value)
FilterConstraint.GTE -> value >= (st.values?.getOrNull(0) ?: value)
FilterConstraint.LT -> value < (st.values?.getOrNull(0) ?: value)
FilterConstraint.LTE -> value <= (st.values?.getOrNull(0) ?: value)
FilterConstraint.EQUALS -> value == (st.values?.getOrNull(0) ?: value)
FilterConstraint.NOT_EQUALS -> value != (st.values?.getOrNull(0) ?: value)
FilterConstraint.BETWEEN -> {
val from = st.values?.getOrNull(0) ?: value
val to = st.values?.getOrNull(1) ?: value
from <= value && value <= to
}
else -> true
}
}
else -> true
}
}
}
)
Table(
itemsCount = items.size,
itemAt = { index -> items[index] },
state = state,
columns = columns,
customization = customization
)
// Optional dialog
FormatDialog(
showDialog = show,
rules = rules,
onRulesChange = { /* persist */ },
getNewRule = { id -> TableFormatRule.new<PersonField, Person>(id, Person("", 0)) },
getTitle = { it.name },
filters = { rule, onApply -> emptyList() }, // build `FormatFilterData` list for your fields
entries = PersonField.entries.toImmutableList(),
key = Unit,
strings = DefaultStrings,
onDismissRequest = { show = false }
)
Public API highlights:
rememberCustomization<T, C, FILTER>(rules, matches = ...) : TableCustomization<T, C>.TableFormatRule<FIELD, FILTER>withcolumns: List<FIELD>,cellStyle: TableCellStyleConfig,filter: FILTER.FormatDialog(...)andFormatDialogSettingsfor UX tweaks.FormatDialogContent(...)to host the same rule editor in any container (a dialog host, side panel, split pane).FormatDialogColors/FormatDialogDefaults.colors()for dialog/content colors, via acolorsparameter on both.FormatFilterData<E>to describe per‑field filter controls in the dialog.FilterConstraint.isNullCheck()extension function to check for IS_NULL/IS_NOT_NULL constraints.TableFilterState.isActive()extension function to determine if a filter is active.VerticalScrollbarRendererandVerticalScrollbarStatefor custom scrollbar rendering in formatting dialogs.