@edc-motor/ui 0.5.5 → 0.5.7
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.5.
|
|
3
|
+
"version": "0.5.7",
|
|
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",
|
|
@@ -87,6 +87,23 @@ $icon-chevron-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000
|
|
|
87
87
|
cursor: pointer;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// MITIGACIÓN del «baile» al hover en el modal de bloque (DPI fraccionario):
|
|
91
|
+
// dentro de un modal, el realce de hover/focus es INSTANTÁNEO. La
|
|
92
|
+
// transición de border-color/box-shadow fuerza ~10 repintados seguidos con
|
|
93
|
+
// un área de invalidación mayor que el propio input (el box-shadow pinta
|
|
94
|
+
// fuera de su caja) y, con las filas del esquema en columnas de ancho
|
|
95
|
+
// fraccional (auto-fit 1fr) y un devicePixelRatio no entero (125%/150%),
|
|
96
|
+
// esa re-rasterización repetida puede hacer temblar los campos VECINOS.
|
|
97
|
+
// Un único repintado por cambio de estado elimina el tembleque sin tocar
|
|
98
|
+
// la geometría ni el aspecto final.
|
|
99
|
+
.modal & {
|
|
100
|
+
&__input,
|
|
101
|
+
&__textarea,
|
|
102
|
+
&__select {
|
|
103
|
+
transition: none;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
90
107
|
&--error &__input,
|
|
91
108
|
&--error &__textarea,
|
|
92
109
|
&--error &__select {
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
@use "tokens" as *;
|
|
2
|
+
|
|
3
|
+
// =========================================================
|
|
4
|
+
// Sistema COMPARTIDO de filas y grupos de formulario
|
|
5
|
+
// =========================================================
|
|
6
|
+
// Un único lenguaje para TODOS los formularios modales del admin:
|
|
7
|
+
// sustituye a los layouts ad hoc que había repartidos (el `__common-row` de
|
|
8
|
+
// PageBlocks y las filas `--pair`/`--row`/`--image-group` de SchemaFields).
|
|
9
|
+
//
|
|
10
|
+
// PROHIBIDO auto-fit/auto-fill en todo el sistema: columnas FIJAS + cortes
|
|
11
|
+
// EXPLÍCITOS de container query. Con auto-fit las pistas se re-resolvían
|
|
12
|
+
// contra el ancho de `modal-body` (que depende de si hay scrollbar),
|
|
13
|
+
// quedaban pistas fantasma a 0px y los `1fr` resultaban fraccionales
|
|
14
|
+
// desiguales: al repintar con hover a DPI 125 %/150 % los campos vecinos
|
|
15
|
+
// temblaban («baile» en modales largos). Con pistas fijas el layout es
|
|
16
|
+
// idéntico en cada repintado.
|
|
17
|
+
//
|
|
18
|
+
// El contenedor `modal-body` lo define _modal.scss sobre el cuerpo del
|
|
19
|
+
// modal: el patrón funciona en CUALQUIER modal y los cortes miden el ancho
|
|
20
|
+
// REAL del formulario, no el del viewport.
|
|
21
|
+
.form-row {
|
|
22
|
+
display: grid;
|
|
23
|
+
// Base: dos columnas fijas iguales.
|
|
24
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
25
|
+
gap: $space-3;
|
|
26
|
+
align-items: start;
|
|
27
|
+
|
|
28
|
+
> * { min-width: 0; }
|
|
29
|
+
|
|
30
|
+
// Un checkbox como celda: flex para que se comporte como bloque de su
|
|
31
|
+
// celda junto a sus compañeros de fila.
|
|
32
|
+
> .checkbox { display: flex; }
|
|
33
|
+
|
|
34
|
+
// Tres columnas fijas iguales, para filas de 3 campos pequeños.
|
|
35
|
+
&--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
36
|
+
|
|
37
|
+
// Columna izquierda MUCHO más ancha que la derecha (2:1): un textarea o
|
|
38
|
+
// wysiwyg a la izquierda con su campo pequeño natural al lado (p. ej. el
|
|
39
|
+
// select de alineación). 2fr y no 2.5fr: en el modal `md` (~520px útiles)
|
|
40
|
+
// 2:1 deja ~170px a la derecha — lo justo para un select con su etiqueta
|
|
41
|
+
// («Alineación del subtítulo»…) sin cortarse; con 2.5fr quedaría corto.
|
|
42
|
+
&--wide-left { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
|
|
43
|
+
|
|
44
|
+
// Fila de imagen (especial): la celda izquierda (el input de imagen, con
|
|
45
|
+
// su etiqueta) es UNA sola celda que llena TODO el alto de la fila; la
|
|
46
|
+
// derecha es una pila de filas (.form-row__stack) con los ajustes
|
|
47
|
+
// (posición, escalado, reparto de columnas…). El cuadro de arrastre NO se
|
|
48
|
+
// estira con la celda: conserva su tamaño natural y se centra en el
|
|
49
|
+
// espacio (margin-block auto en _image-upload.scss).
|
|
50
|
+
&--media {
|
|
51
|
+
align-items: stretch;
|
|
52
|
+
|
|
53
|
+
> :first-child {
|
|
54
|
+
display: flex;
|
|
55
|
+
flex-direction: column;
|
|
56
|
+
gap: $space-1;
|
|
57
|
+
min-width: 0;
|
|
58
|
+
min-height: 0;
|
|
59
|
+
|
|
60
|
+
// El input crece para llenar la celda (envoltorios de etiqueta
|
|
61
|
+
// incluidos: .form-field de TranslatableImage, etc.).
|
|
62
|
+
.image-upload {
|
|
63
|
+
flex: 1;
|
|
64
|
+
min-height: 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Pila de filas apiladas dentro de una celda (la columna derecha de
|
|
70
|
+
// --media): mismo ritmo que la propia fila.
|
|
71
|
+
&__stack {
|
|
72
|
+
display: grid;
|
|
73
|
+
gap: $space-3;
|
|
74
|
+
align-content: start;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Cortes explícitos (en vez del auto-fit):
|
|
78
|
+
// — la fila de 3 apila ANTES que el resto: por debajo de ~564px de
|
|
79
|
+
// modal-body (3 campos de 180px mínimos cómodos + 2 gaps) ya no caben
|
|
80
|
+
// tres columnas. Pasa DIRECTA a una columna, sin estadio intermedio
|
|
81
|
+
// 2+1 que dejaría una celda huérfana descolgada.
|
|
82
|
+
@container modal-body (max-width: 563px) {
|
|
83
|
+
&--3 { grid-template-columns: minmax(0, 1fr); }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// — TODAS las variantes apilan a una columna en el modal angosto.
|
|
87
|
+
@container modal-body (max-width: #{$bp-sm - 1px}) {
|
|
88
|
+
&,
|
|
89
|
+
&--3,
|
|
90
|
+
&--wide-left,
|
|
91
|
+
&--media {
|
|
92
|
+
grid-template-columns: minmax(0, 1fr);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Separador de grupos de campos relacionados dentro de un formulario:
|
|
98
|
+
// <fieldset class="form-fieldset"><legend>…</legend>…</fieldset>
|
|
99
|
+
// Raya superior dashed (el mismo lenguaje que tenía la divisoria de la
|
|
100
|
+
// sección General del form de bloque); el navegador corta la raya donde
|
|
101
|
+
// asienta el legend (comportamiento nativo de fieldset+legend).
|
|
102
|
+
.form-fieldset {
|
|
103
|
+
margin: 0;
|
|
104
|
+
padding: $space-3 0 0; // aire entre la raya y el primer campo
|
|
105
|
+
border: 0;
|
|
106
|
+
border-top: 1px dashed $border;
|
|
107
|
+
min-width: 0; // los fieldset traen min-width min-content; aquí estorba
|
|
108
|
+
display: grid;
|
|
109
|
+
gap: $space-4; // el mismo ritmo que .edit-modal__body
|
|
110
|
+
|
|
111
|
+
// Legend discreto asentado sobre la raya: pegado al borde izquierdo y
|
|
112
|
+
// con un poco de aire antes de que sigan los guiones.
|
|
113
|
+
> legend {
|
|
114
|
+
padding-inline: 0 $space-2;
|
|
115
|
+
font-size: $fs-13;
|
|
116
|
+
color: $text-2;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// El PRIMER grupo del formulario no lleva raya superior (el formulario
|
|
120
|
+
// empieza directamente con él; su legend hace de título).
|
|
121
|
+
&:first-child {
|
|
122
|
+
border-top: 0;
|
|
123
|
+
padding-top: 0;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -59,10 +59,17 @@ const { hasContent, collapsed, mobileOpen, overlay, title, isOpen, toggle, close
|
|
|
59
59
|
const OVERLAY_BREAKPOINT = 900
|
|
60
60
|
|
|
61
61
|
function checkOverlay() {
|
|
62
|
-
overlay.value =
|
|
62
|
+
overlay.value =
|
|
63
|
+
props.overlayAlways || (typeof window !== 'undefined' && window.innerWidth < OVERLAY_BREAKPOINT)
|
|
63
64
|
if (!overlay.value) mobileOpen.value = false
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
// EAGER, en el propio setup (no esperar a mounted): que no exista NI UN
|
|
68
|
+
// frame en el que overlayAlways pudiera pintarse como columna atracada
|
|
69
|
+
// (p. ej. si el singleton del composable se recrea en caliente con HMR y
|
|
70
|
+
// `overlay` vuelve a su valor inicial `false`).
|
|
71
|
+
checkOverlay()
|
|
72
|
+
|
|
66
73
|
// Si el consumidor cambia el modo en caliente, el estado se recalcula.
|
|
67
74
|
watch(() => props.overlayAlways, checkOverlay)
|
|
68
75
|
|
|
@@ -87,8 +94,13 @@ const handleLabel = computed(() => (isOpen.value ? props.closeLabel : props.open
|
|
|
87
94
|
|
|
88
95
|
// Desplegada en ANCHO: el cascarón le hace hueco (body:has en su scss).
|
|
89
96
|
// hasContent va en la clase porque v-show (display: none) no saca al aside
|
|
90
|
-
// de los selectores :has.
|
|
91
|
-
|
|
97
|
+
// de los selectores :has. Con `overlayAlways`, `--docked` es imposible por
|
|
98
|
+
// construcción (aunque `overlay` aún no se hubiera recalculado): el
|
|
99
|
+
// cascarón no puede hacerle hueco JAMÁS y el drawer estrecho es la única
|
|
100
|
+
// versión que existe.
|
|
101
|
+
const docked = computed(
|
|
102
|
+
() => !props.overlayAlways && hasContent.value && !overlay.value && !collapsed.value,
|
|
103
|
+
)
|
|
92
104
|
</script>
|
|
93
105
|
|
|
94
106
|
<template>
|