danielgm-ui 0.4.4 → 0.4.6
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/context-menu.d.ts +3 -3
- package/dist/context-menu.js +2 -1
- package/dist/dropdown-menu.d.ts +21 -13
- package/dist/dropdown-menu.js +2 -1
- package/dist/internal/menu-items.d.ts +10 -0
- package/dist/internal/menu-items.js +46 -0
- package/dist/styles.css +10 -2
- package/package.json +1 -1
package/dist/context-menu.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/** Menú contextual sobre una zona concreta. Radix gestiona el anclaje al puntero y la navegación. */
|
|
2
2
|
import { type ReactElement } from 'react';
|
|
3
3
|
import type { DropdownMenuItem } from './dropdown-menu.js';
|
|
4
|
-
/** Mismo contrato de acciones y separadores que DropdownMenu. */
|
|
4
|
+
/** Mismo contrato de acciones, categorías anidadas y separadores que DropdownMenu. */
|
|
5
5
|
export type ContextMenuItem = DropdownMenuItem;
|
|
6
6
|
export interface ContextMenuProps {
|
|
7
7
|
/** Un único elemento DOM o componente que transmita ref y props; se conserva su aspecto. */
|
|
8
8
|
children: ReactElement;
|
|
9
|
-
/** Acciones y separadores con IDs únicos. Los atajos son leyendas
|
|
9
|
+
/** Acciones, categorías y separadores con IDs únicos por nivel. Los atajos son leyendas. */
|
|
10
10
|
items: readonly ContextMenuItem[];
|
|
11
11
|
/** Apariencia de las opciones; no modifica el panel. @defaultValue 'default' */
|
|
12
12
|
itemVariant?: 'default' | 'ghost';
|
|
@@ -16,7 +16,7 @@ export interface ContextMenuProps {
|
|
|
16
16
|
disabled?: boolean;
|
|
17
17
|
/** Notifica apertura y cierre. La posición y la apertura proceden del disparador. */
|
|
18
18
|
onOpenChange?: (open: boolean) => void;
|
|
19
|
-
/** Clase CSS
|
|
19
|
+
/** Clase CSS de todos los paneles, incluidos los submenús, renderizados en portales. */
|
|
20
20
|
className?: string;
|
|
21
21
|
}
|
|
22
22
|
/** Clic derecho, pulsación prolongada o Shift+F10. El clic izquierdo conserva la acción del hijo. */
|
package/dist/context-menu.js
CHANGED
|
@@ -6,6 +6,7 @@ import * as Primitive from '@radix-ui/react-context-menu';
|
|
|
6
6
|
import { menuItemFocusProps } from './internal/menu-item-focus.js';
|
|
7
7
|
import { restoreFocus } from './internal/focus.js';
|
|
8
8
|
import { OverlayLayerProvider, useOverlayLayer } from './internal/overlay-layer.js';
|
|
9
|
+
import { MenuItems } from './internal/menu-items.js';
|
|
9
10
|
import { cx } from './utils.js';
|
|
10
11
|
/** Clic derecho, pulsación prolongada o Shift+F10. El clic izquierdo conserva la acción del hijo. */
|
|
11
12
|
export function ContextMenu({ children, items, itemVariant = 'default', label, disabled = false, onOpenChange, className, }) {
|
|
@@ -47,5 +48,5 @@ export function ContextMenu({ children, items, itemVariant = 'default', label, d
|
|
|
47
48
|
if (!outside.current && trigger.current?.isConnected)
|
|
48
49
|
restoreFocus(trigger.current);
|
|
49
50
|
outside.current = false;
|
|
50
|
-
}, children: [_jsx(Primitive.Label, { className: "dgm-dropdown-label", children: label }),
|
|
51
|
+
}, children: [_jsx(Primitive.Label, { className: "dgm-dropdown-label", children: label }), _jsx(MenuItems, { primitive: Primitive, items: items, itemVariant: itemVariant, className: cx('dgm-context-menu', className) })] }) })] }) }));
|
|
51
52
|
}
|
package/dist/dropdown-menu.d.ts
CHANGED
|
@@ -1,30 +1,37 @@
|
|
|
1
1
|
/** Menú de acciones: Radix gestiona teclado, búsqueda por letras, portal y foco. */
|
|
2
2
|
import { type ReactElement, type ReactNode } from 'react';
|
|
3
|
-
/**
|
|
3
|
+
/** Acciones, separadores o categorías anidadas con IDs estables y únicos por nivel. */
|
|
4
4
|
export type DropdownMenuItem = {
|
|
5
5
|
id: string;
|
|
6
6
|
type: 'separator';
|
|
7
|
-
} | {
|
|
8
|
-
id: string;
|
|
7
|
+
} | (MenuEntry & {
|
|
9
8
|
type?: 'item';
|
|
10
|
-
/**
|
|
9
|
+
/** Leyenda decorativa; no registra un atajo. */
|
|
10
|
+
shortcut?: string;
|
|
11
|
+
/** Ejecuta la acción; preventDefault mantiene el menú abierto. */
|
|
12
|
+
onSelect?: (event: Event) => void;
|
|
13
|
+
}) | (MenuEntry & {
|
|
14
|
+
type: 'submenu';
|
|
15
|
+
/** Opciones del panel lateral; admite más categorías. Hover no ejecuta acciones. */
|
|
16
|
+
items: readonly DropdownMenuItem[];
|
|
17
|
+
});
|
|
18
|
+
/** Presentación compartida por acciones y categorías. */
|
|
19
|
+
interface MenuEntry {
|
|
20
|
+
id: string;
|
|
21
|
+
/** Texto visible y accesible, también usado para buscar escribiendo. */
|
|
11
22
|
label: string;
|
|
12
23
|
/** Icono decorativo opcional. */
|
|
13
24
|
icon?: ReactNode;
|
|
14
|
-
/** Leyenda visual: no registra por sí misma un atajo de teclado. */
|
|
15
|
-
shortcut?: string;
|
|
16
25
|
disabled?: boolean;
|
|
17
|
-
/**
|
|
26
|
+
/** Hereda itemVariant del menú, también en paneles anidados. */
|
|
18
27
|
variant?: 'default' | 'ghost';
|
|
19
|
-
/**
|
|
28
|
+
/** Tono semántico destructivo. */
|
|
20
29
|
danger?: boolean;
|
|
21
|
-
|
|
22
|
-
onSelect?: (event: Event) => void;
|
|
23
|
-
};
|
|
30
|
+
}
|
|
24
31
|
export interface DropdownMenuProps {
|
|
25
32
|
/** Un único botón que acepte ref y props; Button es compatible. */
|
|
26
33
|
children: ReactElement;
|
|
27
|
-
/** Acciones y separadores en orden de lectura. IDs únicos. */
|
|
34
|
+
/** Acciones, categorías y separadores en orden de lectura. IDs únicos por nivel. */
|
|
28
35
|
items: readonly DropdownMenuItem[];
|
|
29
36
|
/** Apariencia de las opciones; no modifica el panel. @defaultValue 'default' */
|
|
30
37
|
itemVariant?: 'default' | 'ghost';
|
|
@@ -44,8 +51,9 @@ export interface DropdownMenuProps {
|
|
|
44
51
|
sideOffset?: number;
|
|
45
52
|
/** Desactiva el disparador. @defaultValue false */
|
|
46
53
|
disabled?: boolean;
|
|
47
|
-
/** Clase
|
|
54
|
+
/** Clase de los paneles, incluidos los submenús, renderizados en portales. */
|
|
48
55
|
className?: string;
|
|
49
56
|
}
|
|
50
57
|
/** El menú no bloquea el scroll de la página y devuelve el foco sin desplazarla. */
|
|
51
58
|
export declare function DropdownMenu({ children, items, itemVariant, label, open, defaultOpen, onOpenChange, side, align, sideOffset, disabled, className, }: DropdownMenuProps): ReactElement;
|
|
59
|
+
export {};
|
package/dist/dropdown-menu.js
CHANGED
|
@@ -6,6 +6,7 @@ import * as Primitive from '@radix-ui/react-dropdown-menu';
|
|
|
6
6
|
import { menuItemFocusProps } from './internal/menu-item-focus.js';
|
|
7
7
|
import { restoreFocus } from './internal/focus.js';
|
|
8
8
|
import { OverlayLayerProvider, useOverlayLayer } from './internal/overlay-layer.js';
|
|
9
|
+
import { MenuItems } from './internal/menu-items.js';
|
|
9
10
|
import { cx } from './utils.js';
|
|
10
11
|
/** El menú no bloquea el scroll de la página y devuelve el foco sin desplazarla. */
|
|
11
12
|
export function DropdownMenu({ children, items, itemVariant = 'default', label, open, defaultOpen, onOpenChange, side = 'bottom', align = 'start', sideOffset = 6, disabled, className, }) {
|
|
@@ -21,5 +22,5 @@ export function DropdownMenu({ children, items, itemVariant = 'default', label,
|
|
|
21
22
|
restoreFocus(trigger.current);
|
|
22
23
|
}
|
|
23
24
|
interactedOutside.current = false;
|
|
24
|
-
}, children: [label && _jsx(Primitive.Label, { className: "dgm-dropdown-label", children: label }),
|
|
25
|
+
}, children: [label && _jsx(Primitive.Label, { className: "dgm-dropdown-label", children: label }), _jsx(MenuItems, { primitive: Primitive, items: items, itemVariant: itemVariant, className: className })] }) })] }) }));
|
|
25
26
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type * as DropdownPrimitive from '@radix-ui/react-dropdown-menu';
|
|
2
|
+
import type { DropdownMenuItem } from '../dropdown-menu.js';
|
|
3
|
+
type MenuPrimitive = Pick<typeof DropdownPrimitive, 'Item' | 'Separator' | 'Sub' | 'SubTrigger' | 'SubContent' | 'Portal'>;
|
|
4
|
+
export declare function MenuItems({ primitive: Primitive, items, itemVariant, className, }: {
|
|
5
|
+
primitive: MenuPrimitive;
|
|
6
|
+
items: readonly DropdownMenuItem[];
|
|
7
|
+
itemVariant: 'default' | 'ghost';
|
|
8
|
+
className?: string;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** Mantiene idéntica la composición de ambos menús sin reemplazar la intención de puntero de Radix. */
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { cx } from '../utils.js';
|
|
5
|
+
import { menuItemFocusProps } from './menu-item-focus.js';
|
|
6
|
+
import { useOverlayLayer } from './overlay-layer.js';
|
|
7
|
+
export function MenuItems({ primitive: Primitive, items, itemVariant, className, }) {
|
|
8
|
+
return (_jsx(_Fragment, { children: items.map((item) => {
|
|
9
|
+
if (item.type === 'separator')
|
|
10
|
+
return _jsx(Primitive.Separator, { className: "dgm-dropdown-separator" }, item.id);
|
|
11
|
+
const itemClass = cx('dgm-dropdown-item', (item.variant ?? itemVariant) === 'ghost' && 'dgm-dropdown-item--ghost', item.danger && 'dgm-dropdown-item--danger');
|
|
12
|
+
const content = (_jsxs(_Fragment, { children: [item.icon && (_jsx("span", { className: "dgm-option-icon", "aria-hidden": "true", children: item.icon })), _jsx("span", { className: "dgm-dropdown-text", children: item.label }), item.type === 'submenu' ? (_jsx("svg", { className: "dgm-dropdown-chevron", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: _jsx("path", { d: "m9 6 6 6-6 6" }) })) : (item.shortcut && (_jsx("span", { className: "dgm-dropdown-shortcut", "aria-hidden": "true", children: item.shortcut })))] }));
|
|
13
|
+
if (item.type !== 'submenu')
|
|
14
|
+
return (_jsx(Primitive.Item, { textValue: item.label, disabled: item.disabled, onSelect: item.onSelect, className: itemClass, children: content }, item.id));
|
|
15
|
+
return (_jsx(Submenu, { primitive: Primitive, item: item, itemVariant: itemVariant, className: className, itemClass: itemClass, children: content }, item.id));
|
|
16
|
+
}) }));
|
|
17
|
+
}
|
|
18
|
+
/** Solo Escape se adapta: Radix cierra toda la raíz por defecto; aquí conserva los niveles padres. */
|
|
19
|
+
function Submenu({ primitive: Primitive, item, itemVariant, className, itemClass, children, }) {
|
|
20
|
+
const [open, setOpen] = useState(false);
|
|
21
|
+
const trigger = useRef(null);
|
|
22
|
+
const leaveTimer = useRef(undefined);
|
|
23
|
+
const cancelLeave = () => clearTimeout(leaveTimer.current);
|
|
24
|
+
// Radix conserva su corredor de intención entre paneles; el cierre exterior espera
|
|
25
|
+
// a que el puntero pueda entrar en cualquiera de las dos superficies.
|
|
26
|
+
const scheduleLeave = (event) => {
|
|
27
|
+
if (event.pointerType !== 'mouse')
|
|
28
|
+
return;
|
|
29
|
+
cancelLeave();
|
|
30
|
+
leaveTimer.current = setTimeout(() => setOpen(false), 400);
|
|
31
|
+
};
|
|
32
|
+
useEffect(() => cancelLeave, []);
|
|
33
|
+
const { style } = useOverlayLayer();
|
|
34
|
+
return (_jsxs(Primitive.Sub, { open: open, onOpenChange: (next) => {
|
|
35
|
+
cancelLeave();
|
|
36
|
+
setOpen(next);
|
|
37
|
+
}, children: [_jsx(Primitive.SubTrigger, { ref: trigger, onPointerEnter: cancelLeave, onPointerLeave: scheduleLeave, textValue: item.label, disabled: item.disabled, className: itemClass, children: children }), _jsx(Primitive.Portal, { children: _jsx(Primitive.SubContent, { ...menuItemFocusProps, onPointerEnter: cancelLeave, onPointerLeave: scheduleLeave, style: style, sideOffset: 6, collisionPadding: 12, className: cx('dgm-dropdown', className), onEscapeKeyDown: (event) => {
|
|
38
|
+
event.preventDefault();
|
|
39
|
+
cancelLeave();
|
|
40
|
+
setOpen(false);
|
|
41
|
+
trigger.current?.focus({ preventScroll: true });
|
|
42
|
+
}, onContextMenu: (event) => {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
event.stopPropagation();
|
|
45
|
+
}, children: _jsx(MenuItems, { primitive: Primitive, items: item.items, itemVariant: itemVariant, className: className }) }) })] }));
|
|
46
|
+
}
|
package/dist/styles.css
CHANGED
|
@@ -1651,8 +1651,8 @@ button.dgm-step:hover:not(:disabled),
|
|
|
1651
1651
|
.dgm-dropdown {
|
|
1652
1652
|
box-sizing: border-box;
|
|
1653
1653
|
z-index: calc(var(--dgm-layer-base) + var(--dgm-layer-depth, 0) * var(--dgm-layer-step) + 1);
|
|
1654
|
-
min-width: min(12rem, calc(100vw - 1.5rem));
|
|
1655
|
-
max-width: min(24rem, calc(100vw - 1.5rem));
|
|
1654
|
+
min-width: min(12rem, var(--radix-popper-available-width, calc(100vw - 1.5rem)));
|
|
1655
|
+
max-width: min(24rem, calc(100vw - 1.5rem), var(--radix-popper-available-width));
|
|
1656
1656
|
max-height: var(--radix-dropdown-menu-content-available-height);
|
|
1657
1657
|
overflow-y: auto;
|
|
1658
1658
|
padding: var(--dgm-list-padding);
|
|
@@ -2191,6 +2191,14 @@ button.dgm-step:hover:not(:disabled),
|
|
|
2191
2191
|
}
|
|
2192
2192
|
}
|
|
2193
2193
|
|
|
2194
|
+
/* La flecha no reduce el texto de categorías largas ni cambia sus métricas al abrir. */
|
|
2195
|
+
.dgm-dropdown-chevron {
|
|
2196
|
+
flex-shrink: 0;
|
|
2197
|
+
}
|
|
2198
|
+
.dgm-dropdown-chevron:dir(rtl) {
|
|
2199
|
+
transform: rotate(180deg);
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2194
2202
|
/* Componentes compuestos: esta hoja se distribuye unida a styles.css, sin imports adicionales. */
|
|
2195
2203
|
.dgm-visually-hidden,
|
|
2196
2204
|
.dgm-form-value {
|