@edc-motor/ui 0.4.28 → 0.4.30

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.28",
3
+ "version": "0.4.30",
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",
@@ -17,7 +17,12 @@
17
17
  &__zone {
18
18
  position: relative;
19
19
  display: flex;
20
- align-self: flex-start;
20
+ // Centrado EN EL CAMPO: horizontal siempre, y vertical cuando el
21
+ // contenedor le da alto extra (p. ej. el grupo a dos columnas del form
22
+ // de bloque) — los márgenes auto solo absorben espacio si lo hay.
23
+ align-self: center;
24
+ margin-block: auto;
25
+ flex-shrink: 0;
21
26
  align-items: center;
22
27
  justify-content: center;
23
28
  width: 160px;
@@ -81,8 +86,10 @@
81
86
  &__icon { width: 32px; height: 32px; color: $text-3; }
82
87
  &__text { font-size: $fs-12; color: $text-2; }
83
88
 
84
- // Nombre del fichero (pendiente o guardado) bajo la miniatura.
89
+ // Nombre del fichero (pendiente o guardado) bajo la miniatura, centrado
90
+ // con ella.
85
91
  &__name {
92
+ align-self: center;
86
93
  max-width: 160px;
87
94
  overflow: hidden;
88
95
  font-size: $fs-12;
@@ -69,34 +69,21 @@
69
69
  outline: none;
70
70
  }
71
71
 
72
- // Selector de iconos del juego (y el mismo contenedor sirve de anclaje
73
- // para el mini-popover del enlace).
74
- &__picker { position: relative; display: inline-flex; }
75
-
76
- // Mini-popover del enlace: input de URL + botón de aplicar, misma
77
- // posición/estética que el panel de iconos.
78
- &__link-popover {
79
- position: absolute;
80
- top: calc(100% + 4px);
81
- left: 0;
82
- z-index: 100;
72
+ // Franjas del editor (URL del enlace, iconos): EN FLUJO entre la toolbar y
73
+ // el contenido nada flota, así que dentro de un modal quedan siempre a la
74
+ // vista enteras. Mismo fondo que la toolbar, de la que son una extensión.
75
+ &__link-bar {
83
76
  display: flex;
84
77
  align-items: center;
85
78
  gap: 2px;
86
- // `width: max-content` (no simplemente `auto`): dentro de un ancla tan
87
- // angosta como el propio botón (`.rich-text__picker`), el algoritmo de
88
- // shrink-to-fit de un absoluto con solo `left` fijado lo recorta al
89
- // ancho del ancla — el input de 220px quedaba aplastado a ~35px.
90
- width: max-content;
91
79
  padding: $space-1;
92
- background: $surface;
93
- border: 1px solid $border;
94
- border-radius: $radius-md;
95
- box-shadow: $shadow-md;
80
+ border-bottom: 1px solid $border;
81
+ background: $surface-2;
96
82
  }
97
83
 
98
84
  &__link-input {
99
- width: 220px;
85
+ flex: 1;
86
+ min-width: 0;
100
87
  height: 30px;
101
88
  padding: 0 $space-2;
102
89
  border: 1px solid $input-border;
@@ -113,21 +100,16 @@
113
100
  }
114
101
 
115
102
  &__icons {
116
- position: absolute;
117
- top: calc(100% + 4px);
118
- left: 0;
119
- z-index: 100;
120
103
  display: grid;
121
- grid-template-columns: repeat(5, 1fr);
104
+ // A todo el ancho del editor: caben muchos más iconos por fila que en el
105
+ // popover de 5 columnas de antes; si aun así no entran, scroll propio.
106
+ grid-template-columns: repeat(auto-fill, minmax(36px, 1fr));
122
107
  gap: 2px;
123
- max-width: 220px;
124
108
  max-height: 200px;
125
109
  overflow-y: auto;
126
110
  padding: $space-1;
127
- background: $surface;
128
- border: 1px solid $border;
129
- border-radius: $radius-md;
130
- box-shadow: $shadow-md;
111
+ border-bottom: 1px solid $border;
112
+ background: $surface-2;
131
113
  }
132
114
 
133
115
  &__icon {
@@ -330,18 +330,23 @@ function insertTable() {
330
330
  }
331
331
 
332
332
  // --- Selector de iconos ---
333
+ // Los paneles del editor (iconos y enlace) NO flotan: son franjas EN FLUJO
334
+ // entre la toolbar y el contenido — dentro de un modal quedan siempre a la
335
+ // vista (nada que recortar ni que anclar por coordenadas).
333
336
  const pickerOpen = ref(false)
334
- const pickerRef = ref<HTMLElement>()
337
+ const pickerRef = ref<HTMLElement | null>(null)
338
+ const iconsPanelRef = ref<HTMLElement | null>(null)
335
339
 
336
340
  function insertIcon(icon: RichIcon) {
337
341
  editor.value?.chain().focus().setImage({ src: icon.url, alt: icon.name, title: icon.name }).run()
338
342
  pickerOpen.value = false
339
343
  }
340
344
 
341
- // --- Enlaces: mini-popover con la URL (mismo patrón que el selector de
342
- // iconos: botón + panel flotante, cierra con Escape o click fuera) ---
345
+ // --- Enlaces: franja con la URL (mismo patrón que el selector de iconos:
346
+ // botón + franja bajo la toolbar, cierra con Escape o click fuera) ---
343
347
  const linkPopoverOpen = ref(false)
344
- const linkPickerRef = ref<HTMLElement>()
348
+ const linkPickerRef = ref<HTMLElement | null>(null)
349
+ const linkPanelRef = ref<HTMLElement | null>(null)
345
350
  const linkInputRef = ref<HTMLInputElement>()
346
351
  const linkUrlInput = ref('')
347
352
 
@@ -369,8 +374,12 @@ function removeLink() {
369
374
 
370
375
  function onClickOutside(e: MouseEvent) {
371
376
  const target = e.target as Node
372
- if (pickerRef.value && !pickerRef.value.contains(target)) pickerOpen.value = false
373
- if (linkPickerRef.value && !linkPickerRef.value.contains(target)) linkPopoverOpen.value = false
377
+ // Botón y franja son elementos hermanos: un click en cualquiera de los dos
378
+ // mantiene el panel abierto (insertar icono / escribir la URL).
379
+ if (!pickerRef.value?.contains(target) && !iconsPanelRef.value?.contains(target))
380
+ pickerOpen.value = false
381
+ if (!linkPickerRef.value?.contains(target) && !linkPanelRef.value?.contains(target))
382
+ linkPopoverOpen.value = false
374
383
  }
375
384
  onMounted(() => document.addEventListener('click', onClickOutside))
376
385
  onBeforeUnmount(() => {
@@ -439,33 +448,18 @@ function onSourceInput(value: string) {
439
448
 
440
449
  <span class="rich-text__sep" />
441
450
 
442
- <!-- Enlace: botón + mini-popover con la URL -->
443
- <div ref="linkPickerRef" class="rich-text__picker">
444
- <button
445
- type="button"
446
- class="rich-text__tool"
447
- :class="{ 'rich-text__tool--active': editor?.isActive('link') || linkPopoverOpen }"
448
- :title="t.link"
449
- :disabled="disabled"
450
- @click="toggleLinkPopover"
451
- >
452
- <LinkIcon :size="16" />
453
- </button>
454
- <div v-if="linkPopoverOpen" class="rich-text__link-popover">
455
- <input
456
- ref="linkInputRef"
457
- v-model="linkUrlInput"
458
- type="text"
459
- class="rich-text__link-input"
460
- :placeholder="t.linkUrl"
461
- @keydown.enter.prevent="applyLink"
462
- @keydown.escape.prevent="linkPopoverOpen = false"
463
- />
464
- <button type="button" class="rich-text__tool" :title="t.linkApply" @click="applyLink">
465
- <LinkIcon :size="14" />
466
- </button>
467
- </div>
468
- </div>
451
+ <!-- Enlace: botón que abre la franja de la URL bajo la toolbar -->
452
+ <button
453
+ ref="linkPickerRef"
454
+ type="button"
455
+ class="rich-text__tool"
456
+ :class="{ 'rich-text__tool--active': editor?.isActive('link') || linkPopoverOpen }"
457
+ :title="t.link"
458
+ :disabled="disabled"
459
+ @click="toggleLinkPopover"
460
+ >
461
+ <LinkIcon :size="16" />
462
+ </button>
469
463
  <button
470
464
  v-if="editor?.isActive('link')"
471
465
  type="button"
@@ -524,33 +518,20 @@ function onSourceInput(value: string) {
524
518
  <Redo2 :size="16" />
525
519
  </button>
526
520
 
527
- <!-- Selector de iconos del juego -->
521
+ <!-- Selector de iconos del juego: abre la franja bajo la toolbar -->
528
522
  <template v-if="icons.length">
529
523
  <span class="rich-text__sep" />
530
- <div ref="pickerRef" class="rich-text__picker">
531
- <button
532
- type="button"
533
- class="rich-text__tool"
534
- :class="{ 'rich-text__tool--active': pickerOpen }"
535
- :title="t.insertIcon"
536
- :disabled="disabled"
537
- @click="pickerOpen = !pickerOpen"
538
- >
539
- <Smile :size="16" />
540
- </button>
541
- <div v-if="pickerOpen" class="rich-text__icons">
542
- <button
543
- v-for="icon in icons"
544
- :key="icon.url"
545
- type="button"
546
- class="rich-text__icon"
547
- :title="icon.name"
548
- @click="insertIcon(icon)"
549
- >
550
- <img :src="icon.url" :alt="icon.name" />
551
- </button>
552
- </div>
553
- </div>
524
+ <button
525
+ ref="pickerRef"
526
+ type="button"
527
+ class="rich-text__tool"
528
+ :class="{ 'rich-text__tool--active': pickerOpen }"
529
+ :title="t.insertIcon"
530
+ :disabled="disabled"
531
+ @click="pickerOpen = !pickerOpen"
532
+ >
533
+ <Smile :size="16" />
534
+ </button>
554
535
  </template>
555
536
  </template>
556
537
 
@@ -567,6 +548,38 @@ function onSourceInput(value: string) {
567
548
  </button>
568
549
  </div>
569
550
 
551
+ <!-- Franja del enlace: EN FLUJO bajo la toolbar (dentro de un modal queda
552
+ siempre a la vista; los popovers flotantes se recortaban o quedaban
553
+ descolgados del editor) -->
554
+ <div v-if="linkPopoverOpen && !source" ref="linkPanelRef" class="rich-text__link-bar">
555
+ <input
556
+ ref="linkInputRef"
557
+ v-model="linkUrlInput"
558
+ type="text"
559
+ class="rich-text__link-input"
560
+ :placeholder="t.linkUrl"
561
+ @keydown.enter.prevent="applyLink"
562
+ @keydown.escape.prevent="linkPopoverOpen = false"
563
+ />
564
+ <button type="button" class="rich-text__tool" :title="t.linkApply" @click="applyLink">
565
+ <LinkIcon :size="14" />
566
+ </button>
567
+ </div>
568
+
569
+ <!-- Franja de iconos: misma idea, a todo el ancho del editor -->
570
+ <div v-if="pickerOpen && !source" ref="iconsPanelRef" class="rich-text__icons">
571
+ <button
572
+ v-for="icon in icons"
573
+ :key="icon.url"
574
+ type="button"
575
+ class="rich-text__icon"
576
+ :title="icon.name"
577
+ @click="insertIcon(icon)"
578
+ >
579
+ <img :src="icon.url" :alt="icon.name" />
580
+ </button>
581
+ </div>
582
+
570
583
  <textarea
571
584
  v-if="source"
572
585
  class="rich-text__source"