Widgets
Los widgets son la manera en que markast lleva componentes ricos y estructurados a través de Markdown hasta el AST.
Sintaxis
:::widget-name key="value" key2=value
Contenido del cuerpo con soporte completo de **markdown**.
# slot-name
Contenido de un slot adicional.
:::
…llega al AST como:
{
"type": "widget",
"widget": "widget-name",
"props": { "key": "value", "key2": "value" },
"slots": {
"default": [ /* cuerpo */ ],
"slot-name": [ /* slot */ ]
}
}
Widgets builtin
| Widget | Propósito | Props notables |
|---|---|---|
tip | Callout amistoso | title, icon |
note | Aclaración neutral | title, icon |
info | Callout informativo | title, icon |
warning | Callout de aviso | title, icon |
caution | Aviso más fuerte | title, icon |
danger | Aviso de acción destructiva | title, icon |
card | Contenedor con header/body/footer | title, color, elevated |
video | Embed de video | src*, poster, controls, … |
code-group | Bloques de código en pestañas | default_tab |
code-collapse | Bloque colapsable | summary, open |
tabs | Pestañas genéricas | default, vertical |
steps | Lista de pasos numerados | start |
badge | Etiqueta inline | label*, color |
Escribir un widget propio
from markast import BaseWidget, Parser, WidgetParam
class CalloutWidget(BaseWidget):
"""Un callout coloreado simple."""
name = "callout"
params = {
"level": WidgetParam(str, default="info",
choices=["info", "warn", "error"],
description="Severidad del callout"),
"title": WidgetParam(str, default=None),
}
parser = Parser(widgets=[CalloutWidget])
doc = parser.parse(""":::callout level=warn title="Atención"
Algo **importante**.
:::""")
print(doc.to_html())
Ese es el contrato completo. La clase base se encarga de parsear el header en props tipados, rellenar defaults, validar choices y emitir diagnósticos W004, validar props requeridos (W005), hacer roundtrip a Markdown, y un render HTML por defecto. Solo sobrescribes lo que necesitas.
Slots
El cuerpo de un widget se puede dividir en slots con nombre usando headings # slot-name de nivel 1 a la raíz del cuerpo:
:::card title="Perfil"
Contenido del slot por defecto.
# header
Contenido *personalizado* del header.
# footer
Un footer.
:::
Para que el widget reconozca los nombres extra de slot, decláralos en la clase:
class CardWidget(BaseWidget):
name = "card"
slots = ["header", "footer"]
params = { "title": WidgetParam(str, default=None) }
Si quieres aceptar cualquier nombre de slot (como tabs o steps), deja slots = [].
Tipos de Param
type_ | Valor en Markdown | Resultado |
|---|---|---|
str | key=hola | "hola" |
str | key="varias palabras" | "varias palabras" |
int | count=3 | 3 |
float | ratio=1.5 | 1.5 |
bool | autoplay=true | True (también 1, yes, on) |
list | tags=a,b,c | ["a", "b", "c"] |
list | tags=[1,2,3] | [1, 2, 3] (forma JSON) |
dict | meta={"a":1} | {"a": 1} (solo JSON) |
Enum | level=high | el miembro de Enum correspondiente |
Validadores propios:
WidgetParam(int, default=10,
validator=lambda x: None if 1 <= x <= 100 else "debe estar en 1..100")
Render personalizado
class BadgeWidget(BaseWidget):
name = "badge"
params = {
"label": WidgetParam(str, required=True),
"color": WidgetParam(str, default="gray"),
}
def to_html(self, node, render_children):
p = node["props"]
return f'<span class="badge badge-{p["color"]}">{p["label"]}</span>'
render_children es un callable que tu widget recibe. Pásale cualquier lista de nodos hijos y te devuelve el string renderizado en el formato del renderer. Úsalo para renderizar slots recursivamente:
def to_html(self, node, render_children):
body = render_children(node["slots"]["default"])
return f'<aside class="callout">{body}</aside>'
Patrones de registro
# 1. Por parser (recomendado) — mantiene el estado local.
from markast import Parser
parser = Parser(widgets=[MyWidget])
# 2. Globalmente en el registry por defecto — el parse() top-level lo verá.
from markast.widgets import default_registry
default_registry.register(MyWidget)
from markast import parse
parse(":::my-widget\n...\n:::")
Introspección de schema
CalloutWidget.schema()
# {
# "name": "callout",
# "params": {
# "level": {"type": "str", "required": False, "default": "info",
# "description": "Severidad del callout",
# "choices": ["info", "warn", "error"]},
# "title": {"type": "str", "required": False}
# },
# "slots": ["default"],
# "doc": "Un callout coloreado simple."
# }