Ir al contenido
Bondry

Designer para autores

Los contratos que un módulo o un tema usa para extender el constructor de páginas, con la referencia de controles de schema.

Esta es la guía para extender el Designer, no para implementarlo. La regla que atraviesa toda la página: todo registro de aquí es inerte sin el Designer. Registras en tu propio boot(), y si el módulo no está instalado, no pasa nada. Un módulo nunca depende del constructor de páginas para funcionar.

Dónde viven los contratos#

Están en el core, junto a los demás registries. El módulo Designer los consume.

Contrato Para
App\Support\Widgets Elementos arrastrables en el editor
App\Support\Designer\Surfaces Pantallas cuyo centro el Designer puede reemplazar
App\Support\Designer\Collections Fuentes de datos para el elemento loop
App\Support\Designer\DynamicTags Valores dinámicos en los controles
App\Support\Designer\Conditions Reglas de visualización por nodo y por documento
App\Support\Designer\FormActions Acciones tras el envío de un formulario
App\Support\Designer\Tokens Lectura de los tokens del tema activo
App\Support\Breadcrumbs El rastro que lee el elemento breadcrumb

Todo es estático y se registra en boot(). Los labels siempre son callables: en el momento del boot, tu archivo de idioma puede no estar cargado todavía.

Los registros del lado del módulo se cubren en Widgets y surfaces. Lo que sigue es el lado del tema y el material de referencia.

Tokens del tema#

El Designer no crea tokens: lee los que declara el tema activo. Cada control de customizer.json con un var se convierte en un token disponible en los selectores de color, fuente, radio y espaciado.

La cadena de padre a hijo se resuelve con eso, así que un child theme que sobrescribe --color-primary cambia todo layout que apunte a ese token, sin tocar el layout.

Cuando el tema no cubre algo, el administrador crea una variable de Designer (--bd-*), guardada por el módulo en su propia hoja de estilos. Cambiar una no vuelve a publicar nada.

themes/<slug>/designer.json#

Opcional, junto a customizer.json.

JSON
{
    "presets": {
        "spacing": ["0", "0.25rem", "0.5rem", "1rem", "1.5rem", "2rem", "3rem"],
        "shadows": { "soft": "0 1px 2px rgba(15, 23, 42, 0.06)" },
        "type_scale": { "sm": "0.875rem", "base": "1rem", "lg": "1.125rem" }
    },
    "global_classes": [
        { "name": "card", "styles": { "desktop": { "normal": {
            "background-color": { "token": "--color-background" },
            "border-radius": { "token": "--radius" },
            "padding": "1.5rem"
        }}}}
    ],
    "surfaces": { "blog.index": "designer/blog-index.json" },
    "site_parts": { "header": "designer/header.json" },
    "templates": ["designer/templates/hero-1.json"]
}
  • presets alimenta los sliders y pickers del inspector. Un tema con su propia escala tipográfica hace que el editor ofrezca esa escala en lugar de valores crudos.
  • global_classes entra en la biblioteca de clases globales como .bdg-<name>, y el administrador puede editarlas después.
  • surfaces, site_parts y templates son layouts que tu tema sugiere. Activar el tema ofrece importarlos; nunca sobrescribe lo que el usuario ya publicó sin confirmación, y los layouts viven en la base de datos, así que cambiar de tema preserva el trabajo.

Un valor de estilo puede ser crudo ("1.5rem") o apuntar a un token ({ "token": "--radius" }). Prefiere el token: es lo que hace que un layout siga un cambio de tema.

La cascada#

De más débil a más fuerte:

Código
CSS del tema  ->  CSS del módulo  ->  variables de Designer  ->  hoja de estilos del documento  ->  CSS personalizado del tema

Reglas de la hoja de estilos compilada del documento:

  • sin !important;
  • un selector por nodo (.bd-<id>) y uno por clase global (.bdg-<name>), ambos con la misma especificidad, así que el orden decide y el estilo local de un nodo siempre va después de la clase global;
  • desktop first, con media queries de la más ancha a la más angosta, que es lo que hace que la herencia de desktop a tablet a mobile funcione sin trucos de especificidad;
  • estados: hover se convierte en :hover, focus se convierte en :focus-visible, active se convierte en :active;
  • dark mode: el estado dark se convierte en .dark .bd-<id>. Un tema que declara soporte de dark mode no tiene nada más que hacer.

Referencia de controles de schema#

Estos son los valores de type aceptados en designer.schema de un widget, en filters de una collection y en schema de una form action. label siempre es una clave de idioma.

Type Valor guardado Opciones del descriptor
text string maxlength, placeholder
textarea string rows
code string monoespaciado, nunca se ejecuta
richtext HTML saneado el editor del core, bajo demanda
number int o float min, max, step
slider string con una unidad ("24px") min, max, unit, presets, token_group
select string options
select2 lista de strings options, selección múltiple
choose string options, radios dibujados como botones
switcher bool
color string o {token} la paleta del tema en el picker
media {path,width,height} accept, meta
icon nombre de ícono o un SVG subido
url string solo esquemas seguros
gallery lista de {image,alt,caption}
repeater lista de maps fields, add
dimensions {top,right,bottom,left} cuatro lados con un toggle de enlace
font string las familias del tema
popup id de popup picker de popups publicados
heading nada un label de sección
divider nada una regla

Los controles que guardan un solo valor (text, textarea, url, media, number, slider, color) reciben el botón de dynamic tag automáticamente.

Permisos#

El Designer no inventa un sistema de permisos por módulo. Usa la matriz de grupos:

  • una surface se restringe mediante designer.surfaces.<module>, generado automáticamente a partir de las surfaces que registraste: no hay nada que hacer en tu módulo;
  • un elemento individual lo restringe el administrador, en Designer > Element Manager;
  • para exigir un permiso antes de que se pueda guardar un nodo de tu elemento, declara 'requires' => 'unfiltered_html' en el descriptor designer. Es lo que hace el elemento HTML del core.

Checklist antes de publicar#

  1. Todo label es un callable que devuelve __('...') desde los archivos de idioma de tu módulo. Ningún string hardcodeado en PHP, Blade o JS.
  2. Sin acceso directo a las tablas de otro módulo. Guards con class_exists y Route::has en cada integración entre módulos.
  3. Tablas del módulo con el prefijo mod_<slug>_ y eliminadas en onUninstall.
  4. Toda query de una collection aplica la visibilidad del módulo dueño.
  5. Toda vista de widget escapa todo, emite un alt obligatorio en las imágenes (o las marca como decorativas) y define width y height cuando se conocen.
  6. Un widget devuelve un string vacío cuando no tiene nada que mostrar, en lugar de una caja vacía.
  7. Sin script inline: la CSP del producto lo bloquea. El JS va en tu bundle.
  8. Con el Designer desinstalado, tu módulo funciona igual.
  9. Con el Designer instalado y ningún layout publicado, la pantalla muestra exactamente el Blade que mostraba antes.
  10. Cero violaciones de axe en cada pantalla nueva, en ambos roles.