@r2digisolutions/ui 0.34.0 → 0.34.1
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.
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts" generics="T">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
+
import { tick } from 'svelte';
|
|
3
4
|
import { EllipsisVertical, ChevronDown } from 'lucide-svelte';
|
|
4
5
|
import type { ColumnDef, RowAction } from './core/types.js';
|
|
5
6
|
import type { DataTableController } from './core/DataTableController.svelte';
|
|
@@ -56,10 +57,22 @@
|
|
|
56
57
|
{} as Record<keyof T, { left?: number; right?: number }>
|
|
57
58
|
);
|
|
58
59
|
|
|
60
|
+
// CONTEXT MENU
|
|
59
61
|
let contextPopover = $state<HTMLDivElement | null>(null);
|
|
60
62
|
let contextRow = $state<T | null>(null);
|
|
63
|
+
|
|
64
|
+
// punto preferido donde se abrió (cursor o botón)
|
|
61
65
|
let contextPos = $state<{ x: number; y: number }>({ x: 0, y: 0 });
|
|
62
66
|
|
|
67
|
+
// ✅ posición final render (clamp + flip)
|
|
68
|
+
let contextRender = $state<{ x: number; y: number; transform: string }>({
|
|
69
|
+
x: 0,
|
|
70
|
+
y: 0,
|
|
71
|
+
transform: 'translate(-100%, 8px)'
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const CONTEXT_MARGIN = 10;
|
|
75
|
+
|
|
63
76
|
let openRows = $state<Set<string>>(new Set());
|
|
64
77
|
|
|
65
78
|
const contextOpen = $derived(contextRow !== null);
|
|
@@ -123,6 +136,22 @@
|
|
|
123
136
|
};
|
|
124
137
|
});
|
|
125
138
|
|
|
139
|
+
// ✅ Reposicionar en resize / scroll (capture) mientras esté abierto
|
|
140
|
+
$effect(() => {
|
|
141
|
+
if (!contextOpen) return;
|
|
142
|
+
|
|
143
|
+
const onWin = () => positionContext();
|
|
144
|
+
|
|
145
|
+
window.addEventListener('resize', onWin);
|
|
146
|
+
// capture: true para enterarte del scroll en contenedores overflow-auto
|
|
147
|
+
window.addEventListener('scroll', onWin, { passive: true, capture: true });
|
|
148
|
+
|
|
149
|
+
return () => {
|
|
150
|
+
window.removeEventListener('resize', onWin);
|
|
151
|
+
window.removeEventListener('scroll', onWin, true as any);
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
|
|
126
155
|
let resizingId: keyof T | null = null;
|
|
127
156
|
let startX = 0;
|
|
128
157
|
let startWidth = 0;
|
|
@@ -165,21 +194,95 @@
|
|
|
165
194
|
return String(value);
|
|
166
195
|
}
|
|
167
196
|
|
|
168
|
-
|
|
197
|
+
// =========================
|
|
198
|
+
// ✅ Context positioning logic
|
|
199
|
+
// =========================
|
|
200
|
+
function clamp(n: number, min: number, max: number) {
|
|
201
|
+
return Math.max(min, Math.min(max, n));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function computeContextPosition(preferred: { x: number; y: number }, pop: DOMRect) {
|
|
205
|
+
const vw = window.innerWidth;
|
|
206
|
+
const vh = window.innerHeight;
|
|
207
|
+
|
|
208
|
+
const fitsLeft = preferred.x - pop.width - CONTEXT_MARGIN >= 0;
|
|
209
|
+
const fitsRight = preferred.x + pop.width + CONTEXT_MARGIN <= vw;
|
|
210
|
+
const fitsDown = preferred.y + pop.height + CONTEXT_MARGIN <= vh;
|
|
211
|
+
const fitsUp = preferred.y - pop.height - CONTEXT_MARGIN >= 0;
|
|
212
|
+
|
|
213
|
+
// Horizontal: preferimos “a la izquierda” (como translate(-100%, ...))
|
|
214
|
+
const placeToRight = !fitsLeft && fitsRight;
|
|
215
|
+
|
|
216
|
+
// Vertical: preferimos “abajo”
|
|
217
|
+
const placeUp = !fitsDown && fitsUp;
|
|
218
|
+
|
|
219
|
+
let transformX = placeToRight ? '0%' : '-100%';
|
|
220
|
+
let transformY = placeUp ? 'calc(-100% - 8px)' : '8px';
|
|
221
|
+
|
|
222
|
+
let x = preferred.x;
|
|
223
|
+
let y = preferred.y;
|
|
224
|
+
|
|
225
|
+
// Clamp horizontal según transform
|
|
226
|
+
if (transformX === '-100%') {
|
|
227
|
+
// pop ocupa [x - w, x]
|
|
228
|
+
x = clamp(x, CONTEXT_MARGIN + pop.width, vw - CONTEXT_MARGIN);
|
|
229
|
+
} else {
|
|
230
|
+
// pop ocupa [x, x + w]
|
|
231
|
+
x = clamp(x, CONTEXT_MARGIN, vw - CONTEXT_MARGIN - pop.width);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Clamp vertical según lado
|
|
235
|
+
if (placeUp) {
|
|
236
|
+
// pop ocupa [y - 8 - h, y - 8]
|
|
237
|
+
y = clamp(y, CONTEXT_MARGIN + pop.height + 8, vh - CONTEXT_MARGIN);
|
|
238
|
+
} else {
|
|
239
|
+
// pop ocupa [y + 8, y + 8 + h]
|
|
240
|
+
y = clamp(y, CONTEXT_MARGIN - 8, vh - CONTEXT_MARGIN - pop.height - 8);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
x,
|
|
245
|
+
y,
|
|
246
|
+
transform: `translate(${transformX}, ${transformY})`
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function positionContext() {
|
|
251
|
+
if (!contextPopover || !contextRow) return;
|
|
252
|
+
|
|
253
|
+
// Si está cerrado, evita medir
|
|
254
|
+
// (aunque contextRow sea truthy, por seguridad)
|
|
255
|
+
const rect = contextPopover.getBoundingClientRect();
|
|
256
|
+
if (!rect.width || !rect.height) return;
|
|
257
|
+
|
|
258
|
+
const next = computeContextPosition(contextPos, rect);
|
|
259
|
+
contextRender = next;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function openContextAt(event: MouseEvent, row: T) {
|
|
169
263
|
event.preventDefault();
|
|
170
264
|
event.stopPropagation();
|
|
171
265
|
contextRow = row;
|
|
172
266
|
contextPos = { x: event.clientX, y: event.clientY };
|
|
173
267
|
if (contextPopover) contextPopover.showPopover();
|
|
268
|
+
|
|
269
|
+
await tick();
|
|
270
|
+
await positionContext();
|
|
174
271
|
}
|
|
175
272
|
|
|
176
|
-
function openContextFromButton(event: MouseEvent, row: T) {
|
|
273
|
+
async function openContextFromButton(event: MouseEvent, row: T) {
|
|
177
274
|
event.preventDefault();
|
|
178
275
|
event.stopPropagation();
|
|
179
276
|
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
180
277
|
contextRow = row;
|
|
278
|
+
|
|
279
|
+
// Punto preferido: esquina inferior derecha del botón
|
|
181
280
|
contextPos = { x: rect.right, y: rect.bottom };
|
|
281
|
+
|
|
182
282
|
if (contextPopover) contextPopover.showPopover();
|
|
283
|
+
|
|
284
|
+
await tick();
|
|
285
|
+
await positionContext();
|
|
183
286
|
}
|
|
184
287
|
|
|
185
288
|
function closeContext() {
|
|
@@ -613,8 +716,8 @@
|
|
|
613
716
|
bind:this={contextPopover}
|
|
614
717
|
popover="manual"
|
|
615
718
|
data-context-host="true"
|
|
616
|
-
class="z-[1300] max-w-xs min-w-[190px] 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"
|
|
617
|
-
style={`position: fixed; left: ${
|
|
719
|
+
class="z-[1300] max-w-xs min-w-[190px] 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 transition-transform duration-75 will-change-transform dark:border-neutral-700/80 dark:bg-neutral-900/95 dark:text-neutral-50"
|
|
720
|
+
style={`position: fixed; left: ${contextRender.x}px; top: ${contextRender.y}px; transform: ${contextRender.transform};`}
|
|
618
721
|
onbeforetoggle={(e) => {
|
|
619
722
|
if ((e as any).newState === 'closed') contextRow = null;
|
|
620
723
|
}}
|