@r2digisolutions/ui 0.23.0 → 0.24.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.
- package/dist/components/container/DataTable/components/ContextMenu.svelte +38 -6
- package/dist/components/container/DataTable/components/ContextMenu.svelte.d.ts +1 -0
- package/dist/components/container/DataTable/utils/portal.d.ts +3 -0
- package/dist/components/container/DataTable/utils/portal.js +13 -0
- package/dist/components/container/DataTable/utils/position.d.ts +4 -0
- package/dist/components/container/DataTable/utils/position.js +11 -0
- package/dist/components/index.d.ts +2 -1
- package/dist/components/index.js +2 -1
- package/dist/components/ui/Selector/Selector.svelte +102 -0
- package/dist/components/ui/Selector/Selector.svelte.d.ts +37 -0
- package/package.json +14 -14
|
@@ -1,5 +1,7 @@
|
|
|
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';
|
|
3
5
|
|
|
4
6
|
type Props = {
|
|
5
7
|
items?: TContextMenuEntry[];
|
|
@@ -9,6 +11,8 @@
|
|
|
9
11
|
title?: string;
|
|
10
12
|
searchable?: boolean;
|
|
11
13
|
context?: any;
|
|
14
|
+
// por si algún día quieres desactivar el portal:
|
|
15
|
+
portalTarget?: HTMLElement | null;
|
|
12
16
|
};
|
|
13
17
|
let {
|
|
14
18
|
items = [],
|
|
@@ -17,7 +21,8 @@
|
|
|
17
21
|
open = $bindable(false),
|
|
18
22
|
title = '',
|
|
19
23
|
searchable = true,
|
|
20
|
-
context = null
|
|
24
|
+
context = null,
|
|
25
|
+
portalTarget = null
|
|
21
26
|
}: Props = $props();
|
|
22
27
|
|
|
23
28
|
let stack = $state<{ label: string; items: TContextMenuEntry[] }[]>([]);
|
|
@@ -61,7 +66,6 @@
|
|
|
61
66
|
const query = q.trim().toLowerCase();
|
|
62
67
|
let arr = query ? list.filter((it) => matches(it, query)) : list.slice();
|
|
63
68
|
|
|
64
|
-
// limpiar divisores (sin duplicados, ni al principio/fin)
|
|
65
69
|
const out: TContextMenuEntry[] = [];
|
|
66
70
|
let prevDiv = false;
|
|
67
71
|
for (const it of arr) {
|
|
@@ -100,20 +104,44 @@
|
|
|
100
104
|
document.addEventListener('keydown', handler);
|
|
101
105
|
return () => document.removeEventListener('keydown', handler);
|
|
102
106
|
});
|
|
107
|
+
|
|
108
|
+
// --- CLAMP dinámico al abrir o al cambiar x/y ---
|
|
109
|
+
let menuEl: HTMLDivElement | null = $state(null);
|
|
110
|
+
|
|
111
|
+
$effect(() => {
|
|
112
|
+
if (!open || !menuEl) return;
|
|
113
|
+
// siguiente frame para medir dimensiones reales
|
|
114
|
+
requestAnimationFrame(() => {
|
|
115
|
+
if (!menuEl) return;
|
|
116
|
+
const rect = menuEl.getBoundingClientRect();
|
|
117
|
+
const { x: nx, y: ny } = clampToViewport(x, y, rect.width, rect.height, 8);
|
|
118
|
+
// solo si cambian, re-ubica
|
|
119
|
+
if (nx !== x || ny !== y) {
|
|
120
|
+
x = nx;
|
|
121
|
+
y = ny;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
|
103
125
|
</script>
|
|
104
126
|
|
|
105
127
|
{#if open}
|
|
128
|
+
<!-- BACKDROP: va al body por el portal también -->
|
|
106
129
|
<div
|
|
130
|
+
use:portal={portalTarget}
|
|
107
131
|
role="dialog"
|
|
108
|
-
class="fixed inset-0 z-
|
|
132
|
+
class="fixed inset-0 z-[2147483646]"
|
|
109
133
|
onclick={() => close()}
|
|
110
134
|
oncontextmenu={(e) => e.preventDefault()}
|
|
111
135
|
aria-modal="true"
|
|
112
136
|
tabindex="0"
|
|
137
|
+
style="pointer-events:auto"
|
|
113
138
|
/>
|
|
114
139
|
|
|
140
|
+
<!-- MENU: fijado al viewport y portaleado al body -->
|
|
115
141
|
<div
|
|
116
|
-
|
|
142
|
+
bind:this={menuEl}
|
|
143
|
+
use:portal={portalTarget}
|
|
144
|
+
class="fixed z-[2147483647] w-72 rounded-2xl bg-white p-2 shadow-xl ring-1 ring-black/5 dark:bg-gray-900"
|
|
117
145
|
style={`left:${x}px; top:${y}px`}
|
|
118
146
|
oncontextmenu={(e) => e.preventDefault()}
|
|
119
147
|
>
|
|
@@ -133,8 +161,10 @@
|
|
|
133
161
|
stroke="currentColor"
|
|
134
162
|
stroke-width="2"
|
|
135
163
|
stroke-linecap="round"
|
|
136
|
-
stroke-linejoin="round"
|
|
164
|
+
stroke-linejoin="round"
|
|
137
165
|
>
|
|
166
|
+
<polyline points="15 18 9 12 15 6" />
|
|
167
|
+
</svg>
|
|
138
168
|
</button>
|
|
139
169
|
{/if}
|
|
140
170
|
<div class="min-w-0 flex-1 truncate px-1 text-xs font-medium opacity-70">
|
|
@@ -181,8 +211,10 @@
|
|
|
181
211
|
stroke="currentColor"
|
|
182
212
|
stroke-width="2"
|
|
183
213
|
stroke-linecap="round"
|
|
184
|
-
stroke-linejoin="round"
|
|
214
|
+
stroke-linejoin="round"
|
|
185
215
|
>
|
|
216
|
+
<polyline points="9 18 15 12 9 6" />
|
|
217
|
+
</svg>
|
|
186
218
|
{/if}
|
|
187
219
|
</span>
|
|
188
220
|
</button>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function portal(node, target = null) {
|
|
2
|
+
const tgt = target ?? document.body;
|
|
3
|
+
const placeholder = document.createComment('portal-placeholder');
|
|
4
|
+
node.parentNode?.insertBefore(placeholder, node);
|
|
5
|
+
tgt.appendChild(node);
|
|
6
|
+
return {
|
|
7
|
+
destroy() {
|
|
8
|
+
node.remove();
|
|
9
|
+
placeholder.parentNode?.insertBefore(node, placeholder);
|
|
10
|
+
placeholder.remove();
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function clampToViewport(x, y, menuW, menuH, padding = 8) {
|
|
2
|
+
const vw = document.documentElement.clientWidth;
|
|
3
|
+
const vh = document.documentElement.clientHeight;
|
|
4
|
+
let nx = x;
|
|
5
|
+
let ny = y;
|
|
6
|
+
if (nx + menuW + padding > vw)
|
|
7
|
+
nx = Math.max(padding, vw - menuW - padding);
|
|
8
|
+
if (ny + menuH + padding > vh)
|
|
9
|
+
ny = Math.max(padding, vh - menuH - padding);
|
|
10
|
+
return { x: nx, y: ny };
|
|
11
|
+
}
|
|
@@ -27,4 +27,5 @@ import DialogTitle from './ui/Dialog/DialogTitle.svelte';
|
|
|
27
27
|
import DialogDescription from './ui/Dialog/DialogDescription.svelte';
|
|
28
28
|
import DialogContent from './ui/Dialog/DialogContent.svelte';
|
|
29
29
|
import DialogFooter from './ui/Dialog/DialogFooter.svelte';
|
|
30
|
-
|
|
30
|
+
import Selector from './ui/Selector/Selector.svelte';
|
|
31
|
+
export { Selector, Dialog, DialogHeader, DialogTitle, DialogDescription, DialogContent, DialogFooter, Tag, NoContent, Alert, Avatar, Button, Badge, Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, Container, Checkbox, Field, Section, Loading, TableList, Heading, Label, Input, InputRadio, Textarea, };
|
package/dist/components/index.js
CHANGED
|
@@ -27,4 +27,5 @@ import DialogTitle from './ui/Dialog/DialogTitle.svelte';
|
|
|
27
27
|
import DialogDescription from './ui/Dialog/DialogDescription.svelte';
|
|
28
28
|
import DialogContent from './ui/Dialog/DialogContent.svelte';
|
|
29
29
|
import DialogFooter from './ui/Dialog/DialogFooter.svelte';
|
|
30
|
-
|
|
30
|
+
import Selector from './ui/Selector/Selector.svelte';
|
|
31
|
+
export { Selector, Dialog, DialogHeader, DialogTitle, DialogDescription, DialogContent, DialogFooter, Tag, NoContent, Alert, Avatar, Button, Badge, Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, Container, Checkbox, Field, Section, Loading, TableList, Heading, Label, Input, InputRadio, Textarea, };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<script lang="ts" generics="M extends string">
|
|
2
|
+
interface ModeOption<M extends string> {
|
|
3
|
+
id: M;
|
|
4
|
+
label: string;
|
|
5
|
+
icon?: any;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
tooltip?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface Props<M extends string> {
|
|
11
|
+
modes: ModeOption<M>[];
|
|
12
|
+
currentMode: M; // bindable
|
|
13
|
+
onModeChange?: (m: M) => void;
|
|
14
|
+
activation?: 'auto' | 'manual';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
modes,
|
|
19
|
+
currentMode = $bindable<M>(),
|
|
20
|
+
onModeChange,
|
|
21
|
+
activation = 'auto'
|
|
22
|
+
}: Props<M> = $props();
|
|
23
|
+
|
|
24
|
+
const buttons = new Map<M, HTMLButtonElement>();
|
|
25
|
+
|
|
26
|
+
function focusActive() {
|
|
27
|
+
const el = buttons.get(currentMode);
|
|
28
|
+
queueMicrotask(() => el?.focus());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function change(m: M) {
|
|
32
|
+
if (m === currentMode) return;
|
|
33
|
+
currentMode = m;
|
|
34
|
+
onModeChange?.(m);
|
|
35
|
+
focusActive();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function onKeydown(e: KeyboardEvent) {
|
|
39
|
+
const horiz = e.key === 'ArrowLeft' || e.key === 'ArrowRight';
|
|
40
|
+
const vert = e.key === 'ArrowUp' || e.key === 'ArrowDown';
|
|
41
|
+
const home = e.key === 'Home';
|
|
42
|
+
const end = e.key === 'End';
|
|
43
|
+
|
|
44
|
+
if (!horiz && !vert && !home && !end) return;
|
|
45
|
+
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
const enabled = modes.filter((m) => !m.disabled);
|
|
48
|
+
if (enabled.length === 0) return;
|
|
49
|
+
|
|
50
|
+
const curIdx = enabled.findIndex((m) => m.id === currentMode);
|
|
51
|
+
const idx = curIdx === -1 ? 0 : curIdx;
|
|
52
|
+
|
|
53
|
+
if (home) return change(enabled[0].id);
|
|
54
|
+
if (end) return change(enabled[enabled.length - 1].id);
|
|
55
|
+
|
|
56
|
+
const dir = e.key === 'ArrowRight' || e.key === 'ArrowDown' ? 1 : -1;
|
|
57
|
+
const nextIndex = (idx + dir + enabled.length) % enabled.length;
|
|
58
|
+
change(enabled[nextIndex].id);
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<div
|
|
63
|
+
class="flex rounded-lg bg-gray-100 p-1"
|
|
64
|
+
role="tablist"
|
|
65
|
+
aria-label="Seleccionar vista"
|
|
66
|
+
aria-orientation="horizontal"
|
|
67
|
+
onkeydown={onKeydown}
|
|
68
|
+
tabindex="0"
|
|
69
|
+
>
|
|
70
|
+
{#each modes as m (m.id)}
|
|
71
|
+
<button
|
|
72
|
+
bind:this={
|
|
73
|
+
() => buttons.get(m.id),
|
|
74
|
+
(el) => {
|
|
75
|
+
if (el) buttons.set(m.id, el);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
type="button"
|
|
79
|
+
role="tab"
|
|
80
|
+
aria-selected={currentMode === m.id}
|
|
81
|
+
aria-controls={undefined}
|
|
82
|
+
disabled={m.disabled}
|
|
83
|
+
tabindex={currentMode === m.id ? 0 : -1}
|
|
84
|
+
onfocus={() => {
|
|
85
|
+
if (activation === 'auto' && !m.disabled && currentMode !== m.id) {
|
|
86
|
+
change(m.id);
|
|
87
|
+
}
|
|
88
|
+
}}
|
|
89
|
+
onclick={() => !m.disabled && change(m.id)}
|
|
90
|
+
class={`flex items-center space-x-1 rounded-md px-3 py-2 text-sm font-medium transition-all
|
|
91
|
+
${currentMode === m.id ? 'bg-white text-purple-600 shadow-sm' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'}
|
|
92
|
+
${m.disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}
|
|
93
|
+
focus:outline-none focus-visible:ring-2 focus-visible:ring-purple-500 focus-visible:ring-offset-1`}
|
|
94
|
+
title={m.tooltip ?? m.label}
|
|
95
|
+
>
|
|
96
|
+
{#if m.icon}
|
|
97
|
+
<m.icon class="h-4 w-4" />
|
|
98
|
+
{/if}
|
|
99
|
+
<span class="hidden sm:inline">{m.label}</span>
|
|
100
|
+
</button>
|
|
101
|
+
{/each}
|
|
102
|
+
</div>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface ModeOption<M extends string> {
|
|
2
|
+
id: M;
|
|
3
|
+
label: string;
|
|
4
|
+
icon?: any;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
tooltip?: string;
|
|
7
|
+
}
|
|
8
|
+
interface Props<M extends string> {
|
|
9
|
+
modes: ModeOption<M>[];
|
|
10
|
+
currentMode: M;
|
|
11
|
+
onModeChange?: (m: M) => void;
|
|
12
|
+
activation?: 'auto' | 'manual';
|
|
13
|
+
}
|
|
14
|
+
declare function $$render<M extends string>(): {
|
|
15
|
+
props: Props<M>;
|
|
16
|
+
exports: {};
|
|
17
|
+
bindings: "currentMode";
|
|
18
|
+
slots: {};
|
|
19
|
+
events: {};
|
|
20
|
+
};
|
|
21
|
+
declare class __sveltets_Render<M extends string> {
|
|
22
|
+
props(): ReturnType<typeof $$render<M>>['props'];
|
|
23
|
+
events(): ReturnType<typeof $$render<M>>['events'];
|
|
24
|
+
slots(): ReturnType<typeof $$render<M>>['slots'];
|
|
25
|
+
bindings(): "currentMode";
|
|
26
|
+
exports(): {};
|
|
27
|
+
}
|
|
28
|
+
interface $$IsomorphicComponent {
|
|
29
|
+
new <M extends string>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<M>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<M>['props']>, ReturnType<__sveltets_Render<M>['events']>, ReturnType<__sveltets_Render<M>['slots']>> & {
|
|
30
|
+
$$bindings?: ReturnType<__sveltets_Render<M>['bindings']>;
|
|
31
|
+
} & ReturnType<__sveltets_Render<M>['exports']>;
|
|
32
|
+
<M extends string>(internal: unknown, props: ReturnType<__sveltets_Render<M>['props']> & {}): ReturnType<__sveltets_Render<M>['exports']>;
|
|
33
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
34
|
+
}
|
|
35
|
+
declare const Selector: $$IsomorphicComponent;
|
|
36
|
+
type Selector<M extends string> = InstanceType<typeof Selector<M>>;
|
|
37
|
+
export default Selector;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@r2digisolutions/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"packageManager": "bun@1.2.23",
|
|
6
6
|
"publishConfig": {
|
|
@@ -53,20 +53,20 @@
|
|
|
53
53
|
"@playwright/test": "^1.55.1",
|
|
54
54
|
"@storybook/addon-essentials": "^8.6.14",
|
|
55
55
|
"@storybook/addon-interactions": "^8.6.14",
|
|
56
|
-
"@storybook/addon-svelte-csf": "5.0.
|
|
56
|
+
"@storybook/addon-svelte-csf": "5.0.10",
|
|
57
57
|
"@storybook/blocks": "^8.6.14",
|
|
58
|
-
"@storybook/svelte": "^9.1.
|
|
59
|
-
"@storybook/sveltekit": "^9.1.
|
|
58
|
+
"@storybook/svelte": "^9.1.10",
|
|
59
|
+
"@storybook/sveltekit": "^9.1.10",
|
|
60
60
|
"@storybook/test": "^8.6.14",
|
|
61
|
-
"@sveltejs/adapter-static": "^3.0.
|
|
62
|
-
"@sveltejs/kit": "^2.
|
|
61
|
+
"@sveltejs/adapter-static": "^3.0.10",
|
|
62
|
+
"@sveltejs/kit": "^2.44.0",
|
|
63
63
|
"@sveltejs/package": "^2.5.4",
|
|
64
64
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
65
|
-
"@tailwindcss/postcss": "^4.1.
|
|
65
|
+
"@tailwindcss/postcss": "^4.1.14",
|
|
66
66
|
"@testing-library/svelte": "^5.2.8",
|
|
67
67
|
"@vitest/browser": "^3.2.4",
|
|
68
68
|
"changeset": "^0.2.6",
|
|
69
|
-
"eslint": "^9.
|
|
69
|
+
"eslint": "^9.37.0",
|
|
70
70
|
"eslint-config-prettier": "^10.1.8",
|
|
71
71
|
"eslint-plugin-svelte": "^3.12.4",
|
|
72
72
|
"globals": "^16.4.0",
|
|
@@ -75,14 +75,14 @@
|
|
|
75
75
|
"prettier": "^3.6.2",
|
|
76
76
|
"prettier-plugin-svelte": "^3.4.0",
|
|
77
77
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
|
78
|
-
"publint": "^0.3.
|
|
79
|
-
"storybook": "^9.1.
|
|
80
|
-
"svelte": "^5.39.
|
|
78
|
+
"publint": "^0.3.14",
|
|
79
|
+
"storybook": "^9.1.10",
|
|
80
|
+
"svelte": "^5.39.9",
|
|
81
81
|
"svelte-check": "^4.3.2",
|
|
82
|
-
"tailwindcss": "^4.1.
|
|
83
|
-
"typescript": "^5.9.
|
|
82
|
+
"tailwindcss": "^4.1.14",
|
|
83
|
+
"typescript": "^5.9.3",
|
|
84
84
|
"typescript-eslint": "^8.45.0",
|
|
85
|
-
"vite": "^7.1.
|
|
85
|
+
"vite": "^7.1.9",
|
|
86
86
|
"vitest": "^3.2.4"
|
|
87
87
|
},
|
|
88
88
|
"dependencies": {
|