@beweco/aurora-ui 1.0.0-beta.140 → 1.0.0-beta.141

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.
Files changed (34) hide show
  1. package/dist/assets/css/styles.css +1 -1
  2. package/dist/index.cjs.js +571 -99
  3. package/dist/index.esm.js +572 -100
  4. package/dist/types/components/autocomplete/AutoComplete.d.ts.map +1 -1
  5. package/dist/types/components/autocomplete/AutoComplete.types.d.ts +39 -1
  6. package/dist/types/components/autocomplete/AutoComplete.types.d.ts.map +1 -1
  7. package/dist/types/components/autocomplete/index.d.ts +1 -1
  8. package/dist/types/components/autocomplete/index.d.ts.map +1 -1
  9. package/dist/types/components/card/Card.d.ts +2 -1
  10. package/dist/types/components/card/Card.d.ts.map +1 -1
  11. package/dist/types/components/card/Card.types.d.ts +6 -5
  12. package/dist/types/components/card/Card.types.d.ts.map +1 -1
  13. package/dist/types/components/chip/Chip.d.ts.map +1 -1
  14. package/dist/types/components/chip/Chip.types.d.ts +7 -3
  15. package/dist/types/components/chip/Chip.types.d.ts.map +1 -1
  16. package/dist/types/components/drawer-filters/DrawerFilters.d.ts.map +1 -1
  17. package/dist/types/components/drawer-filters/DrawerFilters.types.d.ts +2 -0
  18. package/dist/types/components/drawer-filters/DrawerFilters.types.d.ts.map +1 -1
  19. package/dist/types/components/drawer-filters/_internal/DrawerFiltersHeader.d.ts.map +1 -1
  20. package/dist/types/components/icon/Icon.d.ts.map +1 -1
  21. package/dist/types/components/icon/Icon.types.d.ts +9 -1
  22. package/dist/types/components/icon/Icon.types.d.ts.map +1 -1
  23. package/dist/types/components/select/Select.d.ts.map +1 -1
  24. package/dist/types/components/select/Select.types.d.ts +109 -6
  25. package/dist/types/components/select/Select.types.d.ts.map +1 -1
  26. package/dist/types/components/select/index.d.ts +1 -1
  27. package/dist/types/components/select/index.d.ts.map +1 -1
  28. package/dist/types/components/tabs/Tabs.d.ts.map +1 -1
  29. package/dist/types/components/tabs/Tabs.types.d.ts +3 -2
  30. package/dist/types/components/tabs/Tabs.types.d.ts.map +1 -1
  31. package/dist/types/components/textarea/Textarea.d.ts.map +1 -1
  32. package/dist/types/components/textarea/Textarea.types.d.ts +24 -0
  33. package/dist/types/components/textarea/Textarea.types.d.ts.map +1 -1
  34. package/package.json +1 -1
package/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Card as Card$1, Breadcrumbs, ProgressBar, cn, Label, ListBoxSection, ListBoxItem, Separator, RouterProvider, I18nProvider, useOverlayState, Spinner, buttonVariants, Button as Button$1, Chip as Chip$1, CloseButton, Pagination as Pagination$1, Skeleton, Accordion, DatePicker as DatePicker$1, DateField, Description, Calendar, FieldError, DateRangePicker as DateRangePicker$1, RangeCalendar, TextField, InputGroup, RadioGroup, Radio, Tabs as Tabs$1, Dropdown as Dropdown$1, Header, ComboBox, Input as Input$1, ListBox, Tooltip as Tooltip$2, DropdownTrigger, DropdownPopover, DropdownMenu, DropdownSection as DropdownSection$1, DropdownItem, Badge as Badge$1, Popover, PopoverTrigger, PopoverContent, Avatar, Link, Switch as Switch$1, TimeField, Select as Select$1, Checkbox as Checkbox$1, Table as Table$1, TextArea, toastVariants, WarningIcon, SuccessIcon, InfoIcon, DangerIcon, Modal as Modal$1, Slider, Drawer as Drawer$1, ModalHeader as ModalHeader$1, ModalBody as ModalBody$1, ModalFooter as ModalFooter$1, DrawerHeader as DrawerHeader$1, DrawerBody as DrawerBody$1, DrawerFooter as DrawerFooter$1, Alert as Alert$1 } from '@heroui/react';
1
+ import { Card as Card$1, Breadcrumbs, ProgressBar, cn, Label, ListBoxSection, ListBoxItem, Separator, RouterProvider, I18nProvider, useOverlayState, Spinner, buttonVariants, Button as Button$1, Chip as Chip$1, CloseButton, Pagination as Pagination$1, Skeleton, Accordion, DatePicker as DatePicker$1, DateField, Description, Calendar, FieldError, DateRangePicker as DateRangePicker$1, RangeCalendar, TextField, InputGroup, RadioGroup, Radio, Tabs as Tabs$1, Dropdown as Dropdown$1, Header, ComboBox, Input as Input$1, ListBox, Tooltip as Tooltip$2, DropdownTrigger, DropdownPopover, DropdownMenu, DropdownSection as DropdownSection$1, DropdownItem, Badge as Badge$1, Popover, PopoverTrigger, PopoverContent, Avatar, Link, Switch as Switch$1, TimeField, useFilter, Autocomplete, Select as Select$1, SearchField, Checkbox as Checkbox$1, Table as Table$1, TextArea, toastVariants, WarningIcon, SuccessIcon, InfoIcon, DangerIcon, Modal as Modal$1, Slider, Drawer as Drawer$1, ModalHeader as ModalHeader$1, ModalBody as ModalBody$1, ModalFooter as ModalFooter$1, DrawerHeader as DrawerHeader$1, DrawerBody as DrawerBody$1, DrawerFooter as DrawerFooter$1, Alert as Alert$1 } from '@heroui/react';
2
2
  export { Accordion, AccordionItem, Avatar, Breadcrumbs, CardFooter, CardHeader, CheckboxGroup, CloseButton, DropdownItem, DropdownMenu, DropdownTrigger, Link, Popover, PopoverContent, PopoverTrigger, Radio, RadioGroup, Skeleton, Slider, Spinner, TableBody, TableCell, TableColumn, TableHeader, TableRow, cn } from '@heroui/react';
3
3
  import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
4
4
  import $8WNJc$react, { Children, isValidElement, Fragment, useState, useRef, useCallback, useEffect, createElement, useId, forwardRef, useMemo, cloneElement, useImperativeHandle, createContext, useContext, useLayoutEffect, memo, useReducer, version } from 'react';
@@ -759,6 +759,41 @@ const SHAPE_BY_VARIANT = {
759
759
  faded: "outline",
760
760
  // `shadow` de v2 es `solid` + sombra teñida (ver `SHADOW_CLASS`).
761
761
  shadow: "solid",
762
+ // `dot` de v2: chip neutro con un punto de color (ver `DOT_BASE_CLASS`).
763
+ dot: "dot",
764
+ };
765
+ // ── `variant="dot"` (TECH-2459, reportado por beweossmbs sobre la .140) ─────────────────────
766
+ //
767
+ // Literal de `@heroui/theme@2.4.15` (`chip.js`) y `@heroui/chip@2.2.25` (`chip.js`):
768
+ //
769
+ // variant.dot.base = "border-medium border-default text-foreground bg-transparent"
770
+ // slots.dot = "w-2 h-2 ml-1 rounded-full" + color.<c>.dot = "bg-<c>" (default: "bg-default-400")
771
+ // render = `isDot && !startContent` → `<span class={slots.dot}/>` en el hueco de `startContent`
772
+ //
773
+ // O sea: el `color` NO tiñe el chip (no hay ningún `compoundVariant` de `dot` × color), solo el
774
+ // punto; el chip queda neutro con el borde de 2px de `default`. Y el punto NO cuenta como
775
+ // `hasStartContent` (`!!avatar || !!startContent`), así que el texto conserva su sangrado izquierdo
776
+ // normal. El compound `{ isOneChar, variant: "dot" }` (base "w-auto h-7 px-1", content "px-2")
777
+ // anula el cuadrado de un carácter: aquí basta con no aplicarlo.
778
+ //
779
+ // Uso real: `shared/ui/components/page-header` de beweossmbs reenvía un `variant` de su propio tipo
780
+ // que incluye `"dot"` (hoy ningún sitio lo pasa, pero el tipo no compilaba).
781
+ const DOT_BASE_CLASS = "border-2 border-default bg-transparent text-foreground";
782
+ const DOT_SLOT_CLASS = "h-2 w-2 ml-1 shrink-0 rounded-full";
783
+ // Clases literales por color (Tailwind 4 solo genera lo que encuentra escrito). Los 5 colores fijos
784
+ // de Aurora no existían en v2: su punto toma el tono del texto que ya les da `CUSTOM_COLOR_CLASSES`.
785
+ const DOT_COLOR_CLASS = {
786
+ default: "bg-default-400",
787
+ primary: "bg-primary",
788
+ secondary: "bg-secondary",
789
+ success: "bg-success",
790
+ warning: "bg-warning",
791
+ danger: "bg-danger",
792
+ red: "bg-red-500",
793
+ purple: "bg-purple-500",
794
+ blue: "bg-blue-500",
795
+ green: "bg-green-500",
796
+ orange: "bg-orange-500",
762
797
  };
763
798
  // `variant="shadow"` de v2, literal de `@heroui/theme@2.4.15` (`dist/utils/variants.js`,
764
799
  // `colorVariants.shadow`): `shadow-lg shadow-<color>/40 bg-<color> text-<color>-foreground`
@@ -876,6 +911,7 @@ const V3_VARIANT_BY_SHAPE = {
876
911
  soft: "soft",
877
912
  transparent: "tertiary",
878
913
  outline: "tertiary",
914
+ dot: "tertiary",
879
915
  };
880
916
  /**
881
917
  * TECH-2459 · F5 · P-08d — La tabla `flat` de v2, medida contra los PNG congelados y no deducida.
@@ -983,6 +1019,13 @@ const CLOSE_ICON_SIZE_CLASS = {
983
1019
  lg: "size-5",
984
1020
  };
985
1021
  function colorStyle(color, shape) {
1022
+ if (shape === "dot") {
1023
+ // `dot`: el chip es neutro para cualquier color (el color va solo en el punto).
1024
+ return {
1025
+ "--chip-bg": "transparent",
1026
+ "--chip-fg": "var(--color-foreground)",
1027
+ };
1028
+ }
986
1029
  const t = COLOR_TOKENS[color];
987
1030
  if (shape === "solid") {
988
1031
  return { "--chip-bg": t.bg, "--chip-fg": t.fg };
@@ -1018,14 +1061,17 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
1018
1061
  const hasEndContent = Boolean(endContent) || Boolean(onClose);
1019
1062
  // `isOneChar` de v2 (`use-chip.js:58`): SOLO si el children es un string de un carácter — un
1020
1063
  // número no cuenta. El cuadrado además exige no llevar nada a los lados (ver `ONE_CHAR_CLASS`).
1021
- const isSquare = typeof children === "string" &&
1064
+ const isDot = shape === "dot";
1065
+ const isSquare = !isDot &&
1066
+ typeof children === "string" &&
1022
1067
  children.length === 1 &&
1023
1068
  !startContent &&
1024
1069
  !hasEndContent;
1025
1070
  const vars = isStandard
1026
1071
  ? colorStyle(color, shape)
1027
1072
  : undefined;
1028
- const customClass = !isStandard ? CUSTOM_COLOR_CLASSES[color] : "";
1073
+ // Con `dot` el color no tiñe el chip, tampoco el de los colores fijos.
1074
+ const customClass = isStandard || isDot ? "" : CUSTOM_COLOR_CLASSES[color];
1029
1075
  const outlineClass = shape === "outline" ? "border-2" : "";
1030
1076
  // El `dark:text-*` que v2 declaraba solo para el texto de `flat` en success/warning/danger
1031
1077
  // (P-08d). Va como clase porque `--chip-fg` es inline y no puede depender del tema; la capa
@@ -1062,7 +1108,7 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
1062
1108
  className: cn(SIZE_CLASS$6[size],
1063
1109
  // Después de `SIZE_CLASS` a propósito: el cuadrado de un carácter ENCOGE la caja
1064
1110
  // respecto al tamaño normal y tiene que ganar en tailwind-merge.
1065
- isSquare ? ONE_CHAR_CLASS[size] : "", RADIUS_CLASS$9[radius], outlineClass, shadowClass, flatDarkFgClass, customClass, isDisabled ? "opacity-50 pointer-events-none" : "", classNames?.base, className), style: style, children: [startContent, jsx("span", { className: cn("flex-1 text-inherit font-normal", startContent
1111
+ isSquare ? ONE_CHAR_CLASS[size] : "", RADIUS_CLASS$9[radius], outlineClass, isDot ? DOT_BASE_CLASS : "", shadowClass, flatDarkFgClass, customClass, isDisabled ? "opacity-50 pointer-events-none" : "", classNames?.base, className), style: style, children: [startContent, isDot && !startContent && (jsx("span", { "aria-hidden": "true", "data-slot": "dot", className: cn(DOT_SLOT_CLASS, DOT_COLOR_CLASS[color]) })), jsx("span", { className: cn("flex-1 text-inherit font-normal", startContent
1066
1112
  ? CONTENT_PADDING[size].leftWithContent
1067
1113
  : CONTENT_PADDING[size].left, hasEndContent
1068
1114
  ? CONTENT_PADDING[size].rightWithContent
@@ -1087,7 +1133,11 @@ const sizeMap = {
1087
1133
  const IconComponent = ({ icon, size = "md", className, style, color, hFlip, vFlip, flip, rotate, ...nativeProps }) => {
1088
1134
  // Un número son píxeles tal cual (p. ej. 64): la escala con nombre se queda en 32 y
1089
1135
  // `width`/`height` no son props del componente (caerían en el <span>, sin efecto).
1090
- const iconSize = typeof size === "number" ? `${size}px` : (sizeMap[size] ?? sizeMap.md);
1136
+ // Un nombre fuera de la escala (`"xs"`, el alias v2 del tipo) cae al default, como en v2
1137
+ // (`sizeMap[size] ?? sizeMap.md`, `@beweco/aurora-ui@0.6.85`): 20px.
1138
+ const iconSize = typeof size === "number"
1139
+ ? `${size}px`
1140
+ : (sizeMap[size] ?? sizeMap.md);
1091
1141
  return (jsx("span", { ...nativeProps, children: jsx(Icon, { icon: icon, width: iconSize, height: iconSize, className: className, style: style, color: color, hFlip: hFlip, vFlip: vFlip, flip: flip, rotate: rotate }) }));
1092
1142
  };
1093
1143
 
@@ -2400,6 +2450,40 @@ const PRESSABLE_CLASS = "isolate cursor-pointer text-foreground outline-none foc
2400
2450
  const PRESS_ANIMATION_CLASS = "transition-[transform,background-color] motion-reduce:transition-none active:scale-[0.97]";
2401
2451
  const HOVERABLE_CLASS = "hover:bg-content2";
2402
2452
  const DISABLED_CLASS = "opacity-50 cursor-not-allowed";
2453
+ // Controles que tienen su propia pulsación. Un clic que nace dentro de uno de ellos es SUYO, no de
2454
+ // la card: los `Button` de Aurora (react-aria) ya paran la propagación del `click` (medido en
2455
+ // jsdom con el `Button` de la librería), pero un `<button>`/`<a>` nativo o un `<input>` no.
2456
+ const CONTROL_INTERNO = 'button, a[href], input, select, textarea, summary, [role="button"], [role="link"], [role="checkbox"], [role="switch"], [role="menuitem"], [role="tab"], [role="option"]';
2457
+ function desdeControlInterno(event) {
2458
+ const target = event.target;
2459
+ if (!target || target === event.currentTarget) {
2460
+ return false;
2461
+ }
2462
+ const control = target.closest?.(CONTROL_INTERNO);
2463
+ return (!!control &&
2464
+ control !== event.currentTarget &&
2465
+ event.currentTarget.contains(control));
2466
+ }
2467
+ /**
2468
+ * Tecla → `PressEvent` con la forma del de react-aria (`pointerType: "keyboard"`, punto en el
2469
+ * centro de la card, como hace `pressEventFromClick` con un click sintético de `detail === 0`).
2470
+ */
2471
+ function pressEventFromKey(event) {
2472
+ const target = event.currentTarget;
2473
+ const rect = target.getBoundingClientRect();
2474
+ return {
2475
+ type: "press",
2476
+ pointerType: "keyboard",
2477
+ target,
2478
+ shiftKey: event.shiftKey,
2479
+ ctrlKey: event.ctrlKey,
2480
+ metaKey: event.metaKey,
2481
+ altKey: event.altKey,
2482
+ x: rect.width / 2,
2483
+ y: rect.height / 2,
2484
+ continuePropagation: () => undefined,
2485
+ };
2486
+ }
2403
2487
  /**
2404
2488
  * Card genérica siguiendo las reglas de diseño BeweOS basada en HeroUI v3.
2405
2489
  *
@@ -2422,39 +2506,83 @@ const DISABLED_CLASS = "opacity-50 cursor-not-allowed";
2422
2506
  * `isHoverable`/`isDisabled`/`fullWidth`.
2423
2507
  *
2424
2508
  * `ref` se reenvía con `forwardRef` (patrón de las fachadas de campo del repo, ver
2425
- * `Input.tsx`) y apunta al nodo raíz: el `<div>` o, con `isPressable`, el `<button>`.
2509
+ * `Input.tsx`) y apunta al nodo raíz, que es siempre un `<div>` (con `isPressable`,
2510
+ * `<div role="button">`).
2426
2511
  */
2427
- const Card = forwardRef(({ children, className, shadow = "sm", radius = "sm", padding = "md", isPressable = false, onPress, onClick, disableAnimation = false, disableRipple = false, isHoverable = false, isDisabled = false, fullWidth = false, render, ...props }, ref) => {
2512
+ const Card = forwardRef(({ children, className, shadow = "sm", radius = "sm", padding = "md", isPressable = false, onPress, onClick, onKeyDown, onKeyUp, disableAnimation = false, disableRipple = false, isHoverable = false, isDisabled = false, fullWidth = false, render, ...props }, ref) => {
2428
2513
  // Como en v2 (`@heroui/card@2.2.21`, `useCard`): `disableAnimation` apaga también la onda.
2429
2514
  const sinRipple = disableRipple || disableAnimation;
2430
2515
  const { ondas, lanzar, quitar } = useRipple();
2516
+ const pulsable = isPressable && !isDisabled;
2517
+ const press = useCallback((pressEvent) => {
2518
+ onPress?.(pressEvent);
2519
+ if (!sinRipple) {
2520
+ lanzar(pressEvent);
2521
+ }
2522
+ }, [onPress, sinRipple, lanzar]);
2431
2523
  const handleClick = useCallback((event) => {
2524
+ // Card pulsable deshabilitada: ni `onClick` de la app, como el `disabled` nativo del
2525
+ // `<button>` que era hasta la .140.
2526
+ if (isPressable && isDisabled) {
2527
+ return;
2528
+ }
2432
2529
  onClick?.(event);
2433
- if (!isPressable || isDisabled) {
2530
+ if (!pulsable || desdeControlInterno(event)) {
2434
2531
  return;
2435
2532
  }
2436
- const pressEvent = pressEventFromClick(event);
2437
- onPress?.(pressEvent);
2438
- if (!sinRipple) {
2439
- lanzar(pressEvent);
2533
+ press(pressEventFromClick(event));
2534
+ }, [onClick, isPressable, isDisabled, pulsable, press]);
2535
+ // Teclado de un `<button>` nativo, que el `<div role="button">` no trae: Enter pulsa al
2536
+ // bajar la tecla y Espacio al soltarla. `preventDefault` en ambos: Espacio haría scroll de
2537
+ // la página y Enter podría enviar un `<form>` que envuelva la card. Solo cuando el foco está
2538
+ // EN la card: una tecla sobre un botón interno es de ese botón (react-aria ya para su
2539
+ // propagación, y esta guarda cubre también a los controles nativos).
2540
+ const handleKeyDown = useCallback((event) => {
2541
+ onKeyDown?.(event);
2542
+ if (!pulsable || event.target !== event.currentTarget) {
2543
+ return;
2440
2544
  }
2441
- }, [onClick, isPressable, isDisabled, onPress, sinRipple, lanzar]);
2545
+ if (event.key === " ") {
2546
+ event.preventDefault();
2547
+ }
2548
+ else if (event.key === "Enter") {
2549
+ event.preventDefault();
2550
+ if (!event.repeat) {
2551
+ press(pressEventFromKey(event));
2552
+ }
2553
+ }
2554
+ }, [onKeyDown, pulsable, press]);
2555
+ const handleKeyUp = useCallback((event) => {
2556
+ onKeyUp?.(event);
2557
+ if (!pulsable ||
2558
+ event.target !== event.currentTarget ||
2559
+ event.key !== " ") {
2560
+ return;
2561
+ }
2562
+ event.preventDefault();
2563
+ press(pressEventFromKey(event));
2564
+ }, [onKeyUp, pulsable, press]);
2442
2565
  const combinedClassName = cn(paddingClasses[padding], RADIUS_CLASS$5[radius], SHADOW_CLASS[shadow], fullWidth ? "w-full" : "", isPressable ? PRESSABLE_CLASS : "", isPressable && !disableAnimation ? PRESS_ANIMATION_CLASS : "", isHoverable ? HOVERABLE_CLASS : "", isDisabled ? DISABLED_CLASS : "",
2443
2566
  // El `className` de la app, último: gana por fusión (ver JSDoc).
2444
2567
  className);
2445
- // v2 cambiaba el ELEMENTO a `<button>` con `isPressable` (`Component = as || (isPressable
2446
- // ? "button" : "div")`). v3 lo permite con `render` (DOMRenderProps: basta con conectar
2447
- // el `ref`, que ya viene en `domProps`), así que se conserva el `CardContext` del
2448
- // compound y los `Card.Header`/`CardBody` de dentro siguen funcionando.
2449
- const pressableRender = isPressable
2450
- ? (domProps) => (jsx("button", { ...domProps, type: "button",
2451
- // v2 (`useCard`): `{ ...buttonProps, ...focusProps, role: "button" }` y
2452
- // `tabIndex: isPressable ? 0 : -1`. En un `<button>` nativo el rol ya es
2453
- // `button` implícito (biome marca el explícito como redundante); el
2454
- // `tabIndex` se escribe igual que v2.
2455
- tabIndex: isDisabled ? -1 : 0, disabled: isDisabled }))
2456
- : render;
2457
- return (jsxs(Card$1, { ...props, ref: ref, className: combinedClassName, "data-pressable": isPressable || undefined, "data-disabled": isDisabled || undefined, onClick: isPressable || onClick ? handleClick : undefined, render: pressableRender, children: [children, isPressable && ondas.length > 0 && (jsx(CapaRipple, { ondas: ondas, onFin: quitar }))] }));
2568
+ // v2 cambiaba el ELEMENTO a `<button>` con `isPressable`, y hasta la .140 esta fachada lo
2569
+ // imitaba con el `render` de v3. Se quitó porque las apps meten botones DENTRO de la card
2570
+ // pulsable (`ChannelCard` de smbs, Linda → Compartir: «Compartir»/«Integrar»), y
2571
+ // `<button>` dentro de `<button>` es HTML inválido: React avisa en consola
2572
+ // («cannot be a descendant of <button>», medido en jsdom) y el parser del navegador lo
2573
+ // reparenta. La card se queda en el `<div>` de v3 con la semántica de botón de v2
2574
+ // (`useCard`: `role: "button"` + `tabIndex: isPressable ? 0 : -1`) y el teclado a mano
2575
+ // (ver `handleKeyDown`). El `render` de la app pasa tal cual.
2576
+ const pressableProps = isPressable
2577
+ ? {
2578
+ role: "button",
2579
+ tabIndex: isDisabled ? undefined : 0,
2580
+ "aria-disabled": isDisabled || undefined,
2581
+ onKeyDown: handleKeyDown,
2582
+ onKeyUp: handleKeyUp,
2583
+ }
2584
+ : {};
2585
+ return (jsxs(Card$1, { ...props, ...pressableProps, ref: ref, className: combinedClassName, "data-pressable": isPressable || undefined, "data-disabled": isDisabled || undefined, onClick: isPressable || onClick ? handleClick : undefined, render: render, children: [children, isPressable && ondas.length > 0 && (jsx(CapaRipple, { ondas: ondas, onFin: quitar }))] }));
2458
2586
  });
2459
2587
  Card.displayName = "Card";
2460
2588
 
@@ -3184,7 +3312,7 @@ const CLEAR_GAP_WITH_TRIGGER_UNDERLINED_CLASS = "me-9";
3184
3312
  * ⚠️ `h-4 w-4 p-0` deja el glifo de 16px ocupando la caja entera; el `[&_svg]:m-0` quita los
3185
3313
  * márgenes negativos de v3 para que el centro del glifo coincida con el centro de la caja.
3186
3314
  */
3187
- const CLEAR_BUTTON_CLASS = "h-4 w-4 min-h-0 shrink-0 rounded-full bg-transparent p-0 text-default-500 hover:bg-transparent [&_svg]:m-0";
3315
+ const CLEAR_BUTTON_CLASS$2 = "h-4 w-4 min-h-0 shrink-0 rounded-full bg-transparent p-0 text-default-500 hover:bg-transparent [&_svg]:m-0";
3188
3316
  const DEFAULT_ICON = (jsx(IconComponent, { icon: "solar:magnifer-outline", className: "text-default-400 pointer-events-none shrink-0", size: "sm", "aria-hidden": true }));
3189
3317
  /**
3190
3318
  * AuraAutocomplete genérico reutilizable, migrado al `ComboBox` compound de HeroUI v3 (el
@@ -3209,7 +3337,9 @@ const AuraAutocomplete = ({ label, id, icon, placeholder, className, children, v
3209
3337
  // limpiarlo. Se reenvían tal cual al `ComboBox`, así que para el consumidor no cambia nada:
3210
3338
  // pasar `undefined` es indistinguible de no pasarlos (react-aria decide controlado/no
3211
3339
  // controlado con `!== undefined`).
3212
- isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, isInvalid, errorMessage, listboxProps, isLoading = false, allowsCustomValue, allowsEmptyCollection, ...props }) => {
3340
+ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, isInvalid, errorMessage, listboxProps, isLoading = false, allowsCustomValue, allowsEmptyCollection, isOpen, onOpenChange, popoverProps,
3341
+ // Aceptada y descartada: ver `AuraAutocompleteProps.labelPlacement`.
3342
+ labelPlacement: _labelPlacement, scrollShadowProps, ...props }) => {
3213
3343
  const generatedId = useId();
3214
3344
  const autoId = id || generatedId;
3215
3345
  // Con `isLoading` el spinner ocupa el extremo (ver `SPINNER`): a efectos de maquetación es el
@@ -3225,6 +3355,26 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
3225
3355
  const emptyContent = listboxProps?.emptyContent ?? t.emptyContent;
3226
3356
  /** El `<input>` real, para devolverle el foco tras limpiar (ver `handleClear`). */
3227
3357
  const inputRef = useRef(null);
3358
+ /*
3359
+ * `isOpen` controlado (ver `AuraAutocompleteProps.isOpen`). La fachada sigue el estado de
3360
+ * apertura de react-aria por su `onOpenChange` y decide con él si el popover se ve: visible solo
3361
+ * si react-aria lo tiene abierto Y la app no lo ha cerrado. `ComboBox.Popover` es el `Popover`
3362
+ * de react-aria-components, que con `isOpen` propio usa ese valor en vez del estado del
3363
+ * `ComboBox` (`Popover.mjs`: `props.isOpen != null ? localState : contextState`).
3364
+ */
3365
+ const [ariaOpen, setAriaOpen] = useState(false);
3366
+ const handleOpenChange = (open, menuTrigger) => {
3367
+ setAriaOpen(open);
3368
+ onOpenChange?.(open, menuTrigger);
3369
+ };
3370
+ const handleInputChange = (value) => {
3371
+ // La lista está abierta para react-aria pero la app la cerró: react-aria no volverá a avisar
3372
+ // (para él ya está abierta), así que la fachada pide abrir como hacía v2 al teclear.
3373
+ if (isOpen === false && ariaOpen) {
3374
+ onOpenChange?.(true, "input");
3375
+ }
3376
+ onInputChange?.(value);
3377
+ };
3228
3378
  // `data-visible` de v2, traducido a montar/no montar: `!!state.selectedItem ||
3229
3379
  // state.inputValue?.length > 0` (`chunk-NJJHRP6D.mjs:273`), y apagado por `isDisabled`/
3230
3380
  // `isReadOnly` como en el cálculo de `isClearable` del hook (línea 20 del mismo fichero).
@@ -3258,7 +3408,7 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
3258
3408
  onInputChange?.("");
3259
3409
  inputRef.current?.focus();
3260
3410
  };
3261
- return (jsxs(ComboBox, { id: autoId, isDisabled: isDisabled, isReadOnly: isReadOnly, inputValue: inputValue, selectedKey: selectedKey, onInputChange: onInputChange, onSelectionChange: onSelectionChange, isInvalid: isInvalid, allowsCustomValue: allowsCustomValue, allowsEmptyCollection: allowsEmptyCollection ?? !hideEmptyContent, className: [FIELD_STACK_CLASS$2, className].filter(Boolean).join(" "), ...props, children: [label && jsx(Label, { className: LABEL_CLASS$3, children: label }), jsxs(ComboBox.InputGroup, { className: [
3411
+ return (jsxs(ComboBox, { id: autoId, isDisabled: isDisabled, isReadOnly: isReadOnly, inputValue: inputValue, selectedKey: selectedKey, onInputChange: handleInputChange, onSelectionChange: onSelectionChange, onOpenChange: handleOpenChange, isInvalid: isInvalid, allowsCustomValue: allowsCustomValue, allowsEmptyCollection: allowsEmptyCollection ?? !hideEmptyContent, className: [FIELD_STACK_CLASS$2, className].filter(Boolean).join(" "), ...props, children: [label && jsx(Label, { className: LABEL_CLASS$3, children: label }), jsxs(ComboBox.InputGroup, { className: [
3262
3412
  SIZE_CLASS$2[size],
3263
3413
  PADDING_CLASS$1[size],
3264
3414
  variant === "underlined" ? UNDERLINED_INNER_CLASS[size] : "",
@@ -3307,7 +3457,7 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
3307
3457
  * su `aria-label` traducido, y alcanzable con teclado como la ✕ de `Input`.
3308
3458
  */
3309
3459
  slot: null, "aria-label": t.clearButtonAriaLabel, className: [
3310
- CLEAR_BUTTON_CLASS,
3460
+ CLEAR_BUTTON_CLASS$2,
3311
3461
  resolvedIcon != null
3312
3462
  ? CLEAR_GAP_WITH_ICON_CLASS
3313
3463
  : variant === "underlined"
@@ -3321,7 +3471,13 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
3321
3471
  isDisabled ? ICON_DISABLED_CLASS : "",
3322
3472
  ]
3323
3473
  .filter(Boolean)
3324
- .join(" "), children: resolvedIcon })), jsx(ComboBox.Trigger, { className: resolvedIcon != null ? "hidden" : undefined })] }), isInvalid && errorMessage != null && errorMessage !== false && (jsx(FieldError, { className: ERROR_MESSAGE_CLASS, children: errorMessage })), jsx(ComboBox.Popover, { children: jsx(ListBox, { autoFocus: "first", className: OPCIONES_CLASS, renderEmptyState: hideEmptyContent
3474
+ .join(" "), children: resolvedIcon })), jsx(ComboBox.Trigger, { className: resolvedIcon != null ? "hidden" : undefined })] }), isInvalid && errorMessage != null && errorMessage !== false && (jsx(FieldError, { className: ERROR_MESSAGE_CLASS, children: errorMessage })), jsx(ComboBox.Popover, { ...popoverProps, isOpen: isOpen === undefined ? undefined : isOpen && ariaOpen, children: jsx(ListBox, { autoFocus: "first",
3475
+ // `scrollShadowProps` de v2 al contenedor con scroll (el `ListBox`): la clase
3476
+ // de la app va al final y gana por `cn`, y `overflow-y-auto` para que un
3477
+ // `max-h-*` recorte desplazando y no desbordando.
3478
+ className: scrollShadowProps?.className
3479
+ ? cn(OPCIONES_CLASS, "overflow-y-auto", scrollShadowProps.className)
3480
+ : OPCIONES_CLASS, style: scrollShadowProps?.style, renderEmptyState: hideEmptyContent
3325
3481
  ? undefined
3326
3482
  : () => jsx("div", { className: EMPTY_CONTENT_CLASS$1, children: emptyContent }), children: withV2ItemContent(
3327
3483
  // El normalizador de `utils/` está tipado solo para `ReactNode`; con una
@@ -9662,7 +9818,98 @@ const SPINNER_CLASS = "pointer-events-none absolute inset-y-0 end-3 my-auto text
9662
9818
  */
9663
9819
  const DEFAULT_TRANSLATIONS$8 = {
9664
9820
  emptyContent: "No hay opciones",
9821
+ searchPlaceholder: "Buscar…",
9822
+ // Mismo literal que la lista vacía de `AuraAutocomplete`: es el mismo caso (un filtro por texto
9823
+ // que no encuentra nada), así que las dos fachadas lo dicen igual.
9824
+ noResults: "No se encontraron resultados",
9825
+ // Mismo literal que la ✕ de `Input` y de `AuraAutocomplete`.
9826
+ clearButtonAriaLabel: "Limpiar",
9665
9827
  };
9828
+ /*
9829
+ * EL BUSCADOR Y LA ✕ (TECH-2459, hallazgo beweossmbs 23-sep): `isSearchable` e `isClearable`.
9830
+ *
9831
+ * Con cualquiera de los dos la fachada cambia de RAÍZ: monta el compound `Autocomplete` de v3 en
9832
+ * vez de `Select`. No es un compound distinto por dentro —`AutocompleteRoot` ES el `Select` de
9833
+ * react-aria (`@heroui/react/dist/components/autocomplete/autocomplete.js:50`, `jsx(Select, …)`),
9834
+ * así que `value`/`onChange`/`selectionMode`/`disabledKeys` y la colección funcionan igual y la
9835
+ * traducción de la API v2 de abajo no cambia ni una línea—, pero su disparador sí:
9836
+ *
9837
+ * - `Autocomplete.Trigger` es un `Group` (un `<div>`), no un `<button>`. Es lo que permite meter la
9838
+ * ✕ DENTRO del campo sin anidar dos botones (trampa nº 4 de `auraui-components`). El botón que
9839
+ * recibe el foco es el del `Autocomplete.Indicator`, que consume el `ButtonContext` del Select
9840
+ * (`autocomplete.js`, `jsx(Button, { children: indicator })`): Tab llega al chevron y desde ahí
9841
+ * las flechas/Enter abren la lista, igual que en el `Select` de siempre.
9842
+ * - `Autocomplete.Filter` (el `Autocomplete` de react-aria) conecta un `SearchField` con el
9843
+ * `ListBox`: el texto filtra la colección y las flechas mueven el foco virtual de la lista sin
9844
+ * salir del buscador. Es la receta oficial «Migrating to v3 Autocomplete (searchable select)»
9845
+ * de la guía de migración, con `useFilter({ sensitivity: "base" })`: sin distinguir mayúsculas
9846
+ * ni acentos («cancun» encuentra «Cancún»).
9847
+ *
9848
+ * Sin ninguna de las dos props la fachada sigue montando `Select`, exactamente como antes: los
9849
+ * 107 `Select` de beweossmbs que no las pasan no cambian ni un nodo.
9850
+ *
9851
+ * Por qué la ✕ va también por aquí y no sobre el `Select`: el disparador de `Select` es un
9852
+ * `<button>` (no admite otro dentro) y la fachada no alcanza el estado de react-aria para vaciar la
9853
+ * selección de un campo no controlado (`SelectStateContext` no lo reexporta `@heroui/react`, e
9854
+ * importarlo de `react-aria-components` metería una SEGUNDA copia del contexto en el bundle, que no
9855
+ * casaría con la de HeroUI). `Autocomplete.ClearButton` sí lo alcanza: vacía la selección con
9856
+ * `state.selectionManager.setSelectedKeys(new Set())` y llama a `onClear`.
9857
+ */
9858
+ /** Hueco del valor cuando la ✕ va a su derecha: el hueco del chevron lo reserva la ✕. */
9859
+ const VALUE_ROOM_CLEARABLE_CLASS = "me-1 min-w-0 truncate";
9860
+ /*
9861
+ * La ✕. `.autocomplete__clear-button` de v3 ya la pinta como v2 (glifo sin caja, `bg-transparent`,
9862
+ * gris `text-muted`) y la esconde con `opacity-0` cuando no hay nada elegido. v2 no la montaba sin
9863
+ * selección (`@heroui/select@2.4.33`, `chunk-BJWIL32D.mjs:64`: `isClearable &&
9864
+ * state.selectedItems?.length`), así que aquí se retira del flujo en ese estado en vez de dejar un
9865
+ * hueco invisible de 20px.
9866
+ *
9867
+ * `me-5`: el chevron es `absolute end-3` (12px del borde, 16px de ancho) y la ✕ va en flujo, que
9868
+ * acaba en el relleno del trigger (`px-3`, 12px). Sin margen caería ENCIMA del chevron; con 20px su
9869
+ * canto derecho queda a 4px del izquierdo del chevron.
9870
+ */
9871
+ const CLEAR_BUTTON_CLASS$1 = "z-10 me-5 data-[empty=true]:hidden";
9872
+ /*
9873
+ * EL FOCO DEL TECLADO (hallazgo del coordinador con Playwright, 23-sep). En la raíz `Autocomplete`
9874
+ * el único elemento tabulable es el botón del chevron —el `Button` de react-aria que monta
9875
+ * `Autocomplete.Indicator`, sin clase propia ni forma de pasarle una—, y en el flujo mide **0×0**:
9876
+ * su único hijo es el SVG, que es `absolute`. El foco caía en una caja vacía (el borde del disparador
9877
+ * sí cambiaba, por `focus-within`, pero el elemento enfocado no tenía caja para un lector de
9878
+ * pantalla, un magnificador o un anillo propio).
9879
+ *
9880
+ * Se estira ese botón sobre el disparador entero (`absolute inset-0`, mismo radio): el elemento
9881
+ * enfocado pasa a medir lo mismo que el disparador, como el `<button>` del `Select` normal, y el
9882
+ * SVG sigue en su sitio porque su contenedor posicionado cubre la misma caja. Pulsar en cualquier
9883
+ * punto sigue abriendo (el `Group` ya abría al pulsar, y el botón ya estaba DENTRO del grupo).
9884
+ * La ✕ va por encima (`z-10` en `CLEAR_BUTTON_CLASS`) para seguir recibiendo su clic.
9885
+ *
9886
+ * Se apunta por estructura porque es la única vía: `button:not([data-slot])` es ese botón, ya que
9887
+ * la ✕ lleva `data-slot="autocomplete-clear-button"`.
9888
+ */
9889
+ const INDICATOR_BUTTON_OVERLAY_CLASS = [
9890
+ "[&>button:not([data-slot])]:absolute",
9891
+ "[&>button:not([data-slot])]:inset-0",
9892
+ "[&>button:not([data-slot])]:rounded-[inherit]",
9893
+ "[&>button:not([data-slot])]:outline-none",
9894
+ "[&>button:not([data-slot])]:cursor-[inherit]",
9895
+ ].join(" ");
9896
+ /*
9897
+ * El foco en el disparador `Group`. El `FOCUS_BORDER_CLASS` del Select casa `aria-expanded` y
9898
+ * `:focus` del `<button>`; aquí el disparador es un `<div>` que no lleva ninguno de los dos: el foco
9899
+ * lo tiene el botón del chevron, DENTRO (`focus-within`), y el estado abierto lo escribe v3 en la
9900
+ * RAÍZ (`data-open="true"` en `div.autocomplete`), así que va por un `group` con nombre.
9901
+ */
9902
+ const FOCUS_BORDER_GROUP_CLASS = "focus-within:border-primary-500! group-data-[open=true]/aura-select:border-primary-500!";
9903
+ /*
9904
+ * El buscador del desplegable. `.autocomplete__popover` de v3 ya lo coloca (`[data-slot=
9905
+ * "search-field"] { px-3 py-1 }`, `pt-2` en el popover, `overflow-hidden` + lista con su propio
9906
+ * scroll, de modo que el buscador no se va al desplazar la lista). Solo se retira el anillo de
9907
+ * foco de v3 (`status-focused-field` → `ring-2`), por lo mismo que en todos los campos de la
9908
+ * librería (`FIELD_FOCUS_CLASS`): el foco lo marca el borde, en primario.
9909
+ */
9910
+ const SEARCH_GROUP_CLASS = "w-full ring-0 ring-offset-0 outline-0 focus-within:border-primary-500";
9911
+ /** El `ListBox` es el contenedor que se desplaza cuando el consumidor le fija alto. */
9912
+ const LISTBOX_SCROLL_CLASS = "overflow-y-auto";
9666
9913
  // Medidas del estado vacío. Se toman las del ITEM de v3 (`@heroui/styles`,
9667
9914
  // `dist/components/list-box-item.css:6`: `min-h-9 … px-2 py-1.5`) y no las del slot `emptyContent`
9668
9915
  // de v2, y es una decisión medida: el literal de v2 era
@@ -9934,7 +10181,9 @@ size = "md", variant = "bordered", radius = "md",
9934
10181
  //
9935
10182
  // Por eso este componente sigue sin `SelectTranslations`: la corrección consiste precisamente en
9936
10183
  // que la librería no aporte NINGÚN texto propio, no en traducir el que había.
9937
- placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, onSelectionChange, disabledKeys, isInvalid = false, isRequired = false, isDisabled = false,
10184
+ placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, onSelectionChange, disabledKeys, isInvalid = false, isRequired = false,
10185
+ // Alias nativo de `isRequired` (ver `SelectProps.required`).
10186
+ required = false, isDisabled = false,
9938
10187
  // `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
9939
10188
  // (verificado en el paquete v2 compilado, `dist/components/input.js`:
9940
10189
  // `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
@@ -9944,14 +10193,35 @@ placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, o
9944
10193
  // columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
9945
10194
  fullWidth = true, isLoading = false,
9946
10195
  // Aceptada y descartada: ver `SelectProps.isVirtualized`.
9947
- isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = false, renderValue, startContent, listboxProps, isOpen, defaultOpen, onOpenChange, className, classNames, translations = {}, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-details": ariaDetails, }) => {
10196
+ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = false, renderValue, startContent, listboxProps, onChange,
10197
+ // Aceptada y descartada: ver `SelectProps.labelPlacement`.
10198
+ labelPlacement: _labelPlacement, scrollShadowProps, items, isSearchable = false, searchPlaceholder, isClearable = false, onClear, closeOnSelect, isOpen, defaultOpen, onOpenChange, className, classNames, translations = {}, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-details": ariaDetails, }) => {
9948
10199
  const generatedId = useId();
9949
10200
  const selectId = id || generatedId;
9950
- const requiredField = isRequired;
10201
+ const requiredField = isRequired || required;
9951
10202
  const t = { ...DEFAULT_TRANSLATIONS$8, ...translations };
10203
+ // Raíz `Autocomplete` (buscador y/o ✕) o `Select` de siempre: ver el bloque «EL BUSCADOR Y LA ✕».
10204
+ const usesAutocompleteShell = isSearchable || isClearable;
10205
+ // Filtro de react-aria sin mayúsculas ni acentos. El hook va fuera de la condición (reglas de
10206
+ // hooks); sin `isSearchable` no se usa.
10207
+ const { contains } = useFilter({ sensitivity: "base" });
10208
+ // Texto del buscador, en la fachada para saber qué mensaje poner en la lista vacía y para
10209
+ // vaciarlo al cerrar (abrir de nuevo = lista completa, que es lo que hacía el buscador manual
10210
+ // de `timezone-search-select` con su `onOpenChange`).
10211
+ const [searchText, setSearchText] = useState("");
9952
10212
  // v2 pintaba `listboxProps.emptyContent` en el hueco de la lista vacía; gana a la traducción,
9953
- // que es el default de la fachada (el de v2 era el literal inglés "No items.").
9954
- const emptyContent = listboxProps?.emptyContent ?? t.emptyContent;
10213
+ // que es el default de la fachada (el de v2 era el literal inglés "No items."). Con el buscador
10214
+ // escrito, la lista vacía significa «sin resultados», no «sin opciones».
10215
+ const emptyContent = isSearchable && searchText.trim() !== ""
10216
+ ? t.noResults
10217
+ : (listboxProps?.emptyContent ?? t.emptyContent);
10218
+ const resolvedSearchPlaceholder = searchPlaceholder ?? t.searchPlaceholder;
10219
+ const handleOpenChange = (open) => {
10220
+ if (!open) {
10221
+ setSearchText("");
10222
+ }
10223
+ onOpenChange?.(open);
10224
+ };
9955
10225
  // Estado propio SOLO para `multiple` + `disallowEmptySelection` sin controlar: para rechazar el
9956
10226
  // cambio que vaciaría la selección, la fachada tiene que ser dueña del valor. En los demás casos
9957
10227
  // no se lee (el hook va fuera de la condición por las reglas de hooks).
@@ -9963,55 +10233,97 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
9963
10233
  // En v2 el estado inválido MANDABA también con el campo enfocado
9964
10234
  // (`group-data-[focus=true]:!border-danger`), así que el borde de foco no se aplica cuando
9965
10235
  // el campo está en error: se queda el `danger`.
9966
- isInvalid ? INVALID_VARIANT_CLASS[variant] : FOCUS_BORDER_CLASS, FIELD_FOCUS_CLASS$1, FIELD_SHADOW_CLASS$1,
10236
+ isInvalid
10237
+ ? INVALID_VARIANT_CLASS[variant]
10238
+ : usesAutocompleteShell
10239
+ ? FOCUS_BORDER_GROUP_CLASS
10240
+ : FOCUS_BORDER_CLASS, FIELD_FOCUS_CLASS$1, FIELD_SHADOW_CLASS$1, usesAutocompleteShell ? INDICATOR_BUTTON_OVERLAY_CLASS : "",
9967
10241
  // Incondicional, como el slot `trigger` de v2 (`"relative px-3 gap-3 w-full inline-flex …"`,
9968
10242
  // sin variante que lo condicione): el disparador llena siempre su raíz, y es la raíz la que
9969
10243
  // responde a `fullWidth` y al ancho que pase el consumidor. v3 hace lo mismo por su cuenta
9970
10244
  // con `.select__trigger--full-width`, pero esa regla vive en la capa `components` y la pisa
9971
10245
  // cualquier utilidad.
9972
10246
  "w-full", classNames?.trigger);
10247
+ // `id`/`textValue` desde la `key` de React: ver `utils/collection-items.ts` — sin esto un select
10248
+ // múltiple no pinta lo elegido y uno controlado arranca vacío. `withV2ItemContent` traduce
10249
+ // `description`/`startContent`/`endContent`/`classNames` del item y `title`/`showDivider` de la
10250
+ // sección a la anatomía de v3 (ver `_internal/v2-collection.tsx` para el orden), y
10251
+ // `withItemIndicator` monta el checkmark del seleccionado, que en v3 es un subcomponente aparte
10252
+ // (ver `_internal/with-item-indicator.tsx`, y ahí por qué va en este orden).
10253
+ const decorate = (nodes) => withItemIndicator(withV2ItemContent(withCollectionItemIds(nodes)));
10254
+ // Colección dinámica (`items` + función, la forma de `Profile.tsx`). La función se envuelve para
10255
+ // decorar lo que devuelva, y el paso de `withCollectionItemIds` importa aquí MÁS que en la forma
10256
+ // estática: react-aria toma la clave de una colección dinámica de `rendered.props.id ??
10257
+ // item.key ?? item.id`, nunca de la `key` de React. Sin reponer el `id` desde la `key`, las
10258
+ // opciones de `Profile.tsx` (objetos `{ value: "es", id: 1 }` con `<SelectItem key={value}>`)
10259
+ // se llamarían `1`/`2` y ni `defaultSelectedKeys={["es"]}` casaría ni `onSelectionChange`
10260
+ // devolvería la clave de la app.
10261
+ const collection = typeof children === "function"
10262
+ ? (item) => decorate(children(item))
10263
+ : decorate(children);
10264
+ const listBox = (jsx(ListBox
10265
+ // v2 `items`: react-aria recorre la colección y llama a la función de `children`.
10266
+ , {
10267
+ // v2 `items`: react-aria recorre la colección y llama a la función de `children`.
10268
+ items: items,
10269
+ // v2: `classNames.listboxWrapper` y `scrollShadowProps.className` iban al mismo slot
10270
+ // (`cn(classNames.listboxWrapper, scrollShadowProps.className)`, `chunk-YFRCT47S.mjs:
10271
+ // 321-331`), en ese orden.
10272
+ className: classNames?.listboxWrapper || scrollShadowProps?.className
10273
+ ? cn(LISTBOX_SCROLL_CLASS, classNames?.listboxWrapper, scrollShadowProps?.className)
10274
+ : undefined, style: scrollShadowProps?.style,
10275
+ // El hueco que v2 llenaba con su `emptyContent` (ver el bloque de
10276
+ // `DEFAULT_TRANSLATIONS`). `renderEmptyState` es la vía de v3: el `ListBox` de
10277
+ // react-aria solo pinta algo con la colección vacía si se le pasa, y envuelve el
10278
+ // resultado en un `<div role="option" style="display:contents">`
10279
+ // (`react-aria-components/dist/private/ListBox.mjs:222-228`), o sea que la caja que se
10280
+ // ve es la nuestra.
10281
+ renderEmptyState: () => (jsx("div", { className: EMPTY_CONTENT_CLASS, children: emptyContent })), children: collection }));
10282
+ // Las piezas del disparador son las mismas en las dos raíces; solo cambia de qué compound salen
10283
+ // (ver el bloque «EL BUSCADOR Y LA ✕»). `Autocomplete.Value` y `Select.Value` son el mismo
10284
+ // `SelectValue` de react-aria con otra clase base, así que reciben las mismas props.
10285
+ const ValuePart = usesAutocompleteShell
10286
+ ? Autocomplete.Value
10287
+ : Select$1.Value;
10288
+ const triggerContent = (jsxs(Fragment$1, { children: [startContent != null && startContent !== false && (jsx("span", { "data-slot": "start-content", className: START_CONTENT_CLASS, children: startContent })), jsx(ValuePart, { className: cn(SIZE_TEXT_CLASS$1[size], isMultiline
10289
+ ? MULTILINE_VALUE_ROOM_CLASS
10290
+ : isClearable
10291
+ ? VALUE_ROOM_CLEARABLE_CLASS
10292
+ : VALUE_ROOM_CLASS, HIDE_ITEM_EXTRAS_CLASS, isInvalid ? INVALID_TEXT_CLASS$1 : ""), children: renderValue
10293
+ ? // `renderValue` de v2 (`chunk-BJWIL32D.mjs`): sin selección, el placeholder;
10294
+ // con selección, la función con los items en la forma de v2. El nodo de la
10295
+ // colección de v3 trae los mismos campos que el de v2, así que el mapeo es
10296
+ // 1:1. `defaultChildren` es aquí el placeholder (o `""`, ver arriba).
10297
+ ({ state, isPlaceholder, defaultChildren }) => {
10298
+ if (isPlaceholder) {
10299
+ return defaultChildren;
10300
+ }
10301
+ const items = state.selectedItems.map((item) => ({
10302
+ key: item.key,
10303
+ data: item.value,
10304
+ type: item.type,
10305
+ props: item.props,
10306
+ textValue: item.textValue,
10307
+ rendered: item.rendered,
10308
+ "aria-label": item["aria-label"],
10309
+ }));
10310
+ return renderValue(items);
10311
+ }
10312
+ : undefined }), isClearable && (jsx(Autocomplete.ClearButton, { "aria-label": t.clearButtonAriaLabel, className: CLEAR_BUTTON_CLASS$1 })), usesAutocompleteShell ? (
10313
+ /* En el `Autocomplete` el chevron es el BOTÓN que recibe el foco (ver el bloque
10314
+ «EL BUSCADOR Y LA ✕»), así que el spinner va DENTRO de él en vez de sustituirlo:
10315
+ sin el botón el campo no se alcanzaría con el teclado. `Autocomplete.Indicator` clona
10316
+ su hijo con la clase del indicador (`absolute inset-y-0 end-2 my-auto`), así que el
10317
+ spinner cae en el sitio del chevron sin clase propia. */
10318
+ jsx(Autocomplete.Indicator, { className: "end-3", children: isLoading ? (jsx(Spinner, { "aria-hidden": true, "data-slot": "select-spinner", size: "sm", color: "current" })) : undefined })) : isLoading ? (jsx(Spinner, { "aria-hidden": true, "data-slot": "select-spinner", size: "sm", color: "current", className: SPINNER_CLASS })) : (
10319
+ /* `.select__indicator` de v3 va a `end-2` (8px del borde interior); el
10320
+ `selectorIcon` de v2 iba a `end-3` (12px). Medido en los PNG: la tinta del chevron
10321
+ acaba a 17px del borde derecho en la referencia y a 13px con el valor de v3 — 4px
10322
+ de más. */
10323
+ jsx(Select$1.Indicator, { className: "end-3" }))] }));
9973
10324
  const inner = (jsxs(Fragment$1, { children: [label && (jsx(Label, { className: cn(LABEL_CLASS$1, hasHelper
9974
10325
  ? LABEL_OFFSET_HELPER_CLASS[size]
9975
- : LABEL_OFFSET_CLASS[size], isInvalid ? INVALID_LABEL_CLASS$1 : "", classNames?.label), children: label })), jsxs(Select$1.Trigger, { className: triggerClassName, children: [startContent != null && startContent !== false && (jsx("span", { "data-slot": "start-content", className: START_CONTENT_CLASS, children: startContent })), jsx(Select$1.Value, { className: cn(SIZE_TEXT_CLASS$1[size], isMultiline ? MULTILINE_VALUE_ROOM_CLASS : VALUE_ROOM_CLASS, HIDE_ITEM_EXTRAS_CLASS, isInvalid ? INVALID_TEXT_CLASS$1 : ""), children: renderValue
9976
- ? // `renderValue` de v2 (`chunk-BJWIL32D.mjs`): sin selección, el placeholder;
9977
- // con selección, la función con los items en la forma de v2. El nodo de la
9978
- // colección de v3 trae los mismos campos que el de v2, así que el mapeo es
9979
- // 1:1. `defaultChildren` es aquí el placeholder (o `""`, ver arriba).
9980
- ({ state, isPlaceholder, defaultChildren }) => {
9981
- if (isPlaceholder) {
9982
- return defaultChildren;
9983
- }
9984
- const items = state.selectedItems.map((item) => ({
9985
- key: item.key,
9986
- data: item.value,
9987
- type: item.type,
9988
- props: item.props,
9989
- textValue: item.textValue,
9990
- rendered: item.rendered,
9991
- "aria-label": item["aria-label"],
9992
- }));
9993
- return renderValue(items);
9994
- }
9995
- : undefined }), isLoading ? (jsx(Spinner, { "aria-hidden": true, "data-slot": "select-spinner", size: "sm", color: "current", className: SPINNER_CLASS })) : (
9996
- /* `.select__indicator` de v3 va a `end-2` (8px del borde interior); el
9997
- `selectorIcon` de v2 iba a `end-3` (12px). Medido en los PNG: la tinta del chevron
9998
- acaba a 17px del borde derecho en la referencia y a 13px con el valor de v3 — 4px
9999
- de más. */
10000
- jsx(Select$1.Indicator, { className: "end-3" }))] }), description && !isInvalid && (jsx(Description, { className: `${HELPER_CLASS$1} text-left`, children: description })), jsx(Select$1.Popover, { className: classNames?.popoverContent, children: jsx(ListBox
10001
- // El hueco que v2 llenaba con su `emptyContent` (ver el bloque de
10002
- // `DEFAULT_TRANSLATIONS`). `renderEmptyState` es la vía de v3: el `ListBox` de
10003
- // react-aria solo pinta algo con la colección vacía si se le pasa, y envuelve
10004
- // el resultado en un `<div role="option" style="display:contents">`
10005
- // (`react-aria-components/dist/private/ListBox.mjs:222-228`), o sea que la caja
10006
- // que se ve es la nuestra.
10007
- , {
10008
- // El hueco que v2 llenaba con su `emptyContent` (ver el bloque de
10009
- // `DEFAULT_TRANSLATIONS`). `renderEmptyState` es la vía de v3: el `ListBox` de
10010
- // react-aria solo pinta algo con la colección vacía si se le pasa, y envuelve
10011
- // el resultado en un `<div role="option" style="display:contents">`
10012
- // (`react-aria-components/dist/private/ListBox.mjs:222-228`), o sea que la caja
10013
- // que se ve es la nuestra.
10014
- renderEmptyState: () => (jsx("div", { className: EMPTY_CONTENT_CLASS, children: emptyContent })), children: withItemIndicator(withV2ItemContent(withCollectionItemIds(children))) }) }), isInvalid && errorMessage && (jsx(FieldError, { className: `${HELPER_CLASS$1} text-left`, children: errorMessage }))] }));
10326
+ : LABEL_OFFSET_CLASS[size], isInvalid ? INVALID_LABEL_CLASS$1 : "", classNames?.label), children: label })), usesAutocompleteShell ? (jsx(Autocomplete.Trigger, { className: triggerClassName, children: triggerContent })) : (jsx(Select$1.Trigger, { className: triggerClassName, children: triggerContent })), description && !isInvalid && (jsx(Description, { className: `${HELPER_CLASS$1} text-left`, children: description })), usesAutocompleteShell ? (jsx(Autocomplete.Popover, { className: classNames?.popoverContent, children: isSearchable ? (jsxs(Autocomplete.Filter, { filter: contains, inputValue: searchText, onInputChange: setSearchText, children: [jsx(SearchField, { autoFocus: true, "aria-label": resolvedSearchPlaceholder, fullWidth: true, children: jsxs(SearchField.Group, { className: SEARCH_GROUP_CLASS, children: [jsx(SearchField.SearchIcon, {}), jsx(SearchField.Input, { placeholder: resolvedSearchPlaceholder })] }) }), listBox] })) : (listBox) })) : (jsx(Select$1.Popover, { className: classNames?.popoverContent, children: listBox })), isInvalid && errorMessage && (jsx(FieldError, { className: `${HELPER_CLASS$1} text-left`, children: errorMessage }))] }));
10015
10327
  const commonProps = {
10016
10328
  id: selectId,
10017
10329
  name,
@@ -10037,7 +10349,14 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
10037
10349
  isDisabled,
10038
10350
  isOpen,
10039
10351
  defaultOpen,
10040
- onOpenChange,
10352
+ onOpenChange: handleOpenChange,
10353
+ // v2 `closeOnSelect` → `shouldCloseOnSelect` de react-stately, que ya trae el mismo default
10354
+ // por modo (`useSelectState.mjs:23`: cierra en `single`, se queda abierto en `multiple`).
10355
+ // `undefined` deja ese default intacto.
10356
+ ...(closeOnSelect === undefined
10357
+ ? {}
10358
+ : { shouldCloseOnSelect: closeOnSelect }),
10359
+ onClear,
10041
10360
  fullWidth,
10042
10361
  // `cn`, no una concatenación: el `className` del consumidor tiene que GANAR a las clases
10043
10362
  // de la fachada, y con una cadena suelta llegan las dos al DOM y decide el orden en que
@@ -10050,7 +10369,13 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
10050
10369
  className: cn(FIELD_STACK_CLASS$1[size],
10051
10370
  // `w-full` y `min-w-40` son las dos ramas EXCLUYENTES de `fullWidth` en el `tv` de v2
10052
10371
  // (ver `ROOT_MIN_WIDTH_CLASS`).
10053
- fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS, className),
10372
+ fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS,
10373
+ // Grupo con nombre para el borde de foco del disparador `Group` (ver
10374
+ // `FOCUS_BORDER_GROUP_CLASS`). Sin la raíz `Autocomplete` no hace falta.
10375
+ usesAutocompleteShell ? "group/aura-select" : "",
10376
+ // v2 `classNames.base`: la raíz, igual que `className`, y por eso delante de él (en v2
10377
+ // los dos acababan en `slots.base({ class: cn(classNames?.base, className) })`).
10378
+ classNames?.base, className),
10054
10379
  };
10055
10380
  if (selectionMode === "multiple") {
10056
10381
  // `disallowEmptySelection` en `multiple`: react-stately lo fuerza a `false` en este modo
@@ -10058,7 +10383,12 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
10058
10383
  // la prop), así que la guarda la pone la fachada: el cambio que dejaría la selección vacía
10059
10384
  // no se propaga. Controlado basta con no avisar; sin controlar, la fachada lleva el valor.
10060
10385
  const ownsValue = disallowEmptySelection && selectedKeys === undefined;
10061
- return (jsx(Select$1, { ...commonProps, selectionMode: "multiple", value: ownsValue ? ownMultiple : toArray(selectedKeys), defaultValue: ownsValue ? undefined : toArray(defaultSelectedKeys), onChange: (keys) => {
10386
+ const multipleProps = {
10387
+ ...commonProps,
10388
+ selectionMode: "multiple",
10389
+ value: ownsValue ? ownMultiple : toArray(selectedKeys),
10390
+ defaultValue: ownsValue ? undefined : toArray(defaultSelectedKeys),
10391
+ onChange: (keys) => {
10062
10392
  if (disallowEmptySelection && keys.length === 0) {
10063
10393
  return;
10064
10394
  }
@@ -10066,7 +10396,10 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
10066
10396
  setOwnMultiple(keys);
10067
10397
  }
10068
10398
  onSelectionChange?.(new Set(keys));
10069
- }, children: inner }));
10399
+ onChange?.({ target: { name, value: keys.join(",") } });
10400
+ },
10401
+ };
10402
+ return usesAutocompleteShell ? (jsx(Autocomplete, { ...multipleProps, children: inner })) : (jsx(Select$1, { ...multipleProps, children: inner }));
10070
10403
  }
10071
10404
  // `Array.from` cubre las tres formas de `SelectSelection` sin ramas nuevas: `"all"` sigue
10072
10405
  // cayendo a `null` (v3 no admite "todo" en selección única) y un array se lee igual que un
@@ -10081,7 +10414,17 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
10081
10414
  : defaultSelectedKeys === "all"
10082
10415
  ? null
10083
10416
  : (Array.from(defaultSelectedKeys)[0] ?? null);
10084
- return (jsx(Select$1, { ...commonProps, value: singleValue, defaultValue: singleDefault, onChange: (key) => onSelectionChange?.(key == null ? new Set() : new Set([key])), children: inner }));
10417
+ const singleProps = {
10418
+ ...commonProps,
10419
+ value: singleValue,
10420
+ defaultValue: singleDefault,
10421
+ onChange: (key) => {
10422
+ onSelectionChange?.(key == null ? new Set() : new Set([key]));
10423
+ // v2 llamaba a `onChange` después de `onSelectionChange` (ver `SelectChangeEvent`).
10424
+ onChange?.({ target: { name, value: key == null ? "" : String(key) } });
10425
+ },
10426
+ };
10427
+ return usesAutocompleteShell ? (jsx(Autocomplete, { ...singleProps, children: inner })) : (jsx(Select$1, { ...singleProps, children: inner }));
10085
10428
  };
10086
10429
 
10087
10430
  const defaultTranslations$4 = {
@@ -11122,6 +11465,28 @@ const VARIANT_CLASS = {
11122
11465
  // placeholder en el gris `default-500` (`113,113,122`) pese a ser un campo inválido.
11123
11466
  const INVALID_LABEL_CLASS = "text-danger";
11124
11467
  const INVALID_TEXT_CLASS = "text-danger";
11468
+ // ── `isClearable` / `onClear` / `classNames.clearButton` (API v2, repuesta) ──────────────────
11469
+ //
11470
+ // El `Textarea` de v2 ES el `Input` con `isMultiline` (mismo `useInput`), así que heredaba su botón
11471
+ // de limpiar: `isClearable = !!onClear || originalProps.isClearable` y `handleClear` pone el valor
11472
+ // a "" (lo que dispara `onValueChange("")`), llama a `onClear` y devuelve el foco
11473
+ // (`@heroui/input@2.4.33`, `dist/chunk-KQW2TNLT.mjs`); Escape con valor también limpia. Uso real:
11474
+ // `campaigns/**/AudienceReachStep.tsx` en beweossmbs (`isClearable` + `onClear` +
11475
+ // `classNames.clearButton`), el único de los 33 `Textarea` de la app que lo pide.
11476
+ //
11477
+ // Posición, literal del `tv` de v2 (`@heroui/theme@2.4.15`, `dist/components/input.js`):
11478
+ // · `isMultiline.clearButton` → "absolute top-2 right-2 rtl:right-auto rtl:left-2 z-10": la X va
11479
+ // ARRIBA a la derecha, no centrada como en el campo de una línea (aquí `end-2` = right/left
11480
+ // según la dirección, lo mismo que el par `right-2 rtl:left-2`);
11481
+ // · `isClearable.input` → "pe-6": el texto deja sitio a la X aunque todavía no se vea,
11482
+ // para que no salte al aparecer.
11483
+ // Lo relativo en v2 era el `inputWrapper`, que aquí no existe (el `<textarea>` es la caja), así que
11484
+ // se añade un envoltorio `relative` SOLO cuando el campo es limpiable: sin la prop el DOM no cambia.
11485
+ // El glifo es el `CloseButton` de v3, el mismo que ya usa la fachada `Input` (§9.1: verse como v3),
11486
+ // y `classNames.clearButton` va el último para que las clases del consumidor ganen.
11487
+ const CLEAR_WRAPPER_CLASS = "relative w-full";
11488
+ const CLEAR_BUTTON_CLASS = "absolute top-2 end-2 z-10 h-4 w-4 min-h-0 p-0.5";
11489
+ const CLEARABLE_FIELD_CLASS = "pe-6";
11125
11490
  // ── El `className` del consumidor: FUSIONADO y en la RAÍZ ────────────────────────────────────
11126
11491
  //
11127
11492
  // Mismo defecto doble que en `Input.tsx` (ver ahí el razonamiento completo y las medidas), porque
@@ -11164,7 +11529,10 @@ minRows, rows,
11164
11529
  // llegue al DOM (antes viajaba en `...rest` y React avisaba de un atributo desconocido).
11165
11530
  maxRows = DEFAULT_MAX_ROWS,
11166
11531
  // Compat v2: `true` apaga el autocrecimiento y deja la caja fija en su alto mínimo.
11167
- disableAutosize = false, placeholder, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
11532
+ disableAutosize = false, placeholder, isClearable: isClearableProp = false, onClear,
11533
+ // Sin efecto (ver `TextareaProps.labelPlacement`): fuera del spread para que no llegue
11534
+ // al `<textarea>` como atributo desconocido.
11535
+ labelPlacement: _labelPlacement, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
11168
11536
  // `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
11169
11537
  // (verificado en el paquete v2 compilado, `dist/components/input.js`:
11170
11538
  // `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
@@ -11172,7 +11540,7 @@ disableAutosize = false, placeholder, isInvalid = false, isRequired = false, isD
11172
11540
  // `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
11173
11541
  // de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
11174
11542
  // columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
11175
- fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, name, style, ...rest }, ref) => {
11543
+ fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, name, style, onKeyDown, ...rest }, ref) => {
11176
11544
  const generatedId = useId();
11177
11545
  const textareaId = id || generatedId;
11178
11546
  const requestedRows = resolveRows(minRows, rows);
@@ -11209,6 +11577,46 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
11209
11577
  minHeight: `${fieldRows * SIZE_LINE_HEIGHT[size] + FIELD_PADDING_Y + FIELD_BORDER_Y}px`,
11210
11578
  ...style,
11211
11579
  };
11580
+ // v2: `isClearable = !!onClear || originalProps.isClearable`.
11581
+ const isClearable = isClearableProp || Boolean(onClear);
11582
+ // Limpiar en modo NO controlado tiene que poder vaciar el valor, y el estado interno del
11583
+ // `TextField` no se puede escribir desde fuera: en ese caso (y solo en ese) la fachada lleva
11584
+ // el valor en su propio estado, igual que `Input.tsx`. Sin `isClearable` nada cambia: el
11585
+ // valor no controlado sigue siendo del `TextField` (y su `reset`, de `useFormReset`).
11586
+ const isControlled = value !== undefined;
11587
+ const ownsValue = isClearable && !isControlled;
11588
+ const [ownValue, setOwnValue] = useState(defaultValue ?? "");
11589
+ const currentValue = isControlled ? value : ownsValue ? ownValue : "";
11590
+ const showClear = isClearable && !isDisabled && !isReadOnly && Boolean(currentValue);
11591
+ // Orden de v2 (`handleClear`): el valor a "" —que en v2 disparaba `onValueChange`—, luego
11592
+ // `onClear`. El `onChange` recibe un evento de valor vacío, como en `Input.tsx`, para que un
11593
+ // consumidor que solo escucha `onChange` (p. ej. `field.onChange` de react-hook-form) se entere.
11594
+ const clearValue = () => {
11595
+ if (ownsValue) {
11596
+ setOwnValue("");
11597
+ }
11598
+ onValueChange?.("");
11599
+ if (onChange) {
11600
+ const emptyEvent = {
11601
+ target: { value: "" },
11602
+ currentTarget: { value: "" },
11603
+ };
11604
+ onChange(emptyEvent);
11605
+ }
11606
+ onClear?.();
11607
+ };
11608
+ const handleClear = () => {
11609
+ clearValue();
11610
+ innerRef.current?.focus();
11611
+ };
11612
+ // Escape limpia, igual que v2 (con valor, limpiable y no de solo lectura). El `onKeyDown` del
11613
+ // consumidor se llama primero.
11614
+ const handleKeyDown = (event) => {
11615
+ onKeyDown?.(event);
11616
+ if (event.key === "Escape" && showClear) {
11617
+ clearValue();
11618
+ }
11619
+ };
11212
11620
  // El estado del valor lo actualiza el `TextField` con su propio `onChange` (llega al
11213
11621
  // `<textarea>` por contexto y `mergeProps` encadena los dos). Aquí: medir —el nodo ya trae
11214
11622
  // el valor nuevo, y en modo no controlado la fachada no se re-renderiza— y avisar.
@@ -11217,6 +11625,10 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
11217
11625
  onChange?.(event);
11218
11626
  onValueChange?.(event.target.value);
11219
11627
  };
11628
+ const field = (jsx(TextArea, { ...rest, ref: setRefs, rows: fieldRows, style: fieldStyle, placeholder: placeholder, onChange: handleChange, onKeyDown: handleKeyDown, className: cn(SIZE_TEXT_CLASS[size], RESIZE_CLASS, RADIUS_CLASS$2[radius], VARIANT_CLASS[variant], isInvalid ? INVALID_TEXT_CLASS : "", FIELD_FOCUS_CLASS, FIELD_SHADOW_CLASS,
11629
+ // Incondicional, como el slot `input` de v2: el campo llena siempre su raíz,
11630
+ // y es la raíz la que responde a `fullWidth` y al ancho del consumidor.
11631
+ "w-full", isClearable ? CLEARABLE_FIELD_CLASS : "", classNames?.input ?? classNames?.inputWrapper) }));
11220
11632
  return (
11221
11633
  // ── `value` / `defaultValue` van al `TextField`, NO al `<textarea>` ─────────────────────
11222
11634
  //
@@ -11231,13 +11643,12 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
11231
11643
  // pero el estado interno del `TextField` seguía en "".
11232
11644
  // Ahora el `TextField` tiene el valor en los dos modos, y el `reset` del formulario también
11233
11645
  // es suyo (`useFormReset` devuelve el estado al `defaultValue`; el autosize lo escucha).
11234
- jsxs(TextField, { id: textareaId, value: value, defaultValue: defaultValue, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: cn(FIELD_STACK_CLASS, fullWidth ? "w-full" : "",
11646
+ jsxs(TextField, { id: textareaId, value: ownsValue ? ownValue : value, defaultValue: ownsValue ? undefined : defaultValue,
11647
+ // Solo cuando la fachada es dueña del valor (limpiable y no controlado).
11648
+ onChange: ownsValue ? setOwnValue : undefined, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: cn(FIELD_STACK_CLASS, fullWidth ? "w-full" : "",
11235
11649
  // El `className` del consumidor, en la raíz y en último lugar: el slot `base` de
11236
11650
  // v2 y la posición que le deja ganar en tailwind-merge (ver bloque de arriba).
11237
- className), children: [label && (jsx(Label, { className: cn(LABEL_CLASS, isInvalid ? INVALID_LABEL_CLASS : "", classNames?.label), children: label })), jsx(TextArea, { ...rest, ref: setRefs, rows: fieldRows, style: fieldStyle, placeholder: placeholder, onChange: handleChange, className: cn(SIZE_TEXT_CLASS[size], RESIZE_CLASS, RADIUS_CLASS$2[radius], VARIANT_CLASS[variant], isInvalid ? INVALID_TEXT_CLASS : "", FIELD_FOCUS_CLASS, FIELD_SHADOW_CLASS,
11238
- // Incondicional, como el slot `input` de v2: el campo llena siempre su raíz,
11239
- // y es la raíz la que responde a `fullWidth` y al ancho del consumidor.
11240
- "w-full", classNames?.input ?? classNames?.inputWrapper) }), description && !isInvalid && (jsx(Description, { className: cn(HELPER_CLASS, "text-left", classNames?.description), children: description })), isInvalid && errorMessage && (jsx(FieldError, { className: cn(HELPER_CLASS, "text-left", classNames?.errorMessage), children: errorMessage }))] }));
11651
+ className), children: [label && (jsx(Label, { className: cn(LABEL_CLASS, isInvalid ? INVALID_LABEL_CLASS : "", classNames?.label), children: label })), isClearable ? (jsxs("div", { className: CLEAR_WRAPPER_CLASS, children: [field, showClear && (jsx(CloseButton, { "aria-label": "Limpiar", className: cn(CLEAR_BUTTON_CLASS, classNames?.clearButton), onPress: handleClear }))] })) : (field), description && !isInvalid && (jsx(Description, { className: cn(HELPER_CLASS, "text-left", classNames?.description), children: description })), isInvalid && errorMessage && (jsx(FieldError, { className: cn(HELPER_CLASS, "text-left", classNames?.errorMessage), children: errorMessage }))] }));
11241
11652
  });
11242
11653
  Textarea.displayName = "Textarea";
11243
11654
 
@@ -12990,8 +13401,8 @@ const DrawerFiltersFooter = ({ onClearFilters, onCancel, onApplyFilters, transla
12990
13401
  /**
12991
13402
  * Header of the DrawerFilters with title and description.
12992
13403
  */
12993
- const DrawerFiltersHeader = ({ title, description, }) => {
12994
- return (jsxs("div", { className: "px-6 pt-10", children: [jsx(H2, { className: "text-xl font-semibold text-foreground mb-2", children: title }), jsx(P, { className: "text-default-500", children: description })] }));
13404
+ const DrawerFiltersHeader = ({ titleId, title, description, }) => {
13405
+ return (jsxs("div", { className: "px-6 pt-10", children: [jsx(H2, { id: titleId, className: "text-xl font-semibold text-foreground mb-2", children: title }), jsx(P, { className: "text-default-500", children: description })] }));
12995
13406
  };
12996
13407
 
12997
13408
  const NO_YEAR_CLASS = "[&_[data-type='year']]:hidden [&_[data-type='literal']:has(+[data-type='year'])]:hidden";
@@ -13670,6 +14081,11 @@ const NumberFilter = ({ filter, value, onChange }) => {
13670
14081
  */
13671
14082
  const DrawerFilters = ({ isOpen, onOpenChange, config, onApplyFilters, onClearFilters, onCancel, className, translations, initialValues, }) => {
13672
14083
  const [filterValues, setFilterValues] = useState(initialValues ?? {});
14084
+ // Nombre accesible del panel: su título. Con `Drawer.Root`, el `DialogTrigger` nombraba el
14085
+ // diálogo con el id de su disparador (que aquí no existe); sin root nadie lo nombra y
14086
+ // react-aria avisa en cada apertura («A dialog must have a title for accessibility», medido en
14087
+ // jsdom). Mismo arreglo que `headerId` en la fachada `Drawer`.
14088
+ const titleId = useId();
13673
14089
  // Handle changes in individual filters
13674
14090
  const handleFilterChange = useCallback((filterKey, value) => {
13675
14091
  setFilterValues((prev) => ({
@@ -13782,9 +14198,16 @@ const DrawerFilters = ({ isOpen, onOpenChange, config, onApplyFilters, onClearFi
13782
14198
  onApplyFilters(processedFilters);
13783
14199
  onOpenChange(false);
13784
14200
  }, [filterValues, config.data, onApplyFilters, onOpenChange]);
13785
- return (jsx(Drawer$1, { children: jsx(Drawer$1.Backdrop, { isOpen: isOpen, onOpenChange: handleOpenChange, isDismissable: false, children: jsx(Drawer$1.Content, { placement: "right", className: cn("drawer-filters", className), children: jsxs(Drawer$1.Dialog, { className: "w-full p-0 sm:max-w-md", children: [jsx(Drawer$1.CloseTrigger, {}), jsx(DrawerFiltersHeader, { title: config.title, description: config.description }), jsx(Drawer$1.Body, { className: "px-6 py-10", children: jsx("div", { className: "space-y-3", children: config.data.map((filter) => {
13786
- return (jsx(FilterSection, { filter: filter, translations: translations, value: filterValues[filter.key], onChange: (value) => handleFilterChange(filter.key, value) }, filter.key));
13787
- }) }) }), jsx(DrawerFiltersFooter, { onClearFilters: handleClearFilters, onCancel: handleCancel, onApplyFilters: handleApplyFilters, translations: translations })] }) }) }) }));
14201
+ // Sin `Drawer.Root` (el `DialogTrigger` de react-aria) a propósito, igual que la fachada
14202
+ // `Drawer` (`src/components/drawer/Drawer.tsx`): aquí no hay disparador —lo abre el `isOpen`
14203
+ // de la app— y un `DialogTrigger` sin hijo pulsable avisa en consola en CADA montaje, abierto o
14204
+ // cerrado («A PressResponder was rendered without a pressable child», medido en jsdom y por
14205
+ // beweossmbs con la .140). `Drawer.Backdrop` (`ModalOverlay`) ya crea su propio estado de
14206
+ // overlay con `isOpen`/`onOpenChange` y calcula todos los slots de `drawerVariants()`, así que
14207
+ // el root no aportaba nada.
14208
+ return (jsx(Drawer$1.Backdrop, { isOpen: isOpen, onOpenChange: handleOpenChange, isDismissable: false, children: jsx(Drawer$1.Content, { placement: "right", className: cn("drawer-filters", className), children: jsxs(Drawer$1.Dialog, { "aria-labelledby": titleId, className: "w-full p-0 sm:max-w-md", children: [jsx(Drawer$1.CloseTrigger, {}), jsx(DrawerFiltersHeader, { titleId: titleId, title: config.title, description: config.description }), jsx(Drawer$1.Body, { className: "px-6 py-10", children: jsx("div", { className: "space-y-3", children: config.data.map((filter) => {
14209
+ return (jsx(FilterSection, { filter: filter, translations: translations, value: filterValues[filter.key], onChange: (value) => handleFilterChange(filter.key, value) }, filter.key));
14210
+ }) }) }), jsx(DrawerFiltersFooter, { onClearFilters: handleClearFilters, onCancel: handleCancel, onApplyFilters: handleApplyFilters, translations: translations })] }) }) }));
13788
14211
  };
13789
14212
  DrawerFilters.displayName = "DrawerFilters";
13790
14213
 
@@ -16929,21 +17352,66 @@ function hasIndicator(children) {
16929
17352
  return node.type === Fragment && hasIndicator(node.props.children);
16930
17353
  });
16931
17354
  }
17355
+ // ---------------------------------------------------------------------------------------------
17356
+ // `size` de v2 → medidas de pestaña de v2
17357
+ // ---------------------------------------------------------------------------------------------
17358
+ /**
17359
+ * Medidas de pestaña por `size`, **copiadas del oráculo** `@heroui/theme@2.4.15`
17360
+ * `dist/components/tabs.js` (slot `tab`, `px-3` en la base y `size.{sm,md,lg}.tab` para la altura y
17361
+ * el cuerpo), traducidas a clases de Tailwind 4 con el MISMO valor. Las `text-tiny` /
17362
+ * `text-small` / `text-medium` de v2 son del plugin `heroui()` y en TW4 no existen
17363
+ * (`default-layout.js:32-40`):
17364
+ *
17365
+ * | size | v2 | aquí | cuerpo / interlineado |
17366
+ * |------|------------------------|----------------|-----------------------|
17367
+ * | sm | `h-7 text-tiny` + px-3 | `h-7 px-3 text-xs` | 0.75rem / 1rem |
17368
+ * | md | `h-8 text-small` + px-3 | `h-8 px-3 text-sm` | 0.875rem / 1.25rem |
17369
+ * | lg | `h-9 text-medium` + px-3 | `h-9 px-3 text-base` | 1rem / 1.5rem |
17370
+ *
17371
+ * Por qué se mapea, cuando `radius` y `color` se ignoran (§9.1): `size` no es estética, es
17372
+ * TAMAÑO, y descartarlo provoca un desbordamiento medido. linda-business embebido en el host v1 con
17373
+ * la `.140`: con `size="sm"` descartado, el texto pasaba de 12 px a 14 px y cada pestaña salía de 6
17374
+ * a 17 px más ancha. El carril medía 352 px en una columna de 287 px y el chevron del scroller
17375
+ * tapaba la última pestaña. smbs tiene 10 `size="sm"`.
17376
+ *
17377
+ * Lo que NO se copia: el peso (v3 pone `font-medium`, v2 no ponía nada) y el radio (`rounded-small`),
17378
+ * que siguen como v3 por §9.1. Tampoco el `py-1` de v2: con altura fija y `items-center` no
17379
+ * cambia nada.
17380
+ *
17381
+ * Las clases van ANTES de `classNames.tab` y del `className` del `Tab` en un `cn`
17382
+ * (tailwind-merge): lo de la app gana. El `h-7 px-2` de linda-business sigue mandando sobre el
17383
+ * `px-3`. Clases literales en el fuente (no construidas), para que Tailwind las genere.
17384
+ */
17385
+ const TAB_SIZE_CLASS = {
17386
+ sm: "h-7 px-3 text-xs",
17387
+ md: "h-8 px-3 text-sm",
17388
+ lg: "h-9 px-3 text-base",
17389
+ };
17390
+ /**
17391
+ * El `size` del root llega a los `Tabs.Tab` de la forma v3 por contexto, porque esas pestañas las
17392
+ * escribe la app dentro de su `Tabs.List` y la fachada no las recorre. Sin `size` el valor es
17393
+ * `undefined` y la pestaña sale byte a byte como antes.
17394
+ */
17395
+ const TabSizeContext = createContext(undefined);
16932
17396
  const TabFacade = ({ children: ownChildren, title,
16933
17397
  // `titleValue` es de v2 y no tiene destino en v3: se consume para que no llegue al DOM.
16934
17398
  titleValue: _titleValue, ...props }) => {
16935
17399
  // En la forma v3 la etiqueta son los `children`; si solo viene el `title` de v2 (un `Tab` con
16936
17400
  // `title` dentro de `Tabs.List`), hace de etiqueta. Con los dos, mandan los `children`.
16937
17401
  const children = ownChildren ?? title;
17402
+ const sizeClass = useContext(TabSizeContext);
17403
+ const tabProps = sizeClass
17404
+ ? { ...props, className: cn(sizeClass, props.className) }
17405
+ : props;
16938
17406
  // `children` como función es la forma de render props de react-aria (`TabRenderProps`). No se
16939
17407
  // puede inspeccionar, y menos aún combinar con un hermano —React rechaza una función como
16940
17408
  // hijo junto a un elemento—, así que se pasa tal cual. Un consumidor que la use monta el
16941
17409
  // indicador él (es la vía de escape de este caso). Exposición hoy: 0 usos en los 6 repos.
16942
17410
  if (typeof children === "function") {
16943
- return jsx(Tabs$1.Tab, { ...props, children: children });
17411
+ return jsx(Tabs$1.Tab, { ...tabProps, children: children });
16944
17412
  }
16945
17413
  if (hasIndicator(children)) {
16946
- return jsx(Tabs$1.Tab, { ...props, children: children });
17414
+ return jsx(Tabs$1.Tab, { ...tabProps, children: children });
16947
17415
  }
16948
17416
  // El indicador va PRIMERO por legibilidad del marcado, no por efecto: es `position:absolute`
16949
17417
  // con `z-index:-1`, o sea fuera del flujo y detrás del texto, así que el orden no cambia el
@@ -16953,7 +17421,7 @@ titleValue: _titleValue, ...props }) => {
16953
17421
  // `{children}` se pasa SIN normalizar (nada de `Children.toArray`) para no reescribir las
16954
17422
  // keys del consumidor: hacerlo cambiaría la reconciliación de una lista de pestañas
16955
17423
  // condicional. Verificado en el navegador que la anidación no produce aviso de key.
16956
- return (jsxs(Tabs$1.Tab, { ...props, children: [jsx(Tabs$1.Indicator, {}), children] }));
17424
+ return (jsxs(Tabs$1.Tab, { ...tabProps, children: [jsx(Tabs$1.Indicator, {}), children] }));
16957
17425
  };
16958
17426
  TabFacade.displayName = "Aurora.Tab";
16959
17427
  /**
@@ -17064,8 +17532,10 @@ function hasPanelContent(children) {
17064
17532
  }
17065
17533
  const TabsFacade = (allProps) => {
17066
17534
  const { children, classNames, className, variant, fullWidth, items, isVertical, placement, orientation, destroyInactiveTabPanel = true,
17535
+ // v2: medidas de pestaña (ver `TAB_SIZE_CLASS`).
17536
+ size,
17067
17537
  // Sin equivalente en v3: se consumen para que no lleguen al DOM (ver README, «Props v2»).
17068
- size: _size, radius: _radius, color: _color, disableAnimation: _disableAnimation, disableCursorAnimation: _disableCursorAnimation, shouldSelectOnPressUp: _shouldSelectOnPressUp, ...rest } = allProps;
17538
+ radius: _radius, color: _color, disableAnimation: _disableAnimation, disableCursorAnimation: _disableCursorAnimation, shouldSelectOnPressUp: _shouldSelectOnPressUp, ...rest } = allProps;
17069
17539
  const v3Variant = toV3Variant(variant);
17070
17540
  const v3Orientation = orientation ??
17071
17541
  (isVertical || placement === "start" || placement === "end"
@@ -17126,7 +17596,7 @@ const TabsFacade = (allProps) => {
17126
17596
  // `.tabs[data-orientation="horizontal"] > .tabs__list-container { align-self: start }` de
17127
17597
  // neutralize, que no hace falta: dentro de una fila el carril ya mide su contenido.
17128
17598
  const orientationAttr = v3Orientation ?? "horizontal";
17129
- return (jsxs(Tabs$1, { ...rootProps, orientation: v3Orientation, variant: v3Variant, children: [jsx("div", { className: cn("flex", v3Variant === "secondary" && "tabs--secondary", fullWidth && "w-full", classNames?.base, className), "data-orientation": orientationAttr, "data-slot": "tabs-base", children: jsx(Tabs$1.ListContainer, { className: cn(fullWidth && "w-full"), children: jsx(Tabs$1.List, { "aria-describedby": ariaDescribedby, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className: cn(classNames?.tabList), children: tabs.map(({ id, title, tabClassName, tabProps }) => (createElement(Tabs$1.Tab, { ...tabProps, className: cn(classNames?.tab, tabClassName), id: id, key: id },
17599
+ return (jsxs(Tabs$1, { ...rootProps, orientation: v3Orientation, variant: v3Variant, children: [jsx("div", { className: cn("flex", v3Variant === "secondary" && "tabs--secondary", fullWidth && "w-full", classNames?.base, className), "data-orientation": orientationAttr, "data-slot": "tabs-base", children: jsx(Tabs$1.ListContainer, { className: cn(fullWidth && "w-full"), children: jsx(Tabs$1.List, { "aria-describedby": ariaDescribedby, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className: cn(classNames?.tabList), children: tabs.map(({ id, title, tabClassName, tabProps }) => (createElement(Tabs$1.Tab, { ...tabProps, className: cn(size && TAB_SIZE_CLASS[size], classNames?.tab, tabClassName), id: id, key: id },
17130
17600
  jsx(Tabs$1.Indicator, { className: cn(classNames?.cursor) }),
17131
17601
  classNames?.tabContent ? (jsx("span", { className: classNames.tabContent, children: title })) : (title)))) }) }) }), tabs
17132
17602
  .filter(({ panel }) => hasPanelContent(panel))
@@ -17145,7 +17615,9 @@ const TabsFacade = (allProps) => {
17145
17615
  orientation: v3Orientation,
17146
17616
  variant: v3Variant,
17147
17617
  };
17148
- return jsx(TabsV3, { ...v3Props, children: v3Children });
17618
+ const v3Tabs = jsx(TabsV3, { ...v3Props, children: v3Children });
17619
+ // Sin `size` no se monta el provider: el árbol queda idéntico al de antes.
17620
+ return size ? (jsx(TabSizeContext.Provider, { value: TAB_SIZE_CLASS[size], children: v3Tabs })) : (v3Tabs);
17149
17621
  };
17150
17622
  const TabsV3 = ({ children, ...props }) => {
17151
17623
  const nodes = Children.toArray(children);