@edc-motor/ui 0.4.25 → 0.4.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edc-motor/ui",
3
- "version": "0.4.25",
3
+ "version": "0.4.27",
4
4
  "description": "EdC Motor — componentes públicos Vue 3 + tokens SCSS para webs de juegos de mesa (paquete fuente: lo compila el consumidor con Vite)",
5
5
  "license": "GPL-3.0-only",
6
6
  "type": "module",
package/scss/_tokens.scss CHANGED
@@ -38,7 +38,7 @@ $k-fw-bold: 700;
38
38
  $k-lh-tight: 1.15;
39
39
  $k-lh-ui: 1.35;
40
40
  $k-lh-body: 1.55;
41
- $fs-11: 11px; $fs-12: 12px; $fs-13: 13px; $fs-14: 14px; $fs-16: 16px; $fs-18: 18px; $fs-20: 20px; $fs-24: 24px; $fs-28: 28px; $fs-32: 32px;
41
+ $fs-11: 11px; $fs-12: 12px; $fs-13: 13px; $fs-14: 14px; $fs-16: 16px; $fs-18: 18px; $fs-20: 20px; $fs-24: 24px; $fs-28: 28px; $fs-32: 32px; $fs-36: 36px; $fs-40: 40px;
42
42
 
43
43
  // Espaciado (px, como kontuan)
44
44
  $space-0: 0;
@@ -1,7 +1,7 @@
1
1
  @use "tokens" as *;
2
2
 
3
3
  // Bloque `related` (BlockRelated.vue): cabecera en rejilla (títulos + botón
4
- // al índice a la derecha en ≥sm, patrón del relateds de CDL) sobre una
4
+ // al índice a la derecha en ≥600, patrón del relateds de CDL) sobre una
5
5
  // PreviewGrid compacta.
6
6
  .block--related {
7
7
  .block__related-header {
@@ -9,7 +9,11 @@
9
9
  grid-template-columns: 1fr;
10
10
  gap: $space-4;
11
11
 
12
- @media (min-width: $bp-sm) {
12
+ // 600px es un umbral PROPIO de esta cabecera (sin token: no encaja en
13
+ // $bp-sm/$bp-md) — por debajo, título + subtítulo + botón caen en TRES
14
+ // filas (una columna); container query sobre `content` (el ancho real
15
+ // del bloque), coherente con el resto de bloques con imagen.
16
+ @container content (min-width: 600px) {
13
17
  grid-template-columns: 1fr auto;
14
18
  align-items: center;
15
19
  }
@@ -24,14 +28,14 @@
24
28
  .block__related-button {
25
29
  justify-self: center; // estrecho: los botones de bloque van centrados
26
30
 
27
- @media (min-width: $bp-sm) {
31
+ @container content (min-width: 600px) {
28
32
  justify-self: end;
29
33
  }
30
34
  }
31
35
 
32
36
  // Centrado/derecha del campo común `align`: la cabecera acompaña.
33
37
  &.block--align-center .block__related-header {
34
- @media (min-width: $bp-sm) {
38
+ @container content (min-width: 600px) {
35
39
  grid-template-columns: 1fr;
36
40
  }
37
41
  }
@@ -2,8 +2,14 @@
2
2
 
3
3
  // Bloques públicos del CRM (doc 03): estilos base, cada juego los ajusta.
4
4
  .block {
5
- // Aire vertical entre bloques (CDL usa 4rem; aquí algo más contenido).
5
+ // Aire vertical entre bloques (CDL usa 4rem; aquí algo más contenido). En
6
+ // estrecho (contenedor `content') baja un poco: los bloques respiran
7
+ // menos apretados en pantallas pequeñas.
6
8
  padding-block: $space-8 + $space-6; // 56px
9
+
10
+ @container content (max-width: #{$bp-sm - 1px}) {
11
+ padding-block: $space-8 + $space-2; // 40px
12
+ }
7
13
  background: var(--block-bg, transparent);
8
14
 
9
15
  &__inner {
@@ -15,18 +21,47 @@
15
21
  }
16
22
 
17
23
  // Anchura del contenido (campo común `width`): coherente entre bloques.
18
- &--w-narrow .block__inner { max-width: 800px; }
24
+ &--w-narrow .block__inner { max-width: 880px; }
19
25
  &--w-full .block__inner { max-width: none; padding-inline: $space-6; }
20
26
 
21
- // Tipografía de bloque: texto $fs-16, subtítulo $fs-20, título $fs-28
22
- // (la cabecera sube a 32/24). Los títulos nunca se justifican.
27
+ // Las tarjetas (CTA, tarjeta de texto y cita) son algo más angostas que el
28
+ // resto en cada preset (wide/narrow), con la diferencia PROPORCIONAL (no
29
+ // un recorte fijo que desaparece de golpe): min(tope, 90%) — 90% ≈ la
30
+ // relación 1080/1200 del preset ancho, así que en pantallas grandes manda
31
+ // el tope (120px/100px más angosto que el resto) y, según se estrecha,
32
+ // manda el porcentaje y la diferencia decrece sin saltos. "Ancho
33
+ // completo" no lleva tope: full es full.
34
+ &--cta.block--w-wide .block__inner,
35
+ &--text-card.block--w-wide .block__inner,
36
+ &--quote.block--w-wide .block__inner { max-width: min(1080px, 90%); }
37
+ &--cta.block--w-narrow .block__inner,
38
+ &--text-card.block--w-narrow .block__inner,
39
+ &--quote.block--w-narrow .block__inner { max-width: min(780px, 90%); }
40
+
41
+ // Tipografía de bloque: texto BASE $fs-16 (el mismo cuerpo que el wysiwyg
42
+ // por defecto — sin override aquí), subtítulo $fs-24 (entre el título y
43
+ // la base), título $fs-32 (la cabecera sube a 40/28). Los títulos nunca
44
+ // se justifican.
23
45
  // width 100%: en grids con justify-items start (el cuerpo del CTA) el
24
46
  // elemento encoge a su contenido y el text-align no se vería.
25
- &__title { margin: 0; font-size: $fs-28; width: 100%; }
47
+ &__title { margin: 0; font-size: $fs-32; width: 100%; }
26
48
  // pre-line: el subtítulo es un textarea — sus saltos de línea cuentan.
27
- &__subtitle { margin: 0; color: $text-2; font-size: $fs-20; white-space: pre-line; width: 100%; }
49
+ &__subtitle { margin: 0; color: $text-2; font-size: $fs-24; white-space: pre-line; width: 100%; }
28
50
  &__text { color: $text-1; font-size: $fs-16; }
29
51
 
52
+ // Alineación del bloque (campo común `align`): por CLASE (BlockShell ya
53
+ // no la mete como style en línea) para poder pisarla en estrecho.
54
+ &--align-left { text-align: left; }
55
+ &--align-center { text-align: center; }
56
+ &--align-right { text-align: right; }
57
+ &--align-justify { text-align: justify; }
58
+
59
+ // Justificado, a la IZQUIERDA por debajo de $bp-sm — mismo criterio (y
60
+ // mismo mecanismo, @media de viewport) que título/subtítulo, más abajo.
61
+ @media (max-width: $bp-sm - 1px) {
62
+ &--align-justify { text-align: left; }
63
+ }
64
+
30
65
  &--align-justify &__title,
31
66
  &--align-justify &__subtitle { text-align: left; }
32
67
 
@@ -52,17 +87,20 @@
52
87
  &--subtitle-right &__subtitle { text-align: right; }
53
88
  }
54
89
 
90
+ // Etiqueta de la tarjeta de texto: SIN chip — solo texto coloreado de
91
+ // acento, con alineación propia (campo label_align del bloque).
55
92
  &__label {
56
- display: inline-block;
57
- justify-self: start;
58
- font-size: $fs-12;
93
+ display: block;
94
+ width: 100%;
95
+ font-size: $fs-13;
59
96
  font-weight: $k-fw-semibold;
60
97
  text-transform: uppercase;
61
98
  letter-spacing: 0.05em;
62
99
  color: $accent-500;
63
- border: 1px solid color-mix(in srgb, $accent-500 45%, transparent);
64
- border-radius: $radius-pill;
65
- padding: 2px $space-3;
100
+
101
+ &--left { text-align: left; }
102
+ &--center { text-align: center; }
103
+ &--right { text-align: right; }
66
104
  }
67
105
 
68
106
  &__card {
@@ -80,8 +118,8 @@
80
118
  .edc-button { justify-self: start; text-decoration: none; }
81
119
  }
82
120
 
83
- &--header .block__title { font-size: $fs-32; }
84
- &--header .block__subtitle { font-size: $fs-24; }
121
+ &--header .block__title { font-size: $fs-40; }
122
+ &--header .block__subtitle { font-size: $fs-28; }
85
123
 
86
124
  &__media-layout { display: grid; gap: $space-4; }
87
125
  &__image {
@@ -90,9 +128,11 @@
90
128
  justify-self: center;
91
129
  }
92
130
 
131
+ // Columnas solo de $bp-md (768) para arriba: por debajo el bloque pasa a
132
+ // VERTICAL y la imagen va SIEMPRE encima del texto (regla más abajo).
93
133
  &--image-left .block__media-layout,
94
134
  &--image-right .block__media-layout {
95
- @container content (min-width: #{$bp-sm}) {
135
+ @container content (min-width: #{$bp-md}) {
96
136
  // Reparto izquierda:derecha del campo image_columns (--img-cols).
97
137
  grid-template-columns: var(--img-cols, minmax(0, 2fr) minmax(0, 3fr));
98
138
  align-items: stretch;
@@ -102,8 +142,21 @@
102
142
  &--image-right .block__media-layout .block__image { order: 2; }
103
143
  &--image-bottom .block__media-layout .block__image { order: 2; }
104
144
 
105
- // Marco de la imagen en columnas: el ALTO lo marca el texto de al lado
106
- // (la imagen absoluta no aporta altura) y el modo de escalado hace el resto.
145
+ // En vertical (contenedor < 768) la imagen SIEMPRE encima del texto, da
146
+ // igual su posición configurada (derecha/abajo incluidas). Después de las
147
+ // reglas de order de arriba para ganarles el empate.
148
+ @container content (max-width: #{$bp-md}) {
149
+ &[class*="block--image-"] .block__media-layout > .block__image,
150
+ &[class*="block--image-"] .block__media-layout > .block__image-frame {
151
+ order: -1;
152
+ }
153
+ }
154
+
155
+ // Marco de la imagen en columnas: por defecto (cubrir/rellenar) el ALTO
156
+ // lo marca el texto de al lado (la imagen absoluta no aporta altura) y el
157
+ // modo de escalado hace el resto — object-position top: con el marco
158
+ // estirado, "cubrir" centraba la imagen en vertical, SIEMPRE anclada
159
+ // arriba.
107
160
  &__image-frame {
108
161
  position: relative;
109
162
  min-height: 200px; // que no colapse con textos muy cortos
@@ -116,13 +169,32 @@
116
169
  max-width: none;
117
170
  }
118
171
  }
119
-
120
- &--fit-contain &__image-frame .block__image { object-fit: contain; }
121
- &--fit-cover &__image-frame .block__image { object-fit: cover; }
172
+ &--fit-cover &__image-frame .block__image { object-fit: cover; object-position: top; }
122
173
  &--fit-fill &__image-frame .block__image { object-fit: fill; }
123
174
 
124
- // En una columna (estrecho) la imagen vuelve al flujo normal.
125
- @container content (max-width: #{$bp-sm}) {
175
+ // "Contener" es la excepción: la imagen respeta su tamaño NATURAL — ancho
176
+ // de columna, alto según su propia proporción — sin ampliarse ni
177
+ // encogerse para casar con el marco (si es más alta que el texto la fila
178
+ // crece; si es más baja, queda aire debajo). Por eso escapa del marco
179
+ // absoluto: vuelve al flujo normal, anclada arriba (align-self) del grid
180
+ // de columnas.
181
+ &--fit-contain &__image-frame {
182
+ @container content (min-width: #{$bp-md}) {
183
+ position: static;
184
+ min-height: 0;
185
+ align-self: start;
186
+
187
+ .block__image {
188
+ position: static;
189
+ width: 100%;
190
+ height: auto;
191
+ max-width: none;
192
+ }
193
+ }
194
+ }
195
+
196
+ // En una columna (vertical, < 768) la imagen vuelve al flujo normal.
197
+ @container content (max-width: #{$bp-md}) {
126
198
  &__image-frame {
127
199
  position: static;
128
200
  min-height: 0;
@@ -137,7 +209,9 @@
137
209
  }
138
210
 
139
211
  // Imagen flotada: el texto la rodea y sigue por debajo (cleartext, como
140
- // CDL). En estrecho la imagen va arriba a lo ancho, sin flotar.
212
+ // CDL), con un margen de $space-4 (16px) hacia el texto por los lados que
213
+ // lo tocan. En vertical (< 768) la imagen va arriba a lo ancho, sin
214
+ // flotar.
141
215
  &--image-clear-left .block__media-layout,
142
216
  &--image-clear-right .block__media-layout {
143
217
  display: block;
@@ -145,20 +219,24 @@
145
219
  // Clearfix: el bloque no se corta si la imagen es más alta que el texto.
146
220
  &::after { content: ""; display: table; clear: both; }
147
221
 
148
- .block__image { margin-bottom: $space-3; }
222
+ .block__image { margin-bottom: $space-4; }
149
223
  }
150
224
  &--image-clear-left .block__media-layout .block__image {
151
- @container content (min-width: #{$bp-sm}) {
225
+ @container content (min-width: #{$bp-md}) {
152
226
  float: left;
153
227
  max-width: 50%;
154
- margin-right: $space-4;
228
+ // Respiro de $space-4 (16px) por arriba, abajo y hacia el texto
229
+ // (derecha); pegada a su lado exterior (izquierda, sin margen).
230
+ margin: $space-4 $space-4 $space-4 0;
231
+ vertical-align: top;
155
232
  }
156
233
  }
157
234
  &--image-clear-right .block__media-layout .block__image {
158
- @container content (min-width: #{$bp-sm}) {
235
+ @container content (min-width: #{$bp-md}) {
159
236
  float: right;
160
237
  max-width: 50%;
161
- margin-left: $space-4;
238
+ margin: $space-4 0 $space-4 $space-4;
239
+ vertical-align: top;
162
240
  }
163
241
  }
164
242
 
@@ -204,18 +282,20 @@
204
282
  }
205
283
 
206
284
  // La imagen del CTA sangra hasta el borde de la tarjeta según su posición:
207
- // en columnas toca arriba, abajo y su lateral (el título viaja a la
208
- // columna de texto); arriba/abajo toca los laterales. Esquinas siguiendo
209
- // el radio de la tarjeta.
210
- // La tarjeta del CTA lleva un halo SUTIL del acento, igual por los cuatro
211
- // bordes (sin offset), además de su sombra de profundidad normal.
212
- &--cta .block__card {
285
+ // en columnas toca TODA la altura (arriba, abajo y su lateral título y
286
+ // subtítulo viven dentro de `.block__cta-body`, junto al texto, en la
287
+ // columna de contenido); arriba/abajo toca los laterales. Esquinas
288
+ // siguiendo el radio de la tarjeta.
289
+ // Las tarjetas del CTA y de la tarjeta de texto llevan un halo SUTIL del
290
+ // acento, igual por los cuatro bordes, además de su sombra normal.
291
+ &--cta .block__card,
292
+ &--text-card .block__card {
213
293
  box-shadow: $shadow-sm, 0 0 18px color-mix(in srgb, $accent-500 30%, transparent);
214
294
  }
215
295
 
216
296
  &--cta.block--image-right .block__media-layout .block__image-frame,
217
297
  &--cta.block--image-left .block__media-layout .block__image-frame {
218
- @container content (min-width: #{$bp-sm}) {
298
+ @container content (min-width: #{$bp-md}) {
219
299
  overflow: hidden;
220
300
  min-height: 0;
221
301
 
@@ -223,13 +303,13 @@
223
303
  }
224
304
  }
225
305
  &--cta.block--image-right .block__media-layout .block__image-frame {
226
- @container content (min-width: #{$bp-sm}) {
306
+ @container content (min-width: #{$bp-md}) {
227
307
  margin: (-$space-8) (-$space-8) (-$space-8) 0;
228
308
  border-radius: 0 $radius-lg $radius-lg 0;
229
309
  }
230
310
  }
231
311
  &--cta.block--image-left .block__media-layout .block__image-frame {
232
- @container content (min-width: #{$bp-sm}) {
312
+ @container content (min-width: #{$bp-md}) {
233
313
  margin: (-$space-8) 0 (-$space-8) (-$space-8);
234
314
  border-radius: $radius-lg 0 0 $radius-lg;
235
315
  }
@@ -250,11 +330,11 @@
250
330
  border-radius: 0 0 $radius-lg $radius-lg;
251
331
  }
252
332
 
253
- // CTA con imagen en ESTRECHO: da igual la posición elegida — la imagen va
254
- // ARRIBA, a sangre por los laterales de la tarjeta y con proporción 2:1
255
- // (recortada, no deformada). Va DESPUÉS de las reglas por posición para
256
- // ganarles en el empate de especificidad.
257
- @container content (max-width: #{$bp-sm}) {
333
+ // CTA con imagen en VERTICAL (< 768): da igual la posición elegida — la
334
+ // imagen va ARRIBA, a sangre por los laterales de la tarjeta y con
335
+ // proporción 2:1 (recortada, no deformada). Va DESPUÉS de las reglas por
336
+ // posición para ganarles en el empate de especificidad.
337
+ @container content (max-width: #{$bp-md}) {
258
338
  &--cta[class*="block--image-"] .block__card .block__media-layout {
259
339
  > .block__image,
260
340
  > .block__image-frame {
@@ -297,8 +377,8 @@
297
377
  }
298
378
  }
299
379
 
300
- // Cita: sin adorno de borde, en GRANDE ($fs-32, el token más cercano a
301
- // los ~40px pedidos) y del color de acento.
380
+ // Cita: sin adorno de borde, en GRANDE ($fs-32, el mismo tamaño que un
381
+ // título de bloque) y del color de acento.
302
382
  &--quote .block__quote {
303
383
  margin: 0;
304
384
  // Fuente "especial" de la configuración de la web (doc 10); las fuentes
@@ -337,7 +417,21 @@
337
417
  a { color: $accent-500; text-decoration: none; }
338
418
  a:hover { text-decoration: underline; }
339
419
 
340
- // Tamaño por nivel: 24 22 (sin token entre 20 y 24) 20 (3 o más).
420
+ // Numerado: la numeración anidada (1, 1.1, 1.2.1…) la pinta el
421
+ // componente — fuera los números nativos del ol.
422
+ &--numbered {
423
+ list-style: none;
424
+ padding-left: 0;
425
+ }
426
+
427
+ .block__index-number {
428
+ margin-right: $space-2;
429
+ color: $text-2;
430
+ font-variant-numeric: tabular-nums;
431
+ }
432
+
433
+ // Tamaño por nivel: 24 → 22 (no hay token entre 20 y 24) → 20 (el 3
434
+ // agrupa "3 o más").
341
435
  .block__index-level-1 { font-size: $fs-24; }
342
436
  .block__index-level-2 { font-size: 22px; }
343
437
  .block__index-level-3 { font-size: $fs-20; }
@@ -26,6 +26,9 @@
26
26
  &--sm { max-width: 380px; }
27
27
  &--md { max-width: 560px; }
28
28
  &--lg { max-width: 820px; }
29
+ // Formularios con más columnas (el modal de bloque, EditModal): más ancho
30
+ // que el resto, pero sin llegar a ocupar toda la pantalla.
31
+ &--wide { max-width: 940px; }
29
32
 
30
33
  &__header {
31
34
  flex-shrink: 0;
@@ -50,11 +53,16 @@
50
53
  }
51
54
 
52
55
  // El cuerpo es la única zona que scrollea (cabecera y pie quedan fijos).
56
+ // Contenedor con nombre (container query, no viewport): los formularios de
57
+ // dentro (SchemaFields/PageBlocks) apilan sus filas de dos columnas según
58
+ // el ANCHO REAL del modal, no el del viewport.
53
59
  &__body {
54
60
  flex: 1 1 auto;
55
61
  min-height: 0;
56
62
  overflow-y: auto;
57
63
  padding: $space-5;
64
+ container-type: inline-size;
65
+ container-name: modal-body;
58
66
  }
59
67
 
60
68
  &__footer {
@@ -7,6 +7,12 @@
7
7
  font-size: $fs-16;
8
8
  line-height: $k-lh-body;
9
9
 
10
+ // Las NEGRITAS del wysiwyg, en el color de acento.
11
+ strong,
12
+ b {
13
+ color: $accent-500;
14
+ }
15
+
10
16
  // Aire entre TODOS los elementos del wysiwyg (párrafos, listas, títulos…).
11
17
  p,
12
18
  ul,
@@ -28,12 +34,13 @@
28
34
  > :first-child { margin-block-start: 0; }
29
35
  > :last-child { margin-block-end: 0; }
30
36
 
31
- // Escala del wysiwyg: h2 28 · h3 24 · h4 20 · h5 18 · h6 16.
32
- h2 { font-size: $fs-28; }
37
+ // Escala del wysiwyg: h2 32 (el mismo tamaño que un título de bloque) ·
38
+ // h3 24 · h4 22 (no hay token entre 20 y 24) · h5 20 · h6 18.
39
+ h2 { font-size: $fs-32; }
33
40
  h3 { font-size: $fs-24; }
34
- h4 { font-size: $fs-20; }
35
- h5 { font-size: $fs-18; }
36
- h6 { font-size: $fs-16; }
41
+ h4 { font-size: 22px; }
42
+ h5 { font-size: $fs-20; }
43
+ h6 { font-size: $fs-18; }
37
44
 
38
45
  ul,
39
46
  ol {
@@ -28,7 +28,9 @@ const layoutStyle = computed(() => {
28
28
  >
29
29
  <div class="block__card">
30
30
  <!-- En columnas, título y subtítulo viajan a la columna de texto para
31
- que la imagen pueda sangrar a TODA la altura de la tarjeta. -->
31
+ que la imagen pueda sangrar a TODA la altura de la tarjeta (a
32
+ todo ancho, en todos los anchos de pantalla: es una decisión de
33
+ plantilla por settings, no por el ancho del contenedor). -->
32
34
  <template v-if="!inColumns">
33
35
  <h2 v-if="settings.title" class="block__title">{{ settings.title }}</h2>
34
36
  <p v-if="settings.subtitle" class="block__subtitle">{{ settings.subtitle }}</p>
@@ -1,24 +1,43 @@
1
1
  <script setup lang="ts">
2
+ import { computed } from 'vue'
2
3
  import BlockShell from './BlockShell.vue'
3
4
 
4
5
  // Índice automático: enlaces de ancla a los bloques posteriores de la
5
6
  // página, con sangría por nivel (sin límite de profundidad: la escribe
6
7
  // IndexBlock::resolveData subiendo por la cadena de padres del bloque).
7
- defineProps<{
8
+ const props = defineProps<{
8
9
  settings: Record<string, unknown>
9
10
  data: { items?: { id: number; label: string; depth?: number }[] }
10
11
  }>()
12
+
13
+ // Numeración ANIDADA del modo numerado (1, 1.1, 1.2, 1.2.1…): la lista
14
+ // llega plana en preorden con `depth`, así que basta una pila de contadores.
15
+ const numbered = computed(() => {
16
+ const counters: number[] = []
17
+ return (props.data.items ?? []).map((item) => {
18
+ const depth = item.depth ?? 0
19
+ counters.length = depth + 1
20
+ counters[depth] = (counters[depth] ?? 0) + 1
21
+ return { ...item, number: counters.join('.') }
22
+ })
23
+ })
11
24
  </script>
12
25
 
13
26
  <template>
14
27
  <BlockShell :settings="settings" class="block--index">
15
28
  <h2 v-if="settings.title" class="block__title">{{ settings.title }}</h2>
16
29
  <p v-if="settings.subtitle" class="block__subtitle">{{ settings.subtitle }}</p>
17
- <component :is="settings.numbered ? 'ol' : 'ul'" class="block__index">
18
- <!-- Tamaño por nivel (24/22/20, el 3 agrupa "3 o más"); la SANGRÍA, en
19
- cambio, escala con la profundidad real (--depth, sin tope). -->
30
+ <component
31
+ :is="settings.numbered ? 'ol' : 'ul'"
32
+ class="block__index"
33
+ :class="{ 'block__index--numbered': settings.numbered }"
34
+ >
35
+ <!-- Tamaño por nivel (el 3 agrupa "3 o más"); la SANGRÍA, en cambio,
36
+ escala con la profundidad real (--depth, sin tope). Numerado: la
37
+ numeración es ANIDADA (1, 1.1, 1.2.1…), calculada aquí — el ol
38
+ nativo no sabe numerar una lista plana con profundidades. -->
20
39
  <li
21
- v-for="item in data.items ?? []"
40
+ v-for="item in numbered"
22
41
  :key="item.id"
23
42
  :style="{ '--depth': item.depth ?? 0 }"
24
43
  :class="[
@@ -26,6 +45,7 @@ defineProps<{
26
45
  { 'block__index-child': (item.depth ?? 0) > 0 },
27
46
  ]"
28
47
  >
48
+ <span v-if="settings.numbered" class="block__index-number">{{ item.number }}</span>
29
49
  <a :href="`#block-${item.id}`">{{ item.label }}</a>
30
50
  </li>
31
51
  </component>
@@ -19,13 +19,15 @@ const headingAlign = (key: 'title_align' | 'subtitle_align', prefix: string) =>
19
19
  return value && value !== 'inherit' ? `block--${prefix}-${value}` : ''
20
20
  }
21
21
 
22
+ // La alineación va por CLASE (block--align-*, _blocks.scss), no por style en
23
+ // línea: un style inline no se puede pisar desde CSS (bloqueaba el cambio a
24
+ // izquierda del justificado en estrecho, DC-03 ampliado).
22
25
  const style = computed<CSSProperties>(() => {
23
26
  const background = props.settings.background as string | undefined
24
27
  return {
25
28
  '--block-bg': background
26
29
  ? `color-mix(in srgb, ${background} var(--block-tint, 15%), transparent)`
27
30
  : 'transparent',
28
- textAlign: ((props.settings.align as string) || 'justify') as CSSProperties['textAlign'],
29
31
  }
30
32
  })
31
33
  </script>
@@ -33,7 +35,12 @@ const style = computed<CSSProperties>(() => {
33
35
  <template>
34
36
  <section
35
37
  class="block"
36
- :class="[width, align, headingAlign('title_align', 'title'), headingAlign('subtitle_align', 'subtitle')]"
38
+ :class="[
39
+ width,
40
+ align,
41
+ headingAlign('title_align', 'title'),
42
+ headingAlign('subtitle_align', 'subtitle'),
43
+ ]"
37
44
  :style="style"
38
45
  >
39
46
  <div class="block__inner">
@@ -12,7 +12,13 @@ defineProps<{ settings: Record<string, unknown>; data?: Record<string, unknown>
12
12
  :class="settings.image ? `block--image-${settings.image_position || 'top'}` : ''"
13
13
  >
14
14
  <div class="block__card">
15
- <span v-if="settings.label" class="block__label">{{ settings.label }}</span>
15
+ <span
16
+ v-if="settings.label"
17
+ class="block__label"
18
+ :class="`block__label--${settings.label_align || 'left'}`"
19
+ >
20
+ {{ settings.label }}
21
+ </span>
16
22
  <h2 v-if="settings.title" class="block__title">{{ settings.title }}</h2>
17
23
  <p v-if="settings.subtitle" class="block__subtitle">{{ settings.subtitle }}</p>
18
24
  <div class="block__media-layout">
@@ -11,7 +11,7 @@ const props = withDefaults(
11
11
  defineProps<{
12
12
  modelValue: boolean
13
13
  title?: string
14
- size?: 'sm' | 'md' | 'lg'
14
+ size?: 'sm' | 'md' | 'lg' | 'wide'
15
15
  closeLabel?: string
16
16
  }>(),
17
17
  { size: 'md', closeLabel: 'Cerrar' },
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { computed } from 'vue'
2
3
  import BaseModal from './BaseModal.vue'
3
4
  import { Save, X } from '@lucide/vue'
4
5
  import BaseButton from './BaseButton.vue'
@@ -11,7 +12,8 @@ const props = withDefaults(
11
12
  defineProps<{
12
13
  modelValue: boolean
13
14
  title: string
14
- size?: 'sm' | 'md' | 'lg'
15
+ /** 'wide' para formularios con más columnas (el modal de bloque). */
16
+ size?: 'normal' | 'wide'
15
17
  loading?: boolean
16
18
  submitLabel?: string
17
19
  cancelLabel?: string
@@ -20,7 +22,7 @@ const props = withDefaults(
20
22
  localeSwitchLabel?: string
21
23
  }>(),
22
24
  {
23
- size: 'md',
25
+ size: 'normal',
24
26
  loading: false,
25
27
  submitLabel: 'Guardar',
26
28
  cancelLabel: 'Cancelar',
@@ -29,6 +31,11 @@ const props = withDefaults(
29
31
  },
30
32
  )
31
33
 
34
+ // EditModal tiene su propia escala de tamaño (normal/wide, sobre el
35
+ // contenido del FORMULARIO); BaseModal sigue con la suya (sm/md/lg/wide,
36
+ // genérica) — normal se apoya en su 'md' por defecto.
37
+ const modalSize = computed(() => (props.size === 'wide' ? 'wide' : 'md'))
38
+
32
39
  const emit = defineEmits<{ 'update:modelValue': [boolean]; submit: [] }>()
33
40
 
34
41
  // Locale global del formulario: los campos traducibles del slot (Translatable*
@@ -46,7 +53,7 @@ function close() {
46
53
  <BaseModal
47
54
  :model-value="modelValue"
48
55
  :title="title"
49
- :size="size"
56
+ :size="modalSize"
50
57
  @update:model-value="(v) => !loading && emit('update:modelValue', v)"
51
58
  >
52
59
  <!-- Cabecera propia: título + selector de locale global (si hay campos