@edc-motor/ui 0.4.28 → 0.4.29

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.29",
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",
@@ -3,6 +3,7 @@ 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'
6
7
  import UnderlineExtension from '@tiptap/extension-underline'
7
8
  import LinkExtension from '@tiptap/extension-link'
8
9
  import TableExtension from '@tiptap/extension-table'
@@ -331,7 +332,12 @@ function insertTable() {
331
332
 
332
333
  // --- Selector de iconos ---
333
334
  const pickerOpen = ref(false)
334
- const pickerRef = ref<HTMLElement>()
335
+ const pickerRef = ref<HTMLElement | null>(null)
336
+ 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 })
335
341
 
336
342
  function insertIcon(icon: RichIcon) {
337
343
  editor.value?.chain().focus().setImage({ src: icon.url, alt: icon.name, title: icon.name }).run()
@@ -341,7 +347,9 @@ function insertIcon(icon: RichIcon) {
341
347
  // --- Enlaces: mini-popover con la URL (mismo patrón que el selector de
342
348
  // iconos: botón + panel flotante, cierra con Escape o click fuera) ---
343
349
  const linkPopoverOpen = ref(false)
344
- const linkPickerRef = ref<HTMLElement>()
350
+ const linkPickerRef = ref<HTMLElement | null>(null)
351
+ const linkPanelRef = ref<HTMLElement | null>(null)
352
+ useDropdownPanel(linkPickerRef, linkPanelRef, linkPopoverOpen, { matchWidth: false })
345
353
  const linkInputRef = ref<HTMLInputElement>()
346
354
  const linkUrlInput = ref('')
347
355
 
@@ -451,7 +459,12 @@ function onSourceInput(value: string) {
451
459
  >
452
460
  <LinkIcon :size="16" />
453
461
  </button>
454
- <div v-if="linkPopoverOpen" class="rich-text__link-popover">
462
+ <div
463
+ v-if="linkPopoverOpen"
464
+ ref="linkPanelRef"
465
+ class="rich-text__link-popover"
466
+ popover="manual"
467
+ >
455
468
  <input
456
469
  ref="linkInputRef"
457
470
  v-model="linkUrlInput"
@@ -538,7 +551,7 @@ function onSourceInput(value: string) {
538
551
  >
539
552
  <Smile :size="16" />
540
553
  </button>
541
- <div v-if="pickerOpen" class="rich-text__icons">
554
+ <div v-if="pickerOpen" ref="iconsPanelRef" class="rich-text__icons" popover="manual">
542
555
  <button
543
556
  v-for="icon in icons"
544
557
  :key="icon.url"
@@ -20,7 +20,14 @@ 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
+ } = {},
23
29
  ) {
30
+ const { matchWidth = true } = options
24
31
  const GAP = 4 // $space-1: aire entre trigger y panel, como el CSS absolute
25
32
 
26
33
  /** Ancla el panel bajo el trigger (o encima, si abajo no cabe y arriba sí). */
@@ -29,8 +36,11 @@ export function useDropdownPanel(
29
36
  const p = panel.value
30
37
  if (!a || !p) return
31
38
  const r = a.getBoundingClientRect()
32
- p.style.width = `${r.width}px`
33
- p.style.left = `${r.left}px`
39
+ if (matchWidth) p.style.width = `${r.width}px`
40
+ const left = matchWidth
41
+ ? r.left
42
+ : Math.max(GAP, Math.min(r.left, window.innerWidth - p.offsetWidth - GAP))
43
+ p.style.left = `${left}px`
34
44
  const height = p.offsetHeight
35
45
  const below = window.innerHeight - r.bottom - GAP
36
46
  const openUp = height > below && r.top > window.innerHeight - r.bottom