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

WidgetPropósitoProps notables
tipCallout amistosotitle, icon
noteAclaración neutraltitle, icon
infoCallout informativotitle, icon
warningCallout de avisotitle, icon
cautionAviso más fuertetitle, icon
dangerAviso de acción destructivatitle, icon
cardContenedor con header/body/footertitle, color, elevated
videoEmbed de videosrc*, poster, controls, …
code-groupBloques de código en pestañasdefault_tab
code-collapseBloque colapsablesummary, open
tabsPestañas genéricasdefault, vertical
stepsLista de pasos numeradosstart
badgeEtiqueta inlinelabel*, 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 MarkdownResultado
strkey=hola"hola"
strkey="varias palabras""varias palabras"
intcount=33
floatratio=1.51.5
boolautoplay=trueTrue (también 1, yes, on)
listtags=a,b,c["a", "b", "c"]
listtags=[1,2,3][1, 2, 3] (forma JSON)
dictmeta={"a":1}{"a": 1} (solo JSON)
Enumlevel=highel 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:::")
i
El patrón 1 es preferible para librerías y servidores — distintos parsers pueden tener distintos widgets sin contaminarse entre sí.

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."
# }