{
  "$schema": "https://milpa.lat/schemas/component-contract.v1.json",
  "name": "drawer",
  "layer": "component",
  "version": "0.6.0",
  "status": "stable",
  "class": "mui-drawer",
  "summary": "Panel lateral modal sobre <dialog> nativo, anclado al inline-end: alto completo, radio 0, definido por su borde inline-start (no consume --z-*: vive en el top layer). Mismo contrato de foco/teclado que Modal. Entra deslizando desde el borde (keyframe propio mui-drawer-in, solo transform+opacity). --docked reutiliza la misma piel sobre un <aside> estático — panel persistente, NO modal (ver variants.mode y a11y.behavior: docked ≠ dialog).",
  "element": [
    "dialog",
    "aside (--docked)"
  ],
  "anatomy": {
    "root": ".mui-drawer — el <dialog> lateral; columna header/body/footer cuando está [open]",
    "header": ".mui-drawer__header — flex space-between: título + botón cerrar (composición .mui-btn--ghost --icon --sm)",
    "title": ".mui-drawer__title — text-lg medium",
    "body": ".mui-drawer__body — contenido scrolleable; text-sm var(--text-secondary)",
    "footer": ".mui-drawer__footer — anclado al fondo (margin-block-start auto), acciones al final, borde superior var(--border-subtle)"
  },
  "variants": {
    "placement": {
      "end": "default — anclado al inline-end (derecha en LTR)",
      "start": ".mui-drawer--start — anclado al inline-start (izquierda) — espeja ancla/borde/slide; para navs con toggle a la izquierda"
    },
    "mode": {
      "default": "modal — <dialog> abierto con showModal(); top layer, focus trap, Esc y backdrop nativos",
      "docked": ".mui-drawer--docked — panel lateral persistente sobre un <aside> estático — NO modal; sin backdrop/foco-trap/Esc/animación. position:static, height:100%, width:auto, mismo borde inline-start que el modal; ver a11y.behavior"
    }
  },
  "states": {
    "open": "[open] — el UA lo pone al llamar showModal(); dispara mui-drawer-in y el fade del ::backdrop (solo aplica al modo modal — --docked no usa [open])",
    "closed": "sin [open] — display none del UA; no hay clase de estado (n/a para --docked, que siempre está en el documento)",
    "focus": ":focus-visible en el propio dialog — outline 2px var(--focus) offset -2px (el panel toca el borde del viewport: el anillo va por dentro). --docked hereda el mismo estilo de foco si el <aside> llega a ser focusable, pero no lo fuerza",
    "docked": "--docked no tiene estados de apertura/cierre: el <aside> vive siempre en el flujo del documento, sin [open] ni transición"
  },
  "tokens": [
    "--surface",
    "--bg",
    "--border",
    "--border-subtle",
    "--text",
    "--text-secondary",
    "--focus",
    "--shadow-lg",
    "--font-heading",
    "--text-lg",
    "--text-sm",
    "--weight-medium",
    "--leading-snug",
    "--leading-normal",
    "--space-2",
    "--space-3",
    "--space-5",
    "--radius-none",
    "--dur-moderate",
    "--ease-grano",
    "--ease-standard"
  ],
  "a11y": {
    "element": "SIEMPRE <dialog> abierto con dialog.showModal() — top layer, focus trap, Esc y fondo inerte nativos; nunca show() ni un aside posicionado",
    "aria": [
      "aria-labelledby en el <dialog> apuntando al id del __title",
      "el botón cerrar del header requiere aria-label (ej. \"Cerrar panel\")",
      "docked (`--docked`): `<aside aria-label=\"…\">` — sin `aria-labelledby` de dialog ni botón de cierre (es una region, no un dialog)"
    ],
    "keyboard": [
      "Esc cierra (cancel/close nativos)",
      "Tab queda atrapado dentro (top layer)",
      "cerrar sin JS: <form method=\"dialog\"> en los botones de cerrar/cancelar, con type=\"submit\" explícito — el submit-que-cierra es intencional"
    ],
    "behavior": [
      "retorno de foco (JS consumidor): guardar document.activeElement antes de showModal() y devolverle el foco en el evento close (solo aplica al modo modal)",
      "docked ≠ dialog: --docked se monta sobre un <aside aria-label=\"…\"> (landmark region), NUNCA un <dialog> ni showModal(). No atrapa el foco, no cierra con Esc, no tiene backdrop ni [open] — el Tab entra y sale del panel como de cualquier otro contenido en flujo. Si el panel necesita cerrarse/abrirse dinámicamente, eso vuelve a ser el patrón modal (--docked es para contenido siempre visible)"
    ],
    "contrast": "text y text-secondary sobre surface, focus sobre surface y border sobre bg — pares ya verificados por npm test. El separador del footer (var(--border-subtle)) es un delimitador decorativo, no sujeto al piso de 3:1. · Los .mui-btn outline default dentro de __footer reciben borde var(--border-strong) (boundary 3.13 dark / 5.58 light sobre surface — par en el gate); el default border/surface daría 2.17 en dark."
  },
  "motion": {
    "enter": "[open] → mui-drawer-in var(--dur-moderate) var(--ease-grano) both — translateX(100%)→0 + fade, germina desde el borde y se asienta; ::backdrop → milpa-fade var(--dur-moderate) var(--ease-standard)",
    "exit": "cierre nativo instantáneo (sale del top layer); sin animación de salida en v0",
    "reducedMotion": "contrato global de milpa-motion.css: el slide degrada a 1 frame estático (aparece asentado)",
    "rtl": "mui-drawer-in usa translateX(100%) (físico): en dir=\"rtl\" el panel queda en inline-end (izquierda) pero entraría desde la derecha; si servís RTL, redefiní el keyframe en tu capa"
  },
  "examples": [
    {
      "title": "Panel de configuración",
      "html": "<dialog class=\"mui-drawer\" id=\"drw-ajustes\" aria-labelledby=\"drw-ajustes-title\"><header class=\"mui-drawer__header\"><h2 class=\"mui-drawer__title\" id=\"drw-ajustes-title\">Ajustes del módulo</h2><form method=\"dialog\"><button type=\"submit\" class=\"mui-btn mui-btn--ghost mui-btn--icon mui-btn--sm\" aria-label=\"Cerrar panel\"><span aria-hidden=\"true\">✕</span></button></form></header><div class=\"mui-drawer__body\"><div class=\"mui-field\"><label class=\"mui-field__label\" for=\"drw-nombre\">Nombre del terreno</label><input class=\"mui-input\" id=\"drw-nombre\" value=\"milpa-prod\"></div></div><footer class=\"mui-drawer__footer\"><form method=\"dialog\"><button type=\"submit\" class=\"mui-btn\">Cancelar</button></form><button type=\"button\" class=\"mui-btn mui-btn--primary\">Guardar</button></footer></dialog><!-- JS consumidor: trigger.onclick = () => drw.showModal(); drw.addEventListener('close', () => trigger.focus()) -->"
    },
    {
      "title": "Panel docked (persistente, dentro de una región de 2 columnas)",
      "html": "<div class=\"two-col-layout\"><main>…contenido principal…</main><aside class=\"mui-drawer mui-drawer--docked\" aria-label=\"Registro de restauraciones\"><header class=\"mui-drawer__header\"><h2 class=\"mui-drawer__title\">Restore drills</h2></header><div class=\"mui-drawer__body\"><p>Últimas restauraciones verificadas…</p></div></aside></div><!-- sin JS: el <aside> está siempre en el documento, no hay trigger ni showModal() -->"
    }
  ]
}
