I try to automatically add a horizontal line after the table header if a header is present, so I write the following code:
#show table: it => {
let contains-header = it.fields().children.first().func() == table.header
set table(
stroke: (x, y) => (
top: if y == 0 or (contains-header and y == 1) { 1pt + red } else { none }
)
)
it
}
This doesn’t have any effect. It’s weird because a lot of other similar rules work:
// OK
#show table: set table(stroke: 1pt + red)
// OK
#show table: set table(stroke: (x, y) => 1pt + red)
// OK
#show table: it => {
set table(stroke: 1pt + red)
it
}
// No effect
#show table: it => {
set table(stroke: (x, y) => 1pt + red)
it
}
Thank you! Taking a look at this, it looks like using a show-set rule for table does not update the style when the stroke parameter is given a function for some reason…
EDIT: specifically when the show rule is a function itself.
I think this should be considered a bug, see for creating one at issues · typst/typst
This is exactly why (and not only this) show-set rule is preferred over show rules. They also have different semantics, so the actual issue here is why #show table: it => { set table(stroke: red); it }does work.
Basically, show-set rule is a filter-apply rule, but show rule is a filter-override/wrap rule.
#show heading: set heading(numbering: "1.") will apply heading rule for all headings, but #show heading: it => { set heading(numbering: "1."); it } will wrap all headings by adding set heading(numbering: "1.") to headings, but since you are already in a show rule (where heading is/can be constructed), it will not apply, because it’s too late for adding more styling for what you already have (it, which is immutable).
This is why you sometimes have to fiddle around with wrapping stuff in a specific way or reconstructing elements with additional behavior, dodging infinite show rule recursion.
So, in general, it is not possible to have an applied element set rule inside a show rule for the same element.
Hi Andrew, that makes sense.
My initial attempt to style the headers was meant to be a workaround until issue Unable to style `table.header` and `table.footer` · Issue #3640 · typst/typst · GitHub is resolved. So the answer that using a set rule inside a show rule isn’t valid is sufficient. Nevertheless, is there any solution how to style table headers from template without using something like my-table()?
I just need to apply fill, stroke, and strong to the header cells for all tables in the document that have a header. While it’s possible to achieve this with a custom function, it would require an additional import and adjustments to all existing tables, so I was looking for a solution to avoid this. Especially if it means that I have to revert all changes once issue #6218 is resolved.
All headers are horizontal, but some tables have multiple header rows and some tables have no header at all. That’s why I wanted the it in the show rule. Never mind, I just use custom my-table()…
We can create a new table in the show rule, from the old table
If we just do this, it recurses. So we mark the new table with a label so that it’s not done again
You can now apply custom styles to tables with headers. But it overwrites any even manually specified styles unfortunately
#show table: it => {
let contains-header = it.fields().children.first().func() == table.header
if not contains-header or (it.has("label") and it.label == <table-style-done>) {
it
} else {
let new-style = (stroke: (x, y) => (
top: if y == 0 or y == 1 { 1pt + red } else { none })
)
let fields = it.fields()
let chld = fields.remove("children")
// note, this overwrites any previous stroke configuration
[#table(..fields, ..new-style, ..chld)<table-style-done>]
}
}
#table(columns: 2, table.header[a][b], ..range(10).map(str))
#table(columns: 3, table.header[a][b][c], ..range(10).map(str))
#table(columns: 3, ..range(10).map(str))