No, unless you use regex, which isn’t perfect either. You’d have to reimplement show rule and inside add the show rule that only affects required parts. And enum is not of the easiest to re-implement. Ideally, you would just copy something from Add default typst implementation code where possible · Issue #5095 · typst/typst · GitHub, but there isn’t much there now.
I added Add default typst implementation code where possible · Issue #5095 · typst/typst · GitHub, so you can do this, but only for non-nested lists. Or add one layer more with another state hack, and it should just work.
#let enum-implementation(body-style: x => x, is-numbering: none, doc) = {
let numbering-kind-from-char(c) = {
let numberings = (
"1",
"a",
"A",
"i",
"I",
"α",
"Α",
"*",
"א",
"一",
"壹",
"あ",
"い",
"ア",
"イ",
"ㄱ",
"가",
"\u{0661}",
"\u{06F1}",
"\u{0967}",
"\u{09E7}",
"\u{0995}",
"①",
"⓵",
)
if c in numberings { c }
}
let numbering-pattern-from-str(pattern) = {
let pieces = ()
let handled = 0
for (i, c) in pattern.codepoints().enumerate() {
let kind = numbering-kind-from-char(c)
if kind == none { continue }
let prefix = pattern.slice(handled, i)
pieces.push((prefix, kind))
handled = c.len() + i
}
let suffix = pattern.slice(handled)
if pieces.len() == 0 {
panic("invalid numbering pattern")
}
(pieces: pieces, suffix: suffix, trimmed: false)
}
let apply-numbering(numbering, ..numbers) = {
if numbering == none { return }
std.numbering(numbering, ..numbers)
}
let apply-numbering-kth(numbering, k, number) = {
let fmt = ""
let self = numbering-pattern-from-str(numbering)
if self.pieces.len() > 0 {
let (prefix, _) = self.pieces.first()
fmt += prefix
let (_, kind) = if k < self.pieces.len() {
self.pieces.at(k)
} else {
self.pieces.last()
}
fmt += apply-numbering(kind, number)
}
fmt += self.suffix
fmt
}
let saturating-sub(this, rhs) = {
assert(type(this) == int)
calc.max(0, this - rhs)
}
let saturating-add(this, rhs) = {
assert(type(this) == int)
calc.min(18446744073709551615, this + rhs) // Conversion to float.
}
let unwrap-or-else(this, f) = if this != auto { this } else { f() }
let unwrap-or(this, field, value) = {
if field in this.fields() { this.at(field) } else { value }
}
let immediately-preceded-by-par = state("attach-v", false)
let parents-in = state("enum-parents", ())
/// Layout the enumeration.
let layout-enum(elem) = context {
let numbering = elem.numbering
let reversed = elem.reversed
let indent = elem.indent
let body-indent = elem.body-indent
let tight = elem.tight
let gutter = unwrap-or-else(elem.spacing, () => {
if tight { par.leading } else { par.spacing }
})
let cells = ()
let number = unwrap-or-else(elem.start, () => {
if reversed { elem.children.len() } else { 1 }
})
let parents = parents-in.get()
let full = elem.full
// Horizontally align based on the given respective parameter.
// Vertically align to the top to avoid inheriting `horizon` or `bottom`
// alignment from the context and having the number be displaced in
// relation to the item it refers to.
let number-align = elem.number-align
for item in elem.children {
number = unwrap-or(item, "number", number)
let resolved = if full {
let content = apply-numbering(numbering, ..parents, number)
content
} else {
if type(numbering) == str {
text(apply-numbering-kth(
numbering,
parents.len(),
number,
))
} else {
apply-numbering(numbering, number)
}
}
// Disable overhang as a workaround to end-aligned dots glitching
// and decreasing spacing between numbers and items.
let resolved = {
set text(overhang: false)
if is-numbering != none { is-numbering.update(true) }
align(number-align, resolved)
if is-numbering != none { is-numbering.update(false) }
}
// Text in wide enums shall always turn into paragraphs.
let body = item.body
if not tight {
body += parbreak()
}
cells.push([])
cells.push(resolved)
cells.push([])
cells.push({
parents-in.update(arr => arr + (number,))
immediately-preceded-by-par.update(false)
body-style(body)
parents-in.update(arr => (_ = arr.pop()) + arr)
})
number = if reversed {
saturating-sub(number, 1)
} else {
saturating-add(number, 1)
}
}
let grid = grid(
columns: (indent, auto, body-indent, auto),
row-gutter: gutter,
..cells
)
grid
}
let target = dictionary(std).at("target", default: () => "paged")
let sequence = [].func()
let styled = text(red)[].func()
let v-space-hack(doc) = {
let previous
for child in doc.children {
if child.func() == styled {
child = styled(v-space-hack(child.child), child.styles)
}
if child.func() == enum.item {
if child.body.func() in (sequence, styled) {
let fields = child.fields()
let body = fields.remove("body")
child = enum.item(v-space-hack(body), ..fields)
}
if previous != none and previous.func() == parbreak {
child = {
immediately-preceded-by-par.update(true)
child
immediately-preceded-by-par.update(false)
}
}
}
child
previous = child
}
}
show enum: self => {
let tight = self.tight
context if target() == "html" {
let elem = html.elem.with("ol")
let attrs = (:)
if self.reversed(styles) {
attrs += ("reversed": "reversed")
}
if self.start != auto {
attrs += ("start": str(self.start))
}
let body = sequence(..self.children.map(item => {
let li = html.elem.with("li")
let attrs = (:)
if "number" in item.fields() {
attrs += ("value", str(item.number))
}
// Text in wide enums shall always turn into paragraphs.
let body = item.body
if not tight {
body += parbreak()
}
elem(attrs: attrs, body)
}))
elem(attrs: attrs, body)
}
let realized = layout-enum(self)
context if tight and not immediately-preceded-by-par.get() {
let spacing = unwrap-or-else(self.spacing, () => par.leading)
v(spacing, weak: true)
}
realized
}
show: v-space-hack
doc
}
#let is-numbering = state("is-numbering", false)
#show: enum-implementation.with(is-numbering: is-numbering, body-style: it => {
show "1": it => context {
if is-numbering.get() { return it }
text(red, "2")
}
it
})
#lorem(20)1
+ #lorem(20)1
+ #lorem(20)1
+ #lorem(20)
+ #lorem(20)1
+ #lorem(20)1
+ #lorem(20)1