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.
{
"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"]
}
presetsalimenta 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_classesentra en la biblioteca de clases globales como.bdg-<name>, y el administrador puede editarlas después.surfaces,site_partsytemplatesson 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:
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:
hoverse convierte en:hover,focusse convierte en:focus-visible,activese convierte en:active; - dark mode: el estado
darkse 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 descriptordesigner. Es lo que hace el elemento HTML del core.
Checklist antes de publicar#
- 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. - Sin acceso directo a las tablas de otro módulo. Guards con
class_existsyRoute::hasen cada integración entre módulos. - Tablas del módulo con el prefijo
mod_<slug>_y eliminadas enonUninstall. - Toda query de una collection aplica la visibilidad del módulo dueño.
- Toda vista de widget escapa todo, emite un
altobligatorio en las imágenes (o las marca como decorativas) y definewidthyheightcuando se conocen. - Un widget devuelve un string vacío cuando no tiene nada que mostrar, en lugar de una caja vacía.
- Sin script inline: la CSP del producto lo bloquea. El JS va en tu bundle.
- Con el Designer desinstalado, tu módulo funciona igual.
- Con el Designer instalado y ningún layout publicado, la pantalla muestra exactamente el Blade que mostraba antes.
- Cero violaciones de axe en cada pantalla nueva, en ambos roles.