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

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 (55) hide show
  1. package/dist/assets/css/styles.css +34 -3
  2. package/dist/index.cjs.js +910 -207
  3. package/dist/index.esm.js +911 -208
  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/color-picker/ColorPicker.d.ts.map +1 -1
  17. package/dist/types/components/datepicker/DatePicker.d.ts.map +1 -1
  18. package/dist/types/components/drawer/Drawer.d.ts.map +1 -1
  19. package/dist/types/components/drawer/_internal/DrawerFooter.d.ts +3 -1
  20. package/dist/types/components/drawer/_internal/DrawerFooter.d.ts.map +1 -1
  21. package/dist/types/components/drawer-filters/DrawerFilters.d.ts.map +1 -1
  22. package/dist/types/components/drawer-filters/DrawerFilters.types.d.ts +2 -0
  23. package/dist/types/components/drawer-filters/DrawerFilters.types.d.ts.map +1 -1
  24. package/dist/types/components/drawer-filters/_internal/DayMonthPicker.d.ts +6 -0
  25. package/dist/types/components/drawer-filters/_internal/DayMonthPicker.d.ts.map +1 -1
  26. package/dist/types/components/drawer-filters/_internal/DrawerFiltersHeader.d.ts.map +1 -1
  27. package/dist/types/components/drawer-filters/_internal/FilterSection.d.ts.map +1 -1
  28. package/dist/types/components/icon/Icon.d.ts.map +1 -1
  29. package/dist/types/components/icon/Icon.types.d.ts +9 -1
  30. package/dist/types/components/icon/Icon.types.d.ts.map +1 -1
  31. package/dist/types/components/input/Input.d.ts.map +1 -1
  32. package/dist/types/components/menu/_internal/menu-nav-list/MenuNavList.d.ts.map +1 -1
  33. package/dist/types/components/modal/_internal/ModalFooter.d.ts +6 -0
  34. package/dist/types/components/modal/_internal/ModalFooter.d.ts.map +1 -1
  35. package/dist/types/components/pagination/Pagination.d.ts.map +1 -1
  36. package/dist/types/components/range-filter/RangeFilter.d.ts.map +1 -1
  37. package/dist/types/components/select/Select.d.ts.map +1 -1
  38. package/dist/types/components/select/Select.types.d.ts +109 -6
  39. package/dist/types/components/select/Select.types.d.ts.map +1 -1
  40. package/dist/types/components/select/index.d.ts +1 -1
  41. package/dist/types/components/select/index.d.ts.map +1 -1
  42. package/dist/types/components/switch/Switch.d.ts.map +1 -1
  43. package/dist/types/components/switch/Switch.types.d.ts +22 -0
  44. package/dist/types/components/switch/Switch.types.d.ts.map +1 -1
  45. package/dist/types/components/switch/index.d.ts +1 -1
  46. package/dist/types/components/switch/index.d.ts.map +1 -1
  47. package/dist/types/components/table/Table.d.ts.map +1 -1
  48. package/dist/types/components/table/TableContent.d.ts.map +1 -1
  49. package/dist/types/components/tabs/Tabs.d.ts.map +1 -1
  50. package/dist/types/components/tabs/Tabs.types.d.ts +3 -2
  51. package/dist/types/components/tabs/Tabs.types.d.ts.map +1 -1
  52. package/dist/types/components/textarea/Textarea.d.ts.map +1 -1
  53. package/dist/types/components/textarea/Textarea.types.d.ts +24 -0
  54. package/dist/types/components/textarea/Textarea.types.d.ts.map +1 -1
  55. package/package.json +1 -1
package/dist/index.cjs.js CHANGED
@@ -756,9 +756,44 @@ const SHAPE_BY_VARIANT = {
756
756
  light: "transparent",
757
757
  flat: "soft",
758
758
  bordered: "outline",
759
- faded: "outline",
759
+ faded: "faded",
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,8 @@ const V3_VARIANT_BY_SHAPE = {
876
911
  soft: "soft",
877
912
  transparent: "tertiary",
878
913
  outline: "tertiary",
914
+ faded: "tertiary",
915
+ dot: "tertiary",
879
916
  };
880
917
  /**
881
918
  * TECH-2459 · F5 · P-08d — La tabla `flat` de v2, medida contra los PNG congelados y no deducida.
@@ -982,7 +1019,56 @@ const CLOSE_ICON_SIZE_CLASS = {
982
1019
  md: "size-[18px]",
983
1020
  lg: "size-5",
984
1021
  };
1022
+ // ── `bordered` y `faded` (TECH-2459, 24-sep: el usuario ve «muy negro» el `bordered default`) ──
1023
+ //
1024
+ // Antes las dos variantes pintaban borde y texto con `tintFg` (`default-700` = rgb(63,63,70) para
1025
+ // `default`): en el selector de tallas del `addon-card` de beweossmbs el chip sin elegir se leía
1026
+ // como un bloque casi negro al lado del `solid primary` elegido. Y el mismo `tintFg` dejaba el
1027
+ // texto por debajo de AA en otros colores. Medido en `Custom Components/Chip → BorderedColors`
1028
+ // (texto contra el fondo real, blanco / tarjeta `content1` oscura):
1029
+ //
1030
+ // | color | antes claro | antes oscuro | ahora claro | ahora oscuro |
1031
+ // |-----------|-------------|--------------|-------------|--------------|
1032
+ // | default | 10.44 (borde casi negro) | 13.96 (borde casi blanco) | 17.93 | 15.11 |
1033
+ // | primary | 4.66 | 3.80 ✗ | 6.38 | 7.33 |
1034
+ // | secondary | 9.50 | 5.77 | 9.50 | 5.77 |
1035
+ // | success | 2.19 ✗ | 8.08 | 5.50 | 8.08 |
1036
+ // | warning | 2.04 ✗ | 8.68 | 5.20 | 8.68 |
1037
+ // | danger | 4.14 ✗ | 4.27 ✗ | 6.06 | 5.02 |
1038
+ //
1039
+ // Criterio (no es volver a v2 por volver, es que se vea bien dentro de los tokens de Aurora):
1040
+ //
1041
+ // - **Borde**: el color a secas (`border-<color>`). Para `default`, el gris neutro que ya usa el
1042
+ // resto de la familia (`dot` lleva `border-default`, y v2 también en `bordered`/`faded`):
1043
+ // `--color-default`, rgb(212,212,216) en claro y rgb(63,63,70) en oscuro. Se distingue del fondo
1044
+ // (medido: 1.48:1 sobre blanco, 1.70:1 sobre la tarjeta oscura, 2.01:1 sobre el fondo negro) sin
1045
+ // competir con el texto. Input/Select reposan en `default-200`, un paso más claro, porque su borde
1046
+ // enmarca un campo; en un chip el borde ES la forma, y `default-200` (≈1.27:1 sobre blanco,
1047
+ // calculado) la dejaba casi invisible. En oscuro los dos pasos son el mismo rgb(63,63,70).
1048
+ // - **Texto**: el mismo paso legible que ya usa `flat` (`flatFg` + su `dark:`), que es el que la
1049
+ // librería ya validó para texto tintado; `default` usa `foreground`.
1050
+ // - Van como CLASES y no como `borderColor` inline: un estilo inline le ganaba a cualquier
1051
+ // `border-*` de la app, y así `cn` (tailwind-merge) deja ganar la clase del consumidor.
1052
+ // - `faded` conserva lo que lo distinguía en v2 (borde neutro en todos los colores + relleno
1053
+ // `default-100`); hasta ahora era idéntico a `bordered`. Su texto es el `flatFg` SIN el `dark:`
1054
+ // de `flat` (ver `flatDarkFgClass`). Medido: ≥ 4.73:1 en claro y ≥ 4.85:1 en oscuro en los 6
1055
+ // colores. 0 usos en las apps.
1056
+ const OUTLINE_BORDER_CLASS = {
1057
+ default: "border-default",
1058
+ primary: "border-primary",
1059
+ secondary: "border-secondary",
1060
+ success: "border-success",
1061
+ warning: "border-warning",
1062
+ danger: "border-danger",
1063
+ };
985
1064
  function colorStyle(color, shape) {
1065
+ if (shape === "dot") {
1066
+ // `dot`: el chip es neutro para cualquier color (el color va solo en el punto).
1067
+ return {
1068
+ "--chip-bg": "transparent",
1069
+ "--chip-fg": "var(--color-foreground)",
1070
+ };
1071
+ }
986
1072
  const t = COLOR_TOKENS[color];
987
1073
  if (shape === "solid") {
988
1074
  return { "--chip-bg": t.bg, "--chip-fg": t.fg };
@@ -991,7 +1077,14 @@ function colorStyle(color, shape) {
991
1077
  // `flat` de v2: pastilla translúcida + texto del paso -600/-700 (P-08d).
992
1078
  return { "--chip-bg": t.flatBg, "--chip-fg": t.flatFg };
993
1079
  }
994
- // transparent (light) / outline (bordered, faded): sin relleno, texto tinte del color.
1080
+ if (shape === "outline" || shape === "faded") {
1081
+ // `bordered`/`faded`: texto del paso legible de `flat`; `default` en `foreground`.
1082
+ return {
1083
+ "--chip-bg": shape === "faded" ? "var(--color-default-100)" : "transparent",
1084
+ "--chip-fg": color === "default" ? "var(--color-foreground)" : t.flatFg,
1085
+ };
1086
+ }
1087
+ // transparent (light): sin relleno, texto tinte del color.
995
1088
  return { "--chip-bg": "transparent", "--chip-fg": t.tintFg };
996
1089
  }
997
1090
  /**
@@ -1018,26 +1111,32 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
1018
1111
  const hasEndContent = Boolean(endContent) || Boolean(onClose);
1019
1112
  // `isOneChar` de v2 (`use-chip.js:58`): SOLO si el children es un string de un carácter — un
1020
1113
  // 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" &&
1114
+ const isDot = shape === "dot";
1115
+ const isSquare = !isDot &&
1116
+ typeof children === "string" &&
1022
1117
  children.length === 1 &&
1023
1118
  !startContent &&
1024
1119
  !hasEndContent;
1025
1120
  const vars = isStandard
1026
1121
  ? colorStyle(color, shape)
1027
1122
  : undefined;
1028
- const customClass = !isStandard ? CUSTOM_COLOR_CLASSES[color] : "";
1029
- const outlineClass = shape === "outline" ? "border-2" : "";
1030
- // El `dark:text-*` que v2 declaraba solo para el texto de `flat` en success/warning/danger
1031
- // (P-08d). Va como clase porque `--chip-fg` es inline y no puede depender del tema; la capa
1123
+ // Con `dot` el color no tiñe el chip, tampoco el de los colores fijos.
1124
+ const customClass = isStandard || isDot ? "" : CUSTOM_COLOR_CLASSES[color];
1125
+ const hasBorder = shape === "outline" || shape === "faded";
1126
+ const outlineClass = hasBorder
1127
+ ? react.cn("border-2", isStandard
1128
+ ? OUTLINE_BORDER_CLASS[shape === "faded" ? "default" : color]
1129
+ : "")
1130
+ : "";
1131
+ // El `dark:text-*` que v2 declaraba para el texto de `flat` en success/warning/danger (P-08d),
1132
+ // reutilizado por `bordered`, que toma el mismo texto. `faded` NO: sobre su relleno
1133
+ // `default-100` oscuro el `danger-500` se quedaba en 4.22:1; con el paso `-600`/`-700` del
1134
+ // tema oscuro (más claro) todos los colores pasan AA. Va como clase porque `--chip-fg` es inline y no puede depender del tema; la capa
1032
1135
  // `utilities` gana al `color: var(--chip-fg)` de `.chip`, que vive en `components`.
1033
- const flatDarkFgClass = isStandard && shape === "soft"
1136
+ const flatDarkFgClass = isStandard && (shape === "soft" || shape === "outline")
1034
1137
  ? (COLOR_TOKENS[color].flatFgDarkClass ?? "")
1035
1138
  : "";
1036
- const baseStyle = vars
1037
- ? shape === "outline"
1038
- ? { ...vars, borderColor: vars["--chip-fg"] }
1039
- : vars
1040
- : undefined;
1139
+ const baseStyle = vars;
1041
1140
  // El `style` del consumidor va DESPUÉS: pisa el relleno/texto calculados (uso real en
1042
1141
  // beweossmbs: `style={{ backgroundColor, color }}` con el color del agente, 5 sitios).
1043
1142
  const style = baseStyle || consumerStyle ? { ...baseStyle, ...consumerStyle } : undefined;
@@ -1062,7 +1161,7 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
1062
1161
  className: react.cn(SIZE_CLASS$6[size],
1063
1162
  // Después de `SIZE_CLASS` a propósito: el cuadrado de un carácter ENCOGE la caja
1064
1163
  // 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, jsxRuntime.jsx("span", { className: react.cn("flex-1 text-inherit font-normal", startContent
1164
+ 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 && (jsxRuntime.jsx("span", { "aria-hidden": "true", "data-slot": "dot", className: react.cn(DOT_SLOT_CLASS, DOT_COLOR_CLASS[color]) })), jsxRuntime.jsx("span", { className: react.cn("flex-1 text-inherit font-normal", startContent
1066
1165
  ? CONTENT_PADDING[size].leftWithContent
1067
1166
  : CONTENT_PADDING[size].left, hasEndContent
1068
1167
  ? CONTENT_PADDING[size].rightWithContent
@@ -1087,7 +1186,11 @@ const sizeMap = {
1087
1186
  const IconComponent = ({ icon, size = "md", className, style, color, hFlip, vFlip, flip, rotate, ...nativeProps }) => {
1088
1187
  // Un número son píxeles tal cual (p. ej. 64): la escala con nombre se queda en 32 y
1089
1188
  // `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);
1189
+ // Un nombre fuera de la escala (`"xs"`, el alias v2 del tipo) cae al default, como en v2
1190
+ // (`sizeMap[size] ?? sizeMap.md`, `@beweco/aurora-ui@0.6.85`): 20px.
1191
+ const iconSize = typeof size === "number"
1192
+ ? `${size}px`
1193
+ : (sizeMap[size] ?? sizeMap.md);
1091
1194
  return (jsxRuntime.jsx("span", { ...nativeProps, children: jsxRuntime.jsx(react$1.Icon, { icon: icon, width: iconSize, height: iconSize, className: className, style: style, color: color, hFlip: hFlip, vFlip: vFlip, flip: flip, rotate: rotate }) }));
1092
1195
  };
1093
1196
 
@@ -1152,6 +1255,20 @@ const STRIP_CLASS = "w-fit gap-0 rounded-[12px] shadow-xs";
1152
1255
  const STRIP_LOOSE_CLASS = "w-fit gap-1";
1153
1256
  /** Controles anterior/siguiente: celda cuadrada, sin el padding lateral de v3. */
1154
1257
  const NAV_CLASS = "w-9 px-0";
1258
+ /**
1259
+ * TECH-2459 (beweossmbs, 24-sep) — anterior/siguiente deshabilitados por estar en un EXTREMO
1260
+ * (página 1 / última). v3 los apaga con `status-disabled` (`opacity: var(--disabled-opacity)`,
1261
+ * `@heroui/styles/dist/components/pagination.css:57`), que aquí deja el galón en el
1262
+ * `text-default-foreground` de `CELL_CLASS` al 50 %: medido `rgb(0,0,0)` a opacidad 0.5 sobre
1263
+ * `default-100`, que se lee como un control activo. Se pinta el galón en `default-300` y a
1264
+ * opacidad plena, el gris de «apagado» de la paleta (el mismo que usaba v2,
1265
+ * `data-[disabled=true]:text-default-300` de `@heroui/theme@2.4.15` `pagination.js:489`); el
1266
+ * cursor y el `pointer-events` de v3 se conservan.
1267
+ *
1268
+ * Solo en los extremos: con `isDisabled` en toda la paginación manda el apagado uniforme de v3
1269
+ * (todas las celdas al 50 %), y las flechas no deben verse distintas del resto.
1270
+ */
1271
+ const NAV_AT_EDGE_CLASS = "disabled:text-default-300 disabled:opacity-100";
1155
1272
  /**
1156
1273
  * Radio de cada pieza según su posición en la tira.
1157
1274
  *
@@ -1226,6 +1343,33 @@ const DEFAULT_TRANSLATIONS$g = {
1226
1343
  jumpBackwardAriaLabel: "Retroceder {count} páginas",
1227
1344
  jumpForwardAriaLabel: "Avanzar {count} páginas",
1228
1345
  };
1346
+ /**
1347
+ * TECH-2459 (beweossmbs, 24-sep) — los mismos textos en inglés, para cuando el idioma de la app es
1348
+ * inglés. El defecto medido: con la interfaz en inglés, «anterior» se anunciaba «Página anterior».
1349
+ * La app SÍ comunica su idioma (`<HeroUIProvider locale="en-US">` → `I18nProvider` de react-aria,
1350
+ * `src/compat/heroui-v2-shims.tsx`), y es el mismo que ya siguen los textos propios de react-aria
1351
+ * (la casilla «Select all» de la tabla, los calendarios…); la fachada lo ignoraba porque solo
1352
+ * miraba `translations`. Ahora el idioma elige la base y `translations` sigue ganando encima.
1353
+ */
1354
+ const EN_TRANSLATIONS = {
1355
+ previousPageAriaLabel: "Previous page",
1356
+ nextPageAriaLabel: "Next page",
1357
+ jumpBackwardAriaLabel: "Jump back {count} pages",
1358
+ jumpForwardAriaLabel: "Jump forward {count} pages",
1359
+ };
1360
+ /**
1361
+ * Base de textos según el idioma de react-aria (`useLocale`): inglés si el locale es `en*`,
1362
+ * español en cualquier otro caso (el default de la librería, regla `auraui-i18n`).
1363
+ *
1364
+ * Sin provider, react-aria resuelve el idioma del NAVEGADOR (`navigator.language`), igual que
1365
+ * para sus propios textos: una app que no declare `locale` sigue al navegador. Para fijarlo, la
1366
+ * app pasa `locale` a `HeroUIProvider` o los textos por `translations`.
1367
+ */
1368
+ function baseTranslations(locale) {
1369
+ return locale.toLowerCase().startsWith("en")
1370
+ ? EN_TRANSLATIONS
1371
+ : DEFAULT_TRANSLATIONS$g;
1372
+ }
1229
1373
  /**
1230
1374
  * TECH-2459 · elipsis navegable — páginas que salta la elipsis. Literal de v2:
1231
1375
  * `dotsJump = 5`, default desestructurado en `@heroui/pagination@2.2.20`
@@ -1345,7 +1489,8 @@ function getPaginationRange(total, current) {
1345
1489
  const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm", className, isDisabled, showControls = true, isCompact = true, showShadow = false, color = "primary", translations = {}, }) => {
1346
1490
  const [internalPage, setInternalPage] = $8WNJc$react.useState(initialPage);
1347
1491
  const currentPage = page ?? internalPage;
1348
- const t = { ...DEFAULT_TRANSLATIONS$g, ...translations };
1492
+ const { locale } = react.useLocale();
1493
+ const t = { ...baseTranslations(locale), ...translations };
1349
1494
  const goTo = (next) => {
1350
1495
  const clamped = Math.min(Math.max(next, 1), total);
1351
1496
  if (page === undefined) {
@@ -1378,7 +1523,7 @@ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm"
1378
1523
  // lo nombraba ("previous page button", `use-pagination.js:213`), pero en
1379
1524
  // inglés y hardcodeado; aquí va por `translations` (regla obligatoria de
1380
1525
  // i18n del repo, skill `auraui-i18n`).
1381
- , { "aria-label": t.previousPageAriaLabel, className: react.cn(CELL_CLASS, NAV_CLASS, cellRadius(isCompact, { isFirst: true, isLast: false })), isDisabled: isDisabled || currentPage <= 1, onPress: () => goTo(currentPage - 1), children: jsxRuntime.jsx(react.Pagination.PreviousIcon, {}) }) })), items.map((item, index) => {
1526
+ , { "aria-label": t.previousPageAriaLabel, className: react.cn(CELL_CLASS, NAV_CLASS, isDisabled ? "" : NAV_AT_EDGE_CLASS, cellRadius(isCompact, { isFirst: true, isLast: false })), isDisabled: isDisabled || currentPage <= 1, onPress: () => goTo(currentPage - 1), children: jsxRuntime.jsx(react.Pagination.PreviousIcon, {}) }) })), items.map((item, index) => {
1382
1527
  if (item === "ellipsis") {
1383
1528
  const isBefore = index < activeIndex;
1384
1529
  const label = isBefore
@@ -1393,7 +1538,7 @@ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm"
1393
1538
  return (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Link, { className: react.cn(CELL_CLASS, cellRadius(isCompact, itemPosition(index)), item === currentPage ? activeClass : ""), isActive: item === currentPage, isDisabled: isDisabled, onPress: () => goTo(item), children: item }) }, item));
1394
1539
  }), showControls && (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Next
1395
1540
  /* Mismo motivo que en `Previous`: el galón es `aria-hidden`. */
1396
- , { "aria-label": t.nextPageAriaLabel, className: react.cn(CELL_CLASS, NAV_CLASS, cellRadius(isCompact, { isFirst: false, isLast: true })), isDisabled: isDisabled || currentPage >= total, onPress: () => goTo(currentPage + 1), children: jsxRuntime.jsx(react.Pagination.NextIcon, {}) }) }))] }) }));
1541
+ , { "aria-label": t.nextPageAriaLabel, className: react.cn(CELL_CLASS, NAV_CLASS, isDisabled ? "" : NAV_AT_EDGE_CLASS, cellRadius(isCompact, { isFirst: false, isLast: true })), isDisabled: isDisabled || currentPage >= total, onPress: () => goTo(currentPage + 1), children: jsxRuntime.jsx(react.Pagination.NextIcon, {}) }) }))] }) }));
1397
1542
  };
1398
1543
 
1399
1544
  // TECH-2459 · F5 · P-01 — Valores tomados del tema v2 REAL (`@heroui/theme@2.4.15`,
@@ -1669,7 +1814,12 @@ const ROOT_HELPER_SPACE_CLASS = {
1669
1814
  };
1670
1815
  const GROUP_PLACEMENT_CLASS = {
1671
1816
  outside: undefined,
1672
- "outside-left": "relative flex-1",
1817
+ // `min-w-min` (TECH-2459, beweossmbs QA 24-sep, barra de la agenda por debajo de 1440px):
1818
+ // `.date-input-group` de v3 lleva `overflow-hidden`, y un ítem flex con overflow distinto de
1819
+ // `visible` tiene `min-width: auto` = 0. Con `flex-1` el grupo se quedaba con el hueco sobrante
1820
+ // y recortaba su propio botón de calendario (104px con 140 de contenido a 1280px). En v2 el
1821
+ // overflow era visible: el grupo conservaba su contenido y era la etiqueta la que se partía.
1822
+ "outside-left": "relative flex-1 min-w-min",
1673
1823
  };
1674
1824
  const HELPER_PLACEMENT_CLASS = {
1675
1825
  outside: undefined,
@@ -2171,7 +2321,9 @@ labelPlacement: _labelPlacement, isInvalid = false, isRequired = false, isDisabl
2171
2321
  // `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
2172
2322
  // de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
2173
2323
  // columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
2174
- fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, required, name, type = "text", onKeyDown, ...rest }, ref) => {
2324
+ fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, required, name, type = "text", onKeyDown,
2325
+ // Nombre y descripción accesibles: van al `TextField`, no al `<input>` (ver abajo).
2326
+ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, ...rest }, ref) => {
2175
2327
  // El `<input>` real, también para la fachada: v2 devolvía el foco al campo al limpiar
2176
2328
  // (`domRef.current?.focus()` en `handleClear`). Se combina con el `ref` del consumidor.
2177
2329
  const inputRef = $8WNJc$react.useRef(null);
@@ -2214,6 +2366,19 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
2214
2366
  // Solo flota cuando hay label y NO hay placeholder: con placeholder, v2 ya mostraba el label
2215
2367
  // arriba desde el primer render (ver el bloque de arriba).
2216
2368
  const labelFlotante = Boolean(label) && !placeholder;
2369
+ // ── Nombre accesible sin `label` visible ─────────────────────────────────────────────────
2370
+ //
2371
+ // Quien llama a `useLabel` (y avisa «If you do not provide a visible label, you must specify
2372
+ // an aria-label…») es el `TextField` de react-aria, que se los pasa al `<input>` por contexto.
2373
+ // Dejarlos en el spread del `<input>` hacía que avisara AUNQUE la app diera nombre (medido en
2374
+ // beweossmbs: 14/16 avisos al cargar `/clients/table`, del buscador con solo placeholder).
2375
+ // Y `aria-describedby` en el `<input>` pisaba el id de la `Description` propia. v2 no avisaba
2376
+ // nunca: `"aria-label": label ? aria-label : safeAriaLabel(aria-label, placeholder)`
2377
+ // (`@heroui/input@2.4.33`, `dist/use-input.js:119`). Con `aria-labelledby` no se añade el
2378
+ // placeholder: react-aria lo concatenaría al nombre del elemento referido.
2379
+ const accessibleLabel = label || ariaLabelledBy
2380
+ ? ariaLabel
2381
+ : ariaLabel?.trim() || placeholder?.trim() || undefined;
2217
2382
  const handleChange = (event) => {
2218
2383
  onChange?.(event);
2219
2384
  onValueChange?.(event.target.value);
@@ -2246,7 +2411,7 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
2246
2411
  clearValue();
2247
2412
  }
2248
2413
  };
2249
- return (jsxRuntime.jsxs(react.TextField, { id: inputId, value: currentValue,
2414
+ return (jsxRuntime.jsxs(react.TextField, { id: inputId, "aria-label": accessibleLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, value: currentValue,
2250
2415
  // Solo el modo no controlado escribe aquí; en controlado manda el `value` del padre
2251
2416
  // (que se entera por `onChange`/`onValueChange` del `<input>`).
2252
2417
  onChange: isControlled ? undefined : setUncontrolledValue, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: react.cn(FIELD_STACK_CLASS$3[size], fullWidth ? "w-full" : "",
@@ -2263,7 +2428,15 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
2263
2428
  // pase el consumidor.
2264
2429
  "w-full",
2265
2430
  // v3 fusiona `innerWrapper` e `inputWrapper` de v2 en este único elemento.
2266
- classNames?.innerWrapper, classNames?.inputWrapper), children: [startContent && (jsxRuntime.jsx(react.InputGroup.Prefix, { className: "border-none bg-transparent px-0", children: startContent })), jsxRuntime.jsx(react.InputGroup.Input, { ...rest, ref: setInputRef, onKeyDown: handleKeyDown, required: required, type: type, placeholder: labelFlotante ? " " : placeholder, onChange: handleChange, className: react.cn("bg-transparent p-0", SIZE_TEXT_CLASS$2[size], isInvalid ? INVALID_TEXT_CLASS$2 : "", startContent ? "ps-1.5" : "", endContent || showClear ? "pe-1.5" : "", classNames?.input) }), showClear && (jsxRuntime.jsx(react.CloseButton, { "aria-label": "Limpiar", className: react.cn("h-4 w-4 min-h-0 p-0.5 mr-1", classNames?.clearButton), onPress: handleClear })), endContent && (jsxRuntime.jsx(react.InputGroup.Suffix, { className: "border-none bg-transparent px-0", children: endContent }))] }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: react.cn(HELPER_CLASS$2, "text-left", classNames?.helperWrapper, classNames?.description), children: description })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: react.cn(HELPER_CLASS$2, "text-left", classNames?.helperWrapper, classNames?.errorMessage), children: errorMessage }))] }));
2431
+ classNames?.innerWrapper, classNames?.inputWrapper), children: [startContent && (jsxRuntime.jsx(react.InputGroup.Prefix, { className: "border-none bg-transparent px-0", children: startContent })), jsxRuntime.jsx(react.InputGroup.Input, { ...rest, ref: setInputRef, onKeyDown: handleKeyDown, required: required, type: type, placeholder: labelFlotante ? " " : placeholder, onChange: handleChange, className: react.cn(
2432
+ // `w-full min-w-0` (TECH-2459, beweossmbs QA 24-sep): `.input-group__input`
2433
+ // de v3 es `flex-1` sin ancho, así que el `<input>` aportaba su ancho
2434
+ // intrínseco (~151px) como min-content y el campo no encogía en una fila
2435
+ // flex estrecha (hex del color picker de Agenda → Colors: 180px y 35px
2436
+ // fuera de su fila; en v2, 132px). v2 ponía `w-full` en este slot
2437
+ // (`@heroui/theme@2.4.15`, `dist/components/input.js:111`), y un ancho
2438
+ // en porcentaje no cuenta para el min-content de un `<input>`.
2439
+ "w-full min-w-0 bg-transparent p-0", SIZE_TEXT_CLASS$2[size], isInvalid ? INVALID_TEXT_CLASS$2 : "", startContent ? "ps-1.5" : "", endContent || showClear ? "pe-1.5" : "", classNames?.input) }), showClear && (jsxRuntime.jsx(react.CloseButton, { "aria-label": "Limpiar", className: react.cn("h-4 w-4 min-h-0 p-0.5 mr-1", classNames?.clearButton), onPress: handleClear })), endContent && (jsxRuntime.jsx(react.InputGroup.Suffix, { className: "border-none bg-transparent px-0", children: endContent }))] }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: react.cn(HELPER_CLASS$2, "text-left", classNames?.helperWrapper, classNames?.description), children: description })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: react.cn(HELPER_CLASS$2, "text-left", classNames?.helperWrapper, classNames?.errorMessage), children: errorMessage }))] }));
2267
2440
  });
2268
2441
  Input.displayName = "Input";
2269
2442
 
@@ -2385,21 +2558,73 @@ const SHADOW_CLASS = {
2385
2558
  md: "shadow-[shadow:var(--aurora-shadow-medium)]",
2386
2559
  lg: "shadow-[shadow:var(--aurora-shadow-large)]",
2387
2560
  };
2561
+ // Geometría v2 de la raíz (slot `base` de `@heroui/theme@2.4.15`, `dist/components/card.js`):
2562
+ // `flex flex-col relative overflow-hidden`, SIN `gap`. v3 (`@heroui/styles/…/card.css:5`) pone
2563
+ // `gap-3 overflow-visible`, y eso ROMPE layouts de v2 (medido por beweossmbs con la .141): con
2564
+ // tres hijos la card crece 24px (Notificaciones automáticas: 220px frente a 192, y la última card
2565
+ // queda cortada por su contenedor), y el texto que no cabe se sale de la card en vez de
2566
+ // recortarse (cabecera de recurso de la agenda, vista Semana: 80px frente a 56 y los nombres
2567
+ // fuera). Va primero en el `cn`: un `gap-*`/`overflow-*` de la app lo retira por fusión.
2568
+ // `overflow-hidden` no quita nada que se viera: la onda ya se recorta en su propia capa
2569
+ // (`Card.css`), el anillo de foco es un `ring` (`box-shadow`, no lo recorta el `overflow` de su
2570
+ // propio nodo) y la sombra de la card se pinta fuera de su caja. Medido en la story `V2Geometry`: tres
2571
+ // hijos de 20px dan 108px (132 con el gap de v3) y el texto que desborda ya no asoma.
2572
+ const ROOT_V2_CLASS = "gap-0 overflow-hidden";
2388
2573
  // Literales del `tv` de v2 (`@heroui/theme@2.4.15`, `dist/components/card.js`), con sus tokens
2389
2574
  // traducidos a los que existen en Tailwind 4 + Aurora:
2390
2575
  // isPressable → `cursor-pointer` + compound `data-[pressed=true]:scale-[0.97]
2391
2576
  // tap-highlight-transparent` (el hundido lo hace aquí `:active`, que es lo que
2392
2577
  // marcaba `data-pressed` en un botón nativo)
2393
2578
  // disableAnimation=false → `transition-transform-background motion-reduce:transition-none`
2394
- // foco → `dataFocusVisibleClasses` (`outline-2 outline-focus outline-offset-2`)
2579
+ // foco → `dataFocusVisibleClasses` (`outline-2 outline-focus outline-offset-2`). Aquí va con el
2580
+ // MISMO anillo que los botones de v3/Aurora (`status-focused` → `focus-ring`:
2581
+ // `ring-2 ring-focus ring-offset-background` + offset 2px), no con `outline`: en
2582
+ // Tailwind 4 `outline-none` fija `--tw-outline-style: none` y `focus-visible:outline-2`
2583
+ // hereda ese estilo, así que el anillo NO se pintaba (regresión de la .141, al pasar de
2584
+ // `<button>` a `<div role="button">`). El `ring` es `box-shadow` fuera de la caja: no lo
2585
+ // recorta el `overflow-hidden` de la raíz y se compone con la sombra de la card.
2395
2586
  // isHoverable → `data-[hover=true]:bg-content2` (aquí `hover:`, react-aria ya no escribe
2396
2587
  // `data-hover` — trampa 3 de la skill `auraui-components`)
2397
2588
  // isDisabled → `opacity-disabled cursor-not-allowed` (`opacity-disabled` era 0.5 en v2)
2398
2589
  // fullWidth → `w-full`
2399
- const PRESSABLE_CLASS = "isolate cursor-pointer text-foreground outline-none focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-focus focus-visible:outline-offset-2 [-webkit-tap-highlight-color:transparent]";
2590
+ const PRESSABLE_CLASS = "isolate cursor-pointer text-foreground outline-none focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-focus focus-visible:ring-offset-2 focus-visible:ring-offset-background [-webkit-tap-highlight-color:transparent]";
2400
2591
  const PRESS_ANIMATION_CLASS = "transition-[transform,background-color] motion-reduce:transition-none active:scale-[0.97]";
2401
2592
  const HOVERABLE_CLASS = "hover:bg-content2";
2402
2593
  const DISABLED_CLASS = "opacity-50 cursor-not-allowed";
2594
+ // Controles que tienen su propia pulsación. Un clic que nace dentro de uno de ellos es SUYO, no de
2595
+ // la card: los `Button` de Aurora (react-aria) ya paran la propagación del `click` (medido en
2596
+ // jsdom con el `Button` de la librería), pero un `<button>`/`<a>` nativo o un `<input>` no.
2597
+ const CONTROL_INTERNO = 'button, a[href], input, select, textarea, summary, [role="button"], [role="link"], [role="checkbox"], [role="switch"], [role="menuitem"], [role="tab"], [role="option"]';
2598
+ function desdeControlInterno(event) {
2599
+ const target = event.target;
2600
+ if (!target || target === event.currentTarget) {
2601
+ return false;
2602
+ }
2603
+ const control = target.closest?.(CONTROL_INTERNO);
2604
+ return (!!control &&
2605
+ control !== event.currentTarget &&
2606
+ event.currentTarget.contains(control));
2607
+ }
2608
+ /**
2609
+ * Tecla → `PressEvent` con la forma del de react-aria (`pointerType: "keyboard"`, punto en el
2610
+ * centro de la card, como hace `pressEventFromClick` con un click sintético de `detail === 0`).
2611
+ */
2612
+ function pressEventFromKey(event) {
2613
+ const target = event.currentTarget;
2614
+ const rect = target.getBoundingClientRect();
2615
+ return {
2616
+ type: "press",
2617
+ pointerType: "keyboard",
2618
+ target,
2619
+ shiftKey: event.shiftKey,
2620
+ ctrlKey: event.ctrlKey,
2621
+ metaKey: event.metaKey,
2622
+ altKey: event.altKey,
2623
+ x: rect.width / 2,
2624
+ y: rect.height / 2,
2625
+ continuePropagation: () => undefined,
2626
+ };
2627
+ }
2403
2628
  /**
2404
2629
  * Card genérica siguiendo las reglas de diseño BeweOS basada en HeroUI v3.
2405
2630
  *
@@ -2422,39 +2647,83 @@ const DISABLED_CLASS = "opacity-50 cursor-not-allowed";
2422
2647
  * `isHoverable`/`isDisabled`/`fullWidth`.
2423
2648
  *
2424
2649
  * `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>`.
2650
+ * `Input.tsx`) y apunta al nodo raíz, que es siempre un `<div>` (con `isPressable`,
2651
+ * `<div role="button">`).
2426
2652
  */
2427
- const Card = $8WNJc$react.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) => {
2653
+ const Card = $8WNJc$react.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
2654
  // Como en v2 (`@heroui/card@2.2.21`, `useCard`): `disableAnimation` apaga también la onda.
2429
2655
  const sinRipple = disableRipple || disableAnimation;
2430
2656
  const { ondas, lanzar, quitar } = useRipple();
2657
+ const pulsable = isPressable && !isDisabled;
2658
+ const press = $8WNJc$react.useCallback((pressEvent) => {
2659
+ onPress?.(pressEvent);
2660
+ if (!sinRipple) {
2661
+ lanzar(pressEvent);
2662
+ }
2663
+ }, [onPress, sinRipple, lanzar]);
2431
2664
  const handleClick = $8WNJc$react.useCallback((event) => {
2665
+ // Card pulsable deshabilitada: ni `onClick` de la app, como el `disabled` nativo del
2666
+ // `<button>` que era hasta la .140.
2667
+ if (isPressable && isDisabled) {
2668
+ return;
2669
+ }
2432
2670
  onClick?.(event);
2433
- if (!isPressable || isDisabled) {
2671
+ if (!pulsable || desdeControlInterno(event)) {
2434
2672
  return;
2435
2673
  }
2436
- const pressEvent = pressEventFromClick(event);
2437
- onPress?.(pressEvent);
2438
- if (!sinRipple) {
2439
- lanzar(pressEvent);
2674
+ press(pressEventFromClick(event));
2675
+ }, [onClick, isPressable, isDisabled, pulsable, press]);
2676
+ // Teclado de un `<button>` nativo, que el `<div role="button">` no trae: Enter pulsa al
2677
+ // bajar la tecla y Espacio al soltarla. `preventDefault` en ambos: Espacio haría scroll de
2678
+ // la página y Enter podría enviar un `<form>` que envuelva la card. Solo cuando el foco está
2679
+ // EN la card: una tecla sobre un botón interno es de ese botón (react-aria ya para su
2680
+ // propagación, y esta guarda cubre también a los controles nativos).
2681
+ const handleKeyDown = $8WNJc$react.useCallback((event) => {
2682
+ onKeyDown?.(event);
2683
+ if (!pulsable || event.target !== event.currentTarget) {
2684
+ return;
2685
+ }
2686
+ if (event.key === " ") {
2687
+ event.preventDefault();
2688
+ }
2689
+ else if (event.key === "Enter") {
2690
+ event.preventDefault();
2691
+ if (!event.repeat) {
2692
+ press(pressEventFromKey(event));
2693
+ }
2440
2694
  }
2441
- }, [onClick, isPressable, isDisabled, onPress, sinRipple, lanzar]);
2442
- const combinedClassName = react.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 : "",
2695
+ }, [onKeyDown, pulsable, press]);
2696
+ const handleKeyUp = $8WNJc$react.useCallback((event) => {
2697
+ onKeyUp?.(event);
2698
+ if (!pulsable ||
2699
+ event.target !== event.currentTarget ||
2700
+ event.key !== " ") {
2701
+ return;
2702
+ }
2703
+ event.preventDefault();
2704
+ press(pressEventFromKey(event));
2705
+ }, [onKeyUp, pulsable, press]);
2706
+ const combinedClassName = react.cn(ROOT_V2_CLASS, 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
2707
  // El `className` de la app, último: gana por fusión (ver JSDoc).
2444
2708
  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) => (jsxRuntime.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 (jsxRuntime.jsxs(react.Card, { ...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 && (jsxRuntime.jsx(CapaRipple, { ondas: ondas, onFin: quitar }))] }));
2709
+ // v2 cambiaba el ELEMENTO a `<button>` con `isPressable`, y hasta la .140 esta fachada lo
2710
+ // imitaba con el `render` de v3. Se quitó porque las apps meten botones DENTRO de la card
2711
+ // pulsable (`ChannelCard` de smbs, Linda → Compartir: «Compartir»/«Integrar»), y
2712
+ // `<button>` dentro de `<button>` es HTML inválido: React avisa en consola
2713
+ // («cannot be a descendant of <button>», medido en jsdom) y el parser del navegador lo
2714
+ // reparenta. La card se queda en el `<div>` de v3 con la semántica de botón de v2
2715
+ // (`useCard`: `role: "button"` + `tabIndex: isPressable ? 0 : -1`) y el teclado a mano
2716
+ // (ver `handleKeyDown`). El `render` de la app pasa tal cual.
2717
+ const pressableProps = isPressable
2718
+ ? {
2719
+ role: "button",
2720
+ tabIndex: isDisabled ? undefined : 0,
2721
+ "aria-disabled": isDisabled || undefined,
2722
+ onKeyDown: handleKeyDown,
2723
+ onKeyUp: handleKeyUp,
2724
+ }
2725
+ : {};
2726
+ return (jsxRuntime.jsxs(react.Card, { ...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 && (jsxRuntime.jsx(CapaRipple, { ondas: ondas, onFin: quitar }))] }));
2458
2727
  });
2459
2728
  Card.displayName = "Card";
2460
2729
 
@@ -3184,7 +3453,7 @@ const CLEAR_GAP_WITH_TRIGGER_UNDERLINED_CLASS = "me-9";
3184
3453
  * ⚠️ `h-4 w-4 p-0` deja el glifo de 16px ocupando la caja entera; el `[&_svg]:m-0` quita los
3185
3454
  * márgenes negativos de v3 para que el centro del glifo coincida con el centro de la caja.
3186
3455
  */
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";
3456
+ 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
3457
  const DEFAULT_ICON = (jsxRuntime.jsx(IconComponent, { icon: "solar:magnifer-outline", className: "text-default-400 pointer-events-none shrink-0", size: "sm", "aria-hidden": true }));
3189
3458
  /**
3190
3459
  * AuraAutocomplete genérico reutilizable, migrado al `ComboBox` compound de HeroUI v3 (el
@@ -3209,7 +3478,9 @@ const AuraAutocomplete = ({ label, id, icon, placeholder, className, children, v
3209
3478
  // limpiarlo. Se reenvían tal cual al `ComboBox`, así que para el consumidor no cambia nada:
3210
3479
  // pasar `undefined` es indistinguible de no pasarlos (react-aria decide controlado/no
3211
3480
  // controlado con `!== undefined`).
3212
- isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, isInvalid, errorMessage, listboxProps, isLoading = false, allowsCustomValue, allowsEmptyCollection, ...props }) => {
3481
+ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, isInvalid, errorMessage, listboxProps, isLoading = false, allowsCustomValue, allowsEmptyCollection, isOpen, onOpenChange, popoverProps,
3482
+ // Aceptada y descartada: ver `AuraAutocompleteProps.labelPlacement`.
3483
+ labelPlacement: _labelPlacement, scrollShadowProps, ...props }) => {
3213
3484
  const generatedId = $8WNJc$react.useId();
3214
3485
  const autoId = id || generatedId;
3215
3486
  // Con `isLoading` el spinner ocupa el extremo (ver `SPINNER`): a efectos de maquetación es el
@@ -3225,6 +3496,26 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
3225
3496
  const emptyContent = listboxProps?.emptyContent ?? t.emptyContent;
3226
3497
  /** El `<input>` real, para devolverle el foco tras limpiar (ver `handleClear`). */
3227
3498
  const inputRef = $8WNJc$react.useRef(null);
3499
+ /*
3500
+ * `isOpen` controlado (ver `AuraAutocompleteProps.isOpen`). La fachada sigue el estado de
3501
+ * apertura de react-aria por su `onOpenChange` y decide con él si el popover se ve: visible solo
3502
+ * si react-aria lo tiene abierto Y la app no lo ha cerrado. `ComboBox.Popover` es el `Popover`
3503
+ * de react-aria-components, que con `isOpen` propio usa ese valor en vez del estado del
3504
+ * `ComboBox` (`Popover.mjs`: `props.isOpen != null ? localState : contextState`).
3505
+ */
3506
+ const [ariaOpen, setAriaOpen] = $8WNJc$react.useState(false);
3507
+ const handleOpenChange = (open, menuTrigger) => {
3508
+ setAriaOpen(open);
3509
+ onOpenChange?.(open, menuTrigger);
3510
+ };
3511
+ const handleInputChange = (value) => {
3512
+ // La lista está abierta para react-aria pero la app la cerró: react-aria no volverá a avisar
3513
+ // (para él ya está abierta), así que la fachada pide abrir como hacía v2 al teclear.
3514
+ if (isOpen === false && ariaOpen) {
3515
+ onOpenChange?.(true, "input");
3516
+ }
3517
+ onInputChange?.(value);
3518
+ };
3228
3519
  // `data-visible` de v2, traducido a montar/no montar: `!!state.selectedItem ||
3229
3520
  // state.inputValue?.length > 0` (`chunk-NJJHRP6D.mjs:273`), y apagado por `isDisabled`/
3230
3521
  // `isReadOnly` como en el cálculo de `isClearable` del hook (línea 20 del mismo fichero).
@@ -3258,7 +3549,7 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
3258
3549
  onInputChange?.("");
3259
3550
  inputRef.current?.focus();
3260
3551
  };
3261
- return (jsxRuntime.jsxs(react.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 && jsxRuntime.jsx(react.Label, { className: LABEL_CLASS$3, children: label }), jsxRuntime.jsxs(react.ComboBox.InputGroup, { className: [
3552
+ return (jsxRuntime.jsxs(react.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 && jsxRuntime.jsx(react.Label, { className: LABEL_CLASS$3, children: label }), jsxRuntime.jsxs(react.ComboBox.InputGroup, { className: [
3262
3553
  SIZE_CLASS$2[size],
3263
3554
  PADDING_CLASS$1[size],
3264
3555
  variant === "underlined" ? UNDERLINED_INNER_CLASS[size] : "",
@@ -3307,7 +3598,7 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
3307
3598
  * su `aria-label` traducido, y alcanzable con teclado como la ✕ de `Input`.
3308
3599
  */
3309
3600
  slot: null, "aria-label": t.clearButtonAriaLabel, className: [
3310
- CLEAR_BUTTON_CLASS,
3601
+ CLEAR_BUTTON_CLASS$2,
3311
3602
  resolvedIcon != null
3312
3603
  ? CLEAR_GAP_WITH_ICON_CLASS
3313
3604
  : variant === "underlined"
@@ -3321,7 +3612,13 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
3321
3612
  isDisabled ? ICON_DISABLED_CLASS : "",
3322
3613
  ]
3323
3614
  .filter(Boolean)
3324
- .join(" "), children: resolvedIcon })), jsxRuntime.jsx(react.ComboBox.Trigger, { className: resolvedIcon != null ? "hidden" : undefined })] }), isInvalid && errorMessage != null && errorMessage !== false && (jsxRuntime.jsx(react.FieldError, { className: ERROR_MESSAGE_CLASS, children: errorMessage })), jsxRuntime.jsx(react.ComboBox.Popover, { children: jsxRuntime.jsx(react.ListBox, { autoFocus: "first", className: OPCIONES_CLASS, renderEmptyState: hideEmptyContent
3615
+ .join(" "), children: resolvedIcon })), jsxRuntime.jsx(react.ComboBox.Trigger, { className: resolvedIcon != null ? "hidden" : undefined })] }), isInvalid && errorMessage != null && errorMessage !== false && (jsxRuntime.jsx(react.FieldError, { className: ERROR_MESSAGE_CLASS, children: errorMessage })), jsxRuntime.jsx(react.ComboBox.Popover, { ...popoverProps, isOpen: isOpen === undefined ? undefined : isOpen && ariaOpen, children: jsxRuntime.jsx(react.ListBox, { autoFocus: "first",
3616
+ // `scrollShadowProps` de v2 al contenedor con scroll (el `ListBox`): la clase
3617
+ // de la app va al final y gana por `cn`, y `overflow-y-auto` para que un
3618
+ // `max-h-*` recorte desplazando y no desbordando.
3619
+ className: scrollShadowProps?.className
3620
+ ? react.cn(OPCIONES_CLASS, "overflow-y-auto", scrollShadowProps.className)
3621
+ : OPCIONES_CLASS, style: scrollShadowProps?.style, renderEmptyState: hideEmptyContent
3325
3622
  ? undefined
3326
3623
  : () => jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS$1, children: emptyContent }), children: withV2ItemContent(
3327
3624
  // El normalizador de `utils/` está tipado solo para `ReactNode`; con una
@@ -3572,7 +3869,12 @@ const ColorPicker = $8WNJc$react.forwardRef(({ value, onChange, label, required
3572
3869
  ${error
3573
3870
  ? "border-danger dark:border-danger-400"
3574
3871
  : "border-default-300 dark:border-default-700"}
3575
- `, "aria-label": t.customColorLabel, title: t.customColorLabel }), jsxRuntime.jsx(Input, { type: "text", value: textValue, onValueChange: handleTextChange, onBlur: handleTextBlur, placeholder: t.placeholder, disabled: disabled, isDisabled: disabled, isInvalid: error || isInvalidFormat, size: "sm", variant: "bordered", classNames: {
3872
+ `, "aria-label": t.customColorLabel, title: t.customColorLabel }), jsxRuntime.jsx(Input, { type: "text", value: textValue, onValueChange: handleTextChange, onBlur: handleTextBlur, placeholder: t.placeholder, disabled: disabled, isDisabled: disabled, isInvalid: error || isInvalidFormat, size: "sm", variant: "bordered",
3873
+ // `min-w-0` (TECH-2459, beweossmbs QA 24-sep): el campo es un ítem de la fila
3874
+ // flex de la muestra de color y tiene que poder encoger en huecos estrechos
3875
+ // (Agenda → Colors: 180px). El `w-full min-w-0` del `<input>` en la fachada
3876
+ // ya lo arregla; esto es el suelo explícito en la raíz del campo.
3877
+ className: "min-w-0", classNames: {
3576
3878
  input: "uppercase font-mono text-sm",
3577
3879
  inputWrapper: "min-h-8 h-8 focus-within:!border-primary-500",
3578
3880
  }, startContent: textValue.startsWith("#") ? null : (jsxRuntime.jsx("span", { className: "text-default-400 text-sm", children: "#" })) })] }), (error || isInvalidFormat) && (jsxRuntime.jsx("p", { className: "text-xs text-danger", children: isInvalidFormat ? t.invalidColorFormat : errorText }))] }), showPredefinedColors && predefinedColors.length > 0 && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("p", { className: "text-xs font-medium text-default-600 mb-2", children: t.predefinedColorsLabel }), jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: predefinedColors.map((color) => renderColorCircle(color.value, color.name, normalizeHexColor(value) === normalizeHexColor(color.value))) })] })), enableRecentColors && recentColors.length > 0 && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("p", { className: "text-xs font-medium text-default-600 mb-2", children: t.recentColorsLabel }), jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: recentColors.map((color) => renderColorCircle(color, color, normalizeHexColor(value) === color)) })] }))] }));
@@ -6588,7 +6890,16 @@ const ITEM_COLLAPSED_CLASS = "w-11 h-11 gap-0 p-0 justify-center";
6588
6890
  // `w-full flex-1`: el slot `title` de v2 era `flex-1 text-small font-normal w-full`, y su slot
6589
6891
  // `wrapper` `w-full` — el título se comía el espacio libre y empujaba el `endContent` al borde.
6590
6892
  // El `[data-slot="label"]` de v3 es `w-fit`, que es la otra mitad de P-14.
6591
- const ITEM_TITLE_CLASS = "w-full flex-1 text-sm font-medium text-default-500 group-data-[selected=true]:text-default-600";
6893
+ //
6894
+ // `min-w-0 truncate`: v2 le ponía `truncate` al slot `title` siempre que el título fuera texto
6895
+ // (`@heroui/theme@2.4.15`, `dist/components/listbox.js`, variante `hasTitleTextChild: { true:
6896
+ // { title: "truncate" } }`; en el menú el título es siempre un string). La migración copió el slot
6897
+ // base y se dejó la variante. Sin ella, una etiqueta que no cabe no se corta:
6898
+ // medido en beweossmbs y en la story `Menu › LongLabelTruncation`, «menu_automations» ocupaba sus
6899
+ // 126,8px enteros y empujaba la chip «AI» de x=204,7 a x=222,8, 6px FUERA del ítem. `truncate` solo
6900
+ // no basta: un flex item parte de `min-width: auto` (su ancho de contenido) y no encoge por debajo;
6901
+ // `min-w-0` es lo que le deja ceder el hueco a la chip, y entonces sí corta con elipsis.
6902
+ const ITEM_TITLE_CLASS = "w-full min-w-0 flex-1 truncate text-sm font-medium text-default-500 group-data-[selected=true]:text-default-600";
6592
6903
  // TECH-2459 · G2 · fix de navegación — resolución de un ítem por su clave.
6593
6904
  //
6594
6905
  // `onSelectionChange` del `ListBox` entrega la CLAVE, no el ítem. v2 no necesitaba esto:
@@ -8930,52 +9241,23 @@ const COLOR_VAR = {
8930
9241
  warning: "var(--warning)",
8931
9242
  danger: "var(--danger)",
8932
9243
  };
8933
- /*
8934
- * TECH-2459 · F5 (P6) — El Switch se queda con el ASPECTO DE v3, por decisión del usuario.
8935
- *
8936
- * Historia corta, porque este componente ha cambiado de criterio dos veces y conviene que no
8937
- * vuelva a moverse sin dato:
8938
- *
8939
- * · F3/G1 aceptó las medidas de v3 como cambio legítimo (P-23).
8940
- * · F5/R2 las revirtió a las de v2 con un argumento cuantitativo: el tamaño era el 74-77% de
8941
- * lo que quedaba de diferencia en `schedulerow--alineacion-semana` y el 100% de los 367 px
8942
- * de `edge-cases-testing` en oscuro.
8943
- * · Esta ronda las vuelve a v3 porque el usuario, mirando los dos Storybook uno al lado del
8944
- * otro, pidió expresamente el aspecto de v3 para este componente («al switch hay que
8945
- * dejarlo como se ve en la v3»). El contrato de la ronda es explícito en que v3 puede verse
8946
- * distinto y que eso está bien; aquí manda el criterio del usuario, no la cifra.
8947
- *
8948
- * Lo que eso significa, con los literales de los dos lados a la vista:
8949
- *
8950
- * pista v2 (`@heroui/theme@2.4.15`, `dist/components/toggle.js`, slot `wrapper`)
8951
- * sm 40×24 · md 48×28 · lg 56×32, `rounded-full`, `bg-default-200`
8952
- * v3 (`@heroui/styles/dist/components/switch.css`)
8953
- * sm 32×16 · md 40×20 · lg 48×24, `rounded-lg`/`rounded-xl`, `--default`
8954
- * thumb v2 círculo `bg-white` con `shadow-small`; sm 16 · md 20 · lg 24
8955
- * v3 rectángulo redondeado; sm 16,5×12 · md 22×16 · lg 27,5×20
8956
- *
8957
- * Ya no se pisa NADA de eso: la pista, el thumb, su radio, su color y su desplazamiento salen de
8958
- * v3. Por eso desaparecieron `TRACK_CLASS`, `THUMB_CLASS`, el `bg-white`/`shadow-small` del thumb
8959
- * y el `--switch-control-bg` que forzaba el gris apagado de v2 — y con ellos la clase `group`,
8960
- * que solo existía para que el thumb pudiera leer el `data-selected` de esta raíz.
8961
- *
8962
- * Lo que SÍ se conserva, y no es aspecto:
8963
- *
8964
- * · `--switch-control-bg-checked` (ver `COLOR_VAR`): sostiene la prop `color` de la API v2, que
8965
- * v3 eliminó. Sin ella `color="success"` y compañía dejarían de pintar. El valor de
8966
- * `primary` coincide con el de v3 (`--accent` = `--color-primary`), así que el aspecto por
8967
- * defecto es el de v3 igualmente.
8968
- * · `inline-flex flex-row items-center` en la raíz: es COLOCACIÓN, no aspecto del control. v3
8969
- * usa `flex flex-col items-start` para poder apilar `description`/`field-error`, slots que
8970
- * esta fachada no renderiza; al ser **inline**, la caja participa de la línea de texto, y eso
8971
- * es lo que la sitúa donde la esperan los consumidores (medido: 1px de diferencia en un
8972
- * contenedor de bloque, y `schedule-row` se apoya en ello). Dentro de un contenedor flex da
8973
- * igual: un ítem flex se blockifica.
8974
- *
8975
- * Consecuencia asumida: la referencia v2 de las stories con Switch deja de coincidir. Declarado
8976
- * como CAMBIO-V3 en `vrt/classification-overrides.json`, con las cifras en
8977
- * `.bewe/epics/TECH-2459/findings/p6-switch-aspecto-v3.md`.
8978
- */
9244
+ // Literales COMPLETOS (sin interpolar): Tailwind de las apps los descubre escaneando `dist`.
9245
+ const SIZE_CLASSES = {
9246
+ sm: {
9247
+ control: "h-6 w-10 rounded-full",
9248
+ thumb: "h-4 w-5 ms-1 rounded-full group-data-[selected=true]/switch:ms-[calc(100%-1.5rem)]",
9249
+ },
9250
+ md: {
9251
+ control: "h-7 w-12 rounded-full",
9252
+ thumb: "h-5 w-6 ms-1 rounded-full group-data-[selected=true]/switch:ms-[calc(100%-1.75rem)]",
9253
+ },
9254
+ lg: {
9255
+ control: "h-8 w-14 rounded-full",
9256
+ thumb: "h-6 w-7 ms-1 rounded-full group-data-[selected=true]/switch:ms-[calc(100%-2rem)]",
9257
+ },
9258
+ };
9259
+ // Thumb ENCENDIDO blanco en cualquier pack y con cualquier `color` (defecto 1 del bloque de arriba).
9260
+ const THUMB_ON_CLASS = "group-data-[selected=true]/switch:bg-white";
8979
9261
  // Los subcomponentes compound de v3, adjuntos como estáticos para que la API v3 siga funcionando a
8980
9262
  // través de la fachada (`<Switch><Switch.Content><Switch.Control><Switch.Thumb/>…`). `Root` se deja
8981
9263
  // FUERA a propósito (mismo criterio que `Alert` y `Checkbox`): la raíz de v3 es justo la pieza que
@@ -9002,12 +9284,32 @@ function hasCompoundChildren$2(children) {
9002
9284
  *
9003
9285
  * Dos modos, decididos por los `children`:
9004
9286
  * - **API v2** (`children` como label, `isSelected`/`defaultSelected`, `onValueChange`, `color`,
9005
- * `size`): la fachada compone `Switch.Content`/`Switch.Control`/`Switch.Thumb` por dentro y
9006
- * repone `color` pisando `--switch-control-bg-checked`.
9287
+ * `size`, `classNames`): la fachada compone `Switch.Content`/`Switch.Control`/`Switch.Thumb` por
9288
+ * dentro, repone `color` pisando `--switch-control-bg-checked`, aplica su escala de tamaños y el
9289
+ * thumb encendido blanco (ver el bloque de la cabecera).
9007
9290
  * - **API compound v3** (`<Switch><Switch.Content>…`): pasa los children tal cual a la raíz de
9008
- * HeroUI, sin geometría ni colocación propias.
9009
- */
9010
- const SwitchFacade = ({ id, size = "md", color, children, onChange, onValueChange, className, style, ...props }) => {
9291
+ * HeroUI, sin geometría ni colocación propias: ahí manda el JSX del consumidor.
9292
+ *
9293
+ * `classNames` (slots de v2 → nodo de v3; las clases de la app van al FINAL y ganan por `cn`):
9294
+ *
9295
+ * base → `Switch.Content`, la `<label>` que contiene la pista y la etiqueta: es el MISMO papel
9296
+ * que tenía `base` en v2 (una `<label>` con esos dos hijos directos, que la app
9297
+ * coloca con `flex-row-reverse justify-between`, y toda ella clicable). Pero la raíz
9298
+ * de v3 (`SwitchField`) la envuelve con una caja `inline-flex`, y un `w-full` de la
9299
+ * app se resolvería contra esa caja encogida. Así que con `base` la raíz pasa a
9300
+ * `display: contents` (se aparta de la maqueta) y `className` viaja con `base` a la
9301
+ * `<label>`, como en v2 (`cn(classNames.base, className)`). Lo único de la raíz que
9302
+ * `contents` pierde es la opacidad de `status-disabled` (el cursor y el
9303
+ * `pointer-events` se heredan; los tokens de color, también): se repone en la
9304
+ * `<label>` con su `data-disabled`. Medido en el navegador con el JSX de smbs: la
9305
+ * fila entera (390×52) conmuta, no solo el texto y la pista. Sin `base` no cambia
9306
+ * nada.
9307
+ * wrapper → la pista (`Switch.Control`).
9308
+ * thumb → el thumb (`Switch.Thumb`).
9309
+ * label → un `<span data-slot="label">` que envuelve los `children`; solo existe si se pasa
9310
+ * `classNames.label` (sin él los children siguen PELADOS, como hasta ahora).
9311
+ */
9312
+ const SwitchFacade = ({ id, size = "md", color, children, onChange, onValueChange, className, classNames, style, ...props }) => {
9011
9313
  const generatedId = $8WNJc$react.useId();
9012
9314
  const autoId = id || generatedId;
9013
9315
  const handleChange = (isSelected) => {
@@ -9027,6 +9329,10 @@ const SwitchFacade = ({ id, size = "md", color, children, onChange, onValueChang
9027
9329
  // a `status` también en compound). Sin `color`, el encendido es el `--accent` de v3.
9028
9330
  style: color === undefined ? style : colorStyle, onChange: handleChange, children: children }));
9029
9331
  }
9332
+ const sizeClasses = SIZE_CLASSES[size] ?? SIZE_CLASSES.md;
9333
+ // Con `classNames.base`, la `<label>` (`Switch.Content`) hace de `base` de v2 y la raíz se
9334
+ // aparta de la maqueta (ver `classNames` en el JSDoc de arriba).
9335
+ const hasBase = Boolean(classNames?.base);
9030
9336
  return (jsxRuntime.jsx(react.Switch, { ...props, id: autoId, size: size,
9031
9337
  // COLOCACIÓN, no aspecto (ver el bloque de arriba): v3 usa `flex flex-col items-start`
9032
9338
  // para poder apilar `description`/`field-error`, slots que esta fachada no renderiza.
@@ -9034,10 +9340,12 @@ const SwitchFacade = ({ id, size = "md", color, children, onChange, onValueChang
9034
9340
  // (Storybook centra con `#storybook-root` en `display: block`): con `flex` el widget cae
9035
9341
  // en y=346 y con `inline-flex` en y=345, donde lo esperan los consumidores. Dentro de un
9036
9342
  // contenedor flex (el uso real, p. ej. `schedule-row`) da igual: un ítem flex se
9037
- // blockifica.
9038
- className: ["inline-flex flex-row items-center", className]
9039
- .filter(Boolean)
9040
- .join(" "), style: colorStyle, onChange: handleChange, children: jsxRuntime.jsxs(react.Switch.Content, { children: [jsxRuntime.jsx(react.Switch.Control, { children: jsxRuntime.jsx(react.Switch.Thumb, {}) }), children] }) }));
9343
+ // blockifica. `group/switch`: de aquí lee el thumb el `data-selected`.
9344
+ className: hasBase
9345
+ ? "group/switch contents"
9346
+ : react.cn("group/switch inline-flex flex-row items-center", className), style: colorStyle, onChange: handleChange, children: jsxRuntime.jsxs(react.Switch.Content, { className: hasBase
9347
+ ? react.cn("data-[disabled=true]:opacity-(--disabled-opacity)", classNames?.base, className)
9348
+ : undefined, children: [jsxRuntime.jsx(react.Switch.Control, { className: react.cn(sizeClasses.control, classNames?.wrapper), children: jsxRuntime.jsx(react.Switch.Thumb, { className: react.cn(sizeClasses.thumb, THUMB_ON_CLASS, classNames?.thumb) }) }), classNames?.label && children != null ? (jsxRuntime.jsx("span", { "data-slot": "label", className: classNames.label, children: children })) : (children)] }) }));
9041
9349
  };
9042
9350
  SwitchFacade.displayName = "Aurora.Switch";
9043
9351
  /**
@@ -9662,7 +9970,98 @@ const SPINNER_CLASS = "pointer-events-none absolute inset-y-0 end-3 my-auto text
9662
9970
  */
9663
9971
  const DEFAULT_TRANSLATIONS$8 = {
9664
9972
  emptyContent: "No hay opciones",
9973
+ searchPlaceholder: "Buscar…",
9974
+ // Mismo literal que la lista vacía de `AuraAutocomplete`: es el mismo caso (un filtro por texto
9975
+ // que no encuentra nada), así que las dos fachadas lo dicen igual.
9976
+ noResults: "No se encontraron resultados",
9977
+ // Mismo literal que la ✕ de `Input` y de `AuraAutocomplete`.
9978
+ clearButtonAriaLabel: "Limpiar",
9665
9979
  };
9980
+ /*
9981
+ * EL BUSCADOR Y LA ✕ (TECH-2459, hallazgo beweossmbs 23-sep): `isSearchable` e `isClearable`.
9982
+ *
9983
+ * Con cualquiera de los dos la fachada cambia de RAÍZ: monta el compound `Autocomplete` de v3 en
9984
+ * vez de `Select`. No es un compound distinto por dentro —`AutocompleteRoot` ES el `Select` de
9985
+ * react-aria (`@heroui/react/dist/components/autocomplete/autocomplete.js:50`, `jsx(Select, …)`),
9986
+ * así que `value`/`onChange`/`selectionMode`/`disabledKeys` y la colección funcionan igual y la
9987
+ * traducción de la API v2 de abajo no cambia ni una línea—, pero su disparador sí:
9988
+ *
9989
+ * - `Autocomplete.Trigger` es un `Group` (un `<div>`), no un `<button>`. Es lo que permite meter la
9990
+ * ✕ DENTRO del campo sin anidar dos botones (trampa nº 4 de `auraui-components`). El botón que
9991
+ * recibe el foco es el del `Autocomplete.Indicator`, que consume el `ButtonContext` del Select
9992
+ * (`autocomplete.js`, `jsx(Button, { children: indicator })`): Tab llega al chevron y desde ahí
9993
+ * las flechas/Enter abren la lista, igual que en el `Select` de siempre.
9994
+ * - `Autocomplete.Filter` (el `Autocomplete` de react-aria) conecta un `SearchField` con el
9995
+ * `ListBox`: el texto filtra la colección y las flechas mueven el foco virtual de la lista sin
9996
+ * salir del buscador. Es la receta oficial «Migrating to v3 Autocomplete (searchable select)»
9997
+ * de la guía de migración, con `useFilter({ sensitivity: "base" })`: sin distinguir mayúsculas
9998
+ * ni acentos («cancun» encuentra «Cancún»).
9999
+ *
10000
+ * Sin ninguna de las dos props la fachada sigue montando `Select`, exactamente como antes: los
10001
+ * 107 `Select` de beweossmbs que no las pasan no cambian ni un nodo.
10002
+ *
10003
+ * Por qué la ✕ va también por aquí y no sobre el `Select`: el disparador de `Select` es un
10004
+ * `<button>` (no admite otro dentro) y la fachada no alcanza el estado de react-aria para vaciar la
10005
+ * selección de un campo no controlado (`SelectStateContext` no lo reexporta `@heroui/react`, e
10006
+ * importarlo de `react-aria-components` metería una SEGUNDA copia del contexto en el bundle, que no
10007
+ * casaría con la de HeroUI). `Autocomplete.ClearButton` sí lo alcanza: vacía la selección con
10008
+ * `state.selectionManager.setSelectedKeys(new Set())` y llama a `onClear`.
10009
+ */
10010
+ /** Hueco del valor cuando la ✕ va a su derecha: el hueco del chevron lo reserva la ✕. */
10011
+ const VALUE_ROOM_CLEARABLE_CLASS = "me-1 min-w-0 truncate";
10012
+ /*
10013
+ * La ✕. `.autocomplete__clear-button` de v3 ya la pinta como v2 (glifo sin caja, `bg-transparent`,
10014
+ * gris `text-muted`) y la esconde con `opacity-0` cuando no hay nada elegido. v2 no la montaba sin
10015
+ * selección (`@heroui/select@2.4.33`, `chunk-BJWIL32D.mjs:64`: `isClearable &&
10016
+ * state.selectedItems?.length`), así que aquí se retira del flujo en ese estado en vez de dejar un
10017
+ * hueco invisible de 20px.
10018
+ *
10019
+ * `me-5`: el chevron es `absolute end-3` (12px del borde, 16px de ancho) y la ✕ va en flujo, que
10020
+ * acaba en el relleno del trigger (`px-3`, 12px). Sin margen caería ENCIMA del chevron; con 20px su
10021
+ * canto derecho queda a 4px del izquierdo del chevron.
10022
+ */
10023
+ const CLEAR_BUTTON_CLASS$1 = "z-10 me-5 data-[empty=true]:hidden";
10024
+ /*
10025
+ * EL FOCO DEL TECLADO (hallazgo del coordinador con Playwright, 23-sep). En la raíz `Autocomplete`
10026
+ * el único elemento tabulable es el botón del chevron —el `Button` de react-aria que monta
10027
+ * `Autocomplete.Indicator`, sin clase propia ni forma de pasarle una—, y en el flujo mide **0×0**:
10028
+ * su único hijo es el SVG, que es `absolute`. El foco caía en una caja vacía (el borde del disparador
10029
+ * sí cambiaba, por `focus-within`, pero el elemento enfocado no tenía caja para un lector de
10030
+ * pantalla, un magnificador o un anillo propio).
10031
+ *
10032
+ * Se estira ese botón sobre el disparador entero (`absolute inset-0`, mismo radio): el elemento
10033
+ * enfocado pasa a medir lo mismo que el disparador, como el `<button>` del `Select` normal, y el
10034
+ * SVG sigue en su sitio porque su contenedor posicionado cubre la misma caja. Pulsar en cualquier
10035
+ * punto sigue abriendo (el `Group` ya abría al pulsar, y el botón ya estaba DENTRO del grupo).
10036
+ * La ✕ va por encima (`z-10` en `CLEAR_BUTTON_CLASS`) para seguir recibiendo su clic.
10037
+ *
10038
+ * Se apunta por estructura porque es la única vía: `button:not([data-slot])` es ese botón, ya que
10039
+ * la ✕ lleva `data-slot="autocomplete-clear-button"`.
10040
+ */
10041
+ const INDICATOR_BUTTON_OVERLAY_CLASS = [
10042
+ "[&>button:not([data-slot])]:absolute",
10043
+ "[&>button:not([data-slot])]:inset-0",
10044
+ "[&>button:not([data-slot])]:rounded-[inherit]",
10045
+ "[&>button:not([data-slot])]:outline-none",
10046
+ "[&>button:not([data-slot])]:cursor-[inherit]",
10047
+ ].join(" ");
10048
+ /*
10049
+ * El foco en el disparador `Group`. El `FOCUS_BORDER_CLASS` del Select casa `aria-expanded` y
10050
+ * `:focus` del `<button>`; aquí el disparador es un `<div>` que no lleva ninguno de los dos: el foco
10051
+ * lo tiene el botón del chevron, DENTRO (`focus-within`), y el estado abierto lo escribe v3 en la
10052
+ * RAÍZ (`data-open="true"` en `div.autocomplete`), así que va por un `group` con nombre.
10053
+ */
10054
+ const FOCUS_BORDER_GROUP_CLASS = "focus-within:border-primary-500! group-data-[open=true]/aura-select:border-primary-500!";
10055
+ /*
10056
+ * El buscador del desplegable. `.autocomplete__popover` de v3 ya lo coloca (`[data-slot=
10057
+ * "search-field"] { px-3 py-1 }`, `pt-2` en el popover, `overflow-hidden` + lista con su propio
10058
+ * scroll, de modo que el buscador no se va al desplazar la lista). Solo se retira el anillo de
10059
+ * foco de v3 (`status-focused-field` → `ring-2`), por lo mismo que en todos los campos de la
10060
+ * librería (`FIELD_FOCUS_CLASS`): el foco lo marca el borde, en primario.
10061
+ */
10062
+ const SEARCH_GROUP_CLASS = "w-full ring-0 ring-offset-0 outline-0 focus-within:border-primary-500";
10063
+ /** El `ListBox` es el contenedor que se desplaza cuando el consumidor le fija alto. */
10064
+ const LISTBOX_SCROLL_CLASS = "overflow-y-auto";
9666
10065
  // Medidas del estado vacío. Se toman las del ITEM de v3 (`@heroui/styles`,
9667
10066
  // `dist/components/list-box-item.css:6`: `min-h-9 … px-2 py-1.5`) y no las del slot `emptyContent`
9668
10067
  // de v2, y es una decisión medida: el literal de v2 era
@@ -9934,7 +10333,9 @@ size = "md", variant = "bordered", radius = "md",
9934
10333
  //
9935
10334
  // Por eso este componente sigue sin `SelectTranslations`: la corrección consiste precisamente en
9936
10335
  // 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,
10336
+ placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, onSelectionChange, disabledKeys, isInvalid = false, isRequired = false,
10337
+ // Alias nativo de `isRequired` (ver `SelectProps.required`).
10338
+ required = false, isDisabled = false,
9938
10339
  // `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
9939
10340
  // (verificado en el paquete v2 compilado, `dist/components/input.js`:
9940
10341
  // `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
@@ -9944,14 +10345,35 @@ placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, o
9944
10345
  // columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
9945
10346
  fullWidth = true, isLoading = false,
9946
10347
  // 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, }) => {
10348
+ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = false, renderValue, startContent, listboxProps, onChange,
10349
+ // Aceptada y descartada: ver `SelectProps.labelPlacement`.
10350
+ 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
10351
  const generatedId = $8WNJc$react.useId();
9949
10352
  const selectId = id || generatedId;
9950
- const requiredField = isRequired;
10353
+ const requiredField = isRequired || required;
9951
10354
  const t = { ...DEFAULT_TRANSLATIONS$8, ...translations };
10355
+ // Raíz `Autocomplete` (buscador y/o ✕) o `Select` de siempre: ver el bloque «EL BUSCADOR Y LA ✕».
10356
+ const usesAutocompleteShell = isSearchable || isClearable;
10357
+ // Filtro de react-aria sin mayúsculas ni acentos. El hook va fuera de la condición (reglas de
10358
+ // hooks); sin `isSearchable` no se usa.
10359
+ const { contains } = react.useFilter({ sensitivity: "base" });
10360
+ // Texto del buscador, en la fachada para saber qué mensaje poner en la lista vacía y para
10361
+ // vaciarlo al cerrar (abrir de nuevo = lista completa, que es lo que hacía el buscador manual
10362
+ // de `timezone-search-select` con su `onOpenChange`).
10363
+ const [searchText, setSearchText] = $8WNJc$react.useState("");
9952
10364
  // 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;
10365
+ // que es el default de la fachada (el de v2 era el literal inglés "No items."). Con el buscador
10366
+ // escrito, la lista vacía significa «sin resultados», no «sin opciones».
10367
+ const emptyContent = isSearchable && searchText.trim() !== ""
10368
+ ? t.noResults
10369
+ : (listboxProps?.emptyContent ?? t.emptyContent);
10370
+ const resolvedSearchPlaceholder = searchPlaceholder ?? t.searchPlaceholder;
10371
+ const handleOpenChange = (open) => {
10372
+ if (!open) {
10373
+ setSearchText("");
10374
+ }
10375
+ onOpenChange?.(open);
10376
+ };
9955
10377
  // Estado propio SOLO para `multiple` + `disallowEmptySelection` sin controlar: para rechazar el
9956
10378
  // cambio que vaciaría la selección, la fachada tiene que ser dueña del valor. En los demás casos
9957
10379
  // no se lee (el hook va fuera de la condición por las reglas de hooks).
@@ -9963,55 +10385,97 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
9963
10385
  // En v2 el estado inválido MANDABA también con el campo enfocado
9964
10386
  // (`group-data-[focus=true]:!border-danger`), así que el borde de foco no se aplica cuando
9965
10387
  // 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,
10388
+ isInvalid
10389
+ ? INVALID_VARIANT_CLASS[variant]
10390
+ : usesAutocompleteShell
10391
+ ? FOCUS_BORDER_GROUP_CLASS
10392
+ : FOCUS_BORDER_CLASS, FIELD_FOCUS_CLASS$1, FIELD_SHADOW_CLASS$1, usesAutocompleteShell ? INDICATOR_BUTTON_OVERLAY_CLASS : "",
9967
10393
  // Incondicional, como el slot `trigger` de v2 (`"relative px-3 gap-3 w-full inline-flex …"`,
9968
10394
  // sin variante que lo condicione): el disparador llena siempre su raíz, y es la raíz la que
9969
10395
  // responde a `fullWidth` y al ancho que pase el consumidor. v3 hace lo mismo por su cuenta
9970
10396
  // con `.select__trigger--full-width`, pero esa regla vive en la capa `components` y la pisa
9971
10397
  // cualquier utilidad.
9972
10398
  "w-full", classNames?.trigger);
10399
+ // `id`/`textValue` desde la `key` de React: ver `utils/collection-items.ts` — sin esto un select
10400
+ // múltiple no pinta lo elegido y uno controlado arranca vacío. `withV2ItemContent` traduce
10401
+ // `description`/`startContent`/`endContent`/`classNames` del item y `title`/`showDivider` de la
10402
+ // sección a la anatomía de v3 (ver `_internal/v2-collection.tsx` para el orden), y
10403
+ // `withItemIndicator` monta el checkmark del seleccionado, que en v3 es un subcomponente aparte
10404
+ // (ver `_internal/with-item-indicator.tsx`, y ahí por qué va en este orden).
10405
+ const decorate = (nodes) => withItemIndicator(withV2ItemContent(withCollectionItemIds(nodes)));
10406
+ // Colección dinámica (`items` + función, la forma de `Profile.tsx`). La función se envuelve para
10407
+ // decorar lo que devuelva, y el paso de `withCollectionItemIds` importa aquí MÁS que en la forma
10408
+ // estática: react-aria toma la clave de una colección dinámica de `rendered.props.id ??
10409
+ // item.key ?? item.id`, nunca de la `key` de React. Sin reponer el `id` desde la `key`, las
10410
+ // opciones de `Profile.tsx` (objetos `{ value: "es", id: 1 }` con `<SelectItem key={value}>`)
10411
+ // se llamarían `1`/`2` y ni `defaultSelectedKeys={["es"]}` casaría ni `onSelectionChange`
10412
+ // devolvería la clave de la app.
10413
+ const collection = typeof children === "function"
10414
+ ? (item) => decorate(children(item))
10415
+ : decorate(children);
10416
+ const listBox = (jsxRuntime.jsx(react.ListBox
10417
+ // v2 `items`: react-aria recorre la colección y llama a la función de `children`.
10418
+ , {
10419
+ // v2 `items`: react-aria recorre la colección y llama a la función de `children`.
10420
+ items: items,
10421
+ // v2: `classNames.listboxWrapper` y `scrollShadowProps.className` iban al mismo slot
10422
+ // (`cn(classNames.listboxWrapper, scrollShadowProps.className)`, `chunk-YFRCT47S.mjs:
10423
+ // 321-331`), en ese orden.
10424
+ className: classNames?.listboxWrapper || scrollShadowProps?.className
10425
+ ? react.cn(LISTBOX_SCROLL_CLASS, classNames?.listboxWrapper, scrollShadowProps?.className)
10426
+ : undefined, style: scrollShadowProps?.style,
10427
+ // El hueco que v2 llenaba con su `emptyContent` (ver el bloque de
10428
+ // `DEFAULT_TRANSLATIONS`). `renderEmptyState` es la vía de v3: el `ListBox` de
10429
+ // react-aria solo pinta algo con la colección vacía si se le pasa, y envuelve el
10430
+ // resultado en un `<div role="option" style="display:contents">`
10431
+ // (`react-aria-components/dist/private/ListBox.mjs:222-228`), o sea que la caja que se
10432
+ // ve es la nuestra.
10433
+ renderEmptyState: () => (jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS, children: emptyContent })), children: collection }));
10434
+ // Las piezas del disparador son las mismas en las dos raíces; solo cambia de qué compound salen
10435
+ // (ver el bloque «EL BUSCADOR Y LA ✕»). `Autocomplete.Value` y `Select.Value` son el mismo
10436
+ // `SelectValue` de react-aria con otra clase base, así que reciben las mismas props.
10437
+ const ValuePart = usesAutocompleteShell
10438
+ ? react.Autocomplete.Value
10439
+ : react.Select.Value;
10440
+ const triggerContent = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [startContent != null && startContent !== false && (jsxRuntime.jsx("span", { "data-slot": "start-content", className: START_CONTENT_CLASS, children: startContent })), jsxRuntime.jsx(ValuePart, { className: react.cn(SIZE_TEXT_CLASS$1[size], isMultiline
10441
+ ? MULTILINE_VALUE_ROOM_CLASS
10442
+ : isClearable
10443
+ ? VALUE_ROOM_CLEARABLE_CLASS
10444
+ : VALUE_ROOM_CLASS, HIDE_ITEM_EXTRAS_CLASS, isInvalid ? INVALID_TEXT_CLASS$1 : ""), children: renderValue
10445
+ ? // `renderValue` de v2 (`chunk-BJWIL32D.mjs`): sin selección, el placeholder;
10446
+ // con selección, la función con los items en la forma de v2. El nodo de la
10447
+ // colección de v3 trae los mismos campos que el de v2, así que el mapeo es
10448
+ // 1:1. `defaultChildren` es aquí el placeholder (o `""`, ver arriba).
10449
+ ({ state, isPlaceholder, defaultChildren }) => {
10450
+ if (isPlaceholder) {
10451
+ return defaultChildren;
10452
+ }
10453
+ const items = state.selectedItems.map((item) => ({
10454
+ key: item.key,
10455
+ data: item.value,
10456
+ type: item.type,
10457
+ props: item.props,
10458
+ textValue: item.textValue,
10459
+ rendered: item.rendered,
10460
+ "aria-label": item["aria-label"],
10461
+ }));
10462
+ return renderValue(items);
10463
+ }
10464
+ : undefined }), isClearable && (jsxRuntime.jsx(react.Autocomplete.ClearButton, { "aria-label": t.clearButtonAriaLabel, className: CLEAR_BUTTON_CLASS$1 })), usesAutocompleteShell ? (
10465
+ /* En el `Autocomplete` el chevron es el BOTÓN que recibe el foco (ver el bloque
10466
+ «EL BUSCADOR Y LA ✕»), así que el spinner va DENTRO de él en vez de sustituirlo:
10467
+ sin el botón el campo no se alcanzaría con el teclado. `Autocomplete.Indicator` clona
10468
+ su hijo con la clase del indicador (`absolute inset-y-0 end-2 my-auto`), así que el
10469
+ spinner cae en el sitio del chevron sin clase propia. */
10470
+ jsxRuntime.jsx(react.Autocomplete.Indicator, { className: "end-3", children: isLoading ? (jsxRuntime.jsx(react.Spinner, { "aria-hidden": true, "data-slot": "select-spinner", size: "sm", color: "current" })) : undefined })) : isLoading ? (jsxRuntime.jsx(react.Spinner, { "aria-hidden": true, "data-slot": "select-spinner", size: "sm", color: "current", className: SPINNER_CLASS })) : (
10471
+ /* `.select__indicator` de v3 va a `end-2` (8px del borde interior); el
10472
+ `selectorIcon` de v2 iba a `end-3` (12px). Medido en los PNG: la tinta del chevron
10473
+ acaba a 17px del borde derecho en la referencia y a 13px con el valor de v3 — 4px
10474
+ de más. */
10475
+ jsxRuntime.jsx(react.Select.Indicator, { className: "end-3" }))] }));
9973
10476
  const inner = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS$1, hasHelper
9974
10477
  ? LABEL_OFFSET_HELPER_CLASS[size]
9975
- : LABEL_OFFSET_CLASS[size], isInvalid ? INVALID_LABEL_CLASS$1 : "", classNames?.label), children: label })), jsxRuntime.jsxs(react.Select.Trigger, { className: triggerClassName, children: [startContent != null && startContent !== false && (jsxRuntime.jsx("span", { "data-slot": "start-content", className: START_CONTENT_CLASS, children: startContent })), jsxRuntime.jsx(react.Select.Value, { className: react.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 ? (jsxRuntime.jsx(react.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
- jsxRuntime.jsx(react.Select.Indicator, { className: "end-3" }))] }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: `${HELPER_CLASS$1} text-left`, children: description })), jsxRuntime.jsx(react.Select.Popover, { className: classNames?.popoverContent, children: jsxRuntime.jsx(react.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: () => (jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS, children: emptyContent })), children: withItemIndicator(withV2ItemContent(withCollectionItemIds(children))) }) }), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: `${HELPER_CLASS$1} text-left`, children: errorMessage }))] }));
10478
+ : LABEL_OFFSET_CLASS[size], isInvalid ? INVALID_LABEL_CLASS$1 : "", classNames?.label), children: label })), usesAutocompleteShell ? (jsxRuntime.jsx(react.Autocomplete.Trigger, { className: triggerClassName, children: triggerContent })) : (jsxRuntime.jsx(react.Select.Trigger, { className: triggerClassName, children: triggerContent })), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: `${HELPER_CLASS$1} text-left`, children: description })), usesAutocompleteShell ? (jsxRuntime.jsx(react.Autocomplete.Popover, { className: classNames?.popoverContent, children: isSearchable ? (jsxRuntime.jsxs(react.Autocomplete.Filter, { filter: contains, inputValue: searchText, onInputChange: setSearchText, children: [jsxRuntime.jsx(react.SearchField, { autoFocus: true, "aria-label": resolvedSearchPlaceholder, fullWidth: true, children: jsxRuntime.jsxs(react.SearchField.Group, { className: SEARCH_GROUP_CLASS, children: [jsxRuntime.jsx(react.SearchField.SearchIcon, {}), jsxRuntime.jsx(react.SearchField.Input, { placeholder: resolvedSearchPlaceholder })] }) }), listBox] })) : (listBox) })) : (jsxRuntime.jsx(react.Select.Popover, { className: classNames?.popoverContent, children: listBox })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: `${HELPER_CLASS$1} text-left`, children: errorMessage }))] }));
10015
10479
  const commonProps = {
10016
10480
  id: selectId,
10017
10481
  name,
@@ -10037,7 +10501,14 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
10037
10501
  isDisabled,
10038
10502
  isOpen,
10039
10503
  defaultOpen,
10040
- onOpenChange,
10504
+ onOpenChange: handleOpenChange,
10505
+ // v2 `closeOnSelect` → `shouldCloseOnSelect` de react-stately, que ya trae el mismo default
10506
+ // por modo (`useSelectState.mjs:23`: cierra en `single`, se queda abierto en `multiple`).
10507
+ // `undefined` deja ese default intacto.
10508
+ ...(closeOnSelect === undefined
10509
+ ? {}
10510
+ : { shouldCloseOnSelect: closeOnSelect }),
10511
+ onClear,
10041
10512
  fullWidth,
10042
10513
  // `cn`, no una concatenación: el `className` del consumidor tiene que GANAR a las clases
10043
10514
  // de la fachada, y con una cadena suelta llegan las dos al DOM y decide el orden en que
@@ -10050,7 +10521,13 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
10050
10521
  className: react.cn(FIELD_STACK_CLASS$1[size],
10051
10522
  // `w-full` y `min-w-40` son las dos ramas EXCLUYENTES de `fullWidth` en el `tv` de v2
10052
10523
  // (ver `ROOT_MIN_WIDTH_CLASS`).
10053
- fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS, className),
10524
+ fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS,
10525
+ // Grupo con nombre para el borde de foco del disparador `Group` (ver
10526
+ // `FOCUS_BORDER_GROUP_CLASS`). Sin la raíz `Autocomplete` no hace falta.
10527
+ usesAutocompleteShell ? "group/aura-select" : "",
10528
+ // v2 `classNames.base`: la raíz, igual que `className`, y por eso delante de él (en v2
10529
+ // los dos acababan en `slots.base({ class: cn(classNames?.base, className) })`).
10530
+ classNames?.base, className),
10054
10531
  };
10055
10532
  if (selectionMode === "multiple") {
10056
10533
  // `disallowEmptySelection` en `multiple`: react-stately lo fuerza a `false` en este modo
@@ -10058,7 +10535,12 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
10058
10535
  // la prop), así que la guarda la pone la fachada: el cambio que dejaría la selección vacía
10059
10536
  // no se propaga. Controlado basta con no avisar; sin controlar, la fachada lleva el valor.
10060
10537
  const ownsValue = disallowEmptySelection && selectedKeys === undefined;
10061
- return (jsxRuntime.jsx(react.Select, { ...commonProps, selectionMode: "multiple", value: ownsValue ? ownMultiple : toArray(selectedKeys), defaultValue: ownsValue ? undefined : toArray(defaultSelectedKeys), onChange: (keys) => {
10538
+ const multipleProps = {
10539
+ ...commonProps,
10540
+ selectionMode: "multiple",
10541
+ value: ownsValue ? ownMultiple : toArray(selectedKeys),
10542
+ defaultValue: ownsValue ? undefined : toArray(defaultSelectedKeys),
10543
+ onChange: (keys) => {
10062
10544
  if (disallowEmptySelection && keys.length === 0) {
10063
10545
  return;
10064
10546
  }
@@ -10066,7 +10548,10 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
10066
10548
  setOwnMultiple(keys);
10067
10549
  }
10068
10550
  onSelectionChange?.(new Set(keys));
10069
- }, children: inner }));
10551
+ onChange?.({ target: { name, value: keys.join(",") } });
10552
+ },
10553
+ };
10554
+ return usesAutocompleteShell ? (jsxRuntime.jsx(react.Autocomplete, { ...multipleProps, children: inner })) : (jsxRuntime.jsx(react.Select, { ...multipleProps, children: inner }));
10070
10555
  }
10071
10556
  // `Array.from` cubre las tres formas de `SelectSelection` sin ramas nuevas: `"all"` sigue
10072
10557
  // cayendo a `null` (v3 no admite "todo" en selección única) y un array se lee igual que un
@@ -10081,7 +10566,17 @@ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = fal
10081
10566
  : defaultSelectedKeys === "all"
10082
10567
  ? null
10083
10568
  : (Array.from(defaultSelectedKeys)[0] ?? null);
10084
- return (jsxRuntime.jsx(react.Select, { ...commonProps, value: singleValue, defaultValue: singleDefault, onChange: (key) => onSelectionChange?.(key == null ? new Set() : new Set([key])), children: inner }));
10569
+ const singleProps = {
10570
+ ...commonProps,
10571
+ value: singleValue,
10572
+ defaultValue: singleDefault,
10573
+ onChange: (key) => {
10574
+ onSelectionChange?.(key == null ? new Set() : new Set([key]));
10575
+ // v2 llamaba a `onChange` después de `onSelectionChange` (ver `SelectChangeEvent`).
10576
+ onChange?.({ target: { name, value: key == null ? "" : String(key) } });
10577
+ },
10578
+ };
10579
+ return usesAutocompleteShell ? (jsxRuntime.jsx(react.Autocomplete, { ...singleProps, children: inner })) : (jsxRuntime.jsx(react.Select, { ...singleProps, children: inner }));
10085
10580
  };
10086
10581
 
10087
10582
  const defaultTranslations$4 = {
@@ -10576,6 +11071,46 @@ function transformBody(body) {
10576
11071
  return $8WNJc$react.cloneElement(body, undefined, ...spread(mapNodes(children, withSelectionCell)));
10577
11072
  }
10578
11073
 
11074
+ /**
11075
+ * TECH-2459 (beweossmbs, 24-sep: «Sessions history» de finanzas) — la celda del ESTADO VACÍO.
11076
+ *
11077
+ * En v3 `renderEmptyState` es un render prop de react-aria que pinta lo que devuelva dentro de
11078
+ * `<tbody data-empty="true"><tr role="row"><td role="rowheader" colspan>`
11079
+ * (`react-aria-components/dist/private/Table.mjs:886-915`), y ese `<td>` NO lleva `.table__cell`:
11080
+ * se queda sin el `bg-surface`, el padding ni el color de las celdas. Medido en la story
11081
+ * `EmptyState` con la cadena pelada que devuelven 12 de las 13 tablas de beweossmbs: 20 px de
11082
+ * alto, `padding: 0`, `text-align: start` (el texto pegado arriba a la izquierda) y fondo
11083
+ * transparente, o sea el gris de la raíz `table-root--primary` asomando.
11084
+ *
11085
+ * No es una regresión de la librería: ni la fachada ni `Table.Content` pintaron nunca ese estado
11086
+ * (`git log -S renderEmptyState|data-empty|emptyContent -- src/components/table` → 0 commits); lo
11087
+ * que se había arreglado era backoffice, en su propia app (TECH-2298, `49f2b35`: cada
11088
+ * `renderEmptyState` devuelve `<div className="flex h-40 items-center justify-center
11089
+ * text-default-500">`). Aquí se resuelve para todas las apps, en la única pieza por la que pasan
11090
+ * todas las tablas.
11091
+ *
11092
+ * Qué se le da a la celda, con tokens de v3 (no se busca la métrica de v2):
11093
+ * - `h-40`: aire. En una celda de tabla `height` actúa de alto MÍNIMO, así que un nodo propio más
11094
+ * alto sigue mandando, y uno de `h-40` como el de backoffice mide lo mismo (no se suma: por
11095
+ * eso es alto y no padding vertical).
11096
+ * - `px-4 text-center align-middle`: centrado en los dos ejes; `px-4` es el lateral de `.table__cell`.
11097
+ * - `text-sm text-default-500`: el cuerpo de las celdas de v3 (`text-sm`) en el gris atenuado que
11098
+ * ya usan las apps para este texto (backoffice), legible sobre el blanco (contraste AA).
11099
+ * - `bg-surface`: el fondo del cuerpo (`.table__cell` de `table.css`), no el gris de la raíz. Las
11100
+ * esquinas redondeadas ya las pone `table.css` (primera/última celda de la primera/última fila).
11101
+ *
11102
+ * Selector por `data-empty`, que react-aria solo escribe con el cuerpo vacío. Cadenas literales
11103
+ * completas: Tailwind solo emite lo que encuentra escrito entero.
11104
+ */
11105
+ const EMPTY_STATE_CLASS = [
11106
+ "[&>tbody[data-empty]>tr>td]:h-40",
11107
+ "[&>tbody[data-empty]>tr>td]:px-4",
11108
+ "[&>tbody[data-empty]>tr>td]:text-center",
11109
+ "[&>tbody[data-empty]>tr>td]:align-middle",
11110
+ "[&>tbody[data-empty]>tr>td]:text-sm",
11111
+ "[&>tbody[data-empty]>tr>td]:text-default-500",
11112
+ "[&>tbody[data-empty]>tr>td]:bg-surface",
11113
+ ].join(" ");
10579
11114
  /** Default de v2 (`use-table`, `@heroui/table@2.2.32`), sin reinterpretar. */
10580
11115
  function defaultShowCheckboxes(props) {
10581
11116
  const behavior = props.selectionBehavior ?? "toggle";
@@ -10618,7 +11153,8 @@ function TableContent({ children, isHeaderSticky, showSelectionCheckboxes, ...pr
10618
11153
  return child;
10619
11154
  });
10620
11155
  // Esparcido y no `{content}`: ver `spread` (un array como hijo único exigiría claves).
10621
- return $8WNJc$react.createElement(react.Table.Content, props, ...spread(content));
11156
+ // La clase del estado vacío va delante: la de la app, detrás, gana si pisa el mismo grupo.
11157
+ return $8WNJc$react.createElement(react.Table.Content, { ...props, className: react.cn(EMPTY_STATE_CLASS, props.className) }, ...spread(content));
10622
11158
  }
10623
11159
  TableContent.displayName = "Aurora.Table.Content";
10624
11160
 
@@ -10649,6 +11185,24 @@ function isPart$2(node, parts) {
10649
11185
  function withSticky(isHeaderSticky, root) {
10650
11186
  return isHeaderSticky === undefined ? (root) : (jsxRuntime.jsx(TableStickyContext.Provider, { value: isHeaderSticky, children: root }));
10651
11187
  }
11188
+ /**
11189
+ * TECH-2459 (beweossmbs, 24-sep) — barra de scroll NATIVA en el contenedor que inserta la fachada.
11190
+ *
11191
+ * `table.css:20` le aplica a `.table__scroll-container` la utilidad `scrollbar` de v3, que con los
11192
+ * tokens de `@heroui/styles` resuelve a `scrollbar-width: thin` y un pulgar al 15 % de opacidad
11193
+ * sobre carril transparente (medido en la story `Overflow`:
11194
+ * `oklch(0.2103 0.0059 285.89 / 0.15) rgba(0,0,0,0)`). Con barras clásicas (Windows, o macOS con
11195
+ * «mostrar barras: siempre») casi no se ve, y el usuario concluye que la tabla no tiene scroll
11196
+ * horizontal aunque lo tenga: es affordance, no estética. `scrollbar-default` (utilidad de la
11197
+ * propia v3, `utilities/index.css:71`) devuelve la barra del sistema; al ser capa `utilities`
11198
+ * gana a la regla de `components` sin `!important`.
11199
+ *
11200
+ * Solo en el contenedor que monta la fachada. Si la app monta su propio `Table.ScrollContainer`
11201
+ * (o un `ResizableContainer`), la fachada no toca nada y manda lo que la app escriba; y una clase
11202
+ * de la app sobre el root dirigida al contenedor (`[&_.table__scroll-container]:scrollbar-thin`)
11203
+ * gana por especificidad.
11204
+ */
11205
+ const SCROLL_CONTAINER_CLASS = "scrollbar-default";
10652
11206
  const TableFacade = ({ children, isHeaderSticky, ...props }) => {
10653
11207
  const nodes = $8WNJc$react.Children.toArray(children);
10654
11208
  // Sin hijos, o con un contenedor de scroll ya montado a mano: el root tal cual.
@@ -10657,7 +11211,7 @@ const TableFacade = ({ children, isHeaderSticky, ...props }) => {
10657
11211
  }
10658
11212
  const scrollable = nodes.filter((node) => !isPart$2(node, OUTSIDE_SCROLL));
10659
11213
  const outsideScroll = nodes.filter((node) => isPart$2(node, OUTSIDE_SCROLL));
10660
- return withSticky(isHeaderSticky, jsxRuntime.jsxs(react.Table, { ...props, children: [jsxRuntime.jsx(react.Table.ScrollContainer, { children: scrollable }), outsideScroll] }));
11214
+ return withSticky(isHeaderSticky, jsxRuntime.jsxs(react.Table, { ...props, children: [jsxRuntime.jsx(react.Table.ScrollContainer, { className: SCROLL_CONTAINER_CLASS, children: scrollable }), outsideScroll] }));
10661
11215
  };
10662
11216
  TableFacade.displayName = "Aurora.Table";
10663
11217
  /**
@@ -11122,6 +11676,28 @@ const VARIANT_CLASS = {
11122
11676
  // placeholder en el gris `default-500` (`113,113,122`) pese a ser un campo inválido.
11123
11677
  const INVALID_LABEL_CLASS = "text-danger";
11124
11678
  const INVALID_TEXT_CLASS = "text-danger";
11679
+ // ── `isClearable` / `onClear` / `classNames.clearButton` (API v2, repuesta) ──────────────────
11680
+ //
11681
+ // El `Textarea` de v2 ES el `Input` con `isMultiline` (mismo `useInput`), así que heredaba su botón
11682
+ // de limpiar: `isClearable = !!onClear || originalProps.isClearable` y `handleClear` pone el valor
11683
+ // a "" (lo que dispara `onValueChange("")`), llama a `onClear` y devuelve el foco
11684
+ // (`@heroui/input@2.4.33`, `dist/chunk-KQW2TNLT.mjs`); Escape con valor también limpia. Uso real:
11685
+ // `campaigns/**/AudienceReachStep.tsx` en beweossmbs (`isClearable` + `onClear` +
11686
+ // `classNames.clearButton`), el único de los 33 `Textarea` de la app que lo pide.
11687
+ //
11688
+ // Posición, literal del `tv` de v2 (`@heroui/theme@2.4.15`, `dist/components/input.js`):
11689
+ // · `isMultiline.clearButton` → "absolute top-2 right-2 rtl:right-auto rtl:left-2 z-10": la X va
11690
+ // ARRIBA a la derecha, no centrada como en el campo de una línea (aquí `end-2` = right/left
11691
+ // según la dirección, lo mismo que el par `right-2 rtl:left-2`);
11692
+ // · `isClearable.input` → "pe-6": el texto deja sitio a la X aunque todavía no se vea,
11693
+ // para que no salte al aparecer.
11694
+ // Lo relativo en v2 era el `inputWrapper`, que aquí no existe (el `<textarea>` es la caja), así que
11695
+ // se añade un envoltorio `relative` SOLO cuando el campo es limpiable: sin la prop el DOM no cambia.
11696
+ // El glifo es el `CloseButton` de v3, el mismo que ya usa la fachada `Input` (§9.1: verse como v3),
11697
+ // y `classNames.clearButton` va el último para que las clases del consumidor ganen.
11698
+ const CLEAR_WRAPPER_CLASS = "relative w-full";
11699
+ const CLEAR_BUTTON_CLASS = "absolute top-2 end-2 z-10 h-4 w-4 min-h-0 p-0.5";
11700
+ const CLEARABLE_FIELD_CLASS = "pe-6";
11125
11701
  // ── El `className` del consumidor: FUSIONADO y en la RAÍZ ────────────────────────────────────
11126
11702
  //
11127
11703
  // Mismo defecto doble que en `Input.tsx` (ver ahí el razonamiento completo y las medidas), porque
@@ -11164,7 +11740,10 @@ minRows, rows,
11164
11740
  // llegue al DOM (antes viajaba en `...rest` y React avisaba de un atributo desconocido).
11165
11741
  maxRows = DEFAULT_MAX_ROWS,
11166
11742
  // 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,
11743
+ disableAutosize = false, placeholder, isClearable: isClearableProp = false, onClear,
11744
+ // Sin efecto (ver `TextareaProps.labelPlacement`): fuera del spread para que no llegue
11745
+ // al `<textarea>` como atributo desconocido.
11746
+ labelPlacement: _labelPlacement, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
11168
11747
  // `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
11169
11748
  // (verificado en el paquete v2 compilado, `dist/components/input.js`:
11170
11749
  // `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
@@ -11172,7 +11751,7 @@ disableAutosize = false, placeholder, isInvalid = false, isRequired = false, isD
11172
11751
  // `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
11173
11752
  // de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
11174
11753
  // 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) => {
11754
+ fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, name, style, onKeyDown, ...rest }, ref) => {
11176
11755
  const generatedId = $8WNJc$react.useId();
11177
11756
  const textareaId = id || generatedId;
11178
11757
  const requestedRows = resolveRows(minRows, rows);
@@ -11209,6 +11788,46 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
11209
11788
  minHeight: `${fieldRows * SIZE_LINE_HEIGHT[size] + FIELD_PADDING_Y + FIELD_BORDER_Y}px`,
11210
11789
  ...style,
11211
11790
  };
11791
+ // v2: `isClearable = !!onClear || originalProps.isClearable`.
11792
+ const isClearable = isClearableProp || Boolean(onClear);
11793
+ // Limpiar en modo NO controlado tiene que poder vaciar el valor, y el estado interno del
11794
+ // `TextField` no se puede escribir desde fuera: en ese caso (y solo en ese) la fachada lleva
11795
+ // el valor en su propio estado, igual que `Input.tsx`. Sin `isClearable` nada cambia: el
11796
+ // valor no controlado sigue siendo del `TextField` (y su `reset`, de `useFormReset`).
11797
+ const isControlled = value !== undefined;
11798
+ const ownsValue = isClearable && !isControlled;
11799
+ const [ownValue, setOwnValue] = $8WNJc$react.useState(defaultValue ?? "");
11800
+ const currentValue = isControlled ? value : ownsValue ? ownValue : "";
11801
+ const showClear = isClearable && !isDisabled && !isReadOnly && Boolean(currentValue);
11802
+ // Orden de v2 (`handleClear`): el valor a "" —que en v2 disparaba `onValueChange`—, luego
11803
+ // `onClear`. El `onChange` recibe un evento de valor vacío, como en `Input.tsx`, para que un
11804
+ // consumidor que solo escucha `onChange` (p. ej. `field.onChange` de react-hook-form) se entere.
11805
+ const clearValue = () => {
11806
+ if (ownsValue) {
11807
+ setOwnValue("");
11808
+ }
11809
+ onValueChange?.("");
11810
+ if (onChange) {
11811
+ const emptyEvent = {
11812
+ target: { value: "" },
11813
+ currentTarget: { value: "" },
11814
+ };
11815
+ onChange(emptyEvent);
11816
+ }
11817
+ onClear?.();
11818
+ };
11819
+ const handleClear = () => {
11820
+ clearValue();
11821
+ innerRef.current?.focus();
11822
+ };
11823
+ // Escape limpia, igual que v2 (con valor, limpiable y no de solo lectura). El `onKeyDown` del
11824
+ // consumidor se llama primero.
11825
+ const handleKeyDown = (event) => {
11826
+ onKeyDown?.(event);
11827
+ if (event.key === "Escape" && showClear) {
11828
+ clearValue();
11829
+ }
11830
+ };
11212
11831
  // El estado del valor lo actualiza el `TextField` con su propio `onChange` (llega al
11213
11832
  // `<textarea>` por contexto y `mergeProps` encadena los dos). Aquí: medir —el nodo ya trae
11214
11833
  // el valor nuevo, y en modo no controlado la fachada no se re-renderiza— y avisar.
@@ -11217,6 +11836,10 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
11217
11836
  onChange?.(event);
11218
11837
  onValueChange?.(event.target.value);
11219
11838
  };
11839
+ const field = (jsxRuntime.jsx(react.TextArea, { ...rest, ref: setRefs, rows: fieldRows, style: fieldStyle, placeholder: placeholder, onChange: handleChange, onKeyDown: handleKeyDown, className: react.cn(SIZE_TEXT_CLASS[size], RESIZE_CLASS, RADIUS_CLASS$2[radius], VARIANT_CLASS[variant], isInvalid ? INVALID_TEXT_CLASS : "", FIELD_FOCUS_CLASS, FIELD_SHADOW_CLASS,
11840
+ // Incondicional, como el slot `input` de v2: el campo llena siempre su raíz,
11841
+ // y es la raíz la que responde a `fullWidth` y al ancho del consumidor.
11842
+ "w-full", isClearable ? CLEARABLE_FIELD_CLASS : "", classNames?.input ?? classNames?.inputWrapper) }));
11220
11843
  return (
11221
11844
  // ── `value` / `defaultValue` van al `TextField`, NO al `<textarea>` ─────────────────────
11222
11845
  //
@@ -11231,13 +11854,12 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
11231
11854
  // pero el estado interno del `TextField` seguía en "".
11232
11855
  // Ahora el `TextField` tiene el valor en los dos modos, y el `reset` del formulario también
11233
11856
  // es suyo (`useFormReset` devuelve el estado al `defaultValue`; el autosize lo escucha).
11234
- jsxRuntime.jsxs(react.TextField, { id: textareaId, value: value, defaultValue: defaultValue, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: react.cn(FIELD_STACK_CLASS, fullWidth ? "w-full" : "",
11857
+ jsxRuntime.jsxs(react.TextField, { id: textareaId, value: ownsValue ? ownValue : value, defaultValue: ownsValue ? undefined : defaultValue,
11858
+ // Solo cuando la fachada es dueña del valor (limpiable y no controlado).
11859
+ onChange: ownsValue ? setOwnValue : undefined, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: react.cn(FIELD_STACK_CLASS, fullWidth ? "w-full" : "",
11235
11860
  // El `className` del consumidor, en la raíz y en último lugar: el slot `base` de
11236
11861
  // v2 y la posición que le deja ganar en tailwind-merge (ver bloque de arriba).
11237
- className), children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS, isInvalid ? INVALID_LABEL_CLASS : "", classNames?.label), children: label })), jsxRuntime.jsx(react.TextArea, { ...rest, ref: setRefs, rows: fieldRows, style: fieldStyle, placeholder: placeholder, onChange: handleChange, className: react.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 && (jsxRuntime.jsx(react.Description, { className: react.cn(HELPER_CLASS, "text-left", classNames?.description), children: description })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: react.cn(HELPER_CLASS, "text-left", classNames?.errorMessage), children: errorMessage }))] }));
11862
+ className), children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS, isInvalid ? INVALID_LABEL_CLASS : "", classNames?.label), children: label })), isClearable ? (jsxRuntime.jsxs("div", { className: CLEAR_WRAPPER_CLASS, children: [field, showClear && (jsxRuntime.jsx(react.CloseButton, { "aria-label": "Limpiar", className: react.cn(CLEAR_BUTTON_CLASS, classNames?.clearButton), onPress: handleClear }))] })) : (field), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: react.cn(HELPER_CLASS, "text-left", classNames?.description), children: description })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: react.cn(HELPER_CLASS, "text-left", classNames?.errorMessage), children: errorMessage }))] }));
11241
11863
  });
11242
11864
  Textarea.displayName = "Textarea";
11243
11865
 
@@ -12990,8 +13612,8 @@ const DrawerFiltersFooter = ({ onClearFilters, onCancel, onApplyFilters, transla
12990
13612
  /**
12991
13613
  * Header of the DrawerFilters with title and description.
12992
13614
  */
12993
- const DrawerFiltersHeader = ({ title, description, }) => {
12994
- return (jsxRuntime.jsxs("div", { className: "px-6 pt-10", children: [jsxRuntime.jsx(H2, { className: "text-xl font-semibold text-foreground mb-2", children: title }), jsxRuntime.jsx(P, { className: "text-default-500", children: description })] }));
13615
+ const DrawerFiltersHeader = ({ titleId, title, description, }) => {
13616
+ return (jsxRuntime.jsxs("div", { className: "px-6 pt-10", children: [jsxRuntime.jsx(H2, { id: titleId, className: "text-xl font-semibold text-foreground mb-2", children: title }), jsxRuntime.jsx(P, { className: "text-default-500", children: description })] }));
12995
13617
  };
12996
13618
 
12997
13619
  const NO_YEAR_CLASS = "[&_[data-type='year']]:hidden [&_[data-type='literal']:has(+[data-type='year'])]:hidden";
@@ -13020,7 +13642,7 @@ function readConfirmedMonthDay(container, monthSelector = '[data-type="month"]',
13020
13642
  return { month, day };
13021
13643
  return null;
13022
13644
  }
13023
- const DayMonthPicker = ({ value, onChange, translations, }) => {
13645
+ const DayMonthPicker = ({ value, onChange, translations, ariaLabelledBy, }) => {
13024
13646
  const t = {
13025
13647
  dayMonthAriaLabel: "Seleccionar mes y día",
13026
13648
  dayMonthRangeAriaLabel: "Seleccionar rango de mes y día",
@@ -13096,7 +13718,7 @@ const DayMonthPicker = ({ value, onChange, translations, }) => {
13096
13718
  });
13097
13719
  }
13098
13720
  };
13099
- return (jsxRuntime.jsxs("div", { className: "w-full flex flex-wrap xs:gap-5 justify-between items-center", children: [jsxRuntime.jsxs(react.RadioGroup, { orientation: "horizontal", value: mode, onChange: handleModeChange, children: [jsxRuntime.jsx(react.Radio, { value: "single", children: jsxRuntime.jsxs(react.Radio.Content, { children: [jsxRuntime.jsx(react.Radio.Control, { children: jsxRuntime.jsx(react.Radio.Indicator, {}) }), t.dayMonthSingleOption] }) }), jsxRuntime.jsx(react.Radio, { value: "range", children: jsxRuntime.jsxs(react.Radio.Content, { children: [jsxRuntime.jsx(react.Radio.Control, { children: jsxRuntime.jsx(react.Radio.Indicator, {}) }), t.dayMonthRangeOption] }) })] }), mode === "single" ? (jsxRuntime.jsx("div", { ref: singleRef, children: jsxRuntime.jsx(DatePicker, { value: pickerValue, onChange: handleSingleChange, onFocusChange: handleSingleFocusChange, "aria-label": t.dayMonthAriaLabel, endContent: jsxRuntime.jsx(IconComponent, { icon: "solar:calendar-outline" }), className: `w-full md:w-auto md:max-w-40 ${NO_YEAR_CLASS}` }, "dmp") })) : (jsxRuntime.jsx("div", { ref: rangeRef, children: jsxRuntime.jsx(DateRangePicker, { value: rangeValue, onChange: handleRangeChange, onFocusChange: handleRangeFocusChange, "aria-label": t.dayMonthRangeAriaLabel, endContent: jsxRuntime.jsx(IconComponent, { icon: "solar:calendar-outline" }), className: `w-full md:w-auto md:max-w-xs ${NO_YEAR_CLASS}` }, "dmpr") }))] }));
13721
+ return (jsxRuntime.jsxs("div", { className: "w-full flex flex-wrap xs:gap-5 justify-between items-center", children: [jsxRuntime.jsxs(react.RadioGroup, { orientation: "horizontal", value: mode, onChange: handleModeChange, "aria-labelledby": ariaLabelledBy, children: [jsxRuntime.jsx(react.Radio, { value: "single", children: jsxRuntime.jsxs(react.Radio.Content, { children: [jsxRuntime.jsx(react.Radio.Control, { children: jsxRuntime.jsx(react.Radio.Indicator, {}) }), t.dayMonthSingleOption] }) }), jsxRuntime.jsx(react.Radio, { value: "range", children: jsxRuntime.jsxs(react.Radio.Content, { children: [jsxRuntime.jsx(react.Radio.Control, { children: jsxRuntime.jsx(react.Radio.Indicator, {}) }), t.dayMonthRangeOption] }) })] }), mode === "single" ? (jsxRuntime.jsx("div", { ref: singleRef, children: jsxRuntime.jsx(DatePicker, { value: pickerValue, onChange: handleSingleChange, onFocusChange: handleSingleFocusChange, "aria-label": t.dayMonthAriaLabel, endContent: jsxRuntime.jsx(IconComponent, { icon: "solar:calendar-outline" }), className: `w-full md:w-auto md:max-w-40 ${NO_YEAR_CLASS}` }, "dmp") })) : (jsxRuntime.jsx("div", { ref: rangeRef, children: jsxRuntime.jsx(DateRangePicker, { value: rangeValue, onChange: handleRangeChange, onFocusChange: handleRangeFocusChange, "aria-label": t.dayMonthRangeAriaLabel, endContent: jsxRuntime.jsx(IconComponent, { icon: "solar:calendar-outline" }), className: `w-full md:w-auto md:max-w-xs ${NO_YEAR_CLASS}` }, "dmpr") }))] }));
13100
13722
  };
13101
13723
 
13102
13724
  /**
@@ -13232,7 +13854,8 @@ const RangeFilter = ({ title, description, value, onChange, minValue, maxValue,
13232
13854
  const [min, max] = newValue;
13233
13855
  setCurrentValue([min, max]);
13234
13856
  onChange([min, max]);
13235
- }, className: "w-full", isDisabled: false, ...restSliderProps, children: jsxRuntime.jsxs(react.Slider.Track, { children: [jsxRuntime.jsx(react.Slider.Fill, { className: fillClassName }), stepMarks.length > 0 ? (jsxRuntime.jsx(react.Slider.Marks, { children: stepMarks.map((left) => (jsxRuntime.jsx("span", { className: SLIDER_STEP_CLASS, style: { left: `${left}%` } }, left))) })) : null, jsxRuntime.jsx(react.Slider.Thumb, { index: 0, "aria-label": t.minLabel, className: thumbClassName }), jsxRuntime.jsx(react.Slider.Thumb, { index: 1, "aria-label": t.maxLabel, className: thumbClassName })] }) }) }), jsxRuntime.jsxs("div", { className: "flex justify-between items-center text-sm", children: [jsxRuntime.jsxs("div", { className: "flex flex-col items-start", children: [jsxRuntime.jsx("span", { className: "text-xs text-default-500", children: t.minLabel }), jsxRuntime.jsx("span", { className: "font-medium text-default-700", children: formatValue(currentValue[0]) })] }), jsxRuntime.jsxs("div", { className: "flex flex-col items-end", children: [jsxRuntime.jsx("span", { className: "text-xs text-default-500", children: t.maxLabel }), jsxRuntime.jsx("span", { className: "font-medium text-default-700", children: formatValue(currentValue[1]) })] })] })] }));
13857
+ }, className: "w-full", isDisabled: false, "aria-label": restSliderProps["aria-label"] ??
13858
+ (restSliderProps["aria-labelledby"] ? undefined : t.title), ...restSliderProps, children: jsxRuntime.jsxs(react.Slider.Track, { children: [jsxRuntime.jsx(react.Slider.Fill, { className: fillClassName }), stepMarks.length > 0 ? (jsxRuntime.jsx(react.Slider.Marks, { children: stepMarks.map((left) => (jsxRuntime.jsx("span", { className: SLIDER_STEP_CLASS, style: { left: `${left}%` } }, left))) })) : null, jsxRuntime.jsx(react.Slider.Thumb, { index: 0, "aria-label": t.minLabel, className: thumbClassName }), jsxRuntime.jsx(react.Slider.Thumb, { index: 1, "aria-label": t.maxLabel, className: thumbClassName })] }) }) }), jsxRuntime.jsxs("div", { className: "flex justify-between items-center text-sm", children: [jsxRuntime.jsxs("div", { className: "flex flex-col items-start", children: [jsxRuntime.jsx("span", { className: "text-xs text-default-500", children: t.minLabel }), jsxRuntime.jsx("span", { className: "font-medium text-default-700", children: formatValue(currentValue[0]) })] }), jsxRuntime.jsxs("div", { className: "flex flex-col items-end", children: [jsxRuntime.jsx("span", { className: "text-xs text-default-500", children: t.maxLabel }), jsxRuntime.jsx("span", { className: "font-medium text-default-700", children: formatValue(currentValue[1]) })] })] })] }));
13236
13859
  };
13237
13860
 
13238
13861
  /**
@@ -13271,6 +13894,13 @@ const resolveSelectPlaceholder = (translations, title) => (translations?.selectP
13271
13894
  // =========================================================================
13272
13895
  const FilterSection = ({ filter, translations, value, onChange, isExpanded = false, onToggleExpanded, }) => {
13273
13896
  const isDisabled = filter.disabled === true;
13897
+ // TECH-2459 · El título visible de la sección es el NOMBRE ACCESIBLE de su control. Sin él,
13898
+ // react-aria (`useLabel`, vía `useSelect`/`useSlider`/`useListBox`) avisaba en dev «If you do
13899
+ // not provide a visible label, you must specify an aria-label or aria-labelledby» cada vez que
13900
+ // se desplegaba una sección (medido por beweossmbs en el drawer del catálogo con la .141), y el
13901
+ // lector de pantalla anunciaba solo el placeholder («Seleccionar categoría»). El control se
13902
+ // asocia por `aria-labelledby` a este id: sin cambio visual.
13903
+ const titleId = $8WNJc$react.useId();
13274
13904
  const [internalExpanded, setInternalExpanded] = $8WNJc$react.useState(isDisabled ? false : isExpanded);
13275
13905
  const handleToggleExpanded = () => {
13276
13906
  if (isDisabled)
@@ -13367,17 +13997,17 @@ const FilterSection = ({ filter, translations, value, onChange, isExpanded = fal
13367
13997
  const renderFilterContent = () => {
13368
13998
  switch (filter.type) {
13369
13999
  case "select":
13370
- return (jsxRuntime.jsx(SelectFilter, { filter: filter, value: value, onChange: onChange, translations: translations }));
14000
+ return (jsxRuntime.jsx(SelectFilter, { filter: filter, value: value, onChange: onChange, translations: translations, titleId: titleId }));
13371
14001
  case "multiselect":
13372
- return (jsxRuntime.jsx(MultiSelectFilter, { filter: filter, value: value, onChange: onChange, translations: translations }));
14002
+ return (jsxRuntime.jsx(MultiSelectFilter, { filter: filter, value: value, onChange: onChange, translations: translations, titleId: titleId }));
13373
14003
  case "range":
13374
- return (jsxRuntime.jsx(RangeFilterComponent, { filter: filter, value: value, onChange: onChange }));
14004
+ return (jsxRuntime.jsx(RangeFilterComponent, { filter: filter, value: value, onChange: onChange, titleId: titleId }));
13375
14005
  case "date":
13376
- return (jsxRuntime.jsx(DateFilter, { filter: filter, value: value, onChange: onChange, translations: translations }));
14006
+ return (jsxRuntime.jsx(DateFilter, { filter: filter, value: value, onChange: onChange, translations: translations, titleId: titleId }));
13377
14007
  case "text":
13378
- return (jsxRuntime.jsx(TextFilter, { filter: filter, value: value, onChange: onChange }));
14008
+ return (jsxRuntime.jsx(TextFilter, { filter: filter, value: value, onChange: onChange, titleId: titleId }));
13379
14009
  case "number":
13380
- return (jsxRuntime.jsx(NumberFilter, { filter: filter, value: value, onChange: onChange }));
14010
+ return (jsxRuntime.jsx(NumberFilter, { filter: filter, value: value, onChange: onChange, titleId: titleId }));
13381
14011
  default:
13382
14012
  return null;
13383
14013
  }
@@ -13387,7 +14017,7 @@ const FilterSection = ({ filter, translations, value, onChange, isExpanded = fal
13387
14017
  boxShadow: "var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)",
13388
14018
  "--tw-shadow": "var(--aurora-shadow-medium)",
13389
14019
  "--tw-shadow-colored": "var(--aurora-shadow-medium)",
13390
- }, children: [jsxRuntime.jsxs("button", { type: "button", className: react.cn("flex items-center justify-between w-full text-left p-4", isDisabled && "cursor-not-allowed"), onClick: handleToggleExpanded, children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [jsxRuntime.jsx(H2, { className: "text-base font-normal text-foreground", children: filter.title }), !isDisabled && hasActiveFilter() && (jsxRuntime.jsx("span", { className: "text-xs px-2 py-1 rounded-full text-black dark:text-white", style: {
14020
+ }, children: [jsxRuntime.jsxs("button", { type: "button", className: react.cn("flex items-center justify-between w-full text-left p-4", isDisabled && "cursor-not-allowed"), onClick: handleToggleExpanded, children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [jsxRuntime.jsx(H2, { id: titleId, className: "text-base font-normal text-foreground", children: filter.title }), !isDisabled && hasActiveFilter() && (jsxRuntime.jsx("span", { className: "text-xs px-2 py-1 rounded-full text-black dark:text-white", style: {
13391
14021
  backgroundColor: "var(--color-default)",
13392
14022
  }, children: getActiveFilterCount() }))] }), jsxRuntime.jsx(IconComponent, { icon: internalExpanded
13393
14023
  ? "solar:alt-arrow-down-outline"
@@ -13396,10 +14026,10 @@ const FilterSection = ({ filter, translations, value, onChange, isExpanded = fal
13396
14026
  // =========================================================================
13397
14027
  // SELECT FILTER
13398
14028
  // =========================================================================
13399
- const SelectFilter = ({ filter, value, onChange, translations }) => {
14029
+ const SelectFilter = ({ filter, value, onChange, translations, titleId }) => {
13400
14030
  const currentValue = value?.value || "";
13401
14031
  const triggerPlaceholder = resolveSelectPlaceholder(translations, filter.title);
13402
- return (jsxRuntime.jsx(Select, { placeholder: triggerPlaceholder, selectedKeys: new Set(currentValue ? [currentValue] : []), onSelectionChange: (keys) => {
14032
+ return (jsxRuntime.jsx(Select, { "aria-labelledby": titleId, placeholder: triggerPlaceholder, selectedKeys: new Set(currentValue ? [currentValue] : []), onSelectionChange: (keys) => {
13403
14033
  const selectedKey = Array.from(keys)[0];
13404
14034
  onChange({ value: selectedKey || "" });
13405
14035
  }, className: "w-full", children: filter.data.map((item) => (
@@ -13413,7 +14043,8 @@ const SelectFilter = ({ filter, value, onChange, translations }) => {
13413
14043
  // =========================================================================
13414
14044
  // MULTISELECT FILTER
13415
14045
  // =========================================================================
13416
- const MultiSelectFilter = ({ filter, value, onChange, translations }) => {
14046
+ const MultiSelectFilter = ({ filter, value, onChange, translations, titleId }) => {
14047
+ const valueId = $8WNJc$react.useId();
13417
14048
  const triggerRef = $8WNJc$react.useRef(null);
13418
14049
  const [triggerWidthPx, setTriggerWidthPx] = $8WNJc$react.useState(null);
13419
14050
  const [isOpen, setIsOpen] = $8WNJc$react.useState(false);
@@ -13507,11 +14138,11 @@ const MultiSelectFilter = ({ filter, value, onChange, translations }) => {
13507
14138
  maxWidth: Math.max(triggerWidthPx, 220),
13508
14139
  }
13509
14140
  : undefined;
13510
- return (jsxRuntime.jsxs(react.Popover, { isOpen: isOpen, onOpenChange: handleOpenChange, children: [jsxRuntime.jsx(react.PopoverTrigger, { children: jsxRuntime.jsxs("button", { ref: triggerRef, type: "button", className: react.cn(
14141
+ return (jsxRuntime.jsxs(react.Popover, { isOpen: isOpen, onOpenChange: handleOpenChange, children: [jsxRuntime.jsx(react.PopoverTrigger, { "aria-labelledby": titleId, "aria-describedby": valueId, children: jsxRuntime.jsxs("button", { ref: triggerRef, type: "button", "aria-labelledby": titleId, "aria-describedby": valueId, className: react.cn(
13511
14142
  // TECH-2459 · `bg-transparent` y no `bg-background`: este disparador tiene pinta de campo y vive
13512
14143
  // DENTRO de un drawer, o sea el escenario del relleno negro sobre tarjeta gris. En oscuro
13513
14144
  // `--color-background` es negro puro, y el `bordered` de v2 no llevaba ninguna clase `bg-*`.
13514
- "flex w-full min-h-12 items-center justify-between gap-2 rounded-[12px] border border-default-200 bg-transparent px-3 py-2.5 text-left text-sm transition-colors", "hover:border-default-300 focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background", isOpen && "border-primary"), "aria-expanded": isOpen, "aria-haspopup": "dialog", children: [jsxRuntime.jsx("span", { className: "line-clamp-2 min-w-0 flex-1 text-sm font-medium leading-snug text-foreground", title: triggerSelectionTitle, children: triggerSelection.visible.length > 0 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [triggerSelection.visible.map((item, index) => (jsxRuntime.jsxs($8WNJc$react.Fragment, { children: [index > 0 ? (jsxRuntime.jsx("span", { className: "font-normal text-default-400", children: ", " })) : null, jsxRuntime.jsx("span", { children: item.label })] }, item.value))), triggerSelection.overflow > 0 ? (jsxRuntime.jsxs("span", { className: "font-normal text-default-500", children: [" ", moreLabelTemplate.replace("{more}", String(triggerSelection.overflow))] })) : null] })) : (jsxRuntime.jsx("span", { className: "font-normal text-default-400", children: triggerPlaceholder })) }), jsxRuntime.jsx(IconComponent, { icon: isOpen
14145
+ "flex w-full min-h-12 items-center justify-between gap-2 rounded-[12px] border border-default-200 bg-transparent px-3 py-2.5 text-left text-sm transition-colors", "hover:border-default-300 focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background", isOpen && "border-primary"), "aria-expanded": isOpen, "aria-haspopup": "dialog", children: [jsxRuntime.jsx("span", { id: valueId, className: "line-clamp-2 min-w-0 flex-1 text-sm font-medium leading-snug text-foreground", title: triggerSelectionTitle, children: triggerSelection.visible.length > 0 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [triggerSelection.visible.map((item, index) => (jsxRuntime.jsxs($8WNJc$react.Fragment, { children: [index > 0 ? (jsxRuntime.jsx("span", { className: "font-normal text-default-400", children: ", " })) : null, jsxRuntime.jsx("span", { children: item.label })] }, item.value))), triggerSelection.overflow > 0 ? (jsxRuntime.jsxs("span", { className: "font-normal text-default-500", children: [" ", moreLabelTemplate.replace("{more}", String(triggerSelection.overflow))] })) : null] })) : (jsxRuntime.jsx("span", { className: "font-normal text-default-400", children: triggerPlaceholder })) }), jsxRuntime.jsx(IconComponent, { icon: isOpen
13515
14146
  ? "solar:alt-arrow-up-outline"
13516
14147
  : "solar:alt-arrow-down-outline", className: "shrink-0 text-default-400", size: "sm", "aria-hidden": true })] }) }), jsxRuntime.jsx(react.PopoverContent, { placement: "bottom start", offset: 6, className: "w-full min-w-0 max-w-none box-border border border-default-200 bg-content1 p-0 shadow-medium", style: popoverWidthStyle, children: jsxRuntime.jsxs("div", { className: "flex w-full flex-col gap-0 p-2", children: [searchable && (jsxRuntime.jsx("div", { className: "border-b border-default-100 px-1 pb-2", children: jsxRuntime.jsx(Input, { type: "search", placeholder: searchPlaceholder, value: searchQuery, onValueChange: setSearchQuery, className: "w-full", size: "sm", variant: "flat", radius: "full",
13517
14148
  // `InputClassNames` de G1-forms ya no tiene slot `base` (v3 no
@@ -13525,7 +14156,7 @@ const MultiSelectFilter = ({ filter, value, onChange, translations }) => {
13525
14156
  // =========================================================================
13526
14157
  // RANGE FILTER
13527
14158
  // =========================================================================
13528
- const RangeFilterComponent = ({ filter, value, onChange }) => {
14159
+ const RangeFilterComponent = ({ filter, value, onChange, titleId }) => {
13529
14160
  const currentValue = value || { min: filter.data.min, max: filter.data.max };
13530
14161
  const rangeValue = [currentValue.min, currentValue.max];
13531
14162
  const handleChange = (newValue) => {
@@ -13540,12 +14171,12 @@ const RangeFilterComponent = ({ filter, value, onChange }) => {
13540
14171
  const data = Array.from({
13541
14172
  length: Math.ceil((filter.data.max - filter.data.min) / (filter.data.step || 1)) + 1,
13542
14173
  }, (_, i) => filter.data.min + i * (filter.data.step || 1));
13543
- return (jsxRuntime.jsx(RangeFilter, { title: `Rango de ${filter.title.toLowerCase()}`, minValue: filter.data.min, maxValue: filter.data.max, step: filter.data.step || 1, data: data, value: rangeValue, onChange: handleChange, formatValue: formatter, className: "w-full", showHistogramCount: filter.data.showHistogramCount }));
14174
+ return (jsxRuntime.jsx(RangeFilter, { title: `Rango de ${filter.title.toLowerCase()}`, minValue: filter.data.min, maxValue: filter.data.max, step: filter.data.step || 1, data: data, value: rangeValue, onChange: handleChange, formatValue: formatter, className: "w-full", showHistogramCount: filter.data.showHistogramCount, sliderProps: { "aria-labelledby": titleId } }));
13544
14175
  };
13545
14176
  // =========================================================================
13546
14177
  // DATE FILTER
13547
14178
  // =========================================================================
13548
- const DateFilter = ({ filter, value, onChange, translations }) => {
14179
+ const DateFilter = ({ filter, value, onChange, translations, titleId }) => {
13549
14180
  if (filter.granularity === "dayMonth") {
13550
14181
  const rawDayMonth = value?.type === "dayMonth" && value.dayMonth && typeof value.dayMonth !== "string"
13551
14182
  ? value.dayMonth
@@ -13564,7 +14195,7 @@ const DateFilter = ({ filter, value, onChange, translations }) => {
13564
14195
  dayMonthRange: value.type === "dayMonthRange" ? rawDayMonthRange : null,
13565
14196
  }
13566
14197
  : null;
13567
- return (jsxRuntime.jsx(DayMonthPicker, { value: dayMonthValue, onChange: (v) => onChange({ date: null, dateRange: null, ...v }), translations: translations }));
14198
+ return (jsxRuntime.jsx(DayMonthPicker, { value: dayMonthValue, onChange: (v) => onChange({ date: null, dateRange: null, ...v }), translations: translations, ariaLabelledBy: titleId }));
13568
14199
  }
13569
14200
  const handleDateSelectorChange = (dateSelectorValue) => {
13570
14201
  onChange({
@@ -13636,23 +14267,25 @@ const DateFilter = ({ filter, value, onChange, translations }) => {
13636
14267
  dateSelectorTranslations.selectDateRangeAriaLabel =
13637
14268
  translations.selectDateRangeAriaLabel;
13638
14269
  }
13639
- return (jsxRuntime.jsx(DateSelector, { initialType: getInitialType(), initialDate: getInitialDate(), initialDateRange: getInitialDateRange(), onChange: handleDateSelectorChange, translations: dateSelectorTranslations, className: "w-full" }));
14270
+ return (jsxRuntime.jsx(DateSelector, { initialType: getInitialType(), initialDate: getInitialDate(), initialDateRange: getInitialDateRange(), onChange: handleDateSelectorChange, translations: dateSelectorTranslations,
14271
+ // El grupo «Día / Rango» se nombra con el título de la sección (ver `titleId`).
14272
+ radioGroupProps: { "aria-labelledby": titleId }, className: "w-full" }));
13640
14273
  };
13641
14274
  // =========================================================================
13642
14275
  // TEXT FILTER
13643
14276
  // =========================================================================
13644
- const TextFilter = ({ filter, value, onChange }) => {
14277
+ const TextFilter = ({ filter, value, onChange, titleId }) => {
13645
14278
  const currentValue = value?.value || "";
13646
- return (jsxRuntime.jsx(Input, { type: "text", placeholder: filter.data.placeholder || `Ingrese ${filter.title.toLowerCase()}`, value: currentValue, onValueChange: (newValue) => {
14279
+ return (jsxRuntime.jsx(Input, { "aria-labelledby": titleId, type: "text", placeholder: filter.data.placeholder || `Ingrese ${filter.title.toLowerCase()}`, value: currentValue, onValueChange: (newValue) => {
13647
14280
  onChange({ value: newValue });
13648
14281
  }, className: "w-full" }));
13649
14282
  };
13650
14283
  // =========================================================================
13651
14284
  // NUMBER FILTER
13652
14285
  // =========================================================================
13653
- const NumberFilter = ({ filter, value, onChange }) => {
14286
+ const NumberFilter = ({ filter, value, onChange, titleId }) => {
13654
14287
  const currentValue = value?.value?.toString() || "";
13655
- return (jsxRuntime.jsx(Input, { type: "number", placeholder: filter.data.placeholder || `Ingrese ${filter.title.toLowerCase()}`, value: currentValue, onValueChange: (newValue) => {
14288
+ return (jsxRuntime.jsx(Input, { "aria-labelledby": titleId, type: "number", placeholder: filter.data.placeholder || `Ingrese ${filter.title.toLowerCase()}`, value: currentValue, onValueChange: (newValue) => {
13656
14289
  const numValue = newValue === "" ? null : Number(newValue);
13657
14290
  onChange({ value: numValue });
13658
14291
  }, min: filter.data.min, max: filter.data.max, step: filter.data.step, className: "w-full" }));
@@ -13670,6 +14303,11 @@ const NumberFilter = ({ filter, value, onChange }) => {
13670
14303
  */
13671
14304
  const DrawerFilters = ({ isOpen, onOpenChange, config, onApplyFilters, onClearFilters, onCancel, className, translations, initialValues, }) => {
13672
14305
  const [filterValues, setFilterValues] = $8WNJc$react.useState(initialValues ?? {});
14306
+ // Nombre accesible del panel: su título. Con `Drawer.Root`, el `DialogTrigger` nombraba el
14307
+ // diálogo con el id de su disparador (que aquí no existe); sin root nadie lo nombra y
14308
+ // react-aria avisa en cada apertura («A dialog must have a title for accessibility», medido en
14309
+ // jsdom). Mismo arreglo que `headerId` en la fachada `Drawer`.
14310
+ const titleId = $8WNJc$react.useId();
13673
14311
  // Handle changes in individual filters
13674
14312
  const handleFilterChange = $8WNJc$react.useCallback((filterKey, value) => {
13675
14313
  setFilterValues((prev) => ({
@@ -13782,9 +14420,16 @@ const DrawerFilters = ({ isOpen, onOpenChange, config, onApplyFilters, onClearFi
13782
14420
  onApplyFilters(processedFilters);
13783
14421
  onOpenChange(false);
13784
14422
  }, [filterValues, config.data, onApplyFilters, onOpenChange]);
13785
- return (jsxRuntime.jsx(react.Drawer, { children: jsxRuntime.jsx(react.Drawer.Backdrop, { isOpen: isOpen, onOpenChange: handleOpenChange, isDismissable: false, children: jsxRuntime.jsx(react.Drawer.Content, { placement: "right", className: react.cn("drawer-filters", className), children: jsxRuntime.jsxs(react.Drawer.Dialog, { className: "w-full p-0 sm:max-w-md", children: [jsxRuntime.jsx(react.Drawer.CloseTrigger, {}), jsxRuntime.jsx(DrawerFiltersHeader, { title: config.title, description: config.description }), jsxRuntime.jsx(react.Drawer.Body, { className: "px-6 py-10", children: jsxRuntime.jsx("div", { className: "space-y-3", children: config.data.map((filter) => {
13786
- return (jsxRuntime.jsx(FilterSection, { filter: filter, translations: translations, value: filterValues[filter.key], onChange: (value) => handleFilterChange(filter.key, value) }, filter.key));
13787
- }) }) }), jsxRuntime.jsx(DrawerFiltersFooter, { onClearFilters: handleClearFilters, onCancel: handleCancel, onApplyFilters: handleApplyFilters, translations: translations })] }) }) }) }));
14423
+ // Sin `Drawer.Root` (el `DialogTrigger` de react-aria) a propósito, igual que la fachada
14424
+ // `Drawer` (`src/components/drawer/Drawer.tsx`): aquí no hay disparador —lo abre el `isOpen`
14425
+ // de la app— y un `DialogTrigger` sin hijo pulsable avisa en consola en CADA montaje, abierto o
14426
+ // cerrado («A PressResponder was rendered without a pressable child», medido en jsdom y por
14427
+ // beweossmbs con la .140). `Drawer.Backdrop` (`ModalOverlay`) ya crea su propio estado de
14428
+ // overlay con `isOpen`/`onOpenChange` y calcula todos los slots de `drawerVariants()`, así que
14429
+ // el root no aportaba nada.
14430
+ return (jsxRuntime.jsx(react.Drawer.Backdrop, { isOpen: isOpen, onOpenChange: handleOpenChange, isDismissable: false, children: jsxRuntime.jsx(react.Drawer.Content, { placement: "right", className: react.cn("drawer-filters", className), children: jsxRuntime.jsxs(react.Drawer.Dialog, { "aria-labelledby": titleId, className: "w-full p-0 sm:max-w-md", children: [jsxRuntime.jsx(react.Drawer.CloseTrigger, {}), jsxRuntime.jsx(DrawerFiltersHeader, { titleId: titleId, title: config.title, description: config.description }), jsxRuntime.jsx(react.Drawer.Body, { className: "px-6 py-10", children: jsxRuntime.jsx("div", { className: "space-y-3", children: config.data.map((filter) => {
14431
+ return (jsxRuntime.jsx(FilterSection, { filter: filter, translations: translations, value: filterValues[filter.key], onChange: (value) => handleFilterChange(filter.key, value) }, filter.key));
14432
+ }) }) }), jsxRuntime.jsx(DrawerFiltersFooter, { onClearFilters: handleClearFilters, onCancel: handleCancel, onApplyFilters: handleApplyFilters, translations: translations })] }) }) }));
13788
14433
  };
13789
14434
  DrawerFilters.displayName = "DrawerFilters";
13790
14435
 
@@ -15927,13 +16572,19 @@ ModalBody.displayName = "ModalBody";
15927
16572
  * v3 conserva `flex-row justify-end gap-2` pero pierde el padding, y añade un
15928
16573
  * `.modal__body + .modal__footer { mt-5 }` que v2 no tenía (v2 separaba los slots solo
15929
16574
  * con su propio padding) — de ahí el `mt-0`.
16575
+ *
16576
+ * `items-stretch`: v2 no declaraba `items-*` (`align-items: normal`, que en flex estira), y v3
16577
+ * pone `items-center`. En un footer en columna (`className="flex flex-col"`, «Register payment»
16578
+ * de beweossmbs) eso encogía cada fila a su contenido: medido con la .141, Total 83px frente a
16579
+ * 464 y botones `flex-1` 108px frente a 226. En la fila por defecto no cambia nada visible
16580
+ * (los `Button` tienen alto propio). Un `items-*` de la app lo retira por fusión.
15930
16581
  */
15931
16582
  const ModalFooter = ({ className, ...props }) => {
15932
16583
  const { classNames } = $8WNJc$react.useContext(ModalFacadeContext);
15933
16584
  return (jsxRuntime.jsx(react.ModalFooter, { ...props,
15934
16585
  // v2 `classNames.footer` del `<Modal>` entre la fachada y el `className` propio, que
15935
16586
  // gana (`clsx(classNames.footer, className)` en `@heroui/modal@2.2.19`).
15936
- className: react.cn("mt-0 px-6 py-4", classNames?.footer, className) }));
16587
+ className: react.cn("mt-0 items-stretch px-6 py-4", classNames?.footer, className) }));
15937
16588
  };
15938
16589
  ModalFooter.displayName = "ModalFooter";
15939
16590
 
@@ -16030,7 +16681,8 @@ const DrawerFacade = ({ isOpen, defaultOpen, onOpenChange, onClose, placement =
16030
16681
  // Sin `Drawer.Root` (el `DialogTrigger` de react-aria) a propósito: la fachada no tiene
16031
16682
  // disparador propio —lo abre el `isOpen` de la app— y un `DialogTrigger` sin hijo pulsable
16032
16683
  // avisa en consola en CADA montaje («A PressResponder was rendered without a pressable
16033
- // child», medido en jsdom; `Modal` lo sigue emitiendo). `Drawer.Backdrop` (`ModalOverlay`)
16684
+ // child», medido en jsdom). `Modal` hace lo mismo desde 27958d2 (ya no monta `Modal.Root`) y
16685
+ // `Modal.v2.test.tsx` vigila 0 avisos con modales cerrados. `Drawer.Backdrop` (`ModalOverlay`)
16034
16686
  // ya crea y publica su propio estado de overlay con `isOpen`/`onOpenChange`, y trae todos los
16035
16687
  // slots de `drawerVariants()`, así que el root no aportaba nada.
16036
16688
  return (jsxRuntime.jsx(react.Drawer.Backdrop, { isOpen: isOpen, defaultOpen: defaultOpen, onOpenChange: handleOpenChange, variant: backdrop, isDismissable: isDismissable, isKeyboardDismissDisabled: isKeyboardDismissDisabled, children: jsxRuntime.jsx(react.Drawer.Content, { placement: placement, children: jsxRuntime.jsx(react.Drawer.Dialog, { "aria-labelledby": headerId, className: react.cn(sizeClass(size, placement), "p-0", scrollBehavior === "outside" && "overflow-y-auto", className), children: ({ close }) => (jsxRuntime.jsx(DrawerDialogBody, { close: close, headerId: headerId, scrollBehavior: scrollBehavior, hideCloseButton: hideCloseButton, children: children })) }) }) }));
@@ -16121,9 +16773,11 @@ DrawerBody.displayName = "Aurora.DrawerBody";
16121
16773
  * Geometría v2 del slot `footer`: `flex flex-row gap-2 px-6 py-4 justify-end`
16122
16774
  * (`@heroui/theme@2.4.15`, slot `footer`). v3 conserva `flex-row justify-end gap-2` pero pierde
16123
16775
  * el padding y añade `.drawer__body + .drawer__footer { mt-5 }`, que v2 no tenía — de ahí el
16124
- * `mt-0` (mismo arreglo que `ModalFooter`).
16776
+ * `mt-0` (mismo arreglo que `ModalFooter`). Y `items-stretch` en lugar del `items-center` de
16777
+ * v3: v2 no declaraba `items-*`, así que un footer en columna estiraba sus filas (en «Register
16778
+ * payment» de beweossmbs, Total 83px frente a 464 con la .141; detalle en `ModalFooter`).
16125
16779
  */
16126
- const DrawerFooter = ({ className, children }) => (jsxRuntime.jsx(react.DrawerFooter, { className: react.cn("mt-0 px-6 py-4", className), children: children }));
16780
+ const DrawerFooter = ({ className, children }) => (jsxRuntime.jsx(react.DrawerFooter, { className: react.cn("mt-0 items-stretch px-6 py-4", className), children: children }));
16127
16781
  DrawerFooter.displayName = "Aurora.DrawerFooter";
16128
16782
 
16129
16783
  // TECH-2459 — Fachada `Alert` (hallazgo loginos 07-sep: `index.tsx` reexportaba el `Alert` CRUDO de
@@ -16929,21 +17583,66 @@ function hasIndicator(children) {
16929
17583
  return node.type === $8WNJc$react.Fragment && hasIndicator(node.props.children);
16930
17584
  });
16931
17585
  }
17586
+ // ---------------------------------------------------------------------------------------------
17587
+ // `size` de v2 → medidas de pestaña de v2
17588
+ // ---------------------------------------------------------------------------------------------
17589
+ /**
17590
+ * Medidas de pestaña por `size`, **copiadas del oráculo** `@heroui/theme@2.4.15`
17591
+ * `dist/components/tabs.js` (slot `tab`, `px-3` en la base y `size.{sm,md,lg}.tab` para la altura y
17592
+ * el cuerpo), traducidas a clases de Tailwind 4 con el MISMO valor. Las `text-tiny` /
17593
+ * `text-small` / `text-medium` de v2 son del plugin `heroui()` y en TW4 no existen
17594
+ * (`default-layout.js:32-40`):
17595
+ *
17596
+ * | size | v2 | aquí | cuerpo / interlineado |
17597
+ * |------|------------------------|----------------|-----------------------|
17598
+ * | sm | `h-7 text-tiny` + px-3 | `h-7 px-3 text-xs` | 0.75rem / 1rem |
17599
+ * | md | `h-8 text-small` + px-3 | `h-8 px-3 text-sm` | 0.875rem / 1.25rem |
17600
+ * | lg | `h-9 text-medium` + px-3 | `h-9 px-3 text-base` | 1rem / 1.5rem |
17601
+ *
17602
+ * Por qué se mapea, cuando `radius` y `color` se ignoran (§9.1): `size` no es estética, es
17603
+ * TAMAÑO, y descartarlo provoca un desbordamiento medido. linda-business embebido en el host v1 con
17604
+ * la `.140`: con `size="sm"` descartado, el texto pasaba de 12 px a 14 px y cada pestaña salía de 6
17605
+ * a 17 px más ancha. El carril medía 352 px en una columna de 287 px y el chevron del scroller
17606
+ * tapaba la última pestaña. smbs tiene 10 `size="sm"`.
17607
+ *
17608
+ * Lo que NO se copia: el peso (v3 pone `font-medium`, v2 no ponía nada) y el radio (`rounded-small`),
17609
+ * que siguen como v3 por §9.1. Tampoco el `py-1` de v2: con altura fija y `items-center` no
17610
+ * cambia nada.
17611
+ *
17612
+ * Las clases van ANTES de `classNames.tab` y del `className` del `Tab` en un `cn`
17613
+ * (tailwind-merge): lo de la app gana. El `h-7 px-2` de linda-business sigue mandando sobre el
17614
+ * `px-3`. Clases literales en el fuente (no construidas), para que Tailwind las genere.
17615
+ */
17616
+ const TAB_SIZE_CLASS = {
17617
+ sm: "h-7 px-3 text-xs",
17618
+ md: "h-8 px-3 text-sm",
17619
+ lg: "h-9 px-3 text-base",
17620
+ };
17621
+ /**
17622
+ * El `size` del root llega a los `Tabs.Tab` de la forma v3 por contexto, porque esas pestañas las
17623
+ * escribe la app dentro de su `Tabs.List` y la fachada no las recorre. Sin `size` el valor es
17624
+ * `undefined` y la pestaña sale byte a byte como antes.
17625
+ */
17626
+ const TabSizeContext = $8WNJc$react.createContext(undefined);
16932
17627
  const TabFacade = ({ children: ownChildren, title,
16933
17628
  // `titleValue` es de v2 y no tiene destino en v3: se consume para que no llegue al DOM.
16934
17629
  titleValue: _titleValue, ...props }) => {
16935
17630
  // En la forma v3 la etiqueta son los `children`; si solo viene el `title` de v2 (un `Tab` con
16936
17631
  // `title` dentro de `Tabs.List`), hace de etiqueta. Con los dos, mandan los `children`.
16937
17632
  const children = ownChildren ?? title;
17633
+ const sizeClass = $8WNJc$react.useContext(TabSizeContext);
17634
+ const tabProps = sizeClass
17635
+ ? { ...props, className: react.cn(sizeClass, props.className) }
17636
+ : props;
16938
17637
  // `children` como función es la forma de render props de react-aria (`TabRenderProps`). No se
16939
17638
  // puede inspeccionar, y menos aún combinar con un hermano —React rechaza una función como
16940
17639
  // hijo junto a un elemento—, así que se pasa tal cual. Un consumidor que la use monta el
16941
17640
  // indicador él (es la vía de escape de este caso). Exposición hoy: 0 usos en los 6 repos.
16942
17641
  if (typeof children === "function") {
16943
- return jsxRuntime.jsx(react.Tabs.Tab, { ...props, children: children });
17642
+ return jsxRuntime.jsx(react.Tabs.Tab, { ...tabProps, children: children });
16944
17643
  }
16945
17644
  if (hasIndicator(children)) {
16946
- return jsxRuntime.jsx(react.Tabs.Tab, { ...props, children: children });
17645
+ return jsxRuntime.jsx(react.Tabs.Tab, { ...tabProps, children: children });
16947
17646
  }
16948
17647
  // El indicador va PRIMERO por legibilidad del marcado, no por efecto: es `position:absolute`
16949
17648
  // con `z-index:-1`, o sea fuera del flujo y detrás del texto, así que el orden no cambia el
@@ -16953,7 +17652,7 @@ titleValue: _titleValue, ...props }) => {
16953
17652
  // `{children}` se pasa SIN normalizar (nada de `Children.toArray`) para no reescribir las
16954
17653
  // keys del consumidor: hacerlo cambiaría la reconciliación de una lista de pestañas
16955
17654
  // condicional. Verificado en el navegador que la anidación no produce aviso de key.
16956
- return (jsxRuntime.jsxs(react.Tabs.Tab, { ...props, children: [jsxRuntime.jsx(react.Tabs.Indicator, {}), children] }));
17655
+ return (jsxRuntime.jsxs(react.Tabs.Tab, { ...tabProps, children: [jsxRuntime.jsx(react.Tabs.Indicator, {}), children] }));
16957
17656
  };
16958
17657
  TabFacade.displayName = "Aurora.Tab";
16959
17658
  /**
@@ -17064,8 +17763,10 @@ function hasPanelContent(children) {
17064
17763
  }
17065
17764
  const TabsFacade = (allProps) => {
17066
17765
  const { children, classNames, className, variant, fullWidth, items, isVertical, placement, orientation, destroyInactiveTabPanel = true,
17766
+ // v2: medidas de pestaña (ver `TAB_SIZE_CLASS`).
17767
+ size,
17067
17768
  // 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;
17769
+ radius: _radius, color: _color, disableAnimation: _disableAnimation, disableCursorAnimation: _disableCursorAnimation, shouldSelectOnPressUp: _shouldSelectOnPressUp, ...rest } = allProps;
17069
17770
  const v3Variant = toV3Variant(variant);
17070
17771
  const v3Orientation = orientation ??
17071
17772
  (isVertical || placement === "start" || placement === "end"
@@ -17126,7 +17827,7 @@ const TabsFacade = (allProps) => {
17126
17827
  // `.tabs[data-orientation="horizontal"] > .tabs__list-container { align-self: start }` de
17127
17828
  // neutralize, que no hace falta: dentro de una fila el carril ya mide su contenido.
17128
17829
  const orientationAttr = v3Orientation ?? "horizontal";
17129
- return (jsxRuntime.jsxs(react.Tabs, { ...rootProps, orientation: v3Orientation, variant: v3Variant, children: [jsxRuntime.jsx("div", { className: react.cn("flex", v3Variant === "secondary" && "tabs--secondary", fullWidth && "w-full", classNames?.base, className), "data-orientation": orientationAttr, "data-slot": "tabs-base", children: jsxRuntime.jsx(react.Tabs.ListContainer, { className: react.cn(fullWidth && "w-full"), children: jsxRuntime.jsx(react.Tabs.List, { "aria-describedby": ariaDescribedby, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className: react.cn(classNames?.tabList), children: tabs.map(({ id, title, tabClassName, tabProps }) => ($8WNJc$react.createElement(react.Tabs.Tab, { ...tabProps, className: react.cn(classNames?.tab, tabClassName), id: id, key: id },
17830
+ return (jsxRuntime.jsxs(react.Tabs, { ...rootProps, orientation: v3Orientation, variant: v3Variant, children: [jsxRuntime.jsx("div", { className: react.cn("flex", v3Variant === "secondary" && "tabs--secondary", fullWidth && "w-full", classNames?.base, className), "data-orientation": orientationAttr, "data-slot": "tabs-base", children: jsxRuntime.jsx(react.Tabs.ListContainer, { className: react.cn(fullWidth && "w-full"), children: jsxRuntime.jsx(react.Tabs.List, { "aria-describedby": ariaDescribedby, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className: react.cn(classNames?.tabList), children: tabs.map(({ id, title, tabClassName, tabProps }) => ($8WNJc$react.createElement(react.Tabs.Tab, { ...tabProps, className: react.cn(size && TAB_SIZE_CLASS[size], classNames?.tab, tabClassName), id: id, key: id },
17130
17831
  jsxRuntime.jsx(react.Tabs.Indicator, { className: react.cn(classNames?.cursor) }),
17131
17832
  classNames?.tabContent ? (jsxRuntime.jsx("span", { className: classNames.tabContent, children: title })) : (title)))) }) }) }), tabs
17132
17833
  .filter(({ panel }) => hasPanelContent(panel))
@@ -17145,7 +17846,9 @@ const TabsFacade = (allProps) => {
17145
17846
  orientation: v3Orientation,
17146
17847
  variant: v3Variant,
17147
17848
  };
17148
- return jsxRuntime.jsx(TabsV3, { ...v3Props, children: v3Children });
17849
+ const v3Tabs = jsxRuntime.jsx(TabsV3, { ...v3Props, children: v3Children });
17850
+ // Sin `size` no se monta el provider: el árbol queda idéntico al de antes.
17851
+ return size ? (jsxRuntime.jsx(TabSizeContext.Provider, { value: TAB_SIZE_CLASS[size], children: v3Tabs })) : (v3Tabs);
17149
17852
  };
17150
17853
  const TabsV3 = ({ children, ...props }) => {
17151
17854
  const nodes = $8WNJc$react.Children.toArray(children);