@r2digisolutions/ui 0.34.6 → 0.34.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.
@@ -57,23 +57,23 @@
57
57
  {} as Record<keyof T, { left?: number; right?: number }>
58
58
  );
59
59
 
60
- // =========================
61
- // CONTEXT MENU (FIX)
62
- // =========================
60
+ // ✅ el contenedor que scrollea (tu overflow-auto)
61
+ let scrollEl = $state<HTMLDivElement | null>(null);
62
+
63
+ // CONTEXT
63
64
  let contextPopover = $state<HTMLDivElement | null>(null);
64
65
  let contextRow = $state<T | null>(null);
65
66
 
66
- // Punto preferido: cursor o botón (client coords)
67
+ // punto donde “quieres” abrirlo (coords de viewport)
67
68
  let contextPos = $state<{ x: number; y: number }>({ x: 0, y: 0 });
68
69
 
69
- // ✅ Posición final (clamp + flip) en left/top reales
70
+ // ✅ left/top final (sin transform)
70
71
  let contextRender = $state<{ left: number; top: number }>({ left: 0, top: 0 });
71
72
 
72
73
  const CONTEXT_MARGIN = 12;
73
74
  const CONTEXT_GAP = 8;
74
75
 
75
76
  let openRows = $state<Set<string>>(new Set());
76
-
77
77
  const contextOpen = $derived(contextRow !== null);
78
78
 
79
79
  // GRID / STICKY
@@ -86,7 +86,6 @@
86
86
  parts.push(`${w}px`);
87
87
  });
88
88
 
89
- // Columna de acciones
90
89
  parts.push('64px');
91
90
  gridTemplate = parts.join(' ');
92
91
 
@@ -98,9 +97,7 @@
98
97
  let accLeft = controller.multiSelect ? 40 : 0;
99
98
  controller.mainColumns.forEach((col) => {
100
99
  const w = controller.getColumnWidth(col.id as keyof T);
101
- if (col.sticky === 'left') {
102
- offsets[col.id as keyof T] = { left: accLeft };
103
- }
100
+ if (col.sticky === 'left') offsets[col.id as keyof T] = { left: accLeft };
104
101
  accLeft += w;
105
102
  });
106
103
  stickyOffsets = offsets;
@@ -118,45 +115,35 @@
118
115
  selectAllEl.indeterminate = controller.someVisibleSelected;
119
116
  });
120
117
 
121
- // CERRAR CONTEXT MENU (click fuera)
118
+ // Cerrar por click fuera
122
119
  $effect(() => {
123
120
  function handleDocumentClick(event: MouseEvent) {
124
121
  if (!contextOpen) return;
125
122
  const target = event.target as HTMLElement;
126
- if (!target.closest('[data-context-host="true"]')) {
127
- closeContext();
128
- }
123
+ if (!target.closest('[data-context-host="true"]')) closeContext();
129
124
  }
130
- if (contextOpen) document.addEventListener('click', handleDocumentClick);
131
- return () => {
132
- document.removeEventListener('click', handleDocumentClick);
133
- };
125
+ if (contextOpen) document.addEventListener('mousedown', handleDocumentClick, true);
126
+ return () => document.removeEventListener('mousedown', handleDocumentClick, true);
134
127
  });
135
128
 
136
- // ✅ Reposicionar al hacer scroll/resize mientras está abierto
129
+ // ✅ Reposicionar mientras está abierto: window resize + scroll del contenedor REAL
137
130
  $effect(() => {
138
131
  if (!contextOpen) return;
139
132
 
140
- const handler = () => {
141
- // si se mueve el viewport o scroll, recalculamos
142
- positionContext();
143
- };
133
+ const handler = () => void positionContext();
144
134
 
145
135
  window.addEventListener('resize', handler);
146
- window.addEventListener('scroll', handler, { passive: true, capture: true });
147
136
 
148
- // visualViewport es clave en móviles / zoom / barras
149
- window.visualViewport?.addEventListener('resize', handler);
150
- window.visualViewport?.addEventListener('scroll', handler);
137
+ // 👇 ESTE ES el scroll que te estaba faltando
138
+ scrollEl?.addEventListener('scroll', handler, { passive: true });
151
139
 
152
140
  return () => {
153
141
  window.removeEventListener('resize', handler);
154
- window.removeEventListener('scroll', handler, true as any);
155
- window.visualViewport?.removeEventListener('resize', handler);
156
- window.visualViewport?.removeEventListener('scroll', handler);
142
+ scrollEl?.removeEventListener('scroll', handler as any);
157
143
  };
158
144
  });
159
145
 
146
+ // RESIZE columns
160
147
  let resizingId: keyof T | null = null;
161
148
  let startX = 0;
162
149
  let startWidth = 0;
@@ -174,8 +161,7 @@
174
161
  function onResizeMove(event: MouseEvent) {
175
162
  if (!resizingId) return;
176
163
  const dx = event.clientX - startX;
177
- const width = startWidth + dx;
178
- controller.resizeColumn(resizingId, width);
164
+ controller.resizeColumn(resizingId, startWidth + dx);
179
165
  }
180
166
 
181
167
  function onResizeUp() {
@@ -216,13 +202,12 @@
216
202
 
217
203
  function handleToggleAll(e: Event) {
218
204
  const input = e.currentTarget as HTMLInputElement;
219
- const checked = input.checked;
220
- if (checked) controller.selectAllCurrentPage();
205
+ if (input.checked) controller.selectAllCurrentPage();
221
206
  else controller.unselectAllCurrentPage();
222
207
  }
223
208
 
224
209
  // =========================
225
- // ✅ Positioning helpers
210
+ // ✅ POSICIONAMIENTO REAL (sin transform)
226
211
  // =========================
227
212
  function clamp(n: number, min: number, max: number) {
228
213
  return Math.max(min, Math.min(max, n));
@@ -234,20 +219,8 @@
234
219
 
235
220
  function getViewport() {
236
221
  const vv = window.visualViewport;
237
- if (vv) {
238
- return {
239
- left: vv.offsetLeft,
240
- top: vv.offsetTop,
241
- width: vv.width,
242
- height: vv.height
243
- };
244
- }
245
- return {
246
- left: 0,
247
- top: 0,
248
- width: window.innerWidth,
249
- height: window.innerHeight
250
- };
222
+ if (vv) return { left: vv.offsetLeft, top: vv.offsetTop, width: vv.width, height: vv.height };
223
+ return { left: 0, top: 0, width: window.innerWidth, height: window.innerHeight };
251
224
  }
252
225
 
253
226
  function computeContextPosition(preferred: { x: number; y: number }, pop: DOMRect) {
@@ -259,30 +232,30 @@
259
232
  const maxLeft = vp.left + vp.width - CONTEXT_MARGIN - pop.width;
260
233
  const maxTop = vp.top + vp.height - CONTEXT_MARGIN - pop.height;
261
234
 
262
- // Si es demasiado alto, lo pegamos arriba (y el popover scrollea)
263
235
  const tooTall = pop.height > vp.height - CONTEXT_MARGIN * 2;
264
236
 
265
- // Preferencia UX original: izquierda + abajo
237
+ // default: left + down
266
238
  let left = preferred.x - pop.width;
267
239
  let top = preferred.y + CONTEXT_GAP;
268
240
 
269
- // Flip horizontal si no cabe a la izquierda
270
- if (left < minLeft) {
271
- left = preferred.x + CONTEXT_GAP;
272
- }
241
+ // flip horizontal
242
+ if (left < minLeft) left = preferred.x + CONTEXT_GAP;
273
243
 
274
- // Flip vertical si no cabe abajo
275
- if (!tooTall) {
244
+ if (tooTall) {
245
+ // si no cabe en altura, pegado arriba
246
+ top = minTop;
247
+ } else {
248
+ // flip vertical si se sale abajo
276
249
  const bottomIfDown = top + pop.height;
277
250
  const bottomLimit = vp.top + vp.height - CONTEXT_MARGIN;
278
251
  if (bottomIfDown > bottomLimit) {
279
- top = preferred.y - CONTEXT_GAP - pop.height;
252
+ top = preferred.y - CONTEXT_GAP - pop.height; // arriba
280
253
  }
281
- } else {
282
- top = minTop;
254
+
255
+ // si arriba se sale, pegado abajo
256
+ if (top < minTop) top = maxTop;
283
257
  }
284
258
 
285
- // Clamp final
286
259
  left = clamp(left, minLeft, Math.max(minLeft, maxLeft));
287
260
  top = clamp(top, minTop, Math.max(minTop, maxTop));
288
261
 
@@ -292,21 +265,18 @@
292
265
  async function positionContext() {
293
266
  if (!contextPopover || !contextRow) return;
294
267
 
295
- // Asegura layout real (Popover API a veces “late layout”)
296
268
  await raf();
269
+ const r1 = contextPopover.getBoundingClientRect();
270
+ if (!r1.width || !r1.height) return;
297
271
 
298
- const rect1 = contextPopover.getBoundingClientRect();
299
- if (!rect1.width || !rect1.height) return;
300
-
301
- // 1ª pasada
302
- contextRender = computeContextPosition(contextPos, rect1);
272
+ contextRender = computeContextPosition(contextPos, r1);
303
273
 
304
- // pasada (por si cambia alto/ancho por scrollbar)
274
+ // segunda pasada por scrollbar
305
275
  await raf();
306
- const rect2 = contextPopover.getBoundingClientRect();
307
- if (!rect2.width || !rect2.height) return;
276
+ const r2 = contextPopover.getBoundingClientRect();
277
+ if (!r2.width || !r2.height) return;
308
278
 
309
- contextRender = computeContextPosition(contextPos, rect2);
279
+ contextRender = computeContextPosition(contextPos, r2);
310
280
  }
311
281
 
312
282
  async function openContextAt(event: MouseEvent, row: T) {
@@ -316,9 +286,6 @@
316
286
  contextRow = row;
317
287
  contextPos = { x: event.clientX, y: event.clientY };
318
288
 
319
- if (contextPopover) contextPopover.showPopover();
320
-
321
- // Espera render + posiciona
322
289
  await tick();
323
290
  await positionContext();
324
291
  }
@@ -330,23 +297,21 @@
330
297
  const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
331
298
  contextRow = row;
332
299
 
333
- // Punto preferido: esquina inferior derecha
300
+ // punto: esquina inferior derecha del botón
334
301
  contextPos = { x: rect.right, y: rect.bottom };
335
302
 
336
- if (contextPopover) contextPopover.showPopover();
337
-
338
303
  await tick();
339
304
  await positionContext();
340
305
  }
341
306
 
342
307
  function closeContext() {
343
- if (contextPopover) contextPopover.hidePopover();
344
308
  contextRow = null;
345
309
  }
346
310
  </script>
347
311
 
312
+ <!-- ✅ wrapper principal relativo (para z-index) -->
348
313
  <div
349
- class="flex flex-col overflow-hidden rounded-2xl border border-neutral-200/80 bg-neutral-50/70 text-xs text-neutral-900 shadow-sm backdrop-blur-2xl dark:border-neutral-800/80 dark:bg-neutral-950/70 dark:text-neutral-50"
314
+ class="relative flex flex-col overflow-hidden rounded-2xl border border-neutral-200/80 bg-neutral-50/70 text-xs text-neutral-900 shadow-sm backdrop-blur-2xl dark:border-neutral-800/80 dark:bg-neutral-950/70 dark:text-neutral-50"
350
315
  >
351
316
  <DataTableToolbar
352
317
  {density}
@@ -366,7 +331,8 @@
366
331
  </div>
367
332
  {/if}
368
333
 
369
- <div class="relative max-h-[70vh] flex-1 overflow-auto">
334
+ <!-- ESTE es el scroll container, le hacemos bind -->
335
+ <div bind:this={scrollEl} class="relative max-h-[70vh] flex-1 overflow-auto">
370
336
  {#if controller.loading}
371
337
  <div class="pointer-events-none absolute inset-0 z-20 bg-neutral-900/30 backdrop-blur-md">
372
338
  <div class="flex h-full items-center justify-center">
@@ -476,8 +442,6 @@
476
442
  {@const id = rowIdFor(row, index)}
477
443
 
478
444
  <div class="group relative">
479
- <!-- Fila principal -->
480
- <!-- svelte-ignore a11y_click_events_have_key_events -->
481
445
  <div
482
446
  role="row"
483
447
  tabindex="0"
@@ -522,22 +486,9 @@
522
486
  : ''}
523
487
  >
524
488
  {#if cell}
525
- {@render cell({
526
- row,
527
- column: col,
528
- value,
529
- index
530
- })}
489
+ {@render cell({ row, column: col, value, index })}
531
490
  {:else}
532
- <span
533
- class={`line-clamp-2 text-black dark:text-neutral-50 ${
534
- col.align === 'right'
535
- ? 'ml-auto text-right'
536
- : col.align === 'center'
537
- ? 'mx-auto text-center'
538
- : ''
539
- }`}
540
- >
491
+ <span class="line-clamp-2 text-black dark:text-neutral-50">
541
492
  {formatValue(col, value, row)}
542
493
  </span>
543
494
  {/if}
@@ -551,33 +502,30 @@
551
502
  data-stop-row-toggle="true"
552
503
  >
553
504
  {#if actions.length}
554
- {#if rowActions}
555
- {@render rowActions(row, actions)}
556
- {:else}
557
- <div class="flex items-center gap-1.5">
558
- {#if rowCollapse}
559
- <button
560
- type="button"
561
- onclick={(e) => {
562
- e.stopPropagation();
563
- toggleRow(row, index);
564
- }}
565
- class={`inline-flex h-6 w-6 items-center justify-center rounded-full text-neutral-400 transition-colors hover:bg-neutral-200/80 hover:text-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-800/80 dark:hover:text-neutral-100 ${
566
- openRows.has(id) ? 'rotate-180' : ''
567
- }`}
568
- >
569
- <ChevronDown class="h-3.5 w-3.5" />
570
- </button>
571
- {/if}
505
+ <div class="flex items-center gap-1.5">
506
+ {#if rowCollapse}
572
507
  <button
573
508
  type="button"
574
- onclick={(e) => openContextFromButton(e, row)}
575
- class="inline-flex h-7 w-7 items-center justify-center rounded-full text-neutral-400 transition-colors hover:bg-neutral-200/80 hover:text-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-800/80 dark:hover:text-neutral-100"
509
+ onclick={(e) => {
510
+ e.stopPropagation();
511
+ toggleRow(row, index);
512
+ }}
513
+ class={`inline-flex h-6 w-6 items-center justify-center rounded-full text-neutral-400 transition-colors hover:bg-neutral-200/80 hover:text-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-800/80 dark:hover:text-neutral-100 ${
514
+ openRows.has(id) ? 'rotate-180' : ''
515
+ }`}
576
516
  >
577
- <EllipsisVertical class="h-4 w-4" />
517
+ <ChevronDown class="h-3.5 w-3.5" />
578
518
  </button>
579
- </div>
580
- {/if}
519
+ {/if}
520
+
521
+ <button
522
+ type="button"
523
+ onclick={(e) => openContextFromButton(e, row)}
524
+ class="inline-flex h-7 w-7 items-center justify-center rounded-full text-neutral-400 transition-colors hover:bg-neutral-200/80 hover:text-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-800/80 dark:hover:text-neutral-100"
525
+ >
526
+ <EllipsisVertical class="h-4 w-4" />
527
+ </button>
528
+ </div>
581
529
  {/if}
582
530
  </div>
583
531
  </div>
@@ -621,27 +569,13 @@
621
569
  {/each}
622
570
  </div>
623
571
  {:else}
624
- <!-- GRID VIEW -->
625
572
  <div class="grid gap-3 p-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
626
573
  {#each controller.currentRows as row, index (rowIdFor(row, index))}
627
- {@const id = rowIdFor(row, index)}
628
- {@const cols = controller.mainColumns as any[]}
629
- {@const firstCol = cols[0]}
630
- {@const firstValue = firstCol
631
- ? firstCol.accessor
632
- ? firstCol.accessor(row)
633
- : (row as any)[firstCol.id]
634
- : null}
635
- {@const restCols = cols.slice(1)}
636
574
  <div
637
- class={`group relative rounded-2xl border border-neutral-200/80 bg-white/80 p-3 text-[11px] text-neutral-800 shadow-sm ring-0 transition-all hover:border-purple-400/70 hover:shadow-md dark:border-neutral-800/80 dark:bg-neutral-900/80 dark:text-neutral-50 ${
638
- controller.selectedIds.has(id)
639
- ? 'bg-purple-50/70 ring-1 ring-purple-400/70 dark:bg-purple-950/20'
640
- : ''
641
- }`}
575
+ class="group relative rounded-2xl border border-neutral-200/80 bg-white/80 p-3 text-[11px] text-neutral-800 shadow-sm ring-0 transition-all hover:border-purple-400/70 hover:shadow-md dark:border-neutral-800/80 dark:bg-neutral-900/80 dark:text-neutral-50"
642
576
  oncontextmenu={(e) => openContextAt(e, row)}
643
577
  >
644
- <!-- ... tu grid view (igual que antes) ... -->
578
+ <!-- tu grid content -->
645
579
  </div>
646
580
  {/each}
647
581
  </div>
@@ -655,17 +589,14 @@
655
589
  </div>
656
590
 
657
591
  <DataTableFooter />
658
-
659
- <!-- ✅ CONTEXT POPOVER (FIXED) -->
592
+ </div>
593
+ <!-- ✅ HOST fuera del overflow-auto (ya no lo recorta) -->
594
+ {#if contextOpen}
660
595
  <div
661
596
  bind:this={contextPopover}
662
- popover="manual"
663
597
  data-context-host="true"
664
- class="z-[1300] max-h-[calc(100vh-24px)] max-w-xs min-w-[190px] overflow-auto rounded-2xl border border-neutral-200/80 bg-neutral-50/95 p-1.5 text-xs text-neutral-900 shadow-[0_18px_50px_rgba(15,23,42,0.45)] backdrop-blur-2xl dark:border-neutral-700/80 dark:bg-neutral-900/95 dark:text-neutral-50"
665
- style={`position: fixed; left: ${contextRender.left}px; top: ${contextRender.top}px;`}
666
- onbeforetoggle={(e) => {
667
- if ((e as any).newState === 'closed') contextRow = null;
668
- }}
598
+ class="pointer-events-auto fixed z-[999999] max-h-[calc(100vh-24px)] max-w-xs min-w-[190px] overflow-auto rounded-2xl border border-neutral-200/80 bg-neutral-50/95 p-1.5 text-xs text-neutral-900 shadow-[0_18px_50px_rgba(15,23,42,0.45)] backdrop-blur-2xl dark:border-neutral-700/80 dark:bg-neutral-900/95 dark:text-neutral-50"
599
+ style={`left:${contextRender.left}px; top:${contextRender.top}px;`}
669
600
  >
670
601
  {#if contextRow && actions.length}
671
602
  <ContextMenu {actions} row={contextRow} onClose={closeContext} />
@@ -673,4 +604,4 @@
673
604
  <div class="flex flex-col gap-2">No hay acciones disponibles</div>
674
605
  {/if}
675
606
  </div>
676
- </div>
607
+ {/if}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r2digisolutions/ui",
3
- "version": "0.34.6",
3
+ "version": "0.34.7",
4
4
  "private": false,
5
5
  "packageManager": "bun@1.3.9",
6
6
  "publishConfig": {