Framework for custom elements and types in Typst. Supports Typst 0.11.0 or later.
Read the book: https://pgbiel.github.io/elembic
Mirrors: GitHub (pgbiel/elembic); Codeberg (pgbiel/elembic)
About
Elembic lets you create custom elements, which are reusable and customizable document components, with support for typechecked fields, show and set rules (without using any state
by default!), reference and outline support, as well as features not present in Typst elements today such as revokable rules and nested element selectors. In addition, Elembic lets you create custom types, which also support typechecked fields but also custom casting, and can be used in element fields or by themselves in arbitrary Typst code.
Elembic’s name comes from “element” + “alembic”, to indicate that one of Elembic’s goals is to experiment with different approaches for this functionality, and to help shape a future update to Typst that includes native custom elements, which will eventually remove the need for using this package.
Its documentation is at the Elembic Handbook: https://pgbiel.github.io/elembic
This library has a few limitations which are appropriately noted in the book.
Who is elembic for?
Elembic is especially suited for:
- Packages: elembic’s elements allows creating reusable components which can be freely customized by your package’s end users. With typechecking and other features, elembic’s got you covered in terms of flexibility. See the “Simple Theorems” example for a sample.
- Templates: elembic’s elements can be used for fine-grained configuration of parts of your template. See the “Simple Thesis” example for a sample.
Installation
Just import the latest elembic version from the package manager and you’re ready to go!
#import "@preview/elembic:1.0.0" as e
// See the full example below
#show: e.set_(element, stroke: red)
// ...
Example
Custom element
#import "@preview/elembic:1.0.0" as e: field, types
#let bigbox = e.element.declare(
"bigbox",
prefix: "@preview/my-package,v1",
doc: "A fancy and customizable box.",
display: it => block(fill: it.fill, stroke: it.stroke, inset: 5pt, it.body),
fields: (
field("body", types.option(content), doc: "Box contents", required: true),
field("fill", types.option(types.paint), doc: "Box fill"),
field("stroke", types.option(stroke), doc: "Box border", default: red)
)
)
#bigbox[abc]
#show: e.set_(bigbox, fill: red)
#bigbox(stroke: blue + 2pt)[def]
Custom type
#import "@preview/elembic:1.0.0" as e: field, types
#let person = e.types.declare(
"person",
prefix: "@preview/my-package,v1",
doc: "Relevant data for a person.",
fields: (
field("name", str, doc: "Person's name", required: true),
field("age", int, doc: "Person's age", default: 40),
field("preference", types.any, doc: "Anything the person likes", default: none)
),
casts: (
(from: str, with: person => name => person(name)),
)
)
#assert.eq(
e.repr(person("John", age: 50, preference: "soup")),
"person(age: 50, name: \"John\", preference: \"soup\")"
)
// Manually invoke typechecking and cast
#assert.eq(
types.cast("abc", person),
(true, person("abc"))
)
// Can then use 'person' as the type of an element's field, for example
Source structure
pub/
: Contains public re-exports of each module (so we can keep some things private)data
: Functions and constants related to extracting data from elements and typeselement
: Functions related to creating and using elements and their rules (set rules, revoke rules and so on)types
: Functions and constants related to Elembic’s custom type systemfields
: Functions related to element and type field parsing
License
Licensed under MIT or Apache-2.0, at your option.