Skip to content

Worksheet Components

Worksheet components implement the WorksheetComponent subtrait, which pins the associated types for trait object usage.

trait WorksheetComponent: Component<Metadata = (), Output = WorksheetElement> {
fn id(&self) -> &'static str;
fn name(&self) -> &'static str;
fn description(&self) -> &'static str;
fn icon(&self) -> &'static str;
}

Child metadata is () — worksheet components do not receive spatial metadata from their placement. Each component compiles to a single WorksheetElement. A blanket impl covers any TypedComponent with matching types, so you only implement TypedComponent.

ComponentKeyDescription
SheetHeaderComponent"sheet_header"Page header with title, class name, name/date fields
SectionHeadingComponent"section_heading"Section divider with title and colored accent
QuestionComponent"question"Question with prompt, answer area, type, difficulty
GoalsComponent"goals"Success criteria checklist
AnswerGridComponent"answer_grid"Grid of answer options in columns
ActivityComponent"activity"Free-form activity content

Each component implements TypedComponent and receives a typed properties struct:

#[derive(modality_core::ComponentProperties)]
pub struct QuestionProps {
#[property(key = "questionText", name = "Question")]
pub question_text: String,
#[property(key = "questionNumber", name = "Number", default = "1")]
pub question_number: i32,
#[property(key = "questionType", name = "Type")]
pub question_type: QuestionType,
#[property(name = "Answers", description = "JSON-encoded answer options")]
pub answers: String,
#[property(key = "answerBoxHeight", name = "Answer Box Height", default = "80")]
pub answer_box_height: f64,
// ...
}
impl TypedComponent for QuestionComponent {
type Properties = QuestionProps;
type Metadata = ();
type Output = WorksheetElement;
fn id() -> &'static str { "question" }
fn name() -> &'static str { "Question" }
fn description() -> &'static str { "Question with prompt, answers, and answer area" }
fn icon() -> &'static str { "help_outline" }
fn compile(&self, props: &QuestionProps, _meta: &())
-> Result<WorksheetElement, CompileError>
{
Ok(WorksheetElement::Question(QuestionElement { /* ... */ }))
}
}

The blanket Component impl handles HashMap extraction automatically — TypedComponent::compile() always receives typed props.

Components register via inventory::submit! and are resolved by key:

inventory::submit!(WorksheetComponentEntry(&QuestionComponent));