@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
|
-
|
|
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
|
-
//
|
|
67
|
+
// punto donde “quieres” abrirlo (coords de viewport)
|
|
67
68
|
let contextPos = $state<{ x: number; y: number }>({ x: 0, y: 0 });
|
|
68
69
|
|
|
69
|
-
// ✅
|
|
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
|
-
//
|
|
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('
|
|
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
|
|
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
|
-
//
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
220
|
-
if (checked) controller.selectAllCurrentPage();
|
|
205
|
+
if (input.checked) controller.selectAllCurrentPage();
|
|
221
206
|
else controller.unselectAllCurrentPage();
|
|
222
207
|
}
|
|
223
208
|
|
|
224
209
|
// =========================
|
|
225
|
-
// ✅
|
|
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
|
-
|
|
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
|
-
//
|
|
237
|
+
// default: left + down
|
|
266
238
|
let left = preferred.x - pop.width;
|
|
267
239
|
let top = preferred.y + CONTEXT_GAP;
|
|
268
240
|
|
|
269
|
-
//
|
|
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
|
-
|
|
275
|
-
|
|
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
|
-
|
|
282
|
-
|
|
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
|
-
|
|
299
|
-
if (!rect1.width || !rect1.height) return;
|
|
300
|
-
|
|
301
|
-
// 1ª pasada
|
|
302
|
-
contextRender = computeContextPosition(contextPos, rect1);
|
|
272
|
+
contextRender = computeContextPosition(contextPos, r1);
|
|
303
273
|
|
|
304
|
-
//
|
|
274
|
+
// segunda pasada por scrollbar
|
|
305
275
|
await raf();
|
|
306
|
-
const
|
|
307
|
-
if (!
|
|
276
|
+
const r2 = contextPopover.getBoundingClientRect();
|
|
277
|
+
if (!r2.width || !r2.height) return;
|
|
308
278
|
|
|
309
|
-
contextRender = computeContextPosition(contextPos,
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
555
|
-
{
|
|
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) =>
|
|
575
|
-
|
|
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
|
-
<
|
|
517
|
+
<ChevronDown class="h-3.5 w-3.5" />
|
|
578
518
|
</button>
|
|
579
|
-
|
|
580
|
-
|
|
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=
|
|
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
|
-
<!--
|
|
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
|
-
|
|
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-[
|
|
665
|
-
style={`
|
|
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
|
-
|
|
607
|
+
{/if}
|