refactor(uix): unify matching menu part prop types

astra
dev 2 weeks ago
parent 0f9374183e
commit c668736fad

@ -188,7 +188,7 @@ validación exige comparación de tipos, los tests de ambos proveedores,
`check:gate` y auditoría dirigida de los dos componentes.
Resultado: las declaraciones comunes de `Content` y `SubContent` viven en
`menu-content-props.ts`. Los tipos públicos conservan sus wrappers y sólo
`menu-common-props.ts` (renombrado en la fase siguiente). Los tipos públicos conservan sus wrappers y sólo
DropdownMenu conserva `customAnchor`; no hay imports de valor ni cambios de
runtime. Los tres archivos de producción pasan de 350 líneas físicas en los dos
`types.ts` a 293 entre ambos y el nuevo módulo: **57 líneas netas menos**.
@ -201,6 +201,32 @@ pasó, incluido build/smoke de `apps/base`. La primera suite global falló sólo
en dos pruebas de `boot-check` por acceso denegado al TEMP del sistema; con TEMP
escribible fuera del repositorio pasaron **471/471 archivos y 5470/5470 tests**.
### Pre-flight de Soma para las partes de ítem de los dos menús (2026-09-22)
El usuario autorizó la deduplicación interna sin cambios de comportamiento, API
ni edición de `web/`. Continúa la comparación oficial de cuatro bibliotecas
del pre-flight anterior, limitada ahora a las declaraciones de props:
| Aspecto | [Radix](https://www.radix-ui.com/primitives/docs/components/dropdown-menu) | [Bits UI](https://bits-ui.com/docs/components/dropdown-menu) | [Ark UI](https://ark-ui.com/docs/components/menu) | [React Spectrum](https://react-spectrum.adobe.com/Menu) | Decisión UIX |
| --------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------- |
| Ítem y flecha | Item y Arrow | Item y Arrow | Item y Arrow | MenuItem | Compartir sólo los tipos literalmente iguales; conservar wrappers y markup. |
| Radio y submenú | RadioGroup/RadioItem y Sub | RadioGroup/RadioItem y Sub | Ítems de radio y submenú | Selección y SubmenuTrigger | Compartir sólo las declaraciones iguales; no cambiar selección, foco ni apertura. |
Propiedad exclusiva de Soma: los tipos públicos y los componentes siguen siendo
dos familias. Root, Trigger y CheckboxItem tienen diferencias reales y quedan
locales. No se modifican partes, eventos, tamaños, variantes, colores, demos,
estilos ni efectos. El cambio se caracteriza con igualdad de tipos antes de
extraer, pruebas de ambos proveedores, `check:gate` y auditoría dirigida.
Resultado: `Arrow`, `Item`, `RadioGroup`, `RadioItem`, `Sub` y `SubTrigger`
comparten ahora una sola declaración cada uno. Los tipos públicos mantienen
sus nombres y Root, Trigger y CheckboxItem siguen separados. Los tres archivos
de producción pasan de 293 a 287 líneas físicas: **6 líneas netas menos** en
esta fase, además de eliminar seis fuentes de posible divergencia. Las seis
igualdades de tipos pasaron antes y después; `check:gate` cerró con los 89
errores preexistentes en el ledger, los proveedores pasaron 14/14 y ambos
componentes obtuvieron PASS en la auditoría dirigida.
### Guardia documental L-157 (2026-09-21)
`docs-check` I10 ya obtiene los nombres de declaraciones con el AST de

@ -143,7 +143,9 @@ cableado de cancelación; este candidato está resuelto y no debe reabrirse.
ContextMenu y DropdownMenu mantienen providers y wrappers propios. Sus tipos
`Content`/`SubContent` compartían declaraciones idénticas que ahora residen en
`menu-content-props.ts` (57 líneas netas menos en producción). `customAnchor`
`menu-common-props.ts` (57 líneas netas menos en la primera extracción y 6
adicionales al compartir Arrow, Item, RadioGroup, RadioItem, Sub y SubTrigger).
`customAnchor`
continúa siendo exclusivo de DropdownMenu; ninguna lógica de foco, dismissal o
posicionamiento se trasladó al módulo compartido.

@ -1,7 +1,7 @@
import type { Snippet } from 'svelte';
import type { WithChild, Without, OnChangeFn, Direction } from '../../types';
import type { PrimitiveDivAttributes, PrimitiveSpanAttributes } from '../../types';
import type { MenuContentCommonProps, MenuSubContentCommonProps } from '../menu-content-props';
import type * as MenuCommon from '../menu-common-props';
/**
* ContextMenu root.
@ -31,26 +31,15 @@ export type ContextMenuTriggerProps = WithChild<{
}> &
Without<PrimitiveSpanAttributes, {}>;
export type ContextMenuContentProps = WithChild<MenuContentCommonProps, { open: boolean }> &
export type ContextMenuContentProps = WithChild<
MenuCommon.MenuContentCommonProps,
{ open: boolean }
> &
Without<PrimitiveDivAttributes, {}>;
export type ContextMenuArrowProps = WithChild<{
id?: string;
/** @default 10 */
width?: number;
/** @default 5 */
height?: number;
}>;
export type ContextMenuArrowProps = MenuCommon.MenuArrowCommonProps;
export type ContextMenuItemProps = WithChild<{
id?: string;
/** @default false */
disabled?: boolean;
/** For typeahead search. */ textValue?: string;
/** Callback when item is selected. */ onSelect?: () => void;
/** Close menu after selection. @default true */ closeOnSelect?: boolean;
}> &
Without<PrimitiveDivAttributes, {}>;
export type ContextMenuItemProps = MenuCommon.MenuItemCommonProps;
export type ContextMenuGroupProps = WithChild<{ id?: string }> &
Without<PrimitiveDivAttributes, {}>;
@ -73,38 +62,16 @@ export type ContextMenuCheckboxItemProps = WithChild<
> &
Without<PrimitiveDivAttributes, {}>;
export type ContextMenuRadioGroupProps = WithChild<{
id?: string;
/** Bindable. */ value?: string;
onValueChange?: OnChangeFn<string>;
}> &
Without<PrimitiveDivAttributes, {}>;
export type ContextMenuRadioGroupProps = MenuCommon.MenuRadioGroupCommonProps;
export type ContextMenuRadioItemProps = WithChild<
{
id?: string;
/** Required. */ value: string;
/** @default false */ disabled?: boolean;
textValue?: string;
onSelect?: () => void;
/** @default true */ closeOnSelect?: boolean;
},
{ checked: boolean }
> &
Without<PrimitiveDivAttributes, {}>;
export type ContextMenuRadioItemProps = MenuCommon.MenuRadioItemCommonProps;
export type ContextMenuSubProps = {
/** Bindable. @default false */ open?: boolean;
onOpenChange?: OnChangeFn<boolean>;
children?: Snippet;
};
export type ContextMenuSubProps = MenuCommon.MenuSubCommonProps;
export type ContextMenuSubTriggerProps = WithChild<{
id?: string;
/** @default false */ disabled?: boolean;
textValue?: string;
}> &
Without<PrimitiveDivAttributes, {}>;
export type ContextMenuSubTriggerProps = MenuCommon.MenuSubTriggerCommonProps;
export type ContextMenuSubContentProps = WithChild<MenuSubContentCommonProps, { open: boolean }> &
export type ContextMenuSubContentProps = WithChild<
MenuCommon.MenuSubContentCommonProps,
{ open: boolean }
> &
Without<PrimitiveDivAttributes, {}>;

@ -1,7 +1,7 @@
import type { Snippet } from 'svelte';
import type { WithChild, Without, OnChangeFn, Direction } from '../../types';
import type { PrimitiveDivAttributes, PrimitiveButtonAttributes } from '../../types';
import type { MenuContentCommonProps, MenuSubContentCommonProps } from '../menu-content-props';
import type * as MenuCommon from '../menu-common-props';
export type MenuProps = {
/** Unique identifier. Auto-generated if omitted. */
@ -27,7 +27,7 @@ export type MenuProps = {
export type MenuTriggerProps = WithChild<{ id?: string }> & Without<PrimitiveButtonAttributes, {}>;
export type MenuContentProps = WithChild<
MenuContentCommonProps & {
MenuCommon.MenuContentCommonProps & {
/**
* Anchor the floating panel to this element instead of the Trigger.
* Lets a composing component (e.g. SplitButton) position the menu
@ -41,23 +41,9 @@ export type MenuContentProps = WithChild<
> &
Without<PrimitiveDivAttributes, {}>;
export type MenuArrowProps = WithChild<{
id?: string;
/** @default 10 */
width?: number;
/** @default 5 */
height?: number;
}>;
export type MenuArrowProps = MenuCommon.MenuArrowCommonProps;
export type MenuItemProps = WithChild<{
id?: string;
/** @default false */
disabled?: boolean;
/** For typeahead search. */ textValue?: string;
/** Callback when item is selected. */ onSelect?: () => void;
/** Close menu after selection. @default true */ closeOnSelect?: boolean;
}> &
Without<PrimitiveDivAttributes, {}>;
export type MenuItemProps = MenuCommon.MenuItemCommonProps;
export type MenuGroupProps = WithChild<{ id?: string }> & Without<PrimitiveDivAttributes, {}>;
export type MenuGroupHeadingProps = WithChild<{ id?: string }> &
@ -97,38 +83,16 @@ export type MenuCheckboxGroupProps = WithChild<{
}> &
Without<PrimitiveDivAttributes, {}>;
export type MenuRadioGroupProps = WithChild<{
id?: string;
/** Bindable. */ value?: string;
onValueChange?: OnChangeFn<string>;
}> &
Without<PrimitiveDivAttributes, {}>;
export type MenuRadioGroupProps = MenuCommon.MenuRadioGroupCommonProps;
export type MenuRadioItemProps = WithChild<
{
id?: string;
/** Required. */ value: string;
/** @default false */ disabled?: boolean;
textValue?: string;
onSelect?: () => void;
/** @default true */ closeOnSelect?: boolean;
},
{ checked: boolean }
> &
Without<PrimitiveDivAttributes, {}>;
export type MenuRadioItemProps = MenuCommon.MenuRadioItemCommonProps;
export type MenuSubProps = {
/** Bindable. @default false */ open?: boolean;
onOpenChange?: OnChangeFn<boolean>;
children?: Snippet;
};
export type MenuSubProps = MenuCommon.MenuSubCommonProps;
export type MenuSubTriggerProps = WithChild<{
id?: string;
/** @default false */ disabled?: boolean;
textValue?: string;
}> &
Without<PrimitiveDivAttributes, {}>;
export type MenuSubTriggerProps = MenuCommon.MenuSubTriggerCommonProps;
export type MenuSubContentProps = WithChild<MenuSubContentCommonProps, { open: boolean }> &
export type MenuSubContentProps = WithChild<
MenuCommon.MenuSubContentCommonProps,
{ open: boolean }
> &
Without<PrimitiveDivAttributes, {}>;

@ -1,4 +1,16 @@
import type { Align, Arrayable, Boundary, Direction, Side, StyleProperties } from '../types';
import type { Snippet } from 'svelte';
import type {
Align,
Arrayable,
Boundary,
Direction,
OnChangeFn,
PrimitiveDivAttributes,
Side,
StyleProperties,
WithChild,
Without
} from '../types';
import type { DismissalBehavior } from '../layers/dismissal.svelte';
/** Positioning, focus and dismissal props shared by the two menu content parts. */
@ -47,3 +59,54 @@ export type MenuSubContentCommonProps = {
dir?: Direction;
style?: StyleProperties | string | null;
};
export type MenuArrowCommonProps = WithChild<{
id?: string;
/** @default 10 */
width?: number;
/** @default 5 */
height?: number;
}>;
export type MenuItemCommonProps = WithChild<{
id?: string;
/** @default false */
disabled?: boolean;
/** For typeahead search. */ textValue?: string;
/** Callback when item is selected. */ onSelect?: () => void;
/** Close menu after selection. @default true */ closeOnSelect?: boolean;
}> &
Without<PrimitiveDivAttributes, {}>;
export type MenuRadioGroupCommonProps = WithChild<{
id?: string;
/** Bindable. */ value?: string;
onValueChange?: OnChangeFn<string>;
}> &
Without<PrimitiveDivAttributes, {}>;
export type MenuRadioItemCommonProps = WithChild<
{
id?: string;
/** Required. */ value: string;
/** @default false */ disabled?: boolean;
textValue?: string;
onSelect?: () => void;
/** @default true */ closeOnSelect?: boolean;
},
{ checked: boolean }
> &
Without<PrimitiveDivAttributes, {}>;
export type MenuSubCommonProps = {
/** Bindable. @default false */ open?: boolean;
onOpenChange?: OnChangeFn<boolean>;
children?: Snippet;
};
export type MenuSubTriggerCommonProps = WithChild<{
id?: string;
/** @default false */ disabled?: boolean;
textValue?: string;
}> &
Without<PrimitiveDivAttributes, {}>;

@ -1,5 +1,23 @@
import type { ContextMenuContentProps, ContextMenuSubContentProps } from './context-menu/types';
import type { MenuContentProps, MenuSubContentProps } from './dropdown-menu/types';
import type {
ContextMenuArrowProps,
ContextMenuContentProps,
ContextMenuItemProps,
ContextMenuRadioGroupProps,
ContextMenuRadioItemProps,
ContextMenuSubContentProps,
ContextMenuSubProps,
ContextMenuSubTriggerProps
} from './context-menu/types';
import type {
MenuArrowProps,
MenuContentProps,
MenuItemProps,
MenuRadioGroupProps,
MenuRadioItemProps,
MenuSubContentProps,
MenuSubProps,
MenuSubTriggerProps
} from './dropdown-menu/types';
type Equal<A, B> =
(<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
@ -63,3 +81,13 @@ export type ContextHasNoCustomAnchor = Assert<
export type DropdownCustomAnchor = Assert<
Equal<MenuContentProps['customAnchor'], HTMLElement | null | undefined>
>;
export type SharedArrowContract = Assert<Equal<ContextMenuArrowProps, MenuArrowProps>>;
export type SharedItemContract = Assert<Equal<ContextMenuItemProps, MenuItemProps>>;
export type SharedRadioGroupContract = Assert<
Equal<ContextMenuRadioGroupProps, MenuRadioGroupProps>
>;
export type SharedRadioItemContract = Assert<Equal<ContextMenuRadioItemProps, MenuRadioItemProps>>;
export type SharedSubContract = Assert<Equal<ContextMenuSubProps, MenuSubProps>>;
export type SharedSubTriggerContract = Assert<
Equal<ContextMenuSubTriggerProps, MenuSubTriggerProps>
>;
Loading…
Cancel
Save

Powered by TurnKey Linux.