@edc-motor/admin-kit 0.5.3 → 0.5.5

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/admin-kit",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "EdC Motor — kit del panel de administración: layout, gestores CRUD, imágenes, PDF, páginas, configuración, usuarios y copias (paquete fuente: lo compila el consumidor con Vite)",
5
5
  "license": "GPL-3.0-only",
6
6
  "type": "module",
@@ -33,7 +33,7 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@edc-motor/ui": "^0.5.3",
36
+ "@edc-motor/ui": "^0.5.5",
37
37
  "axios": "^1.7.0"
38
38
  },
39
39
  "peerDependencies": {
@@ -368,40 +368,50 @@
368
368
 
369
369
  // Campos del bloque seleccionado: etiqueta + valor; los valores largos se
370
370
  // recortan a unas pocas líneas.
371
- // Campo del panel de bloque: «Label: valor» EN LÍNEA; las imágenes van
372
- // apiladas (is-stacked), con la miniatura debajo del label.
371
+ // Campo del panel de bloque: «Label: valor» como UN ÚNICO PÁRRAFO corrido
372
+ // dt y dd van en línea, el valor arranca tras los dos puntos y, al
373
+ // envolver, ocupa el ancho completo bajo el label (nada de dos columnas).
374
+ // El clamp de líneas vive ahora en el PÁRRAFO entero (el -webkit-box no
375
+ // convive con hijos inline sueltos, así que recorta label+valor juntos:
376
+ // con labels de una palabra, en la práctica es el mismo recorte de antes).
377
+ // Las imágenes van apiladas (is-stacked), con la miniatura bajo el label.
373
378
  &__field {
374
- display: flex;
375
- align-items: baseline;
376
- gap: $space-1;
377
379
  min-width: 0;
378
380
  font-size: $fs-13;
381
+ overflow-wrap: anywhere;
382
+ display: -webkit-box;
383
+ -webkit-box-orient: vertical;
384
+ -webkit-line-clamp: 4;
385
+ line-clamp: 4;
386
+ overflow: hidden;
379
387
 
380
388
  dt {
381
- flex: none;
389
+ display: inline;
382
390
  color: $text-3;
383
391
  font-size: $fs-12;
384
392
  text-transform: uppercase;
385
393
  letter-spacing: 0.04em;
386
394
 
387
- &::after { content: ':'; }
395
+ // El hueco tras los dos puntos va en margen: Vue condensa el
396
+ // whitespace entre <dt> y <dd> y en línea quedarían pegados.
397
+ &::after {
398
+ content: ':';
399
+ margin-inline-end: 0.4em;
400
+ }
388
401
  }
389
402
 
390
403
  dd {
404
+ display: inline;
391
405
  margin: 0;
392
- min-width: 0;
393
406
  color: $text-2;
394
- overflow-wrap: anywhere;
395
- display: -webkit-box;
396
- -webkit-box-orient: vertical;
397
- -webkit-line-clamp: 4;
398
- line-clamp: 4;
399
- overflow: hidden;
400
407
  }
401
408
 
402
409
  &.is-stacked {
403
410
  display: grid;
404
411
  gap: 2px;
412
+ -webkit-line-clamp: none;
413
+ line-clamp: none;
414
+ overflow: visible;
405
415
 
406
416
  dt::after { content: none; }
407
417
  }
@@ -208,6 +208,20 @@
208
208
  // cortarse (180px sobraba de aire).
209
209
  &__field-align { flex: 0 0 140px; }
210
210
 
211
+ // Fila declarada en el esquema (Field::row): el grupo baja por recursión
212
+ // como un .schema-fields anidado — aquí solo se reparte en columnas
213
+ // iguales MIENTRAS QUEPAN (auto-fit: con poco ancho van cayendo solas);
214
+ // el checkbox se centra en vertical junto a sus compañeros de fila.
215
+ &__field--pair > .schema-fields {
216
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
217
+ gap: $space-3;
218
+ align-items: start;
219
+
220
+ > .schema-fields__field:has(> .schema-fields__field-main > .checkbox) {
221
+ align-self: center;
222
+ }
223
+ }
224
+
211
225
  // Imagen a dos columnas: el input a la izquierda, sus ajustes (posición,
212
226
  // escalado, reparto de columnas) apilados a la derecha. `stretch` para que
213
227
  // la columna de la imagen llene el alto de la fila; el cuadro de arrastre
@@ -253,6 +267,9 @@
253
267
  &__field-align { flex-basis: auto; }
254
268
 
255
269
  &__field--image-group { grid-template-columns: 1fr; }
270
+
271
+ // Las filas declaradas (Field::row) apilan en el modal angosto.
272
+ &__field--pair > .schema-fields { grid-template-columns: 1fr; }
256
273
  }
257
274
 
258
275
  &__color,
@@ -43,6 +43,8 @@ export interface FieldSchema {
43
43
  fields?: FieldSchema[] | null
44
44
  /** Para entity: endpoint de opciones del admin. */
45
45
  options_url?: string | null
46
+ /** Fila declarada en el esquema (Field::row): mismo nombre = misma fila. */
47
+ row?: string | null
46
48
  }
47
49
 
48
50
  const props = withDefaults(
@@ -76,6 +78,13 @@ const emit = defineEmits<{ 'update:modelValue': [value: Record<string, unknown>]
76
78
  // (`image_position`, `image_fit`, `image_columns`) en la misma lista se
77
79
  // agrupa con ellos — columna 1 la imagen, columna 2 los ajustes
78
80
  // apilados.
81
+ // 3) Filas DECLARADAS en el esquema (Field::row('nombre')): los campos que
82
+ // comparten `row` van juntos en una fila de columnas iguales mientras
83
+ // quepan (auto-fit; en un modal angosto apilan). Se pintan por RECURSIÓN
84
+ // sobre este mismo componente (los compañeros pierden su `row` al bajar,
85
+ // para no re-agrupar en bucle). La fila declarada es intención EXPLÍCITA
86
+ // del esquema: gana a las convenciones 1 y 2 — un campo con `row` queda
87
+ // fuera del emparejado implícito (tanto de origen como de destino).
79
88
  const IMAGE_SETTING_KEYS = ['image_position', 'image_fit', 'image_columns']
80
89
 
81
90
  interface LayoutItem {
@@ -84,6 +93,8 @@ interface LayoutItem {
84
93
  align?: FieldSchema
85
94
  /** Ajustes de imagen (image_position/fit/columns) que viajan con `field`. */
86
95
  imageSettings?: FieldSchema[]
96
+ /** Campos de una fila declarada (Field::row), este incluido y sin `row`. */
97
+ rowFields?: FieldSchema[]
87
98
  }
88
99
 
89
100
  const layout = computed<LayoutItem[]>(() => {
@@ -92,32 +103,52 @@ const layout = computed<LayoutItem[]>(() => {
92
103
  const consumed = new Set<string>() // claves ya emparejadas con otro campo
93
104
 
94
105
  for (const field of props.fields) {
95
- if (field.type !== 'select' || !field.key.endsWith('_align')) continue
106
+ if (field.type !== 'select' || !field.key.endsWith('_align') || field.row) continue
96
107
  const base = field.key.slice(0, -'_align'.length)
97
108
  if (!base) continue // el "align" general no tiene prefijo
98
109
  const baseKey = byKey.has(base) ? base : byKey.has(`${base}_text`) ? `${base}_text` : null
99
- if (!baseKey) continue
110
+ if (!baseKey || byKey.get(baseKey)?.row) continue // el base va en fila declarada
100
111
  alignOf.set(baseKey, field)
101
112
  consumed.add(field.key)
102
113
  }
103
114
 
104
115
  const imageSettingsOf = new Map<string, FieldSchema[]>()
105
116
  for (const field of props.fields) {
106
- if (field.key !== 'image' || field.type !== 'image') continue
117
+ if (field.key !== 'image' || field.type !== 'image' || field.row) continue
107
118
  const settings = IMAGE_SETTING_KEYS.map((key) => byKey.get(key)).filter(
108
- (f): f is FieldSchema => !!f,
119
+ (f): f is FieldSchema => !!f && !f.row,
109
120
  )
110
121
  if (!settings.length) continue
111
122
  imageSettingsOf.set(field.key, settings)
112
123
  settings.forEach((f) => consumed.add(f.key))
113
124
  }
114
125
 
126
+ // Filas declaradas: agrupa por nombre de fila los campos aún libres (sin
127
+ // pareja de align/imagen). El grupo se pinta donde va su PRIMER campo.
128
+ const rowGroups = new Map<string, FieldSchema[]>()
129
+ for (const field of props.fields) {
130
+ if (!field.row) continue
131
+ const group = rowGroups.get(field.row) ?? []
132
+ group.push(field)
133
+ rowGroups.set(field.row, group)
134
+ }
135
+ const rowFieldsOf = new Map<string, FieldSchema[]>()
136
+ for (const group of rowGroups.values()) {
137
+ if (group.length < 2) continue // solo, se pinta como siempre
138
+ rowFieldsOf.set(
139
+ group[0].key,
140
+ group.map((f) => ({ ...f, row: null })),
141
+ )
142
+ group.slice(1).forEach((f) => consumed.add(f.key))
143
+ }
144
+
115
145
  return props.fields
116
146
  .filter((f) => !consumed.has(f.key))
117
147
  .map((f) => ({
118
148
  field: f,
119
149
  align: alignOf.get(f.key),
120
150
  imageSettings: imageSettingsOf.get(f.key),
151
+ rowFields: rowFieldsOf.get(f.key),
121
152
  }))
122
153
  })
123
154
 
@@ -211,8 +242,23 @@ function addLabel(): string {
211
242
 
212
243
  <template>
213
244
  <div class="schema-fields">
214
- <template v-for="{ field, align, imageSettings } in layout" :key="field.key">
245
+ <template v-for="{ field, align, imageSettings, rowFields } in layout" :key="field.key">
246
+ <!-- Fila declarada (Field::row): los campos del grupo por recursión,
247
+ en columnas iguales (los compañeros bajan sin `row`) -->
248
+ <div v-if="rowFields" class="schema-fields__field schema-fields__field--pair">
249
+ <SchemaFields
250
+ :fields="rowFields"
251
+ :model-value="modelValue"
252
+ :locales="locales"
253
+ :api="api"
254
+ :icons="icons"
255
+ :rich-labels="richLabels"
256
+ :translate="translate"
257
+ @update:model-value="(v) => emit('update:modelValue', v)"
258
+ />
259
+ </div>
215
260
  <div
261
+ v-else
216
262
  class="schema-fields__field"
217
263
  :class="{
218
264
  'schema-fields__field--row': !!align,