@r2digisolutions/ui 0.24.2 → 0.24.3
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.
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
manager.setReservedWidth(reserved);
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
//
|
|
74
|
+
// Reflow ancho
|
|
75
75
|
$effect(() => {
|
|
76
76
|
if (!container) return;
|
|
77
77
|
const ro = new ResizeObserver((entries) => {
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
return () => ro.disconnect();
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
//
|
|
85
|
+
// Medir DOM
|
|
86
86
|
const SAMPLE_ROWS = 10;
|
|
87
87
|
async function measureColumns() {
|
|
88
88
|
await tick();
|
|
@@ -124,7 +124,15 @@
|
|
|
124
124
|
) {
|
|
125
125
|
e.preventDefault();
|
|
126
126
|
const columnIndex = columnId ? manager.state.visibleColumns.indexOf(columnId) : null;
|
|
127
|
-
|
|
127
|
+
// Always set fresh coordinates and ensure menu is closed before reopening
|
|
128
|
+
if (rightMenu.open) {
|
|
129
|
+
rightMenu = { open: false, x: 0, y: 0 };
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
rightMenu = { open: true, x: e.clientX, y: e.clientY };
|
|
132
|
+
}, 0);
|
|
133
|
+
} else {
|
|
134
|
+
rightMenu = { open: true, x: e.clientX, y: e.clientY };
|
|
135
|
+
}
|
|
128
136
|
rightClickContext = {
|
|
129
137
|
row,
|
|
130
138
|
rowIndex,
|
|
@@ -140,13 +148,14 @@
|
|
|
140
148
|
return manager.state.items.filter((r) => ids.has(rowId(r)));
|
|
141
149
|
}
|
|
142
150
|
|
|
143
|
-
//
|
|
151
|
+
// Tracks
|
|
144
152
|
function colTrack(cId: string, measuring: boolean) {
|
|
145
153
|
if (measuring) return 'max-content';
|
|
146
154
|
const c = manager.getColumn(cId);
|
|
147
155
|
const w = manager.measured[cId] ?? c.width ?? c.minWidth ?? 160;
|
|
148
156
|
return `${Math.max(40, Math.ceil(Number(w)))}px`;
|
|
149
157
|
}
|
|
158
|
+
|
|
150
159
|
function headerTemplateCols(visible: string[], endExtras: boolean) {
|
|
151
160
|
const tracks = [
|
|
152
161
|
`${CHECK_W}px`,
|
|
@@ -1,33 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { TContextMenuEntry } from '../core/types.js';
|
|
3
3
|
|
|
4
|
-
// --- Action portal (interna al componente) ---
|
|
5
|
-
function portal(node: HTMLElement, target: HTMLElement | null = null) {
|
|
6
|
-
const tgt = target ?? document.body;
|
|
7
|
-
const placeholder = document.createComment('portal-placeholder');
|
|
8
|
-
node.parentNode?.insertBefore(placeholder, node);
|
|
9
|
-
tgt.appendChild(node);
|
|
10
|
-
return {
|
|
11
|
-
destroy() {
|
|
12
|
-
node.remove();
|
|
13
|
-
placeholder.parentNode?.insertBefore(node, placeholder);
|
|
14
|
-
placeholder.remove();
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// --- Util para clamping a viewport (interna) ---
|
|
20
|
-
function clampToViewport(x: number, y: number, menuW: number, menuH: number, padding = 8) {
|
|
21
|
-
const vw = document.documentElement.clientWidth;
|
|
22
|
-
const vh = document.documentElement.clientHeight;
|
|
23
|
-
let nx = x;
|
|
24
|
-
let ny = y;
|
|
25
|
-
if (nx + menuW + padding > vw) nx = Math.max(padding, vw - menuW - padding);
|
|
26
|
-
if (ny + menuH + padding > vh) ny = Math.max(padding, vh - menuH - padding);
|
|
27
|
-
return { x: nx, y: ny };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// --- Props ---
|
|
31
4
|
type Props = {
|
|
32
5
|
items?: TContextMenuEntry[];
|
|
33
6
|
x?: number;
|
|
@@ -36,7 +9,6 @@
|
|
|
36
9
|
title?: string;
|
|
37
10
|
searchable?: boolean;
|
|
38
11
|
context?: any;
|
|
39
|
-
portalTarget?: HTMLElement | null;
|
|
40
12
|
};
|
|
41
13
|
let {
|
|
42
14
|
items = [],
|
|
@@ -45,21 +17,22 @@
|
|
|
45
17
|
open = $bindable(false),
|
|
46
18
|
title = '',
|
|
47
19
|
searchable = true,
|
|
48
|
-
context = null
|
|
49
|
-
portalTarget = null
|
|
20
|
+
context = null
|
|
50
21
|
}: Props = $props();
|
|
51
22
|
|
|
52
|
-
// --- Estado interno ---
|
|
53
23
|
let stack = $state<{ label: string; items: TContextMenuEntry[] }[]>([]);
|
|
54
24
|
let q = $state('');
|
|
55
25
|
const current = $derived(stack.length ? stack[stack.length - 1] : { label: title, items });
|
|
56
26
|
|
|
27
|
+
let menuEl: HTMLDivElement | null = $state(null);
|
|
28
|
+
|
|
57
29
|
function close() {
|
|
30
|
+
if (menuEl) menuEl.hidePopover();
|
|
58
31
|
open = false;
|
|
59
32
|
stack = [];
|
|
60
33
|
q = '';
|
|
61
|
-
|
|
62
|
-
|
|
34
|
+
x = 0;
|
|
35
|
+
y = 0;
|
|
63
36
|
}
|
|
64
37
|
|
|
65
38
|
function hasChildren(it: TContextMenuEntry) {
|
|
@@ -91,8 +64,6 @@
|
|
|
91
64
|
const list = current.items ?? [];
|
|
92
65
|
const query = q.trim().toLowerCase();
|
|
93
66
|
let arr = query ? list.filter((it) => matches(it, query)) : list.slice();
|
|
94
|
-
|
|
95
|
-
// limpiar divisores consecutivos y extremos
|
|
96
67
|
const out: TContextMenuEntry[] = [];
|
|
97
68
|
let prevDiv = false;
|
|
98
69
|
for (const it of arr) {
|
|
@@ -125,88 +96,54 @@
|
|
|
125
96
|
}
|
|
126
97
|
}
|
|
127
98
|
|
|
128
|
-
//
|
|
129
|
-
let menuEl: HTMLDivElement | null = $state(null);
|
|
130
|
-
|
|
131
|
-
// Teclado (Escape / Backspace)
|
|
99
|
+
// Manage popover open/close
|
|
132
100
|
$effect(() => {
|
|
133
|
-
if (!
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (!open) return;
|
|
142
|
-
|
|
143
|
-
const onPointerDownCapture = (ev: PointerEvent) => {
|
|
144
|
-
if (!menuEl) return;
|
|
145
|
-
if (!menuEl.contains(ev.target as Node)) {
|
|
146
|
-
close();
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const onNativeCtx = (ev: MouseEvent) => {
|
|
151
|
-
ev.preventDefault();
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const onScrollOrResize = () => close();
|
|
155
|
-
|
|
156
|
-
document.addEventListener('pointerdown', onPointerDownCapture, true);
|
|
157
|
-
document.addEventListener('contextmenu', onNativeCtx);
|
|
158
|
-
window.addEventListener('scroll', onScrollOrResize, true);
|
|
159
|
-
window.addEventListener('resize', onScrollOrResize, true);
|
|
160
|
-
|
|
161
|
-
return () => {
|
|
162
|
-
document.removeEventListener('pointerdown', onPointerDownCapture, true);
|
|
163
|
-
document.removeEventListener('contextmenu', onNativeCtx);
|
|
164
|
-
window.removeEventListener('scroll', onScrollOrResize, true);
|
|
165
|
-
window.removeEventListener('resize', onScrollOrResize, true);
|
|
166
|
-
};
|
|
101
|
+
if (!menuEl) return;
|
|
102
|
+
if (open) {
|
|
103
|
+
menuEl.style.setProperty('--popover-x', `${x}px`);
|
|
104
|
+
menuEl.style.setProperty('--popover-y', `${y}px`);
|
|
105
|
+
menuEl.showPopover();
|
|
106
|
+
} else {
|
|
107
|
+
menuEl.hidePopover();
|
|
108
|
+
}
|
|
167
109
|
});
|
|
168
110
|
|
|
169
|
-
// Clamp
|
|
111
|
+
// Clamp position to viewport
|
|
170
112
|
$effect(() => {
|
|
171
113
|
if (!open || !menuEl) return;
|
|
172
114
|
requestAnimationFrame(() => {
|
|
173
115
|
if (!menuEl) return;
|
|
174
116
|
const rect = menuEl.getBoundingClientRect();
|
|
175
|
-
const
|
|
117
|
+
const vw = document.documentElement.clientWidth;
|
|
118
|
+
const vh = document.documentElement.clientHeight;
|
|
119
|
+
let nx = x;
|
|
120
|
+
let ny = y;
|
|
121
|
+
const padding = 8;
|
|
122
|
+
if (nx + rect.width + padding > vw) nx = Math.max(padding, vw - rect.width - padding);
|
|
123
|
+
if (ny + rect.height + padding > vh) ny = Math.max(padding, vh - rect.height - padding);
|
|
176
124
|
if (nx !== x || ny !== y) {
|
|
177
125
|
x = nx;
|
|
178
126
|
y = ny;
|
|
127
|
+
menuEl.style.setProperty('--popover-x', `${nx}px`);
|
|
128
|
+
menuEl.style.setProperty('--popover-y', `${ny}px`);
|
|
179
129
|
}
|
|
180
130
|
});
|
|
181
131
|
});
|
|
182
132
|
</script>
|
|
183
133
|
|
|
184
134
|
{#if open}
|
|
185
|
-
<!-- BACKDROP (portaleado al body) -->
|
|
186
|
-
<div
|
|
187
|
-
use:portal={portalTarget}
|
|
188
|
-
role="dialog"
|
|
189
|
-
class="fixed inset-0 z-[2147483646]"
|
|
190
|
-
onclick={() => close()}
|
|
191
|
-
oncontextmenu={(e) => e.preventDefault()}
|
|
192
|
-
aria-modal="true"
|
|
193
|
-
tabindex="0"
|
|
194
|
-
style="pointer-events:auto"
|
|
195
|
-
/>
|
|
196
|
-
|
|
197
|
-
<!-- MENÚ (portaleado al body) -->
|
|
198
135
|
<div
|
|
199
136
|
bind:this={menuEl}
|
|
200
|
-
|
|
201
|
-
class="
|
|
202
|
-
style=
|
|
137
|
+
popover="manual"
|
|
138
|
+
class="w-72 rounded-2xl bg-white p-2 shadow-xl ring-1 ring-black/5 dark:bg-gray-900"
|
|
139
|
+
style="position: fixed; left: var(--popover-x); top: var(--popover-y);"
|
|
203
140
|
oncontextmenu={(e) => e.preventDefault()}
|
|
141
|
+
onkeydown={onKey}
|
|
204
142
|
>
|
|
205
143
|
<div class="flex items-center gap-1 px-1 py-1">
|
|
206
144
|
{#if stack.length > 0}
|
|
207
145
|
<button
|
|
208
146
|
class="rounded-lg p-1 hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
209
|
-
role="dialog"
|
|
210
147
|
aria-label="Atrás"
|
|
211
148
|
onclick={back}
|
|
212
149
|
>
|
|
@@ -218,8 +155,10 @@
|
|
|
218
155
|
stroke="currentColor"
|
|
219
156
|
stroke-width="2"
|
|
220
157
|
stroke-linecap="round"
|
|
221
|
-
stroke-linejoin="round"
|
|
158
|
+
stroke-linejoin="round"
|
|
222
159
|
>
|
|
160
|
+
<polyline points="15 18 9 12 15 6" />
|
|
161
|
+
</svg>
|
|
223
162
|
</button>
|
|
224
163
|
{/if}
|
|
225
164
|
<div class="min-w-0 flex-1 truncate px-1 text-xs font-medium opacity-70">
|
|
@@ -245,16 +184,15 @@
|
|
|
245
184
|
{:else}
|
|
246
185
|
<button
|
|
247
186
|
class="flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm hover:bg-gray-100 disabled:opacity-50 dark:hover:bg-gray-800"
|
|
248
|
-
role="dialog"
|
|
249
187
|
disabled={it.disabled}
|
|
250
188
|
onclick={() => clickItem(it)}
|
|
251
189
|
>
|
|
252
190
|
<span class="truncate">{it.label}</span>
|
|
253
191
|
<span class="flex items-center gap-2">
|
|
254
192
|
{#if it.shortcut}
|
|
255
|
-
<kbd class="rounded bg-gray-100 px-1 text-[10px] dark:bg-gray-800"
|
|
256
|
-
|
|
257
|
-
>
|
|
193
|
+
<kbd class="rounded bg-gray-100 px-1 text-[10px] dark:bg-gray-800">
|
|
194
|
+
{it.shortcut}
|
|
195
|
+
</kbd>
|
|
258
196
|
{/if}
|
|
259
197
|
{#if hasChildren(it)}
|
|
260
198
|
<svg
|
|
@@ -266,8 +204,10 @@
|
|
|
266
204
|
stroke="currentColor"
|
|
267
205
|
stroke-width="2"
|
|
268
206
|
stroke-linecap="round"
|
|
269
|
-
stroke-linejoin="round"
|
|
207
|
+
stroke-linejoin="round"
|
|
270
208
|
>
|
|
209
|
+
<polyline points="9 18 15 12 9 6" />
|
|
210
|
+
</svg>
|
|
271
211
|
{/if}
|
|
272
212
|
</span>
|
|
273
213
|
</button>
|
|
@@ -276,3 +216,12 @@
|
|
|
276
216
|
</div>
|
|
277
217
|
</div>
|
|
278
218
|
{/if}
|
|
219
|
+
|
|
220
|
+
<style>
|
|
221
|
+
[popover] {
|
|
222
|
+
margin: 0;
|
|
223
|
+
border: none;
|
|
224
|
+
padding: 0;
|
|
225
|
+
z-index: 2147483647;
|
|
226
|
+
}
|
|
227
|
+
</style>
|