@edc-motor/ui 0.4.29 → 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.
|
|
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
|
-
|
|
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
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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 {
|
|
@@ -3,7 +3,6 @@ import { computed, ref, watch, onBeforeUnmount, onMounted, nextTick } from 'vue'
|
|
|
3
3
|
import { useEditor, EditorContent } from '@tiptap/vue-3'
|
|
4
4
|
import StarterKit from '@tiptap/starter-kit'
|
|
5
5
|
import Image from '@tiptap/extension-image'
|
|
6
|
-
import { useDropdownPanel } from '../composables/useDropdownPanel'
|
|
7
6
|
import UnderlineExtension from '@tiptap/extension-underline'
|
|
8
7
|
import LinkExtension from '@tiptap/extension-link'
|
|
9
8
|
import TableExtension from '@tiptap/extension-table'
|
|
@@ -331,25 +330,23 @@ function insertTable() {
|
|
|
331
330
|
}
|
|
332
331
|
|
|
333
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).
|
|
334
336
|
const pickerOpen = ref(false)
|
|
335
337
|
const pickerRef = ref<HTMLElement | null>(null)
|
|
336
338
|
const iconsPanelRef = ref<HTMLElement | null>(null)
|
|
337
|
-
// Top layer (popover): dentro de un modal, el overflow del cuerpo recortaba
|
|
338
|
-
// el panel y no se podían elegir todos los iconos. Ancho propio, sin igualar
|
|
339
|
-
// al trigger.
|
|
340
|
-
useDropdownPanel(pickerRef, iconsPanelRef, pickerOpen, { matchWidth: false })
|
|
341
339
|
|
|
342
340
|
function insertIcon(icon: RichIcon) {
|
|
343
341
|
editor.value?.chain().focus().setImage({ src: icon.url, alt: icon.name, title: icon.name }).run()
|
|
344
342
|
pickerOpen.value = false
|
|
345
343
|
}
|
|
346
344
|
|
|
347
|
-
// --- Enlaces:
|
|
348
|
-
//
|
|
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) ---
|
|
349
347
|
const linkPopoverOpen = ref(false)
|
|
350
348
|
const linkPickerRef = ref<HTMLElement | null>(null)
|
|
351
349
|
const linkPanelRef = ref<HTMLElement | null>(null)
|
|
352
|
-
useDropdownPanel(linkPickerRef, linkPanelRef, linkPopoverOpen, { matchWidth: false })
|
|
353
350
|
const linkInputRef = ref<HTMLInputElement>()
|
|
354
351
|
const linkUrlInput = ref('')
|
|
355
352
|
|
|
@@ -377,8 +374,12 @@ function removeLink() {
|
|
|
377
374
|
|
|
378
375
|
function onClickOutside(e: MouseEvent) {
|
|
379
376
|
const target = e.target as Node
|
|
380
|
-
|
|
381
|
-
|
|
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
|
|
382
383
|
}
|
|
383
384
|
onMounted(() => document.addEventListener('click', onClickOutside))
|
|
384
385
|
onBeforeUnmount(() => {
|
|
@@ -447,38 +448,18 @@ function onSourceInput(value: string) {
|
|
|
447
448
|
|
|
448
449
|
<span class="rich-text__sep" />
|
|
449
450
|
|
|
450
|
-
<!-- Enlace: botón
|
|
451
|
-
<
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
<div
|
|
463
|
-
v-if="linkPopoverOpen"
|
|
464
|
-
ref="linkPanelRef"
|
|
465
|
-
class="rich-text__link-popover"
|
|
466
|
-
popover="manual"
|
|
467
|
-
>
|
|
468
|
-
<input
|
|
469
|
-
ref="linkInputRef"
|
|
470
|
-
v-model="linkUrlInput"
|
|
471
|
-
type="text"
|
|
472
|
-
class="rich-text__link-input"
|
|
473
|
-
:placeholder="t.linkUrl"
|
|
474
|
-
@keydown.enter.prevent="applyLink"
|
|
475
|
-
@keydown.escape.prevent="linkPopoverOpen = false"
|
|
476
|
-
/>
|
|
477
|
-
<button type="button" class="rich-text__tool" :title="t.linkApply" @click="applyLink">
|
|
478
|
-
<LinkIcon :size="14" />
|
|
479
|
-
</button>
|
|
480
|
-
</div>
|
|
481
|
-
</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>
|
|
482
463
|
<button
|
|
483
464
|
v-if="editor?.isActive('link')"
|
|
484
465
|
type="button"
|
|
@@ -537,33 +518,20 @@ function onSourceInput(value: string) {
|
|
|
537
518
|
<Redo2 :size="16" />
|
|
538
519
|
</button>
|
|
539
520
|
|
|
540
|
-
<!-- Selector de iconos del juego -->
|
|
521
|
+
<!-- Selector de iconos del juego: abre la franja bajo la toolbar -->
|
|
541
522
|
<template v-if="icons.length">
|
|
542
523
|
<span class="rich-text__sep" />
|
|
543
|
-
<
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
<div v-if="pickerOpen" ref="iconsPanelRef" class="rich-text__icons" popover="manual">
|
|
555
|
-
<button
|
|
556
|
-
v-for="icon in icons"
|
|
557
|
-
:key="icon.url"
|
|
558
|
-
type="button"
|
|
559
|
-
class="rich-text__icon"
|
|
560
|
-
:title="icon.name"
|
|
561
|
-
@click="insertIcon(icon)"
|
|
562
|
-
>
|
|
563
|
-
<img :src="icon.url" :alt="icon.name" />
|
|
564
|
-
</button>
|
|
565
|
-
</div>
|
|
566
|
-
</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>
|
|
567
535
|
</template>
|
|
568
536
|
</template>
|
|
569
537
|
|
|
@@ -580,6 +548,38 @@ function onSourceInput(value: string) {
|
|
|
580
548
|
</button>
|
|
581
549
|
</div>
|
|
582
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
|
+
|
|
583
583
|
<textarea
|
|
584
584
|
v-if="source"
|
|
585
585
|
class="rich-text__source"
|
|
@@ -20,14 +20,7 @@ export function useDropdownPanel(
|
|
|
20
20
|
anchor: Ref<HTMLElement | null>,
|
|
21
21
|
panel: Ref<HTMLElement | null>,
|
|
22
22
|
open: Ref<boolean>,
|
|
23
|
-
options: {
|
|
24
|
-
/** Igualar el ancho del panel al del trigger (los select). Los popovers
|
|
25
|
-
* con ancho propio (selector de iconos, popover del enlace) lo apagan:
|
|
26
|
-
* conservan su ancho y solo se CLAMPEA su left para no salirse. */
|
|
27
|
-
matchWidth?: boolean
|
|
28
|
-
} = {},
|
|
29
23
|
) {
|
|
30
|
-
const { matchWidth = true } = options
|
|
31
24
|
const GAP = 4 // $space-1: aire entre trigger y panel, como el CSS absolute
|
|
32
25
|
|
|
33
26
|
/** Ancla el panel bajo el trigger (o encima, si abajo no cabe y arriba sí). */
|
|
@@ -36,11 +29,8 @@ export function useDropdownPanel(
|
|
|
36
29
|
const p = panel.value
|
|
37
30
|
if (!a || !p) return
|
|
38
31
|
const r = a.getBoundingClientRect()
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
? r.left
|
|
42
|
-
: Math.max(GAP, Math.min(r.left, window.innerWidth - p.offsetWidth - GAP))
|
|
43
|
-
p.style.left = `${left}px`
|
|
32
|
+
p.style.width = `${r.width}px`
|
|
33
|
+
p.style.left = `${r.left}px`
|
|
44
34
|
const height = p.offsetHeight
|
|
45
35
|
const below = window.innerHeight - r.bottom - GAP
|
|
46
36
|
const openUp = height > below && r.top > window.innerHeight - r.bottom
|