@r2digisolutions/ui 0.24.1 → 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,7 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { TContextMenuEntry } from '../core/types.js';
|
|
3
|
-
import { portal } from '../utils/portal.js';
|
|
4
|
-
import { clampToViewport } from '../utils/position.js';
|
|
5
3
|
|
|
6
4
|
type Props = {
|
|
7
5
|
items?: TContextMenuEntry[];
|
|
@@ -11,8 +9,6 @@
|
|
|
11
9
|
title?: string;
|
|
12
10
|
searchable?: boolean;
|
|
13
11
|
context?: any;
|
|
14
|
-
// por si algún día quieres desactivar el portal:
|
|
15
|
-
portalTarget?: HTMLElement | null;
|
|
16
12
|
};
|
|
17
13
|
let {
|
|
18
14
|
items = [],
|
|
@@ -21,19 +17,22 @@
|
|
|
21
17
|
open = $bindable(false),
|
|
22
18
|
title = '',
|
|
23
19
|
searchable = true,
|
|
24
|
-
context = null
|
|
25
|
-
portalTarget = null
|
|
20
|
+
context = null
|
|
26
21
|
}: Props = $props();
|
|
27
22
|
|
|
28
23
|
let stack = $state<{ label: string; items: TContextMenuEntry[] }[]>([]);
|
|
29
24
|
let q = $state('');
|
|
30
|
-
|
|
31
25
|
const current = $derived(stack.length ? stack[stack.length - 1] : { label: title, items });
|
|
32
26
|
|
|
27
|
+
let menuEl: HTMLDivElement | null = $state(null);
|
|
28
|
+
|
|
33
29
|
function close() {
|
|
30
|
+
if (menuEl) menuEl.hidePopover();
|
|
34
31
|
open = false;
|
|
35
32
|
stack = [];
|
|
36
33
|
q = '';
|
|
34
|
+
x = 0;
|
|
35
|
+
y = 0;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
function hasChildren(it: TContextMenuEntry) {
|
|
@@ -65,7 +64,6 @@
|
|
|
65
64
|
const list = current.items ?? [];
|
|
66
65
|
const query = q.trim().toLowerCase();
|
|
67
66
|
let arr = query ? list.filter((it) => matches(it, query)) : list.slice();
|
|
68
|
-
|
|
69
67
|
const out: TContextMenuEntry[] = [];
|
|
70
68
|
let prevDiv = false;
|
|
71
69
|
for (const it of arr) {
|
|
@@ -98,58 +96,54 @@
|
|
|
98
96
|
}
|
|
99
97
|
}
|
|
100
98
|
|
|
99
|
+
// Manage popover open/close
|
|
101
100
|
$effect(() => {
|
|
102
|
-
if (!
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
+
}
|
|
106
109
|
});
|
|
107
110
|
|
|
108
|
-
//
|
|
109
|
-
let menuEl: HTMLDivElement | null = $state(null);
|
|
110
|
-
|
|
111
|
+
// Clamp position to viewport
|
|
111
112
|
$effect(() => {
|
|
112
113
|
if (!open || !menuEl) return;
|
|
113
|
-
// siguiente frame para medir dimensiones reales
|
|
114
114
|
requestAnimationFrame(() => {
|
|
115
115
|
if (!menuEl) return;
|
|
116
116
|
const rect = menuEl.getBoundingClientRect();
|
|
117
|
-
const
|
|
118
|
-
|
|
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);
|
|
119
124
|
if (nx !== x || ny !== y) {
|
|
120
125
|
x = nx;
|
|
121
126
|
y = ny;
|
|
127
|
+
menuEl.style.setProperty('--popover-x', `${nx}px`);
|
|
128
|
+
menuEl.style.setProperty('--popover-y', `${ny}px`);
|
|
122
129
|
}
|
|
123
130
|
});
|
|
124
131
|
});
|
|
125
132
|
</script>
|
|
126
133
|
|
|
127
134
|
{#if open}
|
|
128
|
-
<!-- BACKDROP: va al body por el portal también -->
|
|
129
|
-
<div
|
|
130
|
-
use:portal={portalTarget}
|
|
131
|
-
role="dialog"
|
|
132
|
-
class="fixed inset-0 z-[2147483646]"
|
|
133
|
-
onclick={() => close()}
|
|
134
|
-
oncontextmenu={(e) => e.preventDefault()}
|
|
135
|
-
aria-modal="true"
|
|
136
|
-
tabindex="0"
|
|
137
|
-
style="pointer-events:auto"
|
|
138
|
-
/>
|
|
139
|
-
|
|
140
|
-
<!-- MENU: fijado al viewport y portaleado al body -->
|
|
141
135
|
<div
|
|
142
136
|
bind:this={menuEl}
|
|
143
|
-
|
|
144
|
-
class="
|
|
145
|
-
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);"
|
|
146
140
|
oncontextmenu={(e) => e.preventDefault()}
|
|
141
|
+
onkeydown={onKey}
|
|
147
142
|
>
|
|
148
143
|
<div class="flex items-center gap-1 px-1 py-1">
|
|
149
144
|
{#if stack.length > 0}
|
|
150
145
|
<button
|
|
151
146
|
class="rounded-lg p-1 hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
152
|
-
role="dialog"
|
|
153
147
|
aria-label="Atrás"
|
|
154
148
|
onclick={back}
|
|
155
149
|
>
|
|
@@ -190,16 +184,15 @@
|
|
|
190
184
|
{:else}
|
|
191
185
|
<button
|
|
192
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"
|
|
193
|
-
role="dialog"
|
|
194
187
|
disabled={it.disabled}
|
|
195
188
|
onclick={() => clickItem(it)}
|
|
196
189
|
>
|
|
197
190
|
<span class="truncate">{it.label}</span>
|
|
198
191
|
<span class="flex items-center gap-2">
|
|
199
192
|
{#if it.shortcut}
|
|
200
|
-
<kbd class="rounded bg-gray-100 px-1 text-[10px] dark:bg-gray-800"
|
|
201
|
-
|
|
202
|
-
>
|
|
193
|
+
<kbd class="rounded bg-gray-100 px-1 text-[10px] dark:bg-gray-800">
|
|
194
|
+
{it.shortcut}
|
|
195
|
+
</kbd>
|
|
203
196
|
{/if}
|
|
204
197
|
{#if hasChildren(it)}
|
|
205
198
|
<svg
|
|
@@ -223,3 +216,12 @@
|
|
|
223
216
|
</div>
|
|
224
217
|
</div>
|
|
225
218
|
{/if}
|
|
219
|
+
|
|
220
|
+
<style>
|
|
221
|
+
[popover] {
|
|
222
|
+
margin: 0;
|
|
223
|
+
border: none;
|
|
224
|
+
padding: 0;
|
|
225
|
+
z-index: 2147483647;
|
|
226
|
+
}
|
|
227
|
+
</style>
|