Query

Typst provides query(selector) for built-in Typst elements. The equivalent for custom elembic elements is e.query(filter), which, similarly, must be used within context { ... }. It returns a list of elements matching filter. Check "Filters" for information on filters.

For example:

#import "@preview/elembic:1.1.1" as e

#elem(fill: red, name: "A")
#elem(fill: red, name: "B")
#elem(fill: blue, name: "C")

#context {
  let red-elems = e.query(elem.with(fill: red))

  // This will be:
  // "Red element names: A, B"
  [Red element names: #red-elems.map(it => e.fields(it).name).join[, ]]
}

Restricting filter domains

Filters must be restricted to a finite set of potentially matching elements to be used with e.query.

This is only a problem with NOT and within filters, which could potentially match any elements. They can be restricted to certain elements with e.filters.and_(e.filters.or_(elem1, elem2), e.filters.not_(elem1.with(field: 5))) for example.

In addition, using e.within with e.query won't work as expected without using e.settings to manually enable ancestry tracking; see the last section of this page for details.

before and after

In Typst, for built-in elements, you can write query(selector(element).before(here())) to get all element instances before the current location, but not after. Similarly, using .after(here()) will restrict the query to elements after the current location, but not before.

For elembic elements, e.query has the parameters before: location and after: location (can be used simultaneously) for the same effect.

#import "@preview/elembic:1.1.1" as e

#elem()

#elem()

// Before: 2
// After: 1
#context [
  Before: #e.query(elem, before: here()).len()
  After: #e.query(elem, after: here()).len()
]

#elem()

Using e.within with query

The e.within filter, used to match nested elements, will not work with e.query unless both the queried element and its expected parent track ancestry, as per the rules in "Lazy ancestry tracking".

That is, e.query(e.filters.and_(elem1, e.within(elem2))) will return an empty list unless both elem1 and elem2 had ancestry tracking enabled before they were placed, e.g. due to the usage of rules containing e.within(elem1) and e.within(elem2). Otherwise, those elements will not provide the information e.query needs!

However, remember that ancestry tracking can be manually enabled by adding e.settings at the top of your document:

#import "@preview/elembic:1.1.1" as e

// Without the following, the query would return 0 results
#show: e.settings(track-ancestry: (child, parent))

#parent(child(name: "A"))
#child(name: "B")

#context {
  let nested-child = e.query(e.filters.and_(child, e.within(parent)))

  // "Nested child elements are A"
  // (Requires 'e.settings' at the top to work)
  [Nested child elements are #nested-child.map(it => e.fields(it).name).join[, ]]
}