@r2digisolutions/ui 0.34.5 → 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,29 +57,26 @@
|
|
|
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
|
-
// punto
|
|
67
|
+
// punto donde “quieres” abrirlo (coords de viewport)
|
|
67
68
|
let contextPos = $state<{ x: number; y: number }>({ x: 0, y: 0 });
|
|
68
69
|
|
|
69
|
-
// ✅ left/top
|
|
70
|
+
// ✅ left/top final (sin transform)
|
|
70
71
|
let contextRender = $state<{ left: number; top: number }>({ left: 0, top: 0 });
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
const CONTEXT_GAP = 8; // separación visual del cursor/botón
|
|
73
|
+
const CONTEXT_MARGIN = 12;
|
|
74
|
+
const CONTEXT_GAP = 8;
|
|
75
75
|
|
|
76
76
|
let openRows = $state<Set<string>>(new Set());
|
|
77
|
-
|
|
78
77
|
const contextOpen = $derived(contextRow !== null);
|
|
79
78
|
|
|
80
|
-
// =========================
|
|
81
79
|
// GRID / STICKY
|
|
82
|
-
// =========================
|
|
83
80
|
$effect(() => {
|
|
84
81
|
const parts: string[] = [];
|
|
85
82
|
if (controller.multiSelect) parts.push('40px');
|
|
@@ -89,7 +86,6 @@
|
|
|
89
86
|
parts.push(`${w}px`);
|
|
90
87
|
});
|
|
91
88
|
|
|
92
|
-
// columna acciones
|
|
93
89
|
parts.push('64px');
|
|
94
90
|
gridTemplate = parts.join(' ');
|
|
95
91
|
|
|
@@ -104,57 +100,50 @@
|
|
|
104
100
|
if (col.sticky === 'left') offsets[col.id as keyof T] = { left: accLeft };
|
|
105
101
|
accLeft += w;
|
|
106
102
|
});
|
|
107
|
-
|
|
108
103
|
stickyOffsets = offsets;
|
|
109
104
|
});
|
|
110
105
|
|
|
111
106
|
// CHECK ALL
|
|
112
107
|
$effect(() => {
|
|
113
108
|
if (!controller.multiSelect || !selectAllEl) return;
|
|
114
|
-
|
|
115
109
|
if (!controller.currentRows.length) {
|
|
116
110
|
selectAllEl.checked = false;
|
|
117
111
|
selectAllEl.indeterminate = false;
|
|
118
112
|
return;
|
|
119
113
|
}
|
|
120
|
-
|
|
121
114
|
selectAllEl.checked = controller.allVisibleSelected;
|
|
122
115
|
selectAllEl.indeterminate = controller.someVisibleSelected;
|
|
123
116
|
});
|
|
124
117
|
|
|
125
|
-
//
|
|
118
|
+
// Cerrar por click fuera
|
|
126
119
|
$effect(() => {
|
|
127
120
|
function handleDocumentClick(event: MouseEvent) {
|
|
128
121
|
if (!contextOpen) return;
|
|
129
122
|
const target = event.target as HTMLElement;
|
|
130
123
|
if (!target.closest('[data-context-host="true"]')) closeContext();
|
|
131
124
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return () => document.removeEventListener('click', handleDocumentClick);
|
|
125
|
+
if (contextOpen) document.addEventListener('mousedown', handleDocumentClick, true);
|
|
126
|
+
return () => document.removeEventListener('mousedown', handleDocumentClick, true);
|
|
136
127
|
});
|
|
137
128
|
|
|
138
|
-
// ✅ Reposicionar
|
|
129
|
+
// ✅ Reposicionar mientras está abierto: window resize + scroll del contenedor REAL
|
|
139
130
|
$effect(() => {
|
|
140
131
|
if (!contextOpen) return;
|
|
141
132
|
|
|
142
|
-
const
|
|
143
|
-
positionContext(2);
|
|
144
|
-
};
|
|
133
|
+
const handler = () => void positionContext();
|
|
145
134
|
|
|
146
|
-
window.addEventListener('resize',
|
|
147
|
-
|
|
135
|
+
window.addEventListener('resize', handler);
|
|
136
|
+
|
|
137
|
+
// 👇 ESTE ES el scroll que te estaba faltando
|
|
138
|
+
scrollEl?.addEventListener('scroll', handler, { passive: true });
|
|
148
139
|
|
|
149
140
|
return () => {
|
|
150
|
-
window.removeEventListener('resize',
|
|
151
|
-
|
|
141
|
+
window.removeEventListener('resize', handler);
|
|
142
|
+
scrollEl?.removeEventListener('scroll', handler as any);
|
|
152
143
|
};
|
|
153
144
|
});
|
|
154
145
|
|
|
155
|
-
//
|
|
156
|
-
// RESIZE COLUMNS
|
|
157
|
-
// =========================
|
|
146
|
+
// RESIZE columns
|
|
158
147
|
let resizingId: keyof T | null = null;
|
|
159
148
|
let startX = 0;
|
|
160
149
|
let startWidth = 0;
|
|
@@ -181,9 +170,6 @@
|
|
|
181
170
|
window.removeEventListener('mouseup', onResizeUp);
|
|
182
171
|
}
|
|
183
172
|
|
|
184
|
-
// =========================
|
|
185
|
-
// HELPERS
|
|
186
|
-
// =========================
|
|
187
173
|
function rowIdFor(row: T, index: number) {
|
|
188
174
|
return controller.getRowId(row, index);
|
|
189
175
|
}
|
|
@@ -221,7 +207,7 @@
|
|
|
221
207
|
}
|
|
222
208
|
|
|
223
209
|
// =========================
|
|
224
|
-
// ✅
|
|
210
|
+
// ✅ POSICIONAMIENTO REAL (sin transform)
|
|
225
211
|
// =========================
|
|
226
212
|
function clamp(n: number, min: number, max: number) {
|
|
227
213
|
return Math.max(min, Math.min(max, n));
|
|
@@ -231,70 +217,66 @@
|
|
|
231
217
|
return new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));
|
|
232
218
|
}
|
|
233
219
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
220
|
+
function getViewport() {
|
|
221
|
+
const vv = window.visualViewport;
|
|
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 };
|
|
224
|
+
}
|
|
225
|
+
|
|
239
226
|
function computeContextPosition(preferred: { x: number; y: number }, pop: DOMRect) {
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
// - vertical: abajo del punto
|
|
250
|
-
let left = preferred.x - pop.width; // izquierda
|
|
251
|
-
let top = preferred.y + CONTEXT_GAP; // abajo
|
|
252
|
-
|
|
253
|
-
// Horizontal flip: si no cabe a la izquierda, poner a la derecha
|
|
254
|
-
if (left < CONTEXT_MARGIN) {
|
|
255
|
-
left = preferred.x + CONTEXT_GAP;
|
|
256
|
-
}
|
|
227
|
+
const vp = getViewport();
|
|
228
|
+
|
|
229
|
+
const minLeft = vp.left + CONTEXT_MARGIN;
|
|
230
|
+
const minTop = vp.top + CONTEXT_MARGIN;
|
|
231
|
+
|
|
232
|
+
const maxLeft = vp.left + vp.width - CONTEXT_MARGIN - pop.width;
|
|
233
|
+
const maxTop = vp.top + vp.height - CONTEXT_MARGIN - pop.height;
|
|
234
|
+
|
|
235
|
+
const tooTall = pop.height > vp.height - CONTEXT_MARGIN * 2;
|
|
257
236
|
|
|
258
|
-
//
|
|
259
|
-
|
|
237
|
+
// default: left + down
|
|
238
|
+
let left = preferred.x - pop.width;
|
|
239
|
+
let top = preferred.y + CONTEXT_GAP;
|
|
240
|
+
|
|
241
|
+
// flip horizontal
|
|
242
|
+
if (left < minLeft) left = preferred.x + CONTEXT_GAP;
|
|
243
|
+
|
|
244
|
+
if (tooTall) {
|
|
245
|
+
// si no cabe en altura, pegado arriba
|
|
246
|
+
top = minTop;
|
|
247
|
+
} else {
|
|
248
|
+
// flip vertical si se sale abajo
|
|
260
249
|
const bottomIfDown = top + pop.height;
|
|
261
|
-
const
|
|
262
|
-
if (
|
|
250
|
+
const bottomLimit = vp.top + vp.height - CONTEXT_MARGIN;
|
|
251
|
+
if (bottomIfDown > bottomLimit) {
|
|
263
252
|
top = preferred.y - CONTEXT_GAP - pop.height; // arriba
|
|
264
253
|
}
|
|
265
|
-
}
|
|
266
254
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
top = CONTEXT_MARGIN;
|
|
255
|
+
// si arriba se sale, pegado abajo
|
|
256
|
+
if (top < minTop) top = maxTop;
|
|
270
257
|
}
|
|
271
258
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
left = clamp(left, CONTEXT_MARGIN, vw - CONTEXT_MARGIN - pop.width);
|
|
275
|
-
top = clamp(top, CONTEXT_MARGIN, vh - CONTEXT_MARGIN - Math.min(pop.height, maxHeightAllowed));
|
|
259
|
+
left = clamp(left, minLeft, Math.max(minLeft, maxLeft));
|
|
260
|
+
top = clamp(top, minTop, Math.max(minTop, maxTop));
|
|
276
261
|
|
|
277
262
|
return { left, top };
|
|
278
263
|
}
|
|
279
264
|
|
|
280
|
-
|
|
281
|
-
* Posiciona midiendo tamaño real.
|
|
282
|
-
* RAF suele ser más fiable que tick() con Popover API.
|
|
283
|
-
*/
|
|
284
|
-
async function positionContext(retries = 3) {
|
|
265
|
+
async function positionContext() {
|
|
285
266
|
if (!contextPopover || !contextRow) return;
|
|
286
267
|
|
|
287
268
|
await raf();
|
|
269
|
+
const r1 = contextPopover.getBoundingClientRect();
|
|
270
|
+
if (!r1.width || !r1.height) return;
|
|
288
271
|
|
|
289
|
-
|
|
290
|
-
const rect = contextPopover.getBoundingClientRect();
|
|
272
|
+
contextRender = computeContextPosition(contextPos, r1);
|
|
291
273
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (!
|
|
274
|
+
// segunda pasada por scrollbar
|
|
275
|
+
await raf();
|
|
276
|
+
const r2 = contextPopover.getBoundingClientRect();
|
|
277
|
+
if (!r2.width || !r2.height) return;
|
|
296
278
|
|
|
297
|
-
contextRender = computeContextPosition(contextPos,
|
|
279
|
+
contextRender = computeContextPosition(contextPos, r2);
|
|
298
280
|
}
|
|
299
281
|
|
|
300
282
|
async function openContextAt(event: MouseEvent, row: T) {
|
|
@@ -304,10 +286,8 @@
|
|
|
304
286
|
contextRow = row;
|
|
305
287
|
contextPos = { x: event.clientX, y: event.clientY };
|
|
306
288
|
|
|
307
|
-
if (contextPopover) contextPopover.showPopover();
|
|
308
|
-
|
|
309
289
|
await tick();
|
|
310
|
-
await positionContext(
|
|
290
|
+
await positionContext();
|
|
311
291
|
}
|
|
312
292
|
|
|
313
293
|
async function openContextFromButton(event: MouseEvent, row: T) {
|
|
@@ -315,24 +295,23 @@
|
|
|
315
295
|
event.stopPropagation();
|
|
316
296
|
|
|
317
297
|
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
318
|
-
|
|
319
298
|
contextRow = row;
|
|
320
|
-
contextPos = { x: rect.right, y: rect.bottom };
|
|
321
299
|
|
|
322
|
-
|
|
300
|
+
// punto: esquina inferior derecha del botón
|
|
301
|
+
contextPos = { x: rect.right, y: rect.bottom };
|
|
323
302
|
|
|
324
303
|
await tick();
|
|
325
|
-
await positionContext(
|
|
304
|
+
await positionContext();
|
|
326
305
|
}
|
|
327
306
|
|
|
328
307
|
function closeContext() {
|
|
329
|
-
if (contextPopover) contextPopover.hidePopover();
|
|
330
308
|
contextRow = null;
|
|
331
309
|
}
|
|
332
310
|
</script>
|
|
333
311
|
|
|
312
|
+
<!-- ✅ wrapper principal relativo (para z-index) -->
|
|
334
313
|
<div
|
|
335
|
-
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"
|
|
336
315
|
>
|
|
337
316
|
<DataTableToolbar
|
|
338
317
|
{density}
|
|
@@ -352,7 +331,8 @@
|
|
|
352
331
|
</div>
|
|
353
332
|
{/if}
|
|
354
333
|
|
|
355
|
-
|
|
334
|
+
<!-- ✅ ESTE es el scroll container, le hacemos bind -->
|
|
335
|
+
<div bind:this={scrollEl} class="relative max-h-[70vh] flex-1 overflow-auto">
|
|
356
336
|
{#if controller.loading}
|
|
357
337
|
<div class="pointer-events-none absolute inset-0 z-20 bg-neutral-900/30 backdrop-blur-md">
|
|
358
338
|
<div class="flex h-full items-center justify-center">
|
|
@@ -462,7 +442,6 @@
|
|
|
462
442
|
{@const id = rowIdFor(row, index)}
|
|
463
443
|
|
|
464
444
|
<div class="group relative">
|
|
465
|
-
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
466
445
|
<div
|
|
467
446
|
role="row"
|
|
468
447
|
tabindex="0"
|
|
@@ -495,7 +474,7 @@
|
|
|
495
474
|
{@const value = col.accessor ? col.accessor(row) : (row as any)[col.id]}
|
|
496
475
|
{@const sticky = stickyOffsets[col.id as keyof T]}
|
|
497
476
|
<div
|
|
498
|
-
class={`flex items-center border-r border-neutral-200/60 px-3
|
|
477
|
+
class={`flex items-center border-r border-neutral-200/60 px-3 text-black dark:text-neutral-50${
|
|
499
478
|
density === 'compact' ? 'py-1.5' : 'py-2.5'
|
|
500
479
|
} dark:border-neutral-800/70 ${
|
|
501
480
|
col.sticky === 'left'
|
|
@@ -509,15 +488,7 @@
|
|
|
509
488
|
{#if cell}
|
|
510
489
|
{@render cell({ row, column: col, value, index })}
|
|
511
490
|
{:else}
|
|
512
|
-
<span
|
|
513
|
-
class={`line-clamp-2 text-black dark:text-neutral-50 ${
|
|
514
|
-
col.align === 'right'
|
|
515
|
-
? 'ml-auto text-right'
|
|
516
|
-
: col.align === 'center'
|
|
517
|
-
? 'mx-auto text-center'
|
|
518
|
-
: ''
|
|
519
|
-
}`}
|
|
520
|
-
>
|
|
491
|
+
<span class="line-clamp-2 text-black dark:text-neutral-50">
|
|
521
492
|
{formatValue(col, value, row)}
|
|
522
493
|
</span>
|
|
523
494
|
{/if}
|
|
@@ -531,33 +502,30 @@
|
|
|
531
502
|
data-stop-row-toggle="true"
|
|
532
503
|
>
|
|
533
504
|
{#if actions.length}
|
|
534
|
-
|
|
535
|
-
{
|
|
536
|
-
{:else}
|
|
537
|
-
<div class="flex items-center gap-1.5">
|
|
538
|
-
{#if rowCollapse}
|
|
539
|
-
<button
|
|
540
|
-
type="button"
|
|
541
|
-
onclick={(e) => {
|
|
542
|
-
e.stopPropagation();
|
|
543
|
-
toggleRow(row, index);
|
|
544
|
-
}}
|
|
545
|
-
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 ${
|
|
546
|
-
openRows.has(id) ? 'rotate-180' : ''
|
|
547
|
-
}`}
|
|
548
|
-
>
|
|
549
|
-
<ChevronDown class="h-3.5 w-3.5" />
|
|
550
|
-
</button>
|
|
551
|
-
{/if}
|
|
505
|
+
<div class="flex items-center gap-1.5">
|
|
506
|
+
{#if rowCollapse}
|
|
552
507
|
<button
|
|
553
508
|
type="button"
|
|
554
|
-
onclick={(e) =>
|
|
555
|
-
|
|
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
|
+
}`}
|
|
556
516
|
>
|
|
557
|
-
<
|
|
517
|
+
<ChevronDown class="h-3.5 w-3.5" />
|
|
558
518
|
</button>
|
|
559
|
-
|
|
560
|
-
|
|
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>
|
|
561
529
|
{/if}
|
|
562
530
|
</div>
|
|
563
531
|
</div>
|
|
@@ -601,10 +569,14 @@
|
|
|
601
569
|
{/each}
|
|
602
570
|
</div>
|
|
603
571
|
{:else}
|
|
604
|
-
<!-- GRID VIEW (igual que tu versión original; si necesitas que lo pegue completo, pégame el final del archivo) -->
|
|
605
572
|
<div class="grid gap-3 p-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
|
606
573
|
{#each controller.currentRows as row, index (rowIdFor(row, index))}
|
|
607
|
-
|
|
574
|
+
<div
|
|
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"
|
|
576
|
+
oncontextmenu={(e) => openContextAt(e, row)}
|
|
577
|
+
>
|
|
578
|
+
<!-- tu grid content -->
|
|
579
|
+
</div>
|
|
608
580
|
{/each}
|
|
609
581
|
</div>
|
|
610
582
|
{/if}
|
|
@@ -617,17 +589,14 @@
|
|
|
617
589
|
</div>
|
|
618
590
|
|
|
619
591
|
<DataTableFooter />
|
|
620
|
-
|
|
621
|
-
|
|
592
|
+
</div>
|
|
593
|
+
<!-- ✅ HOST fuera del overflow-auto (ya no lo recorta) -->
|
|
594
|
+
{#if contextOpen}
|
|
622
595
|
<div
|
|
623
596
|
bind:this={contextPopover}
|
|
624
|
-
popover="manual"
|
|
625
597
|
data-context-host="true"
|
|
626
|
-
class="z-[
|
|
627
|
-
style={`
|
|
628
|
-
onbeforetoggle={(e) => {
|
|
629
|
-
if ((e as any).newState === 'closed') contextRow = null;
|
|
630
|
-
}}
|
|
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;`}
|
|
631
600
|
>
|
|
632
601
|
{#if contextRow && actions.length}
|
|
633
602
|
<ContextMenu {actions} row={contextRow} onClose={closeContext} />
|
|
@@ -635,4 +604,4 @@
|
|
|
635
604
|
<div class="flex flex-col gap-2">No hay acciones disponibles</div>
|
|
636
605
|
{/if}
|
|
637
606
|
</div>
|
|
638
|
-
|
|
607
|
+
{/if}
|